Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

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コマンドラインツール)がインストールされていること ※インストールされていない場合はこちらを参照
  • GCPのプロジェクトは作成済みであること(GCPコンソールより作成可能)

Google Cloud SDKの認証

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

$ gcloud auth application-default login

Railsプロジェクトの内容

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

モデル

user.rb
class User < ApplicationRecord
  # カラムはemailとnameのみ
  validates :email, presence: true
  validates :name, presence: true
end

コントローラー

users_controller.rb
class UsersController < ApplicationController
  def index
    @user = User.all
  end
end

ビュー

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設定ファイル

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'] %>

ルーティング

routes.rb
Rails.application.routes.draw do
  root to: "users#index"
end

Dockerの内容

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

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
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のインストール

$ gcloud components install kubectl

gcloudコマンドラインツールのデフォルトを設定する

$ gcloud config set project [PROJECT_ID]
$ gcloud config set compute/zone asia-northeast1-a

※この例では、zoneはasia-northeast1-aに設定している。任意のzoneで構わない。

APIの有効化

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

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

Kubernetesクラスタの作成

コンテナクラスタを作成します。
後でGCPのレジストリにコンテナイメージを格納するのですが、このコンテナイメージを実行するのがコンテナクラスタになります。
作成には数分かかります。

$ gcloud container clusters create my-cluster \
                   --num-nodes 2 \
                   --machine-type n1-standard-1 \
                   --zone asia-northeast1-a

★注意!
既存のクラスタを使用する場合、又はGCPコンソールからクラスタを作成した場合は、以下のコマンドを実行する。

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

CloudSQLの設定

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

$ gcloud sql instances create --gce-zone asia-northeast1-a --database-version MYSQL_5_7 --memory 4 --cpu 2 インスタンス名

zoneやメモリ、CPU数は任意で構いません。

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

$ gcloud sql databases create DB名 --instance=インスタンス名

DBユーザーを作成する

DBユーザー作成と同時にパスワードも設定します。

$ gcloud sql users set-password DBユーザー名 \
--instance=インスタンス名 --password=DBパスワード

プロキシのインストール

CloudSQLにプロキシ経由で接続するため、プロキシをインストールします。

プロキシをダウンロード

# 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

実行権限を付与しておく。

$ chmod +x cloud_sql_proxy

サービスアカウント作成

ここでは、[test-user]という名前のアカウントを作成。

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

プロキシアカウントのメールアドレスを確認

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

$ gcloud iam service-accounts list

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

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

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

認証ファイル作成

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

DockerイメージをGCPのContainer RegistryにPushする

$ docker push gcr.io/プロジェクトID/rails_sample

DockerコンテナをGKEにデプロイ

クラスタの情報を確認

$ 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にアクセスするため、環境変数を設定する必要あります。
上記プロキシインストール時に作成した認証ファイルを読み込むことで簡単に作成できます。

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

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

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

マニフェストファイルを作成

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

# ファイルを作成。ファイル名は任意の名前でOK
$ vi deployment.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

デプロイ実行

$ kubectl create -f deployment.yml

デプロイ確認

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

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

サービスの公開

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

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

パブリックIPの確認

公開されるまで数分かかる場合があるので、以下コマンドは数分後に実行する。

$ kubectl describe services sample-app

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

DBのマイグレーション

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

本来は2.でやるべきだと思いますが、今回は1.で実行しました。

コンテナ接続する方法

# 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://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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした