71
65

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

GitLab CI で Docker を使って Rails アプリを CI する

Last updated at Posted at 2017-07-10

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 が書いてあるので、この値をメモしてください。

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 が以下のような内容で作成されます。

/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

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 を動かすためのイメージを構築します。

Dockerfile
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

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

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) 構成を作ります。

.gitlab-ci.yml
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" 画面に表示されます。

jobs

無事 app コンテナー内の RSpec が実行され、ビルドが成功しました。素晴らしい :metal_tone5:

補足

なんで 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 で追加インストールしています。

ビルドを冗長に書いてある理由は?

.gitlab-ci.yml
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 などのラッパースクリプトを用意するのがいいでしょう。詳しくは以下の記事が参考になります。

参考

71
65
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
71
65

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?