Docker Composeとは?Rails、Mysql、Nginxを構築して使い方を覚える

Docker Composeとは?Rails、Mysql、Nginxを構築して使い方を覚える

Dockerを使いこなすのであれば、「Docker Compose」を避けて通ることはできません。

Docker Composeを使えば、Dockerでの環境構築や管理がとっても楽になるからです。

ちなみに、前回解説した「Dockerネットワーク」も自分でわざわざ作る必要がなくなります。

今回は、Docker Composeの使い方を覚えるためにも、「Rails」「Mysql」「Nginx」のコンテナをそれぞれ立ち上げ、コンテナ間で通信できるようにしたいと思います。

「Rails」と「Nginx」をまとめて1つのコンテナにしている記事は多いのですが、別々に動かして連携させる記事は少なかったので、参考になると思います。

キツネ

かなり勉強になるから絶対に読んでね!

Docker Composeとは

Docker Composeとは、複数のDockerコンテナからなるアプリケーションを定義し、操作するためのツールです。

Dockerイメージの作成方法やコンテナ起動時の設定などを「docker-compose.yml」に記述することで、「docker compose」コマンドでまとめて操作できるようになります。

Docker Composeのメリット

  • コンテナ間の通信ができる(Dockerネットワークを作成しなくていい)
  • 実行コマンドが簡潔になる
  • ファイルで管理するため、インフラ構成の可視化、バージョン管理が可能

Docker Composeを使うことで、Dockerネットワークを自分で作らなくても、「ディレクトリ名_default」というネットワークを自動で作成してくれます。

また、「docker run」コマンドのやたら長いオプションも「docker-compose.yml」に記述できるようになり、実行コマンド自体が簡潔になります。

これにより、イメージを作成する「docker build」や、コンテナを起動する「docker run」などのコマンドを、コンテナの数だけ実行するなんてこともなくなるのです。

また、「docker-compose.yml」はファイルなので、コンテナ構成を可視化でき、バージョン管理もできるようになります。

キツネ

Docker Composeはいいこと尽くめだね!

Docker ComposeとDockerfileの違い

「Docker Compose」と「Dockerfile」は、似ているようで少し役割が違います。

  • 「Docker Compose」…Dockerイメージのビルドやコンテナの作成と起動方法、ネットワークを使った構成管理
  • 「Dockerfile」…Dockerイメージの作成手順

「docker-compose.yml」にはイメージのビルド方法やコンテナ起動方法などを書き、「Dockerfile」にはイメージの作成手順を書くようになっているのです。

つまり、今後は「docker-compose.yml」と「Dockerfile」を使って、コンテナ環境を構築していくことになります。

キツネ

「docker run」や「docker build」を使わなくてもいいんだ!

docker-compose.ymlの書き方

「docker-compose.yml」の書き方は、そこまで難しくありません。

「YAML(ヤムル)」と呼ばれる形式を使って、誰でも簡単に記述できるようになっているからです。

基本的には、1つの項目に対して値を設定する「key:value」の関係になっています。プログラミングの連想配列(ハッシュ)とほとんど同じですね。

以下は、今回使用する「docker-compose.yml」になります。

docker-compose.yml
version: '2'
services:
  app:
    build: rails
    environment:
      APP_DATABASE_HOST: db
      APP_DATABASE: app_development
      APP_DATABASE_USER: root
      APP_DATABASE_PASSWORD: root
    depends_on:
      - db
  db:
    build: mysql
    environment:
      MYSQL_ROOT_PASSWORD: root
  web:
    build: nginx
    ports:
      - 80:80
    volumes:
      - ./nginx/log:/var/log/nginx
    volumes_from:
      - app
    depends_on:
      - app

version

「version」では、Docker Composeで使用するバージョンを指定します。

現在は「3」までありますが、ここでは「2」を指定しています。

「3」では、「volumes_from」が使えないためです。

services

