Rails
MySQL
nginx
puma
docker

Docker + Rails + Puma + Nginx(SSL) + MySQL

More than 1 year has passed since last update.

前回 作った環境をSSL化したいと思ったので、調査がてらに構築してみました。
最近はかなり安くなったとはいえ、検証のためにお金を出してSSL証明書を発行するのも嫌だし、
オレオレ証明書もなんだかなぁ〜 と思ったので、最近話題の Let's Encrypt(Certbot)
で証明書発行をしてSSL化したいと思います。

Let's Encrypt とは

Let's Encrypt 総合ポータル
Let's Encrypt は、クライアントソフトウェア「Certbot」を使用することで、SSL/TLS サーバ証明書の取得・更新作業を自動化できる仕組みになっています。
独自ドメインがあれば、簡単なコマンド操作で SSL/TLS 証明書(無料)を取得できます。

構築手順や各ツールのバージョンは 前回 と変わりません。
なので、説明は追加した箇所以外は省略します。

前提

  • 作業はすべて ConoHaVPS のUbuntu16.04上でやってます。
  • OS上にすでに Docker と docker-compose がインストール済みという前提で話を進めます。
  • この環境では フロント に Nginx を配置して、バックエンドの Puma にプロキシしています。

バージョン

  • ホストOS(Ubuntu16.04 Xenial Xerus)
  • Docker(17.06.1-ce)
  • docker-compose(1.11.2)
  • Nginx(1.13.3)
  • Ruby(2.4.1)
  • Ruby on Rails(5.1.1)
  • MySQL(5.7)

ディレクトリ構成

/var/webapp
├── containers
│   └── nginx
│       ├── Dockerfile
│       └── nginx.conf
├── docker-compose.yml
├── Dockerfile
├── environments
│   └── db.env
├── Gemfile
└── Gemfile.lock

1. 各ディレクトリの作成

アプリケーションルート

$ sudo mkdir -p /var/webapp
$ sudo chown -R $USER:$USER /var/webapp

Nginxコンテナディレクトリ

$ mkdir -p /var/webapp/containers/nginx

環境変数用ディレクトリ

$ mkdir /var/webapp/environments

2. コンテナ生成のための各ファイルを作成

※ 以降はアプリケーションルート内で行う

Rails用Dockerfile

$ vim Dockerfile
Dockerfile
FROM ruby:2.4.1

RUN apt-get update -qq && \
    apt-get install -y build-essential \
                       libmysqlclient-dev \
                       nodejs

RUN mkdir /webapp
WORKDIR /webapp

ADD Gemfile /webapp/Gemfile
ADD Gemfile.lock /webapp/Gemfile.lock

RUN bundle install

ADD . /webapp

RUN mkdir -p tmp/sockets

Gemfile

$ vim Gemfile
Gemfile
source 'https://rubygems.org'
gem 'rails', '5.1.1'

Gemfile.lock

$ touch Gemfile.lock

Nginx用Dockerfile

