1、概述
容器生命周期钩子(Container Lifecycle Hooks)监听容器生命周期的特定事件,并在事件发生时执行已注册的回调函数。
钩子函数能够感知自身生命周期中的事件,并在相应的时刻到来时运行用户指定的程序代码。
kubernetes在主容器的启动之后和停止之前提供了两个钩子函数:
- postStart:一种容器钩子。该钩子在容器被创建后立刻触发,通知容器它已经被创建。该钩子不需要向其所对应的Hook Handler传入任何参数。如果该钩子对应的Hook Handler执行失败,则该容器会终止运行,并根据该容器的重启策略决定是否要重启该容器。更多信息,请参见Container Lifecycle Hooks。
- preStop :一种容器钩子。该钩子在容器被删除前触发,其所对应的Hook Handler必须在删除该容器的请求发送给Docker Daemon之前完成。在该钩子对应的Hook Handler完成后不论执行的结果如何,Docker Daemon会发送一个SIGTERN信号给Docker Daemon来删除该容器。更多信息,请参见Container Lifecycle Hooks 以及《详解Kubernetes Pod优雅退出》这篇博文。
2、钩子函数定义方式
钩子的回调函数支持两种方式定义动作,下面以 postStart 为例讲解,preStop 定义方式与其一致:
2.1 exec模式
在容器中执行指定的命令。如果命令退出时返回码为0,判定为执行成功。
示例:
1 2 3 4 5 6 |
|
2.2 httpGet模式
对容器中的指定端点执行HTTP GET请求,如果响应的状态码大于等于200且小于400,判定为执行成功。
httpGet模式支持以下配置参数:
- Host:HTTP请求主机地址,不设置时默认为Pod的IP地址。
- Path:HTTP请求路径,默认值是/。
- Port:HTTP请求端口号。
- Scheme:协议类型,支持HTTP和HTTPS协议,默认是HTTP。
- HttpHeaders:自定义HTTP请求头。
示例:访问 http://192.168.2.150:80/users
1 2 3 4 5 6 7 |
|
3、源码讲解
3.1 PostStart
Kubernetes源码(1.21.5,已在源码中移除dockershim模块):
pkg/kubelet/kuberuntime/kuberuntime_container.go:
开始容器方法主要包括拉取镜像、创建容器、start容器、运行容器postStart钩子函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
|
(1)关注下创建容器,kubelet调用docker csi,相当于执行 docker create 命令快讯:
pkg/kubelet/cri/remote/remote_runtime.go:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
|
(2)start容器,kubelet调用docker csi,相当于执行 docker start 命令:
pkg/kubelet/cri/remote/remote_runtime.go:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
(3)运行容器postStart钩子函数(kubelet):
pkg/kubelet/lifecycle/handlers.go:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
cri-dockerd源码:
(1)创建容器
core/container_create.go:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 |
|
调用CreateContainer方法(libdocker/kube_docker_client.go),然后作为 docker 客户端调用 docker 服务端 ContainerCreate 方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
vendor/github.com/docker/docker/client/container_create.go:
(2)start容器
core/container_start.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
libdocker/kube_docker_client.go
1 2 3 4 5 6 7 8 9 |
|
github.com/docker/docker/client/container_start.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
moby源码(docker改名为moby):
(1)创建容器
api/server/router/container/container.go:
1 |
|
api/server/router/container/container_routes.go:
(2)start容器
api/server/router/container/container.go:
1 |
|
daemon/start.go:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 |
|
3.2 PreStop
源码分析见《详解Kubernetes Pod优雅退出》这篇博文,本文不再赘余。
4、postStart 异步执行
在第三章节讲述开始容器方法时,我们已经很清晰知道,开始容器方法先运行容器,再运行运行容器 postStart 钩子函数。
需要注意的事,docker start 命令在启动容器时,并不会等待 ENTRYPOINT 命令执行完毕后才返回。它只会等待容器的主进程启动并确认其正常运行,然后立即返回。换句话说,docker start 命令返回时,ENTRYPOINT 命令已经开始执行,但不一定已经执行完毕。
所以PostStart的执行相对于容器主进程的执行是异步的,它会在容器start后立刻触发,并不能保证PostStart钩子在容器ENTRYPOINT执行完毕之前运行。
4.1 关于postStart异步执行测试
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
创建上面的pod,通过进入pod,查看log.log打印日志,证明:
- postStart是否会挡住主进程的启动
- postStart是否是异步执行
如果 postStart 会阻挡 ENTRYPOINT 的启动,则日志文件内容应该是:
1 2 |
|
否则内容应该是:
1 2 |
|
实验结果(打印时间相差5秒):
1 2 3 |
|
修改YAML文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
如果 PostStart 不是异步执行,则日志文件内容应该是:
1 2 |
|
否则内容应该是:
1 2 |
|
实验结果(时间相差5秒):
1 2 3 4 5 |
|
4.2 结论
- postStart不会挡住主进程的启动;
- postStart的执行相对于容器主进程的执行是异步的,它会在容器start后立刻触发,并不能保证PostStart钩子在容器ENTRYPOINT指令执行完毕之前运行。
5、使用场景
5.1 postStart
- 数据库连接:在容器启动后,可以使用 postStart 钩子来建立数据库连接,并确保应用程序在启动时可以正常访问数据库;
- 文件下载:容器启动后,可以使用 postStart 钩子从外部下载文件,并将其放置在容器内部以供应用程序使用;
- 启动后台任务:在容器启动后,可以使用 postStart 钩子来启动或触发后台任务,例如数据同步、日志收集等。
5.2 preStop
- 优雅终止:在容器终止之前,可以使用 preStop 钩子发送信号或通知给应用程序,让应用程序优雅地处理未完成的请求或任务,并进行清理操作;
- 数据持久化:在容器终止之前,可以使用 preStop 钩子将容器中的数据持久化到外部存储,以确保数据不会丢失;
- 日志上传:在容器终止之前,可以使用 preStop 钩子将容器中的日志上传到中央日志系统,以便进一步分析和存档。
6、总结
容器生命周期钩子(postStart和preStop)提供在容器启动后和停止前执行自定义操作的能力,适用于数据库连接、文件下载、优雅终止等场景。postStart平时用的场景不是很多,重点关注preStop。