18.4.4 制品管理与版本控制

制品管理与版本控制#

制品管理是CI流程的闭环环节,目标是将构建产物以可追溯、可复用、可回滚的方式沉淀到统一仓库。结合版本控制体系,确保每一次构建都能定位到具体源码、依赖、环境与配置,从而支撑发布与问题排查。

原理草图:制品与版本关联

文章图片

制品类型与范围
- 二进制制品:Jar/War、可执行文件、镜像、压缩包。
- 配置制品:配置文件模板、Helm Chart、清单文件。
- 测试制品:测试报告、覆盖率、静态扫描结果。
- 元数据:构建号、Git提交、依赖清单、构建时间、构建环境。

制品仓库与命名规范
- 使用统一制品仓库(如 Nexus、Artifactory、Harbor)集中管理。
- 命名建议:<项目名>-<环境>-<版本号>-<构建号>
- 采用不可变制品策略,禁止覆盖同版本制品。
- 区分快照与发布版本:1.2.3-SNAPSHOT1.2.3


安装与初始化(Nexus 示例)#

安装 Nexus 3

# 1) 下载并解压
cd /opt
wget https://download.sonatype.com/nexus/3/nexus-3.68.1-02-unix.tar.gz
tar -zxf nexus-3.68.1-02-unix.tar.gz
ln -s nexus-3.68.1-02 nexus

# 2) 创建运行用户
useradd -r -s /sbin/nologin nexus
chown -R nexus:nexus /opt/nexus /opt/sonatype-work

# 3) 配置运行
sed -i 's/^#run_as_user=.*/run_as_user="nexus"/' /opt/nexus/bin/nexus.rc

# 4) 启动
/opt/nexus/bin/nexus start
# 预期:访问 http://<IP>:8081

创建 Maven hosted 仓库(REST 简化示例)

# 需管理员账号,首次登录在 /opt/sonatype-work/nexus3/admin.password 获取
curl -u admin:YOUR_PASS -X POST \
  http://127.0.0.1:8081/service/rest/v1/repositories/maven/hosted \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "maven-releases",
    "online": true,
    "storage": {"blobStoreName": "default", "strictContentTypeValidation": true, "writePolicy": "ALLOW_ONCE"},
    "maven": {"versionPolicy": "RELEASE", "layoutPolicy": "PERMISSIVE"}
  }'

版本控制策略与示例#

