Rails on GKE (Google Kubernetes Engine) 入門

## 本記事の目的
- 細かいことは置いておき、Rails5.1+PostgresqlのアプリケーションをGKEで公開するところまでを目的とする
- 今回使用したサンプルアプリケーションは、以下に置いておきます

[jwako/rails-gke-sample](https://github.com/jwako/rails-gke-sample)

## Rails5.1プロジェクトの作成

```
$ rails new rails-gke-sample --webpack=vue
```

## 開発環境の設定
### Dockerize Rails application
- 開発環境は、`docker-compose`で開発できるようにしておく

### ビルド

```
$ docker-compose build
```

### DB作成

```
$ docker-compose run web rake db:create
$ docker-compose run web rake db:migrate
```

### 起動

```
$ docker-compose up
```

- `http://localhost:3000` にアクセスする

## kubectlのインストール
- kubectlは、Kubernetesのクライアント

```
$ gcloud components update
$ gcloud components install kubectl
$ kubectl version --short
Client Version: v1.8.6
The connection to the server localhost:8080 was refused - did you specify the right host or port?
```

## GCPプロジェクトの作成
- GCPコンソールより、プロジェクトを作成
  - rails-gke-sample-project

## APIの有効化
- GCPコンソールより、作成したプロジェクトで以下のAPIを有効化しておく

```
Google Container Engine API
Google Cloud Container Builder API
Google Cloud SQL API
```

## Cloud SQL
- RailsをGCPで動かす場合、DBは、Cloud SQLでMySQL/PostgreSQLインスタンスを立てるのがよい
- GCPコンソールより、Cloud SQLで、インスタンスの作成からDBの作成までをする
  - Instance: rails-gke-sample-production
  - DBMS: PostgreSQL
  - User: postgres
  - DB: rails_gke_sample_production

## アクティブなプロジェクトの設定
- gcloudコマンドでアクティブなプロジェクトを設定しておく

```
$ gcloud config set project rails-gke-sample-project
Updated property [core/project].
$ gcloud config set compute/zone us-central1-a
Updated property [compute/zone].
```

## クラスタの作成
- クラスタは、複数のノードの集合体
- ノードは、AWSでいうインスタンスに相当
- ポッドは、1個以上のコンテナの集合体.デプロイの最小単位
- GKEでクラスタを起動すると、masterとnode(defaultだと3台)が立ち上がる
  - `kubectl get nodes`でNodesが3台起動していることが確認できる
  - この時点では、Podsは作成されていない

```
$ gcloud container clusters create rails-gke-sample-cluster --machine-type=n1-standard-1 --zone=us-central1-a
```

### クラスタ情報の取得

```
$ gcloud container clusters list
NAME                      LOCATION       MASTER_VERSION  MASTER_IP      MACHINE_TYPE   NODE_VERSION  NUM_NODES  STATUS
rails-gke-sample-cluster  us-central1-a  1.8.8-gke.0     35.188.82.xxx  n1-standard-1  1.8.8-gke.0   3          RUNNING
```

### kubectlで確認
- kubectl cluster-infoでもGKEのクラスタ情報を確認できる

```
$ kubectl cluster-info
```

- ノードの確認

```
$ kubectl get nodes
NAME                                                  STATUS    ROLES     AGE       VERSION
gke-rails-gke-sample-clu-default-pool-bc05e197-9dd8   Ready     <none>    1m        v1.8.8-gke.0
gke-rails-gke-sample-clu-default-pool-bc05e197-9hx8   Ready     <none>    1m        v1.8.8-gke.0
gke-rails-gke-sample-clu-default-pool-bc05e197-tvt2   Ready     <none>    1m        v1.8.8-gke.0
```

## コンテナのビルド/Container Registryへの登録
- docker-compose upでローカルで起動できるようにしておく
- 以下のコマンドで、Container Builderを利用してコンテナのビルドを実施する
- ビルドに成功するとContainer Registryにイメージが登録される
- `rails-gke-sample`部分は、適当に名前をつければ良い

```
$ gcloud container builds submit --tag gcr.io/rails-gke-sample-project/rails-gke-sample .
```

## 環境変数の設定
- 以下のコマンドで環境変数を登録できる
- 環境変数は、クラスタに対して登録する (クラスタを削除したら登録し直す必要がある)
- 設定内容は、
  - GCPコンソールのワークロード > 名前 > 設定
  - ProxyのOverview > Config and Storage > シークレットで確認できる

```
$ kubectl create secret generic <name> --from-literal=<key>=<value>
```

### Rails
#### SecretKeyの設定

```
$ kubectl create secret generic rails --from-literal=secret-key-base=3825d****
```

### DB
#### サービスアカウントの作成
- IAMと管理 > サービス アカウントより、サービスアカウントを作成する
  - rails-gke-sample-admin
  - この時、[役割] で、[Cloud SQL] > [Cloud SQL Client] を選択する
- ダウンロードした秘密鍵を`~/.gcloud-secret/`に配置しておく

#### CloudSQLのクレデンシャルに指定
- ダウンロードした秘密鍵をCloudSQLのクレデンシャルに指定する

```
$ kubectl create secret generic cloudsql-oauth-credentials --from-file=credentials.json=/Users/hogehoge/.gcloud-secret/rails-gke-sample-project-d0c6a11a46eb.json
secret "cloudsql-oauth-credentials" created
```

- 削除する場合

```
$ kubectl delete secret cloudsql-oauth-credentials
```

#### DBアクセスのためのSecretを設定

```
$ kubectl create secret generic cloudsql-password --from-literal=username=postgres --from-literal=password=fugafuga
```

## アプリケーションのデプロイ

### Deploymentオブジェクト
- Deploymentオブジェクトは、新しいバージョンのリリースを管理するための仕組み
- マニフェストファイル(`deployment.yml`)を作成する

```yml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: sample-app
spec:
  replicas: 2
  template:
    metadata:
      labels:
        app: sample-app
    spec:
      containers:
        - image: gcr.io/rails-gke-sample-project/rails-gke-sample
          name: web
          env:
            - name: RAILS_DB_HOST
              value: 127.0.0.1
            - name: RAILS_DB_USER
              valueFrom:
                secretKeyRef:
                  name: cloudsql-password
                  key: username
            - name: RAILS_DB_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: cloudsql-password
                  key: password
            - name: SECRET_KEY_BASE
              valueFrom:
                secretKeyRef:
                  name: rails
                  key: secret-key-base
            - name: RAILS_ENV
              value: production
            - name: RACK_ENV
              value: production
            - name: RAILS_SERVE_STATIC_FILES
              value: 'true'
          ports:
            - containerPort: 3000
              name: sample-app
          command: ["bundle", "exec", "rails", "server", "-p", "3000", "-b", "0.0.0.0"]
        - image: b.gcr.io/cloudsql-docker/gce-proxy:1.11
          name: cloudsql-proxy
          command: ["/cloud_sql_proxy",
                    "-instances=rails-gke-sample-project:us-central1:rails-gke-sample-production=tcp:5432",
                    "-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
```

### コマンド実行
- 登録内容は、Proxyのデプロイメントから確認できる

```
$ kubectl create -f kube/deployment.yml
deployment "sample-app" created
```

### コマンドによる確認
- Podsが起動するまでに、初回は数分時間がかかる

```
$ kubectl get deployments
NAME         DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
sample-app   2         2         2            2           2m
$ kubectl get pods
NAME                         READY     STATUS    RESTARTS   AGE
sample-app-c6854f5d6-hvpfm   2/2       Running   0          2m
sample-app-c6854f5d6-th5xc   2/2       Running   0          2m
```

## 外部への公開

### Serviceオブジェクト
- Serviceオブジェクトは、クラスタの内外に対してサービスを公開するための仕組み
- マニフェストファイル(`service.yml`)を作成
  - ここでは、80ポートを3000ポートと結びつける

```yml
apiVersion: v1
kind: Service
metadata:
  name: sample-app
  labels:
    app: sample-app
spec:
  type: LoadBalancer
  ports:
    - port: 80
      targetPort: 3000
      protocol: TCP
  selector:
    app: sample-app
```

### コマンド実行

```
$ kubectl create -f kube/service.yml
```

### コマンドによる確認
- しばらくすると、EXTERNAL-IPで、IPアドレスが付与されるのを確認できる

```
$ kubectl get services
NAME         TYPE           CLUSTER-IP     EXTERNAL-IP     PORT(S)        AGE
kubernetes   ClusterIP      10.63.240.1    <none>          443/TCP        46m
sample-app   LoadBalancer   10.63.241.63   35.188.172.xxx   80:30516/TCP   1m
```

### 外部への公開
- `http://35.188.172.xxx` でアクセス

## DBのマイグレーション
- `rake db:migrate`をいつやるか問題
  - a) コンテナにSSHした後、`rake db:migrate`コマンドを実行
  - b) Jobを使用する
