Kubernetes 最小部署单位 Pod

这一篇笔记主要整理一下 Pod 里面比较重要的一些知识点如下:

  • Pod 被创建出来后经历哪几个流程

  • Pod 内多个容器网络如何通信

  • Pod 内多个容器如何共享存储

  • Pod 生命周期

  • postStart 与 preStop

  • 健康检查 livenessProbe 与 redinessProbe

  • Pod 资源配置

下面按照从上至下的顺序整理出这些知识点。

Pod 被创建出来后经历哪几个流程

Pod 在 K8S 集群中被创建的基本流程如下图:

create-pod

  • 用户通过 API Server 创建一个 Pod
  • API Server 将消息写入 etcd
  • scheduler 检测到有未绑定 Node 节点的 Pod 开始调度并更新 Pod 绑定到哪个 Node 节点的信息
  • API Server 会把 Pod 绑定 Node 节点的信息写入到 etcd 和 scheduler 本地留存一份
  • Kubelet 检测到有新的 Pod 调度过来,通过 docker 创建 Pod
  • Kubelet 通过底层的 docker 获取到 Pod 状态,更新到 API Server 中

Pod 内多个容器网络如何通信

Pod 内部可以有一个或者多个容器,那么 Pod 内多个容器时候如何共享同一个 Network Namespace 呢?

在使用 Docker 的时候,一个容器要想和另一个容器使用同一个网络,我们需要在创建第二个容器的时候通过指定 --network=container:容器名 这种方式让两个容器使用统一网络名称空间,但是这种方式有先后顺序,就是必须要有一个容器先运行起来之后其它容器才可以指定。

在 Pod 中是利用一个中间容器来实现,这个容器叫 Infra Container (使用 docker ps 查看他的名字是 pause),而且这个容器在 Pod 中永远都会被第一个创建出来,这样 Pod 内部的其它容器启动时都指定与这个 pause 启动的容器共享网络,如下图:

由于多个容器共享同一个网络,所以多个容器之间的应用不能使用相同的端口,可以使用 localhost 进行通信。

Pod 内多个容器如何共享存储

默认情况下容器的文件系统都是互相隔离的,要实现共享需要在 Pod 内部声明一个 Volume 然后在需要共享这个 Volume 的容器中挂载即可,如下图:

下面通过一个示例演示一下如何共享 Volume

apiVersion: v1
kind: Pod
metadata:
  name: pod-volume
spec:
  containers:
  - name: nginx
    image: nginx:1.18
    ports:
    - name: http
      containerPort: 80
    volumeMounts:
    - name: html
      mountPath: /usr/share/nginx/html
  - name: busybox
    image: busybox
    args:
    - /bin/sh
    - -c
    - >
      while true;do
      echo "`date`" > /opt/index.html;
      sleep 3;
      done
    volumeMounts:
    - name: html
      mountPath: /opt
  volumes:
  - name: html
    hostPath:
      path: /opt/html

示例中声明了一个 Volume 名为 html 类型为 hostPath,这样我们就可以将宿主机上的 /opt/html 目录共享给这个 Pod ,容器如果使用这个存储通过 volumeMounts 声明将这个目录挂载到容器当中。

实际的场景中,我们可能会通过这种方式来收集业务容器的日志,这种一个容器辅助另一个容器在 K8S 中被称为 sidecar (边车容器)。

Pod 生命周期

我理解从 Pod 被创建到最后删除退出所经过的时间就是 Pod 的生命周期,整个生命周期中可以经历以下几个阶段:

  • init container 初始化容器
  • postStart 容器启动后钩子
  • 健康检查 livenessProbe 和 readinessProbe 两个探针
  • preStop 容器终止前钩子

下面我会分别演示和总结我对这几个阶段的理解。

Init container 初始化容器

Init container 就是用来做初始化工作的,可以有一个或者多个初始化容器,从上到下依次执行。我们知道 Pod 内部的容器是可以共享网络和存储的,所以 init container 产生的数据可以被主容器使用。从上面的图上可以看到,初始化容器是独立于主容器之外的,只有初始化容器执行完成后,主容器才会被启动。那么什么时候可能会用到初始化容器?

  • 等待其它模块完成,比如 Java 容器启动需要连接 MySQL 数据库,如果 MySQL 没有启动好,Java 会报错,这时候就可以通过初始化容器去检测如果 MySQL 启动成功了再去启动主容器。
  • 做初始化配置,比如一个集群服务,可以通过初始化容器获取到其它节点信息,然后主容器使用这个信息加入集群。

下面演示一下如何配置初始化容器,通过初始化容器准备一个页面,然后给 nginx 主容器使用,如下资源清单(init-pod.yaml)

