RailsアプリをGoogle Container Engineで動かす

# 背景
herokuでRailsアプリを動かしていましたが、いかんせん無料なので起動が遅かったり、デプロイ時にR10（60sでタイムアウトする）に引っかかったりと辛くなってきました。

herokuへの課金や移行先を考えるにあたり、折角なのでGoogle Container Engine (GKE)も試してみました。
その際ちょいちょい躓く所があったので、備忘録として手順を残しておきます。

なお、**とりあえず動かす**ことにフォーカスしています。
参考にされる場合、productionとして必要なNginxやSSLなどの諸々は追加で設定してください。


# 前提
- dockerコマンドはインストールされているものとします
- 動くRailsアプリがあるとします
    - 無ければRails newしたものでも良いですが、DBとの通信を確認できた方が良いです
        - migrationファイルを適当に用意しましょう
    - MySQLで考えます
- 無料枠がある（今まで使ったことがない）と考え、細かい設定は行いません
- Webの画面で作れる所は頼ります
- リージョンは東京です

## Railsの設定

database.ymlは以下のような設定にします。

```yaml
production:
  <<: *default
  adapter: mysql2
  encoding: utf8
  database: <db-name>
  host: <%= ENV["RAILS_DB_HOST"] %>
  username: <%= ENV["RAILS_DB_USER"] %>
```

MySQLで、ホスト/ユーザー名は外部から取ってくる仕様です。
<db-name>はアプリに合わせたDB名に変更してください。
後ほど使いますので覚えておくか、忘れたらGCPの画面で都度確認しましょう。。

# 手順

クレカを登録すると沢山課金されるんじゃ……と怯えてしまいますが、$300あれば基本大丈夫なので色々試せます。
ビバ無料枠。

## GCPへの登録
では、Google Cloud Platform (GCP) の登録から始めましょう。

https://cloud.google.com/

手順に従ってゆけば登録できます。

- 個人/法人を 指定
- 名前、住所、クレカの入力
- 完了 :tada:

$300が付与されます。
なお、試用期間中に仮に超えても、権限を付与しないと課金は発生しないと言われます。
親切設計。

## Projectを作る
大きな括りであるProjectを作ります。

`create project`を選び、好きなProject nameを付けます。
そして、**隠れているオプション**を開き、リージョンを設定します。
自分の場合だと`us-central`がデフォルトで選択されており、変更し忘れるところでした。
`asia-northeast1`に変更して作成します。

作成完了するとDashboard上でステータスが確認できます。
ProjectのIDは後ほど使うので場所を覚えておきます。

## Clusterを作る

ここでGKEの設定を少しだけはさみます。
左のメニューから "Container Engine" を選んで有効化します。
初回に数分ほど時間がかかるので、Twitterを眺めて待ちましょう。

初期起動が終わったら、クラスタを作成します。
nameはわかりやすい名前をつけ、Zoneは`asia-northeast1-a`にします。
また、**Cloud SQLのPermissionをEnable**にします。
他はとりあえずそのままで進めます。

クラスタ名は後ほど使うので、覚えておきます。


## Google Cloud SDKを入れる

手元から操作をするために、Google Cloud SDKというものが必要です。
後々使いますので、この段階でインストールします。

https://cloud.google.com/sdk/docs/

↑を見て、プラットフォームにあったインストールを行います。
基本的にYを入力していれば終わります。

インストールが終わったらアップデートを行います。

```bash
$ gcloud components update
$ gcloud components install kubectl
```

ついでにkubectlというものも入れていますが、後半に出てきます。

ここで認証、デフォルト値の設定を行います。
プロジェクトやクラスタを毎回指定するのは手間ですので、デフォルトとして与えます。

```bash
$ gcloud init
$ gcloud config set container/cluster <クラスタ名>
$ gcloud container clusters get-credentials <クラスタ名>
```

指示に従い進めます。
"Do you want to configure Google Compute Engine"でYを押し、"asia-northeast1-a"を探して選びます。


個別に設定をしたい場合は、↓の中程にある（オプショナル）の箇所を参照してください。
https://cloud.google.com/container-engine/docs/before-you-begin#optional_set_gcloud_defaults

```bash
$ gcloud config set project <プロジェクト名>
$ gcloud config set compute/zone asia-northeast1-a
$ gcloud config set container/cluster <クラスタ名>
$ gcloud container clusters get-credentials <クラスタ名>
```

