こんにちは。ハコベルカーゴのサーバサイドの開発を担当している貞元です。
ハコベルカーゴの運用のためのWebアプリケーションが、サービス本体とは別に存在します。 こちらは改善や変更の必要なく、長期間コミットがない状況となっていました。 ただ、時間が経つことにより、完全に理解できている人は減っている事態となっていました。 久々に手を入れる必要ができたので、まずは開発環境を構築して対応していこうということになりました。 そのため、まずはローカルの開発環境の構築を簡単にするため、Docker, Docker Composeで構築した内容を紹介します。
なお、ハコベルカーゴとは荷物を送りたい荷主と、空いた時間に仕事を受注したい運送事業者を直接つなぐマッチングサービスです。 詳しくはこちらを見てみてくださいね。
ローカル開発環境
- Docker for Mac
- direnv
Docker関連ファイル
一旦、Docker関連のファイルを全て記載し、注目ポイントについては別途記載します。 すべてRAILS_ROOTへ配置したものとなります。
docker-compose.yml
version: '3' services: mysql: image: mysql:5.7.30 environment: LANG: C.UTF-8 MYSQL_ALLOW_EMPTY_PASSWORD: 'yes' MYSQL_ROOT_PASSWORD: '' volumes: - mysql_volume:/var/lib/mysql expose: - '3306' app: &app_base build: ./docker/app command: /bin/sh -c "rm -f tmp/pids/server.pid && rails s -p 3000 -b 0.0.0.0" ports: - '3000:3000' environment: &app_environment PATH: /app/bin:/app/node_modules/.bin:/usr/local/bundle/bin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin USER_ID: ${USER_ID} GROUP_ID: ${GROUP_ID} MYSQL_ROOT_PASSWORD: '' SPRING_SOCKET: /home/docker/.spring/spring.sock volumes: - .:/app:delegated - bundle_volume:/usr/local/bundle - home_volume:/home/docker working_dir: /app depends_on: - mysql - spring - webpack hostname: app privileged: true entrypoint: ./docker/app/docker-entrypoint.sh tty: true stdin_open: true spring: <<: *app_base command: spring server ports: [] hostname: spring depends_on: [] webpack: <<: *app_base command: webpack-dev-server ports: - '8081:8081' environment: <<: *app_environment WEBPACKER_DEV_SERVER_HOST: 0.0.0.0 hostname: webpack depends_on: [] volumes: mysql_volume: driver: local bundle_volume: driver: local home_volume: driver: local
docker/app/Dockerfile
FROM node:X.X.X-slim as node FROM ruby:X.X.X-slim-stretch COPY --from=node /opt/ /opt/ COPY --from=node /usr/local/lib/node_modules /usr/local/lib/node_modules COPY --from=node /usr/local/bin/node /usr/local/bin/node RUN cd /usr/local/bin \ && ln -s /usr/local/bin/node nodejs \ && ln -s ../lib/node_modules/npm/bin/npm-cli.js npm \ && ln -s ../lib/node_modules/npm/bin/npx-cli.js npx \ && ln -s /opt/yarn-vX.X.X/bin/yarn yarn \ && ln -s /opt/yarn-vX.X.X/bin/yarnpkg yarnpkg RUN set -ex \ && apt-get update \ && apt-get upgrade -y \ && apt-get install -y --no-install-recommends \ iputils-ping locales task-japanese gosu sudo curl git vim less zsh \ build-essential patch ruby-dev zlib1g-dev liblzma-dev \ default-mysql-client default-libmysqlclient-dev \ graphviz \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* \ && echo 'ja_JP.UTF-8 UTF-8' > /etc/locale.gen \ && locale-gen \ && update-locale LANG=ja_JP.UTF-8 \ && cp -p /usr/share/zoneinfo/Asia/Tokyo /etc/localtime ENV LANG ja_JP.UTF-8
docker/app/docker-entrypoint.sh
#!/bin/bash if [ -z "$USER_ID" ]; then echo "USER_ID variable is not defined." exit 1; fi if [ -z "$GROUP_ID" ]; then echo "GROUP_ID variable is not defined." exit 1; fi getent passwd $USER_ID > /dev/null user_result=$? getent group $GROUP_ID > /dev/null group_result=$? set -e # User settings USER=$USER_ID unset USER_ID GROUP=$GROUP_ID unset GROUP_ID if [ $user_result -ne 0 ]; then echo "docker:x:$USER:$GROUP:docker:/home/docker:/bin/bash" >> /etc/passwd fi if [ $group_result -ne 0 ]; then echo "docker:x:$GROUP:" >> /etc/group fi echo "docker ALL=NOPASSWD: ALL" > /etc/sudoers.d/docker # Directories settings chown $USER:$GROUP /home/docker if [ ! -e /home/docker/.spring ]; then mkdir /home/docker/.spring chown $USER:$GROUP /home/docker/.spring fi # Exec exec gosu $USER:$GROUP "$@"
.envrc
export USER_ID=`id -u` export GROUP_ID=`id -g`
注目ポイント
ホストディレクトリのマウント設定にオプションを指定
app: &app_base volumes: - .:/app:delegated
有名な内容ですが、Docker for Macのマウントは遅いです。 そのため、Railsアプリのコードをコンテナにマウント設定にオプションを追加しています。 オプションは、 consistent(default), cached, delegated の3種類ありますが、今回は delegated を指定しました。 詳しくはこちらを参照ください。
Railsを実行するappコンテナはマルチステージビルド
FROM node:X.X.X-slim as node FROM ruby:X.X.X-slim-stretch COPY --from=node /opt/ /opt/ COPY --from=node /usr/local/lib/node_modules /usr/local/lib/node_modules COPY --from=node /usr/local/bin/node /usr/local/bin/node RUN cd /usr/local/bin \ && ln -s /usr/local/bin/node nodejs \ && ln -s ../lib/node_modules/npm/bin/npm-cli.js npm \ && ln -s ../lib/node_modules/npm/bin/npx-cli.js npx \ && ln -s /opt/yarn-vX.X.X/bin/yarn yarn \ && ln -s /opt/yarn-vX.X.X/bin/yarnpkg yarnpkg
Railsは、rubyとnodeを合わせて使用することが多いです。 rubyの公式Dockerイメージにはnodeは含まれていないので、nodeの公式イメージよりファイルをコピーしてruby + nodeのDockerイメージを作成しています。
appコンテナの実行ユーザー・グループを変更
rubyの公式Dockerイメージの実行ユーザー・グループは root:root です。 そのため、コンテナ内で作成されたファイルは root:root となり、マウントしたホストディレクトリのファイルのオーナーがrootになります。
そのため、以下のような方法でホストと同じユーザー・グループでappコンテナの実行ユーザー・グループを変更しています。
- .envrc(direnv)にてホストのユーザーID・グループIDを環境変数に登録
- docker-entrypoint.sh にて環境変数から取得したIDをもとにユーザー・グループを作成
- 作成したユーザー・グループにてappコンテナを実行
なお、Docker for Macはユーザー・グループを変換してくれているみたいで、この内容を実施しなくても大丈夫です。
タスク実行用のコンテナの作成
app: &app_base environment: SPRING_SOCKET: /home/docker/.spring/spring.sock spring: <<: *app_base command: spring server
Railsにはspringというpreloader機能がありますが、各コンテナが別れているため有効に使えません。 そのため、環境変数SPRING_SOCKETを指定し、volumeで永続化したところへパスを指定すれば別々のコンテナでもspringを共有でき、Railsタスクの実行等が早くなります。
binding.pry
gem 'pry-rails' を使用したデバッグで、 binding.pryを使うことがあると思います。 ただ、docker-compose upの場合は入力待ち状態にならないため、別途dockerのコンテナに接続する必要があります。
$ docker ps # 該当のコンテナを確認 $ docker attach コンテナID or コンテナ名
また、コンテナを抜ける場合は、 Ctrl + p を押し Ctrl + q を押します。
まとめ
社内で使用するアプリケーションなど、正常に動いていると改善や変更が長期間ないものがあると思います。 時間が経つことによって、ミドルウェアのバージョンやその他環境の変化も出てくると思うので、今回のようにDockerを使用して環境に依存しない構築手順を用意しておくのはいかがでしょうか。