GitLab CI/CD 多环境部署与生产实践

从 dev 一路跑到 prod——点个按钮就上线 📖 前置阅读:本文假设读者已搭建 GitLab CI/CD 流水线(编译 → 测试 → 扫描 → 构建镜像 → 推送 Harbor),并已将微服务部署在 Kubernetes 上。如果还不熟悉,建议先阅读 搭建与 Pipeline 语法精讲 和 流水线实战。 一、⚡ 镜像推到 Harbor 了——但你还得手动 SSH 上去 kubectl apply——这叫啥 CI/CD? 前两篇搭好了 CI/CD Pipeline——代码 push → 编译 → 测试 → 扫描 → 构建镜像 → 推送到 Harbor。 但 Pipeline 到这里就停了——后面的部署还是人来操作: 当前状态(半自动): ✅ 代码 push → 自动编译、测试、扫描、构建镜像、推送 Harbor ❌ 然后——SSH 到跳板机 → kubectl set image → 看有没有报错 ❌ 然后——curl 验证——发现不对——kubectl rollout undo ❌ 然后——staging 和 prod 没有隔离——改了什么全凭记忆力 → CI 有了——CD 没做——半吊子自动化 真正的 CD——镜像推送到 Harbor 后——自动部署到 dev——验证通过——自动部署到 staging——人工审批——部署到 prod。人对生产的操作只剩下"点一个按钮"。 ...

十二月 26, 2022 · 9 分钟 · 1903 字 · yaomingye

GitLab CI/CD 流水线实战——编译、扫描、构建镜像、推送仓库

