Google Container Engine上のRailsアプリにnginxを挟む

# 背景

- [前回の記事](http://qiita.com/esplo/items/76a1ecaf09843c49cfaf)
- [前々回の記事](http://qiita.com/esplo/items/bd2e36cfae797d0d480a)

の続きです。
今回はnginx経由でRailsにアクセスするよう、構成を変更します。

なお、私の場合はhttps化の一環でnginxを挟む必要があったので行いました。

# 手順

[前回](http://qiita.com/esplo/items/76a1ecaf09843c49cfaf)、[前々回](http://qiita.com/esplo/items/bd2e36cfae797d0d480a)の手順に則り構築をしたものとします。

## nginxのDockerイメージ

今回用意するnginxは、以下の処理を行うよう設定ファイルを書きます。

- 80番に来たhttpアクセスを処理
- `$http_x_forwarded_proto`が"http"なら、"https"の同ページへリダイレクト
- （お好みで）"/public/..."に来たらそのまま返す
- それ以外は`unix:///myapp/tmp/sockets/puma.sock`を通してRailsへ渡す

後述の設定と合っていれば、ソケットファイルの位置は自由です。

サンプルは↓です。
お好きな設定を追記してください。

```
upstream myapp {
  server unix:///myapp/tmp/sockets/puma.sock;
}

server {
  listen 80;
  server_name your.domain;

  if ($http_x_forwarded_proto = "http") {
      return 301 https://$host$request_uri;
  }

  root /myapp/public;

  location / {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;

    if (!-f $request_filename) {
      proxy_pass http://myapp;
      break;
    }
  }
}
```

このファイルをnginx.confとして、nginx用Dockerfileと一緒にnginxフォルダの中に置きます。

- nginx
  - Dockerfile
  - nginx.conf

Dockerfileでは、設定ファイルをコピーして使うだけのイメージを作ります。

```Dockerfile
FROM nginx:1.11.7
RUN rm -f /etc/nginx/conf.d/*
ADD nginx.conf /etc/nginx/conf.d/app.conf
CMD /usr/sbin/nginx -g 'daemon off;' -c /etc/nginx/nginx.conf
```


## Railsの調整

nginxのイメージを使ってコンテナを立てるよう、deployment.ymlに追記をします。

ただし、Rails側のコンテナとsocketを共有するため（publicもついでに）、ボリュームをマウントする必要があります。
空のボリュームをマウントするため、元々publicに入っていたものが消えてしまうので、RailsのDockerfileを工夫して回避します。

今回は、事前に中身を/tmpに移し、マウントされてからコピーする苦肉の策を取りました。
回避策はプロジェクトに依存するので、お好みの方法を適用してください。

忘れず起動にsocketを使うようにします。

```Dockerfile
...
# 逃がす
RUN mkdir -p /tmp/public && \
    cp -rf /myapp/public/* /tmp/public
...
# 戻す
CMD \
    bundle exec rails db:migrate \
    && cp -rf /tmp/public/* /myapp/public \
    && bundle exec puma -C config/puma.rb
```

`config/puma.rb`に以下を追記します。

```ruby
app_root = File.expand_path('../..', __FILE__)
bind "unix://#{app_root}/tmp/sockets/puma.sock"
```

## serviceの設定

nginxを挟んでいるので、80番でアクセスしないといけません。
`service.yml`の3000を80に書き換えるだけです。

```yaml
apiVersion: v1
kind: Service
metadata:
  name: testapp
  labels:
    app: testapp
spec:
  type: LoadBalancer
  ports:
    - port: 80
  selector:
    app: testapp
```

## deploymentの設定

deployment.ymlには、ボリュームマウントの設定と、nginxコンテナの設定を追記します。
シンプルですね。

```yaml
...
    spec:
      containers:
        - image <Railsのimage>
          ...

########### 追記 #############
          volumeMounts:
            - mountPath: /myapp/public
              name: web-assets
            - mountPath: /myapp/tmp/sockets
              name: web-sock
########### おわり ############

        - image: <Cloud SQL Proxyのimage>
          ...

########### 追記 #############
        # [START nginx]
        - name: nginx
          image: asia.gcr.io/<Your Project ID>/nginx:latest
          ports:
            - name: http-server
              containerPort: 80
            - name: https-server
              containerPort: 443
          volumeMounts:
            - mountPath: /myapp/public
              name: web-assets
              readOnly: true
            - mountPath: /myapp/tmp/sockets
              name: web-sock
        # [END nginx]

      # [START volumes]
      volumes:
        - name: web-assets
          emptyDir: {}
        - name: web-sock
          emptyDir: {}
########### おわり ############
        - name: cloudsql-oauth-credentials
          secret:
            secretName: cloudsql-oauth-credentials
        - name: ssl-certs
          hostPath:
            path: /etc/ssl/certs
      # [END volumes]
```

## デプロイ

いよいよ大詰めです。

RailsのDockerイメージ、nginxのDockerイメージをビルドしてpushします。
手順は前回と同じです。

```bash
IMAGE_ID=$(docker build --no-cache -q .)
url_base="asia.gcr.io/<Your Project ID>/app"
docker tag ${IMAGE_ID} ${url_base}:latest
gcloud docker -- push ${url_base}:latest
```

```bash
IMAGE_ID=$(docker build --no-cache -q ./nginx)
url_base="asia.gcr.io/<Your Project ID>/nginx"
docker tag ${IMAGE_ID} ${url_base}:latest
gcloud docker -- push ${url_base}:latest
```

両方のpushが完了したら、serviceとdeploymentを更新します。

```bash
kubectl apply -f deployment.yml
kubectl delete svc testapp
kubectl apply -f service.yml
```

なお、誰も使っていないサービスなのでdeleteをしていますが、ダウンタイムが発生するので普通のサービスではちゃんとRolling Updateなどをする必要があります。

一向にExternal IPが振られない場合、↓のコマンドで詳細を確認してみてください。
Static IPの制限に引っかかっているとExternal IPが振られない、という事態になるため、古いIPは削除するなりしてください。

```bash
kubectl describe svc
```

さて、podやsvcが正常に立ち上がったら、80番にアクセスしてみましょう。
以前と変わらない画面が出てくれば成功です:tada: