Kubernetes Pod 實現原理

Pod 的形象表達

在 Kubernetes 中,Pod 是容器組的概念,一個 Pod 可以包含多個容器。這些容器共享網絡、存儲等資源,爲應用程序提供了一個更加靈活的運行環境。

容器的本質是一個特殊的進程,特殊在爲其創建了 NameSpace 隔離運行環境,並用 Cgroups 控制資源開銷,還藉助了一些 Linux 網絡虛擬化技術解決了網絡通信的問題

Pod 所做的則是讓多個容器加入同一個 NameSpace 以實現資源共享。

接下來,我們可以使用 Docker 來還原 Pod 的實現原理。

容器共享 NameSpace

現在我們要部署一個 Pod ,其中包含 nginx 和 busybox 兩個容器,前者作爲主應用提供 Web 服務,後者作爲 Sidecar 調試容器。

首先啓動 nginx 容器:

docker run -d --name nginx --ipc="shareable" -v $PWD/log:/var/log/nginx -v $PWD/html:/usr/share/nginx/html nginx

默認情況下,Docker 的 IPC Namespace 是私有的,我們可以使用 --ipc="shareable" 來指定允許共享。-v 參數的作用就不多講了。

接下來啓動 busybox 容器,並加入到 nginx 容器的 NET、IPC、PID NameSpace 中,同時,我們共享 nginx 容器的 Volume ,以便可以訪問 nginx 的日誌文件:

docker run -d --name busybox --net=container:nginx --ipc=container:nginx --pid=container:nginx -v $PWD/log:/var/log/nginx yauritux/busybox-curl /bin/sh -c 'while true; do sleep 1h; done;'

兩個容器都啓動後,就可以在 busybox 容器中直接調試 nginx 容器的資源了:

[root@vm ~]# echo "hello pod" > $PWD/html/index.html
[root@vm ~]# docker exec -it busybox ps
PID   USER     TIME  COMMAND
    1 root      0:00 nginx: master process nginx -g daemon off;
   29 101       0:00 nginx: worker process
   30 101       0:00 nginx: worker process
   31 root      0:00 /bin/sh -c while true; do sleep 1h; done;
   37 root      0:00 sleep 1h
   38 root      0:00 ps
[root@vm ~]# docker exec -it busybox curl localhost
hello pod
[root@vm ~]# docker exec -it busybox tail /var/log/nginx/access.log
127.0.0.1 - - [30/Mar/2023:08:07:58 +0000] "GET / HTTP/1.1" 200 10 "-" "curl/7.81.0" "-"
[root@vm ~]#

在 busybox 容器中,不僅可以看到 nginx 容器的進程,還可以直接訪問 nginx 服務和共享的日誌文件目錄。

但是,由於 Namespace 是由 nginx 容器創建的,如果 nginx 意外崩潰,那麼所有 Namespace 都會一同被刪除,busybox 容器也會被終止:

[root@vm ~]# docker stop nginx
nginx
[root@vm ~]# docker exec -it busybox ps
Error response from daemon: Container fca6ea0fe9f177f62d4b2d5d7db5bf64766d605f0414a8995c8d93159efeed4a is not running
[root@vm ~]#

顯然,讓業務容器充當共享基礎容器是不可取的,必須保證每個容器都是對等的關係,而不是父子關係。Kubernetes 也考慮到了這一點。

Pause 容器

Pause 容器,又叫 Infra 容器。爲了解決共享基礎容器的安全問題, Kubernetes 會在每個 Pod 裏,額外起一個 Infra 容器來共享整個 Pod 的 Namespace 。

Pause 容器會在 Pod 創建時首先啓動,並創建 Namespace 、配置網絡 IP 地址及路由等相關信息。可以說,Pause 容器的生命週期就相當於是整個 Pod 的生命週期。

等 Pause 容器啓動完成後,其它容器才接着啓動,並與 Pause 容器共享 Namespace。這樣,每個容器就都可以訪問 Pod 中其他容器的資源了。

具體的創建過程可以查看 kubelet 的源碼:pkg/kubelet/kuberuntime/kuberuntime_manager.go#L678[1]

