完整的Kubernetes Deployment yaml文件应该包含什么?
刚开始接触 Kubernetes 那会,从官网下载了一个 nginx Pod 模板文件,通过 kubectl apply 启动后,之后执行 kubectl get pod 展示出了一个处...
刚开始接触 Kubernetes 那会,从官网下载了一个 nginx Pod 模板文件,通过 kubectl apply 启动后,之后执行 kubectl get pod 展示出了一个处于 running 状态的 pod, 第一个 hello word 就跑起来了,转眼一想,Kubernetes 可是工业级的编排平台,能够保证容器的管理、编排、弹性扩缩容,现在编排运行没什么问题,但没体现出对容器的管理和弹性扩缩容。
后来接着翻了翻官网资料,发现了 pod 是 kubernetes 最小单元,同时是散乱分布在各个节点上的,如果要想把它管理起来,必须使用更高级别资源控制对象,囿于应用本身是无状态应用,对号入座就选择了 Kubernetes Deployment,从 yaml 编排文件上看呢,Deployment主要包括标签选择器、期望副本数量、pod 模板组成的。通过简单的配置和修改就把单个 Pod 改成可以滚动扩缩容的 Deployment,当时看来做到这一步就万事大了,Deployment 帮助我管理 Pod 的副本数量,Pod 宕机,它会自动拉起.... 一切看起来是那么的和谐和美好。
但是好景不长,团队所有人员都介入开发之后,就开始有人反馈服务不能正常访问了,但是 kubectl get pod 服务处于 running 状态,查看日志发现服务根本没有正常启动,其实服务内部已经崩溃了,因为主进程没有退出,Kubernetes 认为服务是正常运行的,这种问题 Kubernetes 不能解决?明明已经崩溃,还是显示 running ?后来服务上线了,测试人员在压力测试过程 delete pod 之后,发现故障不能及时转移,总会出现一定失败率,怎么回事呢?服务无缘无故崩溃了,但是根本看不到日志,排查调试问题无从下手......
说好的是把依赖和运行环境打包成自包含、轻量级、可移植的容器,给我带来的好处就是一次构建,到处运行,而现在变成了到处崩溃?如何有效处理,且看下文。
Deployment必须包含资源对象

-
Deployment 是一个控制器,能够用来控制 pod 数量跟期望数量一致,配置 pod 的发布方式 Deployment 会按照给定策略进行发布指定 pod,保证在更新过程中不可用数量在限定范围内。

-
Deployment 控制 ReplicatSet, ReplicateSet控制 pod 副本的数量,pod 所属于 Replicaset,同一个 Replicaset 下的 pod 都是一样的

