あすたぴのブログ

astap(あすたぴ)のブログ

Rail5.1 rc1+Dockerでいい感じの環境を構築する

目的

いい感じに使える、Dockerの開発環境を構築する。 最終的な目的はProductionでDockerを使うイメージを固めること。

環境

  • Vagrant
  • Docker
    • Docker version 17.03.1-ce, build c6d412e
  • Rails5.1 rc1

docker for mac はいい噂を聞かないのでVagrant上でDockerを使用する。 当然、docker for mac でも動く。

Railsアプリ作成

bundle init
gem 'rails', '~> 5.1.0.rc1'
bundle instlal --path vendor/bundle

sprockets, turbolinksを抜いています。 DBはmysqlを使用

bundle exec rails new --skip-bundle -d mysql --skip-sprockets --skip-turbolinks .

ローカルにbundle設定があるとダメなので消します。

rm -rf vendor/bundle
rm -rf .bundle

Docker install

sudo apt-get update
sudo apt-get install \
    linux-image-extra-$(uname -r) \
    linux-image-extra-virtual

sudo apt-get install \
    apt-transport-https \
    ca-certificates \
    curl \
    software-properties-common

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

sudo add-apt-repository \
   "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
   $(lsb_release -cs) \
   stable"

sudo apt-get install docker-ce

dockerグループが出来ているので、vagrantユーザーをdockerグループに追加する必要があります。 その後、vagrantからlogoutし再度sshで入ります。

sudo gpasswd -a vagrant docker

docker-compose install

sudo curl -L "https://github.com/docker/compose/releases/download/1.11.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

Dockerfile

Dockerfileを作成します。 alpine linuxをベースにRuby2.4.1の入っているイメージを元にします。 必要なパッケージをインストール後、不要なファイルを消したりします。 mysql2 gem をbuildするために必要なmariadb-devを入れるとmariadb本体も付いてきてしまうため、mariadb本体のファイルを削除しています。

cmd, entrypointはrun時に指定するので付けていません。

Dockerfile

FROM ruby:2.4.1-alpine

ENV LANG ja_JP.UTF-8
ENV BUILD_PACKAGES="curl-dev build-base" \
    DEV_PACKAGE="mariadb-libs mariadb-client mariadb-client-libs tzdata"

RUN gem install bundler \
  && apk --update --upgrade add $BUILD_PACKAGES \
  && apk add mariadb-dev tzdata linux-headers postgresql-dev sqlite-dev git nodejs \
  && rm /usr/lib/libmysqld* \
  && echo 'gem: --no-document' > /etc/gemrc

WORKDIR /app
COPY Gemfile Gemfile
COPY Gemfile.lock Gemfile.lock
RUN bundle install

EXPOSE  3000

docker-compose.ymlを作成します。

spring server起動用のコンテナをwebと同一Dockerfileから作っています。 springについては以下を参考にさせていただきました。

参考

https://github.com/jonleighton/spring-docker-example http://tech.degica.com/ja/2016/06/14/dockerized-rails-development/

user指定時に、ホストOSのユーザーIDとグループIDを指定している。 ユーザーを指定しない場合、rootで実行されるため、Railsコマンドで生成したファイルがrootの所有となって、ホストOSのエディタから編集が出来なくなる。

version: '2'
services:
  db:
    image: mysql:5.7.17
    volumes:
      - ./store:/var/lib/mysql
    environment:
      MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'

  web:
    build: .
    command: bundle exec rails s
    user: "${uid}:${gid}"
    working_dir: /app
    environment:
      RACK_ENV: 'development'
    volumes:
      - .:/app
    ports:
      - "3000:3000"
    depends_on:
      - db

  spring:
    build: .
    command: docker/run.sh spring
    volumes:
      - .:/app

mysqlの設定

※追記 mysqlコンテナを用意するようにしたのでこちらは不要になりました。

DockerコンテナからホストOSのMySQLに接続するためにホストOS側のMySQLの設定を修正します。

sudo vi /etc/mysql/my.cnf

bindするアドレスに0.0.0.0を追加します。

bind-address    = 127.0.0.1
bind-address    = 0.0.0.0

MySQLを再起動します。

sudo service mysql restart

