Pod原理以及Pod生命周期

一、Pod的介绍

1、为什么需要Pod?

假设 Kubernetes 中调度的基本单元就是容器,对于一个非常简单的应用可以直接被调度直接使用,没有什么问题,但是往往还有很多应用程序是由多个进程组成的,有人可能会说把这些进程都打包到一个容器中去不就可以了吗?理论上是可以实现的,但是不要忘记了 Docker 管理的进程是 pid=1 的主进程,其他进程死掉了就会成为僵尸进程,没办法进行管理了,这种方式本身也不是容器推荐的运行方式,一个容器最好只干一件事情,所以在真实的环境中不会使用这种方式。

那么我们就把这个应用的进程进行拆分,拆分成一个一个的容器总可以了吧?但是不要忘记一个问题,拆分成一个一个的容器后,是不是就有可能出现一个应用下面的某个进程容器被调度到了不同的节点上呀?往往我们应用内部的进程与进程间通信(通过 IPC 或者共享本地文件之类)都是要求在本地进行的,也就是需要在同一个节点上运行。

所以我们需要一个更高级别的结构来将这些容器绑定在一起,并将他们作为一个基本的调度单元进行管理,这样就可以保证这些容器始终在同一个节点上面,这也就是 Pod 设计的初衷。

2、Pod原理

其实 Pod 也只是一个逻辑概念,真正起作用的还是 Linux 容器的 Namespace 和 Cgroup 这两个最基本的概念,Pod 被创建出来其实是一组共享了一些资源的容器而已。首先 Pod 里面的所有容器,都是共享的同一个 Network Namespace,但是涉及到文件系统的时候,默认情况下 Pod 里面的容器之间的文件系统是完全隔离的,但是我们可以通过声明来共享同一个 Volume。

2.1、如何共享Network Namespace?

对于共享一个Network Namespace,这个就和Docker网络模式中的Container模式比较相似,可以指定新创建的容器可以和一个已经运行的容器共享一个Network Namespace,在运行容器的时候只需要指定--net=container:目标容器名这个参数就可以了,但是这种模式有一个明显的问题,就是容器启动的先后顺序。那么Pod是怎么解决这个问题的呢? 那就是加入一个中间容器(没有什么架构是加一个中间件解决不了的?),这个容器叫做 Infra 容器,而且这个容器在 Pod 中永远都是第一个被创建的容器,这样是不是其他容器都加入到这个 Infra 容器就可以了,这样就完全实现了 Pod 中的所有容器都和 Infra 容器共享同一个 Network Namespace 了 。

image

从上面图中我们可以看出普通的容器加入到了 Infra 容器的 Network Namespace 中,所以这个 Pod 下面的所有容器就是共享同一个 Network Namespace 了,普通容器不会创建自己的网卡,配置自己的 IP,而是和 Infra 容器共享 IP、端口范围等,而且容器之间的进程可以通过 lo 网卡设备进行通信,也就是说容器之间是可以直接使用localhost进行通信,看到的网络设备信息都是和Infra容器一样,也就意味着同一个Pod下面的容器运行的多个进程不能绑定相同的端口,而且Pod的声明周期只和Infra容器一致,和容器A、B无关。

2.2、文件系统如何实现共享?

默认情况下容器的文件系统是互相隔离的,要实现共享只需要在 Pod 的顶层声明一个 Volume,然后在需要共享这个 Volume 的容器中声明挂载即可。

image

二、Pod的资源清单

在Kubernetes中基本所有资源的一级属性都是一样的 ,主要包含五个部分:

  • apiVersion : 版本,由Kubernetes内部定义,版本号必须用kubectl api-versions查询。
  • kind : 类型,由Kubernetes内部定义,类型必须用kubectl api-resources查询。
  • metadata : 元数据,主要是资源标示和说明,常用的有name,namespace,labels等。
  • spec :描述,这是配置中最重要的一部分,里面是对各种资源配置的详细描述。
  • status :状态信息,里面的内容不需要定义,由Kubernetes自动生成。

我们可以通过kubectl explain 资源类型命令进行查看资源的可配置项,比如我们查看Pod的一级配置:

# 查看pod的一级配置
kubectl explain pod

KIND:     Pod
VERSION:  v1

DESCRIPTION:
     Pod is a collection of containers that can run on a host. This resource is
     created by clients and scheduled onto hosts.

FIELDS:
   apiVersion	<string>
     APIVersion defines the versioned schema of this representation of an
     object. Servers should convert recognized schemas to the latest internal
     value, and may reject unrecognized values. More info:
     https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources

   kind	<string>
     Kind is a string value representing the REST resource this object
     represents. Servers may infer this from the endpoint the client submits
     requests to. Cannot be updated. In CamelCase. More info:
     https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds

   metadata	<object>
     Standard object's metadata. More info:
     https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata

   spec	<object>
     Specification of the desired behavior of the pod. More info:
     https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status

   status	<object>
     Most recently observed status of the pod. This data may not be up to date.
     Populated by the system. Read-only. More info:
     https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status

我们看到其中 spec 字段是一个 <Object> 类型的,证明该字段下面是一个对象,我们可以继续去查看这个字段下面的详细信息,这也是我们接下来重点要研究的。

pod.sepc常见的属性如下:

  • containers <[]Object>:容器列表,用于定义容器的详细信息。
  • nodeName :根据nodeName的值将Pod调度到指定的Node节点上。
  • nodeSelector <map[]> :根据NodeSelector中定义的信息选择该Pod调度到包含这些Label的Node上。
  • hostNetwork :是否使用主机网络模式,默认为false,如果设置为true,表示使用宿主机网络。
  • volumes <[]Object> :存储卷,用于定义Pod上面挂载的存储信息。
  • restartPolicy :重启策略,表示Pod在遇到故障的时候的处理策略。

