From f7693baeaee55f76e893848cedb9a89f276bfc0d Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Mon, 31 Aug 2026 12:01:19 +0800 Subject: [PATCH] =?UTF-8?q?ci(retag/deploy):=20=E5=A2=9E=E5=8A=A0=E9=95=9C?= =?UTF-8?q?=E5=83=8F=E5=86=85=E5=AE=B9=E6=A0=A1=E9=AA=8C=EF=BC=8C=E9=98=B2?= =?UTF-8?q?=E6=AD=A2=E9=9D=99=E9=BB=98=E9=83=A8=E7=BD=B2=E6=97=A7/?= =?UTF-8?q?=E6=8D=9F=E5=9D=8F=E9=95=9C=E5=83=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit retag_skipped_image.sh 加固: - 源镜像 layers 有效性检查(layers=0 拒绝 retag) - 镜像创建时间审计(>72h 输出警告) - digest 审计记录,方便溯源 ci_staging_deploy.sh 加固: - pull 后逐服务校验镜像 layers 有效性 - 记录每个镜像的 digest/size/created - 对比上次部署 manifest,标注变化状态 - 写入 deploy-manifest.json 供下次对比 - 校验失败直接阻断部署 concurrency 保持 cancel-in-progress: true(不变) 生产发版链路不动 --- scripts/ci/retag_skipped_image.sh | 51 +++++++++++++++++++ scripts/ci_staging_deploy.sh | 84 +++++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+) diff --git a/scripts/ci/retag_skipped_image.sh b/scripts/ci/retag_skipped_image.sh index d2b39f2c8..58b6c6d42 100755 --- a/scripts/ci/retag_skipped_image.sh +++ b/scripts/ci/retag_skipped_image.sh @@ -50,8 +50,59 @@ if [ -z "$SRC_TAG" ]; then fi echo "✅ 源镜像: ${IMAGE}:${SRC_TAG}" + +# ====== 镜像内容校验(CI 加固 - 防止静默部署旧/损坏镜像) ====== +echo "" +echo "--- 镜像内容校验 ---" + +# 1. 检查镜像 layers 有效性 +LAYERS=$(docker inspect --format='{{len .RootFS.Layers}}' "${IMAGE}:${SRC_TAG}" 2>/dev/null || echo "0") +echo "源镜像 layers 数量: $LAYERS" +if [ "$LAYERS" -eq 0 ]; then + echo "❌ 源镜像无有效 layers,可能为损坏镜像,拒绝 retag" + exit 1 +fi + +# 2. 镜像创建时间检查 +CREATED=$(docker inspect --format='{{.Created}}' "${IMAGE}:${SRC_TAG}") +echo "源镜像创建时间: $CREATED" + +# 解析创建时间距现在多少小时 +CREATED_EPOCH=$(date -d "$CREATED" +%s 2>/dev/null || echo "0") +NOW_EPOCH=$(date +%s) +AGE_HOURS=$(( (NOW_EPOCH - CREATED_EPOCH) / 3600 )) +echo "源镜像年龄: ${AGE_HOURS}小时" +if [ "$AGE_HOURS" -gt 72 ]; then + echo "⚠️ 警告: 源镜像已超过 72 小时,可能存在分支 tag 未更新的风险" + echo " 请检查最近几次 CI 是否对该服务成功构建过" +fi + +# 3. Digest 审计记录 +SRC_DIGEST=$(docker inspect --format='{{.Id}}' "${IMAGE}:${SRC_TAG}") +echo "源镜像 ID: $SRC_DIGEST" + +echo "--- 镜像内容校验通过 ---" +echo "" + docker tag "${IMAGE}:${SRC_TAG}" "${NEW_REF}" # 推新 SHA tag;分支 tag 若指向的就是源 digest 则无需重复,失败可忽略 docker push "${NEW_REF}" + +NEW_DIGEST=$(docker inspect --format='{{.Id}}' "${NEW_REF}") echo "✅ retag 推送完成: ${NEW_REF} (from ${SRC_TAG})" +echo " 源 digest: ${SRC_DIGEST}" +echo " 新 tag digest: ${NEW_DIGEST}" + +# 写入 retag 审计记录(供 deploy 步骤参考) +RETAG_LOG="/tmp/retag-audit-$(date +%s).txt" +cat > "$RETAG_LOG" </dev/null 2>&1; then + echo " ❌ $svc: 镜像不存在 ($img_val)" + VERIFY_FAILED=$((VERIFY_FAILED + 1)) + continue + fi + + # 2. 检查 layers 有效性 + LAYER_COUNT=$(docker inspect --format='{{len .RootFS.Layers}}' "$img_val" 2>/dev/null || echo "0") + if [ "$LAYER_COUNT" -eq 0 ]; then + echo " ❌ $svc: 镜像无有效 layers ($img_val)" + VERIFY_FAILED=$((VERIFY_FAILED + 1)) + continue + fi + + # 3. 获取 digest 和创建时间 + IMG_ID=$(docker inspect --format='{{.Id}}' "$img_val") + IMG_CREATED=$(docker inspect --format='{{.Created}}' "$img_val") + IMG_SIZE=$(docker inspect --format='{{.Size}}' "$img_val") + echo " ✅ $svc: ${LAYER_COUNT} layers, size=${IMG_SIZE}, created=${IMG_CREATED}" + echo " id: $IMG_ID" + + # 4. 对比上次部署 + CHANGED="unchanged" + if [ -n "$PREV_MANIFEST" ]; then + PREV_ID=$(echo "$PREV_MANIFEST" | grep "\"${svc}_id\"" | sed 's/.*: *"\(.*\)".*/\1/' 2>/dev/null || echo "") + if [ -n "$PREV_ID" ] && [ "$PREV_ID" != "$IMG_ID" ]; then + CHANGED="changed" + elif [ -n "$PREV_ID" ] && [ "$PREV_ID" = "$IMG_ID" ]; then + CHANGED="unchanged" + else + CHANGED="unknown" + fi + else + CHANGED="first-deploy" + fi + echo " vs last deploy: $CHANGED" + + NEW_MANIFEST_LINES="${NEW_MANIFEST_LINES} \"${svc}_id\": \"${IMG_ID}\", + \"${svc}_created\": \"${IMG_CREATED}\", + \"${svc}_layers\": ${LAYER_COUNT}, +" +done + +if [ "$VERIFY_FAILED" -gt 0 ]; then + echo "" + echo "ERROR: $VERIFY_FAILED 个镜像校验失败,拒绝部署" + exit 1 +fi + +# 写入新 manifest +cat > "$DEPLOY_MANIFEST" </dev/null 2>&1; then _tmpdir="/tmp/legacy-assets-$$" -- 2.54.0