bindアドレス同様に、 rootアカウントがlocalhostからしかログインできないためユーザーを追加します。

mysql -u root
CREATE USER 'root'@'172.17.0.2';
GRANT ALL PRIVILEGES ON *.* TO 'root'@'172.17.0.2';
FLUSH PRIVILEGES;

開発

docker-compose系のコマンドを叩くのが面倒な為、scriptを用意しています。

  • 引数なしでヘルプ表示
  • 第一引数がupなら、docker-compose up
  • 第一引数がrspecならRACK_ENV=testでrspec起動
  • 第一引数がsならフォアグラウンドでRails起動 port指定
  • 第一引数がspringならspringサーバーをバックグラウンドで起動
  • 引数ありの場合は引数をそのまま渡す。

docker/bundle.sh

#!/bin/bash
ENV=$RACK_ENV
export uid=$UID
export gid=$GID

if [ "$ENV" = 'production' ]; then
  docker-compose run --entrypoint=$entrypoint -e RACK_ENV=$ENV web s
  exit 0
fi

entrypoint=docker/run.sh
if [ "$1" = 'up' ]; then
  docker-compose up
elif [ "$1" = 'rspec' ]; then
  docker-compose run --entrypoint=$entrypoint -e RACK_ENV=test web $@
elif [ "$1" = 'spring' ]; then
  docker-compose run -u "$UID:$GID" --entrypoint=$entrypoint -d spring spring
elif [ "$1" = 's' ]; then
  docker-compose run -p 3000:3000 -u "" --entrypoint=$entrypoint -e RACK_ENV=development web s
else
  docker-compose run -u "$UID:$GID" --entrypoint=$entrypoint -e RACK_ENV=development web $@
fi

docker/run.sh

alpine linuxにはbash等が入っていなく、わざわざ入れる必要もないコードなのでデフォルトのシェルであるashで書いています。

#!/bin/ash

if [ "$RACK_ENV" = 'development' -o "$RACK_ENV" = 'test' -o "$RACK_ENV" = '' ]; then
  if [ $# -eq 0 ]; then
    bundle exec rails -h
  elif [ $1 = 'rspec' ]; then
    bundle exec $@
  elif [ $1 = 'spring' ]; then
    bundle exec spring binstub --all
    #bundle exec spring binstub --remove --all
    bin/spring server
  else
    time bundle exec rails $@
  fi
  exit 0
fi

bundle exec unicorn -c config/unicorn.rb -E $RACK_ENV

deploy

deploy時に使用するDockerfileを作る

FROM ruby:2.4.1-alpine

ENV LANG ja_JP.UTF-8
ENV BUILD_PACKAGES="curl-dev build-base" \
    DEV_PACKAGE="mariadb-libs mariadb-client mariadb-client-libs tzdata"

RUN gem install bundler \
  && apk --update --upgrade add $BUILD_PACKAGES \
  && apk add mariadb-dev tzdata linux-headers postgresql-dev sqlite-dev git nodejs \
  && rm /usr/lib/libmysqld* \
  && echo 'gem: --no-document' > /etc/gemrc

WORKDIR /app
COPY Gemfile Gemfile
COPY Gemfile.lock Gemfile.lock
RUN bundle install

ENV APP_HOME /app
RUN mkdir -p $APP_HOME
WORKDIR $APP_HOME
COPY . $APP_HOME

EXPOSE  3000

circle_ciでbuildして、testを実行。 testが通ったら、imageをECRなり、DockerHubなり、GCRへpushする。

まとめ

当初、DockerfileでアプリのソースをCOPYしてイメージを作っていたが、 ソースを含めてイメージを作るのはdeploy時だけでいいのでやめた。 軽く開発してみたが上手く進められている。 deploy周り(CricleCI含め)を妄想しかしていないので、いざやってみたらまた変わるかもしれない。

問題点が1つあって、user指定で実行するとbundler関係のディレクトリがroot所有なのでwarningが出る。 解決策が思いつかない。どなたかいい解決策があれば教えてください。

`/` is not writable.
Bundler will use `/tmp/bundler/home/unknown' as your home directory temporarily.

今回のソースは以下にあります。 https://github.com/astapi/development_rails_with_docker