周五晚上改了个 bug。提交代码、本地构建镜像、push 到 Registry、SSH 到 K3s、kubectl set image……一气呵成。然后发现镜像 tag 写错了,重新来。如果这些步骤能自动跑该多好?
CI/CD 的本质:把"代码到部署"之间所有手动步骤变成自动流水线。
CI vs CD
代码提交 → [CI] → 可部署的产物 → [CD] → 生产环境 │ │ ├ 构建镜像 ├ 部署到 K8s ├ 运行测试 ├ 健康检查 └ 安全扫描 └ 流量切换
架构
GitLab CI/CD 由两部分组成:
HomeLab 场景下,Server 可以用你现有的 Git 服务,Runner 装在 K3s 集群里。
安装 GitLab Runner
# Ubuntu/Debian curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh | sudo bash sudo apt install gitlab-runner # 注册 Runner sudo gitlab-runner register # 输入 GitLab URL: https://gitlab.example.com/ # 输入 Token: 从 GitLab → Settings → CI/CD → Runners 获取 # 输入描述: homelab-runner # 输入 tag: k3s,deploy # 选择 executor: shell(简单) 或 docker(隔离) # 验证 sudo gitlab-runner verify sudo gitlab-runner status
.gitlab-ci.yml:流水线定义
在项目根目录创建 .gitlab-ci.yml。这是一个完整示例:
# 定义阶段 stages: - build - test - scan - deploy # 定义变量 variables: IMAGE_NAME: registry.local:5000/myapp IMAGE_TAG: $CI_COMMIT_SHORT_SHA # 构建阶段 build: stage: build image: docker:24 services: - docker:24-dind script: - docker build -t $IMAGE_NAME:$IMAGE_TAG . - docker push $IMAGE_NAME:$IMAGE_TAG - docker tag $IMAGE_NAME:$IMAGE_TAG $IMAGE_NAME:latest - docker push $IMAGE_NAME:latest only: - main tags: - docker # 测试阶段 test: stage: test image: python:3.12-slim script: - pip install -r requirements.txt - pytest --junitxml=report.xml artifacts: reports: junit: report.xml only: - main # 安全扫描 scan: stage: scan image: aquasec/trivy:latest script: - trivy image --exit-code 1 --severity HIGH,CRITICAL $IMAGE_NAME:$IMAGE_TAG allow_failure: true only: - main # 部署阶段 deploy: stage: deploy script: - kubectl set image deployment/myapp myapp=$IMAGE_NAME:$IMAGE_TAG -n myapp - kubectl rollout status deployment/myapp -n myapp only: - main tags: - k3s
这个 Pipeline 做了 4 件事:
git push 到 main 分支后自动触发,全程无人工干预。
关键语法详解
阶段和作业
stages: - build # 先执行 - test # build 通过后执行 - deploy # test 通过后执行 build-app: stage: build script: - echo "构建中..." build-docs: stage: build # 同一阶段的作业并行执行 script: - echo "构建文档..."
同 stage 内的作业并行执行,不同 stage 串行执行。
条件控制
deploy_prod: stage: deploy script: - kubectl apply -f k8s/prod/ rules: - if: $CI_COMMIT_BRANCH == "main" when: on_success - if: $CI_PIPELINE_SOURCE == "merge_request_event" when: manual # MR 触发时需要手动点击 - when: never # 其他情况不执行
缓存和制品
build: script: - npm ci - npm run build cache: key: ${CI_COMMIT_REF_SLUG} paths: - node_modules/ # 缓存依赖,下次构建更快 artifacts: paths: - dist/ # 构建产物,下载保留 7 天 expire_in: 7 days
环境变量管理
不要把密码写在 .gitlab-ci.yml 里。用 GitLab 的 CI/CD 变量:
GitLab → Settings → CI/CD → Variables Key: REGISTRY_PASSWORD Value: ***** Type: Variable Protected: true # 只有受保护分支可用 Masked: true # 日志中不显示
在 Pipeline 中直接用:
script: - docker login registry.local:5000 -u $REGISTRY_USER -p $REGISTRY_PASSWORD
实战:完整部署流水线
以下是一个 HomeLab 应用的完整 CI/CD 配置:
stages: - lint - build - deploy variables: APP_NAME: vaultwarden IMAGE: registry.local:5000/$APP_NAME TAG: ${CI_COMMIT_SHORT_SHA} # 代码检查 lint: stage: lint image: hadolint/hadolint:latest-debian script: - hadolint Dockerfile allow_failure: true # 构建镜像 build: stage: build image: docker:24 services: - docker:24-dind before_script: - docker login -u $REGISTRY_USER -p $REGISTRY_PASSWORD registry.local:5000 script: - docker build -t $IMAGE:$TAG -t $IMAGE:latest . - docker push $IMAGE:$TAG - docker push $IMAGE:latest after_script: - docker logout registry.local:5000 # 部署到 K3s deploy_staging: stage: deploy environment: name: staging url: https://staging.homelab.local script: - kubectl set image deployment/$APP_NAME $APP_NAME=$IMAGE:$TAG -n vaultwarden - kubectl rollout status deployment/$APP_NAME -n vaultwarden rules: - if: $CI_COMMIT_BRANCH == "develop" deploy_prod: stage: deploy environment: name: production url: https://vault.homelab.local script: - kubectl set image deployment/$APP_NAME $APP_NAME=$IMAGE:$TAG -n vaultwarden - kubectl rollout status deployment/$APP_NAME -n vaultwarden rules: - if: $CI_COMMIT_BRANCH == "main" when: manual # 生产环境需要手动确认
这个配置实现了:
配合 ArgoCD:GitOps 模式
如果用 ArgoCD,CI 只需要构建镜像和更新 Git 仓库中的版本号:
deploy: stage: deploy script: # 更新 Git 仓库中的镜像版本 - git clone https://token:$GIT_TOKEN@gitlab.example.com/homelab/k8s-manifests.git - cd k8s-manifests - sed -i "s|image:.*|image: $IMAGE:$TAG|" apps/vaultwarden/deployment.yaml - git add . - git commit -m "update vaultwarden to $TAG" - git push # ArgoCD 会自动检测 Git 变更并部署
这样 CI 不需要 kubectl 权限,ArgoCD 负责部署。职责分离更清晰。
小结
CI/CD 不是要不要做的问题,是什么时候开始做的问题。2 个应用以上就该上了。下一篇,聊 K8s CronJob——把定时任务也跑进集群里。