SaaSなしでもインフラCIを！GitLab CI + Docker + Ansible + serverspecでインフラCIを試す

インフラ技術週間も最終日となりました。今週の1週間の記事を振り返ると実は**すべてがDocker絡み**。
何をやるにしてもDockerを中心としたコンテナベースで考えることが当たり前になってきている感じですね。
ということで最終日もDockerを絡めてGitLabを使ったインフラCIについて紹介します。

----

#  この記事を読むと何ができるようになる？

記事全文は長くなるので要点だけ書くと。
この記事では**GitLab**(GitHubのようなGitを管理するOSS)を使って**Ansible**(構成管理ツール)や**serverspec**(インフラテストツール)のコードをGit管理。**Docker**上のコンテナを使って自動構築→自動テストというインフラCI/CDの基礎となるOSSの技術を知ることができます。
CIというとTravisCI、CircleCI、WerckerとかSaaSで便利なものもたくさんありますが、今回はあえてSaaS系ではなくOSSのみで実現する方法を試します。
これで社内に閉じた環境でも実践できますよ！

# GitLabとGitLab Runner(CI)

GitLabのバージョン8.0からCIの機能がGitLab本体に組み込まれ提供されています。
GitLabのサーバとGitLab Runnerはそれぞれ別サーバで構成することができ、GitLabはGitのコードの管理やCIの実行結果管理といった役割を担い、GitLab RunnerはCIの実行処理自体を行う役割を担います。

# 前提とする環境

