哈喽,大家好,我是明智
本日跟大家讨论一个服务稳固性相关的话题,对于大部门做业务的小同伴来说,很少会被问到这类问题
不外你如果你盼望面试公司的一些基础部门,例如:基础架构、效能开辟、服务稳固性保障等,就很可能遇到
笔者在前段时间的面试中就被问到了,这个问题还有一系列的问法,例如:
- 服务发布过程中总是出现5xx怎么办?
- 怎样实现服务的优雅启停?
本文重要分为以下几个部门
- 保证流量无损的关键是什么?
- K8s情况下怎样保证流量无损?
- 怎样验证?
- 会有什么消极影响?
❝ 本文只讨论K8s情况下的怎样做到优雅启停
流量无损的关键
发布过程中丧失的流量重要包罗:
- 在启动一个新pod时,尚未完全停当,就接收到了流量,导致服务响应500。例如服务依靠的db、redis还未启动
- 在克制一个旧pod时,这个pod中仍旧存在正在处理中的请求,请求尚未处理完,pod就被杀死
- 在pod克制的过程中,仍旧有部门流量被路由到这个pod上
- pod已经被杀死了,但是网关、注册中心等没有及时更新数据,导致仍旧有请求被路由到一个不存在的实例上
因此我们要保证流量无损,要做到以下几点
- 每次杀死一个pod前,应该先启动一个新pod。即:滚动发布
- 启动一个新pod时,必要等待pod完全停当,才气让这个pod接收流量。这必要利用到K8s的三种探针
- pod在被杀死前,必要先保证流量被摘除干净。这必要利用到K8s提供的preStopHook
- pod的流量摘除干净后,必要保证pod中正在处理的流量正常响应。这必要服务自身响应kill信息
K8s情况下怎样保证流量无损
K8s中的滚动发布
如果是Deployment ,可以通过设置 Deployment 的 rollingUpdate 计谋来控制滚动更新的行为。
关键参数
- maxUnavailable:指定在更新过程中,最多可以有多少个 Pod 处于不可用状态。可以设置为绝对数量(如 1)或百分比(如 25%)。
- maxSurge:指定在更新过程中,最多可以额外创建多少个 Pod。可以设置为绝对数量(如 1)或百分比(如 25%)。
- piVersion: apps/v1
- kind: Deployment
- metadata:
- name: example-deployment
- spec:
- replicas: 3
- strategy:
- type: RollingUpdate
- rollingUpdate:
- maxUnavailable: 1 # 在更新过程中最多允许 1 个 Pod 不可用
- maxSurge: 1 # 在更新过程中最多允许额外创建 1 个 Pod
复制代码 如果是statefulSet,其滚动更新计谋由 updateStrategy 字段指定,重要有以下几个设置选项:
- **RollingUpdate**:这是 StatefulSet 唯一支持的更新计谋类型,按序号从高到低逐个更新 Pod。
- **Partition**:可选字段,用于指定更新过程中保留的 Pod 的最小编号。只有编号大于该分区的 Pod 会被更新。默认值为 0,这意味着所有 Pod 都会被更新。
K8s的三种探针
我们利用这三种探针的目的在于:
- 保证pod在完全停当后才承接流量
- 当应用出现故障时可以及时重启,实现故障自动恢复
webp Startup Probe (启动探针):如果设置了启动探针,在进行启动探测成功之前,不会进行存活探测。如果启动探测失败,k8s会重启pod。启动探针探测成功后,Liveness Probe 和 Readiness Probe 将接管健康查抄。Startup Probe 适合用于那些启动时间较长的应用程序,防止它们在启动过程中被误判为失败。如果应用启动时间不是特别长,没有须要设置这个探针。
Liveness Probe (存活探针):存活探测如果失败,K8s会重启Pod。重要用于检测可通过重启办理的故障,例如:服务端可用线程耗尽、死锁等
Readiness Probe (停当探针):停当探测成功之后,才会将pod加入到svc的端点列表中。如果停当探测失败,会将pod从端点中剔除。
利用示例
deployment文件添加探针:
- apiVersion: apps/v1
- kind: Deployment
- spec:
- template:
- spec:
- containers:
- # 在containers下添加两个指针
- livenessProbe:
- httpGet:
- path: /actuator/health/liveness
- port: 8080
- # pod启动后延迟60s探测
- initialDelaySeconds: 45
- # 每30s测一次
- periodSeconds: 10
- # 每次探测最大超时时间3s
- timeoutSeconds: 3
- # 连续6次探测失败则重启pod
- failureThreshold: 3
- readinessProbe:
- httpGet:
- path: /actuator/health/readiness
- livenessProbe:
- httpGet:
- path: /actuator/health/liveness
- port: 8080
- initialDelaySeconds: 45
- periodSeconds: 10
- timeoutSeconds: 3
- failureThreshold: 3
复制代码 留意事项
1、添加liveness跟readiness探针后,如果组件状态异常,会导致pod重启, 在特殊场景下, 组件异常并不必要重启, 必要利用方自行判断。
例如:redis挂掉, 但是在服务中, redis缓存允许穿透或者异常, redis挂掉不应该重启呆板, 这个在healthcheck中,redis状态异常不应该触发liveness和readness失败
2、initialDelaySeconds的值应该根据应用启动时间进行公道设置,如果设置过短,会导致pod反复被kill无法正常启动
K8s的preStopHook
K8s杀死一个pod的过程
在 Kubernetes Pod 的删除过程中,同时会存在两条并行的时间线,如下图所示。
- 一条时间线是网络规则的更新过程。
- 另一条时间线是 Pod 的删除过程。
pod删除过程 网络层面
- Pod 被删除,状态置为 Terminating。
- Endpoint Controller 将该 Pod 的 ip 从 Endpoint 对象中删除。
- Kube-proxy 根据 Endpoint 对象的改变更新 iptables 规则,不再将流量路由到被删除的 Pod。
- 网关、注册中心等更新数据,剔除对应的pod ip。
Pod 层面
- Pod 被删除,状态置为 Terminating。
- Kubelet 捕获到 ApiServer 中 Pod 状态变化,执行 syncPod 动作。
- 如果 Pod 设置了 preStop Hook ,将会执行。
- kubelet 对 Pod 中各个 container 发送调用 cri 接口中 StopContainer 方法,向 dockerd 发送 stop -t 指令,用 SIGTERM 信号以通知容器内应用进程开始优雅克制。
- 等待容器内应用进程完全克制,如果在 terminationGracePeriodSeconds (默认 30s) - preStop 执行时间内还未完全克制,就发送 SIGKILL 信号强制杀死应用进程。
- 所有容器进程终止,清算 Pod 资源。
由于网络层面跟pod层面的变更是并行的,二者状态的不同等,会导致pod在停机的过程中仍旧在接收流量。为了规避这个问题,我们必要利用到K8s提供的preStopHook
引入preStopHook后,可以在pod杀死前,先执行preStopHook,在preStopHook中我们可以执行一个脚本,这个脚本可以简单的sleep一段时间,等待网络层变更完成后pod再响应SIGTERM信号,确保pod克制时,流量已经完全摘除,如下:
- spec:
- contaienrs:
- - name: my-awesome-container
- lifecycle:
- preStop:
- exec:
- command: ["sh", "-c", "sleep 10"] # 延迟关闭 10秒
复制代码 很多情况下,pod除了接收K8s调理的流量外,还会接收来自rpc的流量,例如:dubbo,这个时间,我们可以在实现一个负责的脚本,除了等待k8s网络层变更完成外,还必要将服务从注册中心中下限
- spec:
- contaienrs:
- - name: my-awesome-container
- lifecycle:
- preStop:
- exec:
- command: ["/bin/sh","-c","/pre-stop.sh"]
复制代码 ❝ 应用程序提供一个服务下线的借口,在pre-stop.sh中,可以调用这个接口,实现服务下线
怎样验证
这里我直接给出一个验证脚本,大家将其中的参数替换为服务核心接口的url即可:
- #!/bin/bash
- # 目标 URL
- url="your url"
- # 请求失败的次数计数器
- fail_count=0
- # 总请求次数计数器
- total_count=0
- # 日志文件
- log_file="request_log.txt"
- # 发送请求并统计失败次数的函数
- send_request() {
- response=$(curl --silent --show-error --write-out "HTTPSTATUS:%{http_code}" "$url")
- # 提取HTTP状态码
- http_status=$(echo "$response" | tr -d '\n' | sed -e 's/.*HTTPSTATUS://')
- response_body=$(echo "$response" | sed -e 's/HTTPSTATUS\:.*//g')
- timestamp=$(date "+%Y-%m-%d %H:%M:%S")
- ((total_count++))
- if [ "$http_status" -ne 200 ]; then
- ((fail_count++))
- echo "$timestamp - 请求失败, HTTP状态码: $http_status, 响应: $response_body" | tee -a "$log_file"
- else
- echo "$timestamp - 请求成功, HTTP状态码: $http_status, 响应: $response_body" | tee -a "$log_file"
- fi
- }
- # 每秒钟发送一个请求
- while true; do
- send_request
- echo "失败次数: $fail_count,请求总次数:$total_count" | tee -a "$log_file"
- sleep 1
- done
复制代码
- 将以下文件生存为xxx.sh,
- 执行chmod +x xxx.sh
- ./ xxx.sh,运行这个脚本
- 发布服务,观察脚本输出
在服务发布过程中,没有出现一次请求失败,说明接入成功
带来的影响
接入优雅启停对服务最大的影响在于发布时间会延伸。发布时间包罗:新pod的启动时间+旧pod的终止时间
如果按照以下设置:
- 探针初始延迟45s
- preStopHook中等待15s
- 滚动比例为25%,分4批 一个应用一个批次滚动完必要先启动新的pod,再杀死老的pod,因此如果一个应用的pod数量超过4个,单次发布的时间预计在4分钟左右
办理方案:可以通过调解滚动比例跟探针的检测时间来调解这个时间,例如滚动比例调解到30%,探针初始延迟调解到30s
总结
本文重要跟大家介绍了K8s情况下怎样实现服务的优雅启停来保证服务发布过程中流量无损,咱们不仅学会了怎么做,也知道了为什么要怎么做。
在文中我有提到,要实现服务的优雅启停还必要服务自身正确的响应kill信息,其实就是K8s发出的SIGTERM信号,对应kill -15命令。
服务正确响应SIGTERM信号要怎么做本文没有提到,别的本文只提到了怎样设置K8s的探针,但没有涉及到探针怎样实现。思量到篇幅缘故因由,关于应用自身必要做的变乱,我们放到下篇文章中。
作者简介:
- 大三退学,创业、求职、自考,一起升级
- 7年it从业履历,多个开源社区contributor
- 专注分享发展路上的所悟所得
- 恒久探索 个人发展|职业发展|副业探索
推荐阅读:
美团一面问我i++跟++i的区别是什么
美团一面,你碰到过CPU 100%的情况吗?你是怎么处理的?
美团一面:碰到过OOM吗?你是怎么处理的?
美团一面,发生OOM了,程序还能继承运行吗?
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |