ci(retag/deploy): 增加镜像内容校验,防止静默部署旧/损坏镜像 #1569

Merged
xiaoxia merged 1 commits from ci/image-content-verification into develop 2026-08-31 13:07:43 +08:00
2 changed files with 135 additions and 0 deletions
+51
View File
@@ -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" <<EOF
timestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ)
image=${IMAGE}
src_tag=${SRC_TAG}
new_tag=${NEW_TAG}
src_digest=${SRC_DIGEST}
new_digest=${NEW_DIGEST}
age_hours=${AGE_HOURS}
EOF
echo "审计记录: $RETAG_LOG"
+84
View File
@@ -260,6 +260,90 @@ fi
echo "All images pulled."
# ====== 镜像内容校验(CI 加固 - 防止静默部署损坏/过期镜像) ======
echo ""
echo "=========================================="
echo " 镜像内容校验"
echo "=========================================="
VERIFY_FAILED=0
DEPLOY_MANIFEST="${GENERATED_DIR}/deploy-manifest.json"
# 读取上次部署的 manifest(用于对比)
PREV_MANIFEST=""
if [ -f "$DEPLOY_MANIFEST" ]; then
PREV_MANIFEST=$(cat "$DEPLOY_MANIFEST")
echo "上次部署 manifest 存在"
fi
NEW_MANIFEST_LINES=""
for svc in api worker web; do
img_var="REGISTRY_$(echo $svc | tr '[:lower:]' '[:upper:]')"
img_val=$(eval echo "\$$img_var")
# 1. 检查镜像是否存在
if ! docker image inspect "$img_val" >/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" <<MANIFEST_EOF
{
"deployed_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
"image_tag": "$IMAGE_TAG",
${NEW_MANIFEST_LINES} "verified": true
}
MANIFEST_EOF
echo ""
echo "部署 manifest 已更新: $DEPLOY_MANIFEST"
echo "=========================================="
echo ""
echo "Backing up legacy assets from current web container..."
if docker inspect xiaoxia-web-staging >/dev/null 2>&1; then
_tmpdir="/tmp/legacy-assets-$$"