🐳

Nginx + Rails (unicorn) + MySQL 環境を Docker で構築する

2022/08/22に公開

はじめに

元の記事同様、次の図のような構成の Rails 環境を Docker を利用して構築していきます。Nginx コンテナはリバースプロキシとして挟んでいて、主な構成は Rails と MySQL の 2 つのコンテナになります。

本記事で利用するディレクトリの構成は以下のようになっています。記事内で出てくるファイル名は、適宜こちらを参考にしてください。また、いくつかメインではない設定ファイルがあり、こちらは記事が冗長になることを避けて掲載していません。成果物は GitHub においてありますので、詳細はこちらを参照していただけると幸いです。

  .
  ├── Gemfile
  ├── Gemfile.lock
  ├── README.md
  ├── docker
  │   ├── gitignore # .gitignore としてコピーして利用することを想定
  │   ├── mysql
  │   │   ├── Dockerfile
  │   │   ├── charset.cnf
  │   │   └── password.yml
  │   ├── nginx
  │   │   ├── Dockerfile
  │   │   ├── default.conf
  │   │   └── nginx.conf
  │   └── rails
  │       ├── Dockerfile
  │       └── unicorn.rb
  └── docker-compose.yml

Rails コンテナの定義と unicorn の設定

はじめに、Rails のコンテナイメージを作成していきましょう。これは、docker/rails/Dockerfile として保存します。

FROM ruby:2.6.3
RUN apt-get update -qq && \
    apt-get install -y apt-utils \
                       build-essential \
                       libpq-dev \
                       nodejs \
                       default-mysql-client
RUN mkdir /app
WORKDIR /app
ADD Gemfile /app/Gemfile
ADD Gemfile.lock /app/Gemfile.lock
RUN bundle install -j4
ADD . /app

EXPOSE 3000

また、Rails をインストールするために以下のような Gemfile も作成しておきます。今回は、ディレクトリ直下に作成しています。

source 'https://rubygems.org'
gem 'rails', '5.2.3'

空の Gemfile.lock も一緒に作成します。

touch Gemfile.lock

最後に、docker/rails/unicorn.rb を以下の内容で作成します。これは、app コンテナで config/unicorn.rb として配置されることになります。

worker_processes 8

pid "/var/run/unicorn.pid"
listen "/var/tmp/unicorn.sock"

stdout_path "./log/unicorn.stdout.log"
stderr_path "./log/unicorn.stderr.log"

ここで、/var/tmp/unicorn.sock という Unix ソケットを Listen するように設定しています。Unix ソケットは、Nginx でリバースプロキシするときに利用するファイルなので覚えておいてください。

MySQL コンテナの定義

つぎに、docker/mysql/Dockerfile を書き、MySQL コンテナのイメージを作成します。

FROM mysql:5.7

