容器生命周期-回调
💥这里说的是容器,容器,容器,不是Pod,Pod,Pod
💥这里说的是容器,容器,容器,不是Pod,Pod,Pod
💥这里说的是容器,容器,容器,不是Pod,Pod,Pod
容器状态包含3个:
-
waiting,一般对应Pod的pending阶段
-
running,容器运行OK
-
terminated,容器退出
我们在实际工作中,可能会遇到需要在容器生命周期中按预定计划执行某个动作:
- 容器程序的准备工作,以及容器结束之前的处理工作。
- 容器开始之前下载一些包,或者容器结束之前上传日志等。
- 容器内程序优雅的关闭。
k8s 给我们提供了两个回调用于处理这些工作,分别是 postStart和 preStop.
https://kubernetes.io/zh/docs/concepts/containers/container-lifecycle-hooks/
执行方式
回调函数的执行方式有三种:
$ kubectl explain pod.spec.containers.lifecycle.postStart
FIELDS:
exec <Object>
One and only one of the following should be specified. Exec specifies the
action to take.
httpGet <Object>
HTTPGet specifies the http request to perform.
tcpSocket <Object>
TCPSocket specifies an action involving a TCP port. TCP hooks not yet
supported
👙通常主要使用的就是 exec 和 httpGet
postStart
- k8s在在容器创建后立即【发送】postStart,但【不保证】在 ENTRYPOINT 之前运行
- 如果它卡住,【容器】无法达到 running 状态。
示例:
apiVersion: v1
kind: Pod
metadata:
name: poststart-test
spec:
containers:
- name: poststart-test
image: nginx
lifecycle:
postStart:
exec:
command: ["/bin/sh", "-c", "echo This is postStart-test > /usr/share/nginx/html/index.html"]
[root@k8s00 ~]# kubectl apply -f poststart-test.yaml
pod/poststart-test created
[root@k8s00 ~]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
poststart-test 0/1 ContainerCreating 0 7s <none> k8s02 <none> <none>
[root@k8s00 ~]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
poststart-test 1/1 Running 0 10s 10.97.2.6 k8s02 <none> <none2
[root@k8s00 ~]# curl 10.97.2.6
This is postStart-test
preStop
- k8s它将在【容器】结束前立即【发送】preStop,但执行时间受限于【Pod】终止宽限期。
- 应该设置足够长的终止宽限期
terminationGracePeriodSeconds
,这个期限应该大于preStop执行时间+kubelet通知【container runtime】发关闭信号给【容器】+【容器】关闭时间。 - 它执行期间,Pod依然处于svc的端点列表内。
【容器】的结束需要被k8s知悉,任何k8s不知悉的结束动作,不会触发preStop。例如容器因执行完自动结束,这种状态k8s无法得知。
示例1:
终止前写入数据
apiVersion: v1
kind: Pod
metadata:
name: prestop-test
spec:
containers:
- name: prestop-test
image: nginx
volumeMounts:
- name: prestop-test-tmp
mountPath: /usr/share
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "echo This is preStop > /usr/share/message"]
volumes:
- name: prestop-test-tmp
hostPath:
path: /tmp
[root@k8s00 ~]# kubectl delete pod prestop-test
# 这里 prestop-test 被调度到 k8s02
[root@k8s02 ~]# cat /tmp/message
This is preStop
示例2:
终止前优雅关闭nginx
apiVersion: v1
kind: Pod
metadata:
name: hook-demo2
spec:
containers:
- name: hook-demo2
image: nginx
lifecycle:
preStop:
exec:
command: ["/usr/sbin/nginx","-s","quit"] # 优雅退出
💥分歧💥
说明: Kubernetes 只有在 Pod 手动结束或因控制器调度的时候才会发送 preStop 事件, 但在 Pod(Completed)时 preStop 的事件处理逻辑不会被触发。这个限制在 issue #55087 中被追踪。
因此,💥Job类不应该使用preStop来执行结束逻辑💥,而是应该将逻辑放在容器内执行。k8s无法提前获悉容器退出信号,因此无法在容器退出前发送preStop。
容器生命周期-探针
https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#lifecycle-1
k8s通过探测器判断何时重启容器。
探测器种类:启动探测器startupProbe、存活探测器livenessProbe,就绪探测器readinessProbe。
每种探测器探测方式:命令方式,http方式,tcp方式。
- ExecAction: 在容器内执行指定命令。如果命令退出时返回码为 0 则认为诊断成功。
- TCPSocketAction: 对容器的 IP 地址上的指定端口执行 TCP 检查。如果端口打开,则诊断被认为是成功的。
- HTTPGetAction: 对容器的 IP 地址上指定端口和路径执行 HTTP Get 请求。如果响应的状态码大于等于 200 且小于 400,则诊断被认为是成功的。
例如命令方式:
livenessProbe:
exec:
command:
- cat
- /tmp/healthy
initialDelaySeconds: 5
periodSeconds: 5
优先级:启动探测器>存活探测器>就绪探测器
👙默认行为:任何没有显式指定的探针,自动设为success
。
kubelet 使用启动探测器可以知道应用程序容器什么时候启动了。 如果配置了这类探测器,就可以控制容器在启动成功后再进行存活性和就绪检查, 确保这些存活、就绪探测器不会影响应用程序的启动。 这可以用于对慢启动容器进行存活性检测,避免它们在启动运行之前就被杀掉。
若启动探针存在,则其它两类探针将延后运行。行为上,探针失败则重启【容器】.。
若存活探针存在,行为上,探针失败重启【容器】。
若就绪探针存在,行为上,探针成功Pod加入svc端点列表。默认为failure
,因此,成功之前svc会将Pod从端点列表中移除。
首先,就绪探针比较符合常见需求,即探针失败,则不会提供服务。但它不会重启容器,因此无法自动解决问题。
其次,需要存活探针来判断程序的基本条件从而确保容器重启。
最后,启动探针可以判断环境,环境不满足直接重启。
三种探测器,均在 pod.spec.containers 下配置
➜ ~ kubectl explain pod.spec.containers | grep Probe
livenessProbe <Object>
readinessProbe <Object>
startupProbe <Object>
探测器的探测周期配置
initialDelaySeconds
:容器启动后要等待多少秒后存活和就绪探测器开始第一次探测,默认是 0 秒,最小值是 0。periodSeconds
:执行探测的时间间隔(单位是秒)。默认是 10 秒。最小值是 1。timeoutSeconds
:探测的超时后等待多少秒。默认值是 1 秒。最小值是 1。successThreshold
:探测器在失败后,被视为成功的最小连续成功数。默认值是 1。 存活和启动探测的这个值必须是 1。最小值是 1。failureThreshold
:当探测失败时,Kubernetes 的重试次数。 存活探测情况下的放弃就意味着重新启动容器。 就绪探测情况下的放弃 Pod 会被打上未就绪的标签。默认值是 3。最小值是 1。
启动探测器 startupProbe
💥目的在于设置一个容器启动宽容期,适合慢速启动程序的初期检测。只要启动探测器在超时时间以内成功一次,后续的健康状态将交给存活探测器。
💥如果启动探测器在超时时间内没有成功,则容器直接被杀死,而pod按照 restartPolicy 策略执行。
例如:
ports:
- name: liveness-port
containerPort: 8080
hostPort: 8080
startupProbe:
httpGet:
path: /healthz
port: liveness-port
failureThreshold: 30 # 错误次数阈值
periodSeconds: 10 # 检测间隔周期
# 启动超时时间=failureThreshold*periodSeconds
上述例子意思是:kubelet 检测总时间超过 300 秒后,如果还未检测成功,认为启动失败。
存活探测器 livenessProbe
💥目的在于确认容器是否健康,但在不健康的时候重启容器。它将在容器整个生命周期中运行。
💥它与就绪探测器最大区别就是,kubelet检测失败后会重启容器。
例子如下:
apiVersion: v1
kind: Pod
metadata:
labels:
test: liveness
name: liveness-http
spec:
containers:
- name: liveness
image: k8s.gcr.io/liveness
args:
- /server
livenessProbe:
httpGet:
path: /healthz
port: 8080
httpHeaders:
- name: Custom-Header
value: Awesome
initialDelaySeconds: 3
periodSeconds: 3
探测器会认为 200<=x<400 的状态码为正常。
就绪探测器 readinessProbe
👙生产应用应该有的配置。
💥目的在于仅确认容器是否健康,但是并不重启容器;它将在容器整个生命周期中运行。
💥容器刚启动的时候,就绪探针会告知kubelet是否把当前pod的endpoint地址加入svc 。
💥容器运行中如果就绪探针失败,就绪探针负责告知kubelet将pod endpoint从svc中移除
适用于下列场景:
- 容器启动后,加载数据多,加载完之前无法提供服务
例如:
apiVersion: v1
kind: Pod
metadata:
name: goproxy
labels:
app: goproxy
spec:
containers:
- name: goproxy
image: k8s.gcr.io/goproxy:0.1
ports:
- containerPort: 8080
readinessProbe:
tcpSocket:
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
上述例子意思是:kubelet 等待 initialDelaySeconds 后开始第一次检测,每次检测间隔10秒,检测成功,认为可以提供服务
最佳实践
-
存活和就绪探针的健康状态接口应该是两个互相独立的接口
-
存活探针的健康状态接口应该是直接返回http code,不应该有任何逻辑
-
就绪探针的健康状态接口应该提供就绪所需要的逻辑。但是不应该添加重新构建就绪的逻辑。
例如:处理http请求的程序需要一个数据库连接的就绪状态,那么就应该在就绪探针的健康状态接口里添加数据库连接的检查逻辑。
-
存活探针可以用来判定程序的最简单功能是否正常,如果最简单都不正常,则无需让程序启动。
-
就绪探针可以用来判定程序复杂一些的逻辑是否正常。