Skip to main content

使用 GitLab Runner 配置 CI/CD 流水线

使用 GitLab Runner 配置 CI/CD 流水线

前言

本文以实际例子介绍如何使用 GitLab Runner 配置 CI/CD 流水线,实现持续集成和持续交付。

安装配置 Runner

.gitlab-ci.yml 文件中定义的作业运行在 Runner 中。 Runner 需要通过网络访问 GitLab.

虽然 Runner 和 GitLab 可以安装在一台机器上,但不推荐这么做。

Runner 可以由多个项目中共享,但出于安全等方面的原因,一个项目或一组项目最好有专门的服务器、虚拟机或容器运行 Runner.

安装配置的官方文档见公司 GitLab 服务器中相关帮助

安装

各种平台下的安装方法详见官方安装帮助文档

RHEL/CentOS/Fedora

# For RHEL/CentOS/Fedora
curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.rpm.sh | sudo bash

# For RHEL/CentOS/Fedora
sudo yum install gitlab-runner

详见官方安装帮助文档

配置

Runner 有 Shared Runners (共享Runner, 整个 GitLab 实例范围内共享使用), Specific Runners (指定Runner, 单个项目专用), Group Runners (群组Runner, 组内项目共享使用) 三种,详见官方文档

Shared Runners 只能由 GitLab 管理配置,对于有特殊要求的项目,应考虑配置 Specific Runners 或 Group Runners.

Step1: 获取注册令牌 (token)

以配置 Specific Runners 为例,在 GitLab 实例中,进入 项目设置 » CI/CD 页面,展开 Runner 可以获取项目的注册令牌。在这个页面也可以修改 Runner 和 CI/CD 的一些配置。

例如,对于 vue-element-admin 这个项目,可以转到这个链接

泄露注册令牌可能会产生一些安全问题,故应妥善保管。万一泄露,可在本页面重置注册令牌。

Step2: 规划 Executors

Shell

Shell 的优势是灵活、调试简单,但一些手工操作并不能从代码中体现出来。在项目初期可以考虑这种方式。

Docker

Docker 是应用最广的方式,可以适应各种类型程序的编译打包。

Step3: 注册

除了 Executors, 在注册 Runner 的过程中还需要考虑命名、标签等,不同平台的注册方法也略有不同,详见官方文档。

Linux

  1. 运行命令
sudo gitlab-runner register
  1. 按提示输入 GitLab instance URL, URL 可从前面复制注册令牌的页面复制
Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com/):
https://gitlab.eaglesoftware.cn/
  1. 按提示输入注册令牌
Please enter the gitlab-ci token for this runner:
xxxxxxxxxxxxxxxxxxxx
  1. 按提示输入 Runner 描述信息,以后可从 CI/CD 设置页面修改
Please enter the gitlab-ci description for this runner:
[localhost.localdomain]: vm-wuzhuangzhuang
  1. 按提示输入与 Runner 关联的标签,标签可以用于选择 Runner, 以后可从 CI/CD 设置页面修改
Please enter the gitlab-ci tags for this runner (comma separated):
vm-wzz,centos
  1. 按提示输入 Runner Executors
Please enter the executor: docker+machine, docker-ssh+machine, kubernetes, virtualbox, docker, docker-ssh, parallels, shell, ssh, custom:
shell
  1. 如果选择 Docker 作为 executor, 还需要输入默认镜像,在 .gitlab-ci.yml 中未定义镜像时使用。

示例

官方文档有各种各样的示例。这里以 vue-element-admin 这个项目的配置演进为例,说明配置 CI/CD 过程中遇到的问题、想要达成的目标,以及解决办法。

项目说明

CI/CD 流水线一般由提交代码触发,包括自动编译(打包)、自动化测试、自动部署等步骤 (stages)。

要配置 CI/CD 流水线,首先需要知道手工如何实现这些工作,然后再编写代码实现自动化。

vue-element-admin 这个项目是基于 npm 的前端演示项目,没有后端和数据库等组件,项目组提供了一台 CentOS 虚拟机用于打包和部署,但没有提供自动化测试相关过程,所以只需在同一台机器上做打包和部署两个过程。

Version 1: Shell 版

Step1: Runner 安装配置

见前面的描述。

Step2: 打包工具安装

虽然这个项目有比较详尽的 README.md, 但仍没说明不支持最新的 node 12.16.1, 也没有说明需要安装 Development Tools. (实际上 .travis.yml 中有一行 node_js: 10)

如果对一个历史比较悠久的项目做一点小改动,这可能是一个需要时间修补的小坑。

