GitLab CI/CD 最佳实践:从入门到生产级流水线
GitLab CI/CD 是 DevOps 工具链的核心组件。很多团队的流水线停在"能跑就行":测试全跑在一台机器上、依赖每次都重新下载、生产部署靠人肉点按钮。这篇文章从流水线设计讲到安全扫描,给出可以照着搭的生产级方案。
在动手写 .gitlab-ci.yml 之前,值得先想清楚一个目标:流水线不是"把命令串起来",而是把"验证—构建—交付"的过程固化成规则,让每一次提交都走同样的路径,让人为失误没有机会发生。下面的配置示例按一个典型 Node.js 项目展开,你可以替换成自己的技术栈。
流水线结构设计
stages:
- lint
- test
- build
- deploy
variables:
DOCKER_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
lint:
stage: lint
image: node:20
script:
- npm ci
- npm run lint
test:
stage: test
image: node:20
script:
- npm ci
- npm run test:ci
coverage: '/Lines\s*:\s*(\d+\.\d+%)/'
stage 的顺序就是执行顺序,前一个阶段全部通过才会进入下一个。把 lint、test、build、deploy 拆开有几个好处:一是失败早暴露,代码风格问题不用等测试跑完才发现;二是可以按阶段并行,test 里的多个 job 能同时跑;三是 deploy 单独成段,方便加人工确认。coverage 正则用来从测试输出里抓取覆盖率,会自动显示在合并请求的详情里。
如果项目里模块多、构建依赖复杂,还可以用 needs 关键字打破严格的阶段顺序,让某个 job 只等它真正依赖的 job 完成就开跑,进一步缩短流水线耗时。比如 UI 测试只需要等 build 完,不必等后端集成测试跑完。
构建产物传递(artifact)
test 和 build 之间要传文件,靠的是 artifact 而不是 cache:
build:
stage: build
script:
- npm run build
artifacts:
paths:
- dist/
expire_in: 1 week
deploy:
stage: deploy
script:
- ls dist/ && ./deploy.sh
dependencies:
- build
dist/ 在 build 阶段打包好,通过 artifact 传给 deploy,expire_in: 1 week 防止仓库无限膨胀。artifact 是"必须存在"的交付物,cache 是"尽力而为"的加速手段——两者用途完全不同,别混用。很多人踩过的坑是:把需要部署的文件放进 cache 而不是 artifact,结果下一台 Runner 上 cache 命中失败,deploy job 里 ls dist/ 直接报错。
Runner 与变量配置
流水线跑在哪、密钥放哪,是新手最容易踩坑的地方。共享 Runner 免费但会排队,自建 Runner 要保证环境一致,建议用 Docker Executor 让每个 job 都在干净容器里跑。密钥和敏感变量用 CI/CD 变量管理,不要在 .gitlab-ci.yml 里写明文,相关做法可参考环境变量与密钥管理。
这里给一个对比:共享 Runner 适合中小团队,零成本但排队时流水线慢;自建 Runner 适合对资源隔离和缓存要求高的场景,可以把 Docker、构建缓存都放在本机,第一次构建后速度明显更快。无论哪种,都要注意把 SSH 私钥、云厂商密钥这类敏感信息放到 GitLab 的 CI/CD 变量里,并开启 "Masked" 选项,避免在日志里泄露。
缓存策略
cache:
key: $CI_COMMIT_REF_SLUG
paths:
- node_modules/
- .npm/
policy: pull-push
cache 和 artifact 是两个容易混淆的概念:cache 缓存依赖,让 npm ci 更快;artifact 传递构建产物,比如把编译好的包传给部署 job。key 按分支区分,避免不同分支互相污染。注意 cache 是尽力而为,不要在里面存必须存在的东西。policy: pull-push 表示流水线既拉取旧缓存也写入新缓存;如果某个 job 只读依赖不写,可以用 policy: pull 省掉上传步骤,减少 Runner 的 IO 压力。
多环境部署
deploy-review:
stage: deploy
only:
- merge_requests
environment:
name: review/$CI_MERGE_REQUEST_IID
url: https://$CI_MERGE_REQUEST_IID.example.com
script:
- ./deploy-review.sh
deploy-production:
stage: deploy
only:
- main
when: manual
environment:
name: production
url: https://example.com
script:
- ./deploy.sh
每个合并请求拉起一套 review 环境,方便设计师和产品直接在真实页面上看效果;生产部署加上 when: manual,由人确认后再点按钮,避免合并即上线。环境名写清楚,GitLab 的环境页会自动聚合每套环境的部署记录和 URL。一个实用的补充是给 review 环境设置自动清理:合并请求关闭后,对应的环境实例如果没人管,会一直占着资源,可以用 GitLab 的清理策略或 cron job 定期回收。
安全扫描
include:
- template: Jobs/SAST.gitlab-ci.yml
- template: Jobs/Dependency-Scanning.gitlab-ci.yml
- template: Jobs/Secret-Detection.gitlab-ci.yml
GitLab 内置的安全模板可以一键接入:SAST 做静态代码扫描、Dependency-Scanning 检查依赖漏洞、Secret-Detection 扫描是否误把密钥提交进了仓库。三者都不用自己写规则,直接用官方模板即可。建议至少在 main 分支上强制开启这些扫描,并把扫描结果接入合并请求的审查流程——一个在合并前就被拦下的密钥提交,比上线后泄露再补救便宜得多。
常见问题
- npm ci 每次都重新下载依赖? 检查 cache 的 key 和 paths 是否写对,node_modules 路径要包含在 paths 里。
- 部署到服务器用什么方式? 常见做法是用 SSH 加部署密钥,或直接推送到容器镜像仓库后由服务器拉取,相关可参考网站 CI/CD 流水线搭建和Docker Compose 生产部署。
- 流水线一直排队怎么办? 大概率是共享 Runner 资源紧张,考虑自建 Runner 或给关键 job 提高优先级。
- 合并请求里看不到测试覆盖率? 确认
coverage正则和测试输出格式匹配,CI 里test:ci的输出需要包含类似Lines: 85.5%的行。
参考
参考:GitLab CI/CD 官方文档 https://docs.gitlab.com/ci/
参考:GitLab CI/CD 变量 https://docs.gitlab.com/ci/variables/
参考:GitLab Cache 与 Artifact https://docs.gitlab.com/ci/caching/