三、配置Pod

Pod中最关键的配置项是什么呢?肯定是containers了。

我们同样使用命令进行查看pod.spec.containers需要哪些配置项。

# 查看pod.spec.containers的可选配置项
kubectl explain pod.spec.containers

KIND:     Pod
VERSION:  v1
RESOURCE: containers <[]Object>   # 数组,代表可以有多个容器FIELDS:
  name  <string>     # 容器名称
  image <string>     # 容器需要的镜像地址
  imagePullPolicy  <string> # 镜像拉取策略 
  command  <[]string> # 容器的启动命令列表,如不指定,使用打包时使用的启动命令
  args   <[]string> # 容器的启动命令需要的参数列表 
  env    <[]Object> # 容器环境变量的配置
  ports  <[]Object>  # 容器需要暴露的端口号列表
  resources <object> # 资源限制和资源请求的设置

1、基本配置

创建pod_base.yaml文件

apiVersion: v1
kind: Pod
metadata:
  name: pod-base  # 此处不能使用_
  namespace: dev  # 指定namespace
  labels:
    user: Negan
spec:
  containers:
    - name: nginx # 容器名称
      image: nginx:1.17.1  # 容器需要的镜像地址
    - name: busybox # 容器名称
      image: busybox:1.30  # 容器需要的镜像地址          

上面定义了一个比较简单的Pod的配置,里面有两个容器:

nginx:用的是1.17.1版本的nginx镜像创建(nginx是一个轻量级的web容器)。

busybox:用的是1.30版本的busybox镜像创建(busybox是一个小巧的linux命令集合)。

接下来我们可以创建Pod以及查看pod状态:

# 创建pod
kubectl apply -f pod_base.yaml

# 查看Pod
kubectl get pods pod-base -n dev
NAME       READY   STATUS             RESTARTS   AGE
pod-base   1/2     ImagePullBackOff   1          2m2s

# 我们发现,当前Pod中有两个容器,但是准备就绪只有一个。
# 再次查看,发现重试了3次
kubectl get pods pod-base -n dev
NAME       READY   STATUS             RESTARTS   AGE
pod-base   1/2     CrashLoopBackOff   3          3m2s

# 查看内部详情
kubectl describe pods pod-base -n dev

# 我们只关心Events部分,
Events:
  Type     Reason     Age                From               Message
  ----     ------     ----               ----               -------
  Normal   Scheduled  <unknown>          default-scheduler  Successfully assigned dev/pod-base to node2
  Normal   Pulled     75s                kubelet, node2     Container image "nginx:1.17.1" already present on machine
  Normal   Created    73s                kubelet, node2     Created container nginx
  Normal   Started    73s                kubelet, node2     Started container nginx
  Normal   Pulling    73s                kubelet, node2     Pulling image "busybox:1.30"
  Normal   Pulled     49s                kubelet, node2     Successfully pulled image "busybox:1.30"
  Normal   Pulled     25s (x2 over 47s)  kubelet, node2     Container image "busybox:1.30" already present on machine
  Normal   Created    24s (x3 over 49s)  kubelet, node2     Created container busybox
  Normal   Started    24s (x3 over 48s)  kubelet, node2     Started container busybox
  Warning  BackOff    11s (x4 over 41s)  kubelet, node2     Back-off restarting failed container
# 发现启动busybox失败了。

2.、command命令

在前面的案例中,busybox容器没有运行成功,那么到底是什么原因导致这个容器的故障呢?

原来busybox并不是一个程序,而是类似于一个工具类的集合,Kubernetes集群启动管理后,它会自动关闭,解决方法就是让其一直在运行,这就用到了command的配置。

创建pod_command.yaml文件:

apiVersion: v1
kind: Pod
metadata:
  name: pod-command
  namespace: dev
  labels:
    user: Negan
spec:
  containers:
    - name: nginx # 容器名称
      image: nginx:1.17.1  # 容器需要的镜像地址
    - name: busybox # 容器名称
      image: busybox:1.30  # 容器需要的镜像地址
      command: ["/bin/sh","-c","touch /tmp/hello.txt;while true; do /bin/echo $(date +%T) >> /tmp/hello.txt;sleep 3;done;"]
      
# 说明:command用于在Pod中的容器初始化完毕之后执行一个命令
# "/bin/sh","-c":使用sh执行命令
# touch /tmp/hello.txt:创建一个/tmp/hello.txt的文件
# while ture;do /bin/echo $(date +%T) >> /tmp/hello.txt:sleep 3;done:每隔三秒,向文件写入当前时间

使用kubectl apply -f pod_command.yaml命令进行创建pod,并确保Pod创建成功后,我们可以使用kubectl exec命令进入容器内部。

kubectl exec -it pod名称 -n 命名空间 -c 容器名称 /bin/sh

kubectl exec -it pod-command -n dev -c busybox /bin/sh

kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl kubectl exec [POD] -- [COMMAND] instead.
/ # tail -f tmp/hello.txt
10:02:15
10:02:18
10:02:21
10:02:24
10:02:27
10:02:30
10:02:33
10:02:36
...

特别说明

​ 通过上面发现command已经可以完成启动命令和传递参数的功能,为什么还要提供一个args选项,用于传递参数?其实和Docker有点关系,Kubernetes中的command和args两个参数其实是为了实现覆盖Dockerfile中的ENTRYPOINT的功能:

1 、如果command和args均没有写,那么用Dockerfile的配置。

2 、如果command写了,但是args没有写,那么Dockerfile默认的配置会被忽略,执行注入的command。

​ 3、如果command没有写,但是args写了,那么Dockerfile中配置的ENTRYPOINT命令会被执行,使用当前args的参数。

​ 4、如果command和args都写了,那么Dockerfile中的配置会被忽略,执行command并追加上args参数。

3、镜像拉取策略以及环境变量设置

上面的两个案例都使用了Nginx镜像,我们不经回想,第一次,本地没有Nginx,肯定是远程仓库拉取的,那么第二次是从本地拉取呢还是从远程仓库拉取的呢?

Kubernetes支持配置三种拉取策略,使用imagePullPolicy设置镜像拉取的策略,:

  • Always: 总是从远程仓库拉取镜像(一直远程下载)
  • IfNotPresent: 本地有则使用本地镜像,本地没有则从远程仓库拉取镜像(本地有就用本地,本地没有就使用远程下载)。
  • Never:只使用本地镜像,从不去远程仓库拉取,本地没有就报错(一直使用本地,没有就报错)

默认值说明:

如果镜像tag为具体版本号,默认策略是IfNotPresent;

如果镜像tag为latest,默认策略是Always。

当然我们也可以给Pod中的容器设置环境变量

env:环境变量,用于在Pod中的容器设置环境变量。

下面我们创建pod_image_env.yaml文件进行测试Kubernetes的镜像拉取策略和环境变量的设置:

apiVersion: v1
kind: Pod
metadata:
  name: pod-image-env
  namespace: dev
  labels:
    user: Negan
spec:
  containers:
    - name: nginx # 容器名称
      image: nginx:1.17.1  # 容器需要的镜像地址
      imagePullPolicy: Always  # 设置镜像的拉取策略
    - name: busybox # 容器名称
      image: busybox:1.30  # 容器需要的镜像地址
      command: ["/bin/sh","-c","touch /tmp/hello.txt;while true; do /bin/echo $(date +%T) >> /tmp/hello.txt;sleep 3;done;"]
      env:
        - name: "username"
          value: "admin"
        - name: "password"
          value: "123456"

Pod创建成功后,我们进入容器内部验证环境变量:

kubectl exec -it pod-image-env -n dev -c busybox -it /bin/sh

kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl kubectl exec [POD] -- [COMMAND] instead.
/ # echo $username
admin
/ # echo $password
12345

4、端口设置

到此我们已经可以自行创建pod了,但是我们创建的pod如何对外提供服务呢?或者准确的说,运行在Pod上的容器怎么对外提供服务呢?在启动Docker的时候我们可以通过-p参数来设置端口,那么Kubernetes是否也可以指定端口呢?答案是肯定的。

接下来我们用kubectl explain pod.spec.containers.ports命令来查看posts支持的子选项:

kubectl explain pod.spec.containers.ports

KIND:     Pod
VERSION:  v1
RESOURCE: ports <[]Object>
FIELDS:
  name <string> # 端口名称,如果指定,必须保证name在pod中是唯一的
  containerPort <integer> # 容器要监听的端口(0<x<65536) hostport="" <integer=""> # 容器要在主机上公开的端口,如果设置,主机上只能运行容器的一个副本(一般省略)
  hostIP <string>  # 要将外部端口绑定到的主机IP(一般省略)
  protocol <string>  # 端口协议。必须是UDP、TCP或SCTP。默认为“TCP”

#----------------------------------------------------------------
KIND:     Pod
VERSION:  v1

RESOURCE: ports <[]Object>

DESCRIPTION:
     List of ports to expose from the container. Exposing a port here gives the
     system additional information about the network connections a container
     uses, but is primarily informational. Not specifying a port here DOES NOT
     prevent that port from being exposed. Any port which is listening on the
     default "0.0.0.0" address inside a container will be accessible from the
     network. Cannot be updated.

     ContainerPort represents a network port in a single container.

FIELDS:
   containerPort	<integer> -required-
     Number of port to expose on the pod's IP address. This must be a valid port
     number, 0 < x < 65536.

   hostIP	<string>
     What host IP to bind the external port to.

   hostPort	<integer>
     Number of port to expose on the host. If specified, this must be a valid
     port number, 0 < x < 65536. If HostNetwork is specified, this must match
     ContainerPort. Most containers do not need this.

   name	<string>
     If specified, this must be an IANA_SVC_NAME and unique within the pod. Each
     named port in a pod must have a unique name. Name for the port that can be
     referred to by services.

   protocol	<string>
     Protocol for port. Must be UDP, TCP, or SCTP. Defaults to "TCP".

接下来我们创建pod_ports.yaml文件,内容如下:

apiVersion: v1
kind: Pod
metadata:
  name: pod-ports
  namespace: dev
  labels:
    user: Negan
spec:
  containers:
    - name: nginx # 容器名称
      image: nginx:1.17.1  # 容器需要的镜像地址
      imagePullPolicy: IfNotPresent  # 设置镜像的拉取策略
      ports: 
        - name: nginx-port  # 端口名称,如果执行,必须保证name在Pod中是唯一的
          containerPort: 80 # 容器要监听的端口
          protocol: TCP # 端口协议

访问Pod中的容器用PodIP:ContainerPort的方式进行访问。

5、资源配额

容器中的程序要运行,肯定会占用一定的资源,比如CPU和内存等,如果不对某个容器的资源做限制,那么它就可能吃掉大量的资源,导致其他的容器无法运行。针对这种情况,Kubernetes提供了对内存和CPU的资源进行配额的机制,这种机制主要通过resources选项实现,它有两个子选项:

​ 1 、limits:用于限制运行的容器的最大占用资源,当容器占用资源超过limits时会被终止,并进行重启。

​ 2、requests:用于设置容器需要的最小资源,如果环境资源不够,容器将无法启动。

可以通过上面的两个选项设置资源的上下限。

下面我们创建pod_resoures.yaml文件,测试资源配额。

apiVersion: v1
kind: Pod
metadata:
  name:  pod-resoures
  namespace: dev
  labels:
    user: Negan
