今天跟大家一起分享一个不经常想起来,但十分有用的暗黑小招式 -- Container Lifecycle Hooks。没有错,Docker 在 kubernetes 运行中也有类似于许多编程语言框架中的组件生命周期钩子。钩子使 Container 能够了解其管理生命周期中的事件,并在执行相应的生命周期钩子时运行在处理程序中实现的代码。 这样一来,我们每个应用程序都可以在启动之前进行一些准备工作或者结束之前进行一些清理工作。
Kubernetes 给我们暴露两个 Lifecycle Hooks 入口 (好抠啊,你看看 openresty 提供了多少 Hooks)
1. PostStart :在创建容器后立即执行。但是,无法保证挂钩将在容器ENTRYPOINT之前执行。没有参数传递给处理程序。由于无法保证和容器内其它进程启动的顺序相关联,所以不是应用程序进行启动前配置的最佳解决方案。
2. PreStop:在销毁容器之前即执行。它是阻塞的,意味着它是同步的,所以它必须在删除容器的调用之前完成。没有参数传递给处理程序。很适合作为应用程序优雅退出的机制的,可以定义一系列的行为来释放容器占有的资源、进行通知和告警来实现优雅退出。
Container可以通过下面的两种类型的钩子处理程序:
Exec - 执行特定命令,例如pre-stop.sh,在Container的cgroups和名称空间内。命令执行过程消耗的 CPU 和内存受到容器的 limits 参数限制。
HTTP - 对Container上的特定端点执行HTTP请求。
重要提示:
通常,Hook 命令只会执行一次。如果你要想多次执行效果,那就自己写 shell 或者 可以执行的程序,让你的逻辑可以重复执行。
上面大致说了一些 Container Lifecycle Hooks 的基本概念,希望大家有所了解。如果还没有看明白,还是自行百度下。那么我就举一些“栗子”看看这东西怎么用,然后是不是非常简单的就能上手呢?(实际 so easy,一看就会用!)
PostStart
真的不想写PostStart,基本用的超级少。 不过还是举一个例,因为可以做一些周边环境同步或者设置环境变量的小工作。 或者你期望启动的时候工作完全就是异步执行,PostStart 不会让你失望。
通过上面的内容我们可以看到,在容器启动的时候,我将外部一个目录里面的自定义的 index.html 去覆盖 nginx 容器中默认的 index.html。 但是由于 PostStart 和 ENTRYPOINT 之间没有保证谁先会执行,所以希望在 nginx 启动之前更换 index.html 的小小期望,怕是没有办法实现。
但是有一种场景,就是容器和和运行内容是分离的情况,就一定需要在 ENTRYPOINT 将外部的执行代码同步到容器内执行。这个是什么鬼场景,没有毛病。我们常见的是代码和环境一起封装到 docker image 里面,然后版本更迭是重新向 kubernetes 推送 delpoyment。而这个场景,代码和容器是分离的,容器启动前就从 http 或则其他文件服务器同步内容文件下来运容器内的服务。 更新这些文件,就可以完成版本更新。而不需要重新分发容器,而只需要简单的重启下容器就行。 要知道在一个 300 以上 node 的 kubernetes 的集群里分发一个新的 docker image,尤其是生产集群,是一项非常有挑战的工作。
那PostStart 既然不能帮我们完成上面的场景,怎么办? 不慌,老李来帮你。 我们在后面就会看到 Init Container。
PreStop
我们还是继续通过部署一个 nginx 的 pod 。通过在 delpoyment.yaml 中添加 PreStop 字段,告诉 Kubernetes 在未来 Pod 消亡的时候,需要执行文件中定义的 command。 用过执行这个命令,让 nginx 执行优雅退出。
PreStop 可能是我觉得在整个生命周期中最有用,实用场景最多。 比如:
1. 关闭前等待某一个状态完成;
2. 关闭前同步一些状态(数据)到其他的地方;
3. 关闭前通知某一个系统或者更新一个状态;
..............
这里好多场景,我就不一一列举,请各位看官自行脑洞。 总得一说,PreStop 就是为了让你在 Pod 在结束之前执行一些列你想要完成的动作,当然再强调一下,这些动作都是同步执行的。
重要提示:
这里需要考虑的一个问题在容器异常终止或者崩溃的情况下,将没有机会进行PreStop操作。所以这里要考虑下异常处理得问题。。。 千万别忘记了。
Init Container
老李偷偷传你一招秘籍,好好拿着。 保不齐那天就能救命。那我们一起来看看它的概念。
Init Container 就是做初始化工作的容器。它可是同步的,同步的,同步的,重要的事情我说了3遍。可以有一个或多个执行命令或者动作,如果有多个,这些 Init Container 按照定义的顺序依次执行,只有所有的InitContainer 执行完后,主容器才启动。由于一个Pod里的存储卷是共享的,所以 Init Container 里产生的数据可以被主容器使用到。
Init Container可以在多种K8S资源里被使用到如Deployment、DaemonSet, PetSet/StatefulSet、Job等,但归根结底都是在Pod启动时,在主容器启动前执行,做初始化工作。
看到上面的这些东西内心是不是一个大石头落地了,总算有一个东西跟 PostStop 配对了。 不多说,我们看看 deployment.yaml 怎么写。
写到这里,还有一些重要的提示,一定要牢记:
1. 如果 Pod 的 Init 容器失败,Kubernetes 会不断地重启该 Pod,直到 Init 容器成功为止。然而,如果 Pod 对应的 restartPolicy 为 Never,它不会重新启动。
2. Init 容器支持应用容器的全部字段和特性,但不支持 Readiness Probe,因为它们必须在 Pod 就绪之前运行完成。
3. 如果为一个 Pod 指定了多个 Init 容器,那些容器会按顺序一次运行一个。 每个 Init 容器必须运行成功,下一个才能够运行。
4. 因为 Init 容器可能会被重启、重试或者重新执行,所以 Init 容器的代码应该是幂等的。 特别地,被写到 EmptyDirs 中文件的代码,应该对输出文件可能已经存在做好准备。
5. 在 Pod 上使用 activeDeadlineSeconds,在容器上使用 livenessProbe,这样能够避免 Init 容器一直失败。 这就为 Init 容器活跃设置了一个期限。
6. 在 Pod 中的每个 app 和 Init 容器的名称必须唯一;与任何其它容器共享同一个名称,会在验证时抛出错误。
7. 对 Init 容器 spec 的修改,被限制在容器 image 字段中。 更改 Init 容器的 image 字段,等价于重启该 Pod。