LoginSignup
0

More than 1 year has passed since last update.

【Docker】Rails6/MySQLのコンテナを作って開発環境を構築

Last updated at Posted at 2021-08-22

背景

これまでローカル/Railsサーバーの開発環境で開発を進めていたが、本番環境(EC2)との差分により環境依存が発生。そこで、Dockerを用いてより本番環境に近い開発環境を構築することで環境依存に取られる時間を削減しようと考え実装しました。

開発環境

Ruby 2.7.2
Rails 6.1
MySQL 5.7
Docker for Mac
Nginx
Puma
Supervisor

docker開発環境

ターミナル
$ mkdir docker/dev

まず、アプリのroot配下にdockerフォルダ、その配下にdevフォルダを作ります。

このdev配下にdocker関連のファイルを作成していきます。

ディレクトリ構造
root
├ docker
     └ dev
        ├ app
           ├ nginx
               ├ dev.ドメイン名.conf
               └ nginx.conf
           ├ supervisor
               └ app.conf
           └ Dockerfile

        ├ mysql
           ├ Dockerfile
           └ mysql.cnf

        └ docker-compose.yml

├ config ─ dev_puma.rb
└ .env

最終的には上記のようなディレクトリ構造になります。
(アプリ自体のディレクトリは省略)

appコンテナ

Nginx

