[GitLab CI] Monorepo(単一レポジトリ)で複数デプロイ可能なディレクトリがある場合の構成案

`マイクロサービス`を`モノレポ`(単一レポジトリ)で管理している場合、ルートに置かれた`.gitlab-ci.yml` のみで記述しているとファイルが肥大化する傾向にあります。 また、フロント／バックエンドのソースを単一レポジトリに含めている場合も同様です。

マイクロサービスなどの独立してデプロイ可能な単位のソースを、別々のレポジトリで管理する場合もあると思いますが、特にプログラムの規模がさほど大きくない場合は、単一のレポジトリで管理したほうが、扱いやすいと思います。


ちなみに Wikipediaには、GoogleやFacebookなどでも非常に大規模なソースを `モノレポ` 構成で管理しているように書いてあります。[^1] (英語ではmonorepo=モノレポ）

[^1]: [出典： Wikipedia - Monorepo](https://en.wikipedia.org/wiki/Monorepo)

> Google,[5] Facebook,[6] Microsoft,[7] Uber,[8] Airbnb and Twitter[9] all employ very large monorepos with varying strategies to scale build systems and version control software with a large volume of code and daily changes.

今回は、`GitLab 12.7` で導入された、`親子関係のパイプライン` と `Only` と `Changes` キーワードを組み合わせた構成をご紹介します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599028/ba55871d-d86b-d8e5-9575-a565638d2184.png)


`モノリシック`なサービスの場合や、単純に `.gitlab-ci.yml` が肥大化した場合の対処法としては、 `include` キーワードでファイルを分けたり、 `extends` キーワードでリファクタリングする方法があると思います。

# 構成案

## レポジトリのディレクトリ構成例

```shell
+ .gitlab-ci.yml  # ルートにあるCI/CD構成スクリプト
+ my_service_a # デプロイ可能なソースディレクトリ
|  + .my_service_a.yml
|  + ...
+ my_service_b # 別のデプロイ可能なソースディレクトリ
|  + .my_service_b.yml
|  + ...
|  ...
```

* 仮にサービスのディレクトリが更に深いサブディレクトリにあっても問題なし。

## 親パイプラインの定義

`.gitlab-ci.yml` の中身

```yml
image: alpine:3.7  # 参考用に軽いalpineに変更

stages:
  - triggers

my_service_a:
  stage: triggers
  only:
    refs: # ブランチを限定する場合
      - develop
      - stage
    changes: # 以下ディレクトリ内に変更があった場合のみトリガーされる
      - my_service_a/**/*
  trigger:
    include: my_service_a/.my_service_a.yml # 子パイプライン定義
    strategy: depend

my_service_b:
  stage: triggers
  only:
    refs:  # ブランチを限定する場合
      - develop
      - stage
    changes: # 以下ディレクトリ内に変更があった場合のみトリガーされる
      - my_service_b/**/*
  trigger:
    include: my_service_b/.my_service_b.yml # 別の子パイプライン定義
    strategy: depend
```

### ポイント

* ステージはtriggersのみ
* デプロイ可能な単位に列挙（下記では my_service_a, my_service_b)
* only > refs は ブランチを限定
* only > changes は プッシュ時に変更があった場合のみトリガーさせる
* trigger キーワードで、参照先のスクリプトを指定
* strategy: depend で子パイプラインがすべて成功するまで親も成功ステータスにしない。


## 子パイプラインの定義

`.my_service_a.yml` (中身は自由ですが参考までに一応記載）

```yml
image: alpine:3.7

stages:
  - one
  - two
  - three

stage_one_one:
  stage: one
  script:
    - echo "ONE"

stage_two_one:
  stage: two
  script:
    - echo "TWO ONE"

stage_two_two:
  stage: two
  allow_failure: false
  only:
    refs:
      - develop
  script:
    - echo "TWO TWO"

stage_three_two:
  stage: three
  when: on_success
  only:
    refs:
      - develop
  script:
    - echo "THREE TWO"
```


## トリガーさせる

developブランチで、`my_service_a/` または `my_service_b/` のディレクトリ配下のソースをコミットしてリモートへプッシュ。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599028/cda1ebc5-ee6e-449d-aba9-82b586c18bb3.png)


## まとめ

* 各種デプロイ可能なディレクトリのルートにCI/CDの構成を書くことで分かりやすくなった。
* Changes キーワードを利用することで、コミット時に変更がある場合のみに実行し、無駄な実行を省いた。
* ~~デメリットとして、小パイプラインのスクリプトのコード共通化の範囲は一部限定される。（例： .my_service_a.yml ⇔ .my_service_b.ymlの間）~~ `include` キーワードで共通のコンフィグを外部ファイル化すれば、サービス間のコンフィグのリファクタリングも可能。

## 参考リンク
* [GitLab CI/CD Pipeline Configuration Reference](https://docs.gitlab.com/ee/ci/yaml/)
* [How to get started with Parent-child pipelines](https://about.gitlab.com/blog/2020/04/24/parent-child-pipelines/)