GitLab CIの基本的な流れ

# GitLab CI

GitLabから他のツールに連携して、ビルド、テスト、デプロイなどのCI連携ができる機能です。
使ってみると意外と色んなことが出来て面白そうなので、作り方をメモしておきます。

# 今回の構成と動作の流れ

### バージョンなど

OS...Debian 9
GitLab...12.6
GitLab Runner...12.6
Ansible...2.10

### 流れ

 - Debian_1にDockerがインストールされていて、GitLabがコンテナで動作している。ここにレポジトリがあり、ソースが保存される。
 - 別のDebian_2にGitLab Runner(後述)とAnsibleがインストールされている。
 - Ansibleはさらに別のDebian_3を構築する。

![gitlab.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215103/6c4ff134-89cf-4545-2eb8-75b570330654.png)

1. pushやmergeなどのタイミングでGitLab本体がGitLab runnerに通知を送る
2. Runnerは実行条件に合致していたら動作を開始する
3. Runnerはまずgit cloneコマンドでレポジトリの内容をローカルにコピーして、そのレポジトリに`cd`する
4. Runnerはそのノード上で`ansible-playbook`コマンドを実行する
5. Ansibleは普通に実行した時と同じようにdebian_3を構築する

 - 2のところの実行条件と、4で実行するコマンドはジョブの定義として作ってあげる必要があります。
 - また、5のところでAnsibleがちゃんとDebian_3を構築できるようにinventryを設定してあげる必要があります。
環境変数とかを使ってうまくやってください。

# GitLab Runnerのセットアップ

上の図でDebian_2にあるGitLab Runnerをインストールします。

## Runnerの種類

