Kubernetes——Pod对象的声明周期(Pod的相位、创建过程、重要行为、探测、重启策略、终止过程)
Pod对象的声明周期(Pod的相位、创建过程、重要行为、探测、重启策略、终止过程)
Pod 对象自从其创建开始至其终止退出的时间范围称为其生命周期。在这段时间中,Pod 会处于多种不同的状态,并执行一些操作;其中,创建主容器(main container)为必须的操作,其他可选的操作还包括进行初始化容器(init container)、容器启动后钩子(post start hook)、容器的存活性探测(liveness probe)、就绪型探测(readiness probe)以及容器终止前钩子(pre stop hook)等,这些操作是否执行则取决于 Pod 的定义。
一、Pod 的相位
无论是用户手动创建,还是通过 Deployment 等控制器创建,Pod 对象总是应该处于其生命进程中以下几个相位(phase)之一:
-
- pending:API Server 创建了 Pod 资源对象并已存入 etcd 中,但是它尚未被调度完成,或者仍处于从仓库下载镜像的过程中。
- Running:Pod 已经被调度至某节点,并且所有容器都已经被 kubelet 创建完成。
- Succeeded:Pod 中的所有容器都已经成功终止并且不会被重启。
- Failed:所有容器都已经终止,但至少有一个容器终止失败,即容器返回了非 0 值的退出状态或已经被系统终止。
- Unkown:API Server 无法正常获取到 Pod 对象的状态信息,通常是由于其无法与所在工作节点的 kubelet 通信所致。
Pod 相位是在其声明周期的宏观概述,而非对容器或 Pod 对象的综合汇总,而且相位的数量和含义被严格界定,它仅包含上面列举的相位值。
二、Pod 的创建过程
Pod 是 Kubernetes 的基础单元,理解它的创建过程对于了解系统运作有很大帮助。
1)用户通过 kubectl 或其他 API 客户端提交 Pod Spec 给 API Server。
2)API Server 尝试着将 Pod 对象的相关信息存入 etcd 中,待写入操作执行完成,API Server 即会返回确认信息至客户端。
3)API Server 返回 etcd 中的状态变化。
4)所有的 Kubernetes 组件均使用 "watch" 机制来跟踪检查 API Server 上的相关的变动。
5)kube-scheduler(调度器)通过其 "watch" 觉察到 API Server 创建了新的 Pod 对象但尚未绑定至任何工作节点。
6)kube-scheduler 为 Pod 对象挑选一个工作节点将结果更新至 API Server。
7)调度结果信息由 API Server 更新至 etcd 存储系统,而且 API Server 也开始反映此 Pod 对象的调度结果。
8)Pod 被调度到的目标工作节点上的 kubelet 尝试在当前节点上调用 Docker 启动容器,并将容器的结果状态送至 API Server。
9)API Server 将 Pod 状态信息存入 etcd 系统中。
10)在 etcd 确认写入操作成功完成后,API Server 将确认信息发送至相关的 kubelet,事件将通过它被接受。
三、Pod 生命周期中的重要行为
除了创建应用容器(主容器及其辅助容器)之外,用户还可以为 Pod 对象定义其生命周期中的多种行为,如初始化容器、存活性探测及就绪性探测等。
1、初始化容器
初始化容器(init container)即应用程序的主容器启动之前要运行的容器,常用于为主容器执行一些预置操作,它们具有两种典型特征:
1)初始化容器必须运行完成执直至结束,若某初始化容器运行失败,那么 Kubernetes 需要重启它直至成功完成。
2)每个初始化容器都必须按定义的顺序串行运行。
有不少场景都需要在应用容器启动之前进行部分初始化操作,例如,等待其他关联组件服务可用、基于环境变量或配置模版为应用程序生成配置文件、从配置中心获取配置等。初始化容器的典型应用需求具体包括如下几种:
用于运行特定的工具程序,处于安全的个方面的原因,这些程序不适于包含在主容器镜像中。
2)提供主容器镜像中不具备的工具程序或自定义代码。
3)为容器镜像的构建和部署人员提供了分离、独立工作的途径,使得他们不必协同起来制作单个镜像文件。
4)初始化容器和主容器处于不同的文件系统视图中,因此可以分别安全地使用敏感数据,例如 Secrets 资源。
5)初始化容器要先于应用容器串行启动并运行完成,因此可用于延后应用容器的启动直至其依赖的条件得到满足。
Pod 资源的 "spec.initContainers" 字段以列表的形式定义可用的初始化容器,其嵌套可用字段类型于 "spec.containers"。下面的资源清单仅是一个初始化容器的使用示例参考:
kind: Pod
apiVersion: v1
metadata:
name: redis-ha-haproxy-64444759d4-wf45w
generateName: redis-ha-haproxy-64444759d4-
namespace: kubesphere-system
labels:
app: redis-ha-haproxy
pod-template-hash: 64444759d4
release: ks-redis
annotations:
checksum/config: 46138f8b2005948188bc12c93b08a9c6460354af2db98eaa2158bcf1717e82de
cni.projectcalico.org/podIP: 10.233.105.1/32
cni.projectcalico.org/podIPs: 10.233.105.1/32
prometheus.io/path: /metrics
prometheus.io/port: '9101'
prometheus.io/scrape: 'true'
spec:
volumes:
- name: config-volume
configMap:
name: redis-ha-configmap
defaultMode: 420
- name: shared-socket
emptyDir: {}
- name: data
emptyDir: {}
- name: redis-ha-haproxy-token-5lqwd
secret:
secretName: redis-ha-haproxy-token-5lqwd
defaultMode: 420
initContainers:
- name: config-init
image: 'registry.cn-beijing.aliyuncs.com/kubesphereio/haproxy:2.0.4'
command:
- sh
args:
- /readonly/haproxy_init.sh
resources: {}
volumeMounts:
- name: config-volume
readOnly: true
mountPath: /readonly
- name: data
mountPath: /data
- name: redis-ha-haproxy-token-5lqwd
readOnly: true
mountPath: /var/run/secrets/kubernetes.io/serviceaccount
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
imagePullPolicy: IfNotPresent
containers:
- name: haproxy
image: 'registry.cn-beijing.aliyuncs.com/kubesphereio/haproxy:2.0.4'
ports:
- name: redis
containerPort: 6379
protocol: TCP
resources: {}
volumeMounts:
- name: data
mountPath: /usr/local/etc/haproxy
- name: shared-socket
mountPath: /run/haproxy
- name: redis-ha-haproxy-token-5lqwd
readOnly: true
mountPath: /var/run/secrets/kubernetes.io/serviceaccount
livenessProbe:
httpGet:
path: /healthz
port: 8888
scheme: HTTP
initialDelaySeconds: 5
timeoutSeconds: 1
periodSeconds: 3
successThreshold: 1
failureThreshold: 3
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
imagePullPolicy: IfNotPresent
restartPolicy: Always
terminationGracePeriodSeconds: 30
dnsPolicy: ClusterFirst
serviceAccountName: redis-ha-haproxy
serviceAccount: redis-ha-haproxy
nodeName: mh-k8s-master-prd-243.24
securityContext:
runAsUser: 1000
runAsNonRoot: true
fsGroup: 1000
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
preference:
matchExpressions:
- key: node-role.kubernetes.io/master
operator: In
values:
- ''
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: redis-ha-haproxy
release: ks-redis
topologyKey: kubernetes.io/hostname
schedulerName: default-scheduler
tolerations:
- key: node-role.kubernetes.io/master
effect: NoSchedule
- key: CriticalAddonsOnly
operator: Exists
- key: node.kubernetes.io/not-ready
operator: Exists
effect: NoExecute
tolerationSeconds: 60
- key: node.kubernetes.io/unreachable
operator: Exists
effect: NoExecute
tolerationSeconds: 60
priority: 0
enableServiceLinks: true
2、声明周期钩子函数
生命周期钩子函数(lifcycle hook)是编程语言(如 Angular)中常用的生命周期管理的组件,它实现了程序运行周期中的关键时刻的可见性,并赋予用户为此采取某种行动的能力。类似地,容器声明周期钩子使它能够感知其自身生命周期管理中的事件,并在相应的时刻到来时运行由用户指定的处理程序代码。Kubernetes 为容器提供了两种生命周期的钩子。
1)postStart:于容器创建完成之后立即运行的钩子处理器(handler),不过 Kubernetes 无法确保它一定会于容器中的 ENTRYPOINT 之前运行。
2)preStop:于容器终止操作之前立即运行的钩子处理器,它以同步的方式调用,因此在其完成之前会阻塞删除容器的操作的调用。
钩子处理器的实现方式有 "Exec" 和 "HTTP" 两种,前一种在钩子事件触发时直接在当前容器中运行由用户定义的命令,后一种则是在当前容器中向某 URL 发起 HTTP请求。
3、容器探测
容器探测(container probe)是 Pod 对象生命周期中的一项重要的日常任务,它是 kubelet 对容器周期性执行的健康状态诊断,诊断操作由容器的处理器(handler)进行定义。
Kubernetes 支持三种处理器用于 Pod 探测。
-
- ExecAction:在容器中执行一个命令,并根据其返回的状态码进行诊断的操作称为 Exec 探测,状态码为 0 表示成功,否则即为不健康状态。
- TCPSocketAction:通过与容器的某 TCP 端口尝试建立连接进行诊断,端口能够成功打开即为正常,否则为不健康状态。
- HTTPGetAction:通过向容器 IP 地址的某指定端口的指定 path 发起 HTTP GET 请求进行诊断,响应码为 2xx 或 3xx 时即为成功,否则为失败。
任何一种探测方式都可能存在三种结果:"Success"(成功)、"Failure"(失败)或 "Unknown"(未知),只有第一种结果表示成功通过检测。
存活性检测:
用于判定容器是否处于 "运行"(Running)状态;一旦此类检测未通过,kubelet 将杀死容器并根据其 restartPolicy 决定是否将其重启;未定义存活性检测的容器的默认状态为 "Success"。
就绪性检测:
用于判断容器是否准备就绪并可对外提供服务;未通过检测的容器意味着其尚未准备就绪,端点控制器(如 Service 对象)会将其 IP 从所有匹配到此 Pod 对象的 Service 对象的端点列表中移除;检测通过之后,会再次将其 IP 添加至端点列表中。
四、容器调度重启策略
容器程序发生崩溃或容器申请超过限制的资源等原因都可能会导致 Pod 对象的终止,此时是否应该重建该 Pod 对象则取决于其重启策略(restartPolicy)属性的定义。
1)Always:但凡 Pod 对象终止就将其重启,此为默认设定。
2)OnFailure:仅在 Pod 对象出现错误时方才能将其重启。
3)Nerver:从不重启
提示:
restartPolicy 适用于 Pod 对象中的所有容器,而且它仅用于控制在同一节点上重新启动 Pod 对象的相关容器。
首次需要重启的容器,将在其需要时立即进行重启,随后再次需要重启的操作由 kubelet 延迟一段时间后进行,且反复的重启操作的延迟时长依次为 10秒、20秒、40秒、80秒、160秒 和 300秒,300秒是最大延迟时长。
Pod 一旦绑定到一个节点,Pod对象将永远不会被重新绑定到另一个点,它要么被重启,要么终止,直到节点发生故障或被删除。
五、Pod 的终止过程
Pod 对象代表了在 Kubernetes 集群节点上运行的进程,它可能曾用于处理生产数据或向用户提供服务等,但是,当 Pod 本身不再具有存在的价值时,如何将其优雅地终止就显得尤为重要了,而用户也需要能够在正常提交删除操作后可以获知其如何开始终止并最终完成。
操作中,当用户提交删除请求后,系统就会进行强制删除操作的宽限期倒计时,并将 TERM 信息发送给 Pod 对象的每个容器中的主进程。宽限期倒计时结束后,这些进程将啊收到强制终止的 KILL 信号,Pod 对象随即也将由 API Server 删除。如果在等待进程终止的过程中,kubelet 或容器管理器发生了重启,那么终止操作会重新获得一个满额的删除宽限期并重新进行删除操作。·
一个典型的 Pod 对象终止流程具体如下:
1)用户发送删除 Pod 对象的命令。
2)API 服务器中的 Pod 对象会随着时间的推移而更新,在宽限期内(默认 30秒),Pod 视为 "dead"。
3)将 Pod 标记为 "Terminating" 状态。
4)(与第 3 步同时运行)kubelet 在监控到 Pod 对象转为 "Terminating" 状态的同时启动 Pod 关闭过程。
5)(与第 3 步同时运行)端点控制器监控到 Pod 对象的关闭行为时将其从所有匹配到此端点的 Service 资源的端点列表中移除。
6)如果当前 Pod 对象定义了 preStop 钩子处理器,则在其标记为 "terminating" 后即会以同步的方式启动执行;如若宽限期结束后,preStop 仍未执行结束,则第 2 步会被重新执行并额外获取一个时长为 2 秒 的小宽限期。
7)Pod 对象中的容器进程收到 TERM 信号。
8)宽限期结束后,若存在任何一个仍在运行的进程,那么 Pod 对象即会收到 SIGKILL 信号。
9)Kubelet 请求 API Server 将此 Pod 资源的宽限期设置为 0 从而完成删除操作,它变得对用户不再可见。
默认情况下,所有删除操作的宽限期都是 30秒,不过, kubectl delete 命令可以使用 "--grace-period=<seconds>"选项自定义其时长,若使用 0 值则表示直接强制删除指定的资源,不过,此时需要同时为命令使用 "--force" 选项。