Pod的相关理解
一、Pod 容器组_概述
什么是 Pod 容器组
Pod(容器组)是 Kubernetes 中最小的可部署单元。
一个 Pod(容器组)包含了一个应用程序容器(某些情况下是多个容器)、存储资源、一个唯一的网络 IP 地址、以及一些确定容器该如何运行的选项。
Pod 容器组代表了 Kubernetes 中一个独立的应用程序运行实例,该实例可能由单个容器或者几个紧耦合在一起的容器组成。
Kubernetes 集群中的 Pod 存在如下两种使用途径:
- 一个 Pod 中只运行一个容器。”one-container-per-pod” 是 Kubernetes 中最常见的使用方式。Kubernetes 通过 Pod 管理容器,而不是直接管理容器。
- 一个 Pod 中运行多个需要互相协作的容器。可以将多个紧密耦合、共享资源且始终在一起运行的容器编排在同一个 Pod 中。
每一个 Pod 都是用来运行某一特定应用程序的一个实例。如果想要水平扩展应用程序(运行多个实例),运行多个 Pod 容器组,每一个代表应用程序的一个实例。
- Kubernetes 中,称其为 replication(复制副本)。
- Kubernetes 中 Controller(控制器)负责为应用程序创建和管理这些复制的副本。
Pod 如何管理多个容器
Pod 的设计目的是用来支持多个互相协同的容器,使得形成一个有意义的服务单元。一个 Pod 中的多个容器很自然就可以随 Pod 被一起调度到集群中的同一个物理机或虚拟机上。Pod 中的容器可以:
- 共享资源、依赖
- 互相通信
- 相互协商何时以何种方式结束运行
Pod 为其成员容器提供了两种类型的共享资源:网络和存储
网络 Networking
每一个 Pod 被分配一个独立的 IP 地址。Pod 中的所有容器共享一个网络名称空间:
- 同一个 Pod 中的所有容器 IP 地址都相同
- 同一个 Pod 中的不同容器不能使用相同的端口,否则会导致端口冲突
- 同一个 Pod 中的不同容器可以通过 localhost:port 进行通信
- 同一个 Pod 中的不同容器可以通过使用常规的进程间通信手段,例如 SystemV semaphores 或者 POSIX 共享内存
- 存储 Storage
Pod 中可以定义一组共享的数据卷。Pod 中所有的容器都可以访问这些共享数据卷,以便共享数据。Pod 中数据卷的数据也可以存储持久化的数据,使得容器在重启后仍然可以访问到之前存入到数据卷中的数据。
不同 Pod 上的两个容器如果要通信,必须使用对方 Pod 的 IP 地址 + 对方容器的端口号进行网络通信
使用 Pod
在 Pod 被创建后(直接创建,或者间接通过 Controller 创建),将被调度到集群中的一个节点上运行。Pod 将一直保留在该节点上,直到 Pod 以下情况发生:
Pod 中的容器全部结束运行
Pod 被删除
由于节点资源不够,Pod 被驱逐
节点出现故障(例如死机)
-
Pod 本身并不会运行,Pod 仅仅是容器运行的一个环境
Pod 本身并不能自愈(self-healing)。如果一个 Pod 所在的 Node (节点)出现故障,或者调度程序自身出现故障,Pod 将被删除;同理,当因为节点资源不够或节点维护而驱逐 Pod 时,Pod 也将被删除。Kubernetes 通过引入 Controller(控制器)的概念来管理 Pod 实例。在 Kubernetes 中,更为推荐的做法是使用 Controller 来管理 Pod,而不是直接创建 Pod。
容器组和控制器
用户应该始终使用控制器来创建 Pod,而不是直接创建 Pod,控制器可以提供如下特性:
水平扩展(运行 Pod 的多个副本)
rollout(版本更新)
self-healing(故障恢复)
例如:当一个节点出现故障,控制器可以自动地在另一个节点调度一个配置完全一样的 Pod,以替换故障节点上的 Pod。
在 Kubernetes 中,广泛使用的控制器有:
- Deployment
- StatefulSet
- DaemonSet
Termination of Pods(终止Pod)
Pod 代表了运行在集群节点上的进程,而进程的终止有两种方式:
- gracefully terminate (优雅地终止)
- 直接 kill,此时进程没有机会执行清理动作
当用户发起删除 Pod 的指令时,Kubernetes 需要:
- 让用户知道 Pod 何时被删除
- 确保删除 Pod 的指令最终能够完成
Kubernetes 收到用户删除 Pod 的指令后:
- 记录强制终止前的等待时长(grace period)
- 向 Pod 中所有容器的主进程发送 TERM 信号
- 一旦等待超时,向超时的容器主进程发送 KILL 信号
- 删除 Pod 在 API Server 中的记录
默认情况下,删除 Pod 的 grace period(等待时长)是 30 秒。
可以通过 kubectl delete 命令的选项 --grace-period= 自己指定 grace period(等待时长)。
强制删除 Pod,必须为 kubectl delete 命令同时指定两个选项 --grace-period=0 和 --force
二、Pod 容器组_声明周期
Pod phase
| Phase | 描述 |
|---|---|
| Pending | Kubernetes 已经创建并确认该 Pod。此时可能有两种情况:Pod 还未完成调度(例如没有合适的节点)正在从 docker registry 下载镜像 |
| Running | 该 Pod 已经被绑定到一个节点,并且该 Pod 所有的容器都已经成功创建。其中至少有一个容器正在运行,或者正在启动/重启 |
| Succeeded | Pod 中的所有容器都已经成功终止,并且不会再被重启 |
| Failed | Pod 中的所有容器都已经终止,至少一个容器终止于失败状态:容器的进程退出码不是 0,或者被系统 kill |
| Unknown | 因为某些未知原因,不能确定 Pod 的状态,通常的原因是 master 与 Pod 所在节点之间的通信故障 |
容器的检查
Probe 是指 kubelet 周期性地检查容器的状况。
有三种类型的 Probe:
- ExecAction: 在容器内执行一个指定的命令。如果该命令的退出状态码为 0,则成功
- TCPSocketAction: 探测容器的指定 TCP 端口,如果该端口处于 open 状态,则成功
- HTTPGetAction: 探测容器指定端口/路径上的 HTTP Get 请求,如果 HTTP 响应状态码在 200 到 400(不包含400)之间,则成功
Probe 有三种可能的结果:
- Success: 容器通过检测
- Failure: 容器未通过检测
- Unknown: 检测执行失败,此时 kubelet 不做任何处理
Kubelet 可以在两种情况下对运行中的容器执行 Probe:
- 就绪检查 readinessProbe: 确定容器是否已经就绪并接收服务请求。如果就绪检查失败,kubernetes 将该 Pod 的 IP 地址从所有匹配的 Service 的资源池中移除掉。
- 健康检查 livenessProbe: 确定容器是否正在运行。如果健康检查失败,kubelete 将结束该容器,并根据 restart policy(重启策略)确定是否重启该容器。
何时使用 健康检查/就绪检查?
- 如果容器中的进程在碰到问题时可以自己 crash,并不需要执行健康检查;kubelet 可以自动的根据 Pod 的 restart policy(重启策略)执行对应的动作
- 如果希望在容器的进程无响应后,将容器 kill 掉并重启,则指定一个健康检查 liveness probe,并同时指定 restart policy(重启策略)为 Always 或者 OnFailure
- 如果想在探测 Pod 确实就绪之后才向其分发服务请求,请指定一个就绪检查 readiness probe。此时,就绪检查的内容可能和健康检查相同。就绪检查适合如下几类容器:
- 初始化时需要加载大量的数据、配置文件
- 启动时需要执行迁移任务
容器的状态
一旦 Pod 被调度到节点上,kubelet 便开始使用容器引擎(通常是 docker)创建容器。容器有三种可能的状态:Waiting / Running / Terminated:
- Waiting: 容器的初始状态。处于 Waiting 状态的容器,仍然有对应的操作在执行,例如:拉取镜像、应用 Secrets等。
- Running: 容器处于正常运行的状态。容器进入 Running 状态之后,如果指定了 postStart hook,该钩子将被执行。
- Terminated: 容器处于结束运行的状态。容器进入 Terminated 状态之前,如果指定了 preStop hook,该钩子将被执行。
重启策略
定义 Pod 或工作负载时,可以指定 restartPolicy,可选的值有:
- Always (默认值)
- OnFailure
- Never
restartPolicy 将作用于 Pod 中的所有容器。kubelete 将在五分钟内,按照递延的时间间隔(10s, 20s, 40s ……)尝试重启已退出的容器,并在十分钟后再次启动这个循环,直到容器成功启动,或者 Pod 被删除。
容器组的存活期
通常,如果没有人或者控制器删除 Pod,Pod 不会自己消失。
只有一种例外,那就是 Pod 处于 Scucceeded 或 Failed 的 phase,并超过了垃圾回收的时长(在 kubernetes master 中通过 terminated-pod-gc-threshold 参数指定),kubelet 自动将其删除。
三、Pod 容器组_初始化容器
初始化容器的行为
- Pod 的启动时,首先初始化网络和数据卷,然后按顺序执行每一个初始化容器。任何一个初始化容器都必须成功退出,才能开始下一个初始化容器。如果某一个容器启动失败或者执行失败,kubelet 将根据 Pod 的 restartPolicy 决定是否重新启动 Pod。
- 只有所有的初始化容器全都执行成功,Pod 才能进入 ready 状态。初始化容器的端口是不能够通过 kubernetes Service 访问的。Pod 在初始化过程中处于 Pending 状态,并且同时有一个 type 为
initializingstatus 为True的 Condition - 如果 Pod 重启,所有的初始化容器也将被重新执行。
- 可以组合使用就绪检查和 activeDeadlineSeconds ,以防止初始化容器始终失败。
- Pod 中不能包含两个同名的容器(初始化容器和工作容器也不能同名)。
Pod 重启的原因
Pod 重启时,所有的初始化容器都会重新执行,Pod 重启的原因可能有:
- 用户更新了 Pod 的定义,并改变了初始化容器的镜像
- 改变任何一个初始化容器的镜像,将导致整个 Pod 重启
- 改变工作容器的镜像,将只重启该工作容器,而不重启 Pod
- Pod 容器基础设施被重启(例如 docker engine),这种情况不常见,通常只有 node 节点的 root 用户才可以执行此操作
- Pod 中所有容器都已经结束,restartPolicy 是 Always,且初始化容器执行的记录已经被垃圾回收,此时将重启整个 Pod
配置初始化容器
Pod的配置文件如下
1 | apiVersion: v1 |
从配置文件可以看出,Pod 中初始化容器和应用程序共享了同一个数据卷。初始化容器将该共享数据卷挂载到 /work-dir 路径,应用程序容器将共享数据卷挂载到 /usr/share/nginx/html 路径。初始化容器执行如下命令后,就退出执行: wget -O /work-dir/index.html https://kuboard.cn
验证:
1 | [root@k8s-master k8s-yamls]# kubectl get pod init-demo |
四、Pod 容器组_Debug初始化容器
查看 Pod 的状态:
1 | kubectl get pod <pod-name> |
查看初始化容器的详情
1 | kubectl describe pod <pod-name> |
理解 Pod 状态
如果 Pod 的状态以 Init: 开头,表示该 Pod 正在执行初始化容器。下表描述了 Debug 初始化容器的过程中,一些可能出现的 Pod 状态:
| 状态 | 描述 |
|---|---|
Init:N/M |
Pod 中包含 M 个初始化容器,其中 N 个初始化容器已经成功执行 |
Init:Error |
Pod 中有一个初始化容器执行失败 |
Init:CrashLoopBackOff |
Pod 中有一个初始化容器反复执行失败 |
Pending |
Pod 还未开始执行初始化容器 |
PodInitializing or Running |
Pod 已经完成初始化容器的执行 |
五、Pod 容器组_配置PodDisruptionBudget
在Kubernetes中,为了保证业务不中断或业务SLA不降级,需要将应用进行集群化部署。通过PodDisruptionBudget控制器可以设置应用 Pod 集群处于运行状态最低个数,也可以设置应用Pod 集群处于运行状态的最低百分比,这样可以保证在主动销毁应用Pod的时候,不会一次性销毁太多的应用Pod,从而保证业务不中断或业务SLA不降级。
在Kubernetes 1.5中,kubectl drain命令已经支持了PodDisruptionBudget控制器,在进行kubectl drain操作时会根据PodDisruptionBudget控制器判断应用Pod集群数量,进而保证在业务不中断或业务SLA不降级的情况下进行应用Pod销毁。
确定需要PDB保护的应用
通常如下几种 Kubernetes 控制器创建的应用程序可以使用 PDB:
- Deployment
- ReplicationController
- ReplicaSet
- StatefulSet
PDB 中 .spec.selector 字段的内容必须与控制器中 .spec.selector 字段的内容相同。
当毁坏发生时,在短时间内,应用程序最多可以容许多少个实例被终止?
- 无状态的前端:
- 关注点:不能让服务能力(serving capacity)降低超过 10%
- 解决方案:在 PDB 中配置 minAvailable 90%
- 单实例有状态应用:
- 关注点:未经同意不能关闭此应用程序
- 解决方案1: 不使用 PDB,并且容忍偶尔的停机
- 解决方案2: 在 PDB 中设置 maxUnavailable=0。与集群管理员达成一致(不是通过Kubernetes,而是邮件、电话或面对面),请集群管理员在终止应用之前与你沟通。当集群管理员联系你时,准备好停机时间,删除 PDB 以表示已准备好应对毁坏。并做后续处理
- 多实例有状态应用,例如 consul、zookeeper、etcd:
- 关注点:不能将实例数降低到某个数值,否则写入会失败
- 解决方案1: 在 PDB 中设置 maxUnavailable 为 1 (如果副本数会发生变化,可以使用此设置)
- 解决方案2: 在 PDB 中设置 minAvailable 为最低数量(例如,当总副本数为 5 时,设置为3)(可以同时容忍更多的毁坏数)
- 可以重新开始的批处理任务:
- 关注点:当发生自愿毁坏时,Job仍然需要完成其执行任务
- 解决方案: 不创建 PDB。Job 控制器将会创建一个 Pod 用于替换被毁坏的 Pod
指定百分比时的舍入逻辑
minAvailable 或 maxUnavailable 可以指定为整数或者百分比。
- 当指定一个整数时,代表 Pod 的数量。例如,设置
minAvailable为 10,则至少 10 个 Pod 必须始终可用,即便是在毁坏发生时 - 当指定一个百分比时(例如,
50%),代表总 Pod 数量的一个百分比。例如,设置maxUnavailable为50%,则最多可以有 50% 的 Pod 可以被毁坏
如果指定这些值为一个百分数,其计算结果可能不会正好是一个整数。例如,假设有 7 个 Pod,minAvailable 设置为 50%,你将很难判断,到底是 3 个还是 4 个 Pod 必须始终保持可用。Kubernetes 将向上舍入(round up to the nearest integer),因此,此处必须有 4 个 Pod 始终可用。
定义PodDisruptionBudget
PodDisruptionBudget 包含三个字段:
- 标签选择器
.spec.selector用于指定 PDB 适用的 Pod。此字段为必填 .spec.minAvailable:当完成驱逐时,最少仍然要保留多少个 Pod 可用。该字段可以是一个整数,也可以是一个百分比.spec.maxUnavailable: 当完成驱逐时,最多可以有多少个 Pod 被终止。该字段可以是一个整数,也可以是一个百分比
在一个 PodDisruptionBudget 中,只能指定 maxUnavailable 和 minAvailable 中的一个。 maxUnavailable 只能应用到那些有控制器的 Pod 上。下面的例子中,“期望的副本数” 是 PodDisruptionBudget 对应 Pod 的控制器的 .spec.replicas 字段:
例子1: minAvailable 为 5 时,只要 PodDisruptionBudget 的 selector 匹配的 Pod 中有超过 5 个仍然可用,就可以继续驱逐 Pod
例子2: minAvailable 为 30% 时,至少保证期望副本数的 30% 可用
例子3: maxUnavailable 为 5 时,最多可以有 5 个副本不可用(unthealthy)
例子4: maxUnavailable 为 30% 时,最多可以有期望副本数的 30% 不可用
通常,一个 PDB 对应一个控制器创建的 Pod,例如,Deployment、ReplicaSet或StatefulSet。
使用 minAvailable
1 | apiVersion: policy/v1beta1 |
使用 maxUnavailable
1 | apiVersion: policy/v1beta1 |