GitLab Runnerには大きくShared RunnerとSpecific Runnerがあります。
自分もよくわかってないので、詳しくは[ここ](https://docs.gitlab.com/ee/ci/runners/)を見てください。

### Shared Runner

複数のプロジェクトで共通して利用できるRunner。
いつどれが使われるかはRunnerとCIジョブに付けられたタグで決まります。

### Specific Runner

特定のプロジェクトでのみ利用されるRunner

## Runnnerのインストール

GitLabの公式サイトから`.deb`のバイナリを落としてくることができます。
Debian以外にもRHEL, Windows, Mac, FreeBSDなど複数に対応します。

これだけでインストールが完了します。

```shell
root@debian:~# dpkg -i gitlab-runner_amd64.deb
```


## Runnerの初期設定

Runner側でコマンドをいくつか打つことで、GitLabにRunnerとして登録することができます。

```shell
root@debian:~# gitlab-runner register
Runtime platform                                    arch=amd64 os=linux pid=4496 revision=1b659122 version=12.8.0
Running in system-mode.

Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com/):
http://10.91.77.8/ #GitLabサーバーのURLを入力
Please enter the gitlab-ci token for this runner:
dx5bFN4PJQ9XNUj_Vxbf #GitLabのadmin画面から取得できるトークンを入力
Please enter the gitlab-ci description for this runner:
[debian]: Ansible runner #このRunnerの説明を入力
Please enter the gitlab-ci tags for this runner (comma separated):
ansible #コンマ区切りでタグを入力
Registering runner... succeeded                     runner=dx5bFN4P
Please enter the executor: docker+machine, docker-ssh+machine, kubernetes, docker, docker-ssh, shell, virtualbox, custom, parallels, ssh:
shell #ジョブの実行方法を入力
Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded!
```

ジョブの実行方法はローカルでシェルを実行する、SSHでリモートを操作する、Dockerコンテナを立ち上げるなど複数から選べます。

# `.gitlab-ci.yml`の作成

環境が完成したら実際にCIを実行する定義`.gitlab-ci.yml`を作ります。
このファイルはレポジトリのルートに置いておき、他のソースと同じようにバージョン管理されます。

今回はレポジトリに登録されたAnsible playbookを自動実行するように以下のようなYAMLを書きました。
詳細な文法などは[GitLabの公式サイト](https://docs.gitlab.com/ee/ci/yaml/README.html)を見てください。

```yaml:.gitlab-ci.yml
# ステージ定義
stages:
  - test
  - deploy
  - result

# Pushするたびに文法チェックをする
sanity_check:
  stage: test
  script:
    - "ansible-playbook site.yml --syntax-check"
  only:
    - push
  tags:
    - ansible

# MRを作成したらDRY RUNで実行し、エラーにならないかを確認
run_playbook_dry:
  stage: test
  script:
    - ansible-playbook -i ansible_hosts.ini site.yml --check
  only:
    - merge_requests
  tags:
    - ansible

# develブランチにマージされたタイミングで自動デプロイ
run_playbook:
  stage: deploy
  script:
    - ansible-playbook -i ansible_hosts.ini site.yml
  only:
    - devel
  tags:
    - ansible

# ジョブが失敗したらMRにコメントを残す
send_failure_message:
  stage: result
  script:
    - 'MR_IID=$(curl --request GET --header "Private-Token: $API_PRIVATE_TOKEN" "http://10.91.77.8/api/v4/projects/$CI_PROJECT_ID/repository/commits/$CI_COMMIT_SHA/merge_requests" --insecure | jq --raw-output ".[0].iid")'
    - 'PROJECT_URL=$(curl --request GET --header "Private-Token: $API_PRIVATE_TOKEN" "http://10.91.77.8/api/v4/projects/$CI_PROJECT_ID" --insecure | jq --raw-output ".web_url")'
    - 'curl --request POST --header "Private-Token: $API_PRIVATE_TOKEN" --data "body=CIジョブに失敗しました。<br><a href=$PROJECT_URL/pipelines>パイプラインの詳細</a>から内容を確認してください。" http://10.91.77.8/api/v4/projects/$CI_PROJECT_ID/merge_requests/$MR_IID/notes --insecure'
  when: on_failure
  only:
    - merge_requests
  tags:
    - ansible
```

まずGitLabから対象のサーバーにレポジトリが`git clone`されます。
前述の通りクローンした後はそのディレクトリに`cd`されるので、scriptブロックに記載するコマンドはすべてレポジトリのルートから見える相対パスで書きます。

## ジョブ

YAMLの一番上の階層に書いてあるもの(予約語以外のもの)がジョブで、この単位でスクリプトや実行条件を指定します。
上のスクリプトでいうと`sanity_test`や`run_playbook_dry`とか`send_failure_message`ですね。

## stages

順序立ててジョブを実行する必要がある場合、stagesを定義してジョブを所属させると順番を決めて実行できます。
同じstageで複数のジョブが定義されている場合はパラレルで実行されます。
上のでいうと、まず`test`に所属するジョブが実行され、次に`deploy`、終わったら`result`が動きます。

## only,except

ジョブごとに実行条件を指定できます。(exceptは除外条件)
`push`はpushした時、`merge_request`はMRを作成した時、ブランチ名を書くとそのブランチが更新された時です。
今回は実際にplaybookを動かすところを`only: devel`にしているので、`devel`ブランチの中身が更新された時だけPlaybookが適用されます。

## tag

Runnerのタグを指定します。
ここに指定したRunnerでジョブが実行されます。

## APIを使ってコメント投稿

ジョブ`send_failure_message`ではジョブに失敗した時という条件でMR画面にコメントを書き込んでいます。

`script`に書かれてる内容は普通にRunnerがインストールされているOS上でコマンドを打ってるのと同じなので、GitLab APIを使ってコメント残したり色々できます。
`$API_PRIVATE_TOKEN`というのはgitlab-runnerの`config.toml`内で定義した環境変数的なやつです。変数の中身はGitLabのWeb画面から取ってきたbot用アカウントのプライベートトークンです。

# 実際に試す

## Push

ローカルからpushすると、レポジトリの一番上に緑のチェックがつきます。
YAMLの文法に問題なかったということがわかります。

![citest2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215103/dd487947-22ed-8ad7-b3c1-e1353f7011cc.png)


## MR作成

MRを作成するとAnsibleのDRY RUNが走り、不具合が起きないかをチェックできます。
成功したらさっきと同じように緑のチェックが付きます。

![citest.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215103/9bd1aea9-a13f-d362-52ec-808ad208c82c.png)

クリックしないと具体的に何のジョブが実行されたのかわからないのは難点ですね。

## マージ

マージされると実際にPlaybookが実行され、サーバーに構成が反映されます。
上の画像の下にもう一つチェックが付きます。

# 参考:詰まったところ

よくわからんエラーで詰まったのでメモしておきます。
エラーはコピーしてなかったので後からネットで拾ってきました。

## HTTP 403

```shell
Running with gitlab-ci-multi-runner 9.4.2 (6d06f2ec)
on host-vm (a126d8fa)
Using SSH executor...
Running on host-vm via host-vm...
Cloning repository...
Cloning into 'builds/a126d8fa/0/project/appName'...
fatal: unable to access 'https://gitlab-ci-token:xxxxxxxxxxxxxxxxxxxx@gitlab.my-company.com/project/appName.git/': The requested URL returned error: 403
ERROR: Job failed: Process exited with: 1. Reason was:  ()
```

→ユーザー権限の問題でした。権限振り直したら直りました。

## git fetch-packのエラー

```shell
Running with gitlab-runner 11.9.2 (fa86510e)
  on stage-test yMEso5rx
Using SSH executor...
Running on d1.XXXXXX.com via d1.XXXXXX.com...
warning: templates not found builds/yMEso5rx/0/kudja/postel-deluxe.tmp/git-template
Reinitialized existing Git repository in /home/qapd/builds/yMEso5rx/0/XXXX/XXXXXX/.git/
Clean repository
Fetching changes with git depth set to 10...
fatal: remote origin already exists.
fatal: git fetch-pack: expected shallow list
ERROR: Job failed: Process exited with: 1. Reason was:  ()
```

→Runnerに入ってるGitのバージョンを上げたら直りました。