いわりょのBlog

IT関連で学んだことを書いていきます。

既存のRailsアプリのローカル開発環境をDockerの仮想環境に切り替える

最近までRailsのアプリの開発はローカル環境で行っていました。

Dockerを学び初めて使ってみたいなと思ったので、開発環境をDockerとdocker-composeで作ってみることにしました。

開発環境の完成図

どういうやつを作ろうか迷ったんですが、、、

Railsアプリをデプロイする際に、AWSで以下のようなWeb3層構成を作りました。↓

f:id:Ryo10Leo:20200115230205p:plain

Webサーバーには「nginx」、アプリケーションサーバーには「puma」「Rails」、データベースには「PostgreSQL」を使っています。

今回は、これと同じ構成にしようと思います。

ということで簡単な完成図はこんな感じ↓

f:id:Ryo10Leo:20200131182635p:plain
Webコンテナ」、「Appコンテナ」、「DBコンテナ」を立ち上げて、それをdocker-composeで管理していく構成で作成していきます。

前提

開発環境の切り替えということを踏まえ、前提は以下となります。

  • ローカルですでにRailsアプリ(作業用ディレクトリなど)を作成済
  • 作業用ファイルのプロジェクトファイル名はmy_appとする
  • Docker,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簡単なようで難しいな、、、
まだ始めたばっかだし、ゆっくり勉強してこう