apiVersion: v1
kind: Pod
metadata:
  name: pod-init
spec:
  initContainers:
  - name: webinit
    image: busybox
    imagePullPolicy: IfNotPresent
    command:
    - wget
    - "-O"
    - "/work-dir/index.html"
    - "http://www.baidu.com"
    volumeMounts:
    - name: html
      mountPath: /work-dir
  containers:
  - name: nginx
    image: nginx:1.18
    imagePullPolicy: IfNotPresent
    ports:
    - name: http
      containerPort: 80
    volumeMounts:
    - name: html
      mountPath: /usr/share/nginx/html
  volumes:
  - name: html
    emptyDir: {}

上面的这个资源清单,首先在 Pod 中声明了一个名为 html 的 Volume,然后定义了初始化容器,该容器会下载百度的 index.html 到 /work-dir 目录中。然后主容器 nginx 也将 html 这个 Volume 挂载到了容器内部,这样就会读取到下载的百度这个 index.html 文件。

创建 Pod

$ kubectl apply -f init-pod.yaml

创建完成后可以查看 Pod 状态

$ kubectl get pods -o wide
NAME             READY   STATUS    RESTARTS   AGE   IP           NODE         NOMINATED NODE   READINESS GATES
pod-init         1/1     Running   0          89s   10.244.1.4   k8s-node01   <none>           <none>

Pod 状态为 Running 后,可以通过 curl 去请求 Pod IP 看看是否是百度的首页内容。

Pod Hook

K8S 在 Pod 生命周期中提供了两个钩子,由 kubelet 发起执行,在容器中的进程启动前或者进程终止之前运行。

这两个钩子分别是:

  • postStart:这个钩子将在容器创建后立即执行,但是并不一定会在容器 ENTRYPOINT 指令之前运行,主要用于资源部署和环境准备。如果这个钩子长时间运行或者挂起会导致容器不能达到 running 状态。
  • preStop:这个钩子在容器被终止前被立即调用,主要用于优雅关闭程序、通知其他系统等。

有两种方式可以实现上面的钩子:

  • exec:用于执行一段命令
  • httpGet:发起一个 http 请求

下面这个示例 (poststart-hook.yaml ) 中,定义了一个 Nginx Pod,在内部设置了 postStart 函数,在容器创建成功之后写入一句话到 /tmp/message 文件中

apiVersion: v1
kind: Pod
metadata:
  name: poststart-hook
spec:
  containers:
  - name: nginx
    image: nginx:1.18
    imagePullPolicy: IfNotPresent
    ports:
    - name: http
      containerPort: 80
    lifecycle:
      postStart:
        exec:
          command: ["/bin/sh","-c","echo Hello World > /tmp/message"]

然后直接创建上面的 Pod

$ kubectl apply -f poststart-hook.yaml

创建成功后查看 /tmp/mesasge 文件中的内容是否正确

$ kubectl exec poststart-hook -- cat /tmp/message

当用户请求删除含有 Pod 的资源对象时例如 Deployment 资源,K8S 为了让程序优雅关闭(让应用程序完成正在处理的请求后在关闭),K8S 提供两种信息通知:

  • 默认:K8S 通知 Node 执行 docker stop 命令,docker 会先向容器中 PID 为 1 的进程发送系统信号 SIGTERM 就是 kill -15 ,然后等待容器中的应用程序终止执行,如果等待时间到达设定的超时时间,或者默认超时时间 30 秒,会继续发送一个 SIGKILL 就是 kill -9 强行干掉进程。
  • 第二种就是使用 preStop 钩子,它会在对容器发送终止信号前,执行,这样我们就可以先去执行一段指令优雅的关闭程序。

默认的超时时间是 30 秒,kubectl delete 指令支持 --grace-period=<seconds> 选项,允许自己指定的值覆盖默认值。值 “0” 代表强制删除 Pod。执行强制删除时必须同时指定 --force --grace-period=0

更多关于删除 Pod 资源过程的说明可以查阅官方文档:https://kubernetes.io/docs/concepts/worklo...

下面示例中 (prestop-hook.yaml)定义了一个 Nginx Pod,设置了 preStop 钩子,在容器退出之前,优雅关闭 Nginx

apiVersion: v1
kind: Pod
metadata:
  name: prestop-hook
spec:
  containers:
  - name: nginx
    image: nginx:1.18
    imagePullPolicy: IfNotPresent
    ports:
    - name: http
      containerPort: 80
    lifecycle:
      preStop:
        exec:
          command: ["nginx","-s","quit"]

健康检查