spec:
  containers:
    - name: nginx # 容器名称
      image: nginx:1.17.1  # 容器需要的镜像地址
      imagePullPolicy: IfNotPresent  # 设置镜像的拉取策略
      ports:
        - name: nginx-port  # 端口名称,如果执行,必须保证name在Pod中是唯一的
          containerPort: 80 # 容器要监听的端口
          protocol: TCP # 端口协议
      resources: # 资源配额
        limits: # 限制资源的上限
          cpu: "2"  # CPU限制,单位是core数
          memory: "10Gi"  # 内存限制
        requests: # 限制资源的下限
          cpu: "1"  
          memory: "10Mi"

这里对cpu和memory的单位做一个说明:

  • CPU:core数,可以为整数或小数
  • memory:内存大小,可以使用Gi,Mi,G,M等形式

启动Pod并查看状态:

kubectl get pods pod-resoures -n dev

NAME           READY   STATUS    RESTARTS   AGE
pod-resoures   1/1     Running   0          20s

发现正常启动,接下来我们修改“requests”中的memory的值为10Gi。

apiVersion: v1
kind: Pod
metadata:
  name: pod-resoures
  namespace: dev
  labels:
    user: Negan
spec:
  containers:
    - name: nginx # 容器名称
      image: nginx:1.17.1  # 容器需要的镜像地址
      imagePullPolicy: IfNotPresent  # 设置镜像的拉取策略
      ports:
        - name: nginx-port  # 端口名称,如果执行,必须保证name在Pod中是唯一的
          containerPort: 80 # 容器要监听的端口
          protocol: TCP # 端口协议
      resources: # 资源配额
        limits: # 限制资源的上限
          cpu: "2"  # CPU限制,单位是core数
          memory: "10Gi"  # 内存限制
        requests: # 限制资源的下限
          cpu: "1"  
          memory: "10Gi"

重新启动并查看

# 删除原来的pod
kubectl delete -f pod_resoures.yaml
# 启动
kubectl apply -f pod_resoures.yaml 

# 查看
kubectl get pods pod-resoures -n dev
NAME           READY   STATUS    RESTARTS   AGE
pod-resoures   0/1     Pending   0          18s

# 查看详情
kubectl describe pods pod-resoures -n dev

# 我们发现提示内存不足
Events:
  Type     Reason            Age        From               Message
  ----     ------            ----       ----               -------
  Warning  FailedScheduling  <unknown>  default-scheduler  0/3 nodes are available: 3 Insufficient memory.

四、Pod的生命周期

我们一般将Pod对象从创建到终止的这段时间范围称为Pod的生命周期,它主要包含下面的过程:

​ 1)Pod创建过程;

​ 2)运行初始化容器(init container)过程;

​ 3)运行主容器(main container):

​ ① 容器启动后钩子(post start)、容器终止前钩子(pre stop)

​ ②容器存活性探测(liveness probe)、就绪性探测(readiness probe)

​ 4) Pod终止过程。

在开始之前,我们首先了解下Pod的状态值,我们可以通过kubectl explain pod.status命令来了解关于Pod的一些信息,Pod的状态定义在PodStatus对象中,其中有一个phase字段,下面是phase的可能取值

  • 挂起(Pending):Pod信息已经提交给了集群,但它尚未被调度完成或仍处于下载镜像的过程中。
  • 运行中(Running):Pod已经被调度到某节点,并且所有容器已经被Kubectl创建完成。
  • 成功(Succeeded):Pod中的所有容器都已经成功终止并且不会被重启。
  • 失败(Failed):所有容器都已经终止,并且至少有一个容器是因为失败终止,即容器返回了非0值的退出状态或被系统终止。
  • 未知(Unknown):API Server无法正常获取到Pod对象的状态信息,通常由于网络通信失败所导致。

除此之外,PodStatus对象中还包含一个PodCondition的数组,里面包含的属性有:

  • lastProbeTime:最后一次探测 Pod Condition 的时间戳。
  • lastTransitionTime:上次 Condition 从一种状态转换到另一种状态的时间。
  • message:上次 Condition 状态转换的详细描述。
  • message:上次 Condition 状态转换的详细描述。
  • reason:Condition 最后一次转换的原因。
  • status:Condition 状态类型,可以为 “True”, “False”, and “Unknown”.
  • type:Condition 类型,包括以下方面:
    • PodScheduled(Pod 已经被调度到其他 node 里)
    • Ready(Pod 能够提供服务请求,可以被添加到所有可匹配服务的负载平衡池中)
    • Initialized(所有的init containers已经启动成功)
    • Unschedulable(调度程序现在无法调度 Pod,例如由于缺乏资源或其他限制)
    • ContainersReady(Pod 里的所有容器都是 ready 状态)

1、Pod的创建过程

​ 1)、用户通过kubectl或其他的api客户端提交需要创建的Pod信息给API Server

​ 2)、API Server开始生成Pod对象的信息,并将信息存入ETCD,然后返回确认信息到客户端。

​ 3)、API Server开始反映ETCD中Pod对象的变化,其他组件使用watch机制来跟踪检查API Server上的变动。

​ 4)、Scheduler发现有新的Pod对象要创建,开始为Pod分配主机并将结果信息更新至API Server

​ 5)、Node节点上的kubelet发现有Pod调度过来,尝试调度Docker启动容器,并将结果返回给API Server

​ 6)、API Server将接收到的Pod状态信息存入到ETCD中。

2、Pod的终止过程

​ 1)、用户向API Server发送删除Pod对象的命令。

​ 2)、API Server中的Pod对象信息会随着时间的推移而更新,在宽限期内(30s),Pod被视为dead。

​ 3)、将Pod标记为terminating状态。

​ 4)、kubelet在监控到Pod对象转为terminating状态的同时启动Pod关闭过程。

