はじめに
インフラの基本構成をAWSで整備できましたので、次の実装として
Docker導入をしました。
作成途中のアプリに初めてDockerを導入しましたので、記事に残します。
ポートフォリオ作成中の初学者のため、間違い等がございましたら
ご教示いただけますと幸いです。
使用技術
・Ruby: 2.7.3
・Rails: 6.1.4
・MySQL: 8.0.25
基礎学習
実装前に下記教材をざあーと学習しました。
はじめにUdemy教材に手をつけましたが、開発環境にdockerを導入するのみなら
YouTube動画 + Qiita記事 で問題ないと思われます。
なので、山浦さんのYoutube動画「docker-compose編」まで完了したら
入門Docker、小島さんのUdemy動画を補助教材に使うのが
個人的にはオススメです。
準備
・Mac版のDockerDesktopをインストール、起動させる
手順
今回の実装は、以下の通り
1: Dockerfileの作成
2: entrypoint.shの作成
3: docker-compose.ymlの作成
4: config/database.ymlの編集
5: ターミナルでコマンド実行
6: ブラウザで確認
000: MySQLへの接続
1: Dockerfileの作成
まず、dockerイメージをビルドする際に必要なファイルを作成する。
# 既存アプリのrubyバージョンと合わせる
FROM ruby:2.7.3
# Node.jsとyarnのインストール
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
&& echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list \
&& apt-get update -qq \
&& apt-get install -y nodejs yarn
# Dockerコンテナに作業ディレクトリを作成する
WORKDIR /自分のアプリ名
# ホスト側のGemfileとGemfile.lockをDockerコンテナ側にコピーする
COPY Gemfile ./Gemfile
COPY Gemfile.lock ./Gemfile.lock
# bundlerをインストールした後、gemをインストールする
RUN gem install bundler
RUN bundle install
# ホストのアプリケーションをdockerコンテナにコピー
COPY . /自分のアプリ名
# dockerコンテナ起動時にentrypoint.shが実行されるように設定
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
# dockerの3000番ポートを解放する
EXPOSE 3000
# Railsサーバーの実行コマンド
# (バインドを0.0.0.0とし、アクセスするipアドレスの制限をなくす)
CMD ["rails", "server", "-b", "0.0.0.0"]
参考:コンテナに使用したimageのOSを調べる
apt-getコマンドはOSがdebian系統であれば使用できるコマンドらしい。
なので、これを機会にimageのOSが気になり調べてみた。
MacOSでこれまで強くOSを意識したことがなかったけど、ちょっと勉強になった。
# 参考まで
% docker run --rm ruby:2.7.3 sh -c "cat /etc/*-release"
↑ DockerHubにあがってる公式イメージ
〜〜返答〜〜
Unable to find image 'ruby:2.7.3' locally
2.7.3: Pulling from library/ruby
0bc3020d05f1: Already exists
a110e5871660: Already exists
83d3c0fa203a: Already exists
a8fd09c11b02: Already exists
14feb89c4a52: Already exists
958d2475f181: Already exists
379448f8a7e9: Already exists
3944488d15f8: Already exists
Digest: sha256:ee2b19ced1d44600963c52691b9b6975e209286e7168230d03afe7894f9c81a0
Status: Downloaded newer image for ruby:2.7.3
PRETTY_NAME="Debian GNU/Linux 10 (buster)"
NAME="Debian GNU/Linux"
VERSION_ID="10"
VERSION="10 (buster)"
VERSION_CODENAME=buster
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"
2: entrypoint.shの作成
DockerfileのENTRYPOINTで当ファイルを参照するように設定しているので
ここも記述していく。
コンテナ起動後の実行される動作を設定するファイル。
(なのでdocker起動時に引数を渡すなら設定は必須ではないはず…)
docker公式ページ内のrailsを実装するコードで紹介されていたので、今回は実装した。
# bin/bashが起動する
set -e
# railsエラーを招くプロセスを削除する
rm -f /myapp/tmp/pids/server.pid
# DockerfileのCMDに動作が移る設定
exec "$@"
3: docker-compose.ymlの作成
次に、複数コンテナを起動させる為のdocker-composeに必要なファイルを作成する。
本来、dockerは1プロセス1コンテナと決まっている為、webアプリのように
webサーバーとdbサーバーを用いる構成の場合、相互を繋ぐネットワークを構成したり
別途の作業が必要で面倒。
ただ、docker-composeを使用すれば、webサーバーとdbサーバーに関する
それぞれのコンテナを作成して、良い感じに繋いでくれるので
設定が容易になる。
# composefileのフォーマットを決める(https://docs.docker.com/compose/compose-file/)
version: '3'
# dbとwebで2つのコンテナを作成する
services:
db:
# イメージはmysqlのバージョン8.0を選択
image: mysql:8.0
# mysql8.0より認証方式が変更された為、従来設定を戻すコマンドを入れる
command: --default-authentication-plugin=mysql_native_password
# ホストとコンテナのデータが同期するようボリュームを設定
volumes:
- ./自分のアプリ名/db/mysql_data:/var/lib/mysql
# 環境変数を用いてrootのパスワードを設定
environment:
MYSQL_ROOT_PASSWORD: Rails.application.credentials.db_development[:password]
web:
# カレントディレクトリでビルドさせる設定
build: .
# server.pidをrmコマンドで消しておく
# サーバーを起動する
command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
# ホストのアプリケーションをdockerコンテナと同期させる
volumes:
- .:/自分のアプリ名
# railsとdockerの3000番ポートを解放する
ports:
- "3000:3000"
# dbとの依存関係を持たせる
depends_on:
- db
補足:
volumesで設定している./自分のアプリ名/db/mysql_dataは
ホスト側に新しくディレクトリを作成されるものなので
任意で、パスやディレクトリ名を決める。
rm -f tmp/pids/server.pidは
サーバーにプロセスが残っていると起動できないという
rails特有のエラーを発生させない為に入れている。
4: config/database.ymlの編集
ここでは、操作しているシェルの設定ファイルに
環境変数を書き込み、直接database.ymlに書き込まないようにしている。
なお、ターミナルからEDITOR="vi" bin/rails credentials:editコマンドを打って
機密情報をcredentials.yml側に記述する方法を採用しているが
この方法の用途は本番環境と説明する情報が多い。
開発環境なので、この方法が望ましいか分からないが
本番環境と同様に機密情報はGitHubに載ってしまうような
情報流出のリスクを防ぐためにcredentials.yml側に記述する方法で
設定しておく事にする。
加えて、先ほどdocker-compose.ymlで設定したdbが
hostになるように記述する。
% EDITOR="vi" bin/rails credentials:edit
~~上記コマンドでvi起動~~
( iキーを押して、insertモードで下記を入力する)
db_development:
password: MySQLのrootユーザーに設定しているパスワード
default: &default
adapter: mysql2
encoding: utf8mb4
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
username: root
password: Rails.application.credentials.db_development[:password]
socket: /tmp/mysql.sock
host: db
development:
<<: *default
database: アプリ名_development
test:
<<: *default
database: アプリ名_test
以下省略
以上でファイル設定は完了。
5: ターミナルからコマンド実行
まずは、ローカルからターミナルを実行して
webとdbの2つのコンテナを起動させる。
**docker-compose upコマンドは、
・ dockerイメージを生成するdocker-compose buildコマンド
・ コンテナを起動させるdocker-compose runコマンド
を1コマンドで実行する**もの。
# Dockerイメージを生成後、同イメージをもとにコンテナを作成/起動させる
## -dオプション: バックグラウンドで起動
% docker-compose up -d
# 起動しているかwebコンテナの名称を確かめる
## コンテナはwebとdbの2つが起動している
% docker-compose ps -a
# 起動中のwebコンテナに接続する
## bashシェルを用いてインタラクティブモードで接続している
% docker container exec -it webコンテナ名 bash
ここまでのコマンド実行では、
仮想環境にあるコンテナにはデータベースが存在しない。
その為、webコンテナ(Rails)内に入った状態で、
DBに関わるのrailsコマンドを実行していく。
# データベースの 新規作成/マイグレーション/テスト用データの投入 を行う
rails db:create
rails db:migrate
rails db:seed
6: ブラウザで確認
あとはブラウザで「localhost:3000」を入力して表示されるかを確認する。
ローカルで、rails sコマンドを実行しなくとも
dockerが仮想環境上でアプリを表示してくれているはず!
000: MySQLへの接続
データベースは以前まで使用していたデータベースとは
別の場所(コンテナ内)にある。
その為、bashを指定してインタラクティブモードで
作成したデータベースのコンテナにログインする。
なお、コンテナへのログイン後にmysqlの接続で入力するパスワードは
docker-compose.ymlで設定したパスワードをそのままコピペする。
# 起動中のDBコンテナ名を確認
% docker-compose ps
# 起動中のDBコンテナにbashシェルでインタラクティブモードとしてログイン
% docker container exec -it データベースのコンテナ名 bash
# rootユーザーでログインすることを宣言
root@1234567890:/# mysql -u root -p
# パスワードをdocker-compose.ymlからコピペ
Enter password: Rails.application.credentials.db_development[:password]
補足情報
上記実装では、Dockerのwebコンテナ内で
RSpecコマンドをしてもこれまで通りテストをパスできません。
(dbコンテナではなく、webコンテナである事に注意)
厳密にいうと、部分的にRSpecのmodelテスト等はパスするが
chromeなどのブラウザ機能をコンテナに入れれていないため
RSpecのsystemテストがパスできません。
加えて、local内でもrspecコマンドで実行してもテストは通りません。
理由は、今回編集したdatabase.ymlの接続先hostをdb(dockerのdbコンテナ)としているため
接続が許されるweb(dockerのwebコンテナ)からしかアクセスできない設定しているから…
だと思われます。
↑ 初学者のため、仮説です。
そのため、私はCI/CDパイプラインを構築できるCircleCIを導入し
CircleCI内で自動テストを起動させる実装をしました。
下記記事でまとめておりますので、宜しければ参考にしてください。
参考記事
終わりに
本番環境(awsのEC2)では、applicationサーバーにnginxを使用しているんですが
開発環境も同じ実装にすべきか悩み中です。
開発環境はアクセス数が高くなって負荷が掛かるとは思えないから
applicationサーバーをかます必要があるのかな…と感じつつも
Qiita記事でdockerでnginx仕様の実装も見られるので、
意味のない実装ではないんだろうと想像しています。
最後までお読みいただき、ありがとうございました。
