GitLab CIのinclude機能を試す

GitLab CIには外部のYAMLファイルを取り込む方法として[`include`](https://gitlab-docs.creationline.com/ee/ci/yaml/#include)というキーワードが用意されている。これを使うとどのような感じで`.gitlab-ci.yml`が分割出来るかを調べた時のメモ。

## `include`とは
`.gitlab-ci.yml`とは別にパイプラインを記述しておいて、それを引っ張ってくる機能である。これにより`.gitlab-ci.yml`を分割できるため、以下の効果が期待できる。
- 可読性の向上
- 設定の外出しによるメンテナビリティ向上

`include`には4種類の外部ファイルの引っ張り方が用意されている。

|方法|説明|
|---|---|
|local|同一プロジェクト内の別ファイルをinclude|
|file|同一GitLab内の別プロジェクト内のファイルをinclude|
|remote|リモートで公開されているファイルのURLを指定してinclude|
|template|GitLabが提供しているテンプレートをinclude|

## 事前準備
検証用にGitLab上に以下の空のプロジェクトを作成し、ほぼ何もしない`.gilab-ci.yaml`を作成する。ディレクトリ構造は以下となる。
```
.
├── .gitlab-ci.yml
└── README.md
```
作成した`.gitlab-ci.yml`は以下となる。
```yaml:/.gitlab-ci.yml
stages:
  - build

build-job:
  stage: build
  script:
    - echo "dummy"
```
このパイプライン(build stage)の実行結果は以下。
```sh
Executing "step_script" stage of the job script
$ echo "dummy"
dummy
Job succeeded
```

## local
`local`は同一プロジェクト内の別ファイルをincludeする。
この機能の確認のために、最初に別ファイルを格納するディレクトリを作成する。
```
mkdir .gitlab-ci
```
次に適当なジョブを記載したファイル(`.gitlab-ci/included.yml`)を作成する。
```yaml
cat << EOF > .gitlab-ci/included.yml
test:
  stage: build
  script:
    - echo "included"
EOF
```
事前準備の際に作成した`/.gitlab-ci.yml`で上記の`.gitlab-ci/included.yml`を呼び出すように修正する。
```yaml:/.gitlab-ci.yml
stages:
  - build

include:
  - local: .gitlab-ci/included.yml

build-job:
  stage: build
  script:
    - echo "dummy"
```
これをコミットすると、2つのジョブが平行して実行される。
`.gitlab-ci/included.yml`で実装した`test`ジョブの出力結果は以下となった。
```sh
Executing "step_script" stage of the job script
$ echo "included"
included
Job succeeded
```
ちなみに[公式のサンプル](https://docs.gitlab.com/ee/ci/yaml/includes.html)にも説明があるが、`local`と`remote`に関しては省略可能であるため以下のようにも書ける。
```yaml
include:
  - .gitlab-ci/included.yml
```
ネストも出来るようなので、こちらも試してみる。デフォルトの設定値を定義したユースケースを想定し、`config-defaults.yml`を作成する
```yaml
cat << EOF > .gitlab-ci/config-defaults.yml
variables:
  DEFAULT_VAL_A: "default_val_a"
  DEFAULT_VAL_B: "default_val_a"
EOF
```
`.gitlab-ci/included.yml`を以下のように修正する。
```yaml:/.gitlab-ci/included.yml
include: .gitlab-ci/config-defaults.yml
test:
  stage: build
  script:
    - echo "included"
```
`.gitlab-ci.yml`を以下のように修正する。
```yaml:/.gitlab-ci.yml
variables:
  DEFAULT_VAL_B: "b"
stages:
  - build

include:
  - local: .gitlab-ci/included.yml

build-job:
  stage: build
  script:
    - echo "$DEFAULT_VAL_A"
    - echo "$DEFAULT_VAL_B"
```
これをpushした時の`build-job`の結果は以下となった。
```sh
Executing "step_script" stage of the job script
$ echo "$DEFAULT_VAL_A"
default_val_a
$ echo "$DEFAULT_VAL_B"
b
Job succeeded
```
ネスト先の`.gitlab-ci/config-defaults.yml`から値が引っ張れることと、値が上書き出来ることが確認できた。

## file
[file](https://gitlab-docs.creationline.com/ee/ci/yaml/#includefile)は同一のGitLab内の別プロジェクトからファイルを引っ張ってくる。
`file:`と同列に`project:`、`ref:`も指定し、プロジェクト名およびGit Refを指定する。`ref`はデフォルトでHEADになっており、スキップすることも可能。
ここでは同一GitLab内に`myapp/common-gitlab-ci`を作成し、includeするYAMLを配置する。
`git clone`したディレクトリで以下を実行して作成する。
```yaml
mkdir .gitlab-ci
cat << EOF > ./.gitlab-ci/common.yaml
common:
  stage: build
  script:
    - echo "common"
EOF
```
次に最初に作成したプロジェクトの`included.yml`を以下のように修正する
```yaml:/.gitlab-ci/included.yml
include:
  - .gitlab-ci/config-defaults.yml
  - project: myapp/common-gitlab-ci
    file: .gitlab-ci/common.yaml
test:
  stage: build
  script:
    - echo "included"
```
これをpushしてパイプラインを実行すると、`build-job`、`test`、`common`の3つのジョブが実行され、commonの結果を見ると外部プロジェクトから引っ張ってこれているのが分かる。
```sh
Executing "step_script" stage of the job script
$ echo "common"
common
Job succeeded
```
これにより、開発リポジトリを変更することなく外部リポジトリのYAMLを修正するだけでパイプラインを変更することが実証できた。
複数プロジェクトでGitLab CIを使う際、共通パラメータなどを格納するのは`file`で指定するプロジェクトにしておくと良さそうだ。

## remote
`remote`は外部公開しているYAMLのURLを直接指定する。検証用に先程作成したプロジェクト`myapp/common-gitlab-ci`をPublic設定に変更し、適当なYAMLを追加してrawのURLを指定することで検証しようとしたのだが、GitLab側でPublicに変更出来なかったため、GitHubで同等のプロジェクト(リポジトリ)を作成して代用した。
以下のファイルを作成する。
```yaml
cat << EOF > ./.gitlab-ci/remote.yml
remote:
  stage: build
  script:
    - echo "remote"
EOF
```
GitHubにpushした結果、以下でYAMLが取得できるようになった。
https://raw.githubusercontent.com/imurata/common-gitlab-ci/main/.gitlab-ci/remote.yml

これを`included.yml`で呼び出すよう、以下のように`remote`行を追加する。
```yaml:/.gitlab-ci/included.yml
include:
  - .gitlab-ci/config-defaults.yml
  - project: myapp/common-gitlab-ci
    file: .gitlab-ci/common.yaml
  - remote: https://raw.githubusercontent.com/imurata/common-gitlab-ci/main/.gitlab-ci/remote.yml
test:
  stage: build
  script:
    - echo "included"
```
なお、前述のように`remote:`は省略することも可能だが、ここでは可読性を重視して省略していない。
パイプラインを実行した結果、`remote`のジョブが増え、以下の結果を得た。
```sh
Executing "step_script" stage of the job script
$ echo "remote"
remote
Job succeeded
```
YAMLを共通化したいが同一のGitLabにプロジェクトを置けないようなケースではremoteが利用できそうだ。

## template
`template`はGitLabが持っている[テンプレート](https://gitlab.com/gitlab-org/gitlab/tree/master/lib/gitlab/ci/templates)を呼び出すことが出来る。
ここでは[`Bash.gitlab-ci.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Bash.gitlab-ci.yml)を試しに呼び出してみる。
`Bash.gitlab-ci.yml`が内部で呼び出しているstageは`build`、`test`、`deploy`なので、`/.gitlab-ci.yml`側でもそれらを使うように変更する。
修正後の`/.gitlab-ci.yml`は以下となる。
```yaml:/.gitlab-ci.yml
variables:
  DEFAULT_VAL_B: "b"
stages:
  - build
  - test
  - deploy

include:
  - local: .gitlab-ci/included.yml

build-job:
  stage: build
  script:
    - echo "$DEFAULT_VAL_A"
    - echo "$DEFAULT_VAL_B"
```
また`included.yml`も`template`を使うように修正する。
```yaml:/.gitlab-ci/included.yml
include:
  - .gitlab-ci/config-defaults.yml
  - project: myapp/common-gitlab-ci
    file: .gitlab-ci/common.yaml
  - remote: https://raw.githubusercontent.com/imurata/common-gitlab-ci/main/.gitlab-ci/remote.yml
  - template: Bash.gitlab-ci.yml
test:
  stage: build
  script:
    - echo "included"
```
これでパイプラインを実行すると、`build`ステージ以外に`Bash.gitlab-ci.yml`で定義していた`test`、`deploy`ステージが実行され、`build`ステージでは`Bash.gitlab-ci.yml`が定義していた`build1`ジョブが実行されることが分かる。

`template`に関しては元々用意してあるものをどう活用するかというよりは、自作したものをGitLab内でテンプレート化した際に使うものだと思われる。
テンプレートの作成については[Development guide for GitLab CI/CD templates](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/development/cicd/templates.md)が参考になるが、ここでは本旨ではないため触れない。

## まとめ
`include`を使うことで単なるファイル分割ではなく、リポジトリ外のYAMLを引っ張ってパイプラインを実行することが出来、`.gitlab-ci.yml`をプロジェクト単位で管理するリスクを軽減できる見込みを得た。
インフラ部門がGitLab CIによるCI/CDを推進する場合、開発部門がプロジェクト毎にジョブを作り込むよりも、インフラ部門で標準化やコンテナイメージの最新化を図ったYAMLを作成してincludeで開発部門に使ってもらうことで、組織全体がよりセキュアに持っていけるのではないかと思う。