【云原生丨Kubernetes系列⑧】深入研究影响 Pod 中容器生命周期的因素

前言

我们知道 Pod 是 Kubernetes 集群中的最⼩单元,⽽ Pod 是有容器组组成的,所以在讨论 Pod 的⽣命周期的时候我们可以先来讨论下容器的⽣命周期。

【云原生丨Kubernetes系列⑧】深入研究影响 Pod 中容器生命周期的因素_第1张图片

文章目录

  • 前言
  • Pod Hook
    • 环境准备
    • 优雅删除资源对象
  • 健康检查


Pod Hook

Pod Hook 是由 kubelet 发起的,当容器中的进程启动前或者容器中的进程终⽌之前运⾏,这是包含在容器的⽣命周期之中。我们可以同时为 Pod 中的所有容器都配置 hook。

Kubernetes 为我们提供了两种钩⼦函数:

  • PostStart:这个钩⼦在容器创建后⽴即执⾏。但是,并不能保证钩⼦将在容器 ENTRYPOINT 之前运 ⾏,因为没有参数传递给处理程序。主要⽤于资源部署、环境准备等。不过需要注意的是如果钩 ⼦花费太⻓时间以⾄于不能运⾏或者挂起, 容器将不能达到 running 状态。
  • PreStop:这个钩⼦在容器终⽌之前⽴即被调⽤。它是阻塞的,意味着它是同步的, 所以它必须 在删除容器的调⽤发出之前完成。主要⽤于优雅关闭应⽤程序、通知其他系统等。如果钩⼦在执 ⾏期间挂起, Pod阶段将停留在 running 状态并且永不会达到 failed 状态。

如果 PostStart 或者 PreStop 钩⼦失败, 它会杀死容器。所以我们应该让钩⼦函数尽可能的轻量。当 然有些情况下,⻓时间运⾏命令是合理的, ⽐如在停⽌容器之前预先保存状态。

另外我们有两种⽅式来实现上⾯的钩⼦函数:

  • Exec - ⽤于执⾏⼀段特定的命令,不过要注意的是该命令消耗的资源会被计⼊容器。
  • HTTP - 对容器上的特定的端点执⾏ HTTP 请求。

环境准备

以下示例中,定义了⼀个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提供两种信息通知:

  • 默认:K8S 通知 node 执⾏ docker stop 命令,docker 会先向容器中 PID 为1的进程发送系统信号 SIGTERM ,然后等待容器中的应⽤程序终⽌执⾏,如果等待时间达到设定的超时时间,或者默认超时时间(30s),会继续发送 SIGKILL 的系统信号强⾏ kill 掉进程。
  • 使⽤ pod ⽣命周期(利⽤ PreStop 回调函数),它执⾏在发送终⽌信号之前。

默认所有的优雅退出时间都在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 命令来获取,如果有错误 将可以看到 FailedPostStartHookFailedPreStopHook 这样的 event。


健康检查

除了上⾯这两个钩⼦函数以外,还有⼀项配置会影响到容器的⽣命周期的,那就是健康检查的探针。

在 Kubernetes 集群当中,我们可以通过配置 liveness probe (存活探针)和 readiness probe (可读性探针)来影响容器的⽣存周期。

  • kubelet 通过使⽤ liveness probe 来确定你的应⽤程序是否正在运⾏,通俗点将就是是否还活着。⼀般来 说,如果你的程序⼀旦崩溃了, Kubernetes 就会⽴刻知道这个程序已经终⽌了,然后就会重启这个程序。⽽我 们的 liveness probe 的⽬的就是来捕获到当前应⽤程序还没有终⽌,还没有崩溃,如果出现了这些情况,那么 就重启处于该状态下的容器,使应⽤程序在存在 bug 的情况下依然能够继续运⾏下去。
  • kubelet 使⽤ readiness probe 来确定容器是否已经就绪可以接收流量过来了。这个探针通俗点讲就是说是 否准备好了,现在可以开始⼯作了。只有当 Pod 中的容器都处于就绪状态的时候 kubelet 才会认定该 Pod 处 于就绪状态,因为⼀个 Pod 下⾯可能会有多个容器。当然 Pod 如果处于⾮就绪状态,那么我们就会将他从我们 的⼯作队列(实际上就是我们后⾯需要重点学习的 Service)中移除出来,这样我们的流量就不会被路由到这个 Pod ⾥⾯来了。

和前⾯的钩⼦函数⼀样的,我们这两个探针的⽀持两种配置⽅式

  • exec:执⾏⼀段命令
  • http:检测某个 http 请求
  • tcpSocket:使⽤此配置, kubelet 将尝试在指定端⼝上打开容器的套接字。如果可以建⽴连接,容器被认为 是健康的,如果不能就认为是失败的。实际上就是检查端⼝

我们先来给⼤家演示下存活探针的使⽤⽅法,⾸先我们⽤ 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 属性外,探针还可以配置如下⼏个参数:

  • timeoutSeconds:探测超时时间,默认1秒,最⼩1秒。
  • successThreshold:探测失败后,最少连续探测成功多少次才被认定为成功。默认是 1,但是如果是livene ss则必须是 1。最⼩值是 1。
  • failureThreshold:探测成功后,最少连续探测失败多少次才被认定为失败。默认是 3,最⼩值是 1。

【云原生丨Kubernetes系列⑧】深入研究影响 Pod 中容器生命周期的因素_第2张图片

你可能感兴趣的:(云原生,从入门到进阶,kubernetes,云原生,docker)