语义化版本与分支策略
- 版本号:主版本.次版本.修订号
- 发布版本由 Tag 触发,快照版本由分支触发
- 建议分支:main/master 发布、develop 集成、feature/* 开发

Git Tag 与制品对应

# 创建发布标签并推送
git tag -a v1.2.3 -m "release 1.2.3"
git push origin v1.2.3
# 预期:CI 监听 tag 触发发布构建

制品元数据与可追溯性(build-info)#

构建产物写入元数据示例

# build-info.json(与制品同目录)
cat > build-info.json <<'EOF'
{
  "project": "order-service",
  "version": "1.2.3",
  "buildNumber": "102",
  "gitCommit": "a1b2c3d4",
  "gitBranch": "main",
  "buildTime": "2024-05-12T10:20:30Z",
  "builder": "jenkins@ci-01",
  "dependencies": [
    "org.springframework.boot:spring-boot-starter-web:3.2.0"
  ]
}
EOF

制品上传与下载(命令完整示例)#

上传制品(curl + Nexus REST)

# 生成制品
mkdir -p dist
echo "hello" > dist/order-service-1.2.3-102.jar
sha256sum dist/order-service-1.2.3-102.jar > dist/order-service-1.2.3-102.jar.sha256

# 上传制品与校验
curl -u admin:YOUR_PASS --upload-file dist/order-service-1.2.3-102.jar \
  "http://127.0.0.1:8081/repository/maven-releases/com/example/order-service/1.2.3/order-service-1.2.3-102.jar"

curl -u admin:YOUR_PASS --upload-file dist/order-service-1.2.3-102.jar.sha256 \
  "http://127.0.0.1:8081/repository/maven-releases/com/example/order-service/1.2.3/order-service-1.2.3-102.jar.sha256"

下载制品(CD 使用)

mkdir -p /opt/releases/order-service
cd /opt/releases/order-service

curl -O "http://127.0.0.1:8081/repository/maven-releases/com/example/order-service/1.2.3/order-service-1.2.3-102.jar"
curl -O "http://127.0.0.1:8081/repository/maven-releases/com/example/order-service/1.2.3/order-service-1.2.3-102.jar.sha256"

# 校验
sha256sum -c order-service-1.2.3-102.jar.sha256
# 预期:order-service-1.2.3-102.jar: OK

Jenkins Pipeline 示例(含制品与元数据)#

pipeline {
  agent any
  environment {
    APP_NAME = "order-service"
    VERSION = "1.2.3"
    BUILD_NO = "${env.BUILD_NUMBER}"
    NEXUS_URL = "http://127.0.0.1:8081"
    REPO = "maven-releases"
  }
  stages {
    stage('Build') {
      steps {
        sh '''
          mkdir -p dist
          echo "hello" > dist/${APP_NAME}-${VERSION}-${BUILD_NO}.jar
        '''
      }
    }
    stage('Build-Info') {
      steps {
        sh '''
cat > dist/build-info.json <<EOF
{
  "project": "${APP_NAME}",
  "version": "${VERSION}",
  "buildNumber": "${BUILD_NO}",
  "gitCommit": "$(git rev-parse HEAD)",
  "gitBranch": "$(git rev-parse --abbrev-ref HEAD)",
  "buildTime": "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
}
EOF
        '''
      }
    }
    stage('Upload') {
      steps {
        withCredentials([usernamePassword(credentialsId: 'nexus-cred', usernameVariable: 'U', passwordVariable: 'P')]) {
          sh '''
            curl -u $U:$P --upload-file dist/${APP_NAME}-${VERSION}-${BUILD_NO}.jar \
              ${NEXUS_URL}/repository/${REPO}/com/example/${APP_NAME}/${VERSION}/${APP_NAME}-${VERSION}-${BUILD_NO}.jar

            curl -u $U:$P --upload-file dist/build-info.json \
              ${NEXUS_URL}/repository/${REPO}/com/example/${APP_NAME}/${VERSION}/build-info-${BUILD_NO}.json
          '''
        }
      }
    }
  }
}

安全与合规(示例)#

GPG 签名与校验

# 生成签名
gpg --batch --yes --detach-sign --armor dist/order-service-1.2.3-102.jar

# 校验签名
gpg --verify dist/order-service-1.2.3-102.jar.asc dist/order-service-1.2.3-102.jar

常见排错与诊断#

1) 上传 401/403

# 检查权限与凭据
curl -u admin:YOUR_PASS http://127.0.0.1:8081/service/rest/v1/status
# 预期:返回运行状态 JSON

2) 制品被覆盖

# 仓库 writePolicy 应为 ALLOW_ONCE(禁止覆盖)
curl -u admin:YOUR_PASS http://127.0.0.1:8081/service/rest/v1/repositories/maven/hosted/maven-releases

3) 校验失败

# 重新生成与比对
sha256sum dist/order-service-1.2.3-102.jar
cat dist/order-service-1.2.3-102.jar.sha256

4) Jenkins 构建号与版本不一致

# 检查 Pipeline 环境变量
echo $BUILD_NUMBER
# 预期:与制品命名中的构建号一致

练习#

  1. 在本地安装 Nexus,创建 maven-snapshotsmaven-releases 仓库并验证上传/下载。
  2. 使用 Git Tag 触发 Jenkins 发布构建,并在 Nexus 中找到对应版本制品与 build-info。
  3. 将制品校验加入 Pipeline,失败时阻断发布。
  4. 模拟上传重复版本,验证仓库拒绝覆盖策略是否生效。