代码 push 之后——五道关卡自动跑完 📖 前置阅读:本文假设读者已搭建 GitLab + Runner 并理解 .gitlab-ci.yml 基础语法(stages/jobs/artifacts/cache/rules)。如果还不熟悉,建议先阅读 GitLab CI/CD 搭建与 Pipeline 语法精讲。 一、⚡ 编译过了——但你敢直接部署吗?代码质量谁保证? 上一篇文章的 Pipeline 只做了编译和测试——但真正的 CI/CD 不止这些: 真正的 CI/CD 流水线要回答 5 个问题: ① 编译成功了吗? → mvn compile ② 测试通过了吗? → mvn test + 覆盖率报告 ③ 代码质量合格吗? → SonarQube 扫描 + Quality Gate ④ 镜像构建成功了吗? → docker build ⑤ 镜像推送到仓库了吗? → docker push → Harbor 这 5 步全自动——缺一步都不能算 CI/CD 这篇的目标——搭一条完整的流水线:代码 push → 自动跑完上述 5 步——任何一个环节失败——Pipeline 变红——阻止部署。 二、🏗️ 完整的 Pipeline 架构 flowchart LR Push["git push"] --> Compile["① 编译\nmvn compile"] Compile --> Test["② 单元测试\nmvn test\n+ 覆盖率报告"] Test --> SonarQube["③ 代码扫描\nSonarQube\n+ Quality Gate"] SonarQube --> Package["④ 打包\nmvn package"] Package --> DockerBuild["⑤ 构建镜像\ndocker build"] DockerBuild --> HarborPush["⑥ 推送仓库\ndocker push\n→ Harbor"] HarborPush --> Notify["⑦ 通知\n企业微信/钉钉"] classDef style_SonarQube fill:#450a0a,stroke:#dc2626,stroke-width:2px,color:#fecaca; classDef style_HarborPush fill:#0f172a,stroke:#3b82f6,stroke-width:2px,color:#bfdbfe; class SonarQube style_SonarQube; class HarborPush style_HarborPush;``` ## 三、🔧 基础设施——SonarQube + Harbor 搭建 ### 3.1 Docker Compose——加 SonarQube 和 Harbor ```yaml # 在上一篇文章的 docker-compose.yml 基础上加两个服务 version: '3.8' services: # ===== GitLab + Runner(同上一篇——省略)===== # ... # ===== SonarQube——代码质量扫描 ===== sonarqube: image: sonarqube:10.3.0-community container_name: sonarqube environment: SONAR_JDBC_URL: jdbc:postgresql://sonarqube-db:5432/sonarqube SONAR_JDBC_USERNAME: sonar SONAR_JDBC_PASSWORD: sonar123 ports: - "9000:9000" volumes: - sonarqube-data:/opt/sonarqube/data - sonarqube-extensions:/opt/sonarqube/extensions depends_on: - sonarqube-db sonarqube-db: image: postgres:15-alpine container_name: sonarqube-db environment: POSTGRES_USER: sonar POSTGRES_PASSWORD: sonar123 POSTGRES_DB: sonarqube volumes: - sonarqube-db-data:/var/lib/postgresql/data # ===== Harbor——私有 Docker 镜像仓库 ===== # Harbor 官方推荐用 docker-compose 独立部署——这里简化 # 生产环境参考 https://goharbor.io/docs harbor: image: goharbor/registry-photon:v2.9.0 container_name: harbor-registry ports: - "5000:5000" volumes: - harbor-data:/var/lib/registry volumes: sonarqube-data: sonarqube-extensions: sonarqube-db-data: harbor-data: 3.2 SonarQube 初始化——创建项目 Token ① 浏览器打开 http://gitlab.local:9000 ② 默认登录:admin / admin——首次强制修改密码 ③ Administration → Projects → Create Project → Project key: order-service → Project name: order-service → 创建 ④ 创建 Token:My Account → Security → Generate Token → Token name: gitlab-ci → 复制 Token——后续要放在 GitLab CI/CD 变量中 ⑤ 在 GitLab 中配置 SonarQube 变量: GitLab → 项目 → Settings → CI/CD → Variables 添加: SONAR_HOST_URL = http://sonarqube:9000 SONAR_TOKEN = squ_xxxxxxxxxxxxxxxxxxxxxxxxxx ← 刚才复制的 Token 3.3 Maven 项目的 SonarQube 配置 <!-- pom.xml——加 JaCoCo 覆盖率插件 + SonarQube 插件 --> <build> <plugins> <!-- JaCoCo——代码覆盖率 --> <plugin> <groupId>org.jacoco</groupId> <artifactId>jacoco-maven-plugin</artifactId> <version>0.8.11</version> <executions> <execution> <goals> <goal>prepare-agent</goal> </goals> </execution> <execution> <id>report</id> <phase>test</phase> <goals> <goal>report</goal> </goals> </execution> </executions> </plugin> </plugins> </build> <properties> <!-- SonarQube 配置 --> <sonar.host.url>${env.SONAR_HOST_URL}</sonar.host.url> <sonar.login>${env.SONAR_TOKEN}</sonar.login> <sonar.projectKey>order-service</sonar.projectKey> <sonar.projectName>order-service</sonar.projectName> <sonar.java.binaries>target/classes</sonar.java.binaries> <sonar.coverage.jacoco.xmlReportPaths>target/site/jacoco/jacoco.xml</sonar.coverage.jacoco.xmlReportPaths> </properties> 四、📝 完整的 .gitlab-ci.yml——从编译到推送镜像 4.1 完整 Pipeline 定义 # order-service/.gitlab-ci.yml # 完整的 CI/CD Pipeline——5 个阶段 stages: - compile # ① 编译 - test # ② 测试 + 覆盖率 - quality # ③ SonarQube 扫描 - package # ④ 打包 + 构建镜像 - push # ⑤ 推送镜像到 Harbor # ===== 全局变量 ===== variables: MAVEN_OPTS: "-Dmaven.repo.local=$CI_PROJECT_DIR/.m2/repository" MAVEN_CLI_OPTS: "-B -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=WARN" # Harbor 地址——在 GitLab CI/CD Variables 中配置 HARBOR_URL: "harbor.local:5000" IMAGE_NAME: "$HARBOR_URL/order-service" # ===== 全局缓存——Maven 依赖 ===== cache: key: maven-${CI_COMMIT_REF_SLUG} paths: - .m2/repository/ policy: pull-push # ===== Stage 1: 编译 ===== compile: stage: compile image: maven:3.9-eclipse-temurin-17 script: - mvn $MAVEN_CLI_OPTS compile artifacts: paths: - target/classes/ expire_in: 1 hour tags: - docker # ===== Stage 2: 单元测试 + 覆盖率 ===== unit-test: stage: test image: maven:3.9-eclipse-temurin-17 script: - mvn $MAVEN_CLI_OPTS test jacoco:report artifacts: when: always paths: - target/surefire-reports/ - target/site/jacoco/ # ← JaCoCo 报告——给 SonarQube 用 expire_in: 7 days reports: junit: target/surefire-reports/TEST-*.xml # ← GitLab 自动展示测试结果 coverage: '/Total.*?([0-9]{1,3})%/' # ← GitLab 自动展示覆盖率百分比 tags: - docker # ===== Stage 3: SonarQube 代码扫描 ===== sonarqube-check: stage: quality image: maven:3.9-eclipse-temurin-17 script: - mvn $MAVEN_CLI_OPTS sonar:sonar -Dsonar.host.url=$SONAR_HOST_URL -Dsonar.login=$SONAR_TOKEN # 只在 MR 或 main 分支扫描——feature 分支不扫(浪费 SonarQube 资源) rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" - if: $CI_COMMIT_BRANCH == "main" tags: - docker # ===== Stage 4: 打包 ===== package: stage: package image: maven:3.9-eclipse-temurin-17 script: - mvn $MAVEN_CLI_OPTS package -DskipTests artifacts: paths: - target/*.jar expire_in: 1 hour tags: - docker # ===== Stage 5: 构建 Docker 镜像并推送到 Harbor ===== docker-build-push: stage: push image: docker:24-dind # ← Docker-in-Docker 镜像——在容器内跑 Docker services: - docker:24-dind # ← 启动 Docker daemon sidecar before_script: - apk add --no-cache bash # Alpine 需要 bash # 等待 Docker daemon 启动 - until docker info > /dev/null 2>&1; do sleep 1; done script: # ① 构建镜像——用 commit SHA 作为 tag - docker build -t $IMAGE_NAME:$CI_COMMIT_SHORT_SHA . # ② 打标签——如果 main 分支——打 latest;如果有 tag——打 release 版本 - | if [ "$CI_COMMIT_BRANCH" = "main" ]; then docker tag $IMAGE_NAME:$CI_COMMIT_SHORT_SHA $IMAGE_NAME:latest fi - | if [ -n "$CI_COMMIT_TAG" ]; then docker tag $IMAGE_NAME:$CI_COMMIT_SHORT_SHA $IMAGE_NAME:$CI_COMMIT_TAG fi # ③ 登录 Harbor——用户名密码配在 GitLab CI/CD Variables 中 - echo "$HARBOR_PASSWORD" | docker login $HARBOR_URL -u "$HARBOR_USERNAME" --password-stdin # ④ 推送所有标签 - docker push $IMAGE_NAME:$CI_COMMIT_SHORT_SHA - | if [ "$CI_COMMIT_BRANCH" = "main" ]; then docker push $IMAGE_NAME:latest fi - | if [ -n "$CI_COMMIT_TAG" ]; then docker push $IMAGE_NAME:$CI_COMMIT_TAG fi tags: - docker 4.2 Dockerfile——配合 CI/CD 的镜像构建 # order-service/Dockerfile # 多阶段构建——分离构建和运行——最终镜像只含 JRE # ===== Stage 1: 构建——用 Maven 编译 ===== FROM maven:3.9-eclipse-temurin-17 AS builder WORKDIR /build COPY pom.xml . # 先下载依赖——利用 Docker 缓存层——pom.xml 不变就不重新下载 RUN mvn dependency:go-offline -B COPY src ./src RUN mvn package -DskipTests -B # ===== Stage 2: 运行——只含 JRE——镜像小 ===== FROM eclipse-temurin:17-jre-alpine WORKDIR /app # 创建非 root 用户——安全最佳实践 RUN addgroup -S appgroup && adduser -S appuser -G appgroup # 从构建阶段复制 jar COPY --from=builder /build/target/*.jar app.jar # 切换到非 root 用户 USER appuser # Health check——K8s 会调用这个 HEALTHCHECK --interval=30s --timeout=5s --retries=3 \ CMD wget -qO- http://localhost:8081/actuator/health || exit 1 EXPOSE 8081 ENTRYPOINT ["java", "-jar", "app.jar"] ⚠️ 新手提示:上面的 Dockerfile 是"CI 内编译"的方式——jar 包在 CI Pipeline 中由 Maven 打好——Dockerfile 只需要 COPY jar。还有一种方式是"Dockerfile 内编译"——CI 不编译——Dockerfile 用多阶段构建完成编译。两种方式的区别: ...

十二月 25, 2022 · 8 分钟 · 1615 字 · yaomingye

GitLab CI/CD 搭建与 Pipeline 语法精讲

把 15 步手动操作变成一次 git push 一、⚡ 周五下午 5 点上线——你手动执行了 15 步操作——到第 12 步出错了 回想一下你现在的发布流程: 发布一个微服务的流程: ① git pull latest ② mvn clean package -DskipTests("测试先跳过——着急") ③ 手动改 application-prod.yml 中的配置("这个值上次没改对") ④ docker build -t order-service:v1.2.3 . ⑤ docker tag order-service:v1.2.3 harbor.internal/order-service:v1.2.3 ⑥ docker push harbor.internal/order-service:v1.2.3 ⑦ ssh root@k8s-master ⑧ kubectl set image deployment/order-service order-service=harbor.internal/order-service:v1.2.3 ⑨ kubectl rollout status deployment/order-service ⑩ curl 验证——啊——404——服务没起来 ⑪ kubectl logs——发现是 application.yml 中的 Nacos 地址配错了 ⑫ kubectl rollout undo——回滚 ⑬ 改配置——重新来——docker build + push + deploy ⑭ 又发现 product-service 没同步上线——接口报错了 ⑮ 告警响了——用户已经在群里骂了 → 每次发布都像拆炸弹——不知道哪一步会出问题 CI/CD 要解决的就是:把人从 15 步中解放出来——每次 git push——自动编译、自动测试、自动构建镜像、自动部署——15 步变成 1 步。 ...

十二月 24, 2022 · 9 分钟 · 1883 字 · yaomingye
Cat Radio