深入理解 Pod


深入掌握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 供容器使用的典型用法如下:

  1. 生成為容器內的環境變量
  2. 設置容器啟動命令的啟動參數(需設置為環境變量)
  3. 以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有兩種方法:

  1. 通過環境變量獲取ConfigMap中內容
  2. 通過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定期執行這兩類探針來診斷容器的健康狀況。

  1. LivenessProbe探針:用於判斷容器是否存活(Running狀態),如果LivenessProbe探針探測到容器不健康,則kubelet將殺掉該容器,並根據容器的重啟策略做相應的處理。如果一個容器不包含LivenessProbe探針,那么kubelet認為該容器的LivenessProbe探針返回的值永遠是Success。
  2. ReadinessProbe探針:用於判斷容器服務是否可用(Ready狀態),達到Ready狀態的Pod才可以接收請求。對於被Service管理的Pod,Service與Pod Endpoint的關聯關系也將基於Pod是否Ready進行設置。如果在運行過程中Ready狀態變為False,則系統自動將其從Service的后端Endpoint列表中隔離出去,后續再把恢復到Ready狀態的Pod加回后端Endpoint列表。這樣就能保證客戶端在訪問Service時不會被轉發到服務不可用的Pod實例上。

LivenessProbe和ReadinessProbe均可配置以下三種實現方式。

  1. ExecAction:在容器內部執行一個命令,如果該命令的返回碼為0,則表明容器健康。
  2. TCPSocketAction:通過容器的IP地址和端口號執行TCP檢查,如果能夠建立TCP連接,則表明容器健康。
  3. 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定向調度案例的實現方式可分為以下兩步:

  1. 把具有SSD磁盤的Node都打上自定義標簽“disk=ssd”;
  2. 在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字段以指定其他名稱空間。

8.1.3.3 Pod軟親和調度


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM