LoginSignup
9
7

More than 5 years have passed since last update.

GKEに Rails + nginx(+Phusion Passenger ) + CloudSQL を利用したアプリをデプロイする

Last updated at Posted at 2017-09-10

はじめに

GKEにRailsアプリをデプロイした際の備忘録です。

やりたかったこと

  • GKEにRailsアプリをリリースする
  • Railsはnginxと連携させる
  • DBにはCloudSQLを利用する

参考にしたサイト

構築にあたって以下の解説&Githubリポジトリを参考にさせていただきました。

GithubリポジトリのサンプルアプリはDBに接続するための設定は記述されてますが、Modelが作成されてなかったため、追加しました。

$ rails g scaffold User name:string age:integer
$ rake db:migrate
config/routes.rb
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

kubernetes.png

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を環境変数から取得するようにします。

config/database.yml
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の編集

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から取得するようにしてます。

参考:container-engine-samples/cloudsql_deployment.yaml at master · GoogleCloudPlatform/container-engine-samples

kube/deploy+service.yml
# 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ファイルを作成してデプロイします。

kube/all.yml
# 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].
9
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
7