「services」では、アプリケーションを構成するためのサービスを設定します。

ここでは、「app」「db」「web」という3つの構成になっています。

これらのサービスごとにビルドされ、各設定に従ってコンテナが作成、起動する仕組みになっています。

build

「build」では、Dockerfileの場所を指定します。

ここでは、以下のような構成を想定しています。

Dockerfileのディレクトリ構成
docker
├── docker-compose.yml
├── rails
│   └── Dockerfile
├── mysql
│   └── Dockerfile
└── nginx
    └── Dockerfile

environment

「environment」では、コンテナ内で利用する環境変数を設定します。

ここでは、データベースの定義に関する情報をあらかじめ設定しています。

depends_on

「depends_on」では、サービス同士の依存関係を設定します。

この設定により、コンテナの起動タイミングが変わります。

ここでは、「db」サービスが起動したあとに「app」サービスが起動するようになっています。

ports

「ports」では、ホストマシンとゲストマシンのポートフォワードを設定します。

これは「docker run -p」と同様の設定です。

volumes

「volumes」では、マウントする(利用できるように認識させる)ための設定をします。

ここでは、ホストマシンの指定されたディレクトリに、コンテナ内のNginxのログファイルをマウントしています。

キツネ

コンテナを停止してもホストマシンからログを確認できるね!

volumes_from

「volumes_from」では、コンテナ間でマウントするための設定です。

ここでは、「app」サービスのコンテナを「web」サービスのコンテナにマウントするよう設定しています。

Docker Composeの使い方(Rails、Mysql、Nginxの環境構築)

さきほど紹介した「docker-compose.yml」を使って、Rails、Mysql、Nginxのコンテナを起動し、コンテナ間で通信してみます。

ここでは、以下のようなディレクトリ構成を想定して進めていきます。

ディレクトリ構成
docker
├── docker-compose.yml
├── rails
│   ├── Dockerfile
│   ├── Gemfile
│   ├── Gemfile.lock
│   └── config
│       ├── database.yml
│       └── puma.rb
├── mysql
│   ├── Dockerfile
│   └── app.cnf
└── nginx
    ├── Dockerfile
    └── nginx.conf

作業の流れはこんな感じです。

  1. 各サービスのビルド準備
  2. 各サービスのビルド
  3. 各サービスからコンテナを起動
  4. Rails用のデータベース作成

Mysqlビルドの準備

Mysqlのビルドに必要な「Dockerfile」と「app.cnf」を準備します。

ディレクトリ構成
docker
└── mysql
    ├── Dockerfile
    └── app.cnf

ここではMysql8.0の認証方式を変更しています。詳しくはコメントを読んでください。

Dockerfile
# Mysqlをインストール
FROM mysql:8.0

# 「Authentication plugin 'caching_sha2_password' cannot be loaded:」とエラーが発生するため
# Mysqlの認証方式を「caching_sha2_password」から「mysql_native_password」に変更
COPY app.cnf /etc/mysql/conf.d/app.cnf
app.cnf
[mysqld]
default_authentication_plugin= mysql_native_password

Railsビルドの準備

Railsのビルドに必要な「Dockerfile」や「Gemfile」、「database.yml」などの設定ファイルを準備します。

ディレクトリ構成
docker
└── rails
    ├── Dockerfile
    ├── Gemfile
    ├── Gemfile.lock
    └── config
        ├── database.yml
        └── puma.rb

「Dockerfile」は、コメントを読んでもらえれば内容を理解できると思います。

Dockerfile
# Rubyをインストール
FROM ruby:2.6

# 必要なパッケージをインストール
RUN curl -sL https://deb.nodesource.com/setup_10.x | bash - \
    && apt-get install -y nodejs

# 公式では以下のコマンドを推奨しているが、nodeのバージョンが低くBootstrapが使えない
# RUN apt-get update -qq && apt-get install -y nodejs

# 環境変数を設定
ENV APP_HOME /app

