theboyaply
theboyaply
发布于 2020-03-25 / 843 阅读
0
0

k8s Pod 生命周期

Pod 生命周期

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 --


评论