技术博客

Docker 多环境镜像管理:dev/staging/prod 策略、tag 规范与版本治理

系统讲解企业 Docker 镜像的多环境管理体系:dev/staging/prod 三环境镜像策略设计、语义版本 tag 规范(不同场景选择的 tag 类型)、Harbor 项目空间与清理策略(保留最近 N 个版本)、镜像版本晋升流程(同一个 SHA 在不同环境复用)、发布追踪(从 commit 到生产的完整链路),以及镜像管理的自动化脚本与审计实践。

Docker镜像管理版本管理tag规范Harbor多环境企业级DevOps

镜像管理是企业容器化运维中最容易失控的环节:没有规范时,仓库里充斥着含义不明的 latesttestnewfinal2 等 tag,出了问题完全无法追溯。本文建立一套系统的多环境镜像管理体系。

核心原则:镜像不可变

错误做法(不可变原则被违反):
  开发 push → harbor/backend/api:latest
  测试 pull → harbor/backend/api:latest
  生产 pull → harbor/backend/api:latest
  
  问题:latest 随时被覆盖,无法知道生产跑的是哪个版本

正确做法:
  每次构建产生唯一 tag(commit SHA)
  不同环境用不同的 tag 别名指向同一个镜像
  生产只运行经过完整测试的镜像
  
  镜像一旦构建,内容不变(immutable)
  只有 tag 可以新增或移动(指向不同 SHA)

一、Tag 体系设计

四类 Tag 的职责

1. 构建 Tag(不可变,长期保留)
   格式:{commit-sha}
   示例:harbor.company.com/backend/api:a1b2c3d4
   
   特点:
   - 与 git commit 一一对应
   - 永远不会被覆盖
   - 生命周期跟随版本保留策略

2. 版本 Tag(不可变,发布时创建)
   格式:v{major}.{minor}.{patch}
   示例:harbor.company.com/backend/api:v1.2.3
   
   特点:
   - 与 git tag 对应
   - 永远不会被覆盖
   - 长期保留

3. 环境 Tag(可变,指向当前部署的版本)
   格式:{env}
   示例:staging、production
   
   特点:
   - 随每次部署更新
   - 快速查看各环境当前版本
   - 不用于部署决策(只用于观察)

4. 流动 Tag(可变,指向最新状态)
   格式:latest、main、edge
   示例:harbor.company.com/backend/api:latest
   
   特点:
   - 只用于开发环境 pull(方便)
   - 生产禁止使用

实际 Tag 示例

# 一次完整发布后,Harbor 中的 tag 状态:
harbor.company.com/backend/api

  a1b2c3d4 Digest sha256:abc... commit SHA(不变)
  v1.2.3 Digest sha256:abc... 同一个镜像
  v1.2 Digest sha256:abc... 次版本别名
  v1 Digest sha256:abc... 主版本别名
  production Digest sha256:abc... 当前生产版本
  staging Digest sha256:bbb... 当前测试版本(不同版本)
  latest Digest sha256:bbb... main 分支最新

# 查看所有 tag 对应的 digest
docker buildx imagetools inspect harbor.company.com/backend/api \
    --format '{{range .Manifest.Manifests}}{{.Digest}} {{.Platform.OS}}/{{.Platform.Architecture}}{{"\n"}}{{end}}'

二、Harbor 项目空间规划

Harbor 项目结构(按团队和用途分类):

harbor.company.com/
├── backend/           ← 后端服务镜像(生产就绪)
│   ├── api
│   ├── worker
│   └── scheduler
├── frontend/          ← 前端服务镜像
│   └── web
├── infra/             ← 基础设施镜像
│   ├── nginx
│   ├── mysql
│   └── redis
├── cache/             ← BuildKit 构建缓存(不是真实镜像)
│   ├── api:buildcache
│   └── worker:buildcache
└── dev/               ← 开发测试镜像(允许宽松,定期清理)
    ├── api
    └── web

项目权限设置:
  backend/ frontend/:开发者可推送,CI 机器人可推送/拉取
  infra/:只有运维管理员可推送
  cache/:CI 机器人读写
  dev/:所有开发者读写

三、版本晋升流程

正确的版本晋升不是"每个环境分别构建",
而是"同一个镜像在不同环境晋升":

代码合并到 main

CI 构建镜像(一次)

镜像 push 到 Harbor(tag: abc1234)

自动部署到 dev 环境
    ↓ 通过冒烟测试
staging 环境拉取相同 SHA 的镜像
    ↓ 通过集成测试