# ディレクトリの作成と移動
WORKDIR $APP_HOME

# ホストのGemfileなどをコンテナへコピー
COPY Gemfile $APP_HOME/Gemfile
COPY Gemfile.lock $APP_HOME/Gemfile.lock

# BundlerでGemをインストール
RUN bundle install

# Railsアプリを作成(既存のアプリをマウントする場合は不要)
RUN rails new . --database=mysql

# 設定ファイル書き換え(既存のアプリをマウントする場合は不要)
COPY ./config $APP_HOME/config

# マウントできるように公開
VOLUME /app/public
VOLUME /app/tmp

# コンテナ起動時にRailsサーバを起動
CMD ["rails", "server"]

「Gemfile」はRailsのインストール用に作成しています。

このほかに「Gemfile.lock」も空ファイルで作っておいてください。

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

RailsからMysqlにアクセスするために、「database.yml」を変更してデータベースの接続設定をおこないます。

ポイントとしては、「docker-compose.yml」で設定した環境変数を指定しているところです。

config/database.yml
default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch('RAILS_MAX_THREADS') { 5 } %>
  username: <%= ENV.fetch('APP_DATABASE_USER') %>
  password: <%= ENV.fetch('APP_DATABASE_PASSWORD') %>
  host: <%= ENV.fetch('APP_DATABASE_HOST') %>

development:
  <<: *default
  database: <%= ENV.fetch('APP_DATABASE') %>

test:
  <<: *default
  database: app_test

production:
  <<: *default
  database: app_production
  username: app
  password: <%= ENV.fetch('APP_DATABASE_PASSWORD') %>

次に、NginxからRailsへアクセスできるようにPumaの設定を変更しておきます。

デフォルトの設定に「ソケット通信」の設定を追加した「puma.rb」が以下になります。

ソケット通信がわからない方は「【Nginxの設定】Rails(Puma)とソケット通信で連携させる!」を読んでみてください。

config/puma.rb
# デフォルトの設定
threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
threads threads_count, threads_count
port        ENV.fetch("PORT") { 3000 }
environment ENV.fetch("RAILS_ENV") { "development" }
plugin :tmp_restart

# ソケット通信用に追加
bind "unix://#{Rails.root}/tmp/sockets/puma.sock"

Nginxビルドの準備

Nginxのビルドに必要な「Dockerfile」と「nginx.conf」を準備します。

ディレクトリ構成
docker
└── nginx
    ├── Dockerfile
    └── nginx.conf

ここではNginxイメージを取得し、「nginx.conf」をコンテナ内の「/etc/nginx/conf.d/app.conf」にコピーしています。

Dockerfile
FROM nginx
COPY nginx.conf /etc/nginx/conf.d/app.conf

Nginxの設定ファイルである「nginx.conf」を作成します。

以下は、Railsに接続するために必要なソケット通信の設定になります。

nginx.conf
upstream app {
    # UNIXドメインソケット通信の設定
    server unix:///app/tmp/sockets/puma.sock fail_timeout=0;
}

server {
    # 80番ポートを許可
    listen 80;

    # host名を指定
    server_name localhost;

    # 静的ファイル(画像など)のパスをドキュメントルートに設定
    root /app/public;

    # ドキュメントルート配下を以下の先頭から順番に辿る
    try_files $uri/index.html $uri @app;

    # 上記の@training_appが呼び出された場合のみ以下の設定を読み込む
    location @app {
        proxy_pass http://app;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;
    }
}

キツネ

これですべての準備は完了したよ!

Docker Composeでビルド

Docker Composeでビルドする準備がすべて整いました。

あとは、「docker-compose build」コマンドを実行するだけです。

Dockerビルド
# 「docker-compose.yml」のあるディレクトリまで移動
$ cd /path/to/docker

# Dockerビルド(各イメージを作成)
$ docker-compose build
Building db
Step 1/2 : FROM mysql:8.0
 ---> c7109f74d339
    :
