2017-10-25 更新: GitLab Runner 10.0 からインストール手順が変わったので、コマンドを修正しました。
最近 GitLab が GitHub を追い越す勢いで進化しています。プライベートリポジトリが無制限に使え、UI も使い勝手が良く、汎用的な CI ツールの GitLab CI まで無料で使えます。おかげで BitBucket は使うことがなくなりました。
今回、Docker コンテナー上で動かす Rails プロジェクトを gitlab.com 上で CI することになったので、そのやり方をメモしておきます。
やりたいこと
- gitlab.com のリポジトリにソースを push したら自動で CI が走るようにしたい
- Docker 化した Rails アプリを Docker コンテナーのままテストしたい
検証環境
- Ubuntu 16.04.2 Xenial 64bit (on Vagrant)
- Docker Engine 17.03.1-CE
- Docker Compose 1.12.0
- Ruby on Rails 5.1.2
- GitLab Runner 10.1.0
GitLab CI とは?
- GitLab 8.0 から同梱されたCIツール
- オープンソース
- GitLab と GitLab CI 環境を自分でホスティングすれば、プライベート CI サービスも構築可能
- プライベートリポジトリでも利用可能
- 各種開発言語のほか、 Docker のビルドも可能
- パイプラインの構築が可能
- 例: 環境設定→テスト→コード静的解析→成果物の保存→デプロイ
- ビルドの並列実行も可能
GitLab CI の仕組み
- CI が起動すると、 Runner が動く
- Specific Runners (自分で用意したビルド環境) か、 Shared Runner (GitLab.com で CI する場合に利用可能 / ほかのユーザーと共有) を選べる
- Runner は
.gitlab-ci.yml
に記述したコマンド通りに各ステップを実行する - Runner 内のテスト結果は、インターネット経由で GitLab.com に通知される
GitLab Runner のインストール
GitLab CI を利用するためには、GitLab CI API と会話するための GitLab Runner が必要です。 Docker コンテナー内で動かす方法もありますが、今回は直接ホストPCにインストールしました。
参考: Install GitLab Runner using the official GitLab repositories - GitLab Documentation
curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh | sudo bash
sudo apt-get install gitlab-runner
GitLab Runner の登録 (Register)
あらかじめ GitLab 上で作っておいたリポジトリのトップページのヘッダーから "Settings -> Pipelines" 画面を開くと、 Runner Token が書いてあるので、この値をメモしてください。
その後、以下のコマンドを叩いて、 GitLab Runner を登録します。
sudo gitlab-runner register -n \
--url https://gitlab.com/ \
--registration-token <上でメモしたトークン> \
--executor docker \
--description "Docker-in-Docker" \
--docker-image "docker:latest" \
--docker-privileged
成功すると、 /etc/gitlab-runner/config.toml
が以下のような内容で作成されます。
concurrent = 1
check_interval = 0
[[runners]]
name = "Docker-in-Docker"
url = "https://gitlab.com/"
token = "<ユニークなトークン>"
executor = "docker"
[runners.docker]
tls_verify = false
image = "docker:latest"
privileged = true
disable_cache = false
volumes = ["/cache"]
shm_size = 0
[runners.cache]
また、 Pipelines 画面にも、いま登録した Runner が表示されます。
設定ファイルの作成
リポジトリ内のファイルは以下のように配置しています。
.
├── .gitlab-ci.yml
├── Dockerfile
├── Gemfile
├── Gemfile.lock
├── README.md
├── Rakefile
├── app
│ :
├── config
│ ├── database.yml
│ :
├── docker-compose.yml
│ :
重要なファイルの中身は以下のようになっています。
Gemfile
source 'https://rubygems.org'
gem 'mysql2'
gem 'rails', '5.1.2'
group :development, :test do
gem 'listen'
gem 'database_cleaner'
gem 'rspec-rails', '~> 3.5'
end
Dockerfile
Rails を動かすためのイメージを構築します。
FROM ruby:2.4.1
ENV APP_ROOT /myapp
WORKDIR $APP_ROOT
EXPOSE 3000
RUN apt-get update -qq && apt-get install -y \
build-essential \
libpq-dev \
nodejs \
mysql-client \
sqlite3 \
postgresql-client
ADD Gemfile $APP_ROOT
ADD Gemfile.lock $APP_ROOT
RUN bundle install
ADD . $APP_ROOT
docker-compose.yml
version: '2'
services:
app:
build: .
environment:
RAILS_ENV: development
MYAPP_DATABASE_PASSWORD: FOOBAR
command: bundle exec rails s -p 3000 -b '0.0.0.0'
volumes:
- .:/myapp
ports:
- "3000:3000"
depends_on:
- db
db:
image: mysql:5.7
volumes:
- db-data:/var/lib/mysql
environment:
MYSQL_DATABASE: sampleapp_dev
MYSQL_ROOT_PASSWORD: FOOBAR
ports:
- "3306:3306"
volumes:
db-data:
driver: local
何をやっているか簡単に説明すると、以下のようになります。
- Rails アプリと MySQL を別々のコンテナーで立て、アプリから DB を参照できるようにしている
- DB のデータをホストPC上に永続化できるよう、データボリューム (db-data) を db コンテナーの
/var/lib/mysql
にマウントしている
config/database.yml
default: &default
adapter: mysql2
pool: 5
encoding: utf8mb4
collation: utf8mb4_unicode_ci
username: root
password: <%= ENV['MYAPP_DATABASE_PASSWORD'] %>
host: db
development:
<<: *default
database: sampleapp_dev
username: root
password: <%= ENV['MYAPP_DATABASE_PASSWORD'] %>
test:
<<: *default
database: sampleapp_test
production:
<<: *default
database: sampleapp_production
.gitlab-ci.yml
GitLab CI のビルドスクリプトは、 .gitlab-ci.yml
に定義します。
なお、GitLab CI は image
プロパティで指定した Docker イメージの中でビルドスクリプトを実行するため、今回は docker:latest
コンテナーの中で app と db コンテナーを立ち上げる Docker-in-Docker (dind) 構成を作ります。
image: docker:latest
variables:
DOCKER_DRIVER: overlay
services:
- docker:dind
before_script:
- docker info
- apk update
- apk upgrade
- apk add python python-dev py-pip build-base
- pip install docker-compose
build:
stage: build
script:
# To connect db from app, launch 'db' at first
- docker-compose up -d --build db
- docker-compose up -d --build app
- docker-compose ps
- docker-compose run app rake db:create
- docker-compose run app rails db:migrate RAILS_ENV=development
- docker-compose run --rm app bundle exec rspec
docker:latest
コンテナー内に Docker Compose をインストールするため、 before_script
ブロック内でインストールコマンドを記述しています。これについては後述の「なんで docker/compose イメージを使わないの?」で補足しています。
また、build
ブロック内で、わざわざ docker-compose up -d --build
を冗長に書いている理由は、後述の「ビルドを冗長に書いてある理由は?」を参照してください。
CI 実行
ここまで用意したら、リポジトリにソースコードを push してみましょう。 GitLab がソースコードの更新を検知すると、 .gitlab-ci.yml
の内容通りに CI を開始します。進捗は "Pipelines -> Jobs" 画面に表示されます。
無事 app コンテナー内の RSpec が実行され、ビルドが成功しました。素晴らしい
補足
なんで docker/compose イメージを使わないの?
公式イメージの docker/compose は、 Dockerfile 内で ENTRYPOINT ["/code/.tox/py27/bin/docker-compose"]
と記述されているため、ビルドを実行すると No such command: sh
というエラーを吐いてビルドが失敗します。
Running with gitlab-ci-multi-runner 9.3.0-rc.2 (110d530)
on docker-auto-scale (4e4528ca)
Using Docker executor with image docker/compose:1.14.0 ...
Using docker image sha256:3ae34cc70032f8241371a7c6f33c2ea28cbf251db3ab03630f5bb77c648070fa for predefined container...
Pulling docker image docker/compose:1.14.0 ...
Using docker image docker/compose:1.14.0 ID=sha256:c75d0b12cd81547301e8d485063127df882edf1e46924e9455fda31d2920519d for build container...
Running on runner-4e4528ca-project-3115969-concurrent-0 via runner-4e4528ca-machine-1499654908-5011913b-digital-ocean-2gb...
Cloning repository...
Cloning into '/builds/1000k/sampleapp'...
Checking out 70775b6b as build-ci-cycle...
Skipping Git submodules setup
No such command: sh
Commands:
build Build or rebuild services
bundle Generate a Docker bundle from the Compose file
:
ERROR: Job failed: exit code 1
Docker なら --entrypoint
というパラメーターを付けて ENTRYPOINT を上書きできるのですが、 .gitlab-ci.yml
ではその機能が現時点で最新の 9.3 で実装されていないため、回避できません。
なお、 2017年7月22日リリースの GitLab 9.4 では .gitlab-ci.yml
内でイメージの ENTRYPOINT を上書きできるようになるそうです。 (参考: Allow overriding ENTRYPOINT from .gitlab-ci.yml (#1421) · Issues · GitLab.org / gitlab-runner · GitLab)
加えて、 docker/compose の tags を見ると、どのバージョンも 'This image has vulnerabilities' と書いてあるのが不安。
仕方ないので、今回の例では docker イメージを pull した後に docker-compose を apk で追加インストールしています。
ビルドを冗長に書いてある理由は?
build:
stage: build
script:
# 冗長じゃん?
- docker-compose up -d --build db
- docker-compose up -d --build app
# これでよくない?
- docker-compose up -d --build
db コンテナーを app より先に立ち上げるために、あえて冗長に書いています。
Rails のテストを成功させるには、テスト実行前に DB の作成とマイグレーションが必要になります。しかし、 GitLab CI は script で定義されたコマンドの実行結果を待たずに先に進みます。したがって、もし先に app が db より先に立ち上がってしまうと、テスト実行時に DB に接続できず、以下のようなエラーを吐いてビルドが失敗してしまいます。
:
# テスト開始
$ docker-compose run --rm app bundle exec rspec
# テスト開始後に、並列で走っていた db コンテナーの作成が完了している
Starting sampleapp_db_1 ...
Starting sampleapp_db_1 ... done
An error occurred while loading ./spec/models/user_spec.rb.
Failure/Error: ActiveRecord::Migration.maintain_test_schema!
Mysql2::Error:
Can't connect to MySQL server on 'db' (111)
今回は app コンテナー作成時に bundle install
で長らく待たされるのが明らかなので、 docker-compose build
する順番を変えるだけで対応しています。
もっと厳格に「db に接続できるまでリトライする」という処理を実装したい場合は、wait-for-it などのラッパースクリプトを用意するのがいいでしょう。詳しくは以下の記事が参考になります。
参考
-
GitLab Continuous Integration (GitLab CI) - GitLab Documentation
- GitLab 公式の CI マニュアル
-
Using Docker Build - GitLab Documentation
- 今回紹介した方法を含め、 Docker イメージを CI する方法が複数書かれています
- Getting started with GitLab and GitLab CI | GitLab
- RailsアプリをDockerで開発するための手順 - Qiita
- [docker] CMD とENTRYPOINT の違いを試してみた - Qiita
-
Docker in Docker のベタープラクティス - Qiita
- dind 構成の解説がわかりやすい
- 「dind はイケてない」というのが最近の風潮なので、本当は
/var/run/docker.sock
を使う方法の方がベター- すんなり GitLab CI で実現できなかったので、今回は見送りました