圖解Kubernetes的Pod核心資源-來白嫖啊

推薦手機閱讀原文://mp.weixin.qq.com/s/nR6P6eidE1r5A2viLCFHWA
推薦手機閱讀閱讀://mp.weixin.qq.com/s/nR6P6eidE1r5A2viLCFHWA
推薦手機閱讀閱讀://mp.weixin.qq.com/s/nR6P6eidE1r5A2viLCFHWA

一、Pod定義

如下圖,在K8S中資源調度的基本單位是Pod

Pod其實是一個抽象的概念,Pod里是我們的業務容器(docker/containerd)。像大家聽過的Deployment、StatefulSet、CronJob等資源調度對象所調度的資源都是Pod。

為了更好的理解Pod的概念,大家可以將Pod理解成VM 虛擬機,將Pod中的容器理解成VM中的進程。既然這樣理解,就意味着Pod中的容器進程可以直接通過localhost+端口號實現網絡互通,也意味着Pod中的容器可以實現類似直接讀取彼此產出到磁盤上的文件的效果。

如上圖:容器1訪問:127.0.0.1:8082可以訪問到容器2。

在實際用應用中,比如我們有兩個服務:服務A和服務B,並且他倆之間只能通過本地迴環網卡通信,那我們在就應該將它們分配進同一個pod中。

那什麼是資源調度? 簡單來說就是將:為Pod挑選一個合適的物理節點,然後將Pod中的容器啟動好對外提供服務。


二、Pod入門yaml描述文件

簡單的,只要將我們想啟動的Docker容器填入Pod資源對象的containers字段中,再通過kubectl命令創建Pod,K8S會為Pod選擇一個合適的Node,並在該Node上啟動用戶指定的容器。

如下是一個Pod的Yaml描述文件,Pod中定義了兩個容器分別是:nginx、shell

apiVersion: v1  # 必選,API的版本號
kind: Pod       # 必選,類型Pod
metadata:       # 必選,元數據
  name: daemon-pod   # 必選,符合RFC 1035規範的Pod名稱
spec:
  containers:
  - name: nginx
    image: nginx:latest    # 必選,容器所用的鏡像的地址
  - name: shell
    image: busybox
    stdin: true
    tty: true

使用kubectl命名創建Pod,可以看到Ready數為2、Node字段說明該Pod運行在叫node02的宿主機上

驗證,找到node02

登錄node02,查看node02上是否有相應的docker容器

可以看到K8S不光啟動了nginx、busybox容器,還多啟動一個叫pause的容器,大家也叫它infra容器。

Infra容器的作用:Pod中的所有容器會共享一個NetworkNamespace,因為在創建pod中的業務容器前,會先創建pause容器佔用NetworkNamespace,後續創建的業務容器都加入到pause的網絡中,相當於在Docker run命令中添加參數:--net=container:pause,這也是為什麼Pod中的所有容器的ip其實都是pod的ip。

如下圖,進入shell容器中,看到它的ip其實就是上圖中的pod ip。

在nginx容器中,也能看到容器的ip就是pod的ip

三、共享NetworkNamespace

如下圖是K8S創建Pod時,Pod的網絡協議棧的初始化過程

簡單解讀,理解pause容器是K8S網絡模型中的精髓~

  1. kubelet通過CRI協議向底層的容器運行時(docker/containerd)下發命令,讓它創建一個叫pause 的初始化容器,這個pause容器里運行着一個極簡的C程序,具體的邏輯就是將自己阻塞中,目的是讓pause容器快速佔用並一直持有一個networkname
  2. 創建pause容器時,會攜帶參數--net=node意為不初始化網絡協議棧,說白了就是除了自帶的lo迴環網卡外,不添加其他的網卡。
  3. kubelet通過CNI協議為pause容器初始化網絡協議,也就是給它添加網絡並分配IP
  4. Pod中定義的業務容器都加入pause容器的network namespace,它們都使用統一分配給pause的IP

疑問:為什麼pause容器的網絡協議棧不由容器運行時創建它時立即分配好呢?

答:這是個好問題,這麼做也是呼應了K8S網絡的核心目標思想:

  1. IP分配,換句話說K8S要保證在整個集群中每個POD要獨立不重複的IP地址

  2. IP路由,換句話說K8S要保證在整個集群中各個POD的IP是要互通的

