KubernetesでRailsを動かしてみよう！

今回は GoogleCloudPlatform の GKE を利用し、Rails を動かしてみました。

## Kubernetes Engine クラスタの作成

Kubernetes Engine クラスタは、マスターと、ノードと呼ばれる複数のワーカーマシンで構成されます。

アプリケーションをクラスタにデプロイすると、アプリケーションがノードに配置され、それらのノードで実行されます。

GKE ではクラスタを作成するためには次のコマンドを実行します。

```shell-session
$ gcloud container clusters create rails-cluster
```

**※クラスタの作成は完了するのに数分かかります。**

## 認証情報の登録

認証情報は base64 でハッシュ化した値を登録する必要がありますので、まずは登録したい文字列を base64 でハッシュ化します。

```shell-session
$ echo -n "database_root_password" | base64
ZGF0YWJhc2Vfcm9vdF9wYXNzd29yZA==

$ echo -n "secret_key_base" | base64
c2VjcmV0X2tleV9iYXNl
```

次にマニフェストファイルを作成し、先ほどの値をそのまま記述します。

```yml:secret.yml
apiVersion: v1
kind: Secret
metadata:
  name: rails
type: Opaque
data:
  database_root_password: ZGF0YWJhc2Vfcm9vdF9wYXNzd29yZA==
  secret_key_base: c2VjcmV0X2tleV9iYXNl
```

次のコマンドを実行することにより、このマニフェストファイルをデプロイすることができます。

```shell-session
$ kubectl create -f secret.yml
```

## 永続ストレージの作成

Kubernetes で作成される Docker コンテナではデータの永続化は行われないので、MySQL 用に永続ストレージを作成します。

```shell-session
$ gcloud compute disks create --size 200GB mysql-data
```

ここでは `200GB` に指定して永続ストレージを作成しています。

## MySQL をデプロイする

`mysql.yml` というファイル名でマニフェストファイルを作成しデプロイします。

`secretKeyRef` で先ほど登録した認証情報を、`volumes` の部分で永続ストレージを参照するようにします。

```yml:mysql.yml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: mysql
  labels:
    app: mysql
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
        - image: mysql:5.6
          name: mysql
          env:
            - name: MYSQL_ROOT_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: rails
                  key: database_root_password
          ports:
            - containerPort: 3306
              name: mysql
          volumeMounts:
            - name: mysql-persistent-storage
              mountPath: /var/lib/mysql
      volumes:
        - name: mysql-persistent-storage
          gcePersistentDisk:
            pdName: mysql-data
            fsType: ext4
```

`secret.yml` と同じように、このマニフェストファイルをデプロイします。

```shell-session
$ kubectl create -f mysql.yml
```

ここでは Docker コンテナに永続ストレージをアタッチするので、ポッドのステータスが `Running` になるまでは少し時間がかかります。

`kubectl get pod` コマンドでポッドのステータスを確認することができます。

```shell-session
$ kubectl get pod
NAME                     READY     STATUS    RESTARTS   AGE
mysql-xxxxxxxxxx-xxxxx   1/1       Running   0          1m
```

次に、`mysql-service.yml` で MySQL コンテナの 3306 ポートを公開する Service を作成し、あとで作成する Rails コンテナからアクセスできるようにします。

```yml:mysql-service.yml
apiVersion: v1
kind: Service
metadata:
  name: mysql
  labels:
    app: mysql
spec:
  type: ClusterIP
  ports:
    - port: 3306
  selector:
    app: mysql
```

デプロイします。

```shell-session
$ kubectl create -f mysql-service.yml
```

作成された Service の一覧は `kubectl get service` コマンドで確認できます。

```shell-session
$ kubectl get service
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
kubernetes   ClusterIP   10.xx.xx.xx     <none>        443/TCP    29m
mysql        ClusterIP   10.xx.xx.xx     <none>        3306/TCP   7m
```

## Rails アプリケーションをデプロイする

まずは MySQL と同じようにマニフェストファイルを作成し、その後 Service を作成し、外部からアクセスできるようにします。

```yml:app.yml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: rails
  labels:
    app: rails
spec:
  replicas: 1
  selector:
    matchLabels:
      app: rails
  template:
    metadata:
      labels:
        app: rails
    spec:
      containers:
        - image: busybox
          name: rails
          env:
            - name: DATABASE_ROOT_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: rails
                  key: database_root_password
            - name: SECRET_KEY_BASE
              valueFrom:
                secretKeyRef:
                  name: rails
                  key: secret_key_base
          ports:
            - containerPort: 3000
              name: rails
```

後ほど、rails アプリケーションの image ファイルをデプロイするので、ここでは適当な image ファイルを指定しています。

デプロイします。

```shell-session
$ kubectl create -f app.yml
```

外部から Rails アプリケーションに接続できるようにするため、Service を作成します。

MySQL の場合と異なり、ここでは外部に公開するので `type: LoadBalancer` となっているのに注意してください。

```yml:app-service.yml
apiVersion: v1
kind: Service
metadata:
  name: rails
  labels:
    app: rails
spec:
  type: LoadBalancer
  ports:
    - port: 3000
  selector:
    app: rails
```

デプロイします。

```shell-session
$ kubectl create -f app-service.yml
```

`kubectl get service` で rails の `EXTERNAL-IP` が `<pending>` から IP が表示されるまで待ちます。

これで `http://<EXTERNAL-IP>:3000` でアクセスできるようになりましたが、まだアプリケーションをデプロイしていないので、エラー画面が表示されると思います。

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

今回はさくっとローカルに Rails アプリケーションを new したものを使いたいと思います。

```shell-session
$ rails new sampleapp -d mysql
...
* bin/rake: spring inserted
* bin/rails: spring inserted

$ cd sampleapp
```

マイグレーションと画面表示の確認用に適当な scaffold を実行しておきます。

```shell_session
$ bin/rails generate scaffold user name:string age:integer
```

database のパスワードは先ほど `app.yml` で指定した環境変数を参照するように変更します。

```yml:config/database.yml
production:
  <<: *default
  host: mysql
  database: sampleapp_production
  username: root
  password: <%= ENV['DATABASE_ROOT_PASSWORD'] %>
```

Kubernetes では Docker イメージからポッドを作成するので、new した Rails アプリケーションを Docker Registry に登録する必要があります。

GCP には Google Container Registry というサービスがあるので、ここにイメージを push したいと思います。

まずは Dockerfile を作成し、それを build して、イメージファイルを作成したいと思います

```dockerfile:Dockerfile
FROM ruby:2.3

ENV APP_ROOT="/app"

RUN \
    mkdir -p $APP_ROOT

RUN \
    apt-get update -qq && \
    apt-get install -y build-essential nodejs

RUN \
    bundle config --global build.nokogiri --use-system-libraries

WORKDIR $APP_ROOT

ADD . $APP_ROOT

ENV RAILS_ENV production
WORKDIR $APP_ROOT

ENTRYPOINT \
    bundle install && \
    bin/rails db:create && \
    bin/rails db:migrate && \
    bin/rake assets:precompile && \
    bin/rails server -p 3000 -b 0.0.0.0
```

build して、Google Container Registry にイメージを push します。

```shell-session
$ export PROJECT_ID="$(gcloud config get-value project -q)"
$ docker build -t gcr.io/${PROJECT_ID}/sampleapp:v1 .
$ gcloud docker -- push gcr.io/${PROJECT_ID}/sampleapp:v1
```

Google Container Registry にイメージを push できたので、rails のポッドをこのイメージファイルを適応したものに変更します。

```shell-session
$ kubectl set image deployment/rails rails=gcr.io/${PROJECT_ID}/sampleapp:v1
```

実行したあとに pod を確認すると次のように以前の pod が Terminating になり、新しいものが Running になるのが確認できるかと思います。

```
$ kubectl get pod
NAME                     READY     STATUS        RESTARTS   AGE
mysql-xxxxxxxxxx-xxxxx   1/1       Running       0          40m
rails-xxxxxxxxxx-xxxxx   1/1       Running       0          16s
rails-xxxxxxxxxx-xxxxx   1/1       Terminating   0          21m
```

新しい pod が Running になったらアプリケーションのデプロイが完了しているので、`http://<EXTERNAL-IP>:3000/users` にアクセスしてみましょう！

問題なく画面が表示されているのが確認できるはずです！

## クリーンアップ

課金されないように今回作成したリソースを全て削除します。

**削除しないとどんどん課金されていくので気をつけてください**

```shell-session
$ kubectl delete service rails
$ gcloud container clusters delete rails-cluster
$ gcloud compute disks delete mysql-data
```

## まとめ

今回は GCP の GKE を使い、Kubernetes を試してみました。

AWS の EKS の GA など盛り上がりを見せている Kubernetes ですが、弊社でも本番環境での Kubernetes の導入を見据えて絶賛検証中です。

実行環境を作成するだけであれば今回のようにそれほど難しくはないので是非みなさんも Kubernetes を試してみてください！！