RUN apt-get update && \
    apt-get install -y apt-utils \
                       locales && \
    rm -rf /var/lib/apt/lists/* && \
    echo "ja_JP.UTF-8 UTF-8" > /etc/locale.gen && \
    locale-gen ja_JP.UTF-8
ENV LC_ALL ja_JP.UTF-8
ADD ./docker/mysql/charset.cnf /etc/mysql/conf.d/charset.cnf

Rails アプリケーションの作成と起動

Rails と MySQL のコンテナイメージの定義ができたので、docker-compose.yml を書いてオーケストレーションをしていきましょう。ここでは、以下のような内容になります。

version: '2'
services:
  app:
    build:
      context: .
      dockerfile: ./docker/rails/Dockerfile
    # command: bundle exec unicorn -p 3000 -c /app/config/unicorn.rb
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    ports:
      - '3000:3000'
    volumes:
      - /var/tmp
      - .:/app
    depends_on:
      - db
    extends:
      file: ./docker/mysql/password.yml
      service: password

  db:
    build:
      context: .
      dockerfile: ./docker/mysql/Dockerfile
    ports:
      - '3306:3306'
    volumes:
      - db_data:/var/lib/mysql
    extends:
      file: ./docker/mysql/password.yml
      service: password

volumes:
  db_data:

Nginx コンテナはこのあと設定するので、Rails コンテナが起動するときに実行するコマンドには rails s を設定しました。

docker-compose.yml が書けたら、ビルドしてみましょう。docker-compose では次のコマンドを叩くことでビルドが可能です。

docker-compose build

このビルドで、bundle install まで走るので、Rails に必要な gem もコンテナイメージにインストールされることになります。

ビルドが成功しても、この段階ではアプリケーションは作成されていません。そこで、docker-compose コマンドを利用して app サービス上で rails new を実行していきます。

docker ps -aq | xargs docker rm -f
docker-compose run app rails new . --force --database=mysql --skip-bundle

これで、Rails アプリケーションに必要なファイルがホストマシン上にも作成されたかと思います。

このとき、Gemfile が更新されているので、gem 'unicorn' という行を追加して再度 docker-compose build してコンテナイメージを更新しておきましょう。ここで単に bundle install しても、コンテナが終了してしまえば状態は保存されず破棄されてしまうからです。

また、docker-compose.yml の volumes の設定によって、docker-compose の置かれているホスト上のアプリケーションルートと Docker 上のアプリケーションルートを同期するように設定してあります。したがって、ホスト上にもアプリケーションのファイル群が作成されています。

これらのファイルはコンテナ上のファイルと同期されているので、ホストに作成されたファイルを編集することで開発を進めることができます。

パスワードなど非公開情報の隔離

docker-compose の設定ファイルでは、docker/mysql/password.yml の内容を extends する形で読み込んでいます。以下に読み込んでいる内容の例を示しました。

version: '2'
services:
  password:
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_USER: hoge
      MYSQL_PASSWORD: hogepassword
      TZ: "Asia/Tokyo"

docker-compose では、このようにサービスをファイルに分けることができるので、上記のような非公開情報を隔離することができます。バージョン管理にはこのファイルを含めないようにすることで、非公開情報を GitHub に置くことを避けることができます。事故を防ぐために .gitignore にも追加しておくのが望ましいでしょう。

スキーマの作成

アプリケーションが作成できたら、次に示す config/database.yml の一部を編集して Rails が MySQL にアクセスできるようにします。docker-compose で定義したサービス名 db で、コンテナから別のコンテナにアクセスすることができます。

default: &default
  adapter: mysql2
  encoding: utf8
  pool: 5
  username: root
  password: <%= ENV['MYSQL_ROOT_PASSWORD'] %>
  host: db

この編集が終わったら、rake db:create して MySQL にスキーマを作成しましょう。

docker-compose run app rake db:create

Rails アプリケーションの起動確認

ここまでくると、Rails アプリケーションを起動させることができます。以下のコマンドを叩いてアプリケーションを起動しましょう。

docker-compose up

うまく設定ができていれば、ブラウザで localhost:3000 にアクセスすると、次のような画面が表示されます。

unicorn の設定

Rails アプリケーションを立ち上げることができたので、最後に unicorn の設定をおこないましょう。まずは、docker-compose の app サービスが次のコマンドを実行するようにコメントアウトを切り替えておきます。

command: bundle exec unicorn -p 3000 -c /app/config/unicorn.rb

このコマンドでも指定しているように、./config/unicorn.rb を配置しておく必要があるので、次のようにコピーしておきます。

cp ./docker/rails/unicorn.rb ./config/

これで、unicorn でアプリケーションを起動できるようになります。unicorn.rb 自体はリポジトリを参照ください。

Nginx によるリバースプロキシ

ここまでで、Rails アプリケーションを Docker コンテナを利用して起動させることができるようになりました。本節では、ここに Nginx コンテナを加えて、リバースプロキシさせるようにしていきます。

まずは、Nginx の Dockerfile を書いてコンテナイメージを作成しましょう。今回は以下のように書きました。

FROM nginx:1.16
RUN apt-get update && \
    apt-get install -y apt-utils \
                       locales && \
    echo "ja_JP.UTF-8 UTF-8" > /etc/locale.gen && \
    locale-gen ja_JP.UTF-8
ENV LC_ALL ja_JP.UTF-8
ADD ./docker/nginx/nginx.conf /etc/nginx/nginx.conf
ADD ./docker/nginx/default.conf /etc/nginx/conf.d/default.conf

Nginx で unicorn に対してリバースプロキシする設定を ./docker/nginx/default.conf に書いています。全体はリポジトリのほうで見ていただくとして、重要なところを次に抜粋しました。

upstream unicorn {
  server unix:/var/tmp/unicorn.sock;
}

server {
    listen       80;
    server_name  localhost;

    # 略

    location / {
        proxy_pass http://unicorn;
    }

    # 略

このあと docker-compose の設定を見直して app サービスを nginx サービスに volumes_from するので、nginx サービスから app サービスの /app/tmp/sockets/unicorn.sock というパスにおくソケットにアクセスできるようになります。

また、メインの nginx.conf については特に目立った変更をしていませんが、一応ホストから編集できるように、Dockerfile で ADD するようにしています。

リバースプロキシを含めたアプリケーションシステムの起動

あとは、Nginx のサービス設定を加えた docker-compose を用意し、docker-compose 経由で全コンテナを起動することでアプリケーションが立ち上げることができます。最終的な docker-compose.yml はリポジトリからご覧ください。

docker-compose up

今回の設定では、http://localhost でブラウザからアクセスすることができます。アクセスして Nginx でリバースプロキシする以前と同様の画面が表示されたら成功したと言っていいでしょう。

おわりに

すこし説明が煩雑になってしまったかもしれません。不具合、ご指摘などありましたら、コメントいただければ幸いです。

Discussion