Pod 生命周期

上图是 Pod 从创建到消亡的整个过程。
当 kubectl 对 api server 发起请求时,api server 将请求调度到具体的 node 节点上(当然,这还需要 scheduler、 etcd 等的参与),node 节点上的 kubelet 就会开始进行 容器环境的初始化。
-
初始化 Pod 环境:每一个 Pod 在创建时都会先初始化 Pod 运行所必须的条件。然后才继续执行后面的容器初始化操作。比如会先创建 pause 这样的容器。
-
Init C:Init Containers,初始化容器。
我们在 Pod 里面运行的容器,可能会依赖某些文件或者环境才能够正常启动或运行。Init C 就是来完成这些文件或者环境的创建的。
Init C 可以有多个,也可以没有。按顺序执行,必须要等到前一个执行完成(必须是正常退出,否则会根据重启策略判断是否重新执行该 Init C),才会执行后一个。
Init C 只存在容器初始化阶段,Init C 执行完成之后会消亡,不参与 Pod 的后续流程。
所有的 Init C 都正常结束后,Main C 主容器才开始创建并启动。 -
Main C:主容器。即运行在 Pod 里提供服务的容器。主容器一旦停止运行,那么 Pod 也就停止了。需要注意的是一个 Pod 里面可以有很多个 Main C,且每个 Main C 都有自己的 Init C 等。
-
start:Main C 创建之前执行的操作,可以是一条命令或一个脚本。
-
stop:Main C 结束之后执行的操作,可以是一条命令或一个脚本。
-
readiness:容器就绪检测,可以指定在容器启动之后的某个时间点执行,比如 10 秒后。只有当 readiness 正常执行完成,该 Pod 才会显示为就绪状态。
说明:
[root@k8s-master01 ~]# kubectl get pod NAME READY STATUS RESTARTS AGE test-pod 1/1 Running 0 5s查看 Pod 可以看见结果中有
READY和STATUS两个字段。READY表示该 Pod 是否就绪(是否正常提供服务),STATUS表示该 Pod 是否正常运行。那么这两者的区别在哪里呢?假设我们现在要运行一个 tomcat 容器,其 wabapps 下有一个很大的 war 包,tomcat 要 10 秒跑起来,而这个 war 包需要 3 分钟才能跑起来。
我们现在启动 tomcat 容器,10秒后我们就可以正常访问 tomcat 了,但是我们还不能正常访问这个 war 包提供的服务,因为它正在启动当中。
如果我们没有设置
就绪检测,k8s 就会在 tomcat 容器启动之后认为这个 Pod正常运行且准备就绪,即容器里的服务正常对外提供。但实际上这个容器仅仅是正常运行,没有准备就绪。此时就需要我们设置
就绪检测,来检测 tomcat 容器中的 war 包应用是否已经启动成功。如果 war 包应用没有启动成功,那么 Pod 就没有就绪,就不会对外开放。一旦检测到 war 包应用启动成功,就把 Pod 调整为准备就绪,将 Pod 对外开放。 -
liveness:容器存活检测。用来检测 Main C 是否存活(是否正常服务),如果 Main C 不能正常提供服务,那么会根据重启策略来重启或者删除该 Pod。
Init C
Init C 介绍
Pod 能够具有多个容器,应用运行在容器里面,但是它也可能有一个或多个先于应用容器启动的 Init 容器。
Init 容器与普通的容器非常像,除了如下两点:
- Init 容器总是运行到成功完成为止。
- 每个 Init 容器都必须在下一个 Init 容器启动之前成功完成。
如果 Pod 的 Init 容器失败,Kubernetes 会不断地重启该 Pod,直到 Init 容器成功为止。然而, 如果 Pod 对应的 restartPolicy 为 Never,它不会重新启动。
因为 Init 容器具有与应用程序容器分离的单独镜像,所以它们的启动相关代码具有如下优势:
-
它们可以包含并运行实用工具,但是出于安全考虑,是不建议在应用程序容器镜像中包含这 些实用工具的。
-
它们可以包含使用工具和定制化代码来安装,但是不能出现在应用程序镜像中。例如,创建镜像没必要 from 另一个镜像,只需要在安装过程中使用类似 sed、awk、python 或 dig 这样的工具。
-
应用程序镜像可以分离出创建和部署的角色,而没有必要联合它们构建一个单独的镜像。
-
Init 容器使用 Linux Namespace,所以相对应用程序容器来说具有不同的文件系统视图。因此,它们能够具有访问 Secret 的权限,而应用程序容器则不能。
简单一点的说,应用容器需要 Linux 系统中的某个文件,但是应用容器没有权限访问,那么可以让 Init 容器去访问这个文件,然后拿给应用容器使用。
-
它们必须在应用程序容器启动之前运行完成,而应用程序容器是并行运行的,所以 Init 容器能够提供了一种简单的阻塞或延迟应用容器的启动的方法,直到满足了一组先决条件。
简单来说就是依赖启动,现有 A、B 两容器,B 的启动需要依赖 A,那么我们就可以给 B 容器创建一个用于探测 A 容器是否启动成功的 Init 容器,如果探测到 A 容器启动成功,那么才会启动 B 容器。
Init C 简单示例
apiVersion: v1
kind: Pod
metadata:
name: myapp-pod
labels:
app: myapp
spec:
containers:
- name: myapp-container
image: busybox
command: ['sh', '-c', 'echo The app is running! && sleep 3600']
initContainers:
- name: init-myservice
image: busybox
command: [ 'sh', '-c', 'until nslookup myservice; do echo waiting for myservice; sleep 2; done;']
- name: init-mydb
image: busybox
command: [ 'sh', '-c', 'until nslookup mydb; do echo waiting for mydb; sleep 2; done;']
上面使用的镜像 busybox 是一个集成了一百多个最常用Linux命令和工具的软件工具箱,如 cat 和 echo,也包含了一些更大,更复杂的工具,如 grep、find、mount 以及 telnet。同时它只有 1.22 MB 左右的大小。
将上面的资源清单保存为 init.yaml:
-
myapp-pod:Pod 名称。
-
myapp-container:主容器。使用 command 覆盖原启动命令,指定容器启动后输出
echo The app is running!然后休眠 3600 秒,然后结束命令,容器停止。因为下面声明了 Init C,所以主容器需要在 Init C 执行完后才启动。 -
init-myservice:Init C。在主容器之前启动,其 command 的意思是:监听名为 myservice 的服务,一旦监听到了就退出(即该命令结束,容器停止)。若没有监听到,则 输出
waiting for myservice然后休眠 2 秒继续监听。 -
init-mydb:Init C。在主容器之前启动,在 init-myservice 正常退出之后启动。这个容器是监听 mydb 服务。
我们使用这个清单创建 Pod:
[root@k8s-master01 ~]# kubectl create -f init.yaml
pod/myapp-pod created
[root@k8s-master01 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
myapp-pod 0/1 Init:0/2 0 11s
查看 myapp-pod 发现,该 Pod 的 READY 为0/1,即没有就绪,意味着不会向外提供服务。STATUS 为 Init:0/2,意味着存在两个 Init C,并且目前没有一个执行完成。(因为监听的 myservice 和 mydb 不存在,所以会一直卡在这儿)
我们使用 kubectl describe pod 查看这个 Pod 的详细信息:
[root@k8s-master01 ~]# kubectl describe pod myapp-pod
Name: myapp-pod
Namespace: default
......
Init Containers:
init-myservice:
......
State: Running
Started: Wed, 25 Mar 2020 15:05:27 +0800
Ready: False
......
init-mydb:
......
State: Waiting
Reason: PodInitializing
Ready: False
......
Containers:
myapp-container:
......
State: Waiting
Reason: PodInitializing
Ready: False
......
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 7m47s default-scheduler Successfully assigned default/myapp-pod to k8s-node02
Normal Pulling 7m45s kubelet, k8s-node02 Pulling image "busybox"
Normal Pulled 7m36s kubelet, k8s-node02 Successfully pulled image "busybox"
Normal Created 7m36s kubelet, k8s-node02 Created container init-myservice
Normal Started 7m36s kubelet, k8s-node02 Started container init-myservice
结果中只有 init-myservice 正在 Running, init-mydb 和 myapp-container 都是 Waiting。(Init C 按照定义顺序执行。所以先执行 init-myservice)
查看 init-myservice 容器的日志:
[root@k8s-master01 ~]# kubectl log myapp-pod -c init-myservice
waiting for myservice
Server: 10.96.0.10
Address: 10.96.0.10:53
** server can't find myservice.default.svc.cluster.local: NXDOMAIN
*** Can't find myservice.svc.cluster.local: No answer
*** Can't find myservice.cluster.local: No answer
*** Can't find myservice.default.svc.cluster.local: No answer
*** Can't find myservice.svc.cluster.local: No answer
*** Can't find myservice.cluster.local: No answer
可以看见 init-myservice 一直在监听 myservice 服务,但是一直没响应。
下面我们创建 myservice 服务:
将以下资源清单保存为 myservice.yaml :
apiVersion: v1
kind: Service
metadata:
name: myservice
spec:
ports:
- protocol: TCP
port: 80
targetPort: 9376
创建 myservice 服务并查看 Pod 状态:
[root@k8s-master01 ~]# kubectl create -f myservice.yaml
service/myservice created
[root@k8s-master01 ~]# kubectl get pod -w
NAME READY STATUS RESTARTS AGE
myapp-pod 0/1 Init:0/2 0 22m
myapp-pod 0/1 Init:1/2 0 23m
myapp-pod 0/1 Init:1/2 0 23m
结果显示 myapp-pod 的 STATUS 从 Init:0/2 变成了 Init:1/2。意味着我们的 init-myservice 容器已经监听到了 myservice 服务,正常结束了,所以现在轮到了 init-mydb 容器监听 mydb 服务。(根据上面的命令查看 Pod 详细信息,看看现在的 Pod 是什么状态)
下面我们再创建 mydb 服务:
mydb.yaml :
apiVersion: v1
kind: Service
metadata:
name: mydb
spec:
ports:
- protocol: TCP
port: 80
targetPort: 9377
创建 mydb 服务并查看 Pod 状态:
[root@k8s-master01 ~]# kubectl create -f mydb.yaml
service/mydb created
[root@k8s-master01 ~]# kubectl get pod -w
NAME READY STATUS RESTARTS AGE
myapp-pod 0/1 Init:1/2 0 103s
myapp-pod 0/1 PodInitializing 0 3m25s
myapp-pod 1/1 Running 0 3m28s
init-mydb 监听到 mydb 服务之主容器就开始启动。因此 Pod 状态变为 Running 并 READY。
Init C 特点
- 在 Pod 启动过程中,Init 容器会按顺序在网络和数据卷初始化之后启动(即在 pause 容器之后启动)。每个容器必须在下一个 容器启动之前成功退出。
- 如果由于运行时或失败退出,将导致容器启动失败,它会根据 Pod 的 restartPolicy 指定的策略进行重试。然而,如果 Pod 的 restartPolicy 设置为 Always,Init 容器失败时会使用 RestartPolicy 策略。
- 在所有的 Init 容器没有成功之前,Pod 将不会变成 Ready 状态。Init 容器的端口将不会在 Service 中进行聚集。正在初始化中的 Pod 处于 Pending 状态,但应该会将 Initializing 状态设置为true。
- 如果 Pod 重启,所有 Init 容器必须重新执行。
- 对 Init 容器 spec 的修改被限制在容器 image 字段,修改其他字段都不会生效。更改 Init 容器的 image 字段,等价于重启该Pod。
- Init 容器具有应用容器的所有字段。除了 readinessProbe,因为 Init 容器无法定义不同于完成 (conpletion)的就绪(readiness)之外的其他状态。
- 在 Pod 中的每个 app 和 Init 容器的名称必须唯一;与任何其它容器共享同一个名称,会在验证时抛出错误。
探针
探针介绍
探针是由 kubelet 对容器执行的定期诊断。要执行诊断,kubelet 调用由容器实现的 Handler。有三种类型的处理程序:
- ExecAction:在容器内执行指定命令。如果命令退出时返回码为 0 则认为诊断成功。
- TCPSocketAction:对指定端口上的容器的 IP 地址进行 TCP 检查。如果端口打开,则诊断被认为是成功的。
- HTTPGetAction:对指定的端口和路径上的容器的 IP 地址执行 HTTPGet 请求。如果响应的状态码大于等于 200 且小于 400,则诊断被认为是成功的。
每次探测都将获得以下三种结果之一:
- 成功:容器通过了诊断。
- 失败:容器未通过诊断。
- 未知:诊断失败,因此不会采取任何行动。
探针有两种类型:
- readinessProbe:探测容器是否准备好服务请求(即是否就绪 [READY])。如果就绪探测失败,端点控制器将从与 Pod 匹配的所有Service 的端点中删除该 Pod 的 IP 地址。初始延迟之前的就绪状态默认为 Failure 。如果容器不提供就绪探针,则默认状态为 Success。
- livenessProbe:探测容器是否正常运行。如果存活探测失败,则 kubelet 会杀死容器,并且容器将受到其重启策略的影响。如果容器不提供存活探针,则默认状态为 Success。
readinessProbe 简单示例
使用 HTTPGetAction方式进行就绪检测:
apiVersion: v1
kind: Pod
metadata:
name: readness-httpget-pod
spec:
containers:
- name: readness-httpget-container
image: hub.xixihaha.com/library/mynginx:v1
imagePullPolicy: IfNotPresent
readinessProbe:
httpGet:
port: 80
path: /index1.html
initialDelaySeconds: 1
periodSeconds: 3
其中:
-
image:
hub.xixihaha.com/library/mynginx:v1是本地 harbor 仓库的镜像,本质就是一个 nginx 镜像。 -
imagePullPolicy:重启策略。如果本地没有该镜像就从仓库下载。
-
readnessProbe:就绪检测。
-
httpGet:检测方式。使用 httpGet 请求该容器。
-
port:请求该容器的 80 端口。
-
path:请求 80 端口下的
/index1.html。 -
initailDelaySeconds:容器启动后 1 秒开始检测。
-
periodSeconds:如果检测失败,每 3 秒继续检测,知道检测成功或达到检测最大值。
下面创建该 Pod:
[root@k8s-master01 ~]# kubectl create -f readness-httpget.yaml
pod/readness-httpget-pod created
[root@k8s-master01 ~]# kubectl get pod -w
NAME READY STATUS RESTARTS AGE
readness-httpget-pod 0/1 Running 0 5s
查看 Pod 发现容器正常启动并已经在运行中了,但是并没有就绪。
我们可以使用 kubectl describe pod 查看详细信息:
[root@k8s-master01 ~]# kubectl describe pod readness-httpget-pod
Name: readness-httpget-pod
......
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 3m8s default-scheduler Successfully assigned default/readness-httpget-pod to k8s-node02
Normal Pulled 3m8s kubelet, k8s-node02 Container image "hub.xixihaha.com/library/mynginx:v1" already present on machine
Normal Created 3m8s kubelet, k8s-node02 Created container readness-httpget-container
Normal Started 3m8s kubelet, k8s-node02 Started container readness-httpget-container
Warning Unhealthy 2m3s (x22 over 3m6s) kubelet, k8s-node02 Readiness probe failed: HTTP probe failed with statuscode: 404
从详细信息中可以看出,容器在进行就绪检测时,因为容器中没有 /index1.html 这个页面,所以使用 httpGet 请求返回的是 404,上面说到只有 大于等于 200 且小于 400 才会认为是成功的。
下面我们进入容器创建 /index1.html 页面:
[root@k8s-master01 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
readness-httpget-pod 0/1 Running 0 10m
[root@k8s-master01 ~]# kubectl exec readness-httpget-pod -it -- /bin/sh
# cd /usr/share/nginx/html
# ls
50x.html index.html
# echo "123" >> index1.html
# ls
50x.html index.html index1.html
# exit
[root@k8s-master01 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
readness-httpget-pod 1/1 Running 0 10m
此次 Pod 已经变为就绪了。
livenessProbe 简单示例
使用 ExecAction 方式检测:
apiVersion: v1
kind: Pod
metadata:
name: liveness-exec-pod
namespace: default
spec:
containers:
- name: lineness-exec-container
image: busybox
imagePullPolicy: IfNotPresent
command: ['sh', '-c', 'touch /tmp/live; sleep 50; rm -rf /tmp/live; sleep 3600']
livenessProbe:
exec:
command: ['test', '-e', '/tmp/live']
initialDelaySeconds: 1
periodSeconds: 3
其中:
- containers.command:容器启动后创建一个名为
/tmp/live的文件夹,在 50 秒后删除这个文件夹,然后容器休眠 3600 秒。 - livenessProbe:就绪检测。
- exec:检测方式。在容器内执行指定命令。
- livenessProbe.exec.command:检测执行的命令。校验容器中是否存在
/tmp/live这样一个文件夹,若存在则返回真,否则返回假。即如果容器中不存在这个文件夹,那么存活检测失败,会根据容器的重启策略删除或重启容器。
下面创建该 Pod:
[root@k8s-master01 ~]# kubectl create -f liveness-exec-pod
pod/liveness-exec-pod created
[root@k8s-master01 ~]# kubectl get pod -w
NAME READY STATUS RESTARTS AGE
liveness-exec-pod 1/1 Running 0 7s
liveness-exec-pod 1/1 Running 1 88s
从结果看到,容器在运行一段时间后重新启动了,也就是文件被删除后的那次检测。因为我们使用的是默认的重启策略,即 Always,所以容器自动进行重启。如果我们继续观察,那么会发现,过了 60 秒左右之后,容器还会再次进行重启。
下面是使用 TCPSocketAction 和 HTTPGetAction 方式进行存活检测的例子:
# 与就绪检测的示例的意思差不多
apiVersion: v1
kind: Pod
metadata:
name: liveness-exec-pod
namespace: default
spec:
containers:
- name: lineness-exec-container
image: hub.xixihaha.com/library/mynginx:v1
imagePullPolicy: IfNotPresent
livenessProbe:
httpGet:
port: 80
path: /index1.html
initialDelaySeconds: 1
periodSeconds: 3
# 超时时间,若检测超过此时间,同样认为是检测失败
timeoutSeconds: 10
# 检测容器的 80 端口是否正常
apiVersion: v1
kind: Pod
metadata:
name: liveness-exec-pod
namespace: default
spec:
containers:
- name: lineness-exec-container
image: hub.xixihaha.com/library/mynginx:v1
imagePullPolicy: IfNotPresent
livenessProbe:
tcpSocket:
port: 80
Pod hook
Pod hook(钩子)是由 Kubernetes 管理的 kubelet 发起的,当容器中的进程启动前或者容器中的进程终止之前运行,这是包含在容器的生命周期之中,也就是上面 Pod 生命周期 中提到的 start 和 stop。可以同时为 Pod 中的所有容器都配置 hook。
Hook的类型包括两种:
- exec:执行一段命令。
- HTTP:发送HTTP请求。
简单示例:
apiVersion: v1
kind: Pod
metadata:
name: lifecycle-demo
spec:
containers:
- name: lifecycle-demo-container
image: hub.xixihaha.com/library/mynginx:v1
lifecycle:
postStart:
exec:
command: [......]
preStop:
exec:
command: [......]
Pod 相位(phase )
Pod 的 status 字段是一个 PodStatus 对象,PodStatus 中有一个 phase 字段。
Pod 的相位(phase)是 Pod 在其生命周期中的简单宏观概述。该阶段并不是对容器或 Pod 的综合汇总,也不是为了做为综合状态机。
Pod 相位的数量和含义是严格指定的,以下是当前 Pod 相位的所有值:
- 挂起(Pending):Pod 已被 Kubernetes 系统接受,但有一个或者多个容器镜像尚未创建。等待时间包括调度 Pod 的时间和通过网络下载镜像的时间。
- 运行中(Running):该 Pod 己经绑定到了一个节点上,Pod 中所有的容器都己被创建。至少有一个容器正在运行,或者正处于启动或重启状态。
- 成功(Succeeded):Pod 中的所有容器都被成功终止,并且不会再重启。
- 失畋(Failed):Pod 中的所有容器都己终止了,并且至少有一个容器是因为失败终止。也就是说,容器以非0状态退出或者被系统终止。
- 未知(Unknown):因为某些原因无法取得 Pod 的状态,通常是因为与 Pod 所在主机通信失败。
-- end --