app/nginx/dev.ドメイン名.conf
upstream puma {
     server unix:/root/tmp/puma.sock;
}
server {
  listen 80;
  server_name dev.ドメイン名;
  access_log /var/log/nginx/dev.access.log;
  error_log  /var/log/nginx/dev.error.log;
  root /var/www/app/public;

location / {
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Client-IP $remote_addr;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_connect_timeout 60;
        proxy_read_timeout    60;
        proxy_send_timeout    60;
        send_timeout          60;

        proxy_pass http://puma;
 }
  client_max_body_size 100m;
  error_page 404             /404.html;
  error_page 505 502 503 504 /500.html;
  try_files  $uri/index.html $uri @app;
  keepalive_timeout 5;
}
app/nginx/nginx.conf
user  root;
worker_processes  auto;
error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;
events {
    worker_connections  1024;
}
http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;
log_format main '[nginx]\t'
                  'time:$time_iso8601\t'
                  'server_addr:$server_addr\t'
                  'host:$remote_addr\t'
                  'method:$request_method\t'
                  'reqsize:$request_length\t'
                  'uri:$request_uri\t'
                  'query:$query_string\t'
                  'status:$status\t'
                  'size:$body_bytes_sent\t'
                  'referer:$http_referer\t'
                  'ua:$http_user_agent\t'
                  'forwardedfor:$http_x_forwarded_for\t'
                  'reqtime:$request_time\t'
                  'apptime:$upstream_response_time\t';
    access_log  /var/log/nginx/access.log  main;
    sendfile        on;
    tcp_nopush     on;
    keepalive_timeout  65;
    gzip  on;
    include /etc/nginx/conf.d/*.conf;
}

Nginxのファイルです。
内容については以下の記事をご覧ください。

参考:Nginx設定ファイル

logについてはわかりやすいように「dev.」を頭につけたファイルにしています。
開発環境ではソケットファイルをroot配下に置いています。マウントすると権限等による不具合発生とMacはファイルシステムがLinuxと比較すると違うため、遅延を防ぐためです。

Supervisor

app/supervisor/app.conf
[supervisord]
nodaemon=true
[program:app]
command=bundle exec puma -C config/dev_puma.rb -e development
autorestart=true
stopsignal=TERM
user=root
directory=/var/www/app/
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0

[program:nginx]
command=/usr/sbin/nginx -g "daemon off;"
autostart=true
autorestart=true
stopsignal=TERM
user=root
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0

Supervisorはプロセス管理、Daemon化、永続化を役割としています。
SupervisorはDockerでのプロセス管理する上でベストプラクティスとしてDocker社の公式で紹介されています。今回はpumaやNginxの起動を担当。
したがって、コンテナ内でpumaやNginxを再起動したい場合重宝するので必須になるかと思います。

Dockerfile/appコンテナ

app/Dockerfile
FROM ruby:2.7.2
ENV APP_ROOT /var/www/app
WORKDIR $APP_ROOT

RUN mkdir -p /root/tmp

RUN curl https://deb.nodesource.com/setup_12.x | bash
RUN curl https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list

RUN apt-get update -y && \
    apt-get upgrade -y && \
    apt-get install -y --no-install-recommends \
    build-essential \
    libpq-dev \
    locales \
    vim \
    nginx \
    supervisor \
    nodejs \
    yarn \
    mariadb-client && \
    rm -rf /var/cache/apt/archives/* /var/lib/apt/lists/*

# setting locales
RUN localedef -f UTF-8 -i en_US en_US.UTF-8

# Setup UTC+9
RUN cp -p /etc/localtime /etc/localtime.UTC \
    && cp -p /usr/share/zoneinfo/Japan /etc/localtime

RUN gem install bundler 

# yarn install Preparation
RUN npm install n -g
RUN n 12.18.4

## nginx
RUN groupadd nginx
RUN useradd -g nginx nginx
ADD nginx/nginx.conf /etc/nginx/nginx.conf
ADD nginx/dev.ドメイン名.conf /etc/nginx/conf.d/dev.ドメイン名.conf

## supervisor
RUN mkdir -p /var/log/supervisor
ADD supervisor/app.conf /etc/supervisor/conf.d/app.conf

EXPOSE 80
CMD ["/usr/bin/supervisord"]
インストラクション 説明
FROM ベースとなるイメージ。今回はruby:2.7.2とmysql:5.7
ENV 環境変数。 ENV 変数名=値で定義。今回は=を省略した書き方。
WORKDIR この命令文以下の命令文はWORKDIRで指定したディレクトリで実行される。今回はENVで定義されたAPP_ROOT(=/var/www/app)
RUN 実行コマンド。RUN <コマンド>RUN <実行ファイル>の形で書く。
ADD ADDはファイルを追加する命令。ADD <追加元> <追加先>ADD ["<追加元>",... "<追加先>"]という形で書く。
EXPOSE コンテナ実行時にリッスンするポートやプロトコルをDockerに通知する命令。EXPOSE 80/TCPという形で記述。プロトコルは省略するとデフォルトでTCPになる。
CMD コンテナの初期設定の命令。CMD命令はDockerfileの中で1つだけ指定できる。仮に複数書いた場合は最後のものが採用される。今回はsupervisordを指定することで、管理対象のファイル一緒に実行している。

余談だが、Docker imageにはLayerという概念があり、Layerが増えるとimage(コンテナの容量)が大きくなり作業に影響を与えてしまう。Layerを作るコマンドはRUN, COPY, ADDなのでこれらを使う場合は命令文を&&で繋げたり、バックスラッシュで改行することでLayerの節約に繋がる。

参考:Dockerfileリファレンス

MySQLコンテナ

mysql/Dockerfile
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 mysql.cnf /etc/mysql/mysql.cnf
mysql/mysql.cnf
[mysqld]
character-set-server=utf8mb4
collation-server=utf8mb4_general_ci
[client]
default-character-set=utf8mb4

命令文は割愛するが、DockerfileについてはAPPコンテナと同じ要領で書く。

docker-compose.yml

docker/dev/docker-compose.yml
version: '2'
services:
  app:
    container_name: アプリ名-app
    build: ./app/
    image: アプリ名-app
    ports:
      - '80:80'
    volumes:
      - ~/git/github/アプリディレクトリ名:/var/www/app/
    tty: true
    depends_on:
      - db
  db:
    container_name: アプリ名-db
    build: ./mysql/
    image: アプリ名-db
    ports:
      - '3306:3306'
    environment:
      MYSQL_DATABASE: アプリ名_dev
      MYSQL_ROOT_PASSWORD: password
      TZ: "Asia/Tokyo"
    volumes:
      - ./mysql/db_data:/var/lib/mysql
設定 説明
version バージョンは2もしくは3を選択しましょう。詳しくは公式ドキュメントを確認。
services サービスの指定。ネストさせてサービス名を指定。今回はappとdb。
container_name コンテナ名(何でも良いです。わかりやすいのにしましょう)今回はapp側は「アプリ名-app」、db側は「アプリ名-db」としました。
build servicesをビルドしたりリビルドしたりする。
image イメージ名
ports ポートのマッピング設定。'ホスト側:コンテナ側'という形で指定し、コンテナ側のポートをホスト側のポートにマッピングする。
environment 環境変数
volumes *1
tty コンテナを起動し続ける
depends_on サービス間の依存関係を設定。今回はサービスdbに依存させている。

*1

volumes - volume(データの永続化領域)の定義
volume とは、コンテナのライフサイクルが終了した後でもデータを保管しておけるデータ領域です。特徴は以下の通りです。
データの永続化を目的とした機能のため、コンテナが削除されても volume が明示的に破棄されない限り、volume 中のデータは保持される。
volume は、特定のコンテナ専用の volume だけでなく、複数のコンテナ間から参照できる volume も作成できる。
ホスト側のディレクトリを volume としてコンテナ内にマウントできる。
本機能はホストとコンテナ間でファイルを受け渡すときに利用できる。
引用:さわって理解する Docker 入門

volumes:
- ./mysql/db_data:/var/lib/mysql

こちらはホスト側./mysql/db_dataにデータを保存しています。起動した後にローカルのdocker/deb/mysql配下を見るとわかると思いますが、docker-compose downしてもMySQLコンテナのデータは削除されません。なのでデータ全体を削除したい場合は上記のディレクトリを削除する必要があります。
ちなみにgitignoreに上記のディレクトリも追加しないとgitで管理されてしまうので必ずgitignoreに記載してください。

その他

Puma設定ファイル

config/dev_puma.rb
max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count }
threads min_threads_count, max_threads_count

worker_timeout 3600 if ENV.fetch("RAILS_ENV", "development") == "development"

bind "unix:///root/tmp/puma.sock"

environment ENV.fetch("RAILS_ENV") { "development" }

pidfile ENV.fetch("PIDFILE") { "/root/tmp/server.pid" }

plugin :tmp_restart

ここは本番環境と形は同じです。各コードが何を実行しているのか、確認したい場合は以下の記事に記載していますのでご確認ください。
参考: puma設定ファイル

先程Nginx設定ファイルでも説明しましたが、開発環境では権限等による不具合の発生、Macのファイルシステムによる遅延の防止の為、ソケットファイルやpidファイルをroot配下に置いています。

環境変数

.env.example
GOOGLE_MAP_API_KEY=XXXXXXXXXXXXXXX
AMAZON_S3_ACCESS_KEY_ID=XXXXXXXXXXXXXXX
AMAZON_S3_SECRET_ACCESS_KEY=XXXXXXXXXXXXXXX
AMAZON_S3_BUCKET=XXXXXXXXXXXXXXX
DATABASE_HOST=db
DATABASE_NAME=アプリ名_dev
DATABASE_USER=root
DATABASE_PASSWORD=password
DATABASE_SOCKET=/tmp/mysql.sock

私のポートフォリオではGoogleMapsAPIやS3を使っていたため、環境変数に入れています。ご自身のポートフォリオで使っているAPIキーは入れておきましょう。

今回、使っているのがMySQLなので必要な情報を入れています。
HOST名をdbにしておりますが、ここには本来MySQLコンテナのIPアドレスが指定されます。dbとしているのは、docker-compose.ymlでサービス名をdbとしているため、名前解決されMySQLコンテナのIPアドレスを参照して実行できます。

Docker起動

それでは、dockerを起動して開発環境を構築しましょう。

  • git clone (もしまだやっていなかったら)
$ mkdir -p ~/git/github
$ cd ~/git/github
$ git clone リモートリポジトリ
  • add localhost /etc/hosts
$ sudo vi /etc/hosts

以下に修正
127.0.0.1 dev.ドメイン名
  • 環境変数(もしくは自分で環境変数作ってもOKです。)
$ cd ~/git/github/Tamari-Ba
$ cp .env.sample .env
  • docker run
$ cd docker/dev
$ docker-compose up -d
  • app deploy
$ docker exec -it appコンテナ名 bash

$ yarn install
$ bundle install
$ rails db:migrate
$ /usr/bin/supervisorctl restart app
  • Access

http://dev.ドメイン名/にアクセスしてブラウザにアプリ画面が表示されていれば成功です!
あとは開発できるか一通り確認しておきましょう。

  • DB login

ローカルと同じようにmysqlにログインすることも可能です。

docker exec -it appコンテナ名 bash
mysql -u root -h db -p

注意点

  • docker rmdocker rmiでコンテナ/イメージを削除してもpidは残ります。なので、新たにコンテナを立ち上げたときにmigrate等を失敗すると思います。これは、pidファイルを削除するか、個人ポートフォリオレベルであれば一度レポジトリごと削除してしまえば解消可能です。

  • 以下のエラー、もしどこを直しても解消できない場合は右上のdockerアイコンを確認して、起動しているか確認してください。私は全然気が付かず再起動することで解消しましたw

ERROR: for コンテナ名  UnixHTTPConnectionPool(host='localhost', port=None): Read timed out. (read timeout=60)

ERROR: for app  UnixHTTPConnectionPool(host='localhost', port=None): Read timed out. (read timeout=60)
ERROR: An HTTP request took too long to complete. Retry with --verbose to obtain debug information.
If you encounter this issue regularly because of slow network conditions, consider setting COMPOSE_HTTP_TIMEOUT to a higher value (current value: 60).

まとめ

  • Docker環境構築は本番環境とできるだけ環境を近づけることができ、「環境依存」を防げるメリットがある。
  • 扱うにはLinux, MySQL, Nginx等のサーバー周りの知識が必要。
  • Dockerの公式ドキュメントはわかりやすい

参考記事

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0