爆速でオートスケールCI/CD環境（GitLab CI）立ち上げ

この記事は[株式会社クロノス](https://www.kronos.jp/)の[「~2020年春~勝手にやりますアドベントカレンダー」](https://qiita.com/beeeyan/items/342d51fa301bcf81e9c0)の8日目の記事です！

## TL;DR

- GitLab & GitLab CIを
- Docker(docker-compose)使って
- サクッと立てちまう
- （しかもオートスケール対応しちまう）

企業内での開発などクローズドな開発はよくあると思います。
リポジトリやCIなんかも、外部サービスは好まれず、、そんな環境はまだまだ沢山あるんだと思います。
で、そんな中でもちゃんとCI/CDしたい！
でも、、GitLabとか知ってるけど、、セットアップとか結構大変そう…とか思ってるヒトに向けた記事です。
まずは動かすところまで最速で行こう。

*注意: とりあえず動かすまでなので、あんまり細かいところは書いてません :bow:*

## 前提

今回はAWS上でDockerやdocker-composeを使ってセットアップします。
ここらへんがある程度触れるヒト向けの記事です。
(とは言え、何回か触ったことあるぐらいで十分だと思います)

### 主な登場ツール達

#### GitLab

ひとことで言うと「GitHubクローン」。
Gitリモートリポジトリの管理を中心にIssue管理やマージリクエスト（プルリクエスト）＆レビュー機能がある。Ruby製。

#### GitLab CI

GitLabへのGitプッシュなどをトリガーにジョブを実行。ビルドパイプラインを構築できるCI/CDツール

#### その他

- AWS EC2
- Docker
- docker-compose

もはや説明不要と思われるため割愛。

## レシピ

### 1. まずは動かす場所を用意

GitLabを動かすサーバを用意しましょう。
今回はEC2インスタンス上で動かします。

- インスタンスタイプ: t2.medium
- AMI: AmazonLinux2

その他はお好みで。

詳しく手順が知りたい方は[こちら](https://qiita.com/C910/items/772329aa2d59a7f9236d)とかを参考に。

1点注意としては、インスタンスタイプが`t2.medium`より小さいとGitLab自体が動いてくれません（なのでAWS無料枠じゃ試せない...:angel:）

### 2. Docker(+ docker-compose)インストール

今回のレシピでは、DockerコンテナでGitLabを実行します。
逆にDocker以外はインストール不要。

AmazonLinux2上でのインストールコマンドは↓

```
sudo yum update -y

# Dockerインストール
sudo yum install -y docker
sudo service docker start
sudo usermod -a -G docker ec2-user
sudo systemctl enable docker

# docker-composeインストール
sudo curl -L https://github.com/docker/compose/releases/download/1.25.4/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
```

特筆することはなく、、
一応インストールされたかバージョン確認しときましよう。

```
$ docker -v
Docker version 18.09.9-ce, build 039a7df
$ docker-compose -v
docker-compose version 1.25.4, build 8d51620a
```

### 3. GitLabのセットアップ

ここから本題。
まずは本体であるGitLabをセットアップ。

GitLabはありがたいことにDockerイメージを公開してくれてる。なので、細かいインストール作業は不要。

`docker-compose`で起動するための設定ファイル`docker-compose.yml`を書きます。

``` docker-compose.yml
version: '3'
services:
  gitlab:
    image: 'gitlab/gitlab-ce:latest'
    restart: always
    hostname: 'your_domain'
    environment:
      GITLAB_OMNIBUS_CONFIG: |
        external_url 'http://your_domain/'
        gitlab_rails['gitlab_shell_ssh_port'] = 4022
    ports:
      - '80:80'
      - '443:443'
      - '4022:22'
    volumes:
      - '/srv/gitlab/config:/etc/gitlab'
      - '/srv/gitlab/logs:/var/log/gitlab'
      - '/srv/gitlab/data:/var/opt/gitlab'
```

細かい説明は端折りますが、`GITLAB_OMNIBUS_CONFIG`（Dockerコンテナ内で使用される環境変数）がGitLab自体の詳細な設定ができる部分。
この設定は[オフィシャル](https://docs.gitlab.com/omnibus/docker/#install-gitlab-using-docker-compose)でも紹介されています。（ほぼそのまま、、）

↑ファイルをEC2インスタンスの適当なパスに配置して実行してみましょう。

```
cd ~
vim docker-compose.yml       # ↑を参考にファイル編集
docker-compose up -d gitlab  # GitLab起動
```

たったこれだけで、プライベートなGitリポジトリ環境が起動しました！
（t2.mediumだと少し起動に時間かかるんで数分してから）EC2へhttpアクセスしてみましょう。

*初回アクセス時にrootユーザーパスワードとファーストユーザーの登録を求められます。お好みで。*

### 4. GitLab CIセットアップ

さてさてここからが真打ち。
CI/CD環境として`GitLab CI`をセットアップしていきます。

GitLab CIの実態としては `GitLab Runner` というアプリケーションになります。
`GitLab Runner`がGitLabの変更を検知して、Gitリポジトリから最新srcを取得、設定ファイルに従ってビルド処理を実行する仕組みです。

GitLab Runnerも本体同様Dockerイメージが公開されているので、それを使っていきます。

↑で作成した `docker-compose.yml` に↓のrunner設定を書き加えます。

``` docker-compose.yml
  runner:
    image: 'gitlab/gitlab-runner:latest'
    restart: always
    volumes:
      - /srv/gitlab-runner/config:/etc/gitlab-runner
      - /var/run/docker.sock:/var/run/docker.sock
```

できたら起動

```
docker-compose up -d runner  # GitLab Runner起動
```

起動しただけだと、まだCIはできません。
`GitLab Runner`と`GitLab`を接続する設定が必要です。

設定をするための事前準備として、↓の2つの情報をGitLabにアクセスして取得します。

1. URL（GitLabのURL）
2. トークン

この情報はGitLab上の `/admin/runners` パスにアクセスすることで確認できます。
↑パスは管理者権限でしかアクセスできません。`rootユーザー`でログインしてアクセスしましょう（user: root / password: 初回アクセス時に設定したパスワード）

2つの情報が確認できたら、↓コマンドを実行して設定していきます。

```
docker-compose exec runner gitlab-runner register
```

コマンドライン上で質問に答えていきます。（実際の質問内容は[こちら](https://docs.gitlab.com/runner/register/)）
設定が完了すると、↓パスに設定ファイルが生成されます。

`/srv/gitlab-runner/config/config.toml`

以下のように設定すると恐らくこんな感じの設定になっているはず。

- gitlab-ci coordinator URL: http://your_domain/
- gitlab-ci token: token
- gitlab-ci description for this runner: default-runner
- gitlab-ci tags for this runner: （空）
- executor: docker
- Docker image: alpine

``` /srv/gitlab-runner/config/config.toml
concurrent = 1
check_interval = 0

[session_server]
  session_timeout = 1800

[[runners]]
  name = "default-runner"
  url = "http://your_domain/"
  token = "token"
  executor = "docker"
  [runners.custom_build_dir]
  [runners.cache]
    [runners.cache.s3]
    [runners.cache.gcs]
  [runners.docker]
    tls_verify = false
    image = "alpine"
    privileged = true
    disable_entrypoint_overwrite = false
    oom_kill_disable = false
    disable_cache = false
    volumes = ["/cache"]
    shm_size = 0
```

これでとりあえずのCI環境は完了です。

なにかサンプルsrcがあれば実際にGitLabでリポジトリを作成してpushしてみてください。

プロジェクト側に配置するCIパイプラインの設定方法は↓

- https://docs.gitlab.com/ee/ci/introduction/
- https://qiita.com/ynott/items/1ff698868ef85e50f5a1

### 5. GitLab CI Runnerをオートスケール対応

ソロプロジェクトであれば前段の設定まででも十分使えると思うのでここはいらないかもしれませんが、
今回はもう少し踏み込んで、複数エンジニアが活躍するプロジェクトを想定します。

複数人の開発になると、当然同時にpushすることもあり、CI/CDジョブを並列で処理する必要があります。
また、開発が進むとビルドやテストに必要なリソースはかなり大きくなる場合もあります。
（↑設定のような単体サーバでビルドは結構早めに限界を迎えます）

そんな環境のためにGitLabはRunnerをオートスケールする手段を提供しています。
https://docs.gitlab.com/runner/configuration/runner_autoscale_aws/

具体的にはCIビルドジョブ毎にRunnerをEC2インスタンスとして起動することで、同時多発的に発生するビルド処理をさばいていくと。素晴らしい。

先に結論を書くと、設定ファイルを↓のように書き換えるとそうなります。

``` /srv/gitlab-runner/config/config.toml
concurrent = 10
check_interval = 0

[session_server]
  session_timeout = 1800

[[runners]]
  name = "default-runner"
  url = "http://your_domain/"
  token = "token"
  executor = "docker+machine"
  [runners.custom_build_dir]
  [runners.cache]
    [runners.cache.s3]
    [runners.cache.gcs]
  [runners.docker]
    tls_verify = false
    image = "alpine"
    privileged = true
    disable_entrypoint_overwrite = false
    oom_kill_disable = false
    disable_cache = false
    volumes = ["/cache"]
    shm_size = 0
  [runners.machine]
    IdleCount = 1
    IdleTime = 1800
    MaxBuilds = 100
    OffPeakPeriods = [
      "* * 0-9,18-23 * * mon-fri *",
      "* * * * * sat,sun *"
    ]
    OffPeakIdleCount = 0
    OffPeakIdleTime = 1200
    MachineDriver = "amazonec2"
    MachineName = "gitlab-docker-machine-%s"
    MachineOptions = [
      "amazonec2-access-key=accesskey",
      "amazonec2-secret-key=secret",
      "amazonec2-region=us-east-1",
      "amazonec2-zone=d",
      "amazonec2-vpc-id=vpc-xxxxxx",
      "amazonec2-subnet-id=subnet-xxxxxx",
      "amazonec2-use-private-address=true",
      "amazonec2-tags=runner-manager-name,gitlab-aws-autoscaler,gitlab,true,gitlab-runner-autoscale,true",
      "amazonec2-security-group=default",
      "amazonec2-instance-type=t2.micro",
      "amazonec2-request-spot-instance=true",
    ]
```

ポイントをまとめます。

- `concurrent = 10`

同時並列実行数を決めます

- `executor = "docker+machine"`

*docker*から*docker+machine*へ変更しました。

Docker Machineは "仮想マシン上に Docker Engine をインストールするツール" であり、ネットワークを経由した先のインスタンスにもDockerをセットアップした上で実行環境として接続できるようになるツールです。
[Docker Machine オフィシャル](http://docs.docker.jp/machine/overview.html)

この仕組を使って、EC2インスタンスをDocker化し、ジョブ実行環境として使用します。（とてもおもしろい！）

今回はAWS EC2でのセットアップですが、Docker Machineが扱える接続先ならどこでもこの仕組みでノード化できるので、[DigitalOcean](https://www.digitalocean.com/?utm_campaign=DO_Dev_Awareness_G_Search_B_Generic_Alpha&utm_adgroup=digital_ocean&_keyword=digital%20ocean&_device=c&_copytype=&_adposition=&utm_medium=brand_sem&utm_source=google&_dkitrig=&_2dkitrig=&gclid=Cj0KCQjw9ZzzBRCKARIsANwXaeK6E8E8RXKFUVTGVl-FJAJR2IAabu5BsfTXvujGcoKYhQ1A1Of_EHcaApKvEALw_wcB)なんかも対象にできます。

素晴らしい仕組みなんですが、Docker MachineがWindowsOSには対応していない、、ので"docker+machine"executorのオートスケールは今のところ**Windows非対応のようです**
https://gitlab.com/gitlab-org/gitlab-runner/issues/4582

- `MachineOptions`

ここが今回の設定の一番肝な部分ですが、この設定がAWS上に自動作成されるEC2インスタンスの各種オプションになります。

設定内容は[Docker MachineのAWSオプション](https://docs.docker.com/machine/drivers/aws/#options)と同じなので、詳しくは←を参照しながら設定を調整してください。
ここもいくつかポイントだけ紹介しておきます。

- `amazonec2-zone`

EC2インスタンスを作成するアベイラビリティゾーンの設定です。
今回のデモ環境を適当に作ったらデフォルトで`us-east-1d`で作成してしまい、この設定がないとデフォルトAZは`a`で自動設定されるのでVPCがないとか怒られました、、

- `amazonec2-security-group`

EC2インスタンスに設定されるセキュリティグループの指定です。
セキュリティグループIDではなく**セキュリティグループ名**で指定する必要があります。

ここの指定がないとdocker-machineというsshとDockerポートが**IPフル開放(0.0.0.0/0)**のセキュリティグループが自動生成されて**勝手に設定されます**。

折角のブライベートな環境が台無しです。。

- `amazonec2-request-spot-instance`

EC2にはスポットインスタンスという素敵な仕組みがあります。
すごいざっくり言うと、空いてるいるインスタンスが安く叩き売られる仕組みです。
必ず安くなるわけではないですし、安くなる分強制停止されたりするらしいですが、ビルド処理にそこまで高い可用性を求める必要もないかと思われるので、お財布に優しい方がより良いかと。
（もちろんCI/CDの処理内容によります、適切に判断してください！）

---

説明は一旦ここまで。

設定ファイル(config.toml)を書き換えるとrunnerが自動で読み込み直してくれます。
CIを実行するとEC2スポットインスタンスが自動で作られて処理されるはず。

試してみてください。

## まとめ

*便利な世の中になったもんだ！*

"こうしたい" がすぐに実現できる。（実際今回も2つの設定ファイルと数コマンド実行だけでそれなりに動く環境ができた）
特にDocker周りの知識があれば、環境周りや下準備の初速をかなり上げれると思います。
またスケールしやすいことや、環境の情報がすべてコード化できる点もすごいメリット。
本質に注力するためにも、その他のことにエネルギーをさかない技術も大事。

***Enjoy!!***

### 感謝&参考資料

- https://qiita.com/C910/items/772329aa2d59a7f9236d
- https://qiita.com/str416yb/items/2932052a6ead78c167e4
- https://qiita.com/TomoyukiSugiyama/items/b17800d4e142e3dc549b
- https://aimstogeek.hatenablog.com/entry/2020/03/02/080000
- https://dev.classmethod.jp/ci/gitlab-runner-ci-cd-1/
- https://tech.myrefer.co.jp/entry/infrastructure_1
- https://docs.gitlab.com/omnibus/docker/
- https://docs.gitlab.com/runner/

あと、このアドベントカレンダーの仲間たち:metal: