既存のRailsアプリのローカル開発環境をDockerの仮想環境に切り替える
最近までRailsのアプリの開発はローカル環境で行っていました。
Dockerを学び初めて使ってみたいなと思ったので、開発環境をDockerとdocker-composeで作ってみることにしました。
- 開発環境の完成図
- 前提
- ディレクトリ構成
- Railsアプリコンテナの設定
- Nginxコンテナの設定
- PostgreSQLコンテナの設定
- コンテナを管理するためのdocker-composeの設定
- docker-composeでイメージをビルド、コンテナ起動
- 最後にinitdb.sqlを追加した理由
開発環境の完成図
どういうやつを作ろうか迷ったんですが、、、
Railsアプリをデプロイする際に、AWSで以下のようなWeb3層構成を作りました。↓
Webサーバーには「nginx」、アプリケーションサーバーには「puma」「Rails」、データベースには「PostgreSQL」を使っています。
今回は、これと同じ構成にしようと思います。
ということで簡単な完成図はこんな感じ↓
「Webコンテナ」、「Appコンテナ」、「DBコンテナ」を立ち上げて、それをdocker-composeで管理していく構成で作成していきます。
前提
開発環境の切り替えということを踏まえ、前提は以下となります。
ディレクトリ構成
-my_app -app -db -config -database.yml -puma.rb ... -Docker -app -Dockerfile -web -Dockerfile -nginx.conf -db -Dockerfile -initdb.sql -docker-compose.yml -Gemfile -Gemfile.lock ...
Railsアプリはすでに作成済(ファイル名my_app)だったので、Railsアプリのプロジェクトファイル内に上記のような構成で、Dockerファイルとdocker-compose.yml を追加します。
Dockerファイル内は、それぞれのコンテナのDockerイメージを作成するためのDockerfileと必要な設定ファイル(*.confや*.sql)を追加します。
Railsアプリコンテナの設定
Dockerfile
Docker/app/Dockerfile #バージョンを指定してRubyイメージを取得 FROM ruby:2.6.3 #必要なライブラリを取得 RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs ENV RAILS_ROOT /my_app Railsアプリのルートディレクトリ作成 RUN mkdir -p $RAILS_ROOT WORKDIR $RAILS_ROOT #ホストのGemfileとGemfile.lockをRailsコンテナにコピー COPY Gemfile Gemfile COPY Gemfile.lock Gemfile.lock #コピーされたGemfileを参照してbundle install RUN bundle install --jobs 20 --retry 5 --without production #ホストのアプリケーションディレクトリ内をすべてコンテナにコピー COPY . . RUN mkdir -p tmp/sockets
Nginxコンテナの設定
NginxとRailsは「 UNIXソケット通信」を使います。
Dockerfile
Docker/web/Dockerfile FROM nginx # インクルード用のディレクトリ内を削除 RUN rm -f /etc/nginx/conf.d/* # Nginxの設定ファイルをコンテナにコピー COPY /Docker/web/nginx.conf /etc/nginx/conf.d/my_app.conf # ビルド完了後にNginxを起動 CMD /usr/sbin/nginx -g 'daemon off;' -c /etc/nginx/nginx.conf
Nginx設定ファイル
Docker/web/nginx.conf # プロキシ先の指定 # Nginxが受け取ったリクエストをバックエンドのpumaに送信 upstream myapp { # ソケット通信したいのでpuma.sockを指定 server unix:///my_app/tmp/sockets/puma.sock; } server { listen 80; # ドメインもしくはIPを指定 server_name localhost; access_log /var/log/nginx/access.log; error_log /var/log/nginx/error.log; # ドキュメントルートの指定 root /my_app/public; client_max_body_size 100m; error_page 404 /404.html; error_page 505 502 503 504 /500.html; try_files $uri/index.html $uri @my_app; keepalive_timeout 5; # リバースプロキシ関連の設定 location @my_app { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_pass http://myapp; } }
puma.rbの設定
confing/puma.rb threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }.to_i threads threads_count, threads_count port ENV.fetch("PORT") { 3000 } environment ENV.fetch("RAILS_ENV") { "development" } plugin :tmp_restart app_root = File.expand_path("../..", __FILE__) bind "unix://#{app_root}/tmp/sockets/puma.sock" stdout_redirect "#{app_root}/log/puma.stdout.log", "#{app_root}/log/puma.stderr.log", true
PostgreSQLコンテナの設定
Dockerfile
#postgresqlのイメージ取得 FROM postgres #初期化で追加したい処理を書いたファイルを追加 COPY Docker/db/initdb.sql /docker-entrypoint-initdb.d/.
initdb.sqlの設定
Docker/db/initdb.sql ALTER ROLE postgres WITH PASSWORD 'password'; CREATE DATABASE sample_app;
initdb.sqlを必ず初期化で追加する必要なのかはわかりません。笑
なぜ追加したのかは後述。
database.ymlの設定
・ ・ development: <<: *default adapter: postgresql encoding: unicode pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> host: db database: sample_app username: postgres password: password ・ ・
コンテナを管理するためのdocker-composeの設定
version: '3' volumes: postgres_data: public_data: tmp_data: log_data: services: app: build: context: . dockerfile: ./Docker/app/Dockerfile command: bundle exec puma -C config/puma.rb volumes: - .:/my_app - public_data:/my_app/public - tmp_data:/my_app/tmp - log_data:/my_app/log depends_on: - db db: build: context: . dockerfile: ./Docker/db/Dockerfile environment: - POSTGRES_PASSWORD=password volumes: - postgres_data:/var/lib/postgresql/data web: build: context: . dockerfile: ./Docker/web/Dockerfile volumes: - public_data:/my_app/public - tmp_data:/my_app/tmp ports: - 80:80 depends_on: - app
volumesではappの場合、ホスト側のソースコードの変更を即時に反映させるために、ホストのファイルをマウントしています。
dbではデータを永続化するためのマウントです。
「depends_on」はコンテナの作成順序と依存関係を決めるものです。
webコンテナはappコンテナ、appコンテナはdbコンテナって感じです。
docker-composeでイメージをビルド、コンテナ起動
必要な物が揃ったのでコンテナを動かして見ます。
my_app $ docker-compose build
コンテナのイメージをビルドします。
my_app $ docker-compose up -d
コンテナをデタッチで起動
my_app $ docker-compose exec app bin/rails db:migrate
マイグレーションファイルをコンテナ内のRailsアプリに適用させる
これでブラウザでlocalhostに接続するとアプリが表示されました!
最後にinitdb.sqlを追加した理由
initdb.sqlにはCREATE文のみ追加していたんですが、
Docker/db/initdb.sql
CREATE DATABASE sample_app;
その状態でコンテナを起動させてrails db:migrateなどを行おうとすると以下のようなエラーが出ました。
PG::ConnectionBad: FATAL: password authentication failed for user "postgres"
どうやらデフォルトユーザのpostgreにパスワードを与えれば解決できるようだったので、
DBの初期化時にパスワードを与える処理を実行することでうまくRails側でDBを操作することができました。
Docker/db/initdb.sql ALTER ROLE postgres WITH PASSWORD 'password'; CREATE DATABASE sample_app;
こうすることでいちいちDBコンテナに入って設定しなくても、すぐにrailsアプリにマイグレーションファイルを反映させることができます。
ってだけのことでした。
Docker簡単なようで難しいな、、、
まだ始めたばっかだし、ゆっくり勉強してこう