在 K8S 中,可以通过配置 livenessProbe (存活探针)和 readinessProbe(就绪探针)来影响容器的生命周期。在 Kubernetes 1.16 版本以后官方又新增了一个 startupProbe(启动探针)。

  • livenessProbe:检查容器内进程是否在运行,是否还活着。如果检测到程序崩溃活着进程终止了就会重启容器。
  • readinessProbe:用来确定容器内部的程序是否就绪已经可以接收流量请求了。比如我们的 Java 应用启动后虽然进程和端口起来了,但是程序会做一些初始化或者加载数据后才能正常使用。
  • startupProbe:这个探针主要就是针对那些启动较慢的应用使用,这个探针会推迟其它探针,直到 Pod 完全启动为止以后 livenessProbe 和 readinessProbe 探针才会执行。

三种探针支持下面几种配置方式:

  • exec:执行一段命令
  • httpGet:执行一个 http 请求
  • tcpSocket:在指定的端口上建立连接,如果可以建立连接,就被认为是健康的,否则就是失败的。

下面这个示例(liveness-exec.yaml)中使用 exec 方法执行一个命令检测容器的存活

apiVersion: v1
kind: Pod
metadata:
  name: liveness-exec
spec:
  containers:
  - name: nginx
    image: busybox
    imagePullPolicy: IfNotPresent
    args:
    - /bin/sh
    - -c
    - touch /tmp/health; sleep 30; rm -f /tmp/health; sleep 300
    livenessProbe:
      exec:
        command:
        - cat
        - /tmp/health
      initialDelaySeconds: 5
      periodSeconds: 5

然后直接创建 Pod

$ kubectl apply -f liveness-exec.yaml

等待 1 分钟左右,可以查看这个 pod 的信息

$ kubectl describe pod liveness-exec
# 当检测到文件被删除以后会看到下面这条信息
Warning  Unhealthy  2s (x4 over 77s)    kubelet            Liveness probe failed: cat: can't open '/tmp/health': No such file or directory

上面我只演示了如何使用 exec 方式,另外两种方式大家可以自己进行验证多使用 kubectl explain 来获取使用方法。

三种探测方式中还可以设置下面这些参数:

  • initialDelaySeconds:容器运行多少秒以后才开始执行检测

  • periodSeconds:设置探测频率,默认 10 秒,最小 1 秒

  • timeoutSeconds:探测超时时间,默认 1 秒,最小 1 秒

  • successThreshold:探测失败后,最少连续探测成功多少次才会被认定为成功,默认是 1

  • failureThreshold:探测成功后,最少连续探测失败多少次才会被认定为失败。默认是 3 最小值 1

Pod 资源配置

资源配置就是限制容器可以使用的资源 CPU 、内存等。

在具体配置之前,先了解一下容器对 CPU 资源的单位换算

1 CPU = 1000 millicpu (1 Core = 1000m)

0.5 CPU = 500 millicpu (0.5 Core = 500m)

这里的 m 就是毫、毫核的意思,比如一个节点有 4 核,那么这个节点的 CPU 总毫量就是 4000m。在 Pod 内可以通过下面两个参数设置资源:

  • spec.containers.resources.limits.cpu:CPU 上限值,可以短暂超过,容器也不会被停止。

  • spec.containers.resources.requests.cpu:CPU 请求值,K8S 调度算法里面的依据值,如果设置的值大于集群内每个节点的最大 CPU 核数,那么这个 Pod 将无法被调度,因为没有节点能满足它。

内存单位换算比较简单,一般我们使用 Mi 单位,当然你可以使用 Ki、Gi、Pi

requests 是用于集群调度而使用的资源,而 limits 才是真正的用于资源限制的配置,如果你要保证的应用优先级很高,资源吃紧的情况下最后杀掉你的 Pod 那么就把 requests 和 limits 设置一致。

下面这个示例 (pod-resource-demo.yaml) 中定义了如何配置 CPU 和 内存资源

apiVersion: v1
kind: Pod
metadata:
  name: resource-demo
spec:
  containers:
  - name: nginx
    image: nginx:1.18
    imagePullPolicy: IfNotPresent
    ports:
    - name: http
      containerPort: 80
    resources:
      limits:
        cpu: 500m
        memory: 512Mi
      requests:
        cpu: 100m
        memory: 100Mi

然后直接创建 Pod

$ kubectl apply -f resource-demo.yaml

好了到这里,Pod 相关的知识点已经整理结束了,可能有些地方理解的并不是很正确,欢迎各位大佬帮忙指正交流。

本作品采用《CC 协议》,转载必须注明作者和本文链接
(= ̄ω ̄=)··· 暂无内容!

请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!