Webサービスのテスト環境をGitLab CI on Dockerで作る（Ruby on Rails）

えっと、この文書は自サイトで公開してたヤツでして、多少情報が古いと思います。
バージョン違いによるエラーへの対応策など、今は事情が異なってるかもしれないのでその点に御留意いただければ幸いです。

では、乱文で申し訳無いですが、ご興味ある方はご参考下さい。
内容の誤り等に対する容赦ないご指摘、ご質問お待ちしてます。

---

今更ながらRuby on Railsに。完全に初めてなのでゼロベースから。
フレームワークもこれまでFuelPHPとかしか使ったことなかったので色々詰まりました。
備忘録代わりのメモ。ただし初学者向けにちょっとていねいのつもり。
分かりにくいところや間違っているところあったら指摘御願いします。
# Dockerによる環境構築
ソースコードをGitで管理したかったのでGitLab + GitLab CIで環境構築してみました。
以下はその手順です。
ちなみにDockerってのはいまではもうかなり有名になっちゃってますが、準仮想化環境を提供してくれるツールです。
物理デバイスとOSとを仲介するkernel（kernelをOSに含めることも多いのでこの表現は適切じゃない）を仮想OSとホストOSで共用するので準って言われてます。
minimalなOSイメージが用意されてるので、そこから自分でパッケージをインストールしたりして、独自イメージを作ることができます。このパッケージインストール手順書はDockerfileに書かれます。
## Docker, GitLabの導入
sameersbnさんっていう超人がGitlabのDockerイメージを作ってくれてるので、それを使います。
まずはDockerのインストールから。環境はCentOS7。

```sh
sudo yum install -y docker
```

おぼろげな記憶をベースに書いてるのでところどころ間違いがあるかもですがそこはご勘弁。
CentOS6だとdocker-ioだったと思います。
上のコマンドでdockerが入ったら

```sh
# sudo chkconfig docker on
sudo systemctl enable docker.service
# sudo service docker start
sudo systemctl start docker.service
```

でDockerを起動します。
あとはsameersbnさんのdockerレポジトリからGitLabイメージコンテナをpullする！

```sh
sudo docker pull sameersbn/gitlab:latest
```

と、ここまで書いて知ったんですけど、今docker-composeも出てるみたいですね。
必要なコンテナイメージと解放するサービスとか依存性とか書いてれば、環境構築を自動でやってくれる強者らしいですけど、まだ使ったことないなあ…。

まー今回は実際に自分がとった手段で解説します。
ところで、Dockerの使い方って基本的に１サービス＝１コンテナの原則があるんです。
複数のサービスからなるシステムで、一部サービスのバージョンがアップしたとき（あるいはハングしたとき）にシステム全体を止めるってよくないから…？かな。
GitLabはRedis, MySQLを使ってるので

```sh
sudo docker pull sameersbn/mysql:latest
sudo docker pull sameersbn/redis:latest
```

も、とってきます。
### GitLabの起動