這也是它為什麼設計這個流程的原因

四、共享PID

默認情況下Pod中的各容器是不會共享同一個統一個PID Namespace的,需要手動添加參數shareProcessNamespace: true

apiVersion: v1  
kind: Pod       
metadata:       
  name: daemon-pod   
spec:
  # 共享pid namespace
  shareProcessNamespace: true
  containers:
  - name: nginx
    image: nginx:latest    
  - name: shell
    image: busybox
    stdin: true
    tty: true

驗證:如下圖,在shell容器中可以看到nginx進程(通過這更好的將pod理解成虛擬機),同理登陸pod重點任意容器也能看到pid=1的進程是pause進程。

此時pause容器為Pod提供1號進程,在Uninx中,進程為1的進程被稱作init進程。

這個init進程很特殊,因為它會維護一張進程表並不斷的檢測其他進程的狀態,當出現:子進程因父進程的異常退出而變成「孤兒進程」或者是叫「殭屍進程」時,init進程(pause)會收養這個遊離的進程,然後在它退出時,釋放它佔用的資源,否則會可能會出現大量的殭屍進程佔用操作系統的進程表項。

在k8s1.8之前,默認是啟用共享pid namespace

在k8s1.8之後,則需要像本小節一樣,通過參數shareProcessNamespace顯示的開啟

問:既然共享了pause的pid有這麼多好處,為啥後續版本的k8s不再默認開啟了呢?

答:一方面:k8s推薦的做法是,單個pod裏面放盡量少的容器,如只放你的業務容器,這樣殭屍進程帶來的影響幾乎可以忽略不計,共享與不共享,意義不大。

另一方面:像一些特殊的如systemd鏡像,啟動需要獲取pid1,否則無法啟動

五、容器生命周期

可以通過lifecycle字段在容器創建完成後以及關閉前執行指定的動作,如創建用戶/創建目錄,啟動腳本等

apiVersion: v1 
kind: Pod    
metadata:   
  name: nginx   
spec: 
  containers:  
  - name: nginx 
    image: nginx:latest 
    lifecycle:
      postStart: # 容器創建完成後執行的指令, 可以是exec httpGet TCPSocket
        exec:
          command:
          - sh
          - -c
          - 'mkdir /data/ '
      preStop: # 關閉前的操作
        httpGet:      
              path: /
              port: 80
      #  exec:
      #    command:
      #    - sh
      #    - -c
      #    - sleep 9
  restartPolicy: Always

spec.containers.lifecycle.postStart參數可以指定容器在創建完成後執行一段指令

回憶一下,容器還有個command參數即:spec.containers.command也可以指定一段指令。注意點如下:

  • 這倆command執行的先後並不能100%保證。
  • spec.containers.lifecycle.postStart的執行依賴於容器創建後的環境

spec.initContainers.command的不會依賴業務容器的環境,執行時間也會先於如上兩個command。

六、初始化容器

6.1、簡介

業務容器的啟動依賴很多環境配置,如:

  • wget等可以從網絡上下載文件的命令
  • 或者是有些命令需要以root的權限運行初始化,如修改文件的權限、修改內核參數

如果有攻擊性的程序獲得了使用這些命令的權限,就會有很大的安全隱患,為了安全,我們是不希望業務容器中包含這些危險的命令。

這時可以使用initcontainer完成這種工作,因為initcontainer運行結束後會退出,沒有後續的安全隱患。

6.2、與普通容器的區別

  • 初始化容器會依次執行,上一個運行結束,下一個才會執行。
  • 初始化容器不成功結束,不會啟動業務容器,K8S會不斷的重啟該Pod。但是如果Pod的restartPolicy設置為Never,K8S就不再重啟該Pod了。
  • init容器不支持:lifecycle、livenessProbe、readinessProbe、startupProbe探針

6.3、實驗

準備yaml,initContainer和業務容器共享掛載volume的方式,讓業務容器共享initContainer的初始化文件

apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    app: test-init-container
  name: test-init-container
