WEBKT

拒绝过度设计:中小团队微服务多环境 CI/CD 落地实践

27 0 0 0

很多中小团队在从单体架构转向微服务时,最先崩溃的往往不是业务代码,而是发布流水线。

当服务拆分到十几个甚至几十个后,如果还沿用老一套的部署方式,很快就会遇到以下痛点:

  • 配置文件满天飞:每个微服务在测试、预发、生产环境的配置各不相同,稍有不慎就拿测试环境的配置连了生产的数据库。
  • 构建时间慢如牛:每个环境都重新编译、打包、构建一次镜像,服务器 CPU 天天拉满,研发大把时间浪费在等构建上。
  • 发布像拆弹:没有统一的发布控制,全凭研发人员在群里喊:“我准备上测试了,谁别动代码!”

中小团队(研发人数在 10-50 人左右)没有专门的、庞大的运维团队,无法照搬大厂那套复杂的 Spinnaker 或 KubeVela 平台。我们需要的是一套轻量、规范、改造成本低且足够安全的多环境 CI/CD 方案。

本文将结合实际落地经验,聊聊中小团队如何基于 GitLab CI(或 GitHub Actions)与 K8s/Docker 快速搭建一套多环境流水线。


核心原则:一次构建,多环境晋级

这是微服务 CI/CD 最底层、最重要的一条原则:Build Once, Run Anywhere(一次构建,到处运行)。

绝对不要针对测试环境构建一次镜像,针对生产环境又重新构建一次。因为你无法保证重新构建时,拉取的依赖包版本是否完全一致,这会引入极大的不确定性。

正确的工作流:

  1. 在测试阶段(Dev/Test)进行代码编译,构建出 Docker 镜像,并打上唯一标识(如 Commit SHA)。
  2. 测试环境部署该镜像,测试通过。
  3. 预发环境(Staging)直接拉取同一个镜像,仅通过修改环境变量或配置中心进行发布,进行预发验证。
  4. 生产环境(Prod)同样拉取该镜像,点击确认后上线。

规范先行:分支管理与环境对应关系

没有规范的分支管理,CI/CD 流水线就是无源之水。对于中小团队,推荐采用简化版的 GitFlow 或 GitHub Flow:

Git 分支 对应环境 触发方式 权限控制
develop 测试环境 (Test) 代码合并/提交后自动触发 研发人员可提交
release/* 预发环境 (Staging) 合并到 release 分支自动触发 技术负责人/QA 审批合并
main / tags 生产环境 (Prod) 打 Tag(如 v1.2.0)手动触发 仅技术负责人有权限

流水线整体设计图

一条完整的微服务多环境流水线,大致可以划分为以下几个阶段:

[代码提交] ──> [CI 阶段: 编译/单元测试] ──> [构建镜像并推送到私有仓库]
                                                   │
                                                   ├──> [CD 阶段1: 自动部署到测试环境]
                                                   │
                                                   └──> [CD 阶段2: 自动/半自动部署到预发环境]
                                                   │
                                                   └──> [CD 阶段3: 手动确认部署到生产环境]

落地实践:基于 GitLab CI 的极简实现

我们以一个典型的 Spring Boot 或 Node.js 微服务为例,看看 .gitlab-ci.yml 应该怎么写。这里刻意避开了复杂的 Helm 编排,采用最直观的 K8s 部署文件替换或环境变量注入方式。

1. 变量准备

在 GitLab 的 Settings -> CI/CD -> Variables 中,配置好敏感信息:

  • REGISTRY_USER / REGISTRY_PASSWORD(镜像仓库账号密码)
  • KUBE_CONFIG_TEST / KUBE_CONFIG_PROD(不同环境的 K8s 凭证,用于 kubectl 调度)

2. 流水线配置文件示例

stages:
  - build      # 编译与镜像构建
  - deploy_test  # 部署测试环境
  - deploy_pre   # 部署预发环境
  - deploy_prod  # 部署生产环境

variables:
  IMAGE_NAME: "registry.cn-hangzhou.aliyuncs.com/my-project/$CI_PROJECT_NAME"
  IMAGE_TAG: "$CI_COMMIT_SHORT_SHA"

# 缓存设置,加速 Maven/npm 构建
cache:
  paths:
    - .m2/repository
    - node_modules/

# 1. 构建阶段(所有分支提交都会触发,但我们通过 rules 限制)
build_image:
  stage: build
  image: docker:20.10.16
  services:
    - docker:20.10.16-dind
  script:
    - docker login -u "$REGISTRY_USER" -p "$REGISTRY_PASSWORD" registry.cn-hangzhou.aliyuncs.com
    - docker build -t $IMAGE_NAME:$IMAGE_TAG .
    - docker push $IMAGE_NAME:$IMAGE_TAG
  rules:
    - if: '$CI_COMMIT_BRANCH == "develop"'
    - if: '$CI_COMMIT_BRANCH =~ /^release\//'
    - if: '$CI_COMMIT_TAG'

# 2. 部署到测试环境(自动触发)
deploy_to_test:
  stage: deploy_test
  image: bitnami/kubectl:latest
  script:
    - echo "$KUBE_CONFIG_TEST" > kubeconfig
    - export KUBECONFIG=kubeconfig
    - kubectl set image deployment/$CI_PROJECT_NAME $CI_PROJECT_NAME=$IMAGE_NAME:$IMAGE_TAG -n test
  rules:
    - if: '$CI_COMMIT_BRANCH == "develop"'

# 3. 部署到预发环境(合并到 release 分支自动触发)
deploy_to_pre:
  stage: deploy_pre
  image: bitnami/kubectl:latest
  script:
    - echo "$KUBE_CONFIG_PRE" > kubeconfig # 假设预发和生产在同一个集群,或者不同的 kubeconfig
    - export KUBECONFIG=kubeconfig
    - kubectl set image deployment/$CI_PROJECT_NAME $CI_PROJECT_NAME=$IMAGE_NAME:$IMAGE_TAG -n pre
  rules:
    - if: '$CI_COMMIT_BRANCH =~ /^release\//'

# 4. 部署到生产环境(打 Tag 时触发,必须人工点击确认)
deploy_to_prod:
  stage: deploy_prod
  image: bitnami/kubectl:latest
  script:
    - echo "$KUBE_CONFIG_PROD" > kubeconfig
    - export KUBECONFIG=kubeconfig
    - kubectl set image deployment/$CI_PROJECT_NAME $CI_PROJECT_NAME=$IMAGE_NAME:$IMAGE_TAG -n prod
  rules:
    - if: '$CI_COMMIT_TAG'
  when: manual # 关键:设置为手动触发,必须人工审核点击

解决中小团队的三大核心痛点

1. 配置文件如何管理?(避开配置硬编码)

微服务在不同环境下,数据库地址、Redis 密码、第三方 API Key 都不一样。

  • 反面教材:在代码里写 application-dev.ymlapplication-prod.yml,然后打包时指定 profile。这样会导致生产环境的敏感密码直接暴露在 Git 仓库中。
  • 中小团队推荐方案
    • 轻量级:利用 K8s 的 ConfigMapSecret。在 CI 脚本中,通过环境变量(Environment Variables)注入到容器中。
    • 进阶级:引入阿里的 Nacos 或携程的 Apollo。在 K8s 部署文件中只配置一个 Nacos 的地址和命名空间 ID(Namespace),所有的具体配置都在 Nacos 控制台按环境隔离管理。

2. 构建速度太慢,怎么优化?

微服务数量多起来后,如果每个服务构建都要 5-10 分钟,研发体验会极差。

  • 合理利用 Docker 缓存:在 Dockerfile 中,把不经常变动的步骤往上放(比如下载依赖包),把经常变动的代码拷贝(COPY . .)放到最后。
  • 多阶段构建(Multi-stage builds)
    # 第一阶段:编译
    FROM maven:3.8-openjdk-11 AS builder
    WORKDIR /app
    COPY pom.xml .
    RUN mvn dependency:go-offline
    COPY src ./src
    RUN mvn clean package -DskipTests
    
    # 第二阶段:运行(不含任何编译工具,镜像体积缩减 80%)
    FROM openjdk:11-jre-slim
    COPY --from=builder /app/target/*.jar app.jar
    ENTRYPOINT ["java", "-jar", "/app.jar"]
    
    这样不仅构建快,而且生成的镜像只有几十兆,分发极快。

3. 如何实现优雅无缝回滚?

在生产环境,一旦新版本出现故障,必须能在秒级完成回滚。

  • 因为我们采用了 Image Tag 与 Git Commit SHA 绑定 的策略,每个历史版本在镜像仓库中都有唯一的镜像标签。
  • 如果遇到紧急故障,直接在 K8s 中执行一条命令,或者在 GitLab 的 CI 历史记录里,找到上一个稳定版本的 Job,点击 Retry(重试),即可在 10 秒内恢复到上一版本。

总结

对于中小团队,微服务 CI/CD 的设计核心在于**“实用至上,拒绝炫技”**。

在前期,不需要一步到位去搞复杂的 GitOps(如 ArgoCD)或蓝绿发布。先用好 GitLab CI + Docker + K8s 原生命令,把“一次构建、环境晋级、配置分离、手动确认”这套骨架搭建起来。随着团队规模和业务体量的增长,再平滑地往 Service Mesh 或 GitOps 方向演进,才是成本最低、最不容易踩坑的路线。

老陈聊架构 微服务CICDGitLab CI

评论点评