我们知道 Pod 是 Kubernetes 集群中的最⼩单元,⽽ Pod 是有容器组组成的,所以在讨论 Pod 的⽣命周期的时候我们可以先来讨论下容器的⽣命周期。
Pod Hook 是由 kubelet 发起的,当容器中的进程启动前或者容器中的进程终⽌之前运⾏,这是包含在容器的⽣命周期之中。我们可以同时为 Pod 中的所有容器都配置 hook。
Kubernetes 为我们提供了两种钩⼦函数:
如果 PostStart 或者 PreStop 钩⼦失败, 它会杀死容器。所以我们应该让钩⼦函数尽可能的轻量。当 然有些情况下,⻓时间运⾏命令是合理的, ⽐如在停⽌容器之前预先保存状态。
另外我们有两种⽅式来实现上⾯的钩⼦函数:
以下示例中,定义了⼀个Nginx Pod,其中设置了 PostStart 钩⼦函数,即在容器创建成功后,写⼊⼀句话到 /usr/share/message
⽂件中。
apiVersion: v1
kind: Pod
metadata:
name: hook-demo1
spec:
containers:
- name: hook-demo1
image: nginx
lifecycle:
postStart:
exec:
command: ["/bin/sh", "-c", "echo Hello from the postStart handler > /usr/share/m
essage"]
当⽤户请求删除含有 pod 的资源对象时(如Deployment等),K8S 为了让应⽤程序优雅关闭(即让应⽤程序完成正在处理的请求后,再关闭软件),K8S提供两种信息通知:
docker stop
命令,docker 会先向容器中 PID 为1的进程发送系统信号 SIGTERM ,然后等待容器中的应⽤程序终⽌执⾏,如果等待时间达到设定的超时时间,或者默认超时时间(30s),会继续发送 SIGKILL 的系统信号强⾏ kill 掉进程。默认所有的优雅退出时间都在30秒内。kubectl delete
命令⽀持 --grace-period=
选项,这 个选项允许⽤户⽤他们⾃⼰指定的值覆盖默认值。值’0’代表 强制删除 pod. 在 kubectl 1.5 及以上的版 本⾥,执⾏强制删除时必须同时指定 --force --grace-period=0
。
强制删除⼀个 pod 是从集群状态还有 etcd ⾥⽴刻删除这个 pod。 当 Pod 被强制删除时, api 服务器 不会等待来⾃ Pod 所在节点上的 kubelet 的确认信息:pod 已经被终⽌。在 API ⾥ pod 会被⽴刻删 除,在节点上, pods 被设置成⽴刻终⽌后,在强⾏杀掉前还会有⼀个很⼩的宽限期。
以下示例中,定义了⼀个Nginx Pod,其中设置了 PreStop 钩⼦函数,即在容器退出之前,优雅的关闭 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"]
---
apiVersion: v1
kind: Pod
metadata:
name: hook-demo2
labels:
app: hook
spec:
containers:
- name: hook-demo2
image: nginx
ports:
- name: webport
containerPort: 80
volumeMounts:
- name: message
mountPath: /usr/share/
lifecycle:
preStop:
exec:
command: ['/bin/sh', '-c', 'echo Hello from the preStop Handler > /usr/share/mes
sage']
volumes:
- name: message
hostPath:
path: /tmp
另外 Hook 调⽤的⽇志没有暴露个给 Pod 的 event,所以只能通过 describe
命令来获取,如果有错误 将可以看到 FailedPostStartHook
或 FailedPreStopHook
这样的 event。
除了上⾯这两个钩⼦函数以外,还有⼀项配置会影响到容器的⽣命周期的,那就是健康检查的探针。
在 Kubernetes 集群当中,我们可以通过配置 liveness probe
(存活探针)和 readiness probe
(可读性探针)来影响容器的⽣存周期。
和前⾯的钩⼦函数⼀样的,我们这两个探针的⽀持两种配置⽅式:
我们先来给⼤家演示下存活探针的使⽤⽅法,⾸先我们⽤ exec 执⾏命令的⽅式来检测容器的存活,如下:
apiVersion: v1
kind: Pod
metadata:
name: liveness-exec
labels:
test: liveness
spec:
containers:
- name: liveness
image: busybox
args:
- /bin/sh
- -c
- touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600
livenessProbe:
exec:
command:
- cat
- /tmp/healthy
initialDelaySeconds: 5
periodSeconds: 5
我们这⾥需要⽤到⼀个新的属性: livenessProbe
,下⾯通过 exec 执⾏⼀段命令,其 中 periodSeconds 属性表示让 kubelet 每隔5秒执⾏⼀次存活探针,也就是每5秒执⾏⼀次上⾯的 cat /tmp/healthy
命令,如果命令执⾏成功了,将返回0,那么 kubelet 就会认为当前这个容器是存活的并且很监控,如果返回的是⾮0值,那么 kubelet 就会把该容器杀掉然后重启它。另外⼀个属性 initialDelaySeconds
表示在第⼀次执⾏探针的时候要等待5秒,这样能够确保我们的容器能够有⾜够的时间启动起来。
另外我们在容器启动的时候,执⾏了如下命令:
☁ ~ /bin/sh -c "touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600"
意思是说在容器最开始的30秒内有⼀个 /tmp/healthy
⽂件,在这30秒内执⾏ cat /tmp/healthy
命令 都会返回⼀个成功的返回码。30秒后,我们删除这个⽂件,现在执⾏ cat /tmp/healthy
是不是就会失败了,这个时候就会重启容器了。
我们来创建下该 Pod ,在30秒内,查看 Pod 的 Event :
☁ ~ kubectl describe pod liveness-exec
我们可以观察到容器是正常启动的,在隔⼀会⼉,⽐如40s后,再查看下 Pod 的 Event ,在最下⾯有⼀条信息显示 liveness probe 失败了,容器被删掉并重新创建。
然后通过 kubectl get pod liveness-exec
可以看到 RESTARTS 值加1了。
同样的,我们还可以使⽤ HTTP GET 请求来配置我们的存活探针,我们这⾥使⽤⼀个 liveness 镜像来验证演示下
apiVersion: v1
kind: Pod
metadata:
labels:
test: liveness
name: liveness-http
spec:
containers:
- name: liveness
image: cnych/liveness
args:
- /server
livenessProbe:
httpGet:
path: /healthz
port: 8080
httpHeaders:
- name: X-Custom-Header
value: Awesome
initialDelaySeconds: 3
periodSeconds: 3
同样的,根据periodSeconds
属性我们可以知道 kubelet 需要每隔3秒执⾏⼀次 liveness probe
,该探针将向容器中的 server 的8080端⼝发送⼀个 HTTP GET 请求。如果 server 的 /healthz 路径的 handler 返回⼀个成功的返回码, kubelet 就会认定该容器是活着的并且很健康,如果返回失败的返回码, kubelet 将杀掉该容器并重启它。。 initialDelaySeconds
指定 kubelet 在该执⾏第⼀次探测之前需要等待3秒钟。
通常来说,任何⼤于200⼩于400的返回码都会认定是成功的返回码。其他返回码都会被认为是失败的返回码。
我们可以来查看下上⾯的 healthz 的实现:
http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
duration := time.Now().Sub(started)
if duration.Seconds() > 10 {
w.WriteHeader(500)
w.Write([]byte(fmt.Sprintf("error: %v", duration.Seconds())))
} else {
w.WriteHeader(200)
w.Write([]byte("ok"))
}
})
⼤概意思就是最开始前10s返回状态码200,10s过后就返回500的 status_code
了。所以当容器启动3 秒后, kubelet 开始执⾏健康检查。第⼀次健康监测会成功,因为是在10s之内,但是10秒后,健康 检查将失败,因为现在返回的是⼀个错误的状态码了,所以 kubelet 将会杀掉和重启容器。
同样的,我们来创建下该 Pod 测试下效果,10秒后,查看 Pod 的 event,确认 liveness probe
失败并重启了容器。
☁ ~ kubectl describe pod liveness-http
然后我们来通过端⼝的⽅式来配置存活探针,使⽤此配置, kubelet 将尝试在指定端⼝上打开容器的套接字。 如果可以建⽴连接,容器被认为是健康的,如果不能就认为是失败的。
apiVersion: v1
kind: Pod
metadata:
name: goproxy
labels:
app: goproxy
spec:
containers:
- name: goproxy
image: cnych/goproxy
ports:
- containerPort: 8080
readinessProbe:
tcpSocket:
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
tcpSocket:
port: 8080
initialDelaySeconds: 15
periodSeconds: 20
我们可以看到,TCP 检查的配置与 HTTP 检查⾮常相似,只是将 httpGet 替换成了 tcpSocket 。
另外除了上⾯的 initialDelaySeconds
和 periodSeconds
属性外,探针还可以配置如下⼏个参数:
livene ss
则必须是 1。最⼩值是 1。