生产环境拉取相同 SHA 的镜像

关键点:三个环境跑的是完全相同的镜像层(相同 SHA)
只是部署的配置不同(环境变量/资源限制/副本数)
# 版本晋升脚本
#!/bin/bash
# promote.sh — 将镜像从 staging 晋升到 production

SERVICE=$1
VERSION=$2
REGISTRY=harbor.company.com
NAMESPACE=backend

STAGING_IMAGE="${REGISTRY}/${NAMESPACE}/${SERVICE}:${VERSION}"
PROD_IMAGE="${REGISTRY}/${NAMESPACE}/${SERVICE}:${VERSION}"

echo "晋升 ${SERVICE}:${VERSION} 到生产环境"

# 验证镜像存在
docker manifest inspect ${STAGING_IMAGE} > /dev/null 2>&1 || {
    echo "❌ 镜像不存在:${STAGING_IMAGE}"
    exit 1
}

# 更新 production tag(只是添加 tag,不重新构建)
docker buildx imagetools create \
    --tag ${REGISTRY}/${NAMESPACE}/${SERVICE}:production \
    ${STAGING_IMAGE}

# 部署
docker service update \
    --image ${PROD_IMAGE} \
    --with-registry-auth \
    ${SERVICE//-/_}_api   # 转换 service 名格式

# 记录晋升日志
echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) promote ${SERVICE}:${VERSION} → production" \
    >> /var/log/promotions.log

echo "✅ 晋升完成"

四、清理策略(避免仓库无限增长)

Harbor Retention Policy(保留策略)

Harbor Web UI 配置路径:
项目 → 策略 → 添加保留规则

推荐规则(backend 项目):

规则1:保留最近 20 个 tag(按推送时间)
  作用于:除 v*/production/staging 之外的 tag
  → 保留最近 20 次 CI 构建的镜像

规则2:永久保留语义版本 tag
  匹配 tag 格式:v**
  → v1.0.0, v1.1.0, v2.0.0 等永久保留

规则3:保留最近 10 个 staging tag
  匹配 tag 格式:staging-*
  → 保留最近 10 次 staging 部署记录
# Harbor API 设置保留策略
curl -X POST https://harbor.company.com/api/v2.0/projects/backend/immutabletags \
    -H "Content-Type: application/json" \
    -u admin:Harbor12345 \
    -d '{
        "selector": {
            "kind": "doublestar",
            "decoration": "matches",
            "pattern": "v**"
        },
        "tag_selectors": [{
            "kind": "doublestar",
            "decoration": "matches",
            "pattern": "**"
        }]
    }'

# 创建标签保留规则(保留最近 20 个)
curl -X POST https://harbor.company.com/api/v2.0/projects/backend/retention \
    -H "Content-Type: application/json" \
    -u admin:Harbor12345 \
    -d '{
        "algorithm": "or",
        "rules": [
            {
                "priority": 1,
                "disabled": false,
                "action": "retain",
                "template": "latestActiveK",
                "params": {"latestN": 20},
                "tag_selectors": [{"kind": "doublestar", "decoration": "excludes", "pattern": "v**"}],
                "scope_selectors": {"repository": [{"kind": "doublestar", "decoration": "repoMatches", "pattern": "**"}]}
            },
            {
                "priority": 2,
                "disabled": false,
                "action": "retain",
                "template": "always",
                "params": {},
                "tag_selectors": [{"kind": "doublestar", "decoration": "matches", "pattern": "v**"}],
                "scope_selectors": {"repository": [{"kind": "doublestar", "decoration": "repoMatches", "pattern": "**"}]}
            }
        ],
        "trigger": {
            "kind": "Schedule",
            "settings": {"cron": "0 2 * * 0"}    
        }
    }'

本地镜像清理

# 清理脚本(在 CI runner 和生产节点定期执行)
#!/bin/bash
# docker-cleanup.sh

echo "=== $(date) Docker 清理开始 ==="

# 清理停止的容器
STOPPED=$(docker ps -aq --filter status=exited | wc -l)
if [ "$STOPPED" -gt 0 ]; then
    echo "清理 $STOPPED 个已停止的容器"
    docker container prune -f
fi

# 清理悬空镜像(<none>:<none>)
DANGLING=$(docker images -q --filter dangling=true | wc -l)
if [ "$DANGLING" -gt 0 ]; then
    echo "清理 $DANGLING 个悬空镜像"
    docker image prune -f
fi