# 去除以前安装的 nodejs
sudo yum remove nodejs
# 清除以前的安装源
sudo yum clean all
# 按官方文档 https://github.com/nodesource/distributions/blob/master/README.md#debinstall 设置安装源
curl -sL https://rpm.nodesource.com/setup_10.x | sudo bash -
# 安装 nodejs
sudo yum install -y nodejs
# 安装开发工具
yum install gcc-c++ make
# 或
# yum groupinstall 'Development Tools'
# 查看版本信息
npm version

查看版本信息的结果:

{ npm: '6.13.4',
  ares: '1.15.0',
  brotli: '1.0.7',
  cldr: '35.1',
  http_parser: '2.9.3',
  icu: '64.2',
  modules: '64',
  napi: '5',
  nghttp2: '1.39.2',
  node: '10.19.0',
  openssl: '1.1.1d',
  tz: '2019c',
  unicode: '12.1',
  uv: '1.28.0',
  v8: '6.8.275.32-node.55',
  zlib: '1.2.11' }

Step3: 配置权限

因为后面想直接用 gitlab-runner 用户操作 docker 服务,故将 gitlab-runner 加入 docker 组中。

sudo usermod -aG docker gitlab-runner
sudo -u gitlab-runner -H docker info

Step4: 编写 .gitlab-ci.yml 文件

.gitlab-ci.yml 配置流水线如何运行,其编写详见 GitLab CI/CD Pipeline Configuration Reference.

v1.1 - 初始版本

第 1 版设置 2 个步骤,打包和部署, .gitlab-ci.yml 文件的内容如下。

stages:
  - build
  - deploy

# 所有 stage 之前的操作
before_script:
  - pwd
  - npm set registry https://mirrors.huaweicloud.com/repository/npm/

cache:
  paths:
    - node_modules/
    - dist
  
build_job:
  stage: build
  tags:
    - centos
    - vm-wzz
  script:
    - echo '打包'
    - npm install
    - rm -rf ./dist
    - npm run build

deploy_job:
  stage: deploy
  tags:
    - centos
    - vm-wzz
  script:
    - echo '部署'
    - cd docker-compose
    - docker-compose stop
    - docker-compose up -d

在项目组提供的虚拟机上, 总耗时 03:06, 其中 build_job 耗时 02:03, deploy_job 耗时 01:02, deploy_job 耗时超出预期。

经检查,deploy_job 这一步要从 GitLab 上重新拉取代码,前一步 build_job 生成的 node_modules/ 和 dist/ 会被删除,然后从缓存区复制过来,虽然是本地缓存,但 node_modules/ 占用空间多达几百兆,而且不是两个步骤公用的,没有必要进行缓存。

116     ./public
1184    ./.git
44      ./plop-templates
9324    ./dist
8       ./build
12      ./docker-compose
84      ./mock
450948  ./node_modules
2868    ./src
40      ./tests
465488  .

v1.2 - 合并打包和部署

合并两个阶段后,总耗时 02:32. 修改后的 .gitlab-ci.yml 文件内容如下。

stages:
  - build

# 所有 stage 之前的操作
before_script:
  - pwd
  - npm set registry https://mirrors.huaweicloud.com/repository/npm/

cache:
  paths:
    - node_modules/
    - dist
  
build_job:
  stage: build
  tags:
    - shell
    - vm-wzz
  script:
    - echo '打包+部署'
    - npm install
    - npm run build
    - cd docker-compose
    - docker-compose stop
    - docker-compose up -d

package.json 没有变化时,npm install 没有必要每次运行。

v1.3 - npm install 仅在需要时运行

在前面的基础上,继续修改 .gitlab-ci.yml 文件,只在 package.json 文件发生变化时执行 npm install.

新提交版本触发的流水线最快总耗时 01:23。

stages:
  - npm_install
  - build

# 所有 stage 之前的操作
before_script:
  - pwd
  - npm set registry https://mirrors.huaweicloud.com/repository/npm/

cache:
  paths:
    - node_modules/

npm_install_job:
  stage: npm_install
  tags:
    - shell
    - vm-wzz
  script:
    - npm install
  only:
    changes:
      - package.json

build_job:
  stage: build
  tags:
    - shell
    - vm-wzz
  script:
    - npm run build
    - cd docker-compose
    - docker-compose stop
    - docker-compose up -d

按照同样的方法,也可以在 docker-compose/ 文件夹下面的文件发生变化时,重新构建 Docker 镜像。

曾经踩过的坑(修复的问题)

网络问题还是 Git 版本问题?

使用项目组提供的虚拟机,曾经有多次出现拉取代码出错的问题,而在华为云的云主机上注册的 Runner 从未出现过类似问题,当时怀疑是公司网络不稳定。

出现过的错误有:

  1. The remote end hung up unexpectedly
重新初始化现存的 Git 版本库于 /home/gitlab-runner/builds/z5U7Uaf7/0/opd/vue-element-admin/.git/
 error: RPC failed; result=6, HTTP code = 0
 fatal: The remote end hung up unexpectedly
 ERROR: Job failed: exit status 1
  1. Could not resolve host: gitlab.eaglesoftware.cn
Fetching changes with git depth set to 50...
00:01
 重新初始化现存的 Git 版本库于 /home/gitlab-runner/builds/z5U7Uaf7/0/opd/vue-element-admin/.git/
 fatal: unable to access 'https://gitlab-ci-token:[MASKED]@gitlab.eaglesoftware.cn/opd/vue-element-admin.git/': Could not resolve host: gitlab.eaglesoftware.cn; Unknown error
 ERROR: Job failed: exit status 1
  1. Could not resolve host: github.com (npm install 过程中出现的问题)
npm ERR! Error while executing:
npm ERR! /usr/bin/git ls-remote -h -t https://github.com/nhn/raphael.git
npm ERR!
npm ERR! fatal: unable to access 'https://github.com/nhn/raphael.git/': Could not resolve host: github.com; Unknown error
npm ERR!
npm ERR! exited with error code: 128
npm ERR! A complete log of this run can be found in:
npm ERR!     /home/gitlab-runner/.npm/_logs/2020-03-09T07_25_38_871Z-debug.log
ERROR: Job failed: exit status 1
  1. git fetch-pack: expected shallow list
Running on localhost.localdomain...
00:00
Fetching changes with git depth set to 50...
00:03
 重新初始化现存的 Git 版本库于 /home/gitlab-runner/builds/z5U7Uaf7/0/opd/vue-element-admin/.git/
 fatal: git fetch-pack: expected shallow list
 fatal: The remote end hung up unexpectedly
 ERROR: Job failed: exit status 1

前 3 个可能真是网络不稳定,但第 4 个多次重试仍然不行,经百度搜索,确认是 CentOS 7 在安装 Runner 时默认安装的 Git 版本过低 (git version 1.8.3.1), 升级 Git 版本 (git version 2.22.2) 后恢复正常。由此可见,即使是个虚拟机,最好也安装个靠谱的版本。

CentOS 7 下的升级过程在 Git 官网有链接,这里使用 ius.io 提供的安装源:

yum install \
https://repo.ius.io/ius-release-el7.rpm \
https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
yum repolist
yum remove git
yum provides git
yum install git222
git --version
# 删除 git 时同时删除了依赖 git 的 gitlab-runner, 所以重新安装
yum install gitlab-runner

node 版本

node 之类的工具版本更新比较快,而且新版本不一定支持原先的代码。辛辛苦苦安装好 node 12.16.1, 还要想办法清除。详见前面描述。

如果使用 Docker, 这个坑修复的成本会低很多。

Docker Engine 未启动

$ docker-compose stop
 Couldn't connect to Docker daemon at http+docker://localhost - is it running?
 If it's at a non-standard location, specify the URL with the DOCKER_HOST environment variable.
 ERROR: Job failed: exit status 1

这个问题比较明显,启动 Docker Engine 并设置开机启动后恢复正常。

systemctl start docker
systemctl enable docker

Version 2: Docker 版

Why

前面的版本虽然完成了打包和部署,但坑比较多,而且打包和部署是在同一台机器上,而测试环境、正式运行环境一般都是与开发环境分离的。

在这个版本中,我们使用 docker 完成打包,并使用 ssh 把打包好的软件部署到运行环境中。

这种部署方式需要运行 runner 的机器要能通过网络访问部署目标机器,比如从公司内网可以部署到云服务器,但反过来就不太方便;如果目标机器的 IP 地址是动态获取的,也容易出现问题。

Step1: Runner 安装配置

见前面的描述,以下略有不同:

  • gitlab-ci tags: vm-wzz, docker
  • executor: docker
  • default Docker image: node:10.19.0-buster (不重要,随便输入)

Step2: 生成密钥对并分别配置到目标机器和 GitLab 环境变量

生成密钥对,不设置 passphrase, 然后将公钥配置到目标机器。

