引言
上一篇文章咱们围绕如何正当利用资源的主题做了一些最佳实际的分享,这一次咱们就如何进步服务可用性的主题来开展探讨。
怎么进步咱们部署服务的可用性呢?K8S 设计自身就思考到了各种故障的可能性,并提供了一些自愈机制以进步零碎的容错性,但有些状况还是可能导致较长时间不可用,拉低服务可用性的指标。本文将联合生产实践经验,为大家提供一些最佳实际来最大化的进步服务可用性。
如何防止单点故障?
K8S 的设计就是假如节点是不牢靠的。节点越多,产生软硬件故障导致节点不可用的几率就越高,所以咱们通常须要给服务部署多个正本,依据理论状况调整 replicas 的值,如果值为 1 就必然存在单点故障,如果大于 1 但所有正本都调度到同一个节点了,那还是有单点故障,有时候还要思考到劫难,比方整个机房不可用。
所以咱们不仅要有正当的正本数量,还须要让这些不同正本调度到不同的拓扑域 (节点、可用区),打散调度以防止单点故障,这个能够利用 Pod 反亲和性来做到,反亲和次要分强反亲和与弱反亲和两种。更多亲和与反亲和信息可参考官网文档 Affinity and anti-affinity。
先来看个强反亲和的示例,将 DNS 服务强制打散调度到不同节点上:
affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: k8s-app operator: In values: - kube-dns topologyKey: kubernetes.io/hostname
labelSelector.matchExpressions
写该服务对应 pod 中 labels 的 key 与 value,因为 Pod 反亲和性是通过判断 replicas 的 pod label 来实现的。topologyKey
指定反亲和的拓扑域,即节点 label 的 key。这里用的kubernetes.io/hostname
示意防止 pod 调度到同一节点,如果你有更高的要求,比方防止调度到同一个可用区,实现异地多活,能够用failure-domain.beta.kubernetes.io/zone
。通常不会去防止调度到同一个地区,因为个别同一个集群的节点都在一个地区,如果跨地区,即应用专线时延也会很大,所以topologyKey
个别不至于用failure-domain.beta.kubernetes.io/region
。requiredDuringSchedulingIgnoredDuringExecution
调度时必须满足该反亲和性条件,如果没有节点满足条件就不调度到任何节点 (Pending)。
如果不必这种硬性条件能够应用 preferredDuringSchedulingIgnoredDuringExecution
来批示调度器尽量满足反亲和性条件,即弱反亲和性,如果切实没有满足条件的,只有节点有足够资源,还是能够让其调度到某个节点,至多不会 Pending。
咱们再来看个弱反亲和的示例:
affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchExpressions: - key: k8s-app operator: In values: - kube-dns topologyKey: kubernetes.io/hostname
留神到了吗?相比强反亲和有些不同哦,多了一个 weight
,示意此匹配条件的权重,而匹配条件被挪到了 podAffinityTerm
上面。
如何防止节点保护或降级时导致服务不可用?
有时候咱们须要对节点进行保护或进行版本升级等操作,操作之前须要对节点执行驱赶 (kubectl drain),驱赶时会将节点上的 Pod 进行删除,以便它们漂移到其它节点上,当驱赶结束之后,节点上的 Pod 都漂移到其它节点了,这时咱们就能够释怀的对节点进行操作了。
有一个问题就是,驱赶节点是一种有损操作,驱赶的原理:
- 封闭节点 (设为不可调度,防止新的 Pod 调度上来)。
- 将该节点上的 Pod 删除。
- ReplicaSet 控制器检测到 Pod 缩小,会从新创立一个 Pod,调度到新的节点上。
这个过程是先删除,再创立,并非是滚动更新,因而更新过程中,如果一个服务的所有正本都在被驱赶的节点上,则可能导致该服务不可用。
咱们再来下什么状况下驱赶会导致服务不可用:
- 服务存在单点故障,所有正本都在同一个节点,驱赶该节点时,就可能造成服务不可用。
- 服务没有单点故障,但刚好这个服务波及的 Pod 全副都部署在这一批被驱赶的节点上,所以这个服务的所有 Pod 同时被删,也会造成服务不可用。
- 服务没有单点故障,也没有全副部署到这一批被驱赶的节点上,但驱赶时造成这个服务的一部分 Pod 被删,短时间内服务的解决能力降落导致服务过载,局部申请无奈解决,也就升高了服务可用性。
针对第一点,咱们能够应用后面讲的反亲和性来防止单点故障。
针对第二和第三点,咱们能够通过配置 PDB (PodDisruptionBudget) 来防止所有正本同时被删除,驱赶时 K8S 会 “ 察看 ” nginx 的以后可用与冀望的正本数,依据定义的 PDB 来管制 Pod 删除速率,达到阀值时会期待 Pod 在其它节点上启动并就绪后再持续删除,以防止同时删除太多的 Pod 导致服务不可用或可用性升高,上面给出两个示例。
示例一 (保障驱赶时 nginx 至多有 90% 的正本可用):
apiVersion: policy/v1beta1kind: PodDisruptionBudgetmetadata: name: zk-pdbspec: minAvailable: 90% selector: matchLabels: app: zookeeper
示例二 (保障驱赶时 zookeeper 最多有一个正本不可用,相当于一一删除并期待在其它节点实现重建):
apiVersion: policy/v1beta1kind: PodDisruptionBudgetmetadata: name: zk-pdbspec: maxUnavailable: 1 selector: matchLabels: app: zookeeper
如何让服务进行平滑更新?
解决了服务单点故障和驱赶节点时导致的可用性升高问题后,咱们还须要思考一种可能导致可用性升高的场景,那就是滚动更新。为什么服务失常滚动更新也可能影响服务的可用性呢?别急,上面我来解释下起因。
如果集群内存在服务间调用:
当 server 端产生滚动更新时:
产生两种难堪的状况:
- 旧的正本很快销毁,而 client 所在节点 kube-proxy 还没更新完转发规定,依然将新连贯调度给旧正本,造成连贯异样,可能会报 “connection refused” (过程进行过程中,不再承受新申请) 或 “no route to host” (容器曾经齐全销毁,网卡和 IP 已不存在)。
- 新正本启动,client 所在节点 kube-proxy 很快 watch 到了新正本,更新了转发规定,并将新连贯调度给新正本,但容器内的过程启动很慢 (比方 Tomcat 这种 java 过程),还在启动过程中,端口还未监听,无奈解决连贯,也造成连贯异样,通常会报 “connection refused” 的谬误。
针对第一种状况,能够给 container 加 preStop,让 Pod 真正销毁前先 sleep 期待一段时间,期待 client 所在节点 kube-proxy 更新转发规定,而后再真正去销毁容器。这样能保障在 Pod Terminating 后还能持续失常运行一段时间,这段时间如果因为 client 侧的转发规定更新不及时导致还有新申请转发过去,Pod 还是能够失常解决申请,防止了连贯异样的产生。听起来感觉有点不优雅,但实际效果还是比拟好的,分布式的世界没有银弹,咱们只能尽量在以后设计现状下找到并实际可能解决问题的最优解。
针对第二种状况,能够给 container 加 ReadinessProbe (就绪查看),让容器内过程真正启动实现后才更新 Service 的 Endpoint,而后 client 所在节点 kube-proxy 再更新转发规定,让流量进来。这样可能保障等 Pod 齐全就绪了才会被转发流量,也就防止了链接异样的产生。
最佳实际 yaml 示例:
readinessProbe: httpGet: path: /healthz port: 80 httpHeaders: - name: X-Custom-Header value: Awesome initialDelaySeconds: 10 timeoutSeconds: 1 lifecycle: preStop: exec: command: ["/bin/bash", "-c", "sleep 10"]
更多信息请参考 Specifying a Disruption Budget for your Application。
健康检查怎么配才好?
咱们都晓得,给 Pod 配置健康检查也是进步服务可用性的一种伎俩,配置 ReadinessProbe (就绪查看) 能够防止将流量转发给还没启动齐全或出现异常的 Pod;配置 LivenessProbe (存活查看) 能够让存在 bug 导致死锁或 hang 住的利用重启来复原。然而,如果配置配置不好,也可能引发其它问题,这里依据一些踩坑经验总结了一些指导性的倡议:
- 不要轻易应用 LivenessProbe,除非你理解结果并且明确为什么你须要它,参考 Liveness Probes are Dangerous
- 如果应用 LivenessProbe,不要和 ReadinessProbe 设置成一样 (failureThreshold 更大)
- 探测逻辑里不要有内部依赖 (db, 其它 pod 等),防止抖动导致级联故障
- 业务程序应尽量裸露 HTTP 探测接口来适配健康检查,防止应用 TCP 探测,因为程序 hang 死时,TCP 探测依然能通过 (TCP 的 SYN 包探测端口是否存活在内核态实现,应用层不感知)
【腾讯云原生】云说新品、云研新术、云游新活、云赏资讯,扫码关注同名公众号,及时获取更多干货!!