spec:
  replicas: 1
  selector:
    matchLabels:
      app: test-init-container
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: test-init-container
    spec:
      volumes:
      - name: data
        emptyDir: {}
      initContainers:
      - name: init01
        image: busybox
        volumeMounts:
        - name: data
          mountPath: /tmp
        command:
        - sh
        - -c
        - touch /tmp/test-init-container.txt
      containers:
      - image: nginx
        name: nginx
        volumeMounts:
        - name: data
          mountPath: /tmp

查看Pod執行的Event可以看到先執行了初始化容器的相關操作

[root@master01 initContainer]# kubectl describe pod test-init-container-79d689d7d8-fgz2s
... 
Events:
  Type    Reason     Age    From               Message
  ----    ------     ----   ----               -------
  Normal  Scheduled  6m47s  default-scheduler  Successfully assigned default/test-init-container-79d689d7d8-fgz2s to master01
  Normal  Pulling    6m47s  kubelet            Pulling image "busybox"
  Normal  Pulled     6m43s  kubelet            Successfully pulled image "busybox" in 3.781619189s
  Normal  Created    6m43s  kubelet            Created container init01
  Normal  Started    6m43s  kubelet            Started container init01
  Normal  Pulling    6m42s  kubelet            Pulling image "nginx"
  Normal  Pulled     6m11s  kubelet            Successfully pulled image "nginx" in 31.093619016s
  Normal  Created    6m11s  kubelet            Created container nginx
  Normal  Started    6m11s  kubelet            Started container nginx

驗收

[root@master01 initContainer]# kubectl exec -ti test-init-container-79d689d7d8-fgz2s -- sh
Defaulted container "nginx" out of: nginx, init01 (init)
# ls /tmp
test-init-container.txt

七、Pod探針

名稱 作用 引入版本
startupProbe 1. 用於判斷容器內的應用進程是否成功啟動。
2. 若配置了startupProbe。直到它檢測通過前,會禁用其他探針
3. startupProbe檢測未通過,會使用restartPolicy重啟策略重啟
4. startupProbe探測成功後將不再探測。
1.16
readinessProbe 1. 用戶探測容器內的程序是否健康,是否可以接收流量
2. 探測成功表示該容器已經完全啟動,可接收流量。
3. 若未配置,默認返回success
livenessProbe 1. 用於判斷容器是否運行
2. 若探測失敗kubelet根據重啟策略重啟容器
3. 若未配置,默認返回success

檢測方式一:ExecAction

原理如下:執行一個腳本,返回0表示成功,返回非0表示異常

[root@master01 yamls]# touch 1
[root@master01 yamls]# cat 1
[root@master01 yamls]# echo $?
0
[root@master01 yamls]# cat 123123.txt
cat: 123123.txt: 沒有那個文件或目錄
[root@master01 yamls]# echo $?
1
#!/bin/bash
#K8S 的存活探針
function liveness()
{
    result=`nmap 127.0.0.1 -p $Target_PORT | grep $Target_PORT/tcp | awk '{print $2}'`
    if [ "$result" != "open" ];then
        echo 'port not open'
        return 1
    fi
}                    
liveness

檢測方式二:TcpSocketAction

通過Tcp連接檢查容器內的端口是否是連通的,如果連通,認為容器健康。原理如下:

# 連通的情況
[root@master01 yamls]# telnet 10.10.10.101 2380
Trying 10.10.10.101...
Connected to 10.10.10.101.
Escape character is '^]'.
^CConnection closed by foreign host.

# 非連通的情況
[root@master01 yamls]# telnet 10.10.10.101 2381
Trying 10.10.10.101...
telnet: connect to address 10.10.10.101: Connection refused

檢測方式三:HttpGetAction

通過應用程序暴露的Http接口,來檢查應用程序是否健康,若返回的狀態碼在[200,400)之間,認為程序健康。

7.1、livenessProbe

目的:判斷容器是否啟動了,檢測失敗後會重啟容器

參考

livenessProbe:
      failureThreshold: 5
      httpGet:
        path: /health
        port: 8080
        scheme: HTTP
      initialDelaySeconds: 60
      periodSeconds: 10
      successThreshold: 1
      timeoutSeconds: 5

7.2、readinessProbe

目的:探測容器中的應用是否是正常的

檢測通過:表示應用可以接收流量,READY狀態變成1/1

[root@master01 yamls]# kubectl get pod
NAME    READY   STATUS    RESTARTS   AGE
nginx   1/1     Running   0          54s