$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/c/Users/96129/.ssh/id_rsa): id_rsa_ci_test
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in id_rsa_ci_test.
Your public key has been saved in id_rsa_ci_test.pub.
$ ssh-copy-id -i id_rsa_ci_test.pub root@10.44.111.27
$ cat id_rsa_ci_test

在 GitLab 项目设置 » CI/CD 页面,新增 3 个变量:

  • SERVER_HOST: 目标机器的 IP 地址, 如前面的 10.44.111.27
  • SERVER_USER: 目标机器的用户 login, 如前面的 root
  • STAGING_PRIVATE_KEY: 私钥的全部内容,如前面 cat id_rsa_ci_test 的输出结果

这 3 个变量会在接下来的 .gitlab-ci.yml 文件中用到。

Step3: 编写 .gitlab-ci.yml 文件

测试好的 .gitlab-ci.yml 文件如下。

image: node:10.19.0-buster

stages:
  - npm_install
  - build_docker
  - deploy

variables:
  TARGET_DIR: ~/vue-element-admin/

# 所有 stage 之前的操作
before_script:
  - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
  - mkdir -p ~/.ssh
  - eval $(ssh-agent -s)
  - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
  - ssh-add <(echo "$STAGING_PRIVATE_KEY")
  - npm set registry https://mirrors.huaweicloud.com/repository/npm/

stage_npm_install:
  stage: npm_install
  tags:
    - docker
    - vm-wzz
  cache:
    paths:
      - node_modules/  
  script:
    - npm install
  only:
    changes:
      - package.json

stage_build_docker:
  stage: build_docker
  tags:
    - docker
    - vm-wzz
  script:
    - ssh $SERVER_USER@$SERVER_HOST "mkdir -p $TARGET_DIR/docker-compose/"
    - scp -r docker-compose/* $SERVER_USER@$SERVER_HOST:$TARGET_DIR/docker-compose/
    - ssh $SERVER_USER@$SERVER_HOST "docker-compose -f $TARGET_DIR/docker-compose/docker-compose.yml build"
  only:
    changes:
      - docker-compose/**/*
      
stage_deploy:
  stage: deploy
  artifacts:
    paths:
      - dist/
  tags:
    - docker
    - vm-wzz
  cache:
    paths:
      - node_modules/  
    policy: pull
  script:
    - npm run build
    - ssh $SERVER_USER@$SERVER_HOST "mkdir -p $TARGET_DIR/dist/"
    - scp -r dist/* $SERVER_USER@$SERVER_HOST:$TARGET_DIR/dist/
    - ssh $SERVER_USER@$SERVER_HOST "mkdir -p $TARGET_DIR/docker-compose/"
    - scp -r docker-compose/* $SERVER_USER@$SERVER_HOST:$TARGET_DIR/docker-compose/
    - ssh $SERVER_USER@$SERVER_HOST "cd $TARGET_DIR/docker-compose/ && docker-compose stop && docker-compose up -d"

这里把整个流水线分成 3 步,第 1 步为 npm_install, 只在 package.json 发生变化时运行,生成的文件作为后续步骤的缓存。

第 2 步为 build_docker, 在 docker-compose/ 文件夹下的内容发生变化时运行,由于其中并没有 Dockerfile, 直接使用的 nginx 服务,这一步并没有实质作用。

第 3 步为 deploy, 拉取第 1 步生成的缓存目录 node_modules/, 进行打包,并将打包后的软件复制到目标机器,指示目标机器重新启动容器。

注意事项

由于第 3 步依赖第 1 步生成的缓存,而第 1 步只在 package.json 发生变化时才触发运行,在首次提交 .gitlab-ci.yml 而 package.json 未同时变化时,流水线只运行第 3 步会出现失败。

此时,应从 GitLab 项目的 CI / CD 页面单击“运行流水线”,手动触发完整流水线的运行,以后再提交代码时触发运行的流水线将不会因此问题失败。

结果分析

由于拉取 docker 镜像、复制文件等相比 Version 1 的方式都将带来额外的资源消耗,增加了向 GitLab 上传工件的环节,流水线最快总耗时为 02:55 (在 package.json 等不发生变化时), 比 Version 1 的最佳结果约慢 90 秒钟。

但这种部署方式的几乎把所有步骤都写入代码中,非常清楚,而性能的下降可以通过更好的硬件设施来弥补。

更好的部署方式是把打包好的工件(如生成容器镜像)推送到统一的注册中心,在运行环境中拉取工件运行。这样,只有注册中心需要有固定的域名或 IP 地址,但这对资源要求较高,暂不考虑这种方式。