20221218 5. pod 进阶
资源清单格式
资源清单有5个顶级的字段组成:apiVersion、kind、metadata、spec、status
apiVersion: group/apiversion # 如果没有给定 group 名称,那么默认为 core,可以使用 kubectl apiversions 获取当前 k8s 版本上所有的 apiVersion 版本信息 (每个版本可能不同)
kind: #资源类别
metadata: #资源元数据
name
namespace
lables
annotations
spec: # 期望的状态(disired state)
status: # 当前状态,本字段有 Kubernetes 自身维护,用户不能去定义
帮助信息
# 查看apiVersion的各个版本信息
kubectl api-versions
# 获取字段设置帮助文档
kubectl explain pod
字段配置格式类型
资源清单中大致可以分为如下几种类型:
-
int32
-
string
-
IntOrString
-
boolean
-
Object
-
<map[String]string>
-
<[]string>
-
<[]Object>
apiVersion <string> #表示字符串类型
metadata <Object> #表示需要嵌套多层字段
labels <map[string]string> #表示由k:v组成的映射
finalizers <[]string> #表示字串列表
ownerReferences <[]Object> #表示对象列表
hostPID <boolean> #布尔类型
priority <integer> #整型
name <string> -required- #如果类型后面接 -required-,表示为必填字段
pod 生命周期
initContainers 示例
-
initC总是运行到成功完成为止。
-
每个initC容器都必须在下一个initC启动之前成功完成。
-
如果initC容器运行失败,K8S集群会不断的重启该pod,直到initC容器成功为止。
-
如果pod对应的restartPolicy为never,它就不会重新启动。
life/initcpod.yml
apiVersion: v1
kind: Pod
metadata:
name: myapp-pod
labels:
app: myapp
spec:
containers:
- name: myapp-container #相位
image: busybox:1.32.0
imagePullPolicy: IfNotPresent
command: [ 'sh','-c','echo The app is running! && sleep 60' ]
initContainers:
- name: init-myservice
image: busybox:1.32.0
imagePullPolicy: IfNotPresent
command: [ 'sh','-c','until nslookup myservice; do echo waitting for myservice; sleep 2; done;' ]
- name: init-mydb
imagePullPolicy: IfNotPresent
image: busybox:1.32.0
command: [ 'sh','-c','until nslookup mydb; do echo waitting for mydb; sleep 2; done;' ]
restartPolicy: Always
life/initcservice1.yml
apiVersion: v1
kind: Service
metadata:
name: myservice
spec:
ports:
- port: 80
targetPort: 9376
protocol: TCP
life/initcservice2.yml
apiVersion: v1
kind: Service
metadata:
name: mydb
spec:
ports:
- port: 80
targetPort: 9377
protocol: TCP
# 启动 myapp-pod
kubectl apply -f initcpod.yml
# 持续观察pod启动情况
kubectl get -w po myapp-pod
# 过段时间,运行init-myservice服务,观察pod状态变化
kubectl apply -f initcservice1.yml
# 过段时间,运行init-mydb服务,观察pod状态变化
kubectl apply -f initcservice2.yml
# 观察pod启动情况
# kubectl get -w po myapp-pod
NAME READY STATUS RESTARTS AGE
myapp-pod 0/1 Init:0/2 0 13s
myapp-pod 0/1 Init:1/2 0 90s
myapp-pod 0/1 Init:1/2 0 91s
myapp-pod 0/1 PodInitializing 0 4m45s
myapp-pod 1/1 Running 0 4m46s
readinessProbe 示例
就绪检测
life/readinessprobe.yml
apiVersion: v1
kind: Pod
metadata:
name: readinesspod-test
labels:
app: readinesspod-test
spec:
containers:
- name: readinesspod-test
image: nginx:1.17.10-alpine
imagePullPolicy: IfNotPresent
readinessProbe:
httpGet:
port: 80
path: /index1.html
initialDelaySeconds: 2
periodSeconds: 3
restartPolicy: Always
# 创建pod
kubectl apply -f readinessprobe.yml
# 检查pod状态,虽然pod状态显示running,但是ready显示0/1,因为就绪检查未通过
kubectl get pods
# 查看pod详细信息,文件最后一行显示错误信息
# Readiness probe failed: HTTP probe failed with statuscode: 404
kubectl describe pod readinesspod-test
# 进入pod内部,因为是alpine系统,需要使用sh命令
kubectl exec -it readinesspod-test sh
# 进入容器内目录
cd /usr/share/nginx/html/
# 追加一个index1.html文件
echo "welcome lagou" >> index1.html
# 退出容器,再次查看pod状态,pod已经正常启动
exit
# 检查pod状态,ready显示1/1
kubectl get pods
livenessProbe 示例
存活检测
-
exec
:执行命令 -
httpGet
:执行 HTTP 请求 -
tcpSocket
:涉及 TCP 端口的操作
示例一
life/livenessprobe1.yml
apiVersion: v1
kind: Pod
metadata:
name: livenessprobepod1-test
labels:
app: livenessprobepod1-test
spec:
containers:
- name: livenessprobepod1-test
image: busybox:1.32.0
imagePullPolicy: IfNotPresent
command: [ '/bin/sh','-c','touch /tmp/livenesspod;sleep 30; rm -rf /tmp/livenesspod; sleep 3600' ]
livenessProbe:
exec:
command: [ 'test','-e','/tmp/livenesspod' ]
initialDelaySeconds: 1
periodSeconds: 3
restartPolicy: Always
# 创建pod
kubectl apply -f livenessprobe1.yml
# 持续观察pod状态,发现重启过
kubectl get -w -f livenessprobe1.yml
NAME READY STATUS RESTARTS AGE
livenessprobepod1-test 1/1 Running 0 38s
livenessprobepod1-test 1/1 Running 1 (1s ago) 70s
示例二
life/livenessprobe2.yml
apiVersion: v1
kind: Pod
metadata:
name: livenesspod2-test
labels:
app: livenesspod2-test
spec:
containers:
- name: livenesspod2-test
image: nginx:1.17.10-alpine
imagePullPolicy: IfNotPresent
livenessProbe:
httpGet:
port: 80
path: /index.html
initialDelaySeconds: 3
timeoutSeconds: 10
restartPolicy: Always
# 创建pod
kubectl apply -f livenessprobenginxpod.yml
# 持续查看pod状态
kubectl get po livenesspod2-test -w
# 进入容器内部
kubectl exec -it livenesspod2-test sh
# 删除index.html文件,退出容器
rm -rf /usr/share/nginx/html/index.html
exit
# 持续查看pod状态
kubectl get po livenesspod2-test -w
NAME READY STATUS RESTARTS AGE
livenesspod2-test 1/1 Running 0 19s
livenesspod2-test 1/1 Running 1 (1s ago) 3m21s # 说明pod已经重启一次
因为liveness监控index.html页面已经被删除,所以pod需要重新启动,重启后又重新创建nginx容器。nginx镜像中默认有index.html页面。
示例三
life/livenessprobe3.yml
apiVersion: v1
kind: Pod
metadata:
name: livenesspod3-test
labels:
app: livenesspod3-test
spec:
containers:
- name: livenesspod3-test
image: nginx:1.17.10-alpine
imagePullPolicy: IfNotPresent
livenessProbe:
#监听8080端口,如果8080端口没有反馈信息,重启pod
tcpSocket:
port: 8080
initialDelaySeconds: 10
periodSeconds: 3
timeoutSeconds: 5
restartPolicy: Never
# 创建pod
kubectl apply -f livenessprobe3.yml
# 查看pod状态,从Running变成Completed
kubectl get pod -w app: livenesspod3
# 通过describe查看错误信息
Warning Unhealthy 5m3s (x3 over 5m9s) kubelet Liveness probe failed: dial tcp 10.244.2.126:8080: connect: connection refused
Normal Killing 5m3s kubelet Stopping container livenesspod3-test
钩子函数案例
life/lifecycle.yml
apiVersion: v1
kind: Pod
metadata:
name: lifecycle-test
labels:
app: lifecycle-test
spec:
containers:
- name: lifecycle-test
image: busybox:1.32.0
imagePullPolicy: IfNotPresent
command: [ 'sh','-c','sleep 5000' ]
lifecycle:
postStart:
exec:
command: [ 'mkdir','-p','/lagou/k8s/index.html' ]
restartPolicy: Always
# 创建pod
kubectl apply -f lifecycle.yml
# 查看pod状态
kubectl get pod lifecycle-test
# 查看是否创建了/lagou/k8s/index.html文件
kubectl exec lifecycle-test -- ls /lagou/k8s
总结 pod 生命周期
pod对象自从创建开始至终止退出的时间范围称为生命周期,在这段时间中,pod会处于多种不同的状态,并执行一些操作;
其中,创建主容器为必须的操作,其他可选的操作还包括运行初始化容器(init container)、容器启动后钩子(start hook)、容器的存活性探测(liveness probe)、就绪性探测(readiness probe)以及容器终止前钩子(pre stop hook)等,这些操作是否执行则取决于pod 的定义 。
pod 的相位
使用 kubectl get pods
命令,STATUS 被称之为相位(phase)
无论是手动创建还是通过控制器创建pod,pod对象总是应该处于其生命进程中以下几个相位之一:
-
Pending
(悬决):Pod 已被 Kubernetes 系统接受,但有一个或者多个容器尚未创建亦未运行。此阶段包括等待 Pod 被调度的时间和通过网络下载镜像的时间 -
Running
(运行中):Pod 已经绑定到了某个节点,Pod 中所有的容器都已被创建。至少有一个容器仍在运行,或者正处于启动或重启状态 -
Succeeded
(成功):Pod 中的所有容器都已成功终止,并且不会再重启 -
Failed
(失败):Pod 中的所有容器都已终止,并且至少有一个容器是因为失败终止。也就是说,容器以非 0 状态退出或者被系统终止 -
Unknown
(未知):因为某些原因无法取得 Pod 的状态。这种情况通常是因为与 Pod 所在主机通信失败
pod的相位是在其生命周期中的宏观概念,而非对容器或pod对象的综合汇总,而且相位的数量和含义被严格界定。
pod 的创建过程
pod是k8s的基础单元,以下为一个pod资源对象的典型创建过程:
-
用户通过kubectl或其他api客户端提交pod spec给api server
-
api server尝试着将pod对象的相关信息存入etcd中,待写入操作执行完成,api server即会返回确认信息至客户端
-
api server开始反映etcd中的状态变化
-
所有的k8s组件均使用watch机制来跟踪检查api server上的相关变动
-
kube-scheduler通过其watch觉察到api server创建了新的pod对象但尚未绑定至任何工作节点
-
kube-scheduler为pod对象挑选一个工作节点并将结果信息更新至api server
-
调度结果信息由api server更新至etcd,而且api server也开始反映此pod对象的调度结果
-
pod被调度到目标工作节点上的kubelet尝试在当前节点上调用docker启动容器,并将容器的结果状态回送至api server
-
api server将pod状态信息存入etcd中
-
在etcd确认写入操作成功完成后,api server将确认信息发送至相关的kubelet
pod 生命周期中的重要行为
除了创建应用容器之外,用户还可以为pod对象定义其生命周期中的多种行为,如初始化容器、存活性探测及就绪性探测等
初始化容器
初始化容器即应用程序的主容器启动之前要运行的容器,常用于为主容器执行一些预置操作,它们具有两种典型特征
-
初始化容器必须运行完成直至结束,若某初始化容器运行失败,那么k8s需要重启它直到成功完成
-
每个初始化容器都必须按定义的顺序串行运行
有不少场景都需要在应用容器启动之前进行部分初始化操作,例如,等待其他相关联组件服务可用、基于环境变量或配置模板为应用程序生成配置文件、从配置中心获取配置等。
初始化容器的典型应用需求具体包含如下几种
-
用于运行特定的工具程序,出于安全等方面的原因,这些程序不适于包含在主容器镜像中
-
提供主容器镜像中不具备的工具程序或自定义代码
-
为容器镜像的构建和部署人员提供了分离、独立工作的途径,使得它们不必协同起来制作单个镜像文件
-
初始化容器和主容器处于不同的文件系统视图中,因此可以分别安全地使用敏感数据,例如secrets资源
-
初始化容器要先于应用容器串行启动并运行完成,因此可用于延后应用容器的启动直至其依赖的条件得到满足
pod资源的 spec.initContainers
字段以列表的形式定义可用的初始容器,其嵌套可用字段类似于 spec.containers
生命周期钩子函数
容器生命周期钩子使它能够感知其自身生命周期管理中的事件,并在相应的时刻到来时运行由用户指定的处理程序代码。k8s为容器提供了两种生命周期钩子:
-
postStart
:于容器创建完成之后立即运行的钩子处理器(handler),不过k8s无法确保它一定会先于容器中的entrypoint之前运行 -
preStop
:于容器终止操作之前立即运行的钩子处理器,它以同步的方式调用,因此在其完成之前会阻塞删除容器的操作调用
钩子处理器的实现方法由Exec和HTTP两种,前一种在钩子事件触发时直接在当前容器中运行由用户定义的命令,后一种则是在当前容器中向某url发起http请求。
postStart和preStop处理器定义在 spec.lifecycle
嵌套字段中
容器探测
容器探测是pod对象生命周期中的一项重要的日常任务,它是kubelet对容器周期性执行的健康状态诊断,诊断操作由容器的处理器进行定义。
k8s支持三种容器探针用于pod探测:
-
ExecAction:在容器中执行一个命令,并根据其返回的状态码进行诊断的操作称为Exec探测,状态码为0表示成功,否则即为不健康状态
-
TCPSocketAction:通过与容器的某TCP端口尝试建立连接进行诊断,端口能够成功打开即为正常,否则为不健康状态
-
HTTPGetAction:通过向容器IP地址的某指定端口的指定path发起HTTP GET请求进行诊断,响应码大于等于200且小于400时即为成功
任何一种探测方式都可能存在三种结果:
-
success(成功):容器通过了诊断
-
failure(失败):容器未通过了诊断
-
unknown(未知):诊断失败,因此不会采取任何行动
kubelet可在活动容器上执行两种类型的检测:
-
(livenessProbe) 存活性检测:用于判定容器是否处于运行状态,一旦此类检测未通过,kubelet将杀死容器并根据restartPolicy决定是否将其重启;未定义存活性检测的容器的默认状态为success
-
(readinessProbe) 就绪性检测:用于判断容器是否准备就绪并可对外提供服务;未通过检测的容器意味着尚未准备就绪,端点控制器会将其IP从所有匹配到此pod对象的service对象的端点列表中移除;检测通过之后,会再次将其IP添加至端点列表中
容器的重启策略
容器程序发生奔溃或容器申请超出限制的资源等原因都可能会导致pod对象的终止,此时是否应该重建该pod对象则取决于其重启策略(restartPolicy)属性的定义:
-
Always:但凡pod对象终止就将其重启,此为默认设定
-
OnFailure:仅在pod对象出现错误时方才将其重启
-
Never:从不重启
restartPolicy适用于pod对象中的所有容器,而且它仅用于控制在同一节点上重新启动pod对象的相关容器。首次需要重启的容器,将在其需要时立即进行重启,随后再次需要重启的操作将由kubelet延迟一段时间后进行,且反复的重启操作的延迟时长以此为10s、20s、40s、80s、160s和300s,300s是最大延迟时长。
事实上,一旦绑定到一个节点,pod对象将永远不会重新绑定到另一个节点,它要么被重启,要么终止,直到节点发生故障或被删除。
pod 的终止过程
当用户提交删除请求之后,系统就会进行强制删除操作的宽限期倒计时,并将TERM信息发送给pod对象的每个容器中的主进程。宽限期倒计时结束后,这些进程将收到强制终止的KILL信号,pod对象随即也将由api server删除,如果在等待进程终止的过程中,kubelet或容器管理器发生了重启,那么终止操作会重新获得一个满额的删除宽限期并重新执行删除操作。
一个典型的pod对象终止流程具体如下:
-
用户发送删除pod对象的命令
-
api服务器中的pod对象会随着时间的推移而更新,在宽限期内(默认30s),pod被视为dead
-
将pod标记为terminating状态
-
与第三步同时运行,kubelet在监控到pod对象转为terminating状态的同时启动pod关闭过程
-
与第三步同时运行,端点控制器监控到pod对象的关闭行为时将其从所有匹配到此端点的service资源的端点列表中移除
-
如果当前pod对象定义了preStop钩子处理器,则在其标记为terminating后即会以同步的方式启动执行;若宽限期结束后,preStop仍未执行结束,则第二步会被重新执行并额外获取一个时长为2s的小宽限期
-
pod对象中的容器进程收到TERM信号
-
宽限期结束后,若存在任何一个仍在运行的进程,那么pod对象即会收到SIGKILL信号
-
kubelet请求api server将此pod资源的宽限期设置为0从而完成删除操作,它变得对用户不再可见
默认情况下,所有删除操作的宽限期都是30s,不过,kubectl delete命令可以使用 --grace-period
选项自定义其时长,若使用 0 值则表示直接强制删除指定的资源,不过此时需要同时使用命令 --force
选项