檢測失敗,READY狀態變成0/1,且RESTARTS且0,表示不會重啟容器

[root@master01 yamls]# kubectl get pod
NAME    READY   STATUS    RESTARTS   AGE
nginx   0/1     Running   0          54s

參考

readinessProbe:
      failureThreshold: 3
      httpGet:
        path: /ready
        port: 8181
        scheme: HTTP
      periodSeconds: 10
      successThreshold: 1
      timeoutSeconds: 1

7.3、startupProbe

在有了livenessProbe和readinessProbe之後,為啥還整一個startupProbe出來呢?

可用來應對極端情況:應用啟動時各種加載配置,導致啟動的特別慢。 最終導致livenessProbe檢查失敗,當livenessProbe檢查失敗時,k8s會重啟容器。 重啟之後應用啟動還是慢,livenessProbe還是失敗,就進入了死循環

startupProbe其實就是將等待探測應用正常啟動的步驟從livenessProbe中提取出來,放在livenessProbe步驟前。若配置了startupProbe,livenessProbe和readinessProbe會先被禁用,等startupProbe通過後,livenessProbe和readinessProbe才會生效。

實驗:

apiVersion: v1  # 必選,API的版本號
kind: Pod       # 必選,類型Pod
metadata:       # 必選,元數據
  name: nginx   # 必選,符合RFC 1035規範的Pod名稱
spec:   # 必選,用於定義容器的詳細信息
  containers:   # 必選,容器列表
  - name: nginx # 必選,符合RFC 1035規範的容器名稱
    image: nginx:latest    # 必選,容器所用的鏡像的地址
    ports:  # 可選,容器需要暴露的端口號列表
    - name: http          # 端口名稱,如果需要暴露多個端口,需要保證每個port的name不能重複
      containerPort: 80 # 端口號
      protocol: TCP     # 端口協議,默認TCP
    startupProbe: 
      httpGet:  # httpGet檢測方式,生產環境建議使用httpGet實現接口級健康檢查,健康檢查由應用程序提供。
        path: /api/successStart # 檢查路徑
        port: 80
  restartPolicy: Always 

因為沒有/api/successStart接口,所以startupProbe檢測不通過

如下:pod的status為running,但是Ready為0

[root@master01 yamls]# kubectl get pod
NAME    READY   STATUS    RESTARTS   AGE
nginx   0/1     Running   0          19s

查看詳情:

startupProbe會按照繼續按策略探測,當失敗的次數達到預期後,會重啟,如下重啟了4次了

[root@master01 yamls]# kubectl get pod
NAME    READY   STATUS             RESTARTS      AGE
nginx   0/1     CrashLoopBackOff   4 (27s ago)   2m57s

可以將HttpGet檢測方式換成TcpSocket去檢測80端口,startupProbe校驗即可通過

apiVersion: v1 
kind: Pod       
metadata:       
  name: nginx   
spec:  
  containers:  
  - name: nginx
    image: nginx:latest   
    startupProbe: 
      tcpSocket:
        port: 80
  restartPolicy: Always 
[root@master01 yamls]# kubectl get pod
NAME    READY   STATUS    RESTARTS   AGE
nginx   1/1     Running   0          14s

八、Pod退出流程

當我們關閉或者刪除pod時,Pod的狀態會變成:Terninating

[root@master01 yamls]# kubectl get pod
NAME    READY   STATUS     RESTARTS   AGE
nginx   0/1     Terninating   0          2m16s

另外你會發現,執行delete命令時,會等待一段時間

[root@master01 yamls]# kubectl delete pod nginx
pod "nginx" deleted

這個等待的時間是k8s給pod留出來的一段寬限期,可以通過kubectl edit pod xxx查看pod的配置,默認情況下有一個叫terminationGracePeriodSeconds: 30的參數,值為30秒,表示在pod被delete之後,有30秒的寬限期。

在這個寬限期中會做收尾的工作如:

  • 將pod所屬的service的endpoint對應記錄刪除
  • 執行lifecycle 的 preStop 定義的命令

上文說過lifecycle,重新貼出來,如下:

    lifecycle:
      postStart: # 容器創建完成後執行的指令, 可以是exec httpGet TCPSocket
        exec:
          command:
          - sh
          - -c
          - 'mkdir /data/ '
      preStop: # 容器關閉前的操作
        httpGet:      
              path: /
              port: 80
        exec:
          command:
          - sh
          - -c
          - sleep 9