-----

さらに、↓を参考に、デフォルトの認証情報も設定しておきます。
https://developers.google.com/identity/protocols/application-default-credentials

左のメニューから "API Manager" → "Credentials" を選び、上部"Create Credentials" / "Service account key"を選びます。
"Compute Engine default service account" / "JSON"を選択し、作成します。

するとJSONファイルがダウンロードされるので、ローカルの安全な場所に置きます。
`.ssh`の中などがいいかもしれません。
置いた場所を環境変数`GOOGLE_APPLICATION_CREDENTIALS`に設定し、↓を実行しておきます。

```bash
$ gcloud auth application-default login
```

これでデフォルトの設定は完了です。

## DBを作る

アプリに先立って、DBを先に立てておきます。
左のメニュー → "STORAGE" → "SQL" でCloud SQL (MySQL) インスタンスを作ることが出来ます。

Cloud SQLには世代が2つありますが、速くて安い第二世代を選びましょう。
Instance IDはサービスを表すわかりやすい名前を付けます。

↓の記事を見ると、第一世代との性能差が結構あるようです。
シビアなサービスを作って色々と比較してみたいものです。
http://qiita.com/pakotan/items/1db981611ead66ce2c8b

Regionは`asia-northeast1`、Zoneは`asia-northeast1-a`を選びましょう。
Zoneは3つありますが、とりあえず分かりやすさのためaにします。

他、ハードウェアはデフォルトのままにしておきます。
設定を変えた際の各種スループットがグラフで出てとてもわかりやすく、いじってみると楽しいです。

今回はすぐにプロダクション、というわけではないので諸々設定していませんが、設定を眺めて「こんなことができるのかー」と調べておくのが良さそうです。

createボタンを押すとIPが割り振られて出来上がりです。

出来上がったら、"Instance details"から "Access Control" / "Users" を選び、rootのパスワードを変更しておきましょう。

### 手元のクライアントからつなぐ

出来上がったDBに早速つないでみたいですが、IPよりProxyを通してつなぐ方式が推奨されています。
この方法ですと、特定IPを許可する必要もなく安全です。

https://cloud.google.com/sql/docs/mysql-connect-proxy

↑の記事に従い、cloud_sql_proxyをダウンロードします。
サービスアカウントは先程作ったものが使われますので、ここでは作らなくてもよいです。

例では`/cloudsql`というディレクトリを作っていましたが、ルートに作るのが嫌だったのでホーム以下の適当なフォルダに作りました
（なお、Dockerを使ってProxyを立てる方法もありました）。

指定したフォルダにソケットが出来上がるので、それを指定してアクセスします。
コマンドラインでなくても、MySQL Workbenchなどが使えます。
先ほど変更したrootパスワードを使ってrootとしてログインします。

### アプリ用DB、DBユーザーの作成

Railsからアクセスするため、アプリ用のユーザーを作ります。
アプリ-DB間の通信もSQL Proxyを使うのが良いとされているので、それに特化したユーザーにします。

https://cloud.google.com/sql/docs/sql-proxy
の中程「プロキシ用の特別な MySQL ユーザー アカウントの作成について」に書かれています。

ではrootでアクセスをした状態で作業をします。
`<hoge-xxx>`はアプリに合わせてください。

```sql
CREATE DATABASE <hoge-dbname>;
CREATE USER '<hoge-name>'@'cloudsqlproxy~%';
GRANT ALL PRIVILEGES ON <hoge-dbname>.* TO '<hoge-name>'@'cloudsqlproxy~%';
```

これで、Cloud SQL Proxy経由のみでアクセスできるユーザーが生まれました。
アプリからはこのユーザーを指定してつなげます。

## APIの許可

GKE上のCloud SQL ProxyからOAuthを使ってCloud SQLに接続する際、APIを通じて通信を行います。
これは事前に許可してあげないといけません。

左のメニュー"API Manager" -> "Dashboard" -> "Enable API" -> "Google Cloud SQL API ENABLE"の"ENABLE"をクリックすると許可されます。


## Docker Imageのpush
Container Registryにイメージを配置し、Container Engineが簡単に取ってこれるようにします。

RailsアプリのDocker化はされているものとします。
ここでは、サンプルとして以下のようなDockerfileを用意しました。

