runner 可以看成是 24 小时不休息的工人👷🏻♂️,一直监视着 Gitlab 上指定项目,一旦有人提交了代码,runner 就要开始干活了,具体干什么活,就看项目根目录下 .gitlab-ci.yml 这个文件里需要做什么了。
英文好的可以直接看 CI/CD YAML syntax reference 官方文档,本文结合 dkvrius 使用心得记录常用关键词。
目录:
生产环境可能会有很多个 Job,如下配置文件有四个 Job,Lint Test 写在 Build App 之前,但实际运行时会先跑 Build App,成功后再跑 Lint Test,如果前一个 Job 失败了,则后面的 Job 不会继续执行。
Lint Test:
  stage: test
  script:
    - echo "Runnig lint testing..."
Unit Test:
  stage: test
  script:
    - echo "Running unit tests...."
Build App:
  stage: build
  script:
    - echo "Running build...."
Deploy App:
  stage: deploy
  script:
    - echo "Running deploy ...."
配置文件中有个关键词叫 stages,默认值如下,可以看到 build 在 test 之前,这就解释了为什么上面先跑 Build App 再跑 Lint Test。
stages:
  - build
  - test
  - deploy
为什么是这个顺序?
很多编译型编程语言如 Java,源码是没法直接运行的,需要先 build 成可执行文件,才能进行 test 操作。
对于 Javascript 和 Python 这种解释型编程语言来说,源码是可以直接进行 test 操作,测试没问题再进行 build 才是正确的逻辑。
我们可以显式添加 stages 关键词,并修改排序将 test 放到 build 前面。
stages:
  - test
  - build
  - deploy
Lint Test:
  stage: test
  script:
    - echo "Runnig lint testing..."
Unit Test:
  stage: test
  script:
    - echo "Running unit tests...."
Build App:
  stage: build
  script:
    - echo "Running build...."
Deploy App:
  stage: deploy
  script:
    - echo "Running deploy ...."
如下配置,可以看到每个 Job 都指定了 stage 值。
Lint Test:
  stage: test
  script:
    - echo "Runnig lint testing..."
Unit Test:
  stage: test
  script:
    - echo "Running unit tests...."
Build App:
  stage: build
  script:
    - echo "Running build...."
Deploy App:
  stage: deploy
  script:
    - echo "Running deploy ...."
Lint Test 和 Unit Test 的 stage 值都是 test,表示这两个 Job 是并列关系,不是顺序关系,会同时运行,如下图。

为什么配置文件中 Lint Test 写在 Build App 之前,但是上图中会先执行 build,再执行 test,可以参阅 stages。
默认 stages 只有 build、test、deploy 三个阶段,如果要加一个新的阶段 stage: install,那就必须显式添加 stages,在 stages 中也要添加 install 才行,否则会报错。
stages:
  - install
  - test
  - build
Install App:
  stage: install
  script:
    - echo "Installing dependencies..."
    - npm ci
script 中文意思是脚本,就是实际要做什么事情。比如 Build App 这个 Job 的任务是打包代码,在前端项目中就会先执行 npm install 安装依赖,再执行 npm run build 打包代码,一行执行一条命令。
Build App:
  stage: build
  script:
    - echo "Running build...."
    - npm install
    - npm run build
每个 Job 都有一个隐藏的操作:把最新代码 pull 下来,这个不需要在 script 里单独写了,Gitlab 会自动帮我们完成。
执行顺序:artifacts -> before_script -> script -> after_script
如果是在 default 中定义 before_script,那么每一个 Job 的 script 执行之前都会先执行 before_script。
使用 before_script 示例:
在终端中打印信息,可以使用 ANSI 转义码改变文字颜色,如下图:

可以在 before_script 中使用变量定义 ANSI 转义码,然后在 script 中直接使用变量;
job:
  before_script:
    - TXT_RED="\e[31m" && TXT_CLEAR="\e[0m"
  script:
    - echo -e "${TXT_RED}This text is red,${TXT_CLEAR} but this part isn't${TXT_RED} however this part is again."
    - echo "This text is not colored"
执行顺序:artifacts -> before_script -> script -> after_script
如果是在 default 中定义 after_script,那么每一个 Job 的 script 执行之后都会执行 after_script。
job:
  script:
    - echo "An example script section."
  after_script:
    - echo "Execute this command after the `script` section completes."