-
ReplicaSet 管理多个 Pod 副本,当有一个副本出现故障时,会不断的重启,重启的时间间隔以指数级增长,直到 5 分钟,不会自动转移。你或许会很奇怪,为什么 Pod 不会自动移除或者重新调度,这是因为 ReplicaSet 并不关心 Pod 是否处于正常运行状态,它只关心期望的副本数量和当前的副本数量是否一致。
-
一个 Pod 中可以包含多个容器,如果想查看其中单个 Container 容器,可以使用 kubectl logs client-pod -c client-container。
-
如果一个容器依赖与另外一个容器,使用 initContainer 来延迟 Pod 主容器的启动。同时 initContainer 是顺序启动容器。但更建议你添加 Readisness 就绪探针来探测服务是否正常启动,如果这个服务没有准备好,那么 Kubernetes 就会阻止这个服务成为服务端点。
-
volumeMount 数据卷挂载,通常我们会通过定义 pv pvc 把容器内部数据挂载持久性存储卷或者宿主机特定目录。不过我就曾经发现有人把配置和证书等信息放置持久存储卷到特定目录,然后 mount 到容器内部。从管理和使用的角度不建议使用这种方式,更推荐使用 ConfigMap 和Secret。之前总结过一篇关于 Kubernetes ConfigMap 和 Secret 使用,详细见:https://mp.weixin.qq.com/s/OZXcraX04bMXQLv5Lt_XRA
上述资源信息是我们在使用 Deployment 过程中常用的配置对象,配置完成上述内容 Deployment 可以完成对 Pod 管理和运行,但是还不够完美,正如开篇中所说的,鲁棒性不强,在正式使用场景下,会出现各种各样的问题,但是不怕,Kubernetes 是工业级编排平台,一般我们遇到问题,都有对应解决方案。下面就简单介绍下一个完善的 Deployment yaml 文件还需要包含什么?
★存活和就绪探针
”
存活探针(livenessprobe)和就绪探针(readinessprobe),语法相似,但功能不同,存活探针主要是用于检测服务是否正常启动,如果不正常,则重建 pod,直到正常为止,使用过程中要注意初始化延迟时间,如果设置时间太短,可能会导致 Pod 创建进入死循环,影响服务正常启动。
就绪探针主要是用于服务是否能够正常对外提供服务,如果不正常则从端点服务列表中移除,直到正常为止。
探针这个功能是 Kubernetes 中很接地气的一个设计,分布式系统很棘手的一个问题就是服务数量众多,存在一定量的僵尸服务,常规的做法通过侵入式设计,在服务中添加接口,循环检测,发现问题消息通知,在这种机制下消息往往不能得到及时解决。探针属于监控领域的一部分,要想检测服务是否正常,编排文件必须包含探针。
★生命周期钩子
”
preStop 和 postStart 是容器生命周期的钩子,它跟存活和就绪探针类似,是在容器内部执行一个命令或者请求,但是这个钩子是和容器主进程并行执行的,postStart 在容器创建成功后立即执行,主要用于资源的部署和环境的准备,比如把某个文件复制到特定目录。
preStop 容器终止前的任务,主要用于优雅的关闭应用程序或者通知第三方服务等操作, 停止前钩子非常重要,编排文件中应该包含。看完了两个生命周期钩子函数,我们也说了停止前钩子非常重要,为什么呢?下面先简单介绍下一个 pod 被删除后发生了什么?
-
apiServer 发出 http delete 请求后,apiserver 不会直接删除 Pod 而是给 Pod 设置一个删除时间,拥有删除时间的 Pod 就开始停止了。
-
kublet 检测到有需要停止的 Pod ,kublet 会给每个容器一定时间来优雅的停止 Pod,这个时间叫做终止宽限期,这个时间每个 Pod 可以单独配置。终止进程开始之后,计时器开始倒计时,然后执行以下操作:
-
执行停止前钩子(如果配置了的话),然后等待执行完毕
-
向容器主进程发送sigterm信号
-
等待容器优雅的关闭或者等待终止宽限期超时
-
如果容器主进程没有优雅地关闭,那么使用sigkill强制终止进程。即使此时停止前钩子没有执行完成。
如果仔细思考这个过程中,你会发现会有几个问题?
-
停止前钩子没有执行完成怎么办,比如现在运行的有状态服务是数据库,数据库所在 Pod 缩容之后,需要进行数据转移。现在使用了停止前钩子进行数据转移。这个时候更建议使用 DaemonSet 定时任务专门处理此类问题,不要过度依赖停止前钩子函数,因为它无法预料到 Pod 生命周期何时结束。
-
Pod 关闭时客户端连接断开怎么办,因为移除 iptable 规则的时间很可能比删除 Pod 时间要慢,这就导致之后外部请求到内部 Pod 发生 Connection refused,这种场景很难被解决,但是可以从一定程度上去避免,比如在停止前钩子,延迟 5-10s 关闭时间,尽可能多处理一定量请求,具体时间根据场景进行控制。
★资源限制
”
Kubernetes 通过 cgroup 进行资源限制,主要是通过设置 CPU 和内存请求资源和限制物理资源使用,由高到底分为 Guranteed、Burstable、Besteffort 三种资源限制级别。在生产环境中必须设置服务所需资源,可以考虑结合 limitrange 对命名空间所需资源限制,内部分别设置各个 Pod 所需资源,防止出现服务被驱逐;具体根据服务所需资源进行设置,如果 req 设置太大会导致资源浪费,太小会导致 OOMkilld,requests 建议使用历史峰值 * 1.2(系数)。如果线上环境存在突发流量,可以考虑结合 Kubernetes HPA 根据服务资源占用情况进行动态扩缩容。
★镜像合理打标签
”
经常看到有开发人员为镜像设置 latest 标签,简单,不用推送过多镜像,但是这样存在一定的隐患, 常见的就是当你 ImagesPullPolicy 设置为 IfNotPreset,推送镜像到远端,执行 yaml 文件不生效,那是因为 Kubernetes 发现版本没有变化没有去远端拉取,你不得不把拉取策略修改为 Always,这样一样,每当多产生一个 Pod 都会去联系镜像中心,拖慢了服务运行速度,严重情况下,网络出现问题就会导致整个服务无法正常启动。
另一个严重问题是一直使用同一个镜像标签,当服务出现问题时,导致无法回退到之前的版本。所以每当镜像发生变化时,要使用和之前不一样的标签。
★灵活使用 env
”
对于一些日志收集或者有状态服务中,可能存在需要获取 pod 名称或者其它信息的需求,可以通过使用 env 对象获取资源对象,不仅如此,当我们需要调试服务的时候通过动态环境注入的方式,很方便的帮助我们进行服务调试,而不用执行重新打镜像操作,比如 java 服务出现内存溢出,可以通过注入如下信息:
★理性对待Pod崩溃
”
在本地、虚拟机或者物理机部署时服务正常运行,换做容器运行各种崩溃,其实出现崩溃并不可怕,关键是分析为什么崩溃。Pod 在运行过程中会经历如下几个状态 Pending、Running、Succeeded、Faild、UnKnown 等。
-
首先出现问题后注意使用
kubectl describe deployment
查看 deployment 状态, 如果 deployment 一切正常、开始查看kubectl describe replicaset
,如果这两个控制器不正常,查看原因,一般 events 中会有提示。 -
如果一切正常,kubectl describe pod 查看 pod 运行状况,如果看不出问题所在,那么执行 kubectl logs pod 当然你的日志可能没有输出到控制台,你可以到你挂载日志所在宿主机或者日志收集中心查看日志。(前提是你的所有日志文件都已经 mount 到宿主机)
-
如果通过日志仍然无法看出问题或者根据异常信息不能分析出问题所在,其实可以通过
kubectl cp values.yaml pod-9fbfdbf89-rcwhc:/home/
(把本地文件拷贝到 pod)也可以通过kubectl cp pod-deployment-9fbfdbf89-rcwhc:home/values.yaml values.yaml
(把镜像内部复制到宿主机)把一些你认为能够排除问题的工具复制到 Pod 内部进行协助问题排除。 -
最后如果仍然无法找到问题,那么你可以考虑运行一个没有问题的进程,最简单的就是创建一个普通的 centos 容器进行实现,通过如下命令,保证容器启动时拥有一个前台进程
command: ["/bin/bash","-c","while true; do sleep 1000; done"]
然后把有问题的服务 mount 进去,手动执行。
总结
本文主要结合本人使用经验介绍了 Kubernetes Deployment 在使用过程中注意事项,以及出现问题后如何分析处理。洋洋洒洒扯了这么多,还有很多没有覆盖的地方,以后接着扯。如有问题请留言或者关注后拉你进群讨论。希望能够帮助到大家,谢谢阅读!
推荐阅读
如何使用 Ingress-nginx 进行前后端分离?
从Ice到Kubernetes容器技术,微服务架构经历了什么?
原创不易,随手关注或者”在看“,诚挚感谢!
更多推荐
所有评论(0)