- 今回は、簡単のため(a)でやる

## アセットのプリコンパイル
- `rake assets:precompile`をいつやるか問題
  - a) Dockerfileに含めてしまう
  - b) Jobを使用する
- 今回は、簡単のため(a)でやる

## 後始末
### Deploymentの削除

```
$ kubectl delete deployment sample-app
deployment "sample-app" deleted
$ kubectl delete service sample-app
service "sample-app" deleted
```

### クラスタの削除

```
$ gcloud container clusters delete rails-gke-sample-cluster
```

### CloudSQLの停止
- GCPコンソールから停止

## Tips
### コンテナにSSHする方法
- rails c や rails dbが使える

```
$ kubectl exec -it <POD NAME> -c <CONTAINER NAME> bash
root@sample-app-6b9f746b6c-5ct48:/my_app#
```

### ログを確認する方法
- sternを使用すると、複数のPodのログをまとめて参照できるので便利

```
$ stern sample-app
```

[kubernetes使いは全員 stern を導入すべき – Daisuke Maki – Medium](https://medium.com/@lestrrat/kubernetes%E4%BD%BF%E3%81%84%E3%81%AF%E5%85%A8%E5%93%A1-stern-%E3%82%92%E5%B0%8E%E5%85%A5%E3%81%99%E3%81%B9%E3%81%8D-bc9d3eb2c321)

[Kubernetes関連の便利ツール: stern - そんな今日この頃の技術ネタ](http://blue1st-tech.hateblo.jp/entry/2018/01/13/212514)

[wercker/stern: ⎈ Multi pod and container log tailing for Kubernetes](https://github.com/wercker/stern)

### トラブルシューティングの方法
- Kubernetesにおけるデプロイの失敗は通常、特定のPodが立ち上がらないという状態で現れる。
  - 存在しない、あるいは権限的な問題でアクセスできないコンテナイメージの指定
  - アプリケーション実行時のConfigMapあるいはSecretが存在しない
  - Specオブジェクトが無効
  - リソース制限超過 : Podとコンテナにはそれぞれ、CPUおよびメモリ使用量の制限が設定されていて、これらの制限を超過するとPodの生成が行われない
  - リソースクオータ超過 : リソースクオータは、ノード数の固定されたクラスタを複数チームが共有する場合に、名前空間毎のリソース消費を制限するためのメカニズム
  - アプリのクラッシュ : ‘CrashLoopBackOff’メッセージを伴うローンチエラーで検出可能
  - 起動状態チェックに対するURLからの応答に時間がかかると、タイムアウトが発生してデプロイに失敗
- 根本原因の特定には‘kubectrl describe’コマンドが有効
  - `kubectl describe pod <pod名>` を実行すれば、エラーの原因を記述したイベントログが表示される
  - `kubectl logs <Pod名> <コンテナ名>`

[Kubernetesのデプロイに失敗する10の一般的理由](https://www.infoq.com/jp/news/2017/04/common-kubernetes-fail-reasons)

[10 Most Common Reasons Kubernetes Deployments Fail (Part 1)](https://kukulinski.com/10-most-common-reasons-kubernetes-deployments-fail-part-1/)

[10 Most Common Reasons Kubernetes Deployments Fail (Part 2)](https://kukulinski.com/10-most-common-reasons-kubernetes-deployments-fail-part-2/)

## Refs.
### RailsをGKEで動かす際に参考にしたアプリケーションのリンク集
- 別途以下にまとめました

[RailsをGKEで動かす際に参考にしたアプリケーションのリンク集 - Qiita](https://qiita.com/jwako/items/4f053a456bf6cd0569fe)

### その他記事

[RailsアプリをGoogle Container Engineで動かす - Qiita](https://qiita.com/esplo/items/76a1ecaf09843c49cfaf)

[これでもかって言うくらいコピペでKubernetes(Google Kubernetes Engine)に環境変数の設定からRailsアプリケーションのmigrateも考慮したデプロイが動くコマンドを書いていく - Qiita](https://qiita.com/chimame/items/4ac6bdd948a995ca0adf#%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AE%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4%E3%83%9E%E3%82%A4%E3%82%B0%E3%83%AC%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3)

[Kubernetesでのデプロイ中に'db:migrate'や'db:seed'などのRailsタスクを管理する（翻訳）](https://techracho.bpsinc.jp/hachi8833/2017_07_04/42470)

[Managing Rails tasks such as 'db:migrate' and 'db:seed' on Kubernetes while performing rolling deployments | BigBinary Blog](http://blog.bigbinary.com/2017/06/16/managing-rails-tasks-such-as-db-migrate-and-db-seed-on-kuberenetes-while-performing-rolling-deployments.html)

[Deploying Rails on Kubernetes – Adwerx Engineering](https://engineering.adwerx.com/rails-on-kubernetes-8cd4940eacbe)

[Google Container Engine (GKE, Kubernetes) を使ってサービスを外部公開する | つかびーの技術日記](http://tech-blog.tsukaby.com/archives/1261)