#### MySQLの起動
sameersbnさんのサイトに [丁寧なチュートリアル](https://github.com/sameersbn/docker-gitlab/blob/master/README.md) があるのでここでまた説明するのもなーって感じですけど、Dockerでの起動順は、周辺サービス（MySQL, Redis, etc.）起動後にGitLabを起動って感じになります。

```sh
sudo mkdir -p /opt/mysql/data
sudo docker run --name=mysql-gitlab -d \
  --env='DB_NAME=gitlabhq_production' \
  --env='DB_USER=gitlab' --env='DB_PASS=password' \
    --volume=/opt/mysql/data:/var/lib/mysql \
    sameersbn/mysql:latest
```

--volume=もそうですけどDockerの場合はオプションを
    [--<オプション名>=<ホストのリソース>:<コンテナのリソース>]
って書きますね。ホスト8080番のポート経由でコンテナの80番ポートにアクセスさせたいなら
    -p 8080:80
って感じです。cpやmv, lnコマンドと同じくfrom:toの順って考えると分かりやすいかな。
DB\_NAMEとDB\_USER, DB\_PASSを指定して起動すれば、それらの情報を使って勝手にスキーマとユーザが作られます。
つまり2回目以降はこれらを指定する必要がないです。

```sh
sudo docker run --name=mysql -d \
    --volume=/opt/mysql/data:/var/lib/mysql \
    sameersbn/mysql:latest
```

Dockerの特徴はコンテナを停止するとそのコンテナが削除されて、再起動するとまっさらな状態に戻ってることですが、--volumeでホストの/opt/mysql/dataディレクトリをコンテナの/var/lib/mysqlにマウントしてるので、上のようにして起動する限りデータはきちんと引き継がれます。
#### Redisの起動
必要なサービスはMySQLだけじゃないです。KVS型データベースのRedisもセッション情報保持のために必要です。
RedisはHDD上ではなくメモリにデータベースを作るっす。なのでアクセスが高速です。
ログインしたユーザのブラウザキャッシュに自動生成したuniqueな文字列tokenを与えておいて、そのユーザ名(key)とtoken(value)をサーバ側のRedisにも登録(store)しておけば、次そのユーザからアクセスがあった場合にtokenの開示を求めてRedisのtokenと照らし合わせることで、ユーザが本人かどうか判別できます。起動方法は以下のとーり。

```sh
sudo docker run --name=redis -d sameersbn/redis:latest
```

さっきから書いてるコマンドにあるオプション-dはdaemonとして（バックグラウンドで）起動するって意味です。
interactiveに操作してみたいなら-t -iで、最後にどのプログラムとinteractしたいのか書きます。例えばbashとか。
-tの役割は以下を見て判断してくれれば

```sh
[root@valid ~]# docker run -t -i sameersbn/redis:latest bash
root@809603b63da2:/# exit
exit
[root@valid ~]# docker run -i sameersbn/redis:latest bash
ls ~
exit
[root@valid ~]# 
```

はい。この辺昔はめちゃくちゃ面倒くさかったんですけどね。人気のあるツールの開発スピードって早いですね。
#### Security対策(HTTPS接続)
GitLabはアカウント情報やソースコード、ssh鍵をやりとりすることになるので（対外向けに公開するなら）HTTPS接続必須です。
openssl genrsaで生成した鍵(.key)は署名して(.csr)、認証局(CA)にお金払って登録しましょー。

公開鍵(.crt)とCAについての情報が記載された情報(.crt)を一緒くたにしちゃったもの(gitlab.crt)と、最初に生成した秘密鍵(gitlab.key)を適切な場所に置けばあとはGitLab側コンテナが少しの指示でHTTPSをサポートしてくれます。
なおトップレベルドメインが.tkとかじゃなければ、StarSSLとかの認証局が無料で認証してくれます。
ただSSLは本人証明のための署名(.csr生成)の時に住所とか書かなきゃならないので、無料でやってくれるようなとこに名前や住所流してもＯＫって人以外はやらない方が良いかもですね。
まあその情報はドメインのwhois情報と照らし合わされるんで、結局取得しようとした時点で第3者には公開してる状況なんでしょうけど…。

前置きが長くなりましたが秘密鍵をgitlab.key, 公開鍵をgitlab.crtとした場合、保存場所は以下の通りです。
dhparam.pemは暗号化の強化のためで、任意のはずっすが…外したことがないのでなんとも。

```sh
sudo mkdir -p /opt/gitlab/data/certs
sudo cp gitlab.key /opt/gitlab/data/certs/
sudo cp gitlab.crt /opt/gitlab/data/certs/
sudo cp dhparam.pem /opt/gitlab/data/certs/
sudo chmod 400 /opt/gitlab/data/certs/gitlab.key
```

あとはGitLab起動時に --env='GITLAB_HTTPS=true' オプションをつけ忘れないことですね。
#### GitLabの起動
これまで起動したサービスとリンクさせてGitLabの入ったコンテナを起動します。

```sh
sudo docker run --name=gitlab -d -t
        --link mysql:mysql
        --link redis:redisio
        -e "DB_USER=gitlab" -e "DB_PASS=password" -e "DB_NAME=gitlabhq_production"
        -v /opt/gitlab/data:/home/git/data
        -e "GITLAB_HTTPS=true" -p 443:443
        -e "GITLAB_SSH_PORT=22" -p 22:22
        -e "GITLAB_HOST=hoge.fuga" -e "SMTP_USER=xxxxxx@gmail.com" -e "SMTP_PASS=XXXXXXXX" sameersbn/gitlab:latest
```

メール通知に使うメールサーバとかは諸事情で僕はGMail使ってます。
GMailのアカウントを持ってればすぐに設定できます。
メールサーバを建てられる人はそれを使えばいいですけど、OP25B（スパムメール送信規制）のせいでメール送信に使うポート25での通信を規制しているプロパイダーが多く、個人では建てられない場合が多いんですよね…。はあ。
### GitLab CIの起動
ここまでだとただのソース管理にしかなってないです。
自動でデプロイしてテスト鯖に反映させる、までいけてのgitのありがたみです。
ちなみにこんなかんじの機能のことを継続的インテグレーション(CI)ってみんな仰ってます。
そんなわけでgit pushされたら自動でgit pullしてデプロイしてくれるとこまで作っちゃいましょう。
まず超人sameersbnさんのレポジトリからCIを引っ張ってきて

```sh
sudo docker pull sameersbn/gitlab-ci:latest
```

周辺環境のRedisとMySQLを用意して

```sh
# Booting Redis
sudo docker run --name=redis-gitlab-ci -d \
  --volume=/opt/gitlab-ci/redis:/var/lib/redis \
  sameersbn/redis:latest
# First boot of MySQL Container
sudo docker run --name=mysql-gitlab-ci -d \
  --env='DB_NAME=gitlab_ci_production' \
  --env='DB_USER=gitlab_ci' --env='DB_PASS=password' \
  --volume=/opt/gitlab-ci/mysql:/var/lib/mysql \
  sameersbn/mysql:latest
# Second or later boot of MySQL Container
sudo docker run --name=mysql-gitlab-ci -d \
  --volume=/opt/gitlab-ci/mysql:/var/lib/mysql \
  sameersbn/mysql:latest
```

あとはCIの起動

```sh
# Booting GitLab-CI
sudo docker run --name=gitlab-ci -d \
  --link=mysql-gitlab-ci:mysql --link=redis-gitlab-ci:redisio \
  --env='GITLAB_CI_HTTPS=true' \
  --env='GITLAB_CI_PORT=10080' --publish=10080:443 --env='GITLAB_URL=https://hoge.fuga/' \
  --env='GITLAB_APP_ID=abcdefghijklmnopqrstuvwxyz0123456789' \
  --env='GITLAB_APP_SECRET=0123456789abcdefghijklmnopqrstuvwxyz' \
  --volume=/opt/gitlab-ci/gitlab-ci:/home/gitlab_ci/data \
  sameersbn/gitlab-ci:latest
```

だけですね。/opt/gitlab-ci/gitlab-ci/certsにgitlab.crt, gitlab.keyの配置を忘れなければちょーかんたん？
GITLAB\_APP\_IDと、GITLAB\_APP\_SECRETはGitLabログイン後Profile SettingsからApplicationsへ行き、そこにGitLab CIのCallBack URL（上の様にホスト10080番とCIコンテナ443番を繋げるなら https://hoge.fuga:10080/user_sessions/callback ）を指定してやると発行されるのでそれを使います。

ただし自宅サーバでやってる方への注意なんですが、ドメイン名とIPアドレスの紐付けをするDNSサーバが自前でないなら自ドメイン名hoge.fugaで問い合わせたとき192.168..とかのローカルIPではなく自グローバルIPへアクセスしようとして、結果的に弾かれる、ってことが起こりえます。
その場合は自分のマシンのhostsファイルを編集するとかしたほーがよいかもです。

無事起動していればGitLab CIからはGitLabへログインするよう求められ（OAuth認証）、ログインしたらGitLab CIへコールバックされ、CIへログインした状態になります。
これでCIの設定の半分が終わりました。もう半分は何かって…実際の実行環境の構築です。
### GitLab CI Runnerの設定と起動
GitLab CIはGitLab CI Runnerにgit pullで引っ張ってきたものをデプロイします。
つまり実際にWeb Serviceが動く環境はGitLab CI Runner内です。
目的によってはsameersbnさんのイメージをとってくるだけでもいいんですが、僕の場合はHTTPSサポートとRuby on Rails環境を構築したかったので、Dockerfileから弄ることにしました。
てなわけでイメージを構築するのに必要なファイルを丸ごと持ってきます。

```sh
$ git clone https://github.com/sameersbn/docker-gitlab-ci-runner.git
$ tree docker-gitlab-ci-runner/
docker-gitlab-ci-runner
├── Changelog.md
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── VERSION
└── assets
    ├── init
    └── setup
        └── install

2 directories, 8 files
```

弄る必要があるのはイメージ構築手順書であるDockerfileと、その中で起動されているassets/setup/installです。
GitLabにはRubyが使われてて、イメージ構築中にRubyやらRuby Devやらをインストールしてるんですが、assets/setup/installが全消去して去りやがるので、全消去指示部分を消す形です。

あと本来必要無い手順なんですが、GitLab立ち上げに必要なgem（モジュールみたいなもの）のひとつが僕が作業した時点ではマイナーバージョンアップしていて、自動取得できていなかったので、その部分もいじりました。
assets/setup/installで編集したのは以下の部分（行頭に1文字分の+があるところ。行頭-は消去部位）です。

```sh
# diff -u assets/setup/install.original assets/setup/install
--- assets/setup/install.original	2015-06-21 11:46:03.000000000 +0900
+++ assets/setup/install	2015-06-21 11:52:45.000000000 +0900
@@ -20,6 +20,7 @@
 
 cd ${INSTALL_DIR}
 
+sed -i "s@rb-kqueue \(0.2.0\)@rb-kqueue \(0.2.2\)@g" Gemfile.lock
 # rebuild apt cache
 apt-get update
 
@@ -61,13 +62,13 @@
 EOF
 
 # purge build dependencies
-apt-get purge -y --auto-remove gcc g++ patch make \
-  libc6-dev ruby2.1-dev zlib1g-dev libyaml-dev libssl-dev \
-  libgdbm-dev libreadline-dev libncurses5-dev libffi-dev \
-  libxml2-dev libxslt-dev libcurl4-openssl-dev libicu-dev
+#apt-get purge -y --auto-remove gcc g++ patch make \
+#  libc6-dev ruby2.1-dev zlib1g-dev libyaml-dev libssl-dev \
+#  libgdbm-dev libreadline-dev libncurses5-dev libffi-dev \
+#  libxml2-dev libxslt-dev libcurl4-openssl-dev libicu-dev
 
 # cleanup
-rm -rf /var/lib/apt/lists/*
+#rm -rf /var/lib/apt/lists/*
 
 # make sure everything in ${HOME_DIR} is owned by the gitlab_ci user
 chown -R gitlab_ci_runner:gitlab_ci_runner ${HOME_DIR}/
```

23行目に追加しているのがマイナーバージョンアップ対応部分、64行目からの部分が全消去指示の撤回部分です。

assets/setup/installはこれでおわりですが、Dockerfileの方は割と弄るところがあります。
内容としては

 * Ruby on Railsに必要なパッケージとsedのインストール
 * Ruby on Rails on Apache2に必要なパッケージのインストール
 * SSL有効化(apacheモジュールの有効化、crt, key配置)
 * Apache設定ファイルの配置
 * GitLabコンテナからソースコードを持ってくるとき公開鍵の検証に必要な認証局(CA)に関する情報の追加
   * ComodoSSLなどの格安SSLを利用の場合

の５つがあります。

気をつけなくてはならないのが、Apacheをユーザ権限（gitlab\_ci\_runner）で動かす場合、 [いろいろ面倒なこと](http://www.usupi.org/sysad/262.html) をしなくてはならない点です。
僕の場合はゴニョゴニョして面倒を回避したんですが、公開ノートで言えるような方法じゃないので言及しないでおきます。

Apache2の設定ファイルはassets/apache2だとかに次のようなもの（myApp.conf）を作っておき、

```sh
<IfModule mod_ssl.c>
  <VirtualHost *:443>
    ServerAdmin webmaster@hoge.fuga
    DocumentRoot /home/gitlab_ci_runner/myApp/public
    RailsEnv development
    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined
    SSLEngine on
    SSLCertificateFile	/etc/ssl/certs/gitlab.crt
    SSLCertificateKeyFile /etc/ssl/private/gitlab.key
    <Directory "/home/gitlab_ci_runner/myApp/public">
      Options FollowSymLinks
      Require all granted
    </Directory>
  </VirtualHost>
</IfModule>
```

これをDockerfileの中で取り込むようにすればOKです。

```sh
ADD assets/apache2/myApp.conf /etc/apache2/sites-available/
```

SSL有効化に必要なファイルや認証局情報に関しても同じです。

```sh
# private key and public cert
ADD assets/certs/gitlab.key /etc/ssl/private/
RUN chmod 400 /etc/ssl/private/gitlab.key
ADD assets/certs/gitlab.crt /etc/ssl/certs/
RUN mkdir -p /etc/apache2/ssl.crt
# CA
ADD assets/certs/ca.crt /etc/apache2/ssl.crt/
RUN cat /etc/apache2/ssl.crt/ca.crt >> /etc/ssl/certs/ca-certificates.crt
```

Ruby on Rails用の環境構築それ自体については [ここ](https://www.digitalocean.com/community/tutorials/how-to-deploy-a-rails-app-with-passenger-and-apache-on-ubuntu-14-04) にちょー分かりやすい指南書があるので、このStep 4からするのがよいと思います。この部分を要約するとこんな感じでしょうか。

```sh
RUN apt-get update && \
  apt-get install -y curl libxml2-dev libxslt-dev libcurl4-openssl-dev libreadline6-dev libssl-dev patch build-essential zlib1g-dev openssh-server libyaml-dev libicu-dev libreadline-dev openssl sqlite3 libsqlite3-dev libxslt1-dev bison git-core libffi-dev apache2
RUN apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 561F9B9CAC40B2F7
RUN apt-get install -y apt-transport-https ca-certificates
RUN echo 'deb https://oss-binaries.phusionpassenger.com/apt/passenger trusty main' >> /etc/apt/sources.list.d/passenger.list
RUN chown root: /etc/apt/sources.list.d/passenger.list
RUN chmod 600 /etc/apt/sources.list.d/passenger.list
RUN apt-get update
RUN apt-get install -y libapache2-mod-passenger
RUN a2enmod passenger
RUN a2enmod ssl.load
RUN a2dissite 000-default
```

先ほども言いましたけど、ユーザ権限でApacheを起動する場合は適宜差し替えてください。

以上で大体終了なので、後はDockerfileのあるディレクトリで

```sh
docker build --tag=rails/custom-gitlab-ci-runner .
```

を実行してビルドするだけ…といいたいところですが、gitlab-ci-runnerがGitLabの走っているhoge.fugaと同一ローカルネットワークに所属している場合、gitlab-ci-runnerからはhoge.fugaへアクセスすることができず、gitによるソースコードの取得ができません。

そこでdockerでrunした後、hostsをちょっと弄ってあげる必要があります。

```sh
# 初期設定
$ sudo docker run --name gitlab-ci-runner -it --rm -v /opt/gitlab-ci-runner:/home/gitlab_ci_runner/data rails/custom-gitlab-ci-runner app:setup
The keys randomart image is:
+--[ RSA 2048]----+
|        BO*o.    |
|      o.+B.*     |
|       =+ *      |
|        .o o     |
|        E o .    |
|         . o     |
|          .      |
|                 |
|                 |
+-----------------+
Please enter the gitlab-ci coordinator URL (e.g. http://gitlab-ci.org/ )
https://_GitLab_CI_IP_:_port_/
Please enter the gitlab-ci token for this runner: 
_Registration_Token_
Registering runner as d753dc5dfd8f with registration token: _Registration_Token_, url: https://_GitLab_CI_IP_:_port_/.
Runner token: beb0bb9d385da229c9edccaab64348
Runner registered successfully. Feel free to start it!
# 起動
$ sudo run --name gitlab-ci-runner -d -v /opt/gitlab-ci-runner:/home/gitlab_ci_runner/data -p 10443:443 rails/custom-gitlab-ci-runner
# hosts弄る
$ sudo docker exec -it gitlab-ci-runner bash
root@beb0bb9d385d:/# echo "_GitLab_CI_IP_ hoge.fuga" >> /etc/hosts
```

これで自動デプロイができるようになったと思います。
あとはGitLab CIにログイン後、myAppプロジェクトを読み込み、Jobsからpush毎に行いたい内容と成功時行いたい内容を書いておきます。
pushごと：

```sh
git submodule update --init
ls -la
cp -rf myApp $HOME/
cd $HOME/myApp
```

成功時：

```sh
cd $HOME/myApp
bundle install
bin/rake db:migrate RAILS_ENV=development # pushするRailsアプリで既にdatabaseを設定しておく必要がある
apache2restart.sh # 好きなやり方で
```

こんなところかなあ…つっこみあったらください。
突っ込まれたところからなおしていきたいと思います。
では、お疲れさまでした。