Table of Contents
はじめに
FundastA Inc.の片田です!
趣味でやってるチーム開発のメンバーからある日こんな要望が。
「docker-compose upした後に毎回rails db:create, rails db:migrateするの面倒すぎる。なんとかしろ。」
とのお達しがあったので、主にDocker、CircleCI、AWS周りを担当する僕が対応することとなりました。
僕も元々Railsからプログラマーの世界に飛び込んでいるので、Railsを書いていた時は思考停止して一つ一つのコマンドを叩いていたのですが、
確かにこれ面倒臭い…。
ということでさっさと自動化していきましょう〜!
コマンド一発でrails db:createするには?
今回実現すること
今まで、
1 |
docker-compose up |
1 |
docker-compose exec api bin/rails db:create |
1 |
docker-compose exec api bin/rails db:migrate |
1 |
docker-compose up |
とりあえずやってみた
だいたいdocker-compose.ymlいじれば行けそうな気がしたんで、早速修正。
元のdocker-compose.ymlがコチラ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
version: "3" services: db: image: mysql:5.7 environment: MYSQL_ROOT_PASSWORD: password MYSQL_DATABASE: root ports: - "3306:3306" api: build: . depends_on: - db command: /bin/sh -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'" volumes: - .:/languageMemoApp ports: - "3000:3000" tty: true |
修正したものがコチラ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
version: "3" services: db: image: mysql:5.7 environment: MYSQL_ROOT_PASSWORD: password MYSQL_DATABASE: root ports: - "3306:3306" api: build: . depends_on: - db command: /bin/sh -c "rm -f tmp/pids/server.pid && bundle exec rails db:create && bundle exec rails db:migrate && bundle exec rails s -p 3000 -b '0.0.0.0'" volumes: - .:/languageMemoApp ports: - "3000:3000" tty: true |
見栄えは悪いですが、これで実現できるんじゃないか…!
と思ったのですが、これだとエラーが発生します。
エラーの内容は下記の通りです。
1 |
api_1 | Can't connect to MySQL server on 'db' (111 "Connection refused") |
DBコンテナへのコネクションエラーです。
ただ、このエラー、コンテナの初回起動時のみ発生して、もう一度docker-compose upすると普通に起動できるようになるんです。
上記のdocker-compose.ymlでも、depends_onでDBコンテナへの依存関係は定義しているはずだし、DBコンテナの外部ポートも開けてる…。
それに、DBコンテナが起動していない状態なら、dbというホストが見つからないよ!とうエラーが出るはずなので、DBコンテナは問題なく起動できているっぽい。
原因が分からず、チームメンバーのDockerに詳しい方に相談しつつ1日半がたったある日、衝撃の事実を知りました。
depends_onは、起動順序だけを管理しており、コンテナが起動し終えるのを待ってくれない。
コチラをご覧ください。
Compose はコンテナの準備が「整う」まで待ちません(つまり、特定のアプリケーションが利用可能になるまで待ちません)。単に起動するだけです。
とのことです。。初めて知りました。
上記のdocker-compose.ymlで初回のみDBコネクションエラーが起きるのは、DBが起動を完了するのを待たずにAPIコンテナが起動し、rails db:createコマンドを実行していたため、エラーが発生していた模様です。
DBコンテナが起動し終えてからAPIコンテナを起動するには?
上記の公式ドキュメントに方法が書いてありました。
wait-for-it や dockerize のようなツールを使います。これらはラッパー用のスクリプトであり、アプリケーションのイメージに含めることができます。また特定のホスト側のポートに対して、TCP 接続を受け入れ可能です。
今回はwait-for-itを使ってみることにしました。
wait-for-itのリポジトリはコチラ
ここに使い方云々は書いてあるので、その通りに使っていきます。
- wait-for-itリポジトリをローカルにクローンする
- wait-for-it.shをrailsプロジェクトのあるルートディレクトリ直下に配置
- wait-for-it.shのwait_for_wrapperメソッドに下記を追加
1 |
rm -f tmp/pids/server.pid && bundle exec rails db:create && bundle exec rails db:migrate && bundle exec rails s -p 3000 -b '0.0.0.0' |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
wait_for_wrapper() { # In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692 if [[ $WAITFORIT_QUIET -eq 1 ]]; then timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --quiet --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT & else timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT & fi WAITFORIT_PID=$! trap "kill -INT -$WAITFORIT_PID" INT wait $WAITFORIT_PID WAITFORIT_RESULT=$? if [[ $WAITFORIT_RESULT -ne 0 ]]; then echoerr "$WAITFORIT_cmdname: timeout occurred after waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT" fi rm -f tmp/pids/server.pid && rails db:create && rails db:migrate && rails s -p 3000 -b '0.0.0.0' return $WAITFORIT_RESULT } |
- docker-compose.ymlから、起動時に作成したシェルファイルを叩くようにする
1 |
entrypoint: ./wait-for-it.sh db:3306 |
docker-compose.ymlの完成形が下記の通りです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
version: "3" services: db: image: mysql:5.7 environment: MYSQL_ROOT_PASSWORD: password MYSQL_DATABASE: root ports: - "3306:3306" api: build: . depends_on: - db entrypoint: ./wait-for-it.sh db:3306 volumes: - .:/languageMemoApp ports: - "3000:3000" tty: true |
これで無事、docker-compose upコマンド一発でDBの作成、マイグレーションまでを行うことに成功しました!!
おわりに
今回はチームメンバーからの要望を無事に実現できたわけですが、これがベストプラクティスでは無いような気がします…。(特にシェルスクリプトの部分とか)
この記事をご覧になった有識者の方、是非是非ご指摘ください。