​ 5)、端点控制器监控到Pod对象的关闭行为时,将其从所有匹配到此端点的service资源的端点列表中移除。

​ 6)、如果当前Pod对象定义了Pre Stop钩子处理器,则在其标记为terminating后会以同步的方式启动执行。

​ 7)、Pod对象中的容器进程收到停止信号。

​ 8)、宽限期结束后,如果Pod中还存在运行的进程,那么Pod对象会收到立即终止的信号。

​ 9)、kubelet请求API Server将此Pod资源的宽限期设置为0从而完成删除操作,此时Pod对于用户来说已经不可用了。

3、初始化容器

初始化容器Init Container是在Pod的主容器启动之前要运行的容器,主要是做一些主容器的前置工作,可以是一个 它具有两大特征:

  • 初始化容器必须运行完成直至结束,如果某个初始化容器运行失败,那么Kubernetes需要重启它直至成功完成。

  • 初始化容器必须按照定义的顺序执行,当且仅当前一个成功之后,后面一个才能运行。

初始化容器有很多应用场景,下面列出的是最常见的几种:

  • 提供主容器镜像中不具备的工具程序或自定义代码。
  • 初始化容器要先于应用容器串行启动并运行完成;因此可用于延后应用容器的启动直至其依赖的条件得到满足。

案例

假设要以主容器来运行Nginx,但是要求在运行Nginx之前要能够连接上MySQL和Redis所在的服务器。

为了简化测试,实现规定好MySQL和Redis所在的IP地址分别为192.168.209.120,192.168.209.121(注意这两个IP都不能ping通)

第一步,创建pod_initcontainer.yaml文件

apiVersion: v1
kind: Pod
metadata:
  name: pod-initcontainers
  namespace: dev
  labels:
    user: Negan
spec:
  containers:
    - name: nginx # 容器名称
      image: nginx:1.17.1  # 容器需要的镜像地址
      imagePullPolicy: IfNotPresent  # 设置镜像的拉取策略
      ports:
        - name: nginx-port  # 端口名称,如果执行,必须保证name在Pod中是唯一的
          containerPort: 80 # 容器要监听的端口
          protocol: TCP # 端口协议
      resources: # 资源配额
        limits: # 限制资源的上限
          cpu: "2"  # CPU限制,单位是core数
          memory: "10Gi"  # 内存限制
        requests: # 限制资源的下限
          cpu: "1"
          memory: "10Mi"
  initContainers: # 初始化容器配置
    - name: test-mysql
      image: busybox:1.30
      type: NodePort # Service类型为NodePort
      command: ["sh","-c","until ping 192.168.209.120 -c 1;do echo waiting for mysql ...;sleep 2;done;"]
      securityContext:
        privileged: true # 使用特权模式运行
    - name: test-redis
      image: busybox:1.30
      type: NodePort # Service类型为NodePort
      command: ["sh","-c","until ping 192.168.209.121 -c 1;do echo waiting for redis ...;sleep 2; done;"]

接下来启动并动态查看状态

# 启动
kubectl create -f pod_initcontainers.yaml

# 查看状态,发现没有准备就绪
kubectl get pods pod-initcontainers -n dev -w
NAME                 READY   STATUS     RESTARTS   AGE
pod-initcontainers   0/1     Init:0/2   0          74s

我们发现,Pod没有准备就绪,状态是Init。

我们新开一个窗口,给网卡添加IP

# 新开一个shell窗口,执行给master主机添加ip
ifconfig ens32:1 192.168.209.120 netmask 255.255.255.0 up
ifconfig ens32:2 192.168.209.121 netmask 255.255.255.0 up

回到我们原来的窗口,会发现神奇的事情发生了

NAME                 READY   STATUS     RESTARTS   AGE
pod-initcontainers   0/1     Init:0/2   0          4m30s
pod-initcontainers   0/1     Init:1/2   0          6m40s
pod-initcontainers   0/1     Init:1/2   0          6m41s
pod-initcontainers   0/1     PodInitializing   0          6m53s
pod-initcontainers   1/1     Running           0          6m54s

4、Pod Hook

我们知道 Pod 是 Kubernetes 集群中的最小单元,而 Pod 是由容器组成的,所以在讨论 Pod 的生命周期的时候我们可以先来讨论下容器的生命周期。实际上 Kubernetes 为我们的容器提供了生命周期的钩子,就是我们说的Pod Hook,Pod Hook 是由 kubelet 发起的,当容器中的进程启动前或者容器中的进程终止之前运行,这是包含在容器的生命周期之中。我们可以同时为 Pod 中的所有容器都配置 hook。

Kubernetes 为我们提供了两种钩子函数:

  • PostStart:容器创建之后执行,但是,并不能保证钩子将在容器 ENTRYPOINT 之前运行,因为没有参数传递给处理程序。主要用于资源部署、环境准备等。不过需要注意的是如果钩子花费太长时间以至于不能运行或者挂起,容器将不能达到 running 状态。

  • PreStop:容器终止之前执行,它是阻塞的,意味着它是同步的,所以它必须在删除容器的调用发出之前完成。主要用于优雅关闭应用程序、通知其他系统等。如果钩子在执行期间挂起,Pod 阶段将停留在 running 状态并且永不会达到 failed 状态。

如果 PostStart 或者 PreStop 钩子失败, 它会杀死容器。所以我们应该让钩子函数尽可能的轻量。当然有些情况下,长时间运行命令是合理的, 比如在停止容器之前预先保存状态。

另外我们有三种方式来实现上面的钩子函数:

1、exec - 用于执行一特定的命令,不过要注意的是该命令消耗的资源会被计入容器。

......
    lifecycle:
        postStart:
            exec:
                command:
                    - cat
                    - /tmp/healthy
......

2、tcpSocket - 在当前容器尝试访问指定的socket