$ vim containers/nginx/Dockerfile
Dockerfile
FROM nginx:1.13.3
RUN rm -f /etc/nginx/conf.d/*
ADD nginx.conf /etc/nginx/conf.d/webapp.conf
CMD /usr/sbin/nginx -g 'daemon off;' -c /etc/nginx/nginx.conf

Nginx設定ファイル

$ vim containers/nginx/nginx.conf
nginx.conf
upstream webapp {
  server unix:///webapp/tmp/sockets/puma.sock;
}

# httpでのアクセスはhttpsにリダイレクトさせる
server {
  listen 80;
  server_name  _;
  return 301 https://$host$request_uri;
}

server {
  # ポート443をリスン
  listen 443 ssl;
  server_name example.com;

  # SSL証明書の場所を指定
  ssl_certificate     /etc/letsencrypt/live/label8.net/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/label8.net/privkey.pem;
  ssl_protocols       TLSv1 TLSv1.1 TLSv1.2;
  ssl_ciphers         HIGH:!aNULL:!MD5;

  access_log /var/log/nginx/access.log;
  error_log  /var/log/nginx/error.log;

  root /webapp/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 @webapp;
  keepalive_timeout 5;

  # Certbotが指定したドメインにアクセス可能かチェックするためのディレクティブ
  location ^~ /.well-known/ {
    root   /usr/share/nginx/html;
    allow all;
  }

  location @webapp {
    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://webapp;
  }
}

DB接続用の情報ファイル

$ vim environments/db.env
db.env
MYSQL_ROOT_PASSWORD=db_root_password
MYSQL_USER=user_name
MYSQL_PASSWORD=user_password

docker-compose.yml

$ vim docker-compose.yml
docker-compose.yml
version: '3'
services:
  app:
    build:
      context: .
    env_file:
      - ./environments/db.env
    command: bundle exec puma -C config/puma.rb
    volumes:
      - .:/webapp
      - public-data:/webapp/public
      - tmp-data:/webapp/tmp
      - log-data:/webapp/log
    depends_on:
      - db
  db:
    image: mysql:5.7
    env_file:
      - ./environments/db.env
    volumes:
      - db-data:/var/lib/mysql
  web:
    build:
      context: containers/nginx
    volumes:
      - certs:/etc/letsencrypt          # 追加
      - certs-data:/var/lib/letsencrypt # 追加
      - public-data:/webapp/public
      - tmp-data:/webapp/tmp
    ports:
      - 80:80
      - 443:443
    depends_on:
      - app
volumes:
  certs:       # 追加
  certs-data:  # 追加
  public-data:
  tmp-data:
  log-data:
  db-data:

Nginxコンテナから証明書を参照できるように Volume を追加します。

3. Certbotコンテナを作成&起動しSSL証明書を作成する

$ docker run --rm \
  -p 443:443 -p 80:80 --name letsencrypt \
  -v "webapp_certs:/etc/letsencrypt" \
  -v "webapp_certs-data:/var/lib/letsencrypt" \
  certbot/certbot certonly -n \
  -m "test@example.com" \
  -d example.com \
  --standalone --agree-tos

このコンテナは一時的にSSL証明書を発行するためだけに作成するので、
オプション --rm でコンテナ実行後は削除しておきます。

docker-compose.yml の名前付きボリュームに指定したキーは、
ビルドすると アプリケーションルート名_ボリューム名 となるため、
Certbotコンテナのボリューム名も同様の名前にしておきます。

  • certs -> webapp_certs
  • certs-data -> webapp_certs-data

4. Railsプリケーションの生成と編集

Railsの生成

$ docker-compose run --rm app rails new . --force --database=mysql --skip-bundle

権限の変更

$ sudo chown -R $USER:$USER .

puma.rbの編集

$ cp /dev/null config/puma.rb
$ vim config/puma.rb
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

database.ymlの編集

$ cp /dev/null config/puma.rb
$ vim config/puma.rb
database.yml
default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: <%= ENV.fetch('MYSQL_USER') { 'root' } %>
  password: <%= ENV.fetch('MYSQL_PASSWORD') { 'password' } %>
  host: db

development:
  <<: *default
  database: webapp_development

test:
  <<: *default
  database: webapp_test

5. イメージのビルドとコンテナの起動

イメージのビルド

$ docker-compose build
$ docker images
REPOSITORY          TAG                IMAGE ID            CREATED             SIZE
webapp_web          latest             6c3ae1c68ff0        18 hours ago        107MB
webapp_app          latest             fe241efeb525        18 hours ago        820MB
<none>              <none>             36b3d89bfec7        18 hours ago        781MB
ruby                2.4.1              15b96d1e91df        2 days ago          679MB
nginx               1.13.3             b8efb18f159b        4 weeks ago         107MB
mysql               5.7                c73c7527c03a        4 weeks ago         412MB

コンテナの起動

$ docker-compose up -d
$ docker-compose ps
   Name                  Command               State         Ports
-------------------------------------------------------------------------
webapp_app_1   bundle exec puma -C config ...   Up
webapp_db_1    docker-entrypoint.sh mysqld      Up      3306/tcp
webapp_web_1   /bin/sh -c /usr/sbin/nginx ...   Up      0.0.0.0:80->80/tcp

5. DB設定

権限の付与

$ vim db/grant_user.sql
grant_user.sql
GRANT ALL PRIVILEGES ON *.* TO 'user_name'@'%';
FLUSH PRIVILEGES;
$ docker-compose exec db mysql -u root -p -e"$(cat db/grant_user.sql)"
$ docker-compose exec db mysql -u user_name -p -e"show grants;"
+------------------------------------------------+
| Grants for user_name@%                         |
+------------------------------------------------+
| GRANT ALL PRIVILEGES ON *.* TO 'user_name'@'%' |
+------------------------------------------------+

DBの作成

$ docker-compose run --rm app rails db:create

ここまでで一旦環境構築は完了です。
ブラウザにアクセスしてRailsのWelcomeページが表示できるか確認しておきましょう。

https://example.com
スクリーンショット 2017-08-25 14.40.21.png

また、 http でアクセスした場合、ちゃんと https にリダイレクトされるかも確認しておきましょう。

6. scaffoldでアプリのベースを構築

scaffoldの実行

$ docker-compose run --rm app rails g scaffold User name:string email:string

マイグレートの実行

$ docker-compose run --rm app rails db:migrate

確認

ブラウザを開いて https://example.com/users にアクセスしてみてください。
下記の通りに表示されればOKです。
スクリーンショット 2017-08-25 14.47.32.png

CRUD操作が問題なく実行できるか試してみてください。

crud.png

まとめ

Certbot コマンドで証明書を吐き出すことさえできれば、あとはWebサーバー側でそれを読むだけなので、
かなり手軽にSSL化することができました。
前回 と比べても大きく変更した箇所は nginx.conf くらいかな?

昨今はWebサイトの常時SSL化が推奨され、iOSアプリでもHTTPS義務化(ATS)となっているため、
これを機に Let's Encrypt してみてはどうでしょうか?

その他

Let's Encrypt で発行した証明書の有効期限は3ヶ月です。
なので、3ヶ月に一回は証明書の更新を行なわないといけません。

その更新手順はまたあとで追記しようと思います。