在 GitLab 上构建 CI 流水线
连续集成 CI

> 
> <ruby> 连续集成 <rt>  continuous integration </rt></ruby>（CI）是指代码变更会被自动构建和测试。以下是我为自己的 C++ 项目构建 CI 流水线的过程。
> 
> 
> 


![](/data/attachment/album/202308/18/094419rahz9i5pk3n9fkkf.jpg)


本文介绍如何在 [GitLab](https://gitlab.com/) 上配置 CI 流水线。我在前面的文章中介绍了 [基于 CMake 和 VSCodium 的构建系统](/article-14249-1.html) 和 [基于 GoogleTest 和 CTest 的单元测试](/article-16055-1.html)。本文将在此基础上进一步配置 CI 流水线。我会先演示如何布设和运行 CI 流水线，然后再介绍如何配置它。


CI 是指提交到代码仓库的代码变更会被自动构建和测试。在开源领域，GitLab 是一个流行的 CI 流水线平台。除了作为中心 Git 仓库外，GitLab 还提供 CI/CD 流水线、<ruby> 问题跟踪 <rt>  issue tracking </rt></ruby> 和 <ruby> 容器注册表 <rt>  container registry </rt></ruby>功能。


### 相关术语


在进入正题之前，我先介绍在本文和 [GitLab 文档](https://docs.gitlab.com/) 中会遇到的常见术语。


* <ruby> 持续交付 <rt>  continuous delivery </rt></ruby>（CD）：自动化供应软件，以供随时交付
* <ruby> 持续部署 <rt>  continuous deployment </rt></ruby>（CD）：自动化软件发布
* <ruby> 流水线 <rt>  pipeline </rt></ruby>: CI/CD 的直接构件，它由阶段和作业构成
* <ruby> 阶段 <rt>  stage </rt></ruby>：一组作业
* <ruby> 作业 <rt>  job </rt></ruby>：某项需要执行的具体任务，比如编译、单元测试等
* <ruby> 执行器 <rt>  runner </rt></ruby>：实际执行作业的服务


### 布设 CI 流水线


在下面的章节中，我将复用以前的 [示例工程](https://gitlab.com/hANSIc99/cpp_testing_sample)。点击 GitLab 仓库页面右上角的 <ruby> 复刻 <rt>  Fork </rt></ruby> 按钮复刻代码仓库。


![Fork the project](/data/attachment/album/202308/18/094511lhmf7pofu4h3qpoq.png "Fork the project")


#### 设置执行器


为了让你对整个流程有所了解，我们先从在本地安装执行器讲起。


参照执行器服务 [安装指南](https://docs.gitlab.com/runner/install/) 安装好服务，然后注册执行器。


1、选择 GitLab 项目页面左侧的 <ruby> 设置 <rt>  Settings </rt></ruby>，再选择 **CI/CD**。


![Select CI/CD in Settings](/data/attachment/album/202308/18/094511iu5kup3oztpqaeqx.png "Select CI/CD in Settings")


2、展开 <ruby> 执行器 <rt>  Runners </rt></ruby> 区域，关闭 <ruby> 共享的执行器 <rt>  Shared runners </rt></ruby> 选项（黄框处）。特别注意令牌和 URL（绿框处），下一步会用到它们。


![Configure runner](/data/attachment/album/202308/18/094511djgq60t7a2209la2.png "Configure runner")


3、在终端中运行 `gitlab-runner register`，根据提示输入以下注册信息：


* GitLab 实例: <https://gitlab.com/> （如上图）
* 注册令牌：从执行器区域中获取 （如上图）
* 描述：按需自由填写
* 标签：可以不填
* 执行环境：选 **Shell**


如果有需要，你可以在 `~/.gitlab-runner/config.toml` 中修改这些配置。


4、用命令 `gitlab-runner run` 启动执行器。你可以在 GitLab 的项目设置界面执行器区域看到执行器的状态：


![Available specific runners](/data/attachment/album/202308/18/094512cxejm3elepcqdj4q.png "Available specific runners")


### 运行流水线


前面已经提过，流水线就是一组由执行器执行的作业。每个推送到 GitLab 的提交都会生成一个附加到该提交的流水线。如果多个提交被一起推送，那么只会为最后一个提交生成流水线。为了演示，我直接在 GitLab 在线编辑器中提交和推送修改。


打开 `README.md` 文件，添加一行数据：


![Web editor](/data/attachment/album/202308/18/094512s93ddz0m8dq9q9t5.png "Web editor")


现在提交修改。


这里注意默认的行为是为提交新建一个分支，为了简便起见，我们择提交到主分支。


![Commit changes](/data/attachment/album/202308/18/094513o2um2eq2ztxnteef.png "Commit changes")


提交后一会儿后，你就应该改能看到 GitLab 执行器执行的控制台中有输出消息：



```
Checking for jobs... received job=1975932998 repo_url=<https://gitlab.com/hANSIc99/cpp\_testing\_sample.git> runner=Z7MyQsA6

Job succeeded duration_s=3.866619798 job=1975932998 project=32818130 runner=Z7MyQsA6

```

在 GitLab 项目概览界面左侧选择 CI/CD --> <ruby> 管道 <rt>  Pipelines </rt></ruby>，查看最近执行的流水线：


![Pipeline overview](/data/attachment/album/202308/18/094513k34ibi0gee9z069e.png "Pipeline overview")


选中流水线可以在详情界面看到哪些作业失败了，并能查看各个作业的输出。


当遇到非零返回值是就认为作业执行失败了。在下面的例子中我通过调用 `exit 1` 强制让作业执行失败：


![Job overview](/data/attachment/album/202308/18/094513cayxee5yhyyrhzh1.png "Job overview")


### CI 配置


阶段、流水线和作业的配置都在仓库根目录的 [.gitlab-ci.yml](https://gitlab.com/hANSIc99/cpp_testing_sample/-/blob/main/.gitlab-ci.yml) 文件中。我建议使用 GitLab 内置的流水线编辑器，它会自动对配置进行检查。



```
stages:
- build
- test

build:
  stage: build
  script:
    - cmake -B build -S .
    - cmake --build build --target Producer
  artifacts:
    paths:
      - build/Producer

RunGTest:
  stage: test
  script:
    - cmake -B build -S .
    - cmake --build build --target GeneratorTest
    - build/Generator/GeneratorTest

RunCTest:
  stage: test
  script:
    - cmake -B build -S .
    - cd build
    - ctest --output-on-failure -j6

```

文件中定义了两个阶段：`build` 和 `test`，以及三个作业：`build`、`RunGTest` 和 `RunCTest`。其中作业 `build` 属于一个同名的阶段，另外两个作业属于阶段 `test`。


`script` 小节下的命令就是一般的 Shell 命令。你可以认为是将它们逐行输入到 Shell 中。


我要特别提及 <ruby> 产物artifact</ruby> 这个特性。在示例中我定义了二进制的 `Producer` 为作业 `build` 的产物。产物会被上传到 GitLab 服务器，并且可以从服务器的这个页面上被下载：


![Pipeline artifacts](/data/attachment/album/202308/18/094514rmjn5hm90m995ojz.png "Pipeline artifacts")


默认情况下，后续阶段的作业会自动下载先前阶段作业生成的所有产物。


你可以在 [docs.gitlab.com](https://docs.gitlab.com/ee/ci/yaml/) 上查看 `gitlab-ci.yml` 参考指南。


### 总结


上面只是一个最基本的例子，让你对持续集成的一般原则有一个了解。再演示中我禁用了共享执行器，然而这才是 GitLab 的优势所在。你可以在一个干净的容器化的环境中构架、测试和部署程序。除了使用 GitLab 提供的免费执行器，你也可以用自己的容器作为执行器。当然还有更高阶的用法：用 Kubernetes 来协调调度执行者容器，让流水线适应大规模使用的使用场景。如需进一步了解，可以查看 [about.gitlab.com](https://about.gitlab.com/solutions/kubernetes/)。


如果你使用的是 Fedora，需要注意的一点是目前 GitLab 执行者还不支持用 Podman 作为容器引擎。（LCTT 译注：Podman 是 Fedora 自带的容器引擎。）根据 <ruby> 议题 <rt>  issue </rt></ruby> [#27119](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/27119)，对 Podman 支持已将列上日程。（LCTT 译注：Podman 4.2 及以上版本增加了对于 GitLab 执行器的支持。）


把重复性的操作描述成作业，并将作业合并成流水线和阶段，可以让你跟踪它们的质量而不增加额外工作。。特别是在大型社区项目中，适当配置的 CI 可以告诉你提交的代码是否对项目有改善，为你接受或拒绝合并请求提供依据。


*（题图：MJ/fb711c48-251a-4726-a41c-247370e5df25）*




---


via: <https://opensource.com/article/22/2/setup-ci-pipeline-gitlab>


作者：[Stephan Avenwedde](https://opensource.com/users/hansic99) 选题：[lujun9972](https://github.com/lujun9972) 译者：[toknow-gh](https://github.com/toknow-gh) 校对：[wxy](https://github.com/wxy)


本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


OK
This article covers the configuration of a CI pipeline for a C++ project on [GitLab](https://gitlab.com/). My previous articles covered how to set up a build system based on [CMake and VSCodium](https://opensource.com/article/22/1/devops-cmake) and how to integrate unit tests based on [GoogleTest and CTest](https://opensource.com/article/22/1/unit-testing-googletest-ctest). This article is a follow-up on extending the configuration by using a CI pipeline. First, I demonstrate the pipeline setup and then its execution. Next comes the CI configuration itself.

Continuous integration (CI) simply means that code changes, which get committed to a central repository, are built and tested automatically. A popular platform in the open source area for setting up CI pipelines is GitLab. In addition to a central Git repository, GitLab also offers the configuration of CI/CD pipelines, issue tracking, and a container registry.


## Terms to know

Before I dive deeper into this area of the DevOps philosophy, I'll establish some common terms encountered in this article and the [GitLab documentation](https://docs.gitlab.com/):

- Continuous delivery (CD): Automatic provisioning of applications with the aim of deploying them.
- Continuous deployment (CD): Automatic publishing of software
- Pipelines: The top-level component for CI/CD, defines stages and jobs
- Stages: A collection of jobs that must execute successfully
- Jobs: Definition of tasks (e.g., compile, performing unit test)
- Runners: Services that are actually executing the Jobs

## Set up a CI pipeline

I will reuse the example projects from previous articles, which are available on GitLab. To follow the steps described in the coming chapters, fork the [example project](https://gitlab.com/hANSIc99/cpp_testing_sample) by clicking on the *Fork* button, which is found on the top right:

![Fork the project Fork the project](https://opensource.com/sites/default/files/cpp_ci_cd_gitlab_fork.png)

Stephan Avenwedde (CC BY-SA 4.)

### Set up a runner

To get a feeling for how everything works together, start at the bottom by installing a runner on your local system.

Follow the [installation instructions](https://docs.gitlab.com/runner/install/) for the GitLab runner service for your system. Once installed, you have to register a runner.

1. On the GitLab page, select the project and in the left pane, navigate to **Settings** and select **CI/CD**.

![Select CI/CD in Settings Select CI/CD in Settings](https://opensource.com/sites/default/files/cpp_ci_cd_gitlab_project_settings.png)

Stephan Avenwedde (CC BY-SA 4.0)

2. Expand the Runners section and switch **Shared runners** to off (yellow marker). Note the token and URL (green marker); we need them in the next step.

![Configure runner Configure runner](https://opensource.com/sites/default/files/cpp_ci_cd_gitlab_settings_runners2.png)

Stephan Avenwedde (CC BY-SA 4.0)

3. Now open a terminal and enter `gitlab-runner register`

. The command invokes a script that asks for some input. Here are the answers:

- GitLab instance:
[https://gitlab.com/](https://gitlab.com/)(screenshot above) - Registration token: Pick it from the
**Runners**section (screenshot above) - Description: Free selectable
- Tags: This is optional. You don't need to provide tags
- Executor: Choose
**Shell**here

If you want to modify the configuration later, you can find it under `~/.gitlab-runner/config.toml`

.

4. Now, start the runner with the command `gitlab-runner run`

. The runner is now waiting for jobs. Your runner is now available in the **Runners** section of the project settings on GitLab:

![Available specific runners Available specific runners](https://opensource.com/sites/default/files/cpp_ci_cd_gitlab_settings_active_runner.png)

Stephan Avenwedde (CC BY-SA 4.0)

## Execute a pipeline

As previously mentioned, a pipeline is a collection of jobs executed by the runner. Every commit pushed to GitLab generates a pipeline attached to that commit. If multiple commits are pushed together, a pipeline is created for the last commit only. To start a pipeline for demonstration purposes, commit and push a change directly over GitLab's web editor.

For the first test, open the `README.md`

and add a additional line:

![Web editor Web editor](https://opensource.com/sites/default/files/cpp_ci_cd_gitlab_web_editor.png)

Stephan Avenwedde (CC BY-SA 4.0)

Now commit your changes.

Note that the default is **Create a new branch**. To keep it simple, choose **Commit to main branch**.

![Commit changes Commit changes](https://opensource.com/sites/default/files/cpp_ci_cd_gitlab_commit_changes2.png)

Stephan Avenwedde (CC BY-SA 4.0)

A few seconds after the commit, you should notice some output in the console window where the GitLab runner executes:

```
Checking for jobs... received job=1975932998 repo_url=https://gitlab.com/hANSIc99/cpp_testing_sample.git runner=Z7MyQsA6
Job succeeded duration_s=3.866619798 job=1975932998 project=32818130 runner=Z7MyQsA6
```


In the project overview in GitLab, select on the right pane **CI/CD --> Pipelines**. Here you can find a list of recently executed pipelines.

![Pipeline overview Pipeline overview](https://opensource.com/sites/default/files/cpp_ci_cd_gitlab_pipeline_overview2.png)

Stephan Avenwedde (CC BY-SA 4.0)

If you select a pipeline, you get a detailed overview where you can check which job failed (in case the pipeline failed) and see the output of individual jobs.

**A job is considered to have failed if a non-zero value was returned**. In the following case, I just invoked the bash command `exit 1`

(line 26) to let the job fail:

![Job overview Job overview](https://opensource.com/sites/default/files/cpp_ci_cd_gitlab_job_overview.png)

Stephan Avenwedde (CC BY-SA 4.0)

## CI configuration

The stages, pipelines, and jobs configurations are made in the file [.gitlab-ci.yml](https://gitlab.com/hANSIc99/cpp_testing_sample/-/blob/main/.gitlab-ci.yml) in the root of the repository. I recommend editing the configuration with GitLab's build-in Pipeline editor as it automatically checks for accuracy during editing.

```
stages:
- build
- test
build:
stage: build
script:
- cmake -B build -S .
- cmake --build build --target Producer
artifacts:
paths:
- build/Producer
RunGTest:
stage: test
script:
- cmake -B build -S .
- cmake --build build --target GeneratorTest
- build/Generator/GeneratorTest
RunCTest:
stage: test
script:
- cmake -B build -S .
- cd build
- ctest --output-on-failure -j6
```


The file defines the stages **build** and **test**. Next, it defines three jobs: **build**, **RunGTest** and **RunCTest**. The **build** job is assigned to the eponymous stage, and the other jobs are assigned to the *test* stage.

The commands under the **script** section are ordinary shell commands. You can read them as if you were typing them line by line in the shell.

I want to point out one special feature: **artifacts**. In this case, I define the *Producer* binary as an artifact of the **build** job. Artifacts are uploaded to the GitLab server and can be downloaded from there:

![Pipeline artifacts Pipeline artifacts](https://opensource.com/sites/default/files/cpp_ci_cd_gitlab_pipeline_artifacts.png)

Stephan Avenwedde (CC BY-SA 4.0)

By default, jobs in later stages automatically download all the artifacts created by jobs in earlier stages.

A `gitlab-ci.yml`

reference is available on [docs.gitlab.com](https://docs.gitlab.com/ee/ci/yaml/).

## Wrap up

The above example is an elementary one, but it shows the general principle of continuous integration. In the above section about setting up a runner I deactivated shared runners, although this is the actual strength of GitLab. You can build, test, and deploy your application in clean, containerized environments. In addition to the freely available runners for which GitLab provides a free monthly contingent, you can also provide your own container-based, self-hosted runners. Of course, there is also a more advanced way: You can orchestrate container-based runners using Kubernetes, which allows you to scale the processing of pipelines freely. You can read more about it on [about.gitlab.com](https://about.gitlab.com/solutions/kubernetes/).

As I'm running Fedora, I have to mention that Podman is not yet supported as a container engine for GitLab runners. According to gitlab-runner issue [#27119](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/27119), Podman support is already on the list.

Describing the recurring steps as jobs and combining them in pipelines and stages enables you to keep track of their quality without causing additional work. Especially in large community projects where you have to decide whether merge requests get accepted or declined, a properly configured CI approach can tell you if the submitted code will improve or worsen the project.

## Comments are closed.