Kubernetes中的Pause容器到底是干嘛的
引言
Kubernetes出現(xiàn)的報(bào)錯(cuò)如下:
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 authorityk8s.gcr.io 這個(gè)地址是需要連外網(wǎng)才可以拉取到,導(dǎo)致 pause 鏡像拉不下來(lái),Pod無(wú)法啟動(dòng)。以前都沒(méi)關(guān)注過(guò) pause 這個(gè)容器,它是啥,做什么用的,怎么在 Pod 里沒(méi)看到過(guò)他,本文將帶你了解 pause 容器。
Pause容器是個(gè)啥
在Kubernetes中,Pod是最小的調(diào)度單元,但它的內(nèi)部結(jié)構(gòu)卻充滿了許多復(fù)雜的機(jī)制,其中之一就是Pause容器。盡管Pause容器看似不起眼,但它在整個(gè)Kubernetes集群中發(fā)揮了至關(guān)重要的作用。我們?cè)?kubernetes 的 node 節(jié)點(diǎn),執(zhí)行 docker ps,可以發(fā)現(xiàn)每個(gè) node 上都運(yùn)行了一個(gè) pause進(jìn)程的容器,具體如下:
[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會(huì)發(fā)現(xiàn)有很多 pause 容器運(yùn)行于服務(wù)器上面,容器命名也很規(guī)范,然后每次啟動(dòng)一個(gè)容器,都會(huì)伴隨一個(gè)pause這樣的容器啟動(dòng)。那它究竟是干啥子的?它就是 Pause 容器,又叫 Infra 容器。我們?cè)诓渴鹜?kubernetes 集群后,查看 kubelet 進(jìn)程,可以看到配置中有這樣一個(gè)參數(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.5pause 容器使用的鏡像為 registry.aliyuncs.com/google_containers/pause:3.5 該鏡像非常小,只有 683kB,由于它總是處于 Pause (暫時(shí))狀態(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 語(yǔ)言寫的)的可以去官方倉(cāng)庫(kù)上一看究竟:https://github.com/kubernetes/kubernetes/tree/master/build/pause
Pause容器的作用
- 網(wǎng)絡(luò)命名空間隔離:Pod是Kubernetes中最小的調(diào)度單元,可以包含一個(gè)或多個(gè)容器。為了實(shí)現(xiàn)容器之間的網(wǎng)絡(luò)隔離,每個(gè)Pod都有自己獨(dú)立的網(wǎng)絡(luò)命名空間。Pause容器負(fù)責(zé)創(chuàng)建并維護(hù)這個(gè)網(wǎng)絡(luò)命名空間,其他容器共享這個(gè)網(wǎng)絡(luò)命名空間,使它們能夠相互通信,而不會(huì)與其他Pod中的容器發(fā)生沖突。
 - 進(jìn)程隔離:Pause容器保持一個(gè)輕量級(jí)的進(jìn)程運(yùn)行,即使Pod中的其他容器都停止了。這個(gè)進(jìn)程實(shí)際上不執(zhí)行任何有用的工作,但它的存在確保了Pod不會(huì)在沒(méi)有容器運(yùn)行的情況下被刪除。當(dāng)其他容器停止時(shí),Pause容器仍在運(yùn)行,以維持Pod的生命周期。
 - 資源隔離:盡管Pause容器通常不分配大量的CPU和內(nèi)存資源,但它可以配置以使用一些資源。這有助于確保即使Pod中沒(méi)有其他容器運(yùn)行時(shí),Kubernetes仍然可以監(jiān)控和管理Pod的資源使用情況。這也有助于防止Pod被其他具有相同資源要求的Pod占用。
 - IP地址維護(hù):Pause容器負(fù)責(zé)維護(hù)Pod的IP地址。Pod的IP地址通常是動(dòng)態(tài)分配的,但由于Pause容器一直在運(yùn)行,它可以維護(hù)Pod的IP地址,以便其他容器可以通過(guò)該地址進(jìn)行通信。這有助于確保Pod的IP地址在整個(gè)Pod的生命周期內(nèi)保持一致。
 - 生命周期管理:Pause容器的生命周期與Pod的生命周期相同。當(dāng)Pod創(chuàng)建時(shí),Pause容器被創(chuàng)建;當(dāng)Pod刪除時(shí),Pause容器也會(huì)被刪除。這確保了Pod的整個(gè)生命周期都由Kubernetes進(jìn)行管理,包括創(chuàng)建、擴(kuò)展、縮放和刪除。
 
Pause容器工作原理
一個(gè) Pod 可以由一組容器組成的,這些容器之間共享存儲(chǔ)和網(wǎng)絡(luò)資源,那么網(wǎng)絡(luò)資源是如何共享的呢?下面是個(gè)例子:
圖片
比如說(shuō)現(xiàn)在有一個(gè) Pod,其中包含了一個(gè)容器 A 和一個(gè)容器 B,它們兩個(gè)就要共享 Network Namespace。在 Kubernetes 里的解法是這樣的:它會(huì)在每個(gè) Pod 里,額外起一個(gè) Infra container 小容器來(lái)共享整個(gè) Pod 的 Network Namespace。Infra container 是一個(gè)非常小的鏡像,大概 683kB,是一個(gè)C語(yǔ)言寫的、永遠(yuǎn)處于“暫?!睜顟B(tài)的容器。由于有了這樣一個(gè) Infra container 之后,其他所有容器都會(huì)通過(guò) Join Namespace 的方式加入到 Infra container 的 Network Namespace 中。所以說(shuō)一個(gè) Pod 里面的所有容器,它們看到的網(wǎng)絡(luò)視圖可以說(shuō)是完全一樣的。即:它們看到的網(wǎng)絡(luò)設(shè)備、IP地址、Mac地址等等,跟網(wǎng)絡(luò)相關(guān)的信息,其實(shí)全是一份,這一份都來(lái)自于 Pod 第一次創(chuàng)建的這個(gè) Infra container。這就是 Pod 解決網(wǎng)絡(luò)共享的一個(gè)解法。在 Pod 里面,一定有一個(gè) IP 地址,是這個(gè) Pod 的 Network Namespace 對(duì)應(yīng)的地址,也是這個(gè) Infra container 的 IP 地址。所以大家看到的都是一份,而其他所有網(wǎng)絡(luò)資源,都是一個(gè) Pod 一份,并且被 Pod 中的所有容器共享。這就是 Pod 的網(wǎng)絡(luò)實(shí)現(xiàn)方式。由于需要有一個(gè)相當(dāng)于說(shuō)中間的容器存在,所以整個(gè) Pod 里面,必然是 Infra container 第一個(gè)啟動(dòng)。并且整個(gè) Pod 的生命周期是等同于 Infra container 的生命周期的,與容器 A 和 B 是無(wú)關(guān)的。這是非常重要的一個(gè)設(shè)計(jì)。kubernetes的pause容器主要為每個(gè)業(yè)務(wù)容器提供兩個(gè)核心功能:
- 第一,它提供整個(gè)pod的Linux命名空間的基礎(chǔ)。
 - 第二,啟用PID命名空間,它在每個(gè)pod中都作為PID為1的進(jìn)程,并回收僵尸進(jìn)程。
 
手工模擬Pod
我們已經(jīng)知道,一個(gè) Pod 從表面上來(lái)看至少由一個(gè)容器組成,而實(shí)際上一個(gè) Pod 至少要有包含兩個(gè)容器,一個(gè)是應(yīng)用容器,一個(gè)是 pause 容器。運(yùn)行一個(gè)pause容器:
[root@localhost ~]# docker run -d --name pause -p 8080:80 registry.aliyuncs.com/google_containers/pause:3.5
fd315974f5d1a5f52ca47c5dc31aea3774cebf90c88ce065cc9e9ea2f52c103c- --name:指定 pause 容器的名字,pause
 - -p 8080:80:將宿主機(jī)的 8080 端口映射到容器的 80 端口
 
運(yùn)行一個(gè)nginx容器,代理 127.0.0.1:8888 springboot應(yīng)用程序
# 準(zhǔn)備nginx配置文件
[root@k8s001 ~]# cat <<EOF >> 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- --net=container:pause:用于與另一個(gè)容器共享網(wǎng)絡(luò)命名空間。在這種情況下,容器 "nginx" 會(huì)與名為 "pause" 的容器共享網(wǎng)絡(luò)命名空間,它們可以使用相同的網(wǎng)絡(luò)配置和接口。
 - --ipc=container:pause:用于與另一個(gè)容器共享 IPC 命名空間。IPC 命名空間允許容器之間進(jìn)行進(jìn)程間通信(Inter-Process Communication),在這里,容器 "nginx" 與名為 "pause" 的容器共享 IPC 命名空間。
 - --pid=container:pause:用于與另一個(gè)容器共享 PID 命名空間。PID 命名空間允許容器查看和管理其他容器的進(jìn)程。
 - --ipc=shareable:指示 IPC 命名空間是可共享的,以便其他容器也可以加入到這個(gè)共享命名空間中。
 
創(chuàng)建一個(gè)應(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到這里,我們就純手工模擬出了一個(gè)符合 K8S Pod 模型的 “Pod” ,只是它并不由 K8S 進(jìn)行管理。驗(yàn)證,查看運(yùn)行的容器
[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通過(guò)瀏覽器訪問(wèn) http://ip:8080 端口
[root@localhost ~]# curl http://localhost:8080
Hello Docker World從上面的步驟可見:
- pause容器將內(nèi)部80端口映射到宿主機(jī)8080端口。
 - pause容器在宿主機(jī)上設(shè)置好網(wǎng)絡(luò)namespace后,nginx容器加入到該網(wǎng)絡(luò)的namespace中。
 - nginx容器啟動(dòng)的時(shí)候指定了-net=container:pause。
 - springboot 容器啟動(dòng)時(shí),同樣方式加入到該網(wǎng)絡(luò)的namespace中。
 - 這樣三個(gè)容器共享了網(wǎng)絡(luò),互相之間就可以使用localhost直接通信。
 - --ipc=container:pause,--pid=container:pause就是三個(gè)容器的ipc和pid處于同一個(gè)namespace中,init進(jìn)程為pause。
 
這里,我們進(jìn)入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容器的進(jìn)程,并且pause容器的PID為1,而在kubernetes中容器的PID=1的進(jìn)程則為容器本身的業(yè)務(wù)進(jìn)程。
如果沒(méi)有 K8S 的 Pod ,啟動(dòng)一個(gè) 業(yè)務(wù)容器,你需要手動(dòng)創(chuàng)建三個(gè)容器,當(dāng)你想銷毀這個(gè)服務(wù)時(shí),同樣需要?jiǎng)h除三個(gè)容器。而有了 K8S 的 Pod,這三個(gè)容器在邏輯上就是一個(gè)整體,創(chuàng)建 Pod 就會(huì)自動(dòng)創(chuàng)建三個(gè)容器,刪除 Pod 就會(huì)刪除三個(gè)容器,從管理上來(lái)講,方便了不少。
這正是 Pod 存在的一個(gè)根本意義所在。
如何回收僵尸進(jìn)程
在Linux中,PID命名空間中的進(jìn)程是一個(gè)樹型結(jié)構(gòu),每個(gè)進(jìn)程有一個(gè)父進(jìn)程。在樹的根上只有一個(gè)進(jìn)程沒(méi)有真正的父進(jìn)程。這是init進(jìn)程,其PID為1。
僵尸進(jìn)程是指已經(jīng)停止運(yùn)行但它們的進(jìn)程表?xiàng)l目仍然存在的進(jìn)程,在UNIX系統(tǒng)中,一個(gè)子進(jìn)程結(jié)束了,但是它的父進(jìn)程沒(méi)有等待(調(diào)用wait/waitpid)它,那么它將變成一個(gè)僵尸進(jìn)程。
僵尸進(jìn)程是怎么產(chǎn)生的?
出現(xiàn)僵尸進(jìn)程的一種情況是:父進(jìn)程編寫得很糟糕,省略了wait調(diào)用,或者父進(jìn)程意外崩潰在子進(jìn)程之前死亡,而新的父進(jìn)程沒(méi)有調(diào)用wait。當(dāng)一個(gè)進(jìn)程的父進(jìn)程在子進(jìn)程之前死亡時(shí),操作系統(tǒng)將該子進(jìn)程分配給init進(jìn)程或PID 1的進(jìn)程。即init進(jìn)程接納子進(jìn)程并成為其父進(jìn)程。這意味著,現(xiàn)在當(dāng)子進(jìn)程退出時(shí),新的父進(jìn)程(init)必須調(diào)用wait來(lái)獲取它的退出碼,否則它的進(jìn)程表?xiàng)l目將永遠(yuǎn)保留下來(lái),成為僵死進(jìn)程。
在Kubernetes pod中,容器的運(yùn)行方式與上述基本相同,但是為每個(gè)pod創(chuàng)建了一個(gè)特殊的pause容器。
這個(gè)pause容器運(yùn)行了一個(gè)非常簡(jiǎn)單的進(jìn)程,它不執(zhí)行任何函數(shù),本質(zhì)上永遠(yuǎn)休眠,其源碼實(shí)現(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 <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
 
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()使進(jìn)程休眠,還擁有另外一個(gè)重要的功能:
它假定自己為PID 1的角色,當(dāng)僵尸進(jìn)程被其父進(jìn)程孤立時(shí),會(huì)被pause容器進(jìn)行收養(yǎng),通過(guò)調(diào)用wait來(lái)獲取僵尸進(jìn)程。這樣一來(lái)就不會(huì)在Kubernetes pod的PID命名空間中堆積僵尸進(jìn)程了。
那為啥使用 kubectl create 或 kubectl apply 等命令創(chuàng)建Pod時(shí),通常不會(huì)顯式地看到Pause容器。這是因?yàn)镻ause容器是由Kubernetes自動(dòng)創(chuàng)建和管理的,通常不需要用戶手動(dòng)操作或關(guān)注。它是Pod的一個(gè)隱式組成部分,用于維護(hù)Pod的基礎(chǔ)設(shè)施和容器之間的網(wǎng)絡(luò)隔離。
不難想到,這其中的過(guò)程是非常復(fù)雜的。而且我們還沒(méi)有深入探討如何去監(jiān)控和管理這些容器的生命周期。但是不用擔(dān)心,我們不需要這么復(fù)雜的去管理我們的容器,因?yàn)閗ubernetes已經(jīng)都為我們做好了。















 
 
 













 
 
 
 