使用 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 运行命令 sudo gitlab-runner register 按提示输入 GitLab instance URL, URL 可从前面复制注册令牌的页面复制 Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com/): https://gitlab.eaglesoftware.cn/ 按提示输入注册令牌 Please enter the gitlab-ci token for this runner: xxxxxxxxxxxxxxxxxxxx 按提示输入 Runner 描述信息,以后可从 CI/CD 设置页面修改 Please enter the gitlab-ci description for this runner: [localhost.localdomain]: vm-wuzhuangzhuang 按提示输入与 Runner 关联的标签,标签可以用于选择 Runner, 以后可从 CI/CD 设置页面修改 Please enter the gitlab-ci tags for this runner (comma separated): vm-wzz,centos 按提示输入 Runner Executors Please enter the executor: docker+machine, docker-ssh+machine, kubernetes, virtualbox, docker, docker-ssh, parallels, shell, ssh, custom: shell 如果选择 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 从未出现过类似问题,当时怀疑是公司网络不稳定。 出现过的错误有: 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 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 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 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 地址,但这对资源要求较高,暂不考虑这种方式。