......
    lifecycle:
        postStart:
            tcpSocket:
                port:8080
......

3、httpGet - 在当前容器中向某url发起HTTP请求

......
    lifecycle:
        postStart:
            httpGet:
                path:/ # url地址
                port:80  # 端口号
                host:192.168.209.128  # 主机地址
                scheme: HTTP  # 支持的协议,http或https
......

以下示例中,定义了一个Nginx Pod,其中设置了PostStart钩子函数,即在容器创建成功后,写入一句话到/usr/share/message文件中:

apiVersion: v1
kind: Pod
metadata:
  name: hook-demo1
  namespace: dev
spec:
  containers:
    - name: hook-demo1
      image: nginx:1.17.1
      imagePullPolicy: IfNotPresent
      ports:
        - name: nginx-port  
          containerPort: 80 
          protocol: TCP 
      lifecycle:
        postStart:
          exec:
            command: ["/bin/sh","-c","echo Hello from postStart handler > /usr/share/message"]

直接创建上面的pod:

kubectl apply -f pod-postart.yaml
kubectl get pods -n dev

NAME                            READY   STATUS    RESTARTS   AGE
hook-demo1                      1/1     Running   0          43s

创建成功后,可以查看容器中/usr/share/message文件内容是否正确:

# kubectl exec -it hook-demo1 cat /usr/share/message -n dev
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl kubectl exec [POD] -- [COMMAND] instead.
Hello from postStart handler

下面的示例与上面的示例功能基本类似,只是增加了容器终止前的钩子:

apiVersion: v1
kind: Pod
metadata:
  name: pod-hook-exec
  namespace: dev
  labels:
    user: Negan
spec:
  containers:
    - name: nginx # 容器名称
      image: nginx:1.17.1  # 容器需要的镜像地址
      imagePullPolicy: IfNotPresent  # 设置镜像的拉取策略
      ports:
        - name: nginx-port  # 端口名称,如果执行,必须保证name在Pod中是唯一的
          containerPort: 80 # 容器要监听的端口
          protocol: TCP # 端口协议
      resources: # 资源配额
        limits: # 限制资源的上限
          cpu: "2"  # CPU限制,单位是core数
          memory: "10Gi"  # 内存限制
        requests: # 限制资源的下限
          cpu: "1"
          memory: "10Mi"
      lifecycle: # 声明周期配置
        postStart: # 容器创建之后执行,如果失败就会重启容器
          exec: # 在容器启动之后,执行一条命令,修改Nginx的首页内容
            command: ["/bin/sh","-c","echo postStart ... > /usr/share/nginx/html/index.html"]
        preStop: # 容器终止之前执行,执行完毕之后,容器将终止,在其完成之前会阻塞删除容器操作
          exec: # 在容器终止前,停止Nginx
            command: ["/usr/sbin/nginx", "-s", "quit"]

创建Pod,查看pod,以及访问Pod

# 创建
kubectl create -f pod_hook_exec.yaml 
> pod/pod-hook-exec created

# 查看
kubectl get pods pod-hook-exec -n dev -o wide

NAME            READY   STATUS    RESTARTS   AGE   IP              NODE    NOMINATED NODE   READINESS GATES
pod-hook-exec   1/1     Running   0          48s   192.168.104.4   node2   <none>           <none>

# 访问
curl 192.168.104.4
postStart ...

5、Pod健康检查

现在在 Pod 的整个生命周期中,能影响到 Pod 的就只剩下健康检查这一部分了。在 Kubernetes 集群当中,我们可以通过配置liveness probe(存活探针)和readiness probe(可读性探针)来影响容器的生命周期:

  • kubelet 通过使用 liveness probe 来确定你的应用程序是否正在运行,通俗点将就是是否还活着。一般来说,如果你的程序一旦崩溃了, Kubernetes 就会立刻知道这个程序已经终止了,然后就会重启这个程序。而我们的 liveness probe 的目的就是来捕获到当前应用程序还没有终止,还没有崩溃,如果出现了这些情况,那么就重启处于该状态下的容器,使应用程序在存在 bug 的情况下依然能够继续运行下去。
  • kubelet 使用 readiness probe 来确定容器是否已经就绪可以接收流量过来了。这个探针通俗点讲就是说是否准备好了,现在可以开始工作了。只有当 Pod 中的容器都处于就绪状态的时候 kubelet 才会认定该 Pod 处于就绪状态,因为一个 Pod 下面可能会有多个容器。当然 Pod 如果处于非就绪状态,那么我们就会将它从 Service 的 Endpoints 列表中移除出来,这样我们的流量就不会被路由到这个 Pod 里面来了。

说明:

livenessProbe:存活性探测,决定是否重启容器。

readinessProbe:就绪性探测,决定是否将请求转发给容器。

k8s在1.16版本之后新增了startupProbe探针,用于判断容器内应用程序是否已经启动。如果配置了startupProbe探针,就会先禁止其他的探针,直到startupProbe探针成功为止,一旦成功将不再进行探测。

上面的两种探针目前均支持三种探测方式:

1)、exec

在容器内执行一次命令,如果命令执行的退出码为0,则认为程序正常,否则不正常。

......
    livenessProbe:
        postStart:
            exec:
                command:
                    - cat
                    - /tmp/healthy
......

案例

下面案例是创建一个pod,然后对这个pod进行健康检查,执行一个不可能完成的任务,然后容器会不断的重启。

apiVersion: v1
kind: Pod
metadata:
  name: pod-liveness-exec
  namespace: dev
  labels:
    user: Negan