九、HPA

9.1、簡介

全稱:Horizontal Pod Autoscaler

可以根據CPU、內存使用率或者是自定義的指標完成對Pod的自動擴縮容

查看K8S集群的HPA相關API

  • v1版本是穩定版,只支持CPU指標
    • v2beta1支持CPU、內存、自定義指標
  • v2beta2支持CPU、內存、自定義指標、額外指標ExternelMetrics(公有雲廠商提供)

9.2、使用

使用限制:

  • 不能對如DaemonSet類型的資源進行擴所容。
  • 必須安裝了metrics-server
  • 必須配置requests參數

準備測試環境

# 創建模版yaml
kubectl create deployment nginx-dp --image=nginx --dry-run=client -oyaml > nginx-dp.yaml

# 更新resources
      containers:
      - image: nginx
        name: nginx
        resources:
          requests:
            cpu: 10m

# 創建deployment
kubectl apply -f nginx-dp.yaml

# 查看pod的CPU指標
[root@master01 yamls]# kubectl top pod
NAME                        CPU(cores)   MEMORY(bytes)
busybox                     0m           0Mi
nginx-dp-84c4fd8fc6-s7mnx   0m           6Mi

# 為dp暴露一個service
kubectl expose deployment nginx-dp --port=80

可以通過如下命令使用HPA

# 當CPU使用率超過10%就擴容,擴容最大數Pod數為10,最小數為1
kubectl autoscale deployment nginx-dp --cpu-percent=10 --min=1 --max=3

壓測,可以觀察到pod會被自動擴容

while true; do wget -q -O- //192.168.217.66 > /dev/null;done

注意點:如果CPU或者是Memory的飆升的源頭是數據庫壓力,那麼我們對pod進行擴容不僅沒有好轉,返回適得其反。

十、靜態Pod

10.1、簡介

靜態Pod是由kubelet直接管理的,且只能在該kubelet所在的Node上運行。

靜態Pod不受ApiServer管理,無法與ReplicationController、Deployment、DaemonSet進行關聯。

kubelet也無法對靜態Pod進行健康檢查。

有兩種方式創建靜態Pod,分別是使用:靜態文件/Http,若使用靜態配置文件創建pod,需要在kubelet的啟動參數statucPodPath中指定靜態Pod的yaml描述文件的位置。

10.2、實驗

只要將pod的yaml文件放入指定的目錄中,過一會便能通過kubectl查看到pod。

嘗試通過kubectl刪除Pod,會一直處於pending狀態,這是因為kubectl會通過apiserver下發刪除的命令,而apiserver無法管理靜態pod。故,若想刪除靜態Pod,需要將對應的pod的yaml文件移出statucPodPath

十一、更多Pod屬性

十二、對比DockerCompose、DockerSwarm

像Docker公司推出的集群調度工具:Docker Compose或是Docker Swarm它們調度的基本單位都是Docker容器。

點擊查看白日夢的筆記:玩轉Docker容器調度-DockerCompose、DockerSwarm

而在K8S中集群調度的基本單位是上文中長篇介紹的Pod,他們兩者維度是不同的。Pod顯然是站在更高的維度上。

因為在容器編排領域中,難點不是為容器選擇一個合適的節點然後將容器啟動好。難點是解決應用間複雜的相互依賴關係。

比如下:

  • 不同應用之間通過本地文件相互通信
  • 不同應用之間通過Http協議/RPC協議相互通信
  • 不同應用之間通過localhost+端口號互通
  • 在所有宿主機上均啟動一個Pod副本

前文說了,大家可以把Pod理解成傳統意義上的虛擬機,Pod中的容器相當於虛擬機中的不同應用,Pod中有哪些容器由開發人員說了算。

這樣其實就是變相的將容器編排最為複雜環節的皮球重新踢給了開發人員,由他們自己描述好,也就實現了天然的解決了應用的複雜依賴關係編排這一難點。K8S調度時也要以Pod為基本單位去選擇一個相對合適宿主機,批量的啟動好Pod中的容器就行。

十三、參考

Kubernetes官網

《Kubernetes權威指南》

《Kubernetes網絡原理與實踐》