Successfully tagged docker_db:latest
Building app
Step 1/12 : FROM ruby:2.6
 ---> 877a53569182
    :
Successfully tagged docker_app:latest
Building web
Step 1/2 : FROM nginx
 ---> 719cd2e3ed04
    :
Successfully tagged docker_web:latest

# Dockerイメージを確認
$ docker images;
REPOSITORY          TAG                 IMAGE ID            CREATED              SIZE
docker_web          latest              e26fadcbb86b        About a minute ago   109MB
docker_app          latest              509e4edafc01        About a minute ago   1.03GB
docker_db           latest              cd3ebc2ad8ba        3 minutes ago        443MB

キツネ

あっという間にビルドが終わっちゃうね!

Docker Composeでコンテナの作成と起動

ビルドが終わったら、各イメージからコンテナを作成して起動します。

といっても、「docker-compose up -d」を実行するだけで、すてべのコンテナが起動するので簡単です。

「-d」は、バックグラウンドでコンテナを起動させるオプションです。

Dockerコンテナ起動
# 各イメージからコンテナを作成、起動
$ docker-compose up -d
Creating docker_db_1 ... done
Creating docker_app_1 ... done
Creating docker_web_1 ... done

# 起動中のコンテナを確認
$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                 NAMES
f5031eea46a2        docker_web          "nginx -g 'daemon of…"   20 seconds ago      Up 17 seconds       0.0.0.0:80->80/tcp    docker_web_1
091fd51ab7c3        docker_app          "rails server"           20 seconds ago      Up 18 seconds                             docker_app_1
48cf4e0ca349        docker_db           "docker-entrypoint.s…"   21 seconds ago      Up 19 seconds       3306/tcp, 33060/tcp   docker_db_1

キツネ

簡単にすべてのコンテナが立ち上がったね!

Docker Composeでコマンドを実行

Docker Composeを使って、特定のコンテナに対してコマンドを実行することもできます。

ここでは、appサービスを指定して「rails db:create」コマンドを実行しています。

これにより、MysqlにRails用のデータが作成され、ブラウザからアクセスするとRailsの初期画面が表示されるようになります。

Rails用データ作成
# 「docker run --rm」と同様
$ docker-compose run --rm app rails db:create
Starting docker_db_1 ... done
Created database 'app_development'
Created database 'app_test'

# 「docker exec」と同様
# 2回目の「rails db:create」になるので実行しなくてもいいです(パターン紹介)
$ docker-compose exec app rails db:create
Database 'app_development' already exists
Database 'app_test' already exists

Railsの初期画面

キツネ

長かったけど、お疲れ様でした!

「docker-compose」コマンド一覧

最後に、よく使いそうな「docker-compose」コマンドを紹介して終わりにします。

これらのコマンドは「docker-compose.yml」に記述されているサービスに対してのみ有効です。

docker-compose build

「docker-compose build」コマンドは、サービス内のイメージに対してビルドをおこないます。

「--no-cache」オプションを使うと、キャッシュせずにビルドすることもできます。

キツネ

キャッシュの影響でDockerfileの変更が反映されないこともあるんだ!
サービスのビルド
# サービスをビルド
$ docker-compose build
Building db
Step 1/2 : FROM mysql:8.0
 ---> c7109f74d339
    :

# サービスをビルド(キャッシュを使わない)
$ docker-compose build --no-cache
Building db
Step 1/2 : FROM mysql:8.0
 ---> c7109f74d339
    :

# サービスを指定してビルド
$ docker-compose build app
Building app
Step 1/12 : FROM ruby:2.6
 ---> 877a53569182
    :

docker-compose up

「docker-compose up」コマンドは、サービス内のイメージからコンテナを作成して起動します。

すでにコンテナが作成されている場合は、イメージを読み込み直してコンテナを再起動させます。

