掃二維碼與項目經(jīng)理溝通
我們在微信上24小時期待你的聲音
解答本文疑問/技術(shù)咨詢/運營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流
作者:Jack 2023-09-21 07:24:52
云計算
云原生 那為啥使用 kubectl create 或 kubectl apply 等命令創(chuàng)建Pod時,通常不會顯式地看到Pause容器。這是因為Pause容器是由Kubernetes自動創(chuàng)建和管理的,通常不需要用戶手動操作或關(guān)注。它是Pod的一個隱式組成部分,用于維護Pod的基礎(chǔ)設(shè)施和容器之間的網(wǎng)絡(luò)隔離。

Kubernetes出現(xiàn)的報錯如下:
Failed to create pod sandbox: rpc error: code = Unknown desc = failed to get sandbox image "k8s.gcr.io/pause:3.5": failed to pull image "k8s.gcr.io/pause:3.5": failed to pull and unpack image "k8s.gcr.io/pause:3.5": failed to resolve reference "k8s.gcr.io/pause:3.5": failed to do request: Head "https://k8s.gcr.io/v2/pause/manifests/3.5": x509: certificate signed by unknown authority
k8s.gcr.io 這個地址是需要連外網(wǎng)才可以拉取到,導(dǎo)致 pause 鏡像拉不下來,Pod無法啟動。以前都沒關(guān)注過 pause 這個容器,它是啥,做什么用的,怎么在 Pod 里沒看到過他,本文將帶你了解 pause 容器。
在Kubernetes中,Pod是最小的調(diào)度單元,但它的內(nèi)部結(jié)構(gòu)卻充滿了許多復(fù)雜的機制,其中之一就是Pause容器。盡管Pause容器看似不起眼,但它在整個Kubernetes集群中發(fā)揮了至關(guān)重要的作用。我們在 kubernetes 的 node 節(jié)點,執(zhí)行 docker ps,可以發(fā)現(xiàn)每個 node 上都運行了一個 pause進程的容器,具體如下:
[root@localhost ~]# docker ps |grep traefik
66032431a20e 2ae1addee1b2 "/entrypoint.sh --gl…" 30 hours ago Up 30 hours k8s_traefik_traefik-68b9ccfc77-x8sqg_traefik_aa5b97bf-3db8-4b92-89a7-1fe551645e6a_0
10d393461904 registry.aliyuncs.com/google_containers/pause:3.5 "/pause" 30 hours ago Up 30 hours k8s_POD_traefik-68b9ccfc77-x8sqg_traefik_aa5b97bf-3db8-4b92-89a7-1fe551645e6a_0
會發(fā)現(xiàn)有很多 pause 容器運行于服務(wù)器上面,容器命名也很規(guī)范,然后每次啟動一個容器,都會伴隨一個pause這樣的容器啟動。那它究竟是干啥子的?它就是 Pause 容器,又叫 Infra 容器。我們在部署完 kubernetes 集群后,查看 kubelet 進程,可以看到配置中有這樣一個參數(shù):
[root@localhost ~]# ps -ef|grep kubelet
root 8675 1 10 Sep18 ? 03:15:07 /usr/bin/kubelet --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf --config=/var/lib/kubelet/config.yaml --network-plugin=cni --pod-infra-container-image=registry.aliyuncs.com/google_containers/pause:3.5
pause 容器使用的鏡像為 registry.aliyuncs.com/google_containers/pause:3.5 該鏡像非常小,只有 683kB,由于它總是處于 Pause (暫時)狀態(tài),所以取名叫 pause
[root@localhost ~]# docker images|grep pause
registry.aliyuncs.com/google_containers/pause 3.5 ed210e3e4a5b 2 years ago 683kB
想了解該 pause 容器的構(gòu)成(代碼是用 C 語言寫的)的可以去官方倉庫上一看究竟:https://github.com/kubernetes/kubernetes/tree/master/build/pause
一個 Pod 可以由一組容器組成的,這些容器之間共享存儲和網(wǎng)絡(luò)資源,那么網(wǎng)絡(luò)資源是如何共享的呢?下面是個例子:
圖片
比如說現(xiàn)在有一個 Pod,其中包含了一個容器 A 和一個容器 B,它們兩個就要共享 Network Namespace。在 Kubernetes 里的解法是這樣的:它會在每個 Pod 里,額外起一個 Infra container 小容器來共享整個 Pod 的 Network Namespace。Infra container 是一個非常小的鏡像,大概 683kB,是一個C語言寫的、永遠處于“暫停”狀態(tài)的容器。由于有了這樣一個 Infra container 之后,其他所有容器都會通過 Join Namespace 的方式加入到 Infra container 的 Network Namespace 中。所以說一個 Pod 里面的所有容器,它們看到的網(wǎng)絡(luò)視圖可以說是完全一樣的。即:它們看到的網(wǎng)絡(luò)設(shè)備、IP地址、Mac地址等等,跟網(wǎng)絡(luò)相關(guān)的信息,其實全是一份,這一份都來自于 Pod 第一次創(chuàng)建的這個 Infra container。這就是 Pod 解決網(wǎng)絡(luò)共享的一個解法。在 Pod 里面,一定有一個 IP 地址,是這個 Pod 的 Network Namespace 對應(yīng)的地址,也是這個 Infra container 的 IP 地址。所以大家看到的都是一份,而其他所有網(wǎng)絡(luò)資源,都是一個 Pod 一份,并且被 Pod 中的所有容器共享。這就是 Pod 的網(wǎng)絡(luò)實現(xiàn)方式。由于需要有一個相當(dāng)于說中間的容器存在,所以整個 Pod 里面,必然是 Infra container 第一個啟動。并且整個 Pod 的生命周期是等同于 Infra container 的生命周期的,與容器 A 和 B 是無關(guān)的。這是非常重要的一個設(shè)計。kubernetes的pause容器主要為每個業(yè)務(wù)容器提供兩個核心功能:
我們已經(jīng)知道,一個 Pod 從表面上來看至少由一個容器組成,而實際上一個 Pod 至少要有包含兩個容器,一個是應(yīng)用容器,一個是 pause 容器。運行一個pause容器:
[root@localhost ~]# docker run -d --name pause -p 8080:80 registry.aliyuncs.com/google_containers/pause:3.5
fd315974f5d1a5f52ca47c5dc31aea3774cebf90c88ce065cc9e9ea2f52c103c
運行一個nginx容器,代理 127.0.0.1:8888 springboot應(yīng)用程序
# 準(zhǔn)備nginx配置文件
[root@k8s001 ~]# cat <> nginx.conf
error_log stderr;
events { worker_connections 1024; }
http {
server {
listen 80 default_server;
server_name www.kubesre.com;
location / {
proxy_pass http://127.0.0.1:8888;
}
}
}
EOF
# 創(chuàng)建nginx容器
[root@localhost ~]# docker run -d --name nginx -v `pwd`/nginx.conf:/etc/nginx/nginx.conf --net=container:pause --ipc=container:pause --pid=container:pause --ipc=shareable nginx
fa9f858adae826ad536178747e00fffc829c7baf98c3bc29e945230abbf2a5cb
創(chuàng)建一個應(yīng)用容器 springboot
[root@localhost ~]# docker run -d --name springboot --net=container:pause --ipc=container:pause --pid=container:pause --ipc=shareable registry.cn-shanghai.aliyuncs.com/kubesre02/springboot
e33cfa3cebd5aafa714ca6ef0f6a16be52a282c64b8d24b2d98890ccf02c436a
到這里,我們就純手工模擬出了一個符合 K8S Pod 模型的 “Pod” ,只是它并不由 K8S 進行管理。驗證,查看運行的容器
[root@localhost ]~# docker ps | grep -E "pause|nginx|springboot"
4f877cdcba5d registry.cn-shanghai.aliyuncs.com/kubesre02/springboot "java -jar /app.jar" 3 seconds ago Up 2 seconds springboot
e541dc010fb3 nginx "/docker-entrypoint.…" 19 hours ago Up 19 hours nginx
09f94a052d50 registry.aliyuncs.com/google_containers/pause:3.5 "/pause" 19 hours ago Up 19 hours 0.0.0.0:8080->80/tcp, :::8080->80/tcp pause
通過瀏覽器訪問 http://ip:8080 端口
[root@localhost ~]# curl http://localhost:8080
Hello Docker World
從上面的步驟可見:
這里,我們進入springboot 容器內(nèi)部查看:
[root@localhost ~]# /tmp/test# docker exec -it springboot sh
/ # ps aux
PID USER TIME COMMAND
1 65535 0:00 /pause
205 root 0:22 java -jar /app.jar
240 root 0:00 nginx: master process nginx -g daemon off;
261 101 0:00 nginx: worker process
263 root 0:00 sh
269 root 0:00 ps aux
在springboot 容器中可以看到pause和nginx容器的進程,并且pause容器的PID為1,而在kubernetes中容器的PID=1的進程則為容器本身的業(yè)務(wù)進程。
如果沒有 K8S 的 Pod ,啟動一個 業(yè)務(wù)容器,你需要手動創(chuàng)建三個容器,當(dāng)你想銷毀這個服務(wù)時,同樣需要刪除三個容器。而有了 K8S 的 Pod,這三個容器在邏輯上就是一個整體,創(chuàng)建 Pod 就會自動創(chuàng)建三個容器,刪除 Pod 就會刪除三個容器,從管理上來講,方便了不少。
這正是 Pod 存在的一個根本意義所在。
在Linux中,PID命名空間中的進程是一個樹型結(jié)構(gòu),每個進程有一個父進程。在樹的根上只有一個進程沒有真正的父進程。這是init進程,其PID為1。
僵尸進程是指已經(jīng)停止運行但它們的進程表條目仍然存在的進程,在UNIX系統(tǒng)中,一個子進程結(jié)束了,但是它的父進程沒有等待(調(diào)用wait/waitpid)它,那么它將變成一個僵尸進程。
僵尸進程是怎么產(chǎn)生的?
出現(xiàn)僵尸進程的一種情況是:父進程編寫得很糟糕,省略了wait調(diào)用,或者父進程意外崩潰在子進程之前死亡,而新的父進程沒有調(diào)用wait。當(dāng)一個進程的父進程在子進程之前死亡時,操作系統(tǒng)將該子進程分配給init進程或PID 1的進程。即init進程接納子進程并成為其父進程。這意味著,現(xiàn)在當(dāng)子進程退出時,新的父進程(init)必須調(diào)用wait來獲取它的退出碼,否則它的進程表條目將永遠保留下來,成為僵死進程。
在Kubernetes pod中,容器的運行方式與上述基本相同,但是為每個pod創(chuàng)建了一個特殊的pause容器。
這個pause容器運行了一個非常簡單的進程,它不執(zhí)行任何函數(shù),本質(zhì)上永遠休眠,其源碼實現(xiàn):
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include
#include
#include
#include
#include
#include
static void sigdown(int signo) {
psignal(signo, "Shutting down, got signal");
exit(0);
}
static void sigreap(int signo) {
while (waitpid(-1, NULL, WNOHANG) > 0);
}
int main() {
if (getpid() != 1)
/* Not an error because pause sees use outside of infra containers. */
fprintf(stderr, "Warning: pause should be the first process\n");
if (sigaction(SIGINT, &(struct sigaction){.sa_handler = sigdown}, NULL) < 0)
return 1;
if (sigaction(SIGTERM, &(struct sigaction){.sa_handler = sigdown}, NULL) < 0)
return 2;
if (sigaction(SIGCHLD, &(struct sigaction){.sa_handler = sigreap,
.sa_flags = SA_NOCLDSTOP},
NULL) < 0)
return 3;
for (;;)
pause();
fprintf(stderr, "Error: infinite loop terminated\n");
return 42;
}
從上述代碼種我們可以發(fā)現(xiàn),pause容器不僅僅調(diào)用pause()使進程休眠,還擁有另外一個重要的功能:
它假定自己為PID 1的角色,當(dāng)僵尸進程被其父進程孤立時,會被pause容器進行收養(yǎng),通過調(diào)用wait來獲取僵尸進程。這樣一來就不會在Kubernetes pod的PID命名空間中堆積僵尸進程了。
那為啥使用 kubectl create 或 kubectl apply 等命令創(chuàng)建Pod時,通常不會顯式地看到Pause容器。這是因為Pause容器是由Kubernetes自動創(chuàng)建和管理的,通常不需要用戶手動操作或關(guān)注。它是Pod的一個隱式組成部分,用于維護Pod的基礎(chǔ)設(shè)施和容器之間的網(wǎng)絡(luò)隔離。
不難想到,這其中的過程是非常復(fù)雜的。而且我們還沒有深入探討如何去監(jiān)控和管理這些容器的生命周期。但是不用擔(dān)心,我們不需要這么復(fù)雜的去管理我們的容器,因為kubernetes已經(jīng)都為我們做好了。

我們在微信上24小時期待你的聲音
解答本文疑問/技術(shù)咨詢/運營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流