Kubernetes 零宕机滚动更新(kubernetes deployment滚动升级)
nanshan 2024-10-26 11:14 8 浏览 0 评论
软件世界的发展比以往任何时候都快,为了保持竞争力需要尽快推出新的软件版本,而又不影响在线得用户。许多企业已将工作负载迁移到了 Kubernetes 集群,Kubernetes 集群本身就考虑到了一些生产环境的实践,但是要让 Kubernetes 实现真正的零停机不中断或丢失请求,我们还需要做一些额外的操作才行。
滚动更新
默认情况下,Kubernetes 的 Deployment 是具有滚动更新的策略来进行 Pod 更新的,该策略可以在任何时间点更新应用的时候保证某些实例依然可以正常运行来防止应用 down 掉,当新部署的 Pod 启动并可以处理流量之后,才会去杀掉旧的 Pod。
在使用过程中我们还可以指定 Kubernetes 在更新期间如何处理多个副本的切换方式,比如我们有一个3副本的应用,在更新的过程中是否应该立即创建这3个新的 Pod 并等待他们全部启动,或者杀掉一个之外的所有旧的 Pod,或者还是要一个一个的 Pod 进行替换?下面示例是使用默认的滚动更新升级策略的一个 Deployment 定义,在更新过程中最多可以有一个超过副本数的容器(maxSurge),并且在更新过程中没有不可用的容器。
apiVersion: apps/v1
kind: Deployment
metadata:
name: zero-downtime
labels:
app: zero-downtime
spec:
replicas: 3
selector:
matchLabels:
app: zero-downtime
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
# with image nginx
# ...
上面的 zero-downtime 这个应用使用 nginx 这个镜像创建3个副本,该 Deployment 执行滚动更新的方式:首先创建一个新版本的 Pod,等待 Pod 启动并准备就绪,然后删除一个旧的 Pod,然后继续下一个新的 Pod,直到所有副本都已替换完成。为了让 Kubernetes 知道我们的 Pod 何时可以准备处理流量请求了,我们还需要配置上 liveness 和 readiness 探针。下面展示的就是新旧 Pod 替换的输出信息:
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
zero-downtime-d449b5cc4-k8b27 1/1 Running 0 3m9s
zero-downtime-d449b5cc4-n2lc4 1/1 Running 0 3m9s
zero-downtime-d449b5cc4-sdw8b 1/1 Running 0 3m9s
...
zero-downtime-d449b5cc4-k8b27 1/1 Running 0 3m9s
zero-downtime-d449b5cc4-n2lc4 1/1 Running 0 3m9s
zero-downtime-d449b5cc4-sdw8b 1/1 Running 0 3m9s
zero-downtime-d569474d4-q9khv 0/1 ContainerCreating 0 12s
...
zero-downtime-d449b5cc4-n2lc4 1/1 Running 0 3m9s
zero-downtime-d449b5cc4-sdw8b 1/1 Running 0 3m9s
zero-downtime-d449b5cc4-k8b27 1/1 Terminating 0 3m29s
zero-downtime-d569474d4-q9khv 1/1 Running 0 1m
...
zero-downtime-d449b5cc4-n2lc4 1/1 Running 0 5m
zero-downtime-d449b5cc4-sdw8b 1/1 Running 0 5m
zero-downtime-d569474d4-q9khv 1/1 Running 0 1m
zero-downtime-d569474d4-2c7qz 0/1 ContainerCreating 0 10s
...
...
zero-downtime-d569474d4-2c7qz 1/1 Running 0 40s
zero-downtime-d569474d4-mxbs4 1/1 Running 0 13s
zero-downtime-d569474d4-q9khv 1/1 Running 0 67s
可用性检测
如果我们从旧版本到新版本进行滚动更新,只是简单的通过输出显示来判断哪些 Pod 是存活并准备就绪的,那么这个滚动更新的行为看上去肯定就是有效的,但是往往实际情况就是从旧版本到新版本的切换的过程并不总是十分顺畅的,应用程序很有可能会丢弃掉某些客户端的请求。
为了测试是否存在请求被丢弃,特别是那些针对即将要退出服务的实例的请求,我们可以使用一些负载测试工具来连接我们的应用程序进行测试。我们需要关注的重点是所有的 HTTP 请求,包括 keep-alive 的 HTTP 连接是否都被正确处理了,所以我们这里可以使用 Apache Bench(AB Test) 或者 Fortio(Istio 测试工具) 这样的测试工具来测试。
我们使用多个线程以并发的方式去连接到正在运行的应用程序,我们关心的是响应的状态和失败的连接,而不是延迟或吞吐量之类的信息。我们这里使用 Fortio 这个测试工具,比如每秒 500 个请求和 8 个并发的 keep-alive 连接的测试命令如下所示(使用域名zero.qikqiak.com代理到上面的3个 Pod):
$ fortio load -a -c 8 -qps 500 -t 60s "http://zero.qikqiak.com/"
关于 fortio 的具体使用可以查看官方文档:https://github.com/fortio/fortio
使用 -a 参数可以将测试报告保存为网页的形式,这样我们可以直接在浏览器中查看测试报告。如果我们在进行滚动更新应用的过程中启动测试,则可能会看到一些请求无法连接的情况:
Starting at 1000 qps with 8 thread(s) [gomax 2] for 1m0s : 7500 calls each (total 60000)
Ended after 1m0.006243654s : 5485 calls. qps=91.407
Aggregated Sleep Time : count 5485 avg -17.626081 +/- 15 min -54.753398956 max 0.000709054 sum -96679.0518
[...]
Code 200 : 5463 (99.6 %)
Code 502 : 20 (0.4 %)
Response Header Sizes : count 5485 avg 213.14166 +/- 13.53 min 0 max 214 sum 1169082
Response Body/Total Sizes : count 5485 avg 823.18651 +/- 44.41 min 0 max 826 sum 4515178
[...]
从上面的输出可以看出有部分请求处理失败了(502),我们可以运行几种通过不同方式连接到应用程序的测试场景,比如通过 Kubernetes Ingress 或者直接从集群内部通过 Service 进行连接。我们会看到在滚动更新过程中的行为可能会有所不同,具体的还是需要取决于测试的配置参数,和通过 Ingress 的连接相比,从集群内部连接到服务的客户端可能不会遇到那么多的失败连接。
原因分析
现在的问题是需要弄明白当应用在滚动更新期间重新路由流量时,从旧的 Pod 实例到新的实例究竟会发生什么,首先让我们先看看 Kubernetes 是如何管理工作负载连接的。如果我们执行测试的客户端直接从集群内部连接到 zero-downtime 这个 Service,那么首先会通过 集群的 DNS 服务解析到 Service 的 ClusterIP,然后转发到 Service 后面的 Pod 实例,这是每个节点上面的 kube-proxy 通过更新 iptables 规则来实现的。
Kubernetes 会根据 Pods 的状态去更新 Endpoints 对象,这样就可以保证 Endpoints 中包含的都是准备好处理请求的 Pod。
但是 Kubernetes Ingress 连接到实例的方式稍有不同,这就是为什么当客户端通过 Ingresss 连接到应用程序的时候,我们会在滚动更新过程中查看到不同的宕机行为。
大部分 Ingress Controller,比如 nginx-ingress、traefik 都是通过直接 watch Endpoints 对象来直接获取 Pod 的地址的,而不用通过 iptables 做一层转发了。
无论我们如何连接到应用程序,Kubernetes 的目标都是在滚动更新的过程中最大程度地减少服务的中断。一旦新的 Pod 处于活动状态并准备就绪后,Kubernetes 就将会停止就的 Pod,从而将 Pod 的状态更新为 “Terminating”,然后从 Endpoints 对象中移除,并且发送一个 SIGTERM 信号给 Pod 的主进程。SIGTERM 信号就会让容器以正常的方式关闭,并且不接受任何新的连接。Pod 从 Endpoints 对象中被移除后,前面的负载均衡器就会将流量路由到其他(新的)Pod 中去。这个也是造成我们的应用可用性差距的主要原因,因为在负责均衡器注意到变更并更新其配置之前,终止信号就会去停用 Pod,而这个重新配置过程又是异步发生的,所以并不能保证正确的顺序,所以就可能导致很少的请求会被路由到终止的 Pod 上去。
零宕机
那么如何增强我们的应用程序以实现真正的零宕机迁移呢?
首先,要实现这个目标的先决条件是我们的容器要正确处理终止信号,在 SIGTERM 信号上实现优雅关闭。下一步需要添加 readiness 可读探针,来检查我们的应用程序是否已经准备好来处理流量了。
可读探针只是我们平滑滚动更新的起点,为了解决 Pod 停止的时候不会阻塞并等到负载均衡器重新配置的问题,我们需要使用 preStop 这个生命周期的钩子,在容器终止之前调用该钩子。
生命周期钩子函数是同步的,所以必须在将最终终止信号发送到容器之前完成,在我们的示例中,我们使用该钩子简单的等待,然后 SIGTERM 信号将停止应用程序进程。同时,Kubernetes 将从 Endpoints 对象中删除该 Pod,所以该 Pod 将会从我们的负载均衡器中排除,基本上来说我们的生命周期钩子函数等待的时间可以确保在应用程序停止之前重新配置负载均衡器。
这里我们在 zero-downtime 这个 Deployment 中添加一个 preStop 钩子:
apiVersion: apps/v1
kind: Deployment
metadata:
name: zero-downtime
labels:
app: zero-downtime
spec:
replicas: 3
selector:
matchLabels:
app: zero-downtime
template:
spec:
containers:
- name: zero-downtime
image: nginx
livenessProbe:
# ...
readinessProbe:
# ...
lifecycle:
preStop:
exec:
command: ["/bin/bash", "-c", "sleep 20"]
strategy:
# ...
我们这里使用 preStop 设置了一个 20s 的宽限期,Pod 在真正销毁前会先 sleep 等待 20s,这就相当于留了时间给 Endpoints 控制器和 kube-proxy 更新去 Endpoints 对象和转发规则,这段时间 Pod 虽然处于 Terminating 状态,即便在转发规则更新完全之前有请求被转发到这个 Terminating 的 Pod,依然可以被正常处理,因为它还在 sleep,没有被真正销毁。
现在,当我们去查看滚动更新期间的 Pod 行为时,我们将看到正在终止的 Pod 处于 Terminating 状态,但是在等待时间结束之前不会关闭的,如果我们使用 Fortio 重新测试下,则会看到零失败请求的理想行为:
Starting at 1000 qps with 8 thread(s) [gomax 2] for 1m0s : 7500 calls each (total 60000)
Ended after 1m0.091439891s : 10015 calls. qps=166.66
Aggregated Sleep Time : count 10015 avg -23.316213 +/- 14.52 min -50.161414028 max 0.001811225 sum -233511.876
[...]
Code 200 : 10015 (100.0 %)
Response Header Sizes : count 10015 avg 214 +/- 0 min 214 max 214 sum 2143210
Response Body/Total Sizes : count 10015 avg 826 +/- 0 min 826 max 826 sum 8272390
Saved result to data/2020-02-12-162008_Fortio.json
All done 10015 calls 47.405 ms avg, 166.7 qps
总结
Kubernetes 在考虑到生产就绪性方面已经做得很好了,但是为了在生产环境中运行我们的企业级应用,我们就必须了解 Kubernetes 是如何在后台运行的,以及我们的应用程序在启动和关闭期间的行为。而且上面的方式是只适用于短连接的,对于类似于 websocket 这种长连接应用需要做滚动更新的话目前还没有找到一个很好的解决方案,有的团队是将长连接转换成短连接来进行处理的,我这边还是在应用层面来做的支持,比如客户端增加重试机制,连接断掉以后会自动重新连接,大家如果有更好的办法也可以留言互相讨论下方案。
相关推荐
- python获取阿里云云解析dns的域名解析记录
-
最近由于工作原因接触到阿里云的服务,我需要实时获取所有的域名信息,用于对其进行扫描,因此写了一个自动化爬取脚本给需要的人分享。(阿里云有官方的demo,有兴趣的可以自己看一下,后面也会放链接,我只能...
- 前端性能优化系列——DNS预解析和优化
-
简单来说,DNS的作用是将域名解析为IP地址,解析的过程是耗时的,转化后会做本地缓存,我们的优化的目标主要是针对用户第一次访问站点的时候陷入长时间白屏的问题。DNS解析可以分为两类,第一类是页...
- dns错误修复方法
-
最近用户反馈在同一网络的其他电脑可以正常上网,但自己的电脑却提示页面找不到且无法解析服务器的dns地址,接下来给大家带来dns错误修复方法。 1、点击网络图标,选择打开网络共享中心,如图所示: ...
- 技术分享 | 浅谈DNS递归解析和迭代解析之间的区别
-
DNS解析是互联网中的重要环节,承担着将域名翻译为可由计算机直接读取的IP地址的基础功能。根据查询对象不同DNS解析可分为递归解析和迭代解析两种方式,接下来,中科三方将简单介绍下两种查询方式的流程以及...
- 一文读懂DNS解析故障常见情况(中科三方)
-
DNS解析将人们习惯使用的域名翻译成计算机识别的IP地址,是确保人们正常访问网站的重要功能。而在实际域名管理过程中,经常会因为种种原因导致DNS解析故障。DNS解析故障主要表现在人们通过IP地址可以直...
- DNS分离解析实验
-
如果本文对你有帮助,欢迎关注、点赞、收藏、转发给朋友,让我有持续创作的动力目录一、分离解析概述二、实验需求三、实验步骤3.1双网卡服务器配置3.1.1添加两张网卡(内外网)3.1.2对两个网卡进...
- #净网2019# 浏览网页被“劫持”,有问题!
-
明明自己没有设置过,打开网页浏览器却直接到了一个陌生网站,想改回原来的主页设置颇费周折、甚至无能为力。很多网民有过类似经历:在安装了一些软件后,自己的浏览器主页就被修改和锁定。本来打算访问A网站却被强...
- 解决浏览器劫持,360和腾讯安全不敌火绒专杀
-
上午win7莫名其妙显示未激活,并要求当日必须激活,没办法,上网找激活工具,先试了“小马”没起作用,又下载了“WIN7ActivationV2.3绿色版”,就是这个:激活是激活了,顺便给我安了一堆垃...
- 【净网2019】 浏览网页被“劫持”,有问题!
-
明明自己没有设置过,打开网页浏览器却直接到了一个陌生网站,想改回原来的主页设置颇费周折、甚至无能为力。很多网民有过类似经历:在安装了一些软件后,自己的浏览器主页就被修改和锁定。本来打算访问A网站却被...
- 浏览器打开网页被绑架?这里有办法
-
最近小A遇到了一个小问题:在240g上网冲浪的时候,搜索到的网页,点进去却“李逵变李鬼”???在使用浏览器时,点击搜寻结果却导向与搜寻内容不符合的网站,表示您的浏览器可能已遭受恶意软件劫持。例如您在搜...
- 处理浏览器主页被劫持的最新方法
-
给大家说个处理浏览器主页被劫持的最新方法:刚遇件让人哭笑不得的事,这两天发现浏览器的主页打开后不是自己原来设置的,而是hao123,一百度,好家伙,原来这个流氓许多人都遇到过,照着大家分享的方法一一尝...
- 软件性能测试详解
-
性能测试的基本概念性能测试是一种非功能性测试,通过自动化工具模拟多种负载条件(正常、峰值、异常),对系统的各项性能指标进行测试和评估,以验证其是否满足预期的性能需求。以下是核心概念的详细解析:一、性能...
- 国内粉色图标视频网站偷用带宽风波,WebRTC Control 插件来救场
-
最近,B站陷入了一场舆论风波,被指偷偷使用用户上传带宽。有网友在浙江大学论坛投稿称,B崭新版App疑似未经许可大量上传数据致网络卡顿,查看路由器统计信息后,发现B站手机客户端开启大量端口,...
- 服务器知识
-
问:机架式和非机架式是什么意思?服务器中“U”是什么单位?答:机架式、非机架式指的是服务器的类型。机架式是指可以直接插入机柜的标准服务器。非机架式是其他类型的服务器。譬如说我们普通的pc机的主机。大小...
- Vue3 性能拉胯?5 个实战技巧让项目响应速度飙升 60%!
-
作为前端工程师,在开发Vue3项目时,你是否经常遇到页面卡顿、数据更新缓慢,导致用户体验直线下降的情况?看着自己精心搭建的应用,因为性能问题被吐槽,真是既无奈又焦虑。别担心,今天就分享5个超级...
你 发表评论:
欢迎- 一周热门
-
-
爱折腾的特斯拉车主必看!手把手教你TESLAMATE的备份和恢复
-
如何在安装前及安装后修改黑群晖的Mac地址和Sn系列号
-
[常用工具] OpenCV_contrib库在windows下编译使用指南
-
WindowsServer2022|配置NTP服务器的命令
-
Ubuntu系统Daphne + Nginx + supervisor部署Django项目
-
WIN11 安装配置 linux 子系统 Ubuntu 图形界面 桌面系统
-
解决Linux终端中“-bash: nano: command not found”问题
-
NBA 2K25虚拟内存不足/爆内存/内存占用100% 一文速解
-
Linux 中的文件描述符是什么?(linux 打开文件表 文件描述符)
-
K3s禁用Service Load Balancer,解决获取浏览器IP不正确问题
-
- 最近发表
- 标签列表
-
- linux 查询端口号 (58)
- docker映射容器目录到宿主机 (66)
- 杀端口 (60)
- yum更换阿里源 (62)
- internet explorer 增强的安全配置已启用 (65)
- linux自动挂载 (56)
- 禁用selinux (55)
- sysv-rc-conf (69)
- ubuntu防火墙状态查看 (64)
- windows server 2022激活密钥 (56)
- 无法与服务器建立安全连接是什么意思 (74)
- 443/80端口被占用怎么解决 (56)
- ping无法访问目标主机怎么解决 (58)
- fdatasync (59)
- 405 not allowed (56)
- 免备案虚拟主机zxhost (55)
- linux根据pid查看进程 (60)
- dhcp工具 (62)
- mysql 1045 (57)
- 宝塔远程工具 (56)
- ssh服务器拒绝了密码 请再试一次 (56)
- ubuntu卸载docker (56)
- linux查看nginx状态 (63)
- tomcat 乱码 (76)
- 2008r2激活序列号 (65)