GitLab Runner+Capistranoで継続的デプロイ

GitLab Runner の使い勝手がなかなか良くなったそうなので、Capistrano3 と組み合わせたデプロイ環境を作成しました。

# 実現したいこと
+ staging または master ブランチへの push イベントをトリガーとして GitLab 上の Pipeline を回す。
+ Pipeline ではテスト ジョブとデプロイ ジョブを実行するが、デプロイ ジョブの実行は手動とする（GitLab の Web UI 上でジョブを実行させて結果の成功／失敗を確認したいため）。
+ デプロイ ジョブでは Capistrano のタスクを実行し、リモート環境（ステージング または プロダクション環境）にコードをデプロイする。
+ Capistrano では SSH エージェント転送を有効にして、タスク実行元に保管された秘密鍵をデプロイ先サーバーに転送し、この鍵を使って GitLab に SSH 接続を行いコードを取得する。
+ デプロイしたコードに問題があった場合に備え、ロールバック ジョブも用意する（ロールバックも手動実行とする）。

# 各種バージョン
+ [gitlab-ci-multi-runner](https://gitlab.com/gitlab-org/gitlab-ci-multi-runner):  1.7.1
+ [Capistrano](http://capistranorb.com/): 3.6.1
+ [GitLab CE](https://gitlab.com/gitlab-org/gitlab-ce): 8.14.0
+ Runner サーバー（※）OS: Ubuntu 16.04

*※以降、gitlab-ci-multi-runner をインストールするサーバーを Runner サーバーと呼ぶこととします。*

# 各アカウントおよびドメイン名

| サーバー種別            | ドメイン名（※）       | タスク or デプロイ実行アカウント名 |
|-------------------------|------------------------|------------------------------------|
| Runner サーバー         | runner-svr.example.com | gitlab-runner                      |
| ステージング サーバー   | staging.examle.com     | deploy-user                        |
| プロダクション サーバー | production.examle.com  | deploy-user                        |

*※実際のドメイン名ではなく、説明するための例として記載します。*

# 処理フロー イメージ
![runner-environment-image.png](https://qiita-image-store.s3.amazonaws.com/0/148698/38d0ba83-a44f-dba7-c5b7-9f844515eeed.png)

# 環境構築手順
## SSH 公開鍵認証用の設定
### 秘密鍵／公開鍵の生成
GitLab から Runner に対し処理実行を命じるとき、自動的に Runner サーバー上に gitlab-runner アカウントが追加されます。
今回 Runner + Capistrano + SSH エージェント転送を実現するに当たって、gitlab-runner アカウントが利用可能な秘密鍵を用意する必要があるため、あらかじめ gitlab-runner アカウントを Runner サーバー上に追加し、この gitlab-runner のホームディレクトリ以下に秘密鍵と公開鍵を保管しておきます。

```
$ hostname
runner-svr
$ sudo -s
# adduser gitlab-runner
// gitlab-runner アカウント情報を入力
# su - gitlab-runner
$ ssh-keygen -t rsa -N "" -C "gitlab-runner@runner-svr.example.com"
```

### デプロイ先サーバーへの公開鍵配布
Capistrano スクリプト実行時、SSH 公開鍵認証でステージングまたはプロダクション環境にログインさせるため、作成した公開鍵（/home/gitlab-runner/.ssh/id_rsa.pub）を、ssh-copy-id で各サーバー上の（デプロイ用アカウントのホームディレクトリ）/.ssh/authorized_keys に登録します。

```
$ hostname
runner-svr
$ whoami
gitlab-runner
// リモートのステージングおよびプロダクション サーバーに公開鍵を登録する
$ ssh-copy-id -i /home/gitlab-runner/.ssh/id_rsa.pub deploy-user@staging.example.com
$ ssh-copy-id -i /home/gitlab-runner/.ssh/id_rsa.pub deploy-user@production.example.com
```

### GitLab にデプロイ キーを登録する
生成した公開鍵を使って GitLab からソースコードを取得してデプロイするため、この鍵をデプロイ専用の鍵として GitLab に登録します。 
登録は GitLab Web UI 上のプロジェクト > "設定" ドロップダウンリスト > Deploy Keys から行います。

もしプロジェクト共通のデプロイ キーとして登録したい場合は、管理者権限を持つユーザーで Web UI にログインし、"Admin Area" > "設定" ドロップダウンリスト > Deploy Keys から登録してプロジェクトの Deploy Keys ページで対象キーを「Enable」にします。

## Capistrano のインストール & 設定
### インストール
Runner サーバー上に gem を利用して Capistrano をインストールします。

```
$ hostname
runner-svr
$ sudo -s
# apt-get update && apt-get install ruby -y
# gem install bundler net-ssh capistrano
```

### Capistrano スクリプト実行環境の作成
gitlab-runner アカウントで Runner サーバーにログインし、任意のディレクトリを作成します。
このディレクトリ内に Gemfile を保管して bundle install と cap install コマンドを実行します。

```
$ hostname
runner-svr
$ whoami
gitlab-runner
$ mkdir ~/capistrano
$ cd ~/capistrano

// capistrano ディレクトリの中に Gemfile を作成またはコピーする

$ ls -l
-rw-rw-r-- 1 gitlab-runner gitlab-runner  125 Nov  7 18:06 Gemfile

$ bundle install --path vendor/bundle
$ cap install
（必要なファイル、ディレクトリのひな形が自動生成される）
```

#### capistrano ディレクトリ構成イメージ

```
/home/gitlab-runner/capistrano
├── Capfile
├── config
│   ├── deploy
│   │   ├── production.rb
│   │   └── staging.rb
│   └── deploy.rb
├── Gemfile
├── Gemfile.lock
├── log
├── vendor
└── lib
    └── capistrano
            └── tasks
```

### 設定ファイルの編集
#### deploy.rb ファイル
cap コマンドでデプロイを実行するとき、コマンド オプションでデプロイするブランチを指定できるようにするため、次の設定を追加します。

```rb:deploy.rb
# もし環境変数 BRANCH に値がセットされていなかった場合、ブランチ名の確認プロンプトが表示されます
set :branch, ask('Branch: ', nil) unless ENV['BRANCH']
```

*※注意
後述する .gitlab-ci.yml のジョブ内で環境変数 BRANCH にブランチ名をセットしますが、もし BRANCH に値がセットされていなかった場合でも Runner のジョブの実行結果は "Build succeeded" と表示されます。しかしデプロイ自体はなされません（Capistrano の以降のタスクがスキップされます）。*

#### staging.rb と production.rb ファイル
デプロイ時に SSH 公開鍵を使ってデプロイ先サーバーにログインし、また SSH エージェント転送を有効にするため以下のオプションを追加します。

```rb:staging.rb/production.rb
set :ssh_options, {
  user: 'deploy-user',
  keys: %w(/home/gitlab-runner/.ssh/id_rsa),
  forward_agent: true,
  auth_methods: %w{publickey}
}
```

その他の Capistrano 設定項目については、[公式ドキュメント](http://capistranorb.com/documentation/getting-started/configuration/)を参照し設定します。

### デプロイ テスト
Capistrano スクリプトが成功するか一旦テストします。

```
$ hostname
runner-svr
$ whoami
gitlab-runner
$ eval `ssh-agent`
$ ssh-add ~/.ssh/id_rsa

$ cd ~/capistrano
$ BRANCH=staging bundle exec cap production deploy
```

エラーが出ず、成功した場合は Capistrano の準備は完了です。

## gitlab-ci-multi-runner のインストール
Runner サーバーに gitlab-ci-multi-runner をインストールします。
手順は[公式マニュアル](https://github.com/ayufan/gitlab-ci-multi-runner/blob/master/docs/install/linux-repository.md)の通りに実行するだけです。

```
$ hostname
runner-svr
$ sudo -s
# curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-ci-multi-runner/script.deb.sh | sudo bash
# apt-get install gitlab-ci-multi-runner
```

## Runner の登録
Runner を登録します。

```
# hostname
runner-svr
# gitlab-ci-multi-runner register
Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com/):

https://(使用している GitLab のドメイン名)/ci ---- (1)

Please enter the gitlab-ci token for this runner:
（GitLab 上で表示されるトークン） ---- (2)

Please enter the gitlab-ci description for this runner:
myrunner ---- (3)

Please enter the gitlab-ci tags for this runner (comma separated):
myproject ---- (4)

Registering runner... succeeded                     runner=xxxxxxxx
Please enter the executor: shell, virtualbox, docker+machine, docker-ssh+machine, kubernetes, docker, docker-ssh, parallels, ssh:
shell ---- (5)

Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded!
```

### Register オプション
- **(1) gitlab-ci coordinator URL**  
GitLab Web UI 上で何らかのプロジェクト > "設定" ドロップダウンリスト > Runner ページを開くと、"Specific Runners" に Runner 設定用の値が記載されています。  
その内の "Specify the following URL during the Runner setup: " で示された URL を指定します（現状 GitLab の URL + /ci で規定されているようです）。
- **(2) gitlab-ci token**  
同じくプロジェクトの Runner ページに表示されたトークンを指定します。  
Shared Runner （プロジェクト共通で利用可能な Runner）を登録する場合は、GitLab に管理者権限のあるユーザーでログインし、Admin Area > Overview > Runners ページに表示されたトークンを指定します。
- **(3) gitlab-ci description for this runner**  
Runner の表示名を指定します。ここで指定した名称は GitLab Web UI 上にも表示されます。
- **(4) gitlab-ci tags for this runner**  
ジョブと Runner を紐付けるためのタグを指定します。カンマ区切りで複数タグが指定できます。  
指定しなくても構いませんが、タグを付けることでジョブの管理がしやすくなります。
- **(5) executor**  
プロジェクト ビルド環境を指定します。たとえば、docker を選択すると Docker コンテナ上で各ジョブが実行されることとなります。  
docker+machine を選択すると更にコンテナの auto scale もできるそうですが、今回は shell を指定し Runner サーバー上でスクリプトを実行させることとします。
executor についての詳しい説明は、[こちらのページ](https://github.com/ayufan/gitlab-ci-multi-runner/tree/master/docs/executors)を参照ください。

### GitLab Web UI 上で Runner の状態を確認する
Runner の登録が成功すると、GitLab Web UI のプロジェクト > "設定" ドロップダウンリスト > Runner ページに、有効な Runner として表示されるようになります。
このとき、「Enable for this project」ボタンをクリックしないと Runner が有効化されないため必ずクリックしておきます。

![active_runner.png](https://qiita-image-store.s3.amazonaws.com/0/148698/409119bf-a361-cecf-8834-3379128b2482.png)

## .gitlab-ci.yml の作成
Runner を動かすための設定ファイルとして、.gitlab-ci.yml をプロジェクト リポジトリのルート直下に作成します。
最新の GitLab CE では、Web UI のプロジェクト トップページにある「Set Up CI」ボタンをクリックすると .gitlab-ci.yml の作成 & 編集をブラウザからできるようになっているので、こちらを利用するのが手っ取り早いです。

![setup-ci-button.png](https://qiita-image-store.s3.amazonaws.com/0/148698/7b774ede-7dcc-1f7b-ae7e-d0df73b7dc87.png)

#### .gitlab-ci.yml 記述例

```yml:.gitlab-ci.yml
stages:
  - test
  - deploy
  - rollback

test_job:
  stage: test
  script:
    - （テスト スクリプト実行コマンド）
  tags:
    - myproject
  
deploy_to_staging_job:
  stage: deploy
  script:
    - eval `ssh-agent`
    - ssh-add /home/gitlab-runner/.ssh/id_rsa
    - cd /home/gitlab-runner/capistrano
    - BRANCH=staging bundle exec cap staging deploy
  only:
    - staging
  tags:
    - myproject
  when: manual

deploy_to_production_job:
  stage: deploy
  script:
    - eval `ssh-agent`
    - ssh-add /home/gitlab-runner/.ssh/id_rsa
    - cd /home/gitlab-runner/capistrano
    - BRANCH=master bundle exec cap production deploy
  only:
    - master
  tags:
    - myproject
  when: manual
  
rollback_staging_job:
  stage: rollback
  script:
    - eval `ssh-agent`
    - ssh-add /home/gitlab-runner/.ssh/id_rsa
    - cd /home/gitlab-runner/capistrano
    - BRANCH=staging bundle exec cap staging deploy:rollback
  only:
    - staging
  tags:
    - myproject
  when: manual
  
rollback_production_job:
  stage: rollback
  script:
    - eval `ssh-agent`
    - ssh-add /home/gitlab-runner/.ssh/id_rsa
    - cd /home/gitlab-runner/capistrano
    - BRANCH=staging bundle exec cap production deploy:rollback
  only:
    - master
  tags:
    - myproject
  when: manual
```

#### .gitlab-ci.yml の各パラメーターについて
+ **stages**  
テスト、ビルドまたはデプロイなど各ジョブのステージを登録します。ステージ名（"deploy" など）は任意に指定できます。
ステージの進め方（Pipeline の回り方）は基本的に書いてある順で進んで行き、前のジョブが成功したら次のステージのジョブが実行されます。
- **script**  
処理したいコマンドを記述します。
- **only**  
git ref の内容によって、ジョブを実行するかどうか判定します。今回は "staging" または "master" ブランチに push されたときのみにジョブが実行されるように、ブランチ名を指定しました。
- **tags**  
Runner 登録時に設定したタグを追加します。タグを追加することで、複数の Runner が登録されていてもジョブの実行先 Runner を明示的に指定することができます。
- **when**  
ジョブの実行タイミングを指定します。ここではデプロイとロールバックジョブは手動実行にしたかったため "manual" と記述しましたが、指定が無い場合は自動実行となります。

※test_job や deploy_to_staging_job などのジョブ名は適当につけています。ジョブ名は任意に指定できるので、ジョブの内容に合わせて適宜変更します。
※パラメーターのより詳しい説明は[GitLab のドキュメント](https://docs.gitlab.com/ce/ci/yaml/README.html)を参照してください。

# デプロイ実行
準備が整ったのでいよいよ Pipeline を回します。
staging ブランチを push し、Web UI 上で対象プロジェクトの Pipelines ページから最新の Pipeline の Status をクリックします。

![pipeline_success.png](https://qiita-image-store.s3.amazonaws.com/0/148698/37f2c98e-5329-91f6-9cd7-a72928e64d2f.png)

Deploy ジョブは「skipped」になっているため、実行ボタンをクリックしジョブを開始させます。

![manual_start_deploy.png](https://qiita-image-store.s3.amazonaws.com/0/148698/4b1a7801-0a40-138b-6c3f-c05c776dbeac.png)

処理が開始されるとコンソール画面が表示され、最後に "Build succeeded" と出力されると成功です。

![deploy-success.png](https://qiita-image-store.s3.amazonaws.com/0/148698/3e6ebe49-d78f-99ed-531f-288dd8496ba9.png)

もしコードをロールバックしたい場合は、同じく Rollback ジョブを Web UI 上から実行します。

### ※補足  
1. git push の度に新しい Pipeline が追加されて行きますが、数代前の Pipeline の、未実行の manual ジョブを任意のタイミングで実行することもできます。  
今回は「常に staging または master ブランチの最新ソースをデプロイする」ように Capistrano 設定を書いていますので、ジョブの古さに関わらずジョブを実行した時点での最新ソースがデプロイされます。
1. GitLab 8.14 から Pipeline の結果がメールで送信されるようになりました。自動実行ジョブが失敗したときのリカバリがしやすくなりますね。
![Better Pipeline emails](https://about.gitlab.com/images/8_14/emails.png)

# 参考サイト
+ [GitLab CI Documentation](https://docs.gitlab.com/ce/ci/README.html)
+ [gitlab-ci-multi-runner](https://github.com/ayufan/gitlab-ci-multi-runner/tree/a3d5829a46cb94a49c438db7efede4da40a0a238)
+ [GitLab 8.14 Released with Time Tracking Beta and Chat Commands](https://about.gitlab.com/2016/11/22/gitlab-8-14-released/)
+ [capistrano](https://github.com/capistrano/capistrano)
+ [ssh-agentの使い方](http://qiita.com/isaoshimizu/items/84ac5a0b1d42b9d355cf)
+ [ssh-copy-idで公開鍵をリモートホストへ登録する](http://qiita.com/toshiro3/items/e8c26ba53f14ff85b569)