深入掌握POD
1. Pod 定義詳解
YAML格式的Pod定義文件的完整內容如下:
apiVersion: v1 # 必選,版本號,例如v1
kind: Pod # 必選,Pod
metadata: # 必選,元數據
name: string # 必選,Pod名稱
namespace: string # 必選,Pod所屬的命名空間
labels: # 自定義標簽
- name: string # 自定義標簽名字
annotations: # 自定義注釋列表
- name: string
spec: # 必選,Pod中容器的詳細定義
containers: # 必選,Pod中容器列表
- name: string # 必選,容器名稱
image: string # 必選,容器的鏡像名稱
imagePullPolicy: [Always | Never | IfNotPresent]# 鏡像拉取的策略,默認為Always:Alawys 表示每次都嘗試重新拉取鏡像;IfNotPresent 表示如果本地有該鏡像,則使用本地鏡像,本地不存在時拉取鏡像;Nerver表示僅使用本地鏡像
command: [string] # 容器的啟動命令列表,如不指定,使用打包時使用的啟動命令
args: [string] # 容器的啟動命令參數列表
workingDir: string # 容器的工作目錄
volumeMounts: # 掛載到容器內部的存儲卷配置
- name: string # 引用pod定義的共享存儲卷的名稱,需用volumes[]部分定義的的卷名
mountPath: string # 存儲卷在容器內mount的絕對路徑,應少於512字符
readOnly: boolean # 是否為只讀模式,默認為讀寫模式
ports: # 需要暴露的端口庫號列表
- name: string # 端口號名稱
containerPort: int # 容器需要監聽的端口號
hostPort: int # 容器所在主機需要監聽的端口號,默認與Container相同。
# 當設置hostPort時,同一台宿主機將無法啟動該容器的第二個副本
protocol: string # 端口協議,支持TCP和UDP,默認TCP
env: # 容器運行前需設置的環境變量列表
- name: string # 環境變量名稱
value: string # 環境變量的值
resources: # 資源限制和請求的設置
limits: # 資源限制的設置
cpu: string # Cpu的限制,單位為core數,將用於docker run --cpu-shares參數
memory: string # 內存限制,單位可以為Mib/Gib,將用於docker run --memory參數
requests: # 資源請求的設置
cpu: string # Cpu請求,容器啟動的初始可用數量
memory: string # 內存清楚,容器啟動的初始可用數量
livenessProbe:# 對Pod內個容器健康檢查的設置,當探測無響應幾次后將自動重啟該容器;檢查方法有exec、httpGet和tcpSocket,對一個容器只需設置其中一種方法即可
exec: # 對Pod容器內檢查方式設置為exec方式
command: [string] # exec方式需要制定的命令或腳本
httpGet: # 對Pod內個容器健康檢查方法設置為HttpGet,需要制定Path、port
path: string
port: number
host: string
scheme: string
HttpHeaders:
- name: string
value: string
tcpSocket: # 對Pod內個容器健康檢查方式設置為tcpSocket方式
port: number
initialDelaySeconds: 0 # 容器啟動完成后首次探測的時間,單位為秒
timeoutSeconds: 0 # 對容器健康檢查探測等待響應的超時時間,單位秒,默認1秒
periodSeconds: 0 # 對容器監控檢查的定期探測時間設置,單位秒,默認10秒一次
successThreshold: 0
failureThreshold: 0
securityContext:
privileged: false
restartPolicy: [Always | Never | OnFailure]# Pod的重啟策略: Always表示不管以何種方式終止運行,kubelet都將重啟;OnFailure表示只有Pod以非0退出碼退出才重啟; Nerver表示不再重啟該Pod
nodeSelector: obeject # 設置NodeSelector表示將該Pod調度到包含這個label的node上,以key:value的格式指定
imagePullSecrets: # Pull鏡像時使用的secret名稱,以key:secretkey格式指定
- name: string
hostNetwork: false # 是否使用主機網絡模式,默認為false,如果設置為true,表示使用宿主機網絡
volumes: # 在該pod上定義共享存儲卷列表
- name: string # 共享存儲卷名稱 (volumes類型有很多種)
emptyDir: {} # 類型為emtyDir的存儲卷,與Pod同生命周期的一個臨時目錄。為空值
hostPath: string # 類型為hostPath的存儲卷,表示掛載Pod所在宿主機的目錄
path: string # Pod所在宿主機的目錄,將被用於同期中mount的目錄
secret: # 類型為secret的存儲卷,掛載集群與定義的secre對象到容器內部
scretname: string
items:
- key: string
path: string
configMap: # 類型為configMap的存儲卷,掛載預定義的configMap對象到容器內部
name: string
items:
- key: string
path: string
Pod YAML定義文件詳解
2. Pod 的基本用法
Pod可以由1個或多個容器組合而成。舉例說明:
Pod由一個容器組成YAML文件:
以下文件啟動后將生成一個名為“frontend”的Pod,該Pod中包含一個容器。
# cat frontend-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: frontend
labels:
name: frontend
spec:
containers:
- name: frontend
image: ygego/php-frontend
env:
- name: GET_HOSTS_FROM
value: env
ports:
- containerPort: 80
另一個場景是:假如frontend 和 Redis 兩個容器應用為緊耦合的關系,並且組合成一個整體對外提供服務時,應該將這兩個容器打包為一個Pod,屬於同一個Pod的多個容器應用之間相互訪問時僅需要通過localhost就可以通信。如圖:
Pod由兩個個容器組成YAML文件:
# cat frontend-redis-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: php-redis
labels:
name: php-redis
spec:
containers:
- name: frontend
image: ygego/php-frontend
env:
- name: GET_HOSTS_FROM
value: env
ports:
- containerPort: 80
- name: redis
image: ygego/redis-master
ports:
- containerPort: 6379
使用Kubectl 創建該Pod:
# kubectl apply -f frontend-redis-pod.yaml
pod/php-redis created
查看創建好的Pod:
# kubectl get pods
NAME READY STATUS RESTARTS AGE
php-redis 2/2 running 0 1h
可以看到READY信息為2/2,表示Pod中的兩個容器都成功的運行起來了。
查看Pod的詳細信息:
可以看到兩個容器的定義以及創建的過程(Event事件信息)
# kubectl describe pod php-redis
Name: php-redis
Namespace: default
Priority: 0
Node: k8s-node03/10.4.148.228
Start Time: Tue, 07 Jan 2020 17:12:12 +0800
Labels: name=php-redis
Annotations: cni.projectcalico.org/podIP: 10.100.7.161/32
kubectl.kubernetes.io/last-applied-configuration:
{"apiVersion":"v1","kind":"Pod","metadata":{"annotations":{},"labels":{"name":"php-redis"},"name":"php-redis","namespace":"default"},"spec...
Status: Running
IP: 10.100.7.161
Containers:
frontend:
Container ID: docker://7b40ddbaed450da56f4ae9aeb7ddc46a1da0e5630327b41973718a075b805cd5
Image: ygego/php-frontend
Image ID: docker://sha256:d014f67384a11186e135b95a7ed0d794674f7ce258f0dce47267c3052a0d0fa9
Port: 80/TCP
Host Port: 0/TCP
State: Running
Started: Tue, 07 Jan 2020 17:12:15 +0800
Ready: True
Restart Count: 0
Environment:
GET_HOSTS_FROM: env
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from default-token-c6t97 (ro)
redis:
Container ID: docker://3483cbd386fa788302e6d6b1d6bace0233e8a972dfcc14b9a2bb131320d36737
Image: ygego/redis-master
Image ID: docker://sha256:405a0b586f7ebeb545ec65be0e914311159d1baedccd3a93e9d3e3b249ec5cbd
Port: 6379/TCP
Host Port: 0/TCP
State: Running
Started: Tue, 07 Jan 2020 17:12:16 +0800
Ready: True
Restart Count: 0
Environment: <none>
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from default-token-c6t97 (ro)
Conditions:
Type Status
Initialized True
Ready True
ContainersReady True
PodScheduled True
Volumes:
default-token-c6t97:
Type: Secret (a volume populated by a Secret)
SecretName: default-token-c6t97
Optional: false
QoS Class: BestEffort
Node-Selectors: <none>
Tolerations: node.kubernetes.io/not-ready:NoExecute for 300s
node.kubernetes.io/unreachable:NoExecute for 300s
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 17s default-scheduler Successfully assigned default/php-redis to k8s-node03
Normal Pulled 15s kubelet, k8s-node03 Container image "ygego/php-frontend" already present on machine
Normal Created 15s kubelet, k8s-node03 Created container frontend
Normal Started 14s kubelet, k8s-node03 Started container frontend
Normal Pulled 14s kubelet, k8s-node03 Container image "ygego/redis-master" already present on machine
Normal Created 14s kubelet, k8s-node03 Created container redis
Normal Started 13s kubelet, k8s-node03 Started container redis
3. Pod 容器共享Volume
同一個Pod中的多個容器能夠共享Pod級別的存儲卷Volume。Volume可以被定義為各種類型,多個容器各自進行掛載操作,將一個Volume掛載為容器內部需要的目錄,如圖所示:
如下圖所示的例子中,在Pod中包含兩個容器: Tomcat 和 busybox,在Pod級別設置Volume:“app-logs”,用於 Tomcat向其中寫入日志文件,busybox 讀取日志文件。
這里設置的Volume名為app-logs,類型為emptyDir,掛載在Tomcat容器內的/usr/local/tomcat/logs目錄,同時掛載到busybox容器內的/logs目錄。Tomcat容器啟動后會向/usr/local/tomcat/logs目錄寫入文件,busybox容器就可以讀取其中的文件了,busybox 容器的啟動命令為“tail -f /logs/catalina*.log”。
# cat pod-volume-applogs.yaml
apiVersion: v1
kind: Pod
metadata:
name: volume-pod
spec:
containers:
- name: tomcat
image: tomcat
imagePullPolicy: Never
ports:
- containerPort: 8080
volumeMounts:
- name: app-logs
mountPath: /usr/local/tomcat/logs
- name: busybox
image: busybox
imagePullPolicy: Never
command: ["sh", "-c", "tail -f /logs/catalina*.log"]
volumeMounts:
- name: app-logs
mountPath: /logs
volumes:
- name: app-logs
emptyDir: {}
使用Kubectl 創建該Pod:
# kubectl apply -f pod-volume-applogs.yaml
pod/volume-pod created
查看創建好的Pod:
# kubectl get pods
NAME READY STATUS RESTARTS AGE
volume-pod 2/2 Running 0 2m13s
登錄Tomcat容器查看生成的日志文件:
# kubectl exec -it volume-pod -c tomcat -- ls /usr/local/tomcat/logs
catalina.2020-01-07.log host-manager.2020-01-07.log localhost.2020-01-07.log localhost_access_log.2020-01-07.txt manager.2020-01-07.log
查看Tomcat日志中的內容:
# kubectl exec -it volume-pod -c tomcat -- tail /usr/local/tomcat/logs/catalina.2020-01-07.log
07-Jan-2020 09:30:43.227 INFO [main] org.apache.coyote.AbstractProtocol.init Initializing ProtocolHandler ["http-nio-8080"]
07-Jan-2020 09:30:43.237 INFO [main] org.apache.tomcat.util.net.NioSelectorPool.getSharedSelector Using a shared selector for servlet write/read
07-Jan-2020 09:30:43.250 INFO [main] org.apache.coyote.AbstractProtocol.init Initializing ProtocolHandler ["ajp-nio-8009"]
07-Jan-2020 09:30:43.251 INFO [main] org.apache.tomcat.util.net.NioSelectorPool.getSharedSelector Using a shared selector for servlet write/read
07-Jan-2020 09:30:43.251 INFO [main] org.apache.catalina.startup.Catalina.load Initialization processed in 751 ms
07-Jan-2020 09:30:43.274 INFO [main] org.apache.catalina.core.StandardService.startInternal Starting service [Catalina]
07-Jan-2020 09:30:43.274 INFO [main] org.apache.catalina.core.StandardEngine.startInternal Starting Servlet Engine: Apache Tomcat/8.5.50
07-Jan-2020 09:30:43.288 INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["http-nio-8080"]
07-Jan-2020 09:30:43.297 INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["ajp-nio-8009"]
07-Jan-2020 09:30:43.300 INFO [main] org.apache.catalina.startup.Catalina.start Server startup in 48 ms
使用 kubectl logs 命令查看busybox容器的輸出內容:
# kubectl logs volume-pod -c busybox
07-Jan-2020 09:30:43.227 INFO [main] org.apache.coyote.AbstractProtocol.init Initializing ProtocolHandler ["http-nio-8080"]
07-Jan-2020 09:30:43.237 INFO [main] org.apache.tomcat.util.net.NioSelectorPool.getSharedSelector Using a shared selector for servlet write/read
07-Jan-2020 09:30:43.250 INFO [main] org.apache.coyote.AbstractProtocol.init Initializing ProtocolHandler ["ajp-nio-8009"]
07-Jan-2020 09:30:43.251 INFO [main] org.apache.tomcat.util.net.NioSelectorPool.getSharedSelector Using a shared selector for servlet write/read
07-Jan-2020 09:30:43.251 INFO [main] org.apache.catalina.startup.Catalina.load Initialization processed in 751 ms
07-Jan-2020 09:30:43.274 INFO [main] org.apache.catalina.core.StandardService.startInternal Starting service [Catalina]
07-Jan-2020 09:30:43.274 INFO [main] org.apache.catalina.core.StandardEngine.startInternal Starting Servlet Engine: Apache Tomcat/8.5.50
07-Jan-2020 09:30:43.288 INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["http-nio-8080"]
07-Jan-2020 09:30:43.297 INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["ajp-nio-8009"]
07-Jan-2020 09:30:43.300 INFO [main] org.apache.catalina.startup.Catalina.start Server startup in 48 ms
可以看到,busybox容器的日志內容就是Tomcat容器的日志內容。
4. Pod 的配置管理
應用部署的一個最佳實踐是將應用所需的配置信息與程序進行分離,這樣可以使應用程序被更好地復用,通過不同的配置也能實現更靈活的功能。將應用打包為容器鏡像后,可以通過環境變量或者外掛文件的方式在創建容器時進行配置注入,但在大規模容器集群的環境中,對多個容器進行不同的配置將變得非常復雜。Kubernetes 從 1.2 版本開始提供了一種統一的應用配置管理方案---ConfigMap。
4.1 ConfigMap 概述
ConfigMap 供容器使用的典型用法如下:
- 生成為容器內的環境變量
- 設置容器啟動命令的啟動參數(需設置為環境變量)
- 以Volume的形式掛載為容器內部的文件或目錄
ConfigMap以一個或多個"Key:Value"的形式保存在 Kubernetes 系統中給應用使用,既可以用於表示一個變量的值(例如:loglevel=info),也可以用於表示一個完整配置文件的內容(例如:server.xml=<?xml...>...)
可以通過YAML配置文件或者直接使用"kubectl create configmap"命令行的方式來創建ConfigMap。
4.2 創建ConfigMap資源對象
4.2.1. 通過YAML配置文件方式創建
下面的文件描述了將應用所需的變量定義為ConfigMap的配置:
# cat cm-appvars.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: cm-appvars
data:
apploglevel: info
appdatadir: /var/data
使用Kubectl 創建該ConfigMap:
# kubectl apply -f cm-appvars.yaml
configmap/cm-appvars created
查看創建的ConfigMap:
# kubectl get configmap
NAME DATA AGE
cm-appvars 2 18s
# kubectl describe configmap cm-appvars
Name: cm-appvars
Namespace: default
Labels: <none>
Annotations: kubectl.kubernetes.io/last-applied-configuration:
{"apiVersion":"v1","data":{"appdatadir":"/var/data","apploglevel":"info"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"cm-appva...
Data
====
appdatadir:
----
/var/data
apploglevel:
----
info
Events: <none>
# kubectl get configmap -o yaml
apiVersion: v1
items:
- apiVersion: v1
data:
appdatadir: /var/data
apploglevel: info
kind: ConfigMap
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"v1","data":{"appdatadir":"/var/data","apploglevel":"info"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"cm-appvars","namespace":"default"}}
creationTimestamp: "2020-01-07T11:28:13Z"
name: cm-appvars
namespace: default
resourceVersion: "1896678"
selfLink: /api/v1/namespaces/default/configmaps/cm-appvars
uid: b93e6852-4b51-49c7-ac12-5a0833d04025
kind: List
metadata:
resourceVersion: ""
selfLink: ""
4.2.2 通過Kubectl命令行方式創建
通過命令行“kubectl create configmap“創建ConfigMap,可以使用參數“--from-file“或“--from-literal“指定內容,並且可以在一行命令中指定多個參數。
准備配置文件
# cat /root/configmap/cm1.cfg
env=dev1
app.name=cm1
log.level=info
# cat /root/configmap/cm2.cfg
env=alpha
app.name=cm2
log.level=debug
1、從文件創建(--from-file)
通過--from-file參數從文件中進行創建,可以指定key的名稱,也可以在一個命令行中創建包含多個key的ConfigMap。
語法:
# kubectl create configmap NAME --from-file=[key=]CONFIG_FILE
創建ConfigMap
# kubectl create configmap cm-test --from-file=/root/configmap/cm1.cfg --from-file=/root/configmap/cm2.cfg
configmap/cm-test created
查看創建的ConfigMap
# kubectl get configmap
NAME DATA AGE
cm-test 2 8s
# kubectl describe configmap cm-test
Name: cm-test
Namespace: default
Labels: <none>
Annotations: <none>
Data
====
cm1.cfg:
----
env=dev1
app.name=cm1
log.level=info
cm2.cfg:
----
env=alpha
app.name=cm2
log.level=debug
Events: <none>
# kubectl get configmap cm-test -o yaml
apiVersion: v1
data:
cm1.cfg: |
env=dev1
app.name=cm1
log.level=info
cm2.cfg: |
env=alpha
app.name=cm2
log.level=debug
kind: ConfigMap
metadata:
creationTimestamp: "2020-01-07T12:37:25Z"
name: cm-test
namespace: default
resourceVersion: "1903226"
selfLink: /api/v1/namespaces/default/configmaps/cm-test
uid: a03188d3-f0d1-437c-b0ce-b95bf8d3520f
創建時指定Data中的Key
# kubectl create configmap cm-test1 --from-file=cm-test1=/root/kube/kube-book/configmap/cm1.cfg
configmap/cm-test1 created
# kubectl describe configmap cm-test1
Name: cm-test1
Namespace: default
Labels: <none>
Annotations: <none>
Data
====
cm-test1:
----
env=dev1
app.name=cm1
log.level=info
Events: <none>
2、從目錄創建(--from-file)
通過--from-file參數從目錄中進行創建,該目錄下的每個配置文件名都被設置為key,文件的內容被設置為value。
語法為:
# kubectl create configmap NAME --from-file=CONFIG_FILE_DIR
創建ConfigMap
# kubectl create configmap cm-test2 --from-file=/root/configmap/
configmap/cm-test2 created
查看創建的ConfigMap
# kubectl get configmap
NAME DATA AGE
cm-test2 2 6s
# kubectl describe configmap cm-test2
Name: cm-test2
Namespace: default
Labels: <none>
Annotations: <none>
Data
====
cm1.cfg:
----
env=dev1
app.name=cm1
log.level=info
cm2.cfg:
----
env=alpha
app.name=cm2
log.level=debug
Events: <none>
# kubectl get configmap cm-test2 -o yaml
apiVersion: v1
data:
cm1.cfg: |
env=dev1
app.name=cm1
log.level=info
cm2.cfg: |
env=alpha
app.name=cm2
log.level=debug
kind: ConfigMap
metadata:
creationTimestamp: "2020-01-07T12:45:26Z"
name: cm-test2
namespace: default
resourceVersion: "1903985"
selfLink: /api/v1/namespaces/default/configmaps/cm-test2
uid: 72983c0c-df9a-42de-8d3a-7cabbb348fe2
3、指定鍵值創建(--from-literal)
通過 --from-literal 時會從文本中進行創建,直接將指定的 key=value 創建為ConfigMap的內容。
語法為:
# kubectl creata configmap NAME --from-literal=KEY1=VALUE1 --from-literal=KEY2=VALUE2
創建ConfigMap
# kubectl create configmap mysql-env --from-literal=mysql.user=admin --from-literal=mysql.passwd=admin@123
configmap/mysql-env created
查看創建的ConfigMap
# kubectl get configmap
NAME DATA AGE
cm-test 2 14h
cm-test1 1 14h
cm-test2 2 14h
mysql-env 2 54s
# kubectl describe configmap mysql-env
Name: mysql-env
Namespace: default
Labels: <none>
Annotations: <none>
Data
====
mysql.passwd:
----
admin@123
mysql.user:
----
admin
Events: <none>
# kubectl get configmap mysql-env -o yaml
apiVersion: v1
data:
mysql.passwd: admin@123
mysql.user: admin
kind: ConfigMap
metadata:
creationTimestamp: "2020-01-08T03:03:45Z"
name: mysql-env
namespace: default
resourceVersion: "1985164"
selfLink: /api/v1/namespaces/default/configmaps/mysql-env
uid: 3268d53e-35b4-41ed-9820-b7afcaedb10f
4.3 在Pod 中使用ConfigMap
Pod中的容器應用使用ConfigMap有兩種方法:
- 通過環境變量獲取ConfigMap中內容
- 通過Volume掛載的方式將ConfigMap中的內容掛載為容器內部的文件或目錄
4.3.1 通過環境變量方式使用ConfigMap
以上面創建的ConfigMap“mysql-env”為例,該ConfigMap中有兩個鍵值對:mysql.user=admin 、mysql.passwd=admin@123
# kubectl get configmap mysql-env -o yaml
apiVersion: v1
data:
mysql.passwd: admin@123
mysql.user: admin
kind: ConfigMap
metadata:
creationTimestamp: "2020-01-08T03:03:45Z"
name: mysql-env
namespace: default
resourceVersion: "1985164"
selfLink: /api/v1/namespaces/default/configmaps/mysql-env
uid: 3268d53e-35b4-41ed-9820-b7afcaedb10f
通過定義文件“cm-test-pod.yaml”創建Pod,將ConfigMap“mysql-env”中的內容以環境變量方式設置為容器內部的環境變量。
# cat cm-test-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: cm-test-pod
spec:
containers:
- name: cm-pod
image: busybox
imagePullPolicy: Never
command: ["/bin/sh", "-c", "sleep 3600"]
env:
- name: MYSQL_USER # 定義容器內環境變量名稱:MYSQL_USER
valueFrom:
configMapKeyRef:
name: mysql-env # “MYSQL_USER”的值取自“mysql-env”
key: mysql.user # MYSQL_USER對應的key為“mysql.user”
- name: MYSQL_PASSWD # 定義容器內環境變量名稱:MYSQL_PASSWD
valueFrom:
configMapKeyRef:
name: mysql-env # “MYSQL_PASSWD”的值取自“mysql-env”
key: mysql.passwd # MYSQL_PASSWD對應的key為“mysql.passwd”
restartPolicy: Never
執行Kubectl創建POD
# kubectl apply -f cm-test-pod.yaml
pod/cm-test-pod created
# kubectl get pods
NAME READY STATUS RESTARTS AGE
cm-test-pod 1/1 Running 0 10s
進入POD內的容器查看環境變量
# 使用 kubectl exec 進入POD內部容器,執行命令“env |grep MYSQL”查看MYSQL相關環境變量
# kubectl exec -it cm-test-pod env |grep MYSQL
MYSQL_USER=admin
MYSQL_PASSWD=admin@123
可以看到POD內部容器的環境變量已經設置成ConfigMap“mysql-env”中的值。
Kubernetes從1.6版本開始,引入一個新的字段“envFrom”,實現了在POD環境中將ConfigMap(也可用於Secret資源)中所定義的 key=value自動生成為環境變量。
示例如下:
# cat cm-envfrom-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: cm-envfrom-pod
spec:
containers:
- name: cm-envfrom
image: busybox
imagePullPolicy: Never
command: ["/bin/sh", "-c", "sleep 3600"]
envFrom:
- configMapRef:
name: mysql-env # 根據mysql-env中的key=value自動生成環境變量
restartPolicy: Never
# kubectl apply -f cm-envfrom-pod.yaml
pod/cm-envfrom-pod created
# kubectl get pods
NAME READY STATUS RESTARTS AGE
cm-envfrom-pod 1/1 Running 0 25s
# kubectl exec -it cm-envfrom-pod env |grep mysql
mysql.passwd=admin@123
mysql.user=admin
需要注意的是,環境變量的名稱需要遵循格式" [a-zA-Z_][a-zA-Z0-9_]*",不能以數字開頭。如果包含非法字符,則系統將跳過該條環境變量的創建,並記錄一個Event來提示環境變量無法生成,但並不會阻止Pod的啟動。
4.3.2 通過volumeMount使用ConfigMap
准備變量配置文件
# cat /root/configmap/cm1.cfg
env=dev1
app.name=cm1
log.level=info
# cat /root/configmap/cm2.cfg
env=alpha
app.name=cm2
log.level=debug
創建ConfigMap
# kubectl create configmap cm-volume --from-file=/root/configmap/
configmap/cm-volume created
查看創建好的ConfigMap
# kubectl get cm
NAME DATA AGE
cm-volume 2 5s
# kubectl get cm cm-volume -o yaml
apiVersion: v1
data:
cm1.cfg: |
env=dev1
app.name=cm1
log.level=info
cm2.cfg: |
env=alpha
app.name=cm2
log.level=debug
kind: ConfigMap
metadata:
creationTimestamp: "2020-01-08T07:01:20Z"
name: cm-volume
namespace: default
resourceVersion: "2007689"
selfLink: /api/v1/namespaces/default/configmaps/cm-volume
uid: 4c9545b6-4060-4850-bedf-728e6af38295
創建POD定義文件
# cat cm-volume-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: cm-volume-pod
spec:
containers:
- name: cm-volume
image: busybox
imagePullPolicy: Never
command: ["/bin/sh", "-c", "sleep 3600"]
volumeMounts:
- name: config-volume # 引用Volume的名稱
mountPath: /etc/config # 掛載到容器內的目錄
volumes:
- name: config-volume # 定義Volume的名稱
configMap:
name: cm-volume # 引用的ConfigMap為cm-volume
items:
- key: cm1.cfg # key=cm1.cfg
path: cm1-cfg # cm1.cfg的value將以文件名cm1-cfg進行掛載
- key: cm2.cfg # ey=cm2.cfg
path: cm2-cfg # cm2.cfg的value將以文件名cm2-cfg進行掛載
根據定義文件生產Pod
# kubectl apply -f cm-volume-pod.yaml
pod/cm-volume-pod created
進入POD容器內部查看掛載配置文件
# kubectl get pods
NAME READY STATUS RESTARTS AGE
cm-volume-pod 1/1 Running 0 17s
# kubectl exec -it cm-volume-pod /bin/sh
/ # cd /etc/config/
/etc/config # ls
cm1-cfg cm2-cfg
/etc/config # cat cm1-cfg
env=dev1
app.name=cm1
log.level=info
/etc/config # cat cm2-cfg
env=alpha
app.name=cm2
log.level=debug
/etc/config #
執行“kubectl exec”登錄POD內部容器,切換到/etc/config目錄下,可以看到存在cm1-cfg和cm2-cfg兩個文件,它們的內容就是ConfigMap“cm-volume”中兩個key定義的內容。
如果在引用configMap時不指定items,POD內部容器在使用volumeMount掛載ConfigMap時,會根據ConfigMap中數據(data)條目(item)的數量,在容器掛載目錄下為每個條目生成一個以條目名稱為文件名的文件。下面舉例說明:
創建POD定義文件
# cat cm-volume-pod2.yaml
apiVersion: v1
kind: Pod
metadata:
name: cm-volume-pod2
spec:
containers:
- name: cm-volume2
image: busybox
imagePullPolicy: Never
command: ["/bin/sh", "-c", "sleep 3600"]
volumeMounts:
- name: config-volume2 # 引用Volume的名稱
mountPath: /etc/config # 掛載到容器內的目錄
volumes:
- name: config-volume2 # 定義Volume的名稱
configMap:
name: cm-volume # 引用的ConfigMap為cm-volume
根據定義文件生產Pod
# kubectl apply -f cm-volume-pod2.yaml
pod/cm-volume-pod2 created
進入POD容器內部查看掛載配置文件
# kubectl get pods
NAME READY STATUS RESTARTS AGE
cm-volume-pod2 1/1 Running 0 113s
# kubectl exec -it cm-volume-pod2 /bin/sh
/ # cd /etc/config/
/etc/config # ls
cm1.cfg cm2.cfg
/etc/config # cat cm1.cfg
env=dev1
app.name=cm1
log.level=info
/etc/config # cat cm2.cfg
env=alpha
app.name=cm2
log.level=debug
/etc/config #
執行“kubectl exec”登錄POD內部容器,切換到/etc/config目錄下,可以看到存在cm1.cfg和cm2.cfg兩個文件,它們的內容就是ConfigMap“cm-volume”中兩個key定義的內容。
4.3.3 使用ConfigMap的限制條件
使用ConfigMap的限制條件如下:
- ConfigMap必須在Pod之前創建。
- ConfigMap受Namespace限制,只有處於相同Namespace中的Pod才可以引用它。
- ConfigMap中定義的key的值都是明文的,對於一些密碼類型的值不安全。
- 在Pod對ConfigMap進行掛載(volumeMount)操作時,在容器內部只能掛載為“目錄”,無法掛載為“文件”。掛載到容器內部后,在目錄下將包含ConfigMap定義的每個item,如果在該目錄下原來還有其他文件,則容器內的該目錄將被掛載的ConfigMap覆蓋。如果應用程序需要保留原來的其他文件,則需要進行額外的處理。可以將ConfigMap掛載到容器內部的臨時目錄,再通過啟動腳本將配置文件復制或者鏈接到(cp或link命令)應用所用的實際配置目錄下。
5. 容器內讀取Pod的信息(Downward API)
待完善
6. Pod 生命周期和重啟策略
Pod在整個生命周期中被系統定義為各種狀態,熟悉Pod的各種狀態對於理解如何設置Pod的調度策略、重啟策略是很有必要的。
Pod的狀態如表格所示:
狀態值 | 描述 |
---|---|
Pending | API Server 已經創建該Pod,但是在Pod內還有一個或多個容器的鏡像沒有創建,包括正在下載鏡像的過程 |
Running | Pod 內所有容器均已創建,且至少有一個容器處於運行狀態、正在啟動狀態或正在重啟狀態 |
Succeeded | Pod 內所有容器均成功執行后退出,且不會在重啟 |
Failed | Pod 內所有容器均已退出,但至少有一個容器退出為失敗狀態 |
Unknow | 由於某種原因無法獲取該Pod的狀態,可能由於網絡通信不暢導致 |
Pod的重啟策略(RestartPolicy)應用於Pod內的所有容器,並且僅在Pod所處的Node上由kubelet進行判斷和重啟操作。當某個容器異常退出或者健康檢查失敗時,kubelet將根據RestartPolicy的設置來進行相應的操作。
Pod的重啟策略包括Always、OnFailure和Never,默認值為Always。
- Always:當容器失效時,由kubelet自動重啟該容器。
- OnFailure:當容器終止運行且退出碼不為0時,由kubelet自動重啟該容器。
- Never:不論容器運行狀態如何,kubelet都不會重啟該容器。
kubelet重啟失效容器的時間間隔以sync-frequency乘以2n來計算,例如1、2、4、8倍等,最長延時5min,並且在成功重啟后的10min后重置該時間。
Pod的重啟策略與控制方式息息相關,當前可用於管理Pod的控制器包括ReplicationController、Job、DaemonSet及直接通過kubelet管理(靜態Pod)。
每種控制器對Pod的重啟策略要求如下。
- RC和DaemonSet:必須設置為Always,需要保證該容器持續運行。
- Job:OnFailure或Never,確保容器執行完成后不再重啟。
- kubelet:在Pod失效時自動重啟它,不論將RestartPolicy設置為什么值,也不會對Pod進行健康檢查。
結合Pod的狀態和重啟策略,下面表格列出一些常見的狀態轉換場景:
7. Pod健康檢查和服務可用性檢查
Kubernetes 對 Pod 的健康狀態可以通過兩類探針來檢查:LivenessProbe 和ReadinessProbe,Kubelet定期執行這兩類探針來診斷容器的健康狀況。
- LivenessProbe探針:用於判斷容器是否存活(Running狀態),如果LivenessProbe探針探測到容器不健康,則kubelet將殺掉該容器,並根據容器的重啟策略做相應的處理。如果一個容器不包含LivenessProbe探針,那么kubelet認為該容器的LivenessProbe探針返回的值永遠是Success。
- ReadinessProbe探針:用於判斷容器服務是否可用(Ready狀態),達到Ready狀態的Pod才可以接收請求。對於被Service管理的Pod,Service與Pod Endpoint的關聯關系也將基於Pod是否Ready進行設置。如果在運行過程中Ready狀態變為False,則系統自動將其從Service的后端Endpoint列表中隔離出去,后續再把恢復到Ready狀態的Pod加回后端Endpoint列表。這樣就能保證客戶端在訪問Service時不會被轉發到服務不可用的Pod實例上。
LivenessProbe和ReadinessProbe均可配置以下三種實現方式。
- ExecAction:在容器內部執行一個命令,如果該命令的返回碼為0,則表明容器健康。
- TCPSocketAction:通過容器的IP地址和端口號執行TCP檢查,如果能夠建立TCP連接,則表明容器健康。
- HTTPGetAction:通過容器的IP地址、端口號及路徑調用HTTP Get方法,如果響應的狀態碼大於等於200且小於400,則認為容器健康。
對於每種探測方式,都需要設置initialDelaySeconds和timeoutSeconds兩個參數,它們的含義分別如下:
- initialDelaySeconds:啟動容器后進行首次健康檢查的等待時間,單位為s。
- timeoutSeconds:健康檢查發送請求后等待響應的超時時間,單位為s。當超時發生時,kubelet會認為容器已經無法提供服務,將會重啟該容器。
Kubernetes的ReadinessProbe機制可能無法滿足某些復雜應用對容器內服務可用狀態的判斷,所以Kubernetes從1.11版本開始,引入Pod Ready++特性對Readiness探測機制進行擴展,在1.14版本時達到GA穩定版,稱其為Pod Readiness Gates。
通過Pod Readiness Gates機制,用戶可以將自定義的ReadinessProbe探測方式設置在Pod上,輔助Kubernetes設置Pod何時達到服務可用狀態(Ready)。為了使自定義的ReadinessProbe生效,用戶需要提供一個外部的控制器(Controller)來設置相應的Condition狀態。
8. 玩轉Pod調度
在Kubernetes平台上,我們很少會直接創建一個Pod,在大多數情況下會通過RC、Deployment、DaemonSet、Job等控制器完成對一組Pod副本的創建、調度及全生命周期的自動控制任務。
在最早的Kubernetes版本里是沒有這么多Pod副本控制器的,只有一個Pod副本控制器RC(Replication Controller),這個控制器是這樣設計實現的:RC獨立於所控制的Pod,並通過Label標簽這個松耦合關聯關系控制目標Pod實例的創建和銷毀,隨着Kubernetes的發展,RC也出現了新的繼任者——Deployment,用於更加自動地完成Pod副本的部署、版本更新、回滾等功能。
嚴謹地說,RC的繼任者其實並不是Deployment,而是ReplicaSet,因為 ReplicaSet進一步增強了RC標簽選擇器的靈活性。之前RC的標簽選擇器只能選擇一個標簽,而ReplicaSet擁有集合式的標簽選擇器,可以選擇多個Pod標簽,如下所示:
......
selector:
matchLabels:
tier: frontend
matchExpressions:
- {key: tier, operator: In, values: [frontend]}
與RC不同,ReplicaSet被設計成能控制多個不同標簽的Pod副本。一種常見的應用場景是,應用MyApp目前發布了v1與v2兩個版本,用戶希望MyApp的Pod副本數保持為3個,可以同時包含v1和v2版本的Pod,就可以用ReplicaSet來實現這種控制,寫法如下:
......
selector:
matchLabels:
version: v2
matchExpressions:
- {key: version, operator: In, values: [v1, v2]}
其實,Kubernetes的滾動升級就是巧妙運用ReplicaSet的這個特性來實現的,同時,Deployment也是通過ReplicaSet來實現Pod副本自動控制功能的。我們不應該直接使用底層的ReplicaSet來控制Pod副本,而應該使用管理ReplicaSet的Deployment對象來控制副本,這是來自官方的建議。
在大多數情況下,我們希望Deployment創建的Pod副本被成功調度到集群中的任何一個可用節點,而不關心具體會調度到哪個節點。但是,在真實的生產環境中的確也存在一種需求:希望某種Pod的副本全部在指定的一個或者一些節點上運行,比如希望將MySQL數據庫調度到一個具有SSD磁盤的目標節點上,此時Pod模板中的NodeSelector屬性就開始發揮作用了,上述MySQL定向調度案例的實現方式可分為以下兩步:
- 把具有SSD磁盤的Node都打上自定義標簽“disk=ssd”;
- 在Pod模板中設定NodeSelector的值為“disk: ssd”.
如此一來,Kubernetes在調度Pod副本的時候,就會先按照Node的標簽過濾出合適的目標節點,然后選擇一個最佳節點進行調度。
上述邏輯看起來既簡單又完美,但在真實的生產環境中可能面臨以下問題:
- 如果NodeSelector選擇的Label不存在或者不符合條件,比如這些目標節點此時宕機或者資源不足,該怎么辦?
- 如果要選擇多種合適的目標節點,比如SSD磁盤的節點或者超高速硬盤的節點,該怎么辦?
為此,Kubernates引入了NodeAffinity(節點親和性設置)來解決該需求。
在真實的生產環境中還存在如下所述的特殊需求:
- 不同Pod之間的親和性(Affinity)。比如MySQL數據庫與Redis中間件不能被調度到同一個目標節點上,或者兩種不同的Pod必須被調度到同一個Node上,以實現本地文件共享或本地網絡通信等特殊需求,這就是PodAffinity要解決的問題。
- 有狀態集群的調度。對於ZooKeeper、Elasticsearch、MongoDB、Kafka等有狀態集群,雖然集群中的每個Worker節點看起來都是相同的,但每個Worker節點都必須有明確的、不變的唯一ID(主機名或IP地址),這些節點的啟動和停止次序通常有嚴格的順序。此外,由於集群需要持久化保存狀態數據,所以集群中的Worker節點對應的Pod不管在哪個Node上恢復,都需要掛載原來的Volume,因此這些Pod還需要捆綁具體的PV。針對這種復雜的需求,Kubernetes提供了StatefulSet這種特殊的副本控制器來解決問題,在Kubernetes 1.9版本發布后,StatefulSet才可用於正式生產環境中。
- 在每個Node上調度並且僅僅創建一個Pod副本。這種調度通常用於系統監控相關的Pod,比如主機上的日志采集、主機性能采集等進程需要被部署到集群中的每個節點,並且只能部署一個副本,這就是DaemonSet這種特殊Pod副本控制器所解決的問題。
- 對於批處理作業,需要創建多個Pod副本來協同工作,當這些Pod副本都完成自己的任務時,整個批處理作業就結束了。這種Pod運行且僅運行一次的特殊調度,用常規的RC或者Deployment都無法解決,所以Kubernates引入了新的Pod調度控制器Job來解決問題,並繼續延伸了定時作業的調度控制器CronJob。
與單獨的Pod實例不同,由RC、ReplicaSet、Deployment、DaemonSet等控制器創建的Pod副本實例都是歸屬於這些控制器的,這就產生了一個問題:控制器被刪除后,歸屬於控制器的Pod副本該何去何從?在Kubernates 1.9之前,在RC等對象被刪除后,它們所創建的Pod副本都不會被刪除;在Kubernates1.9以后,這些Pod副本會被一並刪除。如果不希望這樣做,則可以通過kubectl命令的--cascade=false參數來取消這一默認特性。
# kubectl delete replicaset my-repset --cascade=false
8.1 全自動調度(Deployment)
Deployment的主要功能之一就是自動部署一個容器應用的多份副本,以及持續監控副本的數量,在集群內始終維持用戶指定的副本數量。
以下是一個Deployment的例子,該配置文件將創建一個RepicaSet,該ReplicaSet會創建3個Nginx應用的Pod:
# cat nginx-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
運行kubectl apply 命令創建該Deployment:
# kubectl apply -f nginx-deployment.yaml
deployment/nginx-deployment created
查看Deployment的狀態:
- -o wide:查看更詳細的信息
# kubectl get deployments -o wide
NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
nginx-deployment 3/3 3 3 2m22s nginx nginx:1.14 app=nginx
通過以上信息可以看到:Deployment已創建好所有3個副本,並且所有副本都是最新的可用的。
通過運行“kubectl get pods”和“kubectl get rs”查看已經創建的ReplicaSet和Pod的信息。
# kubectl get rs -o wide
NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR
nginx-deployment-6cfc444fb 3 3 3 2m30s nginx nginx:1.14 app=nginx,pod-template-hash=6cfc444fb
# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-deployment-6cfc444fb-lqbjv 1/1 Running 0 2m36s 10.100.14.221 k8s-node02 <none> <none>
nginx-deployment-6cfc444fb-q72lc 1/1 Running 0 2m36s 10.100.0.228 k8s-node01 <none> <none>
nginx-deployment-6cfc444fb-zxf7d 1/1 Running 0 2m37s 10.100.7.164 k8s-node03 <none> <none>
以上信息可以看到:ReplicaSet和Pods的名稱之間是有所關聯的。從調度策略上看,這3個Nginx Pod由系統全自動完成調度。它們各自最終運行在哪個節點上,完全由Master的Scheduler經過一系列算法計算得出,用戶無法干預調度過程和結果。
除了使用系統自動調度算法完成一組Pod的部署,Kubernetes也提供了多種豐富的調度策略,用戶只需在Pod的定義中使用NodeSelector、NodeAffinity、PodAffinity、Pod驅逐等更加細粒度的調度策略設置,就能完成對Pod的精准調度。以下是這些策略的詳細說明。
8.1.1 Node定向調度(NodeSelector)
Kubernetes Master上的Scheduler服務(kube-scheduler進程)負責實現Pod的調度,整個調度過程通過執行一系列復雜的算法,最終為每個Pod都計算出一個最佳的目標節點,這一過程是自動完成的,通常我們無法知道Pod最終會被調度到哪個節點上。在實際情況下,也可能需要將Pod調度到指定的一些Node上,可以通過Node的標簽(Label)和Pod的nodeSelector屬性相匹配,來達到上述目的。
首先,我們給目標Node打上標簽,下面我們給節點k8s-node01打上一個“role=middleware”的標簽。
# kubectl label nodes k8s-node01 role=middleware
# kubectl get nodes --show-labels
NAME STATUS ROLES AGE VERSION LABELS
k8s-master Ready master 17d v1.15.5 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-master,kubernetes.io/os=linux,node-role.kubernetes.io/master=
k8s-node01 Ready <none> 17d v1.15.5 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-node01,kubernetes.io/os=linux,role=middleware
k8s-node02 Ready <none> 17d v1.15.5 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-node02,kubernetes.io/os=linux
k8s-node03 Ready <none> 17d v1.15.5 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-node03,kubernetes.io/os=linux
然后,在Pod的資源定義文件總加上nodeSelector的配置,下面以一個Redis的Pod為例:
apiVersion: v1
kind: Pod
metadata:
name: php-redis
labels:
name: php-redis
spec:
containers:
- name: frontend
image: ygego/php-frontend
env:
- name: GET_HOSTS_FROM
value: env
ports:
- containerPort: 80
- name: redis
image: ygego/redis-master
ports:
- containerPort: 6379
nodeSelector:
role: middleware
運行命令創建此Pod,Scheduler就會將給Pod調度到擁有標簽“role=middleware”的Node上。
# kubectl apply -f frontend-redis-pod.yaml
pod/php-redis created
# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
php-redis 2/2 Running 0 8s 10.100.0.229 k8s-node01 <none> <none>
如果我們給多個Node都定義了相同的標簽(例如zone=north),則scheduler會根據調度算法從這組Node中挑選一個可用的Node進行Pod調度。
通過基於Node標簽的調度方式,我們可以把集群中具有不通特點的Node都加上不同的標簽,例如“role=frontedn”、“role=backend”、“role=database”等標簽,在部署應用是就可以根據應用的需求設置NodeSelector來進行指定Node范圍的調度。
需要注意的是,如果我們指定了Pod的nodeSelector條件,且在集群中不存在包含相應標簽的Node,則即使在集群中還有其他可供使用的Node,這個Pod也無法被成功調度。
除了用戶可以給Node添加標簽,Kubernetes集群也會給Node預定義一些標簽,用戶也可以使用這些系統標簽進行Pod的定向調度,包括:
- kubernetes.io/hostname
- beta.kubernetes.io/os(從1.14版本開始更新為穩定版,到1.18版本刪除)
- beta.kubernetes.io/arch(從1.14版本開始更新為穩定版,到1.18版本刪除)
- kubernetes.io/os(從1.14版本開始啟用)
- kubernetes.io/arch(從1.14版本開始啟用)
NodeSelector通過標簽的方式,簡單實現了限制Pod所在節點的方法。親和性調度機制則極大擴展了Pod的調度能力,主要的增強功能如下:
- 更具表達力(不僅僅是“符合全部”的簡單情況)。
- 可以使用軟限制、優先采用等限制方式,代替之前的硬限制,這樣調度器在無法滿足優先需求的情況下,會退而求其次,繼續運行該Pod。
- 可以依據節點上正在運行的其他Pod的標簽來進行限制,而非節點本身的標簽。這樣就可以定義一種規則來描述Pod之間的親和或互斥關系。
親和性調度功能包括節點親和性(NodeAffinity)和Pod親和性(PodAffinity)兩個維度的設置。節點親和性與NodeSelector類似,增強了上述前兩點優勢;Pod的親和與互斥限制則通過Pod標簽而不是節點標簽來實現,也就是上面第3點內容所陳述的方式,同時具有前兩點提到的優點。
NodeSelector將會繼續使用,隨着節點親和性越來越能夠表達nodeSelector的功能,最終NodeSelector會被廢棄。
8.1.2 Node親和性調度(NodeAffinity)
NodeAffinity意為Node親和性的調度策略,是用於替換NodeSelector的全新調度策略。目前有兩種節點親和性表達:
- RequiredDuringSchedulingIgnoredDuringExecution:必須滿足指定的規則才可以調度Pod到Node上(功能與nodeSelector很像,但是使用的是不同的語法),相當於硬限制。
- PreferredDuringSchedulingIgnoredDuringExecution:強調優先滿足指定規則,調度器會嘗試調度Pod到Node上,但並不強求,相當於軟限制。多個優先級規則還可以設置權重(weight)值,以定義執行的先后順序。
IgnoredDuringExecution的意思是:如果一個Pod所在的節點在Pod運行期間標簽發生了變更,不再符合該Pod的節點親和性需求,則系統將忽略Node上Label的變化,該Pod能繼續在該節點運行。
NodeAffinity語法支持的操作符包括In、NotIn、Exists、DoesNotExist、Gt、Lt。雖然沒有節點排斥功能,但是用NotIn和DoesNotExist就可以實現排斥的功能了。
NodeAffinity調度規則示例,具體需求如下:
- requiredDuringSchedulingIgnoredDuringExecution要求只運行在amd64的節點上(beta.kubernetes.io/arch In amd64)
- preferredDuringSchedulingIgnoredDuringExecution要求盡量運行在磁盤類型為ssd的節點上(disk-type In ssd)。
Pod資源定義文件:
apiVersion: v1
kind: Pod
metadata:
name: node-affinity
spec:
containers:
- name: node-affinity
image: nginx:1.14
imagePullPolicy: IfNotPresent
affinity:
nodeAffinity:
requireDuringSchedulingIgnoreDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: beta.kubernetes.io/arch
opertator: In
value:
- amd64
preferredDuringSchedulingIgnoreDuringExecution:
- weight: 1
preference:
matchExpressions:
- key: disk-type
operator: In
values;
- ssd
NodeAffinity規則設置的注意事項如下:
- 如果同時定義了nodeSelector和nodeAffinity,那么必須兩個條件都得到滿足,Pod才能最終運行在指定的Node上。
- 如果nodeAffinity指定了多個nodeSelectorTerms,那么其中一個能夠匹配成功即可。
- 如果nodeSelectorTerms中有多個matchExpressions,則一個節點必須滿足所有matchExpressions才能運行該Pod。
8.1.3 Pod親和與互斥調度(PodAffinity)
出於高效通信的需求,偶爾需要把一些 Pod對象組織在相近的位置(同一節點、機架、區域或地區等),如某業務的前端 Pod和后端Pod等,此時可以將這些Pod對象間的關系稱為親和性。偶爾,出於安全或分布式等原因也有可能需要將一些Pod對象在其運行的位置上隔離開來,如在每個區域運行一個應用代理 Pod對象等,此時可把這些 Pod對象間的關系稱為反親和性(anti-affinity)。
當然,也可以通過節點親和性來定義Pod對象間的親和或反親和特性,但用戶必須為此明確指定Pod可運行的節點標簽,顯然這並非較優的選擇。較理想的實現方式是,允許調度器把第一個 Pod放置於任何位置,而后與其有親和或反親和關系的 Pod據此動態完成位置編排,這就是 Pod親和性調度和反親和性調度的功用。 Pod的親和性定義也存在“硬”(required)親和性和“軟”(preferred)親和性的區別,它們表示的約束意義同節點親和性相似。
Kubemetes調度器通過內建的MatchlnterPodAffinity預選策略為這種調度方式完成節點預選,並基於InterPodAffinityPriority優選函數進行各節點的優選級評估。
8.1.3.1 位置拓撲
Pod親和性調度需要各相關的 Pod對象運行於“同一位置”,而反親和性調度則要求它們不能運行於“同一位置”。何謂同一位置?事實上,它們取決於節點的位置拓撲,拓撲的方式不同,對於如圖 8-1所示的Pod-A和Pod-B是否在同一位置的判定結果也可能有所不同。
如果以基於各節點的 kubernetes.io/hostname標簽作為評判標准,那么很顯然,“同一位置”意味着同一個節點,不同節點即不同的位置,如圖 8-2所示。
而如果是基於如圖 8-3所划分的故障轉移域來進行評判,那么 serverl和 server4屬於同一位置,而 server2和 server3屬於另一個意義上的同一位置
故此,在定義Pod對象的親和性與反親和性時,需要借助於標簽選擇器來選擇被依賴的Pod對象,並根據選出的Pod對象所在節點的標簽來判定“同一位置”的具體意義。
8.1.3.2 Pod硬親和調度
Pod強制約束的親和性調度也使用 requiredDuringSchedulinglgnoredDuringExecution屬性進行定義。 Pod親和性用於描述一個 Pod對象與具有某特征的現存 Pod對象運行位置的依賴關系,因此,測試使用 Pod親和性約束,需要事先存在被依賴的 Pod對象,它們具有特別的識別標簽。下面創建一個有着標簽“ app=nginx”的 Deployment資源部署一個目標 Pod對象:
# kubectl run nginx -l app=nginx --image nginx:1.14
deployment.apps/nginx created
# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-699bd9c545-jhwhs 1/1 Running 0 47s 10.100.7.166 k8s-node03 <none> <none>
下面的資源配置清單中定義了一個 Pod對象,它通過labelSlector定義的標簽選擇器挑選感興趣的現存 Pod對象,而后根據挑選出的 Pod對象所在節點的標簽“kubernetes.io/hostname”來判斷同一位置的具體含義,並將當前 Pod對象調度至這一位置的某節點之上:
# cat required-podAffinity-pod.yaml
apiVersion: vl
kind: Pod
metadata:
name: pod-affinity
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoreDuringExecution:
- labelSelector:
matchExpressions:
- {key: app, operator: In, values: ["nginx"]}
topologyKey: kubernetes.io/hostname
containers:
- name: myapp
image: ikubernetes/myapp:v1
imagePullPolicy: IfNotPresent
事實上,kubernetes.io/hostname標簽是Kubernetes集群節點的內建標簽,它的值為當前節點的節點主機名稱標識,對於各個節點來說,各有不同。因此,新建的 Pod對象將被部署至被依賴的 Pod對象的同一節點之上, requiredDuringSchedulinglgnoredDuringExecution表示這種親和性為強制約束。
# kubectl apply -f required-podAffinity-pod.yaml
pod/pod-affinity created
# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-699bd9c545-jhwhs 1/1 Running 0 16m 10.100.7.166 k8s-node03 <none> <none>
pod-affinity 1/1 Running 0 12s 10.100.7.167 k8s-node03 <none> <none>
基於單一節點的 Pod親和性只在極個別的情況下才有可能會用到,較為常用的通常是基於同一地區(region)、區域(zone)或機架( rack)的拓撲位置約束。例如部署應用程序服務(myapp)與數據庫(db)服務相關的 Pod時,db Pod可能會部署於如圖 8-4所示的 foo或 bar這兩個區域中的某節點之上,依賴於數據服務的 myapp Pod對象可部署於 db Pod所在區域內的節點上。當然,如果 db Pod在兩個區域 foo和 bar中各有副本運行,那么 myapp Pod將可以運行於這兩個區域的任何節點之上。
例如,創建兩個擁有標簽為“app=db”的副本Pod作為被依賴的資源,它們可能運行於類似圖8-5所示的三個節點中的任何一個或兩個節點之上,本示例中它們湊巧運行於兩個 zone標簽值不同的節點之上:
# kubectl run db -l app=db --image=ygego/redis-master:latest --image-pull-policy=Never --replicas=2
deployment.apps/db created
# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
db-d47c9597c-n2pq8 1/1 Running 0 7s 10.100.0.232 k8s-node01 <none> <none>
db-d47c9597c-vjdt8 1/1 Running 0 7s 10.100.7.169 k8s-node03 <none> <none>
因此,依賴親和於這兩個 Pod的其他 Pod對象可運行於 zone標簽值為 foo和 bar的區域內的所有節點之上。以下資源配置清單中定義了這樣一些Pod資源,它們由 Deployment控制器所創建:
# cat deploy-required-podAffinity.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-required-podaffinity
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
name: myapp
labels:
app: myapp
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- {key: app, operator: In, values: ["db"]}
topologyKey: zone
containers:
- name: myapp
image: ygego/myapp:v1
imagePullPolicy: IfNotPresent
# kubectl apply -f deployment-required-affinity.yaml
deployment.apps/myapp-required-podaffinity created
# kubectl get pods -o wide --show-labels
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES LABELS
myapp-required-podaffinity-57ff997984-4snvl 1/1 Running 0 11s 10.100.0.233 k8s-node01 <none> <none> app=myapp,pod-template-hash=57ff997984
myapp-required-podaffinity-57ff997984-mpgx4 1/1 Running 0 11s 10.100.14.222 k8s-node02 <none> <none> app=myapp,pod-template-hash=57ff997984
myapp-required-podaffinity-57ff997984-w9mwn 1/1 Running 0 11s 10.100.7.170 k8s-node03 <none> <none> app=myapp,pod-template-hash=57ff997984
在調度示例中的 Dep loym巳nt控制器創建的 Pod資源時,調度器首先會基於標簽選擇器查詢擁有標簽“ app=db”的所有Pod資源,接着獲取到它們分別所屬的節點的 zone標簽值,接下來再查詢擁有匹配這些標簽值的所有節點,從而完成節點預選。而后根據優選函數計算這些節點的優先級,從而挑選出運行新建Pod對象的節點。
需要注意的是,如果節點上的標簽在運行時發生了更改,以致它不再滿足Pod上的親和性規則,但該Pod還將繼續在該節點上運行,因此它僅會影響新建的Pod資源;另外,labelSelector屬性僅匹配與被調度器的 Pod在同一名稱空間中的 Pod資源,不過也可以通過為其添加 namespace字段以指定其他名稱空間。