阿里云kubernets使用记录10-代码更新导致接口波动

项目已经正常运行一段时间了,前期我们的一个设定带来了快捷性,但是也带来了弊端。那就是代码即时提交,即时生效。

我们使用nas承载源码,docker镜像只提供运行环境,通过目录共享的方式,让每个pod运行同一份源码。

这样的话,当有代码改动,只需要在nas中拉取代码,每个pod都能自动监听到代码的改动,进行自动reload,让修改后的代码生效,非常方便。

但是经过观察发现,这种情况,会导致2个问题。

1、所有的pod几乎在同一时间reload,会造成短暂的所有pod不可用,而reload并不被k8s认为不可用,所有流量还会分配,就可能造成服务不可用。

2、这种波动会被hpa捕获到,进而执行扩容pod,其实这种扩容是无意义的。

所以我们的解决方案是让这些pod能够有序的,有规划的reload,平稳的渡过代码的变动导致的波动。

一、停止自动reload

webman的reload是依赖于monitor进程的,每次项目启动,都会启动一个monitor进程用来监听文件的变化,我们要先从根源上处理掉。

我这里比较简单,webman的1.x是在config/process.php中配置的,我们不让monitor返回就行。

二、增加监听pod是否健康的接口

怎么让svc不再把流量分配到正在reload的pod呢?那么我们需要在reload的时候,告知svc,该pod没有准备好接收流量,这里我们就用到了pod的NotReady状态,我们这里提前准备一个健康的接口来控制。在config/route.php中增加健康探测的接口。代码如下:

use support\Response;

Route::get('/podHealth', function () {
    if (file_exists('/dev/shm/reloading')) {
        return new Response(503, [], 'reloading');
    }
    return new Response(200, [], 'ok');
});

三、修改deployment中关于pod健康的监听


          readinessProbe:
            httpGet:
              path: /podHealth
              port: 8787
            initialDelaySeconds: 5 # 容器启动后延迟5秒再探测
            periodSeconds: 2        # 每2秒探测一次
            failureThreshold: 1     # 探测失败一次就标记未就绪        

这里我们监听的就是上一步添加的接口,我们的设定是如果准备reload代码,就往pod中添加一个文件/dev/shm/reloading,这样的话,探测接口,就会返回503,那么k8s就会自动把该pod设置为NotReady状态,svc就不再分配流量给该pod,然后我们再去reload,执行完毕后,再删除这个/dev/shm/reloading文件,就会自动转为Ready接收流量。

四、处理自动化脚本

准备工作我们已经做好,上述的代码已经提交,deployment的修改已经apply,下一步就是代码提交后的自动化脚本执行。脚本我们准备好,代码如下

#!/bin/bash

DEPLOY_NAME=$1

# 检查是否输入了参数
if [ -z "$DEPLOY_NAME" ]; then
    echo "错误: 请输入 Deployment 名称。"
    echo "用法: $0 <deployment-name>"
    exit 1
fi
# 配置
READY_FILE="/dev/shm/reloading"
SLEEP_BEFORE_RELOAD=3  # 确保 K8s 已经通过探针感知到 503 并摘除流量
SLEEP_AFTER_RELOAD=3   # 等待 Webman Worker 完成平滑切换

# 获取所有 Pod 名称
pods=$(kubectl get pod -l app=$DEPLOY_NAME -o jsonpath='{.items[*].metadata.name}')

if [ -z "$pods" ]; then
    echo "未发现 label 为 app=$DEPLOY_NAME 的 Pod,请检查名称是否正确。"
    exit 1
fi

for pod in $pods; do
  echo "--- 处理 $pod ---"

  # 1. 触发 503 状态
  echo "将 $pod 设置为NotReady"
  kubectl exec $pod -- touch $READY_FILE

  # 2. 等待 K8s Service 摘除 Endpoint
  # 提示:请确保你的 readinessProbe.periodSeconds * failureThreshold < 此时间
  echo "等待服务移除该Pod (sleep ${SLEEP_BEFORE_RELOAD}s)..."
  sleep $SLEEP_BEFORE_RELOAD

  # 3. 发送平滑重启信号
  echo "Reload $pod "
  kubectl exec $pod -- pkill -USR1 php

  # 4. 等待进程切换完成
  echo "等待 $pod reload"
  sleep $SLEEP_AFTER_RELOAD

  # 5. 恢复健康状态
  echo "将 $pod 设置为 Ready..."
  kubectl exec $pod -- rm -f $READY_FILE

  # 6. 验证 (可选)
  # 可以通过 kubectl exec 调用 curl 检查一次 /health 是否返回 200
  
  echo "成功 reloaded $pod"
  sleep 1
done

echo "$DEPLOY_NAME All pods reloaded successfully."

脚本的大概内容就是,获取到我们要处理的项目名称,然后查找对应的pod列表,依次对这些pod执行添加/dev/shm/reloading,触发503状态,然后执行reload,完毕后再移除/dev/shm/reloading,触发健康状态,让relaod后的pod开始接收流量,然后执行下一个。

我们在自动git pull后,就不再自动reload了,我们就要主动执行这个脚本了,执行命令如下:

/bin/bash /www/script/deploy.sh deployment名称

然后,如果我们提交代码,通过查看pod的命令,就能看到每个pod,在依次的NotReady,然后恢复,再下一个。

五、注意点

1、你至少要有2个以上的pod,否则所谓的依次reload没有意,反而影响更大。

2、代码的整体生效时间,约为7s*pod数量,在这个时间里,你的项目,会运行2套代码,一个是修改前,一个是修改后,需要注意兼容性,尤其是消息队列的兼容,你的新pod投递的消息队列,有可能被旧pod消费

3、如果安装的有webman/domain包,注意在中间件中,跳过对/podHealth的判定。

文章作者: Wind
本文链接:
版权声明: 本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 雕刻时光
技术分享 webman k8s Kubernetes
喜欢就支持一下吧