```text
FROM ruby:2.3.1
ENV LANG C.UTF-8
RUN apt-get update -qq && apt-get install -y build-essential nodejs
RUN mkdir /myapp
WORKDIR /myapp
ADD Gemfile Gemfile.lock /myapp/

ENV RAILS_ENV production

ADD . /myapp

RUN bundle install

CMD \
    bundle exec rails db:migrate && \
    bundle exec rake assets:precompile && \
    bundle exec rails s -p 3000 -b '0.0.0.0'
```

アプリに合わせて変更したものを用意してください。

push先は、以下のようなURLです。

`asia.gcr.io/<project-id>/<folder-name>`

project-idはdashboardから確認できます。
folder-nameは好きな名前を付けてください。
URLを決めたら、↓のような手順でbuild、pushできます
（build -qなので反応が無いですが）。

```bash
$ IMAGE_ID=$(docker build -q -t foo .)
$ reg_url="asia.gcr.io/<project-id>/<folder-name>"
$ docker tag ${IMAGE_ID} ${reg_url}
$ gcloud docker -- push ${reg_url}
```

pushが完了したら、GCPのメニューから"Container Engine" → "Container Registry"を見て、イメージが配置されていることを確認しましょう。

## コンテナの管理

さて、ここからはKubernetesを使ってコンテナを扱います。
http://kubernetes.io/docs/hellonode/

podやdeploymentなどの用語をふんわり抑えておくと、理解が深まるかと思います。

### 認証情報の設定

https://cloud.google.com/sql/docs/container-engine-connect

↑のページにもありますが、DBのパスワードなどを安全に管理する仕組みがあります。
kubectl上から設定が可能です。

`<認証のjson>` には、以前.sshなどに保管したService Accountのjsonを指定します。
`<db-user>` には、アプリ用に作成したDBユーザー名を指定します。

```bash
$ kubectl create secret generic cloudsql-oauth-credentials --from-file=credentials.json=<認証のjson>
$ kubectl create secret generic cloudsql --from-literal=username=<db-user>
```

これらの値は、最終的にRailsのdatabase.ymlへと受け渡されます。

### 状態を見る

まず覚えておくと便利な「困った時に叩くコマンド」として、`kubectl get`があります。

```bash
$ kubectl get pod
$ kubectl get service
$ kubectl get deployment
```

などなど、さまざまな状態がkubectlで取得できます。

また、「ブラウザで状態を見たい！」という要望にも対応しており、以下のコマンドを叩いて`http://127.0.0.1:8001/api/v1/proxy/namespaces/kube-system/services/kubernetes-dashboard/`に接続すると、dashboardで確認できます。

```bash
$ kubectl create -f https://rawgit.com/kubernetes/dashboard/master/src/deploy/kubernetes-dashboard.yaml
$ kubectl proxy
```

### deploymentを作成する

https://github.com/GoogleCloudPlatform/container-engine-samples/blob/master/cloudsql/cloudsql_deployment.yaml

↑に公式のサンプルがありますので、流用して1コンテナの設定を記述します。
このファイルを少し改変したものが以下です。

```yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: testapp
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: testapp
    spec:
      containers:
        - image: asia.gcr.io/<project-id>/<folder-name>:latest
          name: web
          env:
            - name: RAILS_DB_HOST
              value: 127.0.0.1
            - name: RAILS_DB_USER
              valueFrom:
                secretKeyRef:
                  name: cloudsql
                  key: username
            - name: SECRET_KEY_BASE
              value: 'hoge'
            - name: RAILS_ENV
              value: production
            - name: RACK_ENV
              value: production
          ports:
            - containerPort: 3000
              name: testapp
        - image: b.gcr.io/cloudsql-docker/gce-proxy:1.05
          name: cloudsql-proxy
          command: ["/cloud_sql_proxy", "--dir=/cloudsql",
                    "-instances=<db-instance-id>=tcp:3306",
                    "-credential_file=/secrets/cloudsql/credentials.json"]
          volumeMounts:
            - name: cloudsql-oauth-credentials
              mountPath: /secrets/cloudsql
              readOnly: true
            - name: ssl-certs
              mountPath: /etc/ssl/certs
      volumes:
        - name: cloudsql-oauth-credentials
          secret:
            secretName: cloudsql-oauth-credentials
        - name: ssl-certs
          hostPath:
            path: /etc/ssl/certs
```

長いですが、やっていることはシンプルです。
プロジェクトに合わせて、↓の箇所を書き換える必要があります。

```yaml
        - image: asia.gcr.io/<project-id>/example:latest
    "-instances=<db-instance-id>=tcp:3306",
```

<db-instance-id>は、ブラウザからGCPを見に行きましょう。
`-instances=$PROJECT:$REGION:INSTANCE=tcp:3306.`
という形式です。

```yaml
env:
  - name: RAILS_DB_HOST
　   value: 127.0.0.1
  - name: RAILS_DB_USER
    valueFrom:
      secretKeyRef:
        name: cloudsql
        key: username
```

この辺りで秘密の情報を環境変数に受け渡しています。
これにより、kubectlで自由に設定した値を各コンテナで使用することができます。

また、Cloud SQL Proxyを別コンテナとして立て、それを利用することでProxyを通したDBへの接続を実現しています。

さて、この設定ファイルの内容を反映させます。
↓のコマンドで作成したファイルを指定すると一発です。

```bash
$ kubectl create -f <file-path>
```

ファイルの構文が正しいと、↓のような表示が出て成功します。
deployment "testapp" created

Railsアプリがちゃんと動いているかは、↓のコマンドで確認します。

```bash
$ kubectl get pod
```

これのステータスが"Running"になればOKです。
CrashLoopBackOffやErrorなどになっていると失敗です。
↓のコマンドなどでログを見て原因を探ると早いです。

```bash
$ kubectl get pod
pod-idを控える
$ kubectl logs <pod-id> web
```

このページの下に書いていますが、より詳細なログは他の場所で見ることがきます。

修正して再度デプロイしたい時は、とりあえずは↓のコマンドでまるまる消えてくれるので再試行できます。

```bash
$ kubectl delete -f deployment.yaml
```

## serviceの作成

ついにここまで来ました。
上記のdeploymentは、まだ外部へのアクセス手段がありません。
そこで、serviceというものを利用し、外部に公開するIPを貼ります。

↓に様々な設定が載っています。
http://kubernetes.io/docs/user-guide/services/

今回は3000番を公開する↓のような設定ファイルを作ります。

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

deploymentと同様のコマンドで作成したファイルを指定します。

```bash
$ kubectl create -f <file-path>
```

これにより、L4ロードバランサーが紐付けられ、外部からアクセスすることができます。

完了したら、以下のコマンドで確認をします。
作成したサービスにexternal IPが貼られたら完了です。
これは多少時間がかかるので、watchコマンドで更新しつつ、Twitterを眺めて待ちましょう。

```bash
$ kubectl get service
```

`http://<external IP>:3000` にアクセスをすると、きっとRailsアプリが出迎えてくれているかと思います :tada:
`Yay! You’re on Rails!`

### 上手くいってない……？？

```bash
$ kubectl get pod
```

をして、状態を確認しましょう。
Running以外（立ち上がっている最中か、エラーで再起動を繰り返しているか）などを確認し調査を進めます。

ログを見ると原因解決に役立ちます。
service-nameはweb, sql-proxyなど、deploymentファイルで指定したものです。

```bash
$ kubectl logs <pod-id> <service-name>
```

また、実際にノードにアクセスをしてみるとデバッグに役立ちます。

```bash
$ kubectl get node
  ノード名をメモる
$ gcloud compute ssh <ノード名>

アクセス先で docker ps / docker exec -it xxxx /bin/bash など
```

詳細なログを見たい場合は、Stackdriverが役に立ちます。
左のメニュー"stackdriver" -> "Logging"でコンテナのログを見たり、SQLのエラーを見たりできます。
例えば、MySQLへの初期通信が上手く行かない場合などは、"API許可してますか？"というメッセージ付きのエラーが出たりします。

# 感想
公式ドキュメントが整備されており（日本語化されているものも多かった）、比較的進めやすかったです。
が、GKEとKubernetesがそれぞれ独立しており、Kubernetesを使ったことがないと少々悩む所が多いように感じました。

UI的にはmaterial designが洗練された感じを醸し出しており、AWSより個人的には見やすいと感じました。
予算設定、アラートも細かく設定できて良い感じです。

stackdriverとの連携など、この記事では扱わなかった機能がたくさんありますので、それらも使ってAWSと比較してみたいと思いました。