また、「-d」オプションをつけることで、バックグラウンドでコンテナを起動させることも可能です。

特定のサービスだけを起動させたい場合は、引数で指定することができますが、「docker-compose.yml」で指定した依存関係(depends_on)にあるサービスも一緒に起動することに注意しましょう。

コンテナの起動
# サービスからコンテナを作成し、起動
$ docker-compose up -d
Creating docker_db_1 ... done
Creating docker_app_1 ... done
Creating docker_web_1 ... done

# サービスからコンテナを再起動(イメージに変更があった場合の表示)
$ docker-compose up -d
docker_db_1 is up-to-date
Recreating docker_app_1 ... done
Recreating docker_web_1 ... done

# 指定したサービスを起動(コンテナが停止していた場合の表示)
$ docker-compose up -d app
Starting docker_db_1 ... done
Starting docker_app_1 ... done

docker-compose logs

「docker-compose logs」コマンドは、コンテナの起動ログを出力します。

たとえば、「docker-compose up」でコンテナが起動しない原因を調査する際に便利です。

「-f」オプションをつけることで、リアルタイムに監視することもできます。

コンテナ起動ログを確認
# サービスの起動ログを出力
$ docker-compose logs
Attaching to docker_web_1, docker_app_1, docker_db_1
web_1  | nginx: [warn] conflicting server name "localhost" on 0.0.0.0:80, ignored
db_1   | Initializing database
    :

# 指定したサービスの起動ログを出力
$ docker-compose logs app
Attaching to docker_app_1
app_1  | => Booting Puma
app_1  | => Rails 5.2.3 application starting in development

docker-compose run

「docker-compose run」コマンドは、指定したサービスから新たなコンテナを作成し、コマンドを実行します。

そのため、このコマンドを実行した回数だけコンテナが増えていきます。

コンテナを増やしたくない場合は、「--rm」オプションをつけましょう。

コマンドの実行
$ docker-compose run --rm app rails db:create
Starting docker_db_1 ... done
Created database 'app_development'
Created database 'app_test'

docker-compose exe

「docker-compose build」コマンドは、指定したサービスのコンテナに対してコマンドを実行します。

「docker-compose run」とは違い、新しくコンテナを作成しないため、既存コンテナに対してのコマンド実行であれば、こちらで十分です。

コマンドの実行
# コンテナにログイン
$ docker-compose exec app bash

# Railsコマンドを実行
$ docker-compose exec app rails db:create
Database 'app_development' already exists
Database 'app_test' already exists

docker-compose start

「docker-compose start」コマンドは、サービス内のコンテナを起動します。

あくまで作成済みのコンテナを起動するだけなので、イメージを読み込み直しません。

コンテナの起動
# サービスのコンテナを停止
$ docker-compose start
Starting db  ... done
Starting app ... done
Starting web ... done

# 指定したサービスのコンテナを停止
$ docker-compose start app
Starting app ... done

docker-compose stop

「docker-compose stop」コマンドは、サービス内のコンテナを停止します。

コンテナの停止
# サービスのコンテナを停止
$ docker-compose stop
Stopping docker_web_1 ... done
Stopping docker_app_1 ... done
Stopping docker_db_1  ... done

# 指定したサービスのコンテナを停止
docker-compose stop app
Stopping docker_app_1 ... done

docker-compose restart

「docker-compose restart」コマンドは、サービス内のコンテナを再起動します。

コンテナ内のサーバを再起動させて、設定ファイルを読み込ませたい場合などに使えます。

コンテナの再起動
# サービスのコンテナを再起動
$ docker-compose restart
Restarting docker_web_1 ... done
Restarting docker_app_1 ... done
Restarting docker_db_1  ... done

# 指定したサービスのコンテナを再起動
$ docker-compose restart app
Restarting docker_app_1 ... done

docker-compose rm

「docker-compose rm」コマンドは、サービス内のコンテナを削除します。

