8435095b4f
ci/woodpecker/tag/build Pipeline was successful
① 部署方式重构为真正的蓝绿(修复 v1.2.55~57 每次部署约 3 分钟的 502 窗口): 根因:旧脚本是「翻到备用端口 → 1 秒后翻回 → 在活跃端口重启容器」,最终稳态只剩一个容器;且两次 nginx -s reload 仅隔 1 秒,openresty 未设 worker_shutdown_timeout(仅 keepalive_timeout 60),旧 worker 会继续持有连接并按旧配置往已删除的备用端口转发 → 502 持续到旧 worker 排空(实测 17:28:27~17:31:21,8885 Connection refused 86 次,8884 为 0)。 新方案:两个固定槽位(blue=8884 / green=8885)常驻。每次部署只更新「当前无流量的备用槽」→ 容器内健康检查 → nginx -t 校验 → 翻一次反代指向它 → 等旧 worker 全部退出;活跃槽容器保留作为上一版本供秒级回滚。因两端口始终都有活容器,旧 worker 无论是否排空都不可能出现 502。 另含:旧命名容器一次性收养(cpdaily-xiaozhushou → cpdaily-xiaozhushou-<端口>,避免下次部署端口冲突)、清理 cpdaily-blue 陈尸容器、反代配置切换前备份与 nginx -t 失败自动回滚、健康检查失败时不动反代。脚本已用 mock docker 干跑验证:两次连续部署正确轮换(8885→8884)、健康检查失败分支正确中止且不改反代。 ② 个人中心设备码按钮:未绑定设备码时显示「立即绑定」,已绑定显示「重新绑定」(新增 hasDeviceCode computed)。 ③ 验证:vue-tsc --noEmit、npm run build(含 postbuild-css)全过。前端改动 + CI 配置,无后端逻辑变更。
190 lines
8.7 KiB
YAML
190 lines
8.7 KiB
YAML
when:
|
||
- event: tag
|
||
tag: v*
|
||
|
||
clone:
|
||
git:
|
||
image: woodpeckerci/plugin-git
|
||
settings:
|
||
remote: http://10.10.10.128:3000/becomer/cpdaily_xiaozhushou.git
|
||
|
||
steps:
|
||
build-image:
|
||
name: 构建 Docker 镜像
|
||
image: docker
|
||
commands: |
|
||
docker build -t cpdaily-xiaozhushou:latest .
|
||
docker tag cpdaily-xiaozhushou:latest cpdaily-xiaozhushou:${CI_COMMIT_TAG:-${CI_COMMIT_SHA:0:8}}
|
||
volumes:
|
||
- /var/run/docker.sock:/var/run/docker.sock
|
||
|
||
deploy:
|
||
name: 蓝绿部署
|
||
image: docker
|
||
commands: |
|
||
set -e
|
||
|
||
IMAGE_TAG="${CI_COMMIT_TAG:-latest}"
|
||
|
||
# 应用配置由宿主机 /opt/cpdaily-xiaozhushou/config.toml 运维维护
|
||
# (已移出版本库,避免密钥入库;容器以只读挂载方式读取)
|
||
|
||
# 两个固定槽位(蓝/绿),任一时刻都保持"两个容器都活着":
|
||
# 活跃槽 = 反代当前指向的端口;备用槽 = 另一个端口(上一版,供秒级回滚)
|
||
SLOT_BLUE=8884
|
||
SLOT_GREEN=8885
|
||
|
||
# ============================================================
|
||
# 第一步:读取当前反代指向的端口,确定活跃/备用槽
|
||
# 反代入口为 1Panel 站点 jinrixiaoyuan.top(frps 公网链路):
|
||
# /www/sites/jinrixiaoyuan.top/proxy/root.conf → proxy_pass http://127.0.0.1:8884|8885
|
||
# 该文件经 conf.d/jinrixiaoyuan.top.conf 的 include /www/sites/.../proxy/*.conf 载入。
|
||
# ============================================================
|
||
CURRENT_PORT=$(docker exec 1Panel-openresty-ESmn \
|
||
grep -oE 'proxy_pass http://[0-9.]+:[0-9]+' \
|
||
/www/sites/jinrixiaoyuan.top/proxy/root.conf | grep -o '[0-9]\+$')
|
||
echo "=== 当前反代端口: $CURRENT_PORT ==="
|
||
|
||
if [ "$CURRENT_PORT" = "$SLOT_BLUE" ]; then
|
||
PORT_ACTIVE=$SLOT_BLUE
|
||
PORT_STANDBY=$SLOT_GREEN
|
||
elif [ "$CURRENT_PORT" = "$SLOT_GREEN" ]; then
|
||
PORT_ACTIVE=$SLOT_GREEN
|
||
PORT_STANDBY=$SLOT_BLUE
|
||
else
|
||
echo "ERROR: 未知反代端口 $CURRENT_PORT,中止部署(期望 $SLOT_BLUE/$SLOT_GREEN)"
|
||
exit 1
|
||
fi
|
||
echo " 活跃槽: $PORT_ACTIVE | 备用槽: $PORT_STANDBY"
|
||
echo " 目标: 更新备用槽 :$PORT_STANDBY 到 $IMAGE_TAG 后,切换反代指向它(活跃槽保留,可回滚)"
|
||
|
||
# ============================================================
|
||
# 第一步附:一次性「收养」旧命名容器
|
||
# 迁移前的容器名为 cpdaily-xiaozhushou(无端口后缀),本脚本改用
|
||
# cpdaily-xiaozhushou-<端口>。若不改名,下次部署 rm -f 找不到它 → 端口
|
||
# 被占用导致 docker run 冲突、部署失败。此处按它实际映射的端口改名(幂等)。
|
||
# 同时清理历史遗留的 cpdaily-blue 陈尸容器。
|
||
# ============================================================
|
||
LEGACY_NAME=cpdaily-xiaozhushou
|
||
if docker inspect "$LEGACY_NAME" >/dev/null 2>&1; then
|
||
LEGACY_PORT=$(docker port "$LEGACY_NAME" 8093 2>/dev/null | head -1 | grep -oE '[0-9]+$' || true)
|
||
if [ -n "$LEGACY_PORT" ]; then
|
||
if docker inspect "cpdaily-xiaozhushou-$LEGACY_PORT" >/dev/null 2>&1; then
|
||
echo " 发现旧命名容器,但 cpdaily-xiaozhushou-$LEGACY_PORT 已存在,移除旧容器"
|
||
docker rm -f "$LEGACY_NAME" || true
|
||
else
|
||
echo " 收养旧命名容器: $LEGACY_NAME → cpdaily-xiaozhushou-$LEGACY_PORT"
|
||
docker rename "$LEGACY_NAME" "cpdaily-xiaozhushou-$LEGACY_PORT"
|
||
fi
|
||
else
|
||
echo " 旧命名容器未映射 8093 端口,跳过收养"
|
||
fi
|
||
fi
|
||
docker rm -f cpdaily-blue 2>/dev/null || true
|
||
|
||
# ============================================================
|
||
# 第二步:更新「备用槽」容器(此槽当前无流量,停它不会影响线上)
|
||
# ============================================================
|
||
echo "=== 停止并移除备用槽旧容器(:$PORT_STANDBY)==="
|
||
docker rm -f cpdaily-xiaozhushou-$PORT_STANDBY 2>/dev/null || true
|
||
|
||
echo "=== 在备用槽启动新容器 :$PORT_STANDBY (image: $IMAGE_TAG) ==="
|
||
docker run -d --name cpdaily-xiaozhushou-$PORT_STANDBY \
|
||
--restart unless-stopped \
|
||
--network likedream-net \
|
||
-e TZ=Asia/Shanghai \
|
||
-p $PORT_STANDBY:8093 \
|
||
-v /opt/cpdaily-xiaozhushou/config.toml:/app/config.toml:ro \
|
||
cpdaily-xiaozhushou:$IMAGE_TAG
|
||
|
||
# ============================================================
|
||
# 第三步:健康检查(新容器必须就绪,否则直接失败且不动反代)
|
||
# 用 docker exec 在容器内自测,避免依赖宿主机到容器的网络路径。
|
||
# ============================================================
|
||
HEALTHY=false
|
||
for i in $(seq 1 30); do
|
||
if docker exec cpdaily-xiaozhushou-$PORT_STANDBY \
|
||
wget -q --timeout=3 -O /dev/null http://127.0.0.1:8093/health 2>/dev/null; then
|
||
HEALTHY=true
|
||
break
|
||
fi
|
||
echo " 等待新容器就绪... ($i/30)"
|
||
sleep 2
|
||
done
|
||
|
||
if [ "$HEALTHY" = false ]; then
|
||
echo "ERROR: 新容器未通过健康检查,中止部署(线上仍由活跃槽 :$PORT_ACTIVE 提供服务,未受影响)"
|
||
docker rm -f cpdaily-xiaozhushou-$PORT_STANDBY 2>/dev/null || true
|
||
exit 1
|
||
fi
|
||
echo " 新容器已就绪 ✓"
|
||
|
||
# ============================================================
|
||
# 第四步:切换 1Panel 反代到备用槽(唯一一次代理切换)
|
||
# 切换前先备份配置,nginx -t 通过后再 reload;reload 后等待旧 worker 退出,
|
||
# 确保旧连接不再按旧配置转发。
|
||
# ============================================================
|
||
echo "=== 切换 1Panel 反代 :$PORT_ACTIVE → :$PORT_STANDBY ==="
|
||
PROXY_CONF=/www/sites/jinrixiaoyuan.top/proxy/root.conf
|
||
docker exec 1Panel-openresty-ESmn \
|
||
cp $PROXY_CONF $PROXY_CONF.bak
|
||
docker exec 1Panel-openresty-ESmn \
|
||
sed -i "s|:$PORT_ACTIVE|:$PORT_STANDBY|g" $PROXY_CONF
|
||
|
||
if ! docker exec 1Panel-openresty-ESmn nginx -t 2>&1; then
|
||
echo "ERROR: nginx 配置校验失败,回滚反代配置"
|
||
docker exec 1Panel-openresty-ESmn cp $PROXY_CONF.bak $PROXY_CONF
|
||
docker rm -f cpdaily-xiaozhushou-$PORT_STANDBY 2>/dev/null || true
|
||
exit 1
|
||
fi
|
||
|
||
# 记录 reload 前的 worker pid,reload 后等其全部退出,避免旧 worker 按旧配置继续转发
|
||
OLD_WORKERS=$(docker exec 1Panel-openresty-ESmn \
|
||
sh -c 'ps -eo pid,cmd | grep "[n]ginx: worker" | awk "{print \$1}"' | tr '\n' ' ')
|
||
docker exec 1Panel-openresty-ESmn nginx -s reload
|
||
echo " Nginx 热加载完成,等待旧 worker 退出..."
|
||
|
||
for i in $(seq 1 15); do
|
||
STILL=0
|
||
for p in $OLD_WORKERS; do
|
||
if docker exec 1Panel-openresty-ESmn sh -c "[ -d /proc/$p ]" 2>/dev/null; then
|
||
STILL=$((STILL + 1))
|
||
fi
|
||
done
|
||
if [ "$STILL" = "0" ]; then
|
||
echo " 旧 worker 已全部退出 ✓"
|
||
break
|
||
fi
|
||
echo " 等待旧 worker 释放连接... ($i/15, 剩余 $STILL)"
|
||
sleep 2
|
||
done
|
||
echo " 反代已指向 :$PORT_STANDBY ✓"
|
||
|
||
# ============================================================
|
||
# 第五步:验证线上确实由新槽服务
|
||
# ============================================================
|
||
if ! docker exec 1Panel-openresty-ESmn \
|
||
sh -c "wget -q --timeout=5 -O /dev/null http://127.0.0.1:$PORT_STANDBY/health" 2>/dev/null; then
|
||
echo "WARN: 新槽健康检查异常,但反代已切换,请人工核查"
|
||
else
|
||
echo " 线上验证通过(:$PORT_STANDBY 正常服务)✓"
|
||
fi
|
||
|
||
# ============================================================
|
||
# 第六步:保留活跃槽用于回滚说明;清理多余历史镜像
|
||
# 注:旧槽容器不删除 —— 它即上一版本,仍需回滚时可直接把反代切回。
|
||
# 下次部署时旧槽会被当作「备用槽」重建,因此不会无限堆积。
|
||
# ============================================================
|
||
echo "=== 当前容器 ==="
|
||
docker ps --filter "name=cpdaily-xiaozhushou-" --format '{{.Names}} → {{.Ports}} ({{.Status}})'
|
||
|
||
echo "=== 清理历史镜像(保留 latest 与最近 2 个版本)==="
|
||
docker images cpdaily-xiaozhushou --format '{{.Tag}}' \
|
||
| grep -v '^latest$' | head -n -2 | while read tag; do
|
||
docker rmi cpdaily-xiaozhushou:$tag 2>/dev/null || true
|
||
done
|
||
|
||
echo "=== 部署完成 ✓ 活跃槽 :$PORT_STANDBY(旧槽 :$PORT_ACTIVE 保留,可回滚)==="
|
||
volumes:
|
||
- /var/run/docker.sock:/var/run/docker.sock
|