代码推上去,然后呢?手动构建镜像、手动 push 到 Registry、手动 kubectl apply?GitLab CI/CD 用一个 .gitlab-ci.yml 把全流程串起来,push 即部署。
周五晚上改了个 bug。提交代码、本地构建镜像、push 到 Registry、SSH 到 K3s、kubectl set image……一气呵成,结果发现镜像 tag 写错了,又得重来。如果这些步骤能自动跑该多好?
CI/CD 的本质:把“代码到部署”之间所有手动步骤变成自动流水线。
CI vs CD
代码提交 → [CI] → 可部署的产物 → [CD] → 生产环境
│ │
├ 构建镜像 ├ 部署到 K8s
├ 运行测试 ├ 健康检查
└ 安全扫描 └ 流量切换
| 概念 |
全称 |
干什么 |
| CI |
Continuous Integration |
代码提交后自动构建+测试 |
| CD |
Continuous Delivery |
构建产物自动部署到环境 |
| CD |
Continuous Deployment |
部署也全自动,无需人工批准 |
架构
GitLab CI/CD 由两部分组成:
- GitLab Server:托管代码仓库,定义 Pipeline
- GitLab Runner:执行具体任务(构建、测试、部署)
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 件事:
- Build:构建 Docker 镜像,推送到私有 Registry
- Test:运行单元测试,生成测试报告
- Scan:用 Trivy 扫描镜像漏洞
- Deploy:更新 K8s 集群中的镜像版本
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 # 生产环境需要手动确认
这个配置实现了:
- push 到 develop → 自动部署到 staging
- push 到 main → 构建镜像,但部署需要手动点“Play”按钮确认
- 生产部署有审批环节,防止误操作
配合 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 负责部署。职责分离更清晰。
小结
| 维度 |
Shell 脚本 |
GitLab CI/CD |
| 触发 |
手动执行 |
git push 自动触发 |
| 记录 |
靠日志文件 |
Pipeline 可视化历史 |
| 失败处理 |
脚本中断 |
某阶段失败,下游不执行 |
| 并行 |
不支持 |
同阶段自动并行 |
| 环境 |
单机 |
多 Runner 分布式 |
CI/CD 不是要不要做的问题,是什么时候开始做的问题。2 个应用以上就该上了。下一篇,聊 K8s CronJob——把定时任务也跑进集群里。