BPSの福岡拠点として一緒にお仕事をさせて頂いています、株式会社ウイングドアのタカキと申します。
ウイングドアでは複数のテーマの勉強会が定期的に開催されており、私は未経験であるRubyの勉強会に参加しています。
その際にDocker環境で試行錯誤した内容について書いていきたいと思います。
Rubyの話?Dockerの話?
本記事の内容はRubyをきっかけとしたDockerの話です。
「Docker環境のRailsサーバをコンテナを止めずに再起動する」ために迷走した記録になります。
結論としてはSupervisorを使用して実現しました。
Dockerについて有用な記事が多数ある中初歩的な内容で恐縮ですが、勉強の一環として見ていただけますと幸いです。
※Supervisorについては既にこちらの記事に大変詳しい内容が載っています。
⚓Docker環境でRailsサーバを再起動するには?
環境
- Docker Desktop for Mac
- Version 2.5.0.1
RubyのDocker環境構築は主にこちらを参考にさせていただき、シェルを実行するだけで設定ファイルの作成〜Railsサーバの起動まで完了するようにしました。
参考:Rails 6 + MySQL on Dockerの環境を秒速で構築する
参考:丁寧すぎるDocker-composeによるrails5 + MySQL on Dockerの環境構築(Docker for Mac)
Ctrl+C
できない
チュートリアルを進めていく中で、Railsサーバを再起動する必要が出てきました。
Ctrl+C
でサーバ停止できるとのことですが、Dockerコンテナ起動と共にバックグラウンドでサーバを起動している場合はどこでCtrl+C
を押せば良いのだろう?と詰まってしまいました。
調べてみると、Docker環境ではコンテナ自体を再起動させるしかないようでした。
コンテナ再起動で無事進めることができましたが、その後も何度か再起動する度に
コンテナ再起動→docker execでコンテナに入り直す
という一手間が地味に面倒に感じるようになりました。
そこで、コンテナ接続中にコマンドでRailsサーバを再起動したいと思い色々試してみることにしました。
⚓プロセスのkill
Apache等とは違い、Railsサーバの停止・再起動のコマンドはないとのこと。
それならプロセスをkill
して再度立ち上げればいいのではないかと思いました。
プロセスを確認します。
root@e7abaa0edd69:/test_app# ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 2384 636 ? Ss 09:25 0:00 /bin/sh -c rm -f tmp/pids/server.pid && bundle exec rails s -p 3900 -b '0.0.0.0'
root 7 1.4 5.1 1305396 105096 ? Sl 09:25 0:06 puma 4.3.7 (tcp://0.0.0.0:3900) [test_app]
root 68 0.2 0.1 5748 3364 pts/0 Ss 09:32 0:00 bash
root 73 0.0 0.1 9388 2956 pts/0 R+ 09:32 0:00 ps aux
pumaというのがRubyのwebサーバなので、pid 7
をkillします。
kill 7
コンテナが停止しました。😐
⚓init: true
の追加
※結果としてこの設定及びpidは無関係でした。
webサーバのプロセスだけkill
したつもりが、コンテナごと落ちてしまいました。
よく見ると起動時のコマンドがpid 1
で動いています。
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 2384 636 ? Ss 09:25 0:00 /bin/sh -c rm -f tmp/pids/server.pid && bundle exec rails s -p 3900 -b '0.0.0.0'
Linuxにおけるpid 1
はinitプロセスと言って特別らしい🤔 だからkillできなかったのでは? と考えました。
そのため起動コマンドをpid 1
以外で起動させるためにdocker-compose.ymlにinit: true
の設定を追加しました。
- docker-compose.yml
version: '3.7'
services:
web:
build: .
init: true # 追加
command: /bin/sh -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3300 -b '0.0.0.0'"
再度プロセスを確認します。
root@e7b5f06e79c9:/test_app# ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 992 4 ? Ss 09:39 0:00 /sbin/docker-init -- /bin/sh -c rm -f tmp/pids/server.pid && bundle exec rails s -p 3900 -b '0.0.0.0'
root 6 0.0 0.0 2384 764 ? S 09:39 0:00 /bin/sh -c rm -f tmp/pids/server.pid && bundle exec rails s -p 3900 -b '0.0.0.0'
root 8 1.2 3.6 1090032 73864 ? Sl 09:39 0:02 puma 4.3.7 (tcp://0.0.0.0:3900) [test_app]
root 52 0.0 0.1 5748 3648 pts/0 Ss 09:39 0:00 bash
root 74 0.0 0.1 9388 3052 pts/0 R+ 09:41 0:00 ps aux
先程pid 1
で動いていたコマンドがpid 6
になっているので、設定が反映されていそうです。
pumaをkillしてみます。
kill 8
コンテナが停止しました。😑
⚓Supervisorを使用する
この後もtty: true
を設定してみたり起動用シェルを作って呼び出してみたりと迷走し、
Dockerは通常1コンテナにつき1プロセスが実行される
という基本的なことに気がつくまで随分かかりました。
docker-compose.ymlにて、docker起動時のcommandでbundle exec rails s -p 3300 -b '0.0.0.0'
を実行しています。
command: /bin/sh -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3300 -b '0.0.0.0'"
これはもちろんRailsサーバとしてコンテナを立ち上げるためですが、そうして指定したRailsサーバのプロセスを殺しているのでコンテナも終了してしまうということでした。(改めて書くと当たり前過ぎてお恥ずかしい限りです…。)
そこでSupervisorを使い複数プロセスを起動できるようにしました。
設定ファイルの変更・追加
- Dockerfile
# apt-getにsupervisorを追加
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs sudo nano vim psmisc supervisor && rm -rf /var/lib/apt/lists/*
(中略)
# supervisorの設定を追記
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
EXPOSE 80
CMD /bin/sh -c "rm -f tmp/pids/server.pid && /usr/bin/supervisord"
- supervisord.conf
[supervisord]
nodaemon=true
[program:rails]
command=bundle exec rails s -p 3300 -b '0.0.0.0'
- docker-compose.yml
version: '3.7'
services:
web:
build: .
init: true
# commandは使用しない
#command: /bin/sh -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3300 -b '0.0.0.0'"
ビルド後のプロセス確認
supervisordからrailsが起動しています。
pumaをkill
してみます。
root@cbf135dc5728:/test_app# ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 992 4 ? Ss 11:21 0:00 /sbin/docker-init -- /bin/sh -c /bin/sh -c "rm -f tmp/pids/server.pid && /usr/b
root 7 0.0 0.0 2384 724 ? S 11:21 0:00 /bin/sh -c /bin/sh -c "rm -f tmp/pids/server.pid && /usr/bin/supervisord"
root 8 0.0 0.0 2384 744 ? S 11:21 0:00 /bin/sh -c rm -f tmp/pids/server.pid && /usr/bin/supervisord
root 10 0.2 1.0 27168 21136 ? S 11:21 0:01 /usr/bin/python2 /usr/bin/supervisord
root 13 1.7 3.8 1093432 77640 ? Sl 11:21 0:08 puma 4.3.7 (tcp://0.0.0.0:3900) [test_app]
root 71 0.6 0.1 5748 3480 pts/0 Ss 11:29 0:00 bash
root 78 0.0 0.1 9388 2896 pts/0 R+ 11:29 0:00 ps aux
kill 13
コンテナは停止していません🎉
再起動コマンド
直接kill
できるようになりましたが、その必要はなく
supervisorctl restart rails
を実行すればrailsのプロセスが再起動されます。
このrestart
コマンドが使えるということもすぐには理解できず、プロセスkill
+rails s
するシェルを作成するなどしてもう一迷走したりしました…。🤦♀️
⚓シェルにまとめる
ということで、最終的に環境構築用のシェルはこのようになりました。
あれこれ手順を踏まずにコマンド1つで楽に環境作成できます。
※可読性・保守性はあまり良くありませんが、あくまで勉強用として作成しています。
- init.sh
#!/bin/bash
#config setting#############
APP_NAME="test_app"
###########################
echo "docker pull ruby2.7.2"
docker pull ruby:2.7.2
echo "docker images"
docker images
if [ ! -e "Dockerfile" ]; then
echo "make Dockerfile"
cat <<EOF > Dockerfile
FROM ruby:2.7.2
ENV LANG C.UTF-8
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs sudo nano vim psmisc supervisor && rm -rf /var/lib/apt/lists/*
#yarnのセットアップ
RUN curl -o- -L https://yarnpkg.com/install.sh | bash
ENV PATH /root/.yarn/bin:/root/.config/yarn/global/node_modules/.bin:\$PATH
# 作業ディレクトリの作成、設定
ENV APP_HOME="/$APP_NAME"
RUN mkdir -p \$APP_HOME
WORKDIR \$APP_HOME
# ホスト側(ローカル)のGemfileを追加する
ADD ./src/Gemfile \$APP_HOME/Gemfile
ADD ./src/Gemfile.lock \$APP_HOME/Gemfile.lock
# Gemfileのbundle install
RUN bundle install
ADD ./src/ \$APP_HOME
# gem版yarnのuninstall
RUN gem uninstall yarn -aIx
# supervisor
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
RUN sed -i -e "$ a alias rbrestart='supervisorctl restart rails'" ~/.bashrc
EXPOSE 80
CMD /bin/sh -c "rm -f tmp/pids/server.pid && /usr/bin/supervisord"
EOF
else
echo "Dockerfile already exists"
fi
if [ ! -d "src" ]; then
mkdir src && cd src
# Gemfileを作成、編集
echo "make Gemfile"
cat <<EOF > Gemfile
source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
gem 'rails', '~> 6.0.3'
EOF
echo "make Gemfile.lock"
touch Gemfile.lock
cd ../
else
echo "src already exists"
fi
# docker-compose.ymlを作成、編集
if [ ! -e "docker-compose.yml" ]; then
echo "make docker-compose.yml"
cat <<EOF > docker-compose.yml
version: '3.7'
services:
web:
build: .
init: true
volumes:
- ./src/:/$APP_NAME
ports:
- '3900:3900'
EOF
else
echo "docker-compose.yml already exists"
fi
# supervisord.confを作成、編集
if [ ! -e "supervisord.conf" ]; then
echo "make supervisord.conf"
cat <<EOF > supervisord.conf
[supervisord]
nodaemon=true
[program:rails]
command=bundle exec rails s -p 3900 -b '0.0.0.0'
EOF
else
echo "supervisord.conf already exists"
fi
# rails newを実行する
echo "docker-compose run web rails new . --force --skip-bundle"
docker-compose run web rails new . --force --skip-bundle
# webpacker install
echo "docker-compose run web rails webpacker:install"
docker-compose run web rails webpacker:install
# コンテナをビルド・起動
echo "docker-compose up -d --build"
docker-compose up -d --build
init.shを実行するとこのようにファイルが配置されてwebコンテナが起動します。
ホームディレクトリ
├── init.sh
├── src
│ ├── Gemfile
│ └── Gemfile.lock
├── docker-compose.yml
├── Dockerfile
└── supervisord.conf
おまけ
少しでも労力を減らすためにaliasを追加してrbrestart
と打てば再起動コマンドが実行されるようにしました。
これで楽に再起動ができます🙌
⚓まとめ
これまでDockerを使っていてもプロセスについてはあまり意識することがなかったので、理解を深めることができ良い機会になりました。
また、サクッとコンテナ作成・削除できるDockerの手軽さを改めて感じることもできました🐳
晴れて気軽に再起動できるようになったので、肝心のRubyも理解を深められるように引き続き精進していきたいと思います。
お読みいただきありがとうございました🙇♀️
株式会社ウイングドアでは、Ruby on RailsやPHPを活用したwebサービス、webサイト制作を中心に、
スマホアプリや業務系システムなど様々なシステム開発を承っています。