Docker + Rails + Puma + Nginx（SSL） + MySQL

[前回](http://qiita.com/eighty8/items/0288ab9c127ddb683315) 作った環境をSSL化したいと思ったので、調査がてらに構築してみました。
最近はかなり安くなったとはいえ、検証のためにお金を出してSSL証明書を発行するのも嫌だし、
オレオレ証明書もなんだかなぁ〜 と思ったので、最近話題の [Let's Encrypt（Certbot） ](https://letsencrypt.org/)
で証明書発行をしてSSL化したいと思います。

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

構築手順や各ツールのバージョンは [前回](http://qiita.com/eighty8/items/0288ab9c127ddb683315)  と変わりません。
なので、説明は追加した箇所以外は省略します。

## __前提__

- 作業はすべて [ConoHaVPS](https://www.conoha.jp) の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.15.8）
- Ruby（2.5.1）
- Ruby on Rails（5.2.0）
- 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.5.1

RUN apt-get update -qq && \
    apt-get install -y build-essential \
                       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
```

``` ruby:Gemfile
source 'https://rubygems.org'
gem 'rails', '5.2.0'
```

### Gemfile.lock

```
$ touch Gemfile.lock
```

### Nginx用Dockerfile

```
$ vim containers/nginx/Dockerfile
```

``` :Dockerfile
FROM nginx:1.15.8
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/example.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/example.com/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/database.yml
```

```
$ vim config/database.yml
```

``` :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
```

``` 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 exec app rails db:create
```

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

https://example.com
![スクリーンショット 2017-08-25 14.40.21.png](https://qiita-image-store.s3.amazonaws.com/0/35509/c501d2d1-2074-22f7-ad61-19fef46ec2a7.png)

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

##__6. scaffoldでアプリのベースを構築__

### scaffoldの実行

```
$ docker-compose exec app rails g scaffold User name:string email:string
```

### マイグレートの実行

```
$ docker-compose exec app rails db:migrate
```

### 確認

ブラウザを開いて https://example.com/users にアクセスしてみてください。
下記の通りに表示されればOKです。
![スクリーンショット 2017-08-25 14.47.32.png](https://qiita-image-store.s3.amazonaws.com/0/35509/9a4a7214-c075-e599-a329-e6a23ff3b66e.png)

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

![crud.png](https://qiita-image-store.s3.amazonaws.com/0/35509/9f647db8-a415-864a-074f-1481e0ddd517.png)

## __まとめ__

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

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

## __その他__

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

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