k8s实践指南之服务高可用
1就必然存在单点故障,如果大于1但所有副本都调度到同一个节点,那还是有单点故障,所以我们不仅要有合理的副本数量,还需要让这些不同副本调度到不同的节点,打散开来避免单点故障,这个可以利用反亲和性来实现,示例调度时必须满足该反亲和性条件,如果没有节点满足条件就不调度到任何节点(Pending)。如果不用这种硬性条件可以使用来指示调度器尽量满足反亲和性条件,如果没有满足条件的也可以调度到某个节点。写该服
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上软件测试知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
1
就必然存在
单点故障,如果大于
1
但所有副本都调度到同一个节点,那还是有单点故障,所以我们不仅要有合理
的副本数量,还需要让这些不同副本调度到不同的节点,打散开来避免单点故障,这个可以利用反亲和
性来实现,示例
:
1. affinity:
2. podAntiAffinity:
3. requiredDuringSchedulingIgnoredDuringExecution:
4. - weight: 100
5. labelSelector:
6. matchExpressions:
7. - key: k8s-app
8. operator: In
9. values:
10. - kube-dns
11. topologyKey: kubernetes.io/hostname
requiredDuringSchedulingIgnoredDuringExecution
调度时必须满足该反亲和性条件,如果
没有节点满足条件就不调度到任何节点
(Pending)
。如果不用这种硬性条件可以使用
preferredDuringSchedulingIgnoredDuringExecution
来指示调度器尽量满足反亲和性条
件,如果没有满足条件的也可以调度到某个节点。
labelSelector.matchExpressions
写该服务对应
pod
中
labels
的
key
与
value
。
topologyKey
这里用
kubernetes.io/hostname
表示避免
pod
调度到同一节点,如果你
有更高的要求,比如避免调度到同一个可用区,实现异地多活,可以用
failure-
domain.beta.kubernetes.io/zone
。通常不会去避免调度到同一个地域,因为一般同一个集群
的节点都在一个地域,如果跨地域,即使用专线时延也会很大,所以
topologyKey
一般不至
于用
failure-domain.beta.kubernetes.io/region
。
使用PodDisruptionBudget 避免驱逐导致服务不可用
驱逐节点是一种有损操作,驱逐的原理:
封锁节点
(
设为不可调度,避免新的
Pod
调度上来
)
。
将该节点上的
Pod
删除。
- ReplicaSet
控制器检测到
Pod
减少,会重新创建一个
Pod
,调度到新的节点上。
这个过程是先删除,再创建,并非是滚动更新,因此更新过程中,如果一个服务的所有副本都在被驱逐
的节点上,则可能导致该服务不可用。
我们再来下什么情况下驱逐会导致服务不可用:
服务存在单点故障,所有副本都在同一个节点,驱逐该节点时,就可能造成服务不可用。
服务在多个节点,但这些节点都被同时驱逐,所以这个服务的所有服务同时被删,也可能造成服
务不可用。
针对第一点,我们可以
使用反亲和性避免单点故障
。
针对第二点,我们可以通过配置
PDB (PodDisruptionBudget)
来避免所有副本同时被删除,下面
给出示例。
示例一
(
保证驱逐时
zookeeper
至少有两个副本可用
):
1. apiVersion: policy/v1beta1
2. kind: PodDisruptionBudget
3. metadata:
4. name: zk-pdb
5. spec:
6. minAvailable: 2
7. selector:
8. matchLabels:
9. app: zookeeper
示例二
(
保证驱逐时
zookeeper
最多有一个副本不可用,相当于逐个删除并在其它节点重建
):
1. apiVersion: policy/v1beta1
2. kind: PodDisruptionBudget
3. metadata:
4. name: zk-pdb
5. spec:
6. maxUnavailable: 1
7. selector:
8. matchLabels:
9. app: zookeeper
更多请参考官方文档
:
https://kubernetes.io/docs/tasks/run-application/configure-pdb/
使用 preStopHook 和 readinessProbe 保证服务平滑更新不中断
如果服务不做配置优化,默认情况下更新服务期间可能会导致部分流量异常,下面我们来分析并给出最佳实践。
服务更新场景:
我们先看下服务更新有哪些场景
:
- 手动调整服务的副本数量
- 手动删除 Pod 触发重新调度
- 驱逐节点 (主动或被动驱逐,Pod会先删除再在其它节点重建)
- 触发滚动更新 (比如修改镜像 tag 升级程序版本)
- HPA (HorizontalPodAutoscaler) 自动对服务进行水平伸缩
- VPA (VerticalPodAutoscaler) 自动对服务进行垂直伸缩
更新过程连接异常的原因:
滚动更新时,
Service
对应的
Pod
会被创建或销毁,
Service
对应的
Endpoint
也会新增或移
除相应的
Pod IP:Port
,然后
kube-proxy
会根据
Service
的
Endpoint
里的
Pod
IP:Port
列表更新节点上的转发规则,而这里
kube-proxy
更新节点转发规则的动作并不是那么及
时,主要是由于
K8S
的设计理念,各个组件的逻辑是解耦的,各自使用
Controller
模式
listAndWatch
感兴趣的资源并做出相应的行为,所以从
Pod
创建或销毁到
Endpoint
更新再到
节点上的转发规则更新,这个过程是异步的,所以会造成转发规则更新不及时,从而导致服务更新期间部分连接异常。
我们分别分析下 Pod 创建和销毁到规则更新期间的过程:
- Pod
被创建,但启动速度没那么快,还没等到
Pod
完全启动就被
Endpoint Controller
加入到
Service
对应
Endpoint
的
Pod IP:Port
列表,然后
kube-proxy watch
到
更新也同步更新了节点上的
Service
转发规则
(iptables/ipvs)
,如果这个时候有请求过
来就可能被转发到还没完全启动完全的
Pod
,这时
Pod
还不能正常处理请求,就会导致连接被
拒绝。
- Pod
被销毁,但是从
Endpoint Controller watch
到变化并更新
Service
对应
Endpoint 再到 kube-proxy 更新节点转发规则这期间是异步的,有个时间差,Pod 可能已经完全被销毁了,但是转发规则还没来得及更新,就会造成新来的请求依旧还能被转发到已经被销毁的Pod,导致连接被拒绝。
平滑更新最佳实践
- 针对第一种情况,可以给 Pod 里的 container 加 readinessProbe (就绪检查),通常是容器完全启动后监听一个 HTTP 端口,kubelet 发就绪检查探测包,正常响应说明容器已经就绪,然后修改容器状态为 Ready,当 Pod 中所有容器都 Ready 了这个 Pod 才会被Endpoint Controller 加进 Service 对应 Endpoint IP:Port 列表,然后 kube-proxy 再更新节点转发规则,更新完了即便立即有请求被转发到的新的 Pod 也能保证能够正常处理连接,避免了连接异常。
- 针对第二种情况,可以给 Pod 里的 container 加 preStop hook,让 Pod 真正销毁前先sleep 等待一段时间,留点时间给 Endpoint controller 和 kube-proxy 更新Endpoint 和转发规则,这段时间 Pod 处于 Terminating 状态,即便在转发规则更新完全之前有请求被转发到这个 Terminating 的 Pod,依然可以被正常处理,因为它还在 sleep,没有被真正销毁。
最佳实践 yaml 示例:
1. apiVersion: extensions/v1beta1
2. kind: Deployment
3. metadata:
4. name: nginx
5. spec:
6. replicas: 1
7. selector:
8. matchLabels:
9. component: nginx
10. template:
11. metadata:
12. labels:
13. component: nginx
14. spec:
15. containers:
16. - name: nginx
17. image: "nginx"
18. ports:
19. - name: http
20. hostPort: 80
21. containerPort: 80
22. protocol: TCP
23. readinessProbe:
24. httpGet:
25. path: /healthz
26. port: 80
27. httpHeaders:
28. - name: X-Custom-Header
29. value: Awesome
30. initialDelaySeconds: 15
31. timeoutSeconds: 1
32. lifecycle:
33. preStop:
34. exec:
35. command: ["/bin/bash", "-c", "sleep 30"]
参考资料
-
Container probes: https://kubernetes.io/docs/concepts/workloads/pods/pod- lifecycle/#container-probes
-
Container Lifecycle Hooks:https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/
解决长连接服务扩容失效
在现网运营中,有很多场景为了提高效率,一般都采用建立长连接的方式来请求。我们发现在客户端以
长连接请求服务端的场景下,
K8S
的自动扩容会失效。原因是客户端长连接一直保留在老的
Pod
容器
中,新扩容的
Pod
没有新的连接过来,导致
K8S
按照步长扩容第一批
Pod
之后就停止了扩容操作,而且新
扩容的
Pod
没能承载请求,进而出现服务过载的情况,自动扩容失去了意义。
对长连接扩容失效的问题,我们的解决方法是将长连接转换为短连接。我们参考了
nginx
keepalive
的设计,
nginx
中
keepalive_requests
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上软件测试知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
对长连接扩容失效的问题,我们的解决方法是将长连接转换为短连接。我们参考了
nginx
keepalive
的设计,
nginx
中
keepalive_requests
[外链图片转存中…(img-n9vTwM7b-1715804398783)]
[外链图片转存中…(img-nwfKXMUQ-1715804398784)]
[外链图片转存中…(img-1hW6Jvij-1715804398784)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上软件测试知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
更多推荐
所有评论(0)