# 清理未使用的构建缓存(保留 10GB)
docker buildx prune -f --keep-storage=10gb

# 清理未使用的网络
docker network prune -f

# 查看清理效果
docker system df
echo "=== 清理完成 ==="
# 每周日凌晨 3 点执行
echo "0 3 * * 0 root /usr/local/bin/docker-cleanup.sh >> /var/log/docker-cleanup.log 2>&1" \
    > /etc/cron.d/docker-cleanup

五、版本追踪与审计

# 查询:生产运行的是哪个 commit?
PROD_DIGEST=$(docker service inspect --format \
    '{{.Spec.TaskTemplate.ContainerSpec.Image}}' backend_api \
    | awk -F@ '{print $2}')

# 通过 Harbor API 查找对应的 commit
curl -s https://harbor.company.com/api/v2.0/projects/backend/repositories/api/artifacts \
    -u admin:Harbor12345 \
    | jq --arg digest "$PROD_DIGEST" \
    '.[] | select(.digest == $digest) | {digest: .digest, tags: [.tags[].name], created: .push_time}'

# 输出:
# {
#   "digest": "sha256:abc...",
#   "tags": ["v1.2.3", "production", "a1b2c3d4"],
#   "created": "2026-05-28T08:00:00.000Z"
# }
# → 从 tag a1b2c3d4 可以查到对应的 git commit

镜像 Label 追踪(构建时注入)

# Dockerfile 注入追踪信息
ARG GIT_COMMIT
ARG BUILD_DATE
ARG APP_VERSION

LABEL org.opencontainers.image.revision="${GIT_COMMIT}" \
      org.opencontainers.image.created="${BUILD_DATE}" \
      org.opencontainers.image.version="${APP_VERSION}" \
      org.opencontainers.image.source="https://gitlab.company.com/backend/api" \
      com.company.deployed-by="${GITLAB_USER_LOGIN:-ci}" \
      com.company.pipeline-id="${CI_PIPELINE_ID:-local}"
# 运行时查看镜像元信息
docker inspect harbor.company.com/backend/api:v1.2.3 \
    --format '{{json .Config.Labels}}' | jq .

# 输出:
# {
#   "org.opencontainers.image.revision": "a1b2c3d4e5f6...",
#   "org.opencontainers.image.created": "2026-05-28T08:00:00Z",
#   "org.opencontainers.image.version": "v1.2.3",
#   "com.company.pipeline-id": "12345"
# }

六、多环境 docker-compose 配置管理

# 项目结构
myapp/
├── compose.yml              # 通用定义
├── compose.dev.yml          # 开发环境:挂载代码,DEBUG 模式
├── compose.staging.yml      # 测试环境:固定 SHA,真实数据库
├── compose.prod.yml         # 生产环境:资源限制,Secrets
├── .env                     # 默认变量(开发)
├── .env.staging             # 测试变量
└── .env.prod                # 生产变量(不提交 git)
# 部署脚本(不同环境用不同配置)
#!/bin/bash
# deploy.sh <env> <version>

ENV=$1
VERSION=$2

case $ENV in
  dev)
    docker compose -f compose.yml -f compose.dev.yml up -d
    ;;
  staging)
    export APP_VERSION=$VERSION
    docker compose \
        -f compose.yml \
        -f compose.staging.yml \
        --env-file .env.staging \
        up -d
    ;;
  prod)
    export APP_VERSION=$VERSION
    docker compose \
        -f compose.yml \
        -f compose.prod.yml \
        --env-file .env.prod \
        up -d --no-deps api    # 只更新 api,不动数据库
    ;;
  *)
    echo "用法: $0 <dev|staging|prod> <version>"
    exit 1
    ;;
esac

echo "✅ ${ENV} 环境部署完成 (version: ${VERSION})"

小结

多环境镜像管理体系的四个核心:

Tag 不混用:commit SHA 用于追踪,语义版本用于发布,环境 tag(staging/production)用于观察,latest 只在开发用。

镜像不重建:同一个 commit 产生的镜像在所有环境用同一个 SHA,不同环境的差异只在配置(环境变量/资源/副本数),不在镜像。

清理自动化:Harbor 保留策略 + 本地 cron 清理,保留语义版本(v*)+ 最近 N 次构建,定期 GC 避免磁盘无限增长。

全链路可追溯:Dockerfile LABEL 注入 commit SHA + pipeline ID,任何时候都能从运行中的容器找到对应的 git commit 和 CI 流水线记录。