Kubernetes 环境下保证服务高可用的部署实践

阅读本文需要读者了解 Kubernetes (k8s) 的基础概念,如 pod、 deployment、 service。本文讨论的更多的是如何利用 k8s 本身提供的服务,低成本地达到我们的应用服务的可用性需求。
由于篇幅过长的话影响阅读体验,本文对各种配置都采用官网链接的方式,不在文中贴代码了

近几年,在我们的新项目上,Kubernetes 作为部署环境使用颇为广泛。在微服务比较流行的背景下,创建新的服务也是经常发生。服务的高可用的必要性自然不用多说,虽然实现高可用的程度和所需付出的努力也是正相关的,但并不线性。经过不断的摸索,我总结了我接触的近几个服务中,逐渐稳定下来的性价比高的常用高可用配置。

1. 保证自身运行与更新的平稳

1.1 livenessProbe

很多应用在长时间运行以后,都可能进入一些不健康的状态,比如处理速度变慢,内存压力变大,甚至死锁,业务服务中止等。这时候虽然容器还在继续运行,但已经没有了处理业务的能力,且通常重启 pod 便可以解决问题。为了自动辨识并修复这种情况,k8s 提供了一种探针 livenessProbe,顾名思义,它可以让 k8s 知道这个容器是否还“活着”,如果容器已经停止服务,则 kubelet 会重启容器。 livenessProbe 支持多种配置,如探针形式、频率,和失败次数等。这样我们就在一定程度上缓解了业务服务“名存实亡”的问题。

1.2 readinessProbe

然而,有些情况业务服务不工作时,重启并不能解决问题。比如我们起一个 spring 服务的 pod,它启动起来就是需要那么几十秒,通常为了等待服务的启动,我们会将 livenessProbe 延后,以免错杀正在启动的容器。但是在业务进程启动的过程中,k8s 感知到的是容器正在运行,因此当你配置了 service 的时候,service 会把流量转发到还没有就绪的容器中,造成请求失败。

为了应对这种情况,k8s 提供了另一种探针 readinessProbe,k8s service 会根据 readinessProbe 返回成功与否的情况,决定是否将流量转发到相应的容器中。这样一来,在服务有多副本的情况下,只要我们还有正常运行的服务,对于外部使用者来说,他的请求总能得到应有的回应。同 livenessProbe 一样,readinessProbe 也支持探针形式、频率,和失败次数等多种配置。

1.3 部署版本检查

持续集成/持续部署作为一种最佳实践如今已被广为采用,它的落地依赖于一个好的流水线设计,我们之所以能做到持续部署,是因为我们的流水线有足够多的检查和测试,且能够在异常情况下挂掉,从而中止问题版本部署到生产环境。由于 k8s 对组件的管理是声明式的,想要更新部署版本只需要在 k8s 中更新一个 deployment 配置,我们更新配置的指令也会在 k8s 接受配置后直接返回,并不会等到新的服务启动。那么问题就出现了,如果新版本的 pod 启动不起来,我们如何知道?如果新启动的服务迟迟没有 Ready,我们又如何知道?

我们的方法比较淳朴,在服务中暴露一个状态接口,返回一些用于检测的环境变量,比如流水线 build number,commit 号等,然后在流水线中循环使用 curl 请求,直到服务能够返回了期望的版本,才会通过,否则会报错,让流水线挂掉。如果通过的话说明至少一个新启动的 pod 已经 Ready,其他正在 rolling update 的副本也基本不会有什么问题,我们就认为部署成功,可以继续进行下一步,比如自动化测试或者部署到下一个环境。

目前我还能想到的一种方法是利用 k8s 的新特性 kubectl wait。这个命令会在等待条件未满足之前一直 hang 住,直到 timeout,用在流水线里看起来更加优雅。我们可以在部署的时候给 pod 打上标签,比如 app: my-app, commit: abcdefg,然后通过 kubectl wait --for=condition=Ready pod -l app=myapp -l commit=abcdefg --timeout=120s,来保证我们想要的版本已经可以提供外界服务。

项目初期我们通过以上三种途径,基本保证了服务在持续集成中的零宕机部署,然而上面提到方式比较简单,适用于主干开发、持续集成/持续部署,靠的是各微服务滚动更新,成本比蓝绿部署低一些。如果对可用性要求更高,可以考虑蓝绿部署等更复杂的方式。

2. 抵御内外干扰

2.1 HorizontalPodAutoscaler

如今大部分系统面临的流量都或多或少的有一些潮汐特性,比如吃饭时间点外卖的人更多些,此时由于流量过大,相关服务同时处理的请求过多,很容易引发各种问题,最终导致服务不可用。然而依照这个流量部署外卖服务器,等过了饭点,又会有大量的服务器处于空闲状态。快递、打车、支付,很多行业都是这样,课件服务的自动伸缩成了很多企业标配的需求。幸运的是,在 k8s 生态下,它也不是什么难事。

HorizontalPodAutoscaler 是 k8s 为我们提供的开箱即用的功能,我们只需要指定该服务可接受的最大(maxReplicas)与最小副本数(minReplicas),目标 cpu 和内存使用率,则 k8s 会自动为我们进行调整,当服务负载过高时,自动增加副本数,反之则自动减少副本数,以维持各容器的实际负载与目标负载相匹配。这样一来,我们既可以抵御高峰流量,又可以在流量低谷时保持一定的经济性。

2.2 podAntiAffinity

有时影响我们服务稳定性的,不仅是外部因素,还可能是内部需求。比如当 k8s 集群为开发团队提供便利性的同时,还需要运维同事对其进行维护。作为 k8s node 的虚拟机也好,其背后的物理机也好,都需要一些定期重启的机制,来保证其稳定性。本来我以为这和我们开发团队没关系的,直到有一天,我们的“零宕机”的服务出现了几分钟的宕机。

简单沟通后我们得到的原因是,运维团队有一个定时任务,每隔一段时间便会清理运行时间大于 n 小时的节点。在清理 node (通过drain node)的时候,k8s 会做 delete pod 的动作,将该节点上所有的 pod 删掉,同时调度器也不会调度新的 pod 到该 node上,从而最终移除该 node。至于被删掉的 pod,由于 deployment 中定义了它应有的副本数,当 k8s 发现实际 pod 数量少于声明数量时,就会在其他 node 上重新创建 pod。当天比较巧的是,我们同一个服务的全部2个副本都在需要删除的 node 上运行,所以该服务的2个副本都被删除了,而就在老的 pod 被删除之后,新的 pod ready 之前,我们宕机了。

在搞明白宕机的原因以后,当时我们得到的建议是使用 podAntiAffinity。通过定义 podAntiAffinity,可以告知 k8s 调度器在创建 pod 时,避开相似的 pod,也就是说,在这种情况下,当一个 node 上已经运行着一个符合条件的 pod 时,新的 pod 可以选择不被调度到这个 node 上,根据使用 requiredDuringSchedulingIgnoredDuringExecution 还是 preferredDuringSchedulingIgnoredDuringExecution,决定了它是强制条件还是 preferred 条件。如果使用强制条件,那么当没有符合条件的 node 可用时,新的 pod 会一直等待。我们通过requiredDuringSchedulingIgnoredDuringExecution保证了同一服务的不同副本不会被调用到同一节点上,从而避免了删除一个 node 导致整个服务宕机的情况。

当然,是否配置上述强制条件必须结合实际情况,比如自动扩缩容的数量如果大于 node 数量,则强制条件会让多出来的 pod 始终处于等待状态。

2.3 PodDisruptionBudget

对于定期清理 node 造成的服务宕机,上述配置 podAntiAffinity 的方法显然是不够的,如果一次需要删除 3 个 node,而我们的服务恰巧在其中的两个,也还是要遭殃。好在 k8s 还提供了 PodDisruptionBudget 配置,这名字非常表义,他可以让我们声明一个最小可用数 minAvailable,或最大不可用数 maxUnavailable,当执行 drain node 的时候,待删除的 pod 先会等待,同时在其他 node 上创建新的 pod,保证删除后副本数仍然达标,然后才再删掉需要删除的 pod。这样就从根本上解决了问题。

2.4 Graceful shutdown

上述这些配置似乎已经能满足大多数情况了,这里再补充一点,就是优雅退出,推荐一篇博客 https://learnk8s.io/graceful-shutdown。简而言之,就是 k8s 在删除 pod 的时候,容器会先进入 Terminating 状态,接着 kubelet 会先给容器发一个 SIGTERM,然后等容器退出,当容器在一定时间内没能自己退出时,kubelet 会给容器发送一个 SIGKILL,强制容器退出。

也就是说,即便我们做到了上面的那些,保证了只有达到条件时,才会删除 pod,也不能保证老的 pod 中正在进行的任务顺利完成,因为它可能被中途 SIGKILL 掉,对于用户来说,还是中途宕机。好在这段从 Terminating 状态到 SIGKILL 信号发出的时间是可配置的,即 terminationGracePeriodSeconds。这里我们可以通过一些策略,设置一个合理的 terminationGracePeriodSeconds,对我们的服务进行保障。

我们使用的 spring boot 也支持 graceful shutdown,但默认不是开启的,需要在 application.yml 中指定 server.shutdown=graceful, 同时也可以设置 graceful shutdown 的时间 spring.lifecycle.timeout-per-shutdown-phase=20s

此外,在 pod 的生命周期中,我们还可以定义 preStop 指令,在发送 SIGTERM 之前做一些必要的等待、检查等工作,用以保证业务在 pod 退出之前被妥善处理。

更多阅读:Graceful Node Shutdown Goes Beta

结语

文中总结了几种简单的高可用部署实践,被 k8s 原生支持,可作为项目模板使用。我们使用至今,服务稳定性也还不错,也有同事提出了使用 helm 进行优化,后续会有继续探索,欢迎大家一起讨论,拍砖!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容