GitLab での 並列 CI パターン

GitLabで並列にCIを行う方法について調査しました。

> GitLab 11.5 でテストしていますが、現時点(2018年12月)の最新版 11.6 でも同じはずです。
> もし差分が見つかれば随時反映します。（ので、詳しい方からのコメントもお待ちしています）

# 同一stageに複数jobを定義

一番基本的な戦略として、並列にビルド・デプロイする処理を、それぞれ「同じstageに属するjob」として定義する方法があります。

## .gitlab-ci.yml の例

```yaml
stages:
  - a
  - b

.job_a_common:
  script: mkdir build && touch build/${CI_JOB_NAME}.txt
  artifacts:
    paths:
      - build

job_a1:
  stage: a
  extends: .job_a_common

job_a2:
  stage: a
  extends: .job_a_common

job_a3:
  stage: a
  extends: .job_a_common

job_b:
  stage: b
  script: ls -l build/
```

## 実行結果

下の様にjobが並列に動作します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/4228/d4584a42-5e1e-b867-21dc-f12e41fcf153.png)

artifacts をまとめることができて、後のstageでそれらを扱うこともできます。

```shell-session
: job_b の出力
$ ls -l build/
total 0
-rw-r--r--    1 root     root             0 Dec 28 04:38 job_a1.txt
-rw-r--r--    1 root     root             0 Dec 28 04:38 job_a2.txt
-rw-r--r--    1 root     root             0 Dec 28 04:38 job_a3.txt
Job succeeded
```

# `parallel`による多重度指定

2018年11月にリリースされた GitLab 11.5 から、 `parallel` というキーワードが導入されました。
https://docs.gitlab.com/ee/ci/yaml/#parallel

指定した数値を多重度として、jobを並列実行することができます。
それぞれのjobの処理内容は、環境変数 `CI_NODE_INDEX` `CI_NODE_TOTAL` を使って変化させることができます。

## .gitlab-ci.yml の例

```yaml
stages:
  - a
  - b

job_a:
  stage: a
  parallel: 3
  script: mkdir build && touch build/${CI_NODE_INDEX}_of_${CI_NODE_TOTAL}.txt
  artifacts:
    paths:
      - build

job_b:
  stage: b
  script: ls -l build/
```

## 実行結果

Pipelineの表示では、ぱっと見では並列に見えませんが、よく見ると多重度`3`が見えています。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/4228/3c4fb998-1fe9-1c80-00aa-2c781021d1e2.png)

Jobsの表示では、確かに並列にJobが動いていることがわかります。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/4228/fba41724-ac05-09ae-a4ff-ec79b7734376.png)

artifactsについても、複数Jobを定義した場合と同様に扱うことができます。

```shell-session
: job_b の出力
$ ls -l build/
total 0
-rw-r--r--    1 root     root             0 Dec 28 04:45 1_of_3.txt
-rw-r--r--    1 root     root             0 Dec 28 04:45 2_of_3.txt
-rw-r--r--    1 root     root             0 Dec 28 04:46 3_of_3.txt
Job succeeded
```

# ファイル単位で並列にビルド （`parallel`の応用)

`parallel`では並列多重度を静的に指定することしかできません。
例えば「ファイル毎の並列ビルド」など、多重度を動的に決定したいことはあると思います。

ですが、現時点(2018年末、GitLab 11.6リリース時点)でそれを行う方法はありません。

> 機能の提案はされているので、将来的には可能になる可能性はあります。
> https://gitlab.com/gitlab-org/gitlab-ce/issues/23455

少し妥協して、適当な多重度で立ち上げたJobで対象ソースを切り替えることで、ビルドを平準化することができます。
以降はその例です。

## repository構造

```shell-session
$ tree
.
├── 1.txt
├── 2.txt
├── 3.txt
├── 4.txt
└── 5.txt

0 directories, 5 files
```
```shell-session
$ head *.txt
==> 1.txt <==
11111

==> 2.txt <==
22222

==> 3.txt <==
33333

==> 4.txt <==
44444

==> 5.txt <==
55555
```

## .gitlab-ci.yml

```yaml
stages:
  - a
  - b

job_a:
  stage: a
  parallel: 3
  script:
    - mkdir build
    - NF=$(ls -1 *.txt | wc -l)
    - ls -1 *.txt
      | awk "(NR-1)%${CI_NODE_TOTAL}==${CI_NODE_INDEX}-1"
      | xargs -P$((NF/CI_NODE_TOTAL+1)) -I_ sh -c "cat _ | tee build/_"
  artifacts:
    paths:
      - build

job_b:
  stage: b
  script: head build/*.txt
```

途中で少々複雑な `awk` の式がありますが、行数の剰余を取って、順にJobに割り振っているだけです。
また、Job内でもプロセスを並列化するため、対象ソースの数から必要なプロセス数を計算して `xargs` に `-P` オプションで渡しています。

## 実行結果

Pipelineの見た目は、前のものと同じなので割愛します。

それぞれのJobの出力を見ると、ちゃんと処理が平準化されていることがわかります。

```
: job_a 1/3 の出力
$ mkdir build
$ NF=$(ls -1 *.txt | wc -l)
$ ls -1 *.txt | awk "(NR-1)%${CI_NODE_TOTAL}==${CI_NODE_INDEX}-1" | xargs -P$((NF/CI_NODE_TOTAL+1)) -I_ sh -c "cat _ | tee build/_"
44444
11111
```
```
: job_a 2/3 の出力
$ mkdir build
$ NF=$(ls -1 *.txt | wc -l)
$ ls -1 *.txt | awk "(NR-1)%${CI_NODE_TOTAL}==${CI_NODE_INDEX}-1" | xargs -P$((NF/CI_NODE_TOTAL+1)) -I_ sh -c "cat _ | tee build/_"
22222
55555
```
```
:job_a 3/3 の出力
$ mkdir build
$ NF=$(ls -1 *.txt | wc -l)
$ ls -1 *.txt | awk "(NR-1)%${CI_NODE_TOTAL}==${CI_NODE_INDEX}-1" | xargs -P$((NF/CI_NODE_TOTAL+1)) -I_ sh -c "cat _ | tee build/_"
33333
```

job_b でも build 以下にファイルが残っているため、意図したとおりに動作したことがわかります。

```shell-session
: job_b の出力
$ head build/*.txt
==> build/1.txt <==
11111

==> build/2.txt <==
22222

==> build/3.txt <==
33333

==> build/4.txt <==
44444

==> build/5.txt <==
55555
Job succeeded
```

-----

以上です。