はじめに
GKEにRailsアプリをデプロイした際の備忘録です。
やりたかったこと
- GKEにRailsアプリをリリースする
- Railsはnginxと連携させる
- DBにはCloudSQLを利用する
参考にしたサイト
構築にあたって以下の解説&Githubリポジトリを参考にさせていただきました。
- Deploying Rails on Kubernetes – Adwerx Engineering
- jbielick/rails-kube-demo: A sample application for development in docker-compose and deployment via Kubernetes (GKE)
GithubリポジトリのサンプルアプリはDBに接続するための設定は記述されてますが、Modelが作成されてなかったため、追加しました。
$ rails g scaffold User name:string age:integer
$ rake db:migrate
Rails.application.routes.draw do
resources :users # 追記
root to: 'users#index' # 変更
get '/_health', to: 'application#health'
end
注意点
解説内のコマンドはカレントディレクトリがリポジトリの直下になってることを前提にしてます。
$ git clone https://github.com/jbielick/rails-kube-demo.git
$ cd rails-kube-demo
利用したサービス
Google Container Service (GKE)
Podに2つのコンテナを配置する構成にしました。
raiisコンテナ
nginxとRailsアプリが配置されてるコンテナです。
Railsはpumaを利用せず、Phusion Passenger
を利用してnginxと連携します。
cloudsql-proxyコンテナ
CloudSQLと安全に接続するために利用するコンテナです。
Google Cloud SQL
GKEにデプロイしたRailsアプリのデータベースとして利用しました。
Railsアプリとはcloudsql-proxy
コンテナを経由して接続するようにしました。
プロジェクト
作成
Google Cloud Platform(GCP)のコンソールでプロジェクトを作成しておく。
API
作成したプロジェクトで以下のAPIを有効化しておく
- Google Container Engine API
- Google Cloud Container Builder API
- Google Cloud SQL API
Cloud SDK
インストール
以下のサイトを参考にCloud SDKのインストールと初期化を実施する。
クイックスタート | Cloud SDK | Google Cloud Platform
アップデート
Cloud SDKをインストール済みの場合は念のためアップデートしておく。
$ gcloud components update
バージョン確認
$ gcloud -v
Google Cloud SDK 170.0.1
bq 2.0.25
core 2017.09.08
gcloud
gsutil 4.27
kubectl
グローバル設定
gcloudコマンドからプロジェクト操作が行えるようにするために認証や設定を済ませておく。
参考:開始方法 | Container Engine | Google Cloud Platform
$ gcloud auth login
$ gcloud config set project [PROJECT_ID]
$ gcloud config set compute/zone [ZONE]
補足:
[PROJECT_ID] プロジェクトIDを指定
[ZONE] リージョンとゾーン(us-west1-a など)を指定
kubectl
GKEを利用するためにkubectl
が必要なためインストールしておく。
参考:開始方法 | Container Engine | Google Cloud Platform
インストール
$ gcloud components install kubectl
バージョン確認
$ kubectl version --short
Client Version: v1.7.5
CloudSQL
インスタンス作成
インスタンスを作成して、ユーザ、データベースを追加しておく。
参考:
インスタンスを作成する
ユーザーを作成する
データベースを作成する
サービスアカウント作成
サービスアカウントを作成して秘密鍵ファイル(json)をダウンロードしておく。
コンテナクラスタ
作成
$ gcloud container clusters create [CLUSTER-NAME] \
--machine-type=[MACHINE] \
--zone=[ZONE]
Creating cluster test-cluster...done.
Created [https://container.googleapis.com/v1/projects/xxxx-xxxx-xxxxxx/zones/us-west1-a/clusters/test-cluster].
kubeconfig entry generated for test-cluster.
NAME ZONE MASTER_VERSION MASTER_IP MACHINE_TYPE NODE_VERSION NUM_NODES STATUS
test-cluster us-west1-a 1.6.9 xx.xxx.xx.xx n1-standard-1 1.6.9 3 RUNNING
補足:
[CLUSTER-NAME] クラスタの名称(test-cluster など)を指定
[MACHINE] マシンタイプ(n1-standard-1 など)を指定
情報取得
$ gcloud container clusters list
$ gcloud container get-server-config
$ kubectl cluster-info
管理コンソール
認証情報のダウンロード
$ gcloud container clusters get-credentials [CLUSTER-NAME] \
--zone [ZONE] --project [PROJECT_ID]
Proxy起動
$ kubectl proxy
ブラウザで下記のURLにアクセスするとkubernetesの管理画面にアクセスできる
http://localhost:8001/ui
Secret
Rails
SECRET_KEY_BASEをSecretに設定
$ kubectl create secret generic rails --from-literal=secret-key-base=1d9b8645dba9b62317546565b43a4c485ee045e51c6f9bab65efcfdc69e97fde8030703e3f3912ef297e11fed30296441059d81f32ab23a13b8497158a35f654
補足:
SECRET_KEY_BASEの生成コマンド
$ bundle exec rake secret
1d9b8645dba9b62317546565b43a4c485ee045e51c6f9bab65efcfdc69e97fde8030703e3f3912ef297e11fed30296441059d81f32ab23a13b8497158a35f654
CloudSQL
インスタンスアクセスのためのSecretを設定
$ kubectl create secret generic cloudsql-oauth-credentials --from-file=credentials.json=[PATH_TO_CREDENTIAL_FILE]
補足:
[PATH_TO_CREDENTIAL_FILE] 秘密鍵ファイル(json)のパス
データベースアクセスのためのSecretを設定
$ kubectl create secret generic cloudsql --from-literal=username=[PROXY_USERNAME] --from-literal=password=[PASSWORD]
補足:
[PROXY_USERNAME] DBユーザ名を指定
[PASSWORD] DBパスワードを指定
コンテナイメージ
データベース設定
database
にはCloudSQLで作成したデータベース名を指定します。
host
,username
,password
を環境変数から取得するようにします。
database: xxxxx
host: <%= ENV['MYSQL_SERVICE_HOST'] %>
username: <%= ENV['MYSQL_USER'] %>
password: <%= ENV['MYSQL_PASSWORD'] %>
補足:
nginx+rails
コンテナで利用する環境変数名は事前にnginx/env.conf
に記述しておく必要があります。
ビルド
Container Builderを利用してコンテナのビルドを実施する。
ビルドに成功するとContainer Registryにイメージが登録される。
参考:既存の Docker ビルドのクイックスタート | Cloud Container Builder のドキュメント | Google Cloud Platform
$ gcloud container builds submit --tag gcr.io/[PROJECT_ID]/[IMAGE] .
...
------------------------------------------------------------------------------------------------------------------------------------------
ID CREATE_TIME DURATION SOURCE IMAGES STATUS
e9c6555c-e7ff-4dc8-bd12-8ac9d9d49c7d 2017-09-10T00:20:04+00:00 3M59S gs://xxxx-xxxx-xxxxxx_cloudbuild/source/1505002801.08-94f9c069c40644748a73c38b01428dc7.tgz gcr.io/xxxx-xxxx-xxxxxx/my-app (+1 more) SUCCESS
補足:
[IMAGE] Continer Registry上の管理名(my-app など)を指定
備考
ビルド中に以下のようなエラーが発生した場合はGemfileの編集とGemfile.lockの更新を行う。
...
Step 17/19 : RUN bundle exec rake assets:precompile
---> Running in cfabcec0d769
rake aborted!
TZInfo::DataSourceNotFound: tzinfo-data is not present. Please add gem 'tzinfo-data' to your Gemfile and run bundle install
/usr/local/rvm/gems/ruby-2.3.3/gems/activesupport-5.0.6/lib/active_support/railtie.rb:22:in `rescue in block in <class:Railtie>'
/usr/local/rvm/gems/ruby-2.3.3/gems/activesupport-5.0.6/lib/active_support/railtie.rb:19:in `block in <class:Railtie>'
/usr/local/rvm/gems/ruby-2.3.3/gems/railties-5.0.6/lib/rails/initializable.rb:30:in `instance_exec'
/usr/local/rvm/gems/ruby-2.3.3/gems/railties-5.0.6/lib/rails/initializable.rb:30:in `run'
/usr/local/rvm/gems/ruby-2.3.3/gems/railties-5.0.6/lib/rails/initializable.rb:55:in `block in run_initializers'
/usr/local/rvm/gems/ruby-2.3.3/gems/railties-5.0.6/lib/rails/initializable.rb:54:in `run_initializers'
/usr/local/rvm/gems/ruby-2.3.3/gems/railties-5.0.6/lib/rails/application.rb:352:in `initialize!'
/home/app/config/environment.rb:5:in `<top (required)>'
/usr/local/rvm/gems/ruby-2.3.3/gems/railties-5.0.6/lib/rails/application.rb:328:in `require'
/usr/local/rvm/gems/ruby-2.3.3/gems/railties-5.0.6/lib/rails/application.rb:328:in `require_environment!'
/usr/local/rvm/gems/ruby-2.3.3/gems/railties-5.0.6/lib/rails/application.rb:448:in `block in run_tasks_blocks'
/usr/local/rvm/gems/ruby-2.3.3/gems/sprockets-rails-3.2.1/lib/sprockets/rails/task.rb:62:in `block (2 levels) in define'
/usr/local/rvm/gems/ruby-2.3.3@global/gems/rake-12.0.0/exe/rake:27:in `<top (required)>'
/usr/local/rvm/gems/ruby-2.3.3/bin/ruby_executable_hooks:15:in `eval'
/usr/local/rvm/gems/ruby-2.3.3/bin/ruby_executable_hooks:15:in `<main>'
TZInfo::ZoneinfoDirectoryNotFound: None of the paths included in TZInfo::ZoneinfoDataSource.search_path are valid zoneinfo directories.
/usr/local/rvm/gems/ruby-2.3.3/gems/tzinfo-1.2.3/lib/tzinfo/zoneinfo_data_source.rb:180:in `initialize'
/usr/local/rvm/gems/ruby-2.3.3/gems/tzinfo-1.2.3/lib/tzinfo/data_source.rb:180:in `new'
/usr/local/rvm/gems/ruby-2.3.3/gems/tzinfo-1.2.3/lib/tzinfo/data_source.rb:180:in `create_default_data_source'
/usr/local/rvm/gems/ruby-2.3.3/gems/tzinfo-1.2.3/lib/tzinfo/data_source.rb:40:in `block in get'
/usr/local/rvm/gems/ruby-2.3.3/gems/tzinfo-1.2.3/lib/tzinfo/data_source.rb:39:in `synchronize'
/usr/local/rvm/gems/ruby-2.3.3/gems/tzinfo-1.2.3/lib/tzinfo/data_source.rb:39:in `get'
/usr/local/rvm/gems/ruby-2.3.3/gems/activesupport-5.0.6/lib/active_support/railtie.rb:20:in `block in <class:Railtie>'
/usr/local/rvm/gems/ruby-2.3.3/gems/railties-5.0.6/lib/rails/initializable.rb:30:in `instance_exec'
/usr/local/rvm/gems/ruby-2.3.3/gems/railties-5.0.6/lib/rails/initializable.rb:30:in `run'
/usr/local/rvm/gems/ruby-2.3.3/gems/railties-5.0.6/lib/rails/initializable.rb:55:in `block in run_initializers'
/usr/local/rvm/gems/ruby-2.3.3/gems/railties-5.0.6/lib/rails/initializable.rb:54:in `run_initializers'
/usr/local/rvm/gems/ruby-2.3.3/gems/railties-5.0.6/lib/rails/application.rb:352:in `initialize!'
/home/app/config/environment.rb:5:in `<top (required)>'
/usr/local/rvm/gems/ruby-2.3.3/gems/railties-5.0.6/lib/rails/application.rb:328:in `require'
/usr/local/rvm/gems/ruby-2.3.3/gems/railties-5.0.6/lib/rails/application.rb:328:in `require_environment!'
/usr/local/rvm/gems/ruby-2.3.3/gems/railties-5.0.6/lib/rails/application.rb:448:in `block in run_tasks_blocks'
/usr/local/rvm/gems/ruby-2.3.3/gems/sprockets-rails-3.2.1/lib/sprockets/rails/task.rb:62:in `block (2 levels) in define'
/usr/local/rvm/gems/ruby-2.3.3@global/gems/rake-12.0.0/exe/rake:27:in `<top (required)>'
/usr/local/rvm/gems/ruby-2.3.3/bin/ruby_executable_hooks:15:in `eval'
/usr/local/rvm/gems/ruby-2.3.3/bin/ruby_executable_hooks:15:in `<main>'
Tasks: TOP => environment
(See full trace by running task with --trace)
The command '/bin/sh -c bundle exec rake assets:precompile' returned a non-zero code: 1
ERROR
ERROR: build step "gcr.io/cloud-builders/docker@sha256:c803d6b936f86702a2a52e4601cf8387eb05d941cc9b3df030fad6b7362b7ce6" failed: exit status 1
------------------------------------------------------------------------------------------------------------------------------------------
ERROR: (gcloud.container.builds.submit) build b77a5051-7651-495c-8000-b2536d9654ca completed with status "FAILURE"
Gemfileの編集
# gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
gem 'tzinfo-data'
Gemfile.lockの更新(bundle installを実行)
$ rm Gemfile.lock
$ bundle install
Deployment&Service
マイグレーション
CloudSQLのマイグレーションを済ませておく
ローカルマシンから実施する場合はCloudSQLの承認済みネットワーク
にローカルマシンのIPアドレスを一時的に追加して実施する。
参考:外部アプリケーションから Cloud SQL に接続する
$ export MYSQL_SERVICE_HOST=xxx.xxx.xxx.xxx
$ export MYSQL_USER=xxxxx
$ export MYSQL_PASSWORD=xxxxx
$ rake db:migrate RAILS_ENV=production
デプロイ
Ingress(負荷分散)を利用しないyamlファイルを作成してデプロイします。
CloudSQLのproxyとの接続には127.0.0.1:3306
が利用されます。
固定値なのでMYSQL_SERVICE_HOST
の値はyamlファイルに直接記述してます。
MYSQL_USER
,MYSQL_PASSWORD
,SECRET_KEY_BASE
については事前に設定したSecretから取得するようにしてます。
# Deployment
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 1
template:
metadata:
labels:
name: my-app
spec:
containers:
- image: gcr.io/[PROJECT_ID]/[IMAGE]:latest
imagePullPolicy: Always
name: rails
env:
- name: MYSQL_SERVICE_HOST
value: 127.0.0.1:3306
- name: MYSQL_USER
valueFrom:
secretKeyRef:
name: cloudsql
key: username
- name: MYSQL_PASSWORD
valueFrom:
secretKeyRef:
name: cloudsql
key: password
- name: SECRET_KEY_BASE
valueFrom:
secretKeyRef:
name: rails
key: secret-key-base
- image: b.gcr.io/cloudsql-docker/gce-proxy:1.05
name: cloudsql-proxy
command: ["/cloud_sql_proxy", "--dir=/cloudsql",
"-instances=[INSTANCE_CONNECTION_NAME]=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
- name: cloudsql
mountPath: /cloudsql
volumes:
- name: cloudsql-oauth-credentials
secret:
secretName: cloudsql-oauth-credentials
- name: ssl-certs
hostPath:
path: /etc/ssl/certs
- name: cloudsql
emptyDir:
---
# Service
apiVersion: v1
kind: Service
metadata:
name: my-app
spec:
type: LoadBalancer
selector:
name: my-app
ports:
- port: 80
targetPort: 3000
$ kubectl create -f kube/deploy+service.yml
deployment "my-app" created
service "my-app" created
アクセス確認
EXTERNAL-IP
を確認して、ブラウザでアクセスする。
$ kubectl get service
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes xx.xx.xxx.x <none> 443/TCP 1h
my-app xx.xx.xxx.xx xx.xxx.xxx.xx 80:31466/TCP 1m
http://[EXTERNAL-IP]
削除
正常にアクセスできたらDeploymentとServiceを削除する。
$ kubectl delete service my-app
service "my-app" deleted
$ kubectl delete deployment my-app
deployment "my-app" deleted
Deployment & Service & Ingress
デプロイ
replicasとIngress(負荷分散)を利用するyamlファイルを作成してデプロイします。
# Deployment
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 3
template:
metadata:
labels:
name: my-app
spec:
containers:
- image: gcr.io/[PROJECT_ID]/[IMAGE]:latest
imagePullPolicy: Always
name: rails
env:
- name: MYSQL_SERVICE_HOST
value: 127.0.0.1:3306
- name: MYSQL_USER
valueFrom:
secretKeyRef:
name: cloudsql
key: username
- name: MYSQL_PASSWORD
valueFrom:
secretKeyRef:
name: cloudsql
key: password
- name: SECRET_KEY_BASE
valueFrom:
secretKeyRef:
name: rails
key: secret-key-base
- image: b.gcr.io/cloudsql-docker/gce-proxy:1.05
name: cloudsql-proxy
command: ["/cloud_sql_proxy", "--dir=/cloudsql",
"-instances=[INSTANCE_CONNECTION_NAME]=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
- name: cloudsql
mountPath: /cloudsql
volumes:
- name: cloudsql-oauth-credentials
secret:
secretName: cloudsql-oauth-credentials
- name: ssl-certs
hostPath:
path: /etc/ssl/certs
- name: cloudsql
emptyDir:
---
# Service
apiVersion: v1
kind: Service
metadata:
name: my-app
spec:
type: NodePort
selector:
name: my-app
ports:
- port: 3000
---
# Ingress
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: my-app
spec:
rules:
- http:
paths:
- path: /*
backend:
serviceName: my-app
servicePort: 3000
$ kubectl create -f kube/all.yml
deployment "my-app" created
service "my-app" created
ingress "my-app" created
アクセス確認
ADDRESS
を確認して、ブラウザでアクセスする。
(アクセスできるようになるまで5分程度かかります)
$ kubectl get ingress
NAME HOSTS ADDRESS PORTS AGE
my-app * xx.xxx.xx.xxx 80 4m
http://[ADDRESS]
削除
不要になったdeployment, service, ingress を削除する
deployment, service, ingress
$ kubectl delete deployment my-app
deployment "my-app" deleted
$ kubectl delete service my-app
service "my-app" deleted
$ kubectl delete ingress my-app
ingress "my-app" deleted
コンテナクラスタ
不要になったコンテナクラスタを削除する
$ gcloud container clusters delete [CLUSTER-NAME]
The following clusters will be deleted.
- [test-cluster] in [us-west1-a]
Do you want to continue (Y/n)? Y
Deleting cluster test-cluster...done.
Deleted [https://container.googleapis.com/v1/projects/xxxx-xxxx-xxxxxx/zones/us-west1-a/clusters/test-cluster].