spec:
  containers:
    - name: nginx # 容器名称
      image: nginx:1.17.1  # 容器需要的镜像地址
      imagePullPolicy: IfNotPresent  # 设置镜像的拉取策略
      ports:
        - name: nginx-port  # 端口名称,如果执行,必须保证name在Pod中是唯一的
          containerPort: 80 # 容器要监听的端口
          protocol: TCP # 端口协议
      livenessProbe: # 生命周期配置
        exec: # 在容器启动之后,执行一条命令,修改Nginx的首页内容
          command: ["/bin/cat","/tmp/hello.txt"] # 执行一个查看文件的命令,必须失败,因为根本没有这个文件

接着我们创建上面声明的pod,并查看其状态:

# kubectl apply -f pod_liveness-exec.yaml 

# kubectl get pods -n dev
NAME                 READY   STATUS     RESTARTS   AGE
pod-liveness-exec    1/1     Running    2          62s

发现RESTARTS不为0,而是2,说明Pod重启了两次,那么我们通过describe查看详情。

kubectl describe pods pod-liveness-exec -n dev

Events:
  Type     Reason     Age                   From               Message
  ----     ------     ----                  ----               -------
  Normal   Scheduled  2m47s                 default-scheduler  Successfully assigned dev/pod-liveness-exec to node2
  Normal   Pulled     78s (x4 over 2m45s)   kubelet, node2     Container image "nginx:1.17.1" already present on machine
  Normal   Created    78s (x4 over 2m45s)   kubelet, node2     Created container nginx
  Normal   Killing    78s (x3 over 2m17s)   kubelet, node2     Container nginx failed liveness probe, will be restarted
  Normal   Started    77s (x4 over 2m44s)   kubelet, node2     Started container nginx
  Warning  Unhealthy  68s (x10 over 2m38s)  kubelet, node2     Liveness probe failed: /bin/cat: /tmp/hello.txt: No such file or directory

观察上面的信息就会发现nginx容器启动之后就进行了健康检查,检查失败之后,容器被kill掉,然后尝试进行重启,这是重启策略的作用。

稍等一会之后,再观察Pod的信息,就会看到RESTARTS不再是2,而是一直增长。

kubectl get pods -n dev

NAME                 READY   STATUS     RESTARTS   AGE
pod-liveness-exec    1/1     Running    6          4m18s

接下来我们尝试修改探测指针执行的任务,使其能成功探测。

首先删除已经运行的pod:

kubectl delete -f pod_liveness_exec.yaml

修改command命令:

command: ["/bin/ls","/tmp"] # 查看tmp目录

创建并查看,发现存活性探测指针已经能正常探测了。

kubectl apply -f pod_liveness-exec.yaml 

kubectl get pods -n dev
NAME                 READY   STATUS     RESTARTS   AGE
pod-liveness-exec    1/1     Running    0          5s

2)、tcpSocket

将会尝试访问一个用户容器的端口,如果能够建立这条连接,则认为程序正常,否则不正常。

......
   livenessProbe:
      tcpSocket:
         port: 8080
......

案例

下面案例主要是通过tcpSocket监听8080端口,来判断Pod中的容器是否正常。

apiVersion: v1
kind: Pod
metadata:
  name: pod-liveness-socket
  namespace: dev
  labels:
    user: Negan
spec:
  containers:
    - name: nginx # 容器名称
      image: nginx:1.17.1  # 容器需要的镜像地址
      imagePullPolicy: IfNotPresent  # 设置镜像的拉取策略
      ports:
        - name: nginx-port  # 端口名称,如果执行,必须保证name在Pod中是唯一的
          containerPort: 80 # 容器要监听的端口
          protocol: TCP # 端口协议
      livenessProbe: # 生命周期配置
        tcpSocket:
          port: 8080 # 尝试访问8080端口,失败,Pod中只有一个Nginx,监听80

创建后查看Pod状态:

kubectl get pod pod-liveness-socket -n dev
NAME                  READY   STATUS    RESTARTS   AGE
pod-liveness-socket   1/1     Running   1          58s

kubectl describe pod pod-liveness-socket -n dev

Events:
  Type     Reason     Age   From               Message
  ----     ------     ----  ----               -------
  Normal   Scheduled  36s   default-scheduler  Successfully assigned dev/pod-liveness-socket to node1
  Normal   Pulled     10s   kubelet, node1     Container image "nginx:1.17.1" already present on machine
  Normal   Created    10s   kubelet, node1     Created container nginx
  Normal   Started    10s   kubelet, node1     Started container nginx
  Warning  Unhealthy  7s    kubelet, node1     Liveness probe failed: dial tcp 192.168.166.131:8080: connect: connection refused

通过上面的Events信息,我们很容易发现,Liveness探针失败了。

3)、httpGet

调用容器内web应用的URL,如果返回的状态码在200和399之前,则认为程序正常,否则不正常。

......
   livenessProbe:
      httpGet:
         path: / #URI地址
         port: 80 #端口号
         host: 127.0.0.1 #主机地址
         scheme: HTTP #支持的协议,http或者https
......

案例

下面案例是通过httpGet方式,在容器内部访问http://127.0.0.1/htalth

apiVersion: v1
kind: Pod
metadata:
  name: pod-liveness-httpGet
  namespace: dev
  labels:
    user: Negan
spec:
  containers:
    - name: nginx # 容器名称
      image: nginx:1.17.1  # 容器需要的镜像地址
      imagePullPolicy: IfNotPresent  # 设置镜像的拉取策略
      ports:
        - name: nginx-port  # 端口名称,如果执行,必须保证name在Pod中是唯一的
          containerPort: 80 # 容器要监听的端口
          protocol: TCP # 端口协议
      livenessProbe: # 生命周期配置
        httpGet: 
          port: 80
          scheme: HTTP
          path: /hello
          host: 127.0.0.1

4)、容器探测的补充

上面已经使用了livenessProbe演示了三种探测方式,但是查看livenessProbe的子属性,会发现除了这三种方式,还有一些其他的配置。