![GitLab環境構成.png](https://qiita-image-store.s3.amazonaws.com/0/45851/b4824081-19f3-f983-6f56-164b2d6a800b.png)


* GitLab Server (ver.8.13.6)
    * Dockerホスト上のCentOS7コンテナに導入
* GitLab Runner (ver.1.8.1)
    * Dockerホスト(Ubuntu)に直接導入

※今回は手軽に試すために上記のような1台のPC上ですべて完結する構成としました。実際にはCIで回す処理にあわせて環境の検討が必要です。

# 事前準備

Dockerホストの環境は既にある前提で話を進めます。

## GitLab用のコンテナ起動

```shell-session
# docker pull centos:latest
# docker run -d -it --privileged -P --expose={80} --name="gitlab" centos:latest /sbin/init
```
CentOSの素のコンテナイメージから作ってみます。もっと手軽に試すならGitLab社が提供する公式のコンテナもあるのでこちらを使っても良いです。[https://hub.docker.com/r/gitlab/gitlab-ce/](https://hub.docker.com/r/gitlab/gitlab-ce/)

## GitLabインストール

公式の手順は[こちら](https://about.gitlab.com/downloads/#centos7)

```
$ sudo yum install curl policycoreutils openssh-server openssh-clients
$ sudo systemctl enable sshd
$ sudo systemctl start sshd
$ sudo yum install postfix
$ sudo systemctl enable postfix
$ sudo systemctl start postfix
```

```
$ curl -sS https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.rpm.sh | sudo bash
$ sudo yum install gitlab-ce  ←コミュニティエディションをRPMパッケージからインストール
```

```
$ sudo gitlab-ctl reconfigure
```
これでChefのレシピが走ってGitLabの初期設定が実施されます。
途中でストップしたりする場合には一度処理を停止し、以下のコマンドで手動でrunsvdirを起動してください。

```
$ sudo /opt/gitlab/embedded/bin/runsvdir-start &
```

あとはブラウザからGitLabのダッシュボードにアクセスしてユーザ登録等実施しておきます。

## GitLab CI Runnerインストール

次にGitLab CI RunnerをDockerホストのUbuntu上にインストールします。
今回は1つのみ設定しますが、Runnerは複数台を立てて処理を分散させることも可能です。

今回は[こちら](https://docs.gitlab.com/runner/install/linux-repository.html)のパッケージからのインストール方法で導入します。

```
$ sudo curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-ci-multi-runner/script.deb.sh | sudo bash
$ sudo apt-get install gitlab-ci-multi-runner
$ gitlab-ci-multi-runner register
Running in system-mode.

Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com/):
http://172.17.0.2/　←GitLabのURLを入力
Please enter the gitlab-ci token for this runner:
x........   ←ここのTokenはhttp://gitlab-hostname/admin/runnersのページ※に表示されているToken情報を控えておいて入力。
Please enter the gitlab-ci description for this runner:
[194b116adda7]: test ci server　←このRunnerを識別するための適当な名前を設定
Please enter the gitlab-ci tags for this runner (comma separated):
test,infraci
Registering runner... succeeded                     runner=xzjCmWnL
Please enter the executor: parallels, ssh, virtualbox, docker+machine, docker, shell, docker-ssh+machine, kubernetes, docker-ssh:
shell　←Runnerは処理の実行形態として様々なことができるようになっています。一番シンプルなshellを選択し自身のサーバ内で処理させるようなパターンで今回は設定
Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded!
```

![token.png](https://qiita-image-store.s3.amazonaws.com/0/45851/6ccbd524-6084-32a7-765d-32dba892f71e.png)
※Tokenの確認画面。

これで完了です。
GitLabのサーバに情報が連携され、admin runnersのところにGitLab Runnerの一覧に一台追加されます。

![gitlab-1.png](https://qiita-image-store.s3.amazonaws.com/0/45851/505b5d91-c439-4f21-bef3-828ed4188c3d.png)

## GitLab Runnerサーバ内にAnsibleインストール

今回CI処理の中でAnsibleでplaybook実行してコンテナ内の構成を行うことを想定しているので、事前にAnsibleは導入しておきます。

```
$ sudo pip install ansible
```
pipを使えばこれだけでOKです。下記の処理を試す場合、ansibleのバージョンは2.1以降のものを利用してください。(それ以前のバージョンのAnsibleをご利用の場合Dockerモジュールの指定方法が異なるので注意です。)

## GitLab Runnerサーバ内にserverspecインストール

Ansibleで作った環境に対してserverspecでテスト実行する必要があるのでserverspecも事前にインストールしておきます。

```
$ sudo gem install serverspec docker-api
```

今回、serverspecのBackendにdocker-apiを使うのでdocker-apiもインストールしておきます。

事前準備は完了です。

# 新しいリポジトリ作成

GitLabのページにて新規でリポジトリを作成します。
このリポジトリに対して.gitlab-ci.ymlのファイルを配置してpushすればRunnerが起動して処理が実行される仕組みになっています。

# CIの定義(.gitlab-ci.yml)作成

CIの処理を実行するには.gitlab-ci.ymlが必要となります。リポジトリの直下に作成します。

```yaml:.gitlab-ci.yml
stages:
  - build
  - test
  - deploy

build_job1:
  stage: build
  script:
    - ansible-playbook site.yml
    - docker commit ci-build-test ci-builded-img

test_job1:
  stage: test
  script:
    - cd test
    - rake spec:ci-builded-img

deploy_job1:
  stage: deploy
  script:
    - echo "Success!"
  when: on_success

deploy_job2:
  stage: deploy
  script:
    - echo "Failure!"
  when: on_failure
```

上記の例は、3つのステージ(build, test, deploy)で処理を走らせる1例です。
stagesという設定項目で1度の処理で行うべきステージ情報を設定しています。
この例の場合、実際に行う処理はbuidステージではbuild_job1、testステージではtest_job1、deployステージではdeploy_job1もしくはdeploy_job2になります。

同一ステージのジョブは並行に処理が走ります。
前ステージの処理を受けて後続の処理を実行したい場合には今回の例のようにステージ分けしてジョブを登録する必要があります。

最後のdeployステージでは、それまでの処理の実行結果が成功であればdeploy_job1、失敗であればdeploy_job2が実行されるという具合になります。

この機能を使うと、Ansibleでbuildを行い、serverspecでテストを実施し、失敗であれば切り戻し、成功であればリリースといった処理もかんたんに行えます。

それでは以降は実際にbuild工程で行うAnsibleでの構築処理コード、test工程で行うserverspecのテストコードの例を見ていきます。

# インフラCI用のAnsible playbookサンプル作成

Ansibleのdocker-containerモジュールを使ってコンテナイメージからコンテナを起動します。
その後、AnsibleのDocker connection pluginを使ってSSHではなくdocker api経由でコンテナに繋いでコンテナ内にmariadb-serverをインストールする例です。

```yaml:site.yml
- name: docker run ci-build container
  hosts: localhost
  tasks:
    - docker_container:
        name: ci-build-test
        image: centos:latest
        privileged: yes
        state: started
        command: /sbin/init

- hosts: ci-build-test
  connection: docker
  tasks:
  - name: install mariadb-server latest
    yum: name=mariadb-server state=latest
```

AnsibleからDockerの操作、Dockerコンテナ内への操作も簡単にできて便利です

# インフラCI時のServerspecテストコードサンプル作成

CIのtestステップにて、上記のbuildステップで作成したコンテナイメージの中身をテストします。
ServerspecのDocker connection pluginを使って、GitLab Runnerからコンテナ生成→コンテナ内の確認→コンテナ削除をこのステップで自動実行します。

GitLabのリポジトリ内にtestディレクトリを作って以下の構成でテスト用コードを作っていきます。

```
./test
├── Rakefile
└── spec
    ├── ci-builded-img
    │   └── sample_spec.rb
    └── spec_helper.rb
```

spec_helper.rbは以下のようにDocker connection pluginを使うような設定にします。

```rb:spec_helper.rb
require 'serverspec'
require 'docker'

set :backend, :docker

set :docker_url, ENV["DOCKER_HOST"]
set :docker_image, ENV["DOCKER_IMAGE"]
```

Rakefileは以下のように設定。

```rb:Rakefile
require 'rake'
require 'rspec/core/rake_task'

task :spec    => 'spec:all'
task :default => :spec

ENV['DOCKER_HOST'] = 'unix:///var/run/docker.sock'
namespace :spec do
  targets = []
  Dir.glob('./spec/*').each do |dir|
    next unless File.directory?(dir)
    target = File.basename(dir)
    target = "_#{target}" if target == "default"
    targets << target
  end

  task :all     => targets
  task :default => :all

  targets.each do |target|
    original_target = target == "_default" ? target[1..-1] : target
    desc "Run serverspec tests to #{original_target}"
    RSpec::Core::RakeTask.new(target.to_sym) do |t|
      #ENV['TARGET_HOST'] = original_target
      ENV['DOCKER_IMAGE'] = original_target
      t.pattern = "spec/#{original_target}/*_spec.rb"
    end
  end
end
```

これでテスト実行すると、GitLab Runner上でDocker APIをsocket経由で実行しコンテナ生成→コンテナ内でテスト処理をexecしてコンテナ削除という一連の処理が行われます。

# リポジトリに登録しpush

最終的には以下の構成でファイルが出来上がります。
これらのファイルをcommitしてpushします。

```
├── .gitlab-ci.yml
├── README.md
├── site.yml
└── test
    ├── .rspec
    ├── Rakefile
    └── spec
        ├── ci-builded-img
        │   └── mariadb_spec.rb
        └── spec_helper.rb
```

# 実行結果確認

GitLab上のリポジトリにpushすると、自動的に処理が走ります。
GitLabの画面上では以下の図のように確認することができます。

![gitlab-pipeline.png](https://qiita-image-store.s3.amazonaws.com/0/45851/d22f696e-7913-cb01-c2bf-d438a51adb6d.png)

![gitlab-build.png](https://qiita-image-store.s3.amazonaws.com/0/45851/ad9f2208-1d2c-6e68-369a-5a47bc1c2d81.png)

結局この一連の処理の中で何を実施しているかをまとめると以下の通りです。

* buildステージ
    * centos:latestイメージからコンテナを起動 (コンテナ名: ci-build-test)
    * コンテナの中にmariadb-serverのパッケージをインストール
    * 出来上がったコンテナをcommitしてイメージ化 (イメージ名: ci-builded-img)
* testステージ
    * ci-builded-imgからコンテナを起動
    * その中に対してmariadb-serverがインストールされているかどうかの確認テスト
* deployステージ
    * build,testがOKなら"Success!"を出力して終了
    * build,testがNGなら"Failure!"を出力して終了

# 注意点1: GitLab Runnerの実行ユーザ

GitLab runnerサーバの中ではgitlub-runnerのプロセスが稼働して処理が行われます。
このプロセスはデフォルトではgitlab-runnerユーザで起動するようになっています。そのため、GitLab Runnerでdockerコマンドを実行するなどの際にPermission deniedで弾かれる可能性があります。その場合にはgitlab-runnerユーザをdockerグループに所属させることで対応可能です。

```
$ sudo usermod -aG gitlab-runner,docker gitlab-runner
```

# 注意点2: GitLab Runner docker executor

今回の例ではGitLab runnerの処理実行のexecutorとしてshellを使っていました。Dockerに対してCI処理を行うのであればdocker executorを使うのも1つの手ではあるかと思います。
その場合、dockerイメージのbuildやpush等は[こちらのURL](https://docs.gitlab.com/ce/ci/docker/using_docker_build.html#use-docker-in-docker-executor)に記載の通りDocker on Dockerの仕組みを使って実行可能です。
ただ、今回紹介した例のように実際のbuild処理をAnsibleとか他のツールでカバーしたいといった場合には扱いにくいかと思います。
Dockerfileベースで管理しているケースでは非常に使い勝手が良いかもしれません。

# まとめ

少し複雑になりましたが、全てOSSで仕組みを作り上げることができました。GitLab CIはGitLabのリポジトリとうまく連携して管理できるので使いようによっては結構便利に使えるかもしれません。今回紹介した例のようにGitLab Runnerが稼働するサーバに対して事前にAnsibleやserverspecをインストールするなどうまく準備しておけばいろんな処理をCIの中に組み込むことが可能になります。この辺はSaaSを使うのではなく自前で作れるメリットですね。

明日は[komikoni](http://qiita.com/komikoni)さんです！よろしくお願いします！！