Rails5.2+MySQLアプリケーションをGKE (Google Kubernetes Engine)にデプロイする

Rails5.2で作成したアプリケーションをGKE(Google　Kubernetes　Engine）にデプロイする方法をまとめておきたいと思います。

GKEは今回初めて使用したため、至らない点があるとは思いますが、ご了承下さい。
# バージョン
Rails 5.2.1
Ruby 2.5.1
MySQL 5.7
# 目標
- GKEにRailsアプリケーションをデプロイして公開する
- データベースはGoogle Cloud SQLを使用する（GKEから接続する）

# 前提
- Google Cloud SDK（gcloudコマンドラインツール）がインストールされていること
※インストールされていない場合は[こちら](https://cloud.google.com/sdk/docs/quickstarts?hl=ja)を参照
- GCPのプロジェクトは作成済みであること(GCPコンソールより作成可能)

# Google Cloud SDKの認証
SDKをインストールしただけでは使用できないため、以下コマンドを実行していない場合は実行する。
実行すると、ブラウザが開いてGoogleアカウントでの認証が求められます。

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

# Railsプロジェクトの内容
構成としては、ビュー、コントローラー、モデルが１つずつのシンプルなもの。
プロジェクト名は「rails_sample」としています。

### モデル
```ruby:user.rb
class User < ApplicationRecord
  # カラムはemailとnameのみ
  validates :email, presence: true
  validates :name, presence: true
end
```
### コントローラー
```ruby:users_controller.rb
class UsersController < ApplicationController
  def index
    @user = User.all
  end
end
```
### ビュー
```erb:users/index.html.erb
<table border="1">
  <tr>
    <th>Email</th>
    <th>Name</th>
  </tr>
  <% @user.each do |user| %>
    <tr>
      <td><%= user.email %></td>
      <td><%= user.name %></td>
    </tr>
  <% end %>
</table>
```
### DB設定ファイル
```yaml:config/database.yml
default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: password
  host: db

development:
  <<: *default
  database: rails_sample_development

test:
  <<: *default
  database: rails_sample_test

# 環境変数は後ほど設定します
production:
  <<: *default
  host: <%= ENV['DB_HOST'] %>
  database: <%= ENV['DB_NAME'] %>
  username: <%= ENV['DB_USER'] %>
  password: <%= ENV['DB_PASS'] %>
```
### ルーティング
```ruby:routes.rb
Rails.application.routes.draw do
  root to: "users#index"
end
```

# Dockerの内容
Dockerについては、buildしてイメージを作成しておく。
イメージ名は「gcr.io/プロジェクトID/任意のアプリ名」形式にしておく必要がある。
ここでは「gcr.io/プロジェクトID/rails_sample」という名前にしている。

```yaml:docker-compose.yml
version: '3'
services:
  db:
    image: mysql:5.7.23
    environment:
      MYSQL_ROOT_PASSWORD: password
    ports:
      - '4306:3306'
    volumes:
      - mysql-data:/var/lib/mysql
  web:
    build: .
    image: gcr.io/プロジェクトID/rails_sample
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    volumes:
      - ./rails_sample:/var/www/rails_sample
    ports:
      - '3000:3000'
    environment:
      - EDITOR=vim
    depends_on:
      - db
volumes:
  mysql-data:
```
```dockerfile:Dockerfile
FROM ruby:2.5.1
ENV LANG C.UTF-8

RUN apt-get update -qq && apt-get install -y build-essential mysql-client default-libmysqlclient-dev vim

# Install nvm with node and npm
ENV NVM_DIR /usr/local/nvm
ENV NODE_VERSION 6.9.4
RUN curl https://raw.githubusercontent.com/creationix/nvm/v0.30.1/install.sh | bash \
    && . $NVM_DIR/nvm.sh \
    && nvm install $NODE_VERSION \
    && nvm alias default $NODE_VERSION \
    && nvm use default

ENV NODE_PATH $NVM_DIR/v$NODE_VERSION/lib/node_modules
ENV PATH      $NVM_DIR/versions/node/v$NODE_VERSION/bin:$PATH

RUN npm install -g yarn

ENV APP_HOME /var/www/rails_sample
RUN mkdir -p $APP_HOME
WORKDIR $APP_HOME

ADD ./rails_sample/Gemfile $APP_HOME/Gemfile
ADD ./rails_sample/Gemfile.lock $APP_HOME/Gemfile.lock
RUN bundle install

ADD ./rails_sample $APP_HOME
RUN bundle exec rake assets:precompile

EXPOSE 3000
CMD ["rails", "server", "-b", "0.0.0.0"]
```

# kubectlのインストール
```sh
$ gcloud components install kubectl
```
# gcloudコマンドラインツールのデフォルトを設定する
```sh
$ gcloud config set project [PROJECT_ID]
$ gcloud config set compute/zone asia-northeast1-a
```
※この例では、zoneはasia-northeast1-aに設定している。任意のzoneで構わない。

# APIの有効化
GCPコンソールより、作業するプロジェクトにて以下のAPIを有効化する

```sh
Google Container Engine API
Google Cloud Container Builder API
Google Cloud SQL API
```
# Kubernetesクラスタの作成
コンテナクラスタを作成します。
後でGCPのレジストリにコンテナイメージを格納するのですが、このコンテナイメージを実行するのがコンテナクラスタになります。
作成には数分かかります。

```sh
$ gcloud container clusters create my-cluster \
                   --num-nodes 2 \
                   --machine-type n1-standard-1 \
                   --zone asia-northeast1-a
```
★注意！
既存のクラスタを使用する場合、又はGCPコンソールからクラスタを作成した場合は、以下のコマンドを実行する。

```sh
$ gcloud container clusters get-credentials クラスタ名
```

# CloudSQLの設定
### MySQLのCloudSQLインスタンスを作成する

```sh
$ gcloud sql instances create --gce-zone asia-northeast1-a --database-version MYSQL_5_7 --memory 4 --cpu 2 インスタンス名
```
zoneやメモリ、CPU数は任意で構いません。

### 作成したCloudSQLインスタンスにDatabaseを作成する

```sh
$ gcloud sql databases create DB名 --instance=インスタンス名
```
### DBユーザーを作成する
DBユーザー作成と同時にパスワードも設定します。

```sh
$ gcloud sql users set-password DBユーザー名 \
--instance=インスタンス名 --password=DBパスワード
```
# プロキシのインストール
CloudSQLにプロキシ経由で接続するため、プロキシをインストールします。

### プロキシをダウンロード
```sh
# Macの場合
$ curl -o cloud_sql_proxy https://dl.google.com/cloudsql/cloud_sql_proxy.darwin.amd64

# Linuxなどwgetが使用できる場合
$ wget https://dl.google.com/cloudsql/cloud_sql_proxy.linux.amd64 -O cloud_sql_proxy
```
実行権限を付与しておく。

```sh
$ chmod +x cloud_sql_proxy
```
### サービスアカウント作成
ここでは、[test-user]という名前のアカウントを作成。

```sh
$ gcloud iam service-accounts create test-user --display-name "test-user"
```

### プロキシアカウントのメールアドレスを確認
サービスアカウントが作成されると、以下コマンドでメールアドレスを確認できる。

```sh
$ gcloud iam service-accounts list

NAME               EMAIL
test-user          test-user@プロジェクトID.iam.gserviceaccount.com
```

### プロキシアカウントにCloudSQLClientロールを付与する

```sh
# メールアドレスは上記で確認したアドレスにする
$ gcloud projects add-iam-policy-binding プロジェクトID --member serviceAccount:test-user@プロジェクトID.iam.gserviceaccount.com --role roles/cloudsql.client
```

### 認証ファイル作成
```sh
# 認証ファイル名は「key.json」としている。メールアドレスは上記で確認したプロキシアカウントのアドレスにする。
$ gcloud iam service-accounts keys create key.json --iam-account test-user@プロジェクトID.iam.gserviceaccount.com
```

# DockerイメージをGCPのContainer RegistryにPushする
```sh
$ docker push gcr.io/プロジェクトID/rails_sample
```

# DockerコンテナをGKEにデプロイ
### クラスタの情報を確認
```sh
$ gcloud container clusters list

# ここではIPアドレスは伏せています
NAME          LOCATION           MASTER_VERSION  MASTER_IP        MACHINE_TYPE  NODE_VERSION  NUM_NODES  STATUS
my-cluster    asia-northeast1-a  1.10.9-gke.5    ***.***.***.***  g1-small      1.10.9-gke.5  1          RUNNING
```

### Kubernetes Secretsの作成
コンテナからプロキシ経由でDBにアクセスするため、環境変数を設定する必要あります。
上記プロキシインストール時に作成した認証ファイルを読み込むことで簡単に作成できます。

```sh
# 認証ファイルへのパスは相対パスで指定
$ kubectl create secret generic cloudsql-instance-credentials --from-file=credentials.json=./key.json
```

以下のようにシークレットを作成することも可能。ここではDBユーザーとパスワードを設定。

```sh
$ kubectl create secret generic cloudsql-db-credentials \
    --from-literal=username=DBユーザー名 \
    --from-literal=password=DBパスワード
```

### マニフェストファイルを作成
コンテナ情報をマニフェストファイルに記載します。
Railsアプリ内で使用している環境変数の値もこのマニフェストファイルにて定義します。

```sh
# ファイルを作成。ファイル名は任意の名前でOK
$ vi deployment.yml
```

以下、マニフェストファイルの例です。ご自身の環境に合わせて設定値を変更して下さい。
設定値の説明については、以下例のコメントを参照下さい。
全て入力したら保存します。

```yml:deployment.yml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  # 任意の名前
  name: sample-app
spec:
  replicas: 2
  template:
    metadata:
      labels:
　　　　　　　　　　　　　　　　# 任意の名前
        app: sample-app
    spec:
      containers:
        # Pushしたイメージの名前
        - image: gcr.io/プロジェクトID/rails_sample
          name: web
          # env配下は全て環境変数。必要な変数は全て定義しています。
          env:
            #　上記で作成したKubernetes Secretsから読み込み
            - name: DB_USER
              valueFrom:
                secretKeyRef:
                  name: cloudsql-db-credentials
                  key: username
            - name: DB_PASS
              valueFrom:
                secretKeyRef:
                  name: cloudsql-db-credentials
                  key: password
            # その他必要な環境変数を定義
            - name: DB_NAME
              value: sample-db
            - name: DB_HOST
              value: 127.0.0.1
            - 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
          # instancesはコンソールにて確認可能。※1を参照。
          command: ["/cloud_sql_proxy",
                    "-instances=プロジェクトID:asia-northeast1:CloudSQLのインスタンス名=tcp:3306",
                    "-credential_file=./key.json"]
          volumeMounts:
            - name: cloudsql-instance-credentials
              mountPath: /secrets/cloudsql
              readOnly: true
      volumes:
        - name: cloudsql-instance-credentials
          secret:
            secretName: cloudsql-instance-credentials
```
※1 GCPのコンソールからSQL → 作成したインスタンス → 「このインスタンスに接続」の「インスタンス接続名」に記載されている。（以下画像の赤で囲った部分）
![スクリーンショット 2018-11-26 15.30.05.png](https://qiita-image-store.s3.amazonaws.com/0/279050/a90d4870-a7e0-0e06-8bd3-283c5c58fe14.png)

### デプロイ実行
```sh
$ kubectl create -f deployment.yml
```

### デプロイ確認
正しくデプロイできている場合は、pod内の両方のコンテナが正しく動作していることを示す2/2が表示される。

```sh
$ kubectl get pods
NAME                         READY     STATUS    RESTARTS   AGE
sample-app-xxxxxxxxx-xxxxx   2/2       Running   0          1m
```

# サービスの公開
外部へ公開するため、ロードバランサーを作成する。80番ポートにて公開するため、3000番ボートを80番ポートに結びつけます。

```sh
$ kubectl expose deployment sample-app --type "LoadBalancer" --port 80 --target-port 3000
```

# パブリックIPの確認
公開されるまで数分かかる場合があるので、以下コマンドは数分後に実行する。

```sh
$ kubectl describe services sample-app

# 上記コマンドを実行すると情報が色々出てくる。その中の以下の部分を参照する。
LoadBalancer Ingress:     xxx.xxx.xxx.xxx
```

# DBのマイグレーション
やり方としては以下が考えられます。
1. コンテナにSSH接続して、rake db:migration コマンドを実行する
2. Jobを使用してデプロイ時に実行する

本来は２．でやるべきだと思いますが、今回は１．で実行しました。

### コンテナ接続する方法
```sh
# POD名はkubectl get pods コマンドで確認可能。コンテナ名はdeployment.ymlのcontainers配下のnameに指定した値
$ kubectl exec -it POD名 -c コンテナ名 bash
```
上記でコンテナに接続すると、アプリケーションのルートディレクトリがカレントになっているので、そのままrake db:migration コマンドを実行すればテーブルが作成されます。

また、このままrails c　コマンドも実行できるので、これでデータも作成できます。

# 公開したアプリケーションの確認
上記でパブリックIPを確認していると思いますので、ブラウザから以下URLにアクセスします。

```
http://パブリックIP
```
以下のような画面が表示されれば成功です。
![スクリーンショット 2018-11-27 11.22.35.png](https://qiita-image-store.s3.amazonaws.com/0/279050/201d775c-2188-03da-c9a2-42cc43b0f46d.png)

# まとめ
手順が多く、最初は大変だなと思ってしまいますが、これを使いこなせるようになると、おそらくこれまでのデプロイ方法に戻れなくなると思います。あまりにも便利なので。

今はまだ、本番環境でもコンテナで稼働しているサービスは少ないと思いますが、これから増えてくると予想されますので、今の内に知識を身に着けて損はないはずです。

おそらくもっとシンプルに、且つ効率的なやり方があると思うので、そこは継続して調査して行きます。

# 参考
https://cloud.google.com/kubernetes-engine/docs/tutorials/hello-app?hl=ja#step_4_create_a_container_cluster
https://qiita.com/jwako/items/90c0cb33cca4456c1d88
https://qiita.com/tkusumi/items/01cd18c59b742eebdc6a
https://qiita.com/chimame/items/4ac6bdd948a995ca0adf