Edited at

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

More than 1 year has passed since last update.


背景

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

herokuへの課金や移行先を考えるにあたり、折角なのでGoogle Container Engine (GKE)も試してみました。

その際ちょいちょい躓く所があったので、備忘録として手順を残しておきます。

なお、とりあえず動かすことにフォーカスしています。

参考にされる場合、productionとして必要なNginxやSSLなどの諸々は追加で設定してください。


前提


  • dockerコマンドはインストールされているものとします

  • 動くRailsアプリがあるとします


    • 無ければRails newしたものでも良いですが、DBとの通信を確認できた方が良いです


      • migrationファイルを適当に用意しましょう



    • MySQLで考えます



  • 無料枠がある(今まで使ったことがない)と考え、細かい設定は行いません

  • Webの画面で作れる所は頼ります

  • リージョンは東京です


Railsの設定

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

production:

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

MySQLで、ホスト/ユーザー名は外部から取ってくる仕様です。

はアプリに合わせたDB名に変更してください。

後ほど使いますので覚えておくか、忘れたらGCPの画面で都度確認しましょう。。


手順

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

ビバ無料枠。


GCPへの登録

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

https://cloud.google.com/

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


  • 個人/法人を 指定

  • 名前、住所、クレカの入力

  • 完了


$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を入力していれば終わります。

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

$ gcloud components update

$ gcloud components install kubectl

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

ここで認証、デフォルト値の設定を行います。

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

$ 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

$ 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に設定し、↓を実行しておきます。

$ 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>はアプリに合わせてください。

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を用意しました。

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なので反応が無いですが)。

$ 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ユーザー名を指定します。

$ 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があります。

$ 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で確認できます。

$ 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コンテナの設定を記述します。

このファイルを少し改変したものが以下です。

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

長いですが、やっていることはシンプルです。

プロジェクトに合わせて、↓の箇所を書き換える必要があります。

        - image: asia.gcr.io/<project-id>/example:latest

"-instances=<db-instance-id>=tcp:3306",

は、ブラウザからGCPを見に行きましょう。

-instances=$PROJECT:$REGION:INSTANCE=tcp:3306.

という形式です。

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への接続を実現しています。

さて、この設定ファイルの内容を反映させます。

↓のコマンドで作成したファイルを指定すると一発です。

$ kubectl create -f <file-path>

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

deployment "testapp" created

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

$ kubectl get pod

これのステータスが"Running"になればOKです。

CrashLoopBackOffやErrorなどになっていると失敗です。

↓のコマンドなどでログを見て原因を探ると早いです。

$ kubectl get pod

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

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

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

$ kubectl delete -f deployment.yaml


serviceの作成

ついにここまで来ました。

上記のdeploymentは、まだ外部へのアクセス手段がありません。

そこで、serviceというものを利用し、外部に公開するIPを貼ります。

↓に様々な設定が載っています。

http://kubernetes.io/docs/user-guide/services/

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

apiVersion: v1

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

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

$ kubectl create -f <file-path>

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

完了したら、以下のコマンドで確認をします。

作成したサービスにexternal IPが貼られたら完了です。

これは多少時間がかかるので、watchコマンドで更新しつつ、Twitterを眺めて待ちましょう。

$ kubectl get service

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



Yay! You’re on Rails!


上手くいってない……??

$ kubectl get pod

をして、状態を確認しましょう。

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

ログを見ると原因解決に役立ちます。

service-nameはweb, sql-proxyなど、deploymentファイルで指定したものです。

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

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

$ 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と比較してみたいと思いました。