theboyaply
theboyaply
发布于 2020-03-25 / 694 阅读
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 可以看见结果中有 READYSTATUS 两个字段。 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 秒左右之后,容器还会再次进行重启。

下面是使用 TCPSocketActionHTTPGetAction 方式进行存活检测的例子:

# 与就绪检测的示例的意思差不多
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 --


评论