其中第 4 步的創建 Sandbox 沙盒容器,實際就是創建 Pause 容器。之後才繼續後面的 init 容器、正常容器的創建。

作用如此重要的 Pause 容器也決定了它的特點:

1、鏡像非常小:gcr.io/google_containers/pause-amd64[2]

2、性能開銷幾乎可以忽略:pause.c[3]

瞭解了 Pause 容器後,重新開始我們的 Pod 原理試驗。

從新環境開始,這次我們首先啓動的應該是 Pause 容器了:

docker run -d --name pause --ipc="shareable" gcr.io/google_containers/pause-amd64:3.2

然後再啓動 nginx 容器,加入到 Pause 容器的 Namespace :

docker run -d --name nginx --net=container:pause --ipc=container:pause --pid=container:pause -v $PWD/log:/var/log/nginx -v $PWD/html:/usr/share/nginx/html nginx

同理 busybox 容器:

docker run -d --name busybox --net=container:pause --ipc=container:pause --pid=container:pause -v $PWD/log:/var/log/nginx yauritux/busybox-curl /bin/sh -c 'while true; do sleep 1h; done;'

現在在 busybox 容器中將可以同時看到 pause 和 nginx 容器的進程:

[root@vm ~]# echo "hello pod" > $PWD/html/index.html
[root@vm ~]# docker exec -it busybox ps
PID   USER     TIME  COMMAND
    1 root      0:00 /pause
    8 root      0:00 nginx: master process nginx -g daemon off;
   36 101       0:00 nginx: worker process
   37 101       0:00 nginx: worker process
   38 root      0:00 /bin/sh -c while true; do sleep 1h; done;
   45 root      0:00 sleep 1h
   46 root      0:00 ps
[root@vm ~]# docker exec -it busybox curl localhost
hello pod
[root@vm ~]# docker exec -it busybox tail /var/log/nginx/access.log
127.0.0.1 - - [30/Mar/2023:08:49:49 +0000] "GET / HTTP/1.1" 200 10 "-" "curl/7.81.0" "-"
[root@vm ~]#

而且即使 nginx 意外崩潰了,也不會影響到 busybox 容器:

[root@vm ~]# docker stop nginx
nginx
[root@vm ~]# docker exec -it busybox ps
PID   USER     TIME  COMMAND
    1 root      0:00 /pause
   38 root      0:00 /bin/sh -c while true; do sleep 1h; done;
   45 root      0:00 sleep 1h
   66 root      0:00 ps
[root@vm ~]#

回到 Kubernetes

相信你現在已經理解,在 Kubernetes 集羣中執行 kubectl apply -f nginx-busybox-pod.yaml 命令後所發生的事情:

apiVersion: v1
kind: Pod
metadata:
  name: nginx-busybox-pod
spec:
  shareProcessNamespace: true
  containers:
  - name: nginx
    image: nginx
    volumeMounts:
    - name: nginx-log
      mountPath: /var/log/nginx
    - name: nginx-html
      mountPath: /usr/share/nginx/html
    ports:
    - containerPort: 80
  - name: busybox
    image: yauritux/busybox-curl
    command: ["/bin/sh""-c""while true; do sleep 1h; done;"]
    volumeMounts:
    - name: nginx-log
      mountPath: /var/log/nginx
  volumes:
  - name: nginx-log
    emptyDir: {}
  - name: nginx-html
    emptyDir: {}

(改:應爲 -c busybox ps)第一個進程就是 pause

原理就是這麼簡單。

參考資料

[1]

pkg/kubelet/kuberuntime/kuberuntime_manager.go#L678: https://github.com/kubernetes/kubernetes/blob/v1.26.1/pkg/kubelet/kuberuntime/kuberuntime_manager.go#L678

[2]

gcr.io/google_containers/pause-amd64: https://console.cloud.google.com/gcr/images/google-containers/GLOBAL/pause-amd64

[3]

pause.c: https://github.com/kubernetes/kubernetes/blob/master/build/pause/linux/pause.c

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/b8Ohs8Z-HRMajAas1GkTvg