コンテナの削除
# サービスのコンテナを削除
$ docker-compose rm
Going to remove docker_web_1, docker_app_1, docker_db_1
Are you sure? [yN] y
Removing docker_web_1 ... done
Removing docker_app_1 ... done
Removing docker_db_1  ... done

# 指定したサービスのコンテナを削除
$ docker-compose rm app
Going to remove docker_app_1
Are you sure? [yN] y
Removing docker_app_1 ... done

docker-compose down

「docker-compose down」コマンドは、サービス内のコンテナを停止し、ネット―ワークごとコンテナを削除します。

「--rmi all」オプションを指定することで、サービスのイメージも削除できます。

コンテナの停止と削除
# コンテナを停止し、ネットワークとコンテナを削除
$ docker-compose down
Stopping docker_web_1 ... done
Stopping docker_app_1 ... done
Stopping docker_db_1  ... done
Removing docker_web_1 ... done
Removing docker_app_1 ... done
Removing docker_db_1  ... done
Removing network docker_default

# 上記に加え、イメージも削除
$ docker-compose down --rmi all
Stopping docker_web_1 ... done
Stopping docker_app_1 ... done
Stopping docker_db_1  ... done
Removing docker_web_1 ... done
Removing docker_app_1 ... done
Removing docker_db_1  ... done
Removing network docker_default
Removing image docker_db
Removing image docker_app
Removing image docker_web

あわせて覚えたいDockerコマンド

「docker-compose」コマンドではありませんが、一緒に覚えておくと便利なDockerコマンドを紹介します。

docker image prune

「docker image prune」コマンドは、「dangling image」をすべて削除します。

「dangling image」とは、何かしらの理由で残ってしまったゴミイメージのことです。

「-a」オプションを使うことで、コンテナとして利用されていないイメージすべてを削除することもできます。

イメージをまとめて削除
# 不要なイメージを削除
$ docker image prune
WARNING! This will remove all dangling images.
Are you sure you want to continue? [y/N] y
Total reclaimed space: 0B

# 使用していないイメージを削除
 docker image prune -a
WARNING! This will remove all images without at least one container associated to them.
Are you sure you want to continue? [y/N] y
Deleted Images:
untagged: nginx:latest
     :

Total reclaimed space: 572MB

docker system prune

「docker system prune」コマンドは、停止中のコンテナ、使っていないイメージやネットワークをまとめて削除します。

不要なイメージやコンテナが溜まっている場合に便利なコマンドです。

停止中のコンテナ、イメージ、ネットワークなどをまとめて削除
$ docker system prune
WARNING! This will remove:
        - all stopped containers
        - all networks not used by at least one container
        - all dangling images
        - all build cache
Are you sure you want to continue? [y/N] y
Deleted Containers:
216f34ddf82f11f16c8829eba1e790cd3a482ed485eeaf94c70a9a4e674bda2e
     :

Deleted Networks:
test-network

Deleted Images:
deleted: sha256:67707f7bc64678b887e90b380d38fac3a90f862aa9c5622c842d4e26ab289250
     :

Total reclaimed space: 151.3MB

キツネ

本当に色々消えちゃうから注意して使おう!

まとめ

今回は、DockerでRailsの初期画面を表示させるための環境構築方法を紹介しました。

ここまで理解できていれば、Dockerについての基本的な操作は問題ないと思います。

ただし、環境構築ってインフラエンジニアでなければ頻繁に触らないので、時間が経つと忘れてしまうんですよね。

業務のなかで環境構築をするタイミングがあれば、積極的にDockerを使って触れる時間を増やすようにしましょう。

また、今回の設定をもっと実用的にするためには、Railsの初期画面ではなく、Gitなどで管理されているRailsアプリを反映させるように変更しなければなりません。

これまでに学んだことを思い出し、自分の力で調べながらやってみてください。(もしかしたら記事を書くかもしれませんが)

テキストのコピーはできません。