only 关键字现在不使用了,GitLab 官方现在更推荐使用功能更强大、逻辑更清晰的 rules 关键字来替代它。
场景1:针对特定分支或标签。如下配置,每当 main 分支有变化时才会跑 deploy_job 这个 Job。
deploy_job:
  script: echo "Deploying"
  only:
    - main
场景2:针对合并请求。如下配置,每当有合并请求时才会跑 test_job 这个 Job。
test_job:
  script: echo "Testing MR"
  only:
    - merge_requests
场景3:文件内容改变时触发。如下配置,只有 Dockerfile 和 docker/scripts/* 文件改变时才会跑 docker_build 这个 Job。
docker_build:
  script: docker build -t my-app .
  only:
    changes:
      - Dockerfile
      - docker/scripts/*
如果你的配置文件中还在使用 only 关键字,Gitlab 官方建议改成 rules 关键字。
场景1:针对特定分支或标签。如下配置,每当 main 分支有变化时才会跑 deploy_job 这个 Job。
deploy_job:
  script: echo "Deploying"
  rules:
    - if: '$CI_COMMIT_REF_NAME == "main"' # 匹配 main 分支
场景2:针对合并请求。如下配置,每当有合并请求时才会跑 test_job 这个 Job。
test_job:
  script: echo "Testing MR"
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"' # 使用预定义变量判断管道来源
场景3:文件内容改变时触发。如下配置,只有 Dockerfile 和 docker/scripts/* 文件改变时才会跑 docker_build 这个 Job。
docker_build:
  script: docker build -t my-app .
  rules:
    - changes: # 检查文件变更
        - Dockerfile
        - docker/scripts/*
      when: on_success # 当有变更时,作业在之前阶段成功后执行
    - when: never # 如果没有匹配到变更规则,则作业不加入流水线
when 的含义是满足条件时才会执行当前 Job,有如下值:
Job 的执行顺序是由 stages 控制的。
如下示例:只要前面的 Job 有一个失败,就会执行 cleanup_build_job 这个 Job。
cleanup_build_job:
  stage: cleanup_build
  script:
    - cleanup build when failed
  when: on_failure
如下示例:往生成环境发布代码是件很严肃的事情,通常会设置手动触发才会执行 Job。
deploy_production:
  stage: deploy
  script:
    - make deploy
  when: manual
指定 Job 使用什么 Docker 镜像。
Build App:
  image: node:24
  stage: build
  script:
    - echo "Running build...."
    - npm install
    - npm run build
如下配置,每个 Job 都有 image 字段,要是能在一个地方定义 image,每个 Job 直接继承就好了,default 就是做这件事的。
Lint Test:
  image: node:lts
  stage: test
  script:
    - echo "Runnig lint testing..."
Unit Test:
  image: node:lts
  stage: test
  script:
    - echo "Running unit tests...."
Build App:
  image: node:lts
  stage: build
  script:
    - echo "Running build...."
修改后如下,Job 如果没有定义 image,默认会使用 default 里定义的 image。Build App 自己定义了 image,就会使用自己定义的 image。
default:
  image: node:lts
Lint Test:
  stage: test
  script:
    - echo "Runnig lint testing..."
Unit Test:
  stage: test
  script:
    - echo "Running unit tests...."
Build App:
  image: node:24
  stage: build
  script:
    - echo "Running build...."
并不是所有关键词都可以写到 default 里的,只有下面这些可以写到 default 里减少重复配置:
指定使用哪个 runner 来执行当前 Job。
项目页面 -> Settings -> CI/CD -> Runners。可以看见创建了哪些 runner,每个 runner 在 创建 时都需要填写 tags,当时填的 tags 就在 .gitlab-ci.yml 文件中使用。

如上图,我创建了很多个 runner,其中第一个 runner 的 tag 为 frontend。
如下示例,两个 Job 都使用标签为 frontend 的 runner 来执行。
Unit Test:
  stage: test
  script:
    - echo "Running unit tests...."
  tags:
    - frontend
Build App:
  image: node:24
  stage: build
  script:
    - echo "Running build...."
  tags:
    - frontend
可以在 Job 中自定义变量,然后 script 中使用变量。
review_job:
  variables:
    DEPLOY_SITE: "https://dev.example.com/"
    REVIEW_PATH: "/review"
  script:
    - deploy-review-script --url $DEPLOY_SITE --path $REVIEW_PATH
如果在 default 中定义变量,那么所有 Job 都可以使用该变量。
default:
  variables:
    DEPLOY_SITE: "https://dev.example.com/"
    REVIEW_PATH: "/review"

一些敏感信息如 token、密码不要直接写在 yaml 配置文件中。进入 Gitlab 项目页面 -> Settings -> CI/CD -> Variables 中添加变量,然后在 Job 中使用。
如上图在 Gitlab 项目的配置中添加了变量 NPM_TOKEN,在 yaml 配置文件中像使用普通变量一样使用。
publish-npm:
  stage: deploy
  script:
    - echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > .npmrc
    - npm install
    - npm run build
    - npm publish
Gitlab 提供了一些 内置变量,可以在 Job 中直接使用。
如下配置:
build_app:
  stage: build
  script:
    - echo "Running build...."
    - echo $CI_COMMIT_REF_NAME
    - echo $CI_COMMIT_SHA
如下配置:build_app 打包前端代码会生成 dist 目录,在 deploy_app 中使用 ls dist 是报错 ls: cannot access ‘dist’: No such file or directory。
原因是:Job 与 Job 之间是完全隔离的,默认是无法进行文件共享的。
build_app:
  stage: build
  script: 
    - npm run build
deploy_app:
  stage: deploy
  script:
    - ls dist
如果我们想要 Job 与 Job 之间进行文件共享,可以使用 artifacts 进行设置。
修改配置如下:在 build_app 中添加 artifacts 配置,在 paths 中可以添加多个需要共享的文件或目录,目录后面要加斜线。
build_app:
  stage: build
  script: 
    - npm run build
  artifacts:
    paths:
      - dist/
deploy_app:
  stage: deploy
  script:
    - ls dist
现在再运行 pipeline 就不会报错了。
build_app 执行完之后,会将 dist 目录上传到 Gitlab -> Build -> Artifacts 中,后续运行的所有 Job 都会自动从这里下载文件,以达到不同 Job 文件共享的目的。如果说后续有些 Job 不需要下载共享文件,可以通过 dependencies 字段改变下载行为。

注意📢:NodeJS 的 node_modules 目录千万别使用 artifacts 进行共享,因为 node_modules 文件太大了,上传时间和下载时间遥遥无期,Pipeline 会一直卡在那不动,可以使用 cache 来处理 node_modules 问题。
前面介绍的 artifacts 是解决不同 Job 之间可能会有文件共享的情况。
一个非常常见的部署场景:写了一个软件,支持多个操作系统:
dependencies 字段让每个测试 Job 去下载对应系统的编译包。
build osx:
  stage: build
  script: make build:osx
  artifacts:
    paths:
      - binaries/
build linux:
  stage: build
  script: make build:linux
  artifacts:
    paths:
      - binaries/
test osx:
  stage: test
  script: make test:osx
  dependencies:
    - build osx
test linux:
  stage: test
  script: make test:linux
  dependencies:
    - build linux
对于 artifacts 来说,理论上之后的 Job 默认都会下载之前 Job 的 artifacts 文件,如果不想下载文件,可以设置 dependencies 为空数组。
build osx:
  stage: build
  script: make build:osx
  artifacts:
    paths:
      - binaries/
deploy:
  stage: deploy
  script:
    - echo "Deploying..."
  dependencies: []
如下示例,每个 Job 的 script 都有一个命令 npm install 安装依赖,这是因为 Job 与 Job 之间是隔离的,这样编写明显会影响自动化的速度。
Lint Test:
  stage: test
  script:
    - echo "Runnig lint testing..."
    - npm install
    - npm run lint
Unit Test:
  stage: test
  script:
    - echo "Running unit tests...."
    - npm install
    - npm run test
Build App:
  stage: build
  script:
    - echo "Running build...."
    - npm install
    - npm run build
TODO
定义 Job 部署到哪个环境。
如下配置使用了 environment 关键字,定义了环境名称为 production,url 一般填写项目的访问地址,没有的话可以不写。
Deploy App:
  stage: deploy
  script:
    - echo "Deploying app...."
  environment:
    name: production
    url: https://www.baidu.com

跑完 Pipeline,进入 Gitlab 项目页面 -> Operate -> Environments 中可以看到定义的环境。
实际开发中可能会往不同的环境发布版本,在这个页面可以清晰的看到每个环境的部署状态,如果配置文件中添加了 url 配置,页面会有个 Open 按钮,点击会跳转到对应环境的网址。
↶ 返回首页 ↶