kubectl explain pod.spec.containers.livenessProbe

KIND:     Pod
VERSION:  v1

RESOURCE: livenessProbe <object>

DESCRIPTION:
     Periodic probe of container liveness. Container will be restarted if the
     probe fails. Cannot be updated. More info:
     https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes

     Probe describes a health check to be performed against a container to
     determine whether it is alive or ready to receive traffic.

FIELDS:
   exec	<object>
     One and only one of the following should be specified. Exec specifies the
     action to take.

   failureThreshold	<integer> 
   # 连续探测失败多少次才被认定为失败。默认是3。最小值是1
     Minimum consecutive failures for the probe to be considered failed after
     having succeeded. Defaults to 3. Minimum value is 1.

   httpGet	<object>
     HTTPGet specifies the http request to perform.

   initialDelaySeconds	<integer>
     Number of seconds after the container has started before liveness probes
     are initiated. More info:
     https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes

   periodSeconds	<integer>
     How often (in seconds) to perform the probe. Default to 10 seconds. Minimum
     value is 1.

   successThreshold	<integer>
     Minimum consecutive successes for the probe to be considered successful
     after having failed. Defaults to 1. Must be 1 for liveness and startup.
     Minimum value is 1.

   tcpSocket	<object>
     TCPSocket specifies an action involving a TCP port. TCP hooks not yet
     supported

   timeoutSeconds	<integer>
     Number of seconds after which the probe times out. Defaults to 1 second.
     Minimum value is 1. More info:
     https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes


initialDelaySeconds    	# 容器启动后等待多少秒执行第一次探测
timeoutSeconds      	# 探测超时时间。默认1秒,最小1秒
periodSeconds       	# 执行探测的频率。默认是10秒,最小1秒
failureThreshold   	 	# 连续探测失败多少次才被认定为失败。默认是3。最小值是1
successThreshold    	# 连续探测成功多少次才被认定为成功。默认是1

initialDelaySeconds可以用来配置第一次执行探针的等待时间,对于启动非常慢的应用这个参数非常有用,比如 JenkinsGitlab 这类应用,但是如何设置一个合适的初始延迟时间呢?这个就和应用具体的环境有关系了,所以这个值往往不是通用的,这样的话可能就会导致一个问题,我们的资源清单在别的环境下可能就会健康检查失败了,为解决这个问题,在 Kubernetes v1.16 版本官方特地新增了一个 startupProbe(启动探针),该探针将推迟所有其他探针,直到 Pod 完成启动为止,使用方法和存活探针一样:

......
    startupProbe:
      httpGet:
        path: /healthz
        port: 8080
      failureThreshold: 30  # 尽量设置大点
      periodSeconds: 10
......

有的时候,应用程序可能暂时无法对外提供服务,例如,应用程序可能需要在启动期间加载大量数据或配置文件。在这种情况下,你不想杀死应用程序,也不想对外提供服务。那么这个时候我们就可以使用readiness probe来检测和减轻这些情况。 Pod 中的容器可以报告自己还没有准备,不能处理 Kubernetes 服务发送过来的流量。readiness probe的配置跟liveness probe基本上一致的。唯一的不同是使用readinessProbe而不是livenessProbe。两者如果同时使用的话就可以确保流量不会到达还未准备好的容器,准备好过后,如果应用程序出现了错误,则会重新启动容器。

6、重启策略

在容器探测中,一旦容器探测出现了问题,Kubernetes就会对容器所在的Pod进行重启,其实这是由Pod的重启策略决定的,Pod的重启策略有3种,分别如下:

​ 1) 、Always:容器失效时,自动重启该容器,默认值。

​ 2)、OnFailure:容器终止运行且退出码不为0时重启。

​ 3)、Never:不论状态如何,都不重启该容器。

重启策略适用于Pod对象中的所有容器,首次需要重启的容器,将在其需要的时候立即进行重启,随后再次重启的操作将由kubelet延迟一段时间后进行,且反复的重启操作的延迟时长以此为10s、20s、40s、80s、160s和300s,300s是最大的延迟时长。

案例

apiVersion: v1
kind: Pod
metadata:
  name: pod-restart-policy
  namespace: dev
  labels:
    user: Negan
spec:
  containers:
    - name: nginx # 容器名称
      image: nginx:1.17.1  # 容器需要的镜像地址
      imagePullPolicy: IfNotPresent  # 设置镜像的拉取策略
      
      ports:
        - name: nginx-port  # 端口名称,如果执行,必须保证name在Pod中是唯一的
          containerPort: 80 # 容器要监听的端口
          protocol: TCP # 端口协议
      livenessProbe: # 声明周期配置
        httpGet:
          port: 80
          scheme: HTTP
          path: /hello
          host: 127.0.0.1
  restartPolicy: Never # 重启策略

接着我们创建,并查看状态

kubectl create -f pod_restart_policy.yaml 

kubectl describe pod pod-restart-policy -n dev

Events:
  Type     Reason     Age                From               Message
  ----     ------     ----               ----               -------
  Normal   Scheduled  <unknown>          default-scheduler  Successfully assigned dev/pod-restart-policy to node2
  Normal   Pulled     73s                kubelet, node2     Container image "nginx:1.17.1" already present on machine
  Normal   Created    73s                kubelet, node2     Created container nginx
  Normal   Started    73s                kubelet, node2     Started container nginx
  Warning  Unhealthy  50s (x3 over 70s)  kubelet, node2     Liveness probe failed: Get http://127.0.0.1:80/hello: dial tcp 127.0.0.1:80: connect: connection refused
  Normal   Killing    50s                kubelet, node2     Stopping container nginx 
  # 我们发现容器探测失败后,直接停止容器,并没有选择重启
原文地址:https://www.cnblogs.com/huiyichanmian/p/15672134.html