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

初心者でもできる!Cloud RunでRailsアプリをデプロイ【Rails/GCP】

はじめに

今回はGCPにおけるKnativeのマネージドサービスであるCloud Runを用いてRailsアプリをデプロイしてみたという内容です。

背景としては、コンテナデプロイを自力でやってみたかったのと、最近は実務でGCPに触れる機会が増えたことから、GCPのコンテナサービスを利用することにしたという感じです。

GCPにおけるコンテナサービスと言えば、真っ先にk8sが浮かんできますが、個人的に今年の4月のGoogle Cloud Next 2019で発表されたCloudRunに興味があったこともあり、今回はそちらを試すことにしました。

色々と試行錯誤して動く形にしましたが、最適解ではない可能性も十分にありますので、その点はご注意下さい!
また、有識者の方々からもっとこうした方がいいよ!といったコメントをいただければ幸いです!

Cloud Runとは?

公式から引用したものを一言で表現すると、「コンテナをサーバーレスで実行できる環境」といったところでしょうか。

Bringing serverless to containers
Cloud Run is a managed compute platform that automatically scales your stateless containers. Cloud Run is serverless: it abstracts away all infrastructure management, so you can focus on what matters most — building great applications. It is built from Knative, letting you choose to easily run your containers either fully managed with Cloud Run, or in your Google Kubernetes Engine cluster with Cloud Run on GKE.

使ってみた印象だと、Dockerイメージを用意してあげるだけで良く、k8sの複雑さを意識することがないので、デプロイするまでの労力がそれほど大きくなくて初学者の方でも十分に扱えるサービスだと思いました。

価格に関しても、リクエストを受けたときの実行時間に対して課金される形となっており、お遊び程度に使う分には大変安価に利用できるレベルなので大変お財布に優しいなと感じました。

なお、Cloud RunとCloud Run on GKEの2種類があるそうですが、今回はより安価なCloud Runの方を使っています。

Cloud Runへデプロイするまでの流れと今回の記事の内容

具体的に、本記事では以下の内容について触れます。

  1. 実務未経験時に転職活動用に作成したポートフォリオ(GitHub / サイト)をコンテナ化
  2. Cloud RegistoryにDockerイメージをpush
  3. Cloud SQLインスタンスおよびユーザー・データベースの作成
  4. ローカルでCloud SQL Proxyを使ってCloud SQLに接続
  5. Cloud Runにアプリケーションをデプロイ

ちなみに、今回の記事の続きとして、勉強のために、CircleCIでの自動デプロイ化や、プチデータ分析基盤の構築(Stackdriver→PubSub→Fluentd→BigQuery→re:dash)などにもちょこちょこ挑戦しているので、完成したら記事にまとめたいと思います💡

前提

  • macOS: Mojave 10.14.5
  • Ruby: 2.5.5
  • Rails: 5.2.3
  • Docker: 18.09.2
  • Cloud SDK: 254.0.0
  • デプロイするアプリケーション: ポートフォリオ(GitHub / サイト
    • 本記事ではMyCurryAppという名前で進めてます
  • 今回使用するGPCサービス

※Googleアカウントの作成、対象プロジェクトの作成、Cloud SDKのインストールなどは完了している前提で進めます

1. ポートフォリオのコンテナ化

Dockerfileの作成

まずはポートフォリオをコンテナで動かせるようにDockerfileを用意します。
特に凝った部分はなく、シンプルな感じにしてます。

Dockerfile
FROM ruby:2.5.5
ENV LANG C.UTF-8

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

RUN gem install bundler -v 2.0.2

WORKDIR /tmp
ADD Gemfile Gemfile
ADD Gemfile.lock Gemfile.lock
RUN bundle install

WORKDIR /MyCurryApp
COPY . /MyCurryApp

docker-compose.ymlの作成

次にdocker-compose.ymlを作成していきます。
Cloud Runへデプロイするだけの目的の場合、docker-composeは必要ないのですが、ローカル環境でDockerを使って普通に開発したい場合には必要なのと、コンテナが動くか確認したかったので作ってます。

最初ポートは3306:3306としていたのですが、上手く行かなかったので、今回はホスト側のポートを5000番にしておきました。5000番は適当な数字なので、3306番やRailsサーバーの3000番以外なら何でも良いです。

上手く行かなかった原因はよく理解できてないのですが、以前このポートフォリオを作成した際のDBとバッティングしてしまったのかなとか思ってます。(お詳しい方がいれば是非ご教示いただければ幸いです。)

docker-compose.yml
version: '3'
services:
  mysql:  # config/database.yml中のローカル環境のホスト名と揃える
    image: mysql:5.7
    command: mysqld --character-set-server=utf8 --collation-server=utf8_general_ci
    ports:
      - '5000:3306' # 3306:3306だと上手く行かなかったので5000:3306とした
    volumes:
      - mysql-data:/var/lib/mysql
    environment:
      MYSQL_DATABASE: MyCurryApp_development  # ローカル環境のDB名
      MYSQL_USER: root                        # ローカル環境のDBを使用する時に使うユーザー名
      MYSQL_PASSWORD: password                # ローカル環境のDBを使用する時に使うパスワード
  app:
    tty: true
    stdin_open: true
    build: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/MyCurryApp
    ports:
      - "3000:3000"
    depends_on:
      - mysql
volumes:
  mysql-data:
    driver: local

database.ymlの修正

docker-compose.ymlと対応するように書き換えていきます。

config/database.yml
default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: password
  host: mysql

development:
  <<: *default
  database: MyCurryApp_development

test:
  <<: *default
  database: MyCurryApp_test

# productionの設定については後述
production:
  <<: *default
  username: <%= ENV['MYSQL_USER'] %>
  password: <%= ENV['MYSQL_ROOT_PASSWORD'] %>
  database: <%= ENV['MYSQL_DATABASE'] %>
  host: <%= ENV['MYSQL_HOST'] %>
  socket: <%= ENV['MYSQL_SOCKET'] %>

ローカル環境で確認

ここまでできたらDockerイメージをビルドして、データベースを作成、seedデータを流し込んでコンテナを起動させます。

$ docker-compose build
$ docker-compose run app rails db:create
$ docker-compose run app rails db:seed
$ docker-compose up

そしてコンテナが立ち上がっているかを確認します。

$ docker-compose ps
       Name                     Command               State                 Ports
-----------------------------------------------------------------------------------------------
mycurryapp_app_1     start.sh bin/start               Up      0.0.0.0:3000->3000/tcp
mycurryapp_mysql_1   docker-entrypoint.sh mysql ...   Up      0.0.0.0:5000->3306/tcp, 33060/tcp

無事に立ち上がっているようです。

念の為、localhost:3000でブラウザでアクセスするとちゃんと表示されることが確認できました。
ただ、やや起動が遅かったりするのでその辺は今後詰めていきたいと思います。

image.png

2. Cloud RegistoryにDockerイメージをpush

さて、ここから徐々にCloud Runで本番環境にデプロイする準備をしていきます。

まずは、Cloud Registory(GCP上のDockerHub的なもの)にイメージをpushしていきます。

$ docker build -t my-curry-app .
$ docker tag my-curry-app gcr.io/<PROJECT_ID>/my-curry-app
$ docker push gcr.io/<PROJECT_ID>/my-curry-app

ここで、pushする際に以下のようなエラーが出た場合は、Cloud Registory 認証方法に従ってCloud Registoryを認証してあげて下さい。

unauthorized: You don't have the needed permissions to perform this operation, and you may have invalid credentials. To authenticate your request, follow the steps in: https://cloud.google.com/container-registry/docs/advanced-authentication

# 上記のようなエラーが出た場合は、下記コマンドで解決
$ gcloud auth configure-docker

GCPコンソールで確認すると、きちんと作成されていることが確認できます。

image.png

3. Cloud SQLインスタンスおよびユーザー・データベースの作成

続いて、本番環境用のMySQLを用意していきます。

まずはインスタンスを作成します。

$ gcloud sql instances create my-curry-app \
    --region us-central1 \      # 2019.7.21現在、CloudRunはUSリージョンでしか使えないのでMySQLもUSリージョンで使用するようにしておく
    --assign-ip \
    --tier db-f1-micro \
    --root-password <MYSQL_ROOT_PASSWORD>  # 好きなパスワードを設定

上手く行けば以下のようにインスタンスが作成されます。

image.png

次にデータベースとユーザーを作成します。

# データベースを作成、<MYSQL_DATABASE>の部分は任意の名前を設定
$ gcloud sql databases create <MYSQL_DATABASE> --instance my-curry-app

# ユーザーを作成、<MYSQL_USER>の部分は任意の名前を設定
$ gcloud sql users create <MYSQL_USER> --instance my-curry-app

データベースとユーザーがきちんと作成されたか確認したかったら以下のコマンドで確認可能です。

$ gcloud sql databases list --instance=[INSTANCE_NAME]
$ gcloud sql users list --instance=[INSTANCE_NAME]

4. Cloud SQLへの接続

Cloud SQLのインスタンスが作成できたので、次に、Cloud SQL Proxyを使ってローカルのターミナルでCloud SQLに接続&マイグレーションをしていきます。(Cloud SQLへの接続はCloud SQL Proxyを介して接続することが推奨されているとのことです。)

ここでやることのイメージとしては、Cloud SQL Proxy についてを見ると分かりやすいです。

image.png
(上図の参照元:Cloud SQL Proxy について

4-1. Cloud API Adminの有効化

GCPコンソールでCloud API Adminを有効化しておきます。

image.png

4-2. プロキシのインストール

僕のPCはMacなので、公式ドキュメントの「MACOS 64 ビット」の部分を見ながら進めて行きます。

$ cd
$ curl -o cloud_sql_proxy https://dl.google.com/cloudsql/cloud_sql_proxy.darwin.amd64
$ chmod +x cloud_sql_proxy

また、Rails アプリケーションを Cloud Run にデプロイするを参考に、ローカルのルートディレクトリにcloudsqlという名前のフォルダを作成しておきます。

$ cd /
$ sudo mkdir /cloudsql
$ sudo chmod 777 /cloudsql

この状態で./cloud_sql_proxy -dir /cloudsqlを実行してもエラーが出たのでエラーの内容を追っていき、色々と試した結果、次に説明するstart.shというファイルを用意したら解決したので説明していきます。

4-3. start.shの作成

Cloud Runを使うならこれだけ知ってると得した気分になるを参考に以下のシェルスクリプトを作成しました。

このシェルスクリプトを用意する意図としては、

ここで大事なのはPORTという環境変数があったら、そのポートでRailsを起動していること。
Cloud RunではPORTという環境変数が渡ってきて、そのポートで起動してるかのチェックが行われます。なので指定されたポートで起動する必要があるため、このような記載になっています。

とのことです。

このファイルなしでデプロイしようとするとポートが云々で怒られたので、きっとここでポートを指定おいてあげることが重要なのでしょう。

また、この中でbundle exec rake assets:precompile RAILS_ENV=productionを実行してあげないとCSSが反映されなかったという背景があり、assets precompileの記述をしています。

start.sh
#!/bin/sh
RAILS_PORT=3000
if [ -n "$PORT" ]; then
  RAILS_PORT=$PORT
fi

# migration
bin/rails db:migrate RAILS_ENV=production

# assets precompile
bundle exec rake assets:precompile RAILS_ENV=production

# Remove a potentially pre-existing server.pid for Rails.
rm -f tmp/pids/server.pid

bin/rails s -p $RAILS_PORT -b 0.0.0.0

4-4. Dockerfileの修正

Dockerイメージをビルドする際に、start.shが毎度実行されるように、上記で作成したDockerfileの最後尾に以下を追記します。

Dockerfile
・
・
・
# 以下を追記
COPY start.sh /usr/bin/
RUN chmod +x /usr/bin/start.sh
ENTRYPOINT ["start.sh"]
EXPOSE 3000

CMD ["bin/start"]

4-5. Cloud SQL ProxyによるMySQLへの繋ぎ込み

Cloud SQL Proxyを起動してローカルのターミナルから本番のMySQLにアクセスできるようにしていきます。

$ ./cloud_sql_proxy -dir /cloudsql

起動したらターミナルの別タブを開いて必要な環境変数を注入していきます。
<MYSQL_HOST>にはlocalhostのようなものを、<MYSQL_SOCKET>には/cloudsql/[Cloud SQLのインスタンス名]のような形で指定してあげると上手く行きました。

$ export RAILS_ENV=production
$ export MYSQL_DATABASE=<MYSQL_DATABASE>
$ export MYSQL_HOST=<MYSQL_HOST>
$ export MYSQL_ROOT_PASSWORD=<MYSQL_ROOT_PASSWORD>
$ export MYSQL_SOCKET=<MYSQL_SOCKET>
$ export MYSQL_USER=<MYSQL_USER>  # ここがrootだと上手く行かなかったです

ここまで来れば、残すはCloud Runでデプロイするだけになります!

(余談)4-7.docker-compose.ymlの修正

Cloud Runでのデプロイとは関係ないですが、Dockerfileとstart.shを編集・作成したことで設定がやや変わり、そのままの設定ではローカル環境で動かなくなるためdocker-compose.ymlにも修正を加えておきます。

修正点としては、apprm -f tmp/pids/server.pidbin/rails s -p 3000 -b 0.0.0.0の部分です。
start.shで実行するようにしたので重複となりエラーが発生するようになるのでコメントアウトしておきます。

docker-compose.yml
   ・
   ・
   ・
  app:
    tty: true
    stdin_open: true
    build: .
    # command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/MyCurryApp
    ports:
      - "3000:3000"
    depends_on:
      - mysql
   ・ 
   ・
   ・

5. Cloud Runでアプリケーションをデプロイ

いよいよCloud Runでアプリケーションをデプロイしていきます。
以下のコマンドを実行すればデプロイされます。

$ gcloud beta run deploy mycurryapp \
   --project <Project ID> \
   --image gcr.io/mycurryapp/my-curry-app \
   --add-cloudsql-instances mycurryapp:us-central1:my-curry-app \
   --allow-unauthenticated  \
   --region us-central1 \
   --memory 1000Mi \
   --set-env-vars RAILS_ENV=production \
   --set-env-vars RAILS_MASTER_KEY=b21bb0d9a126145ab705ace96208344d \
   --set-env-vars MYSQL_SOCKET=/cloudsql/<Project ID>:us-central1:<CloudSQLのインスタンス名> \
   --set-env-vars MYSQL_HOST=<MYSQL_HOST> \
   --set-env-vars MYSQL_DATABASE=<MYSQL_DATABASE> \
   --set-env-vars MYSQL_USER=<MYSQL_USER> \
   --set-env-vars MYSQL_ROOT_PASSWORD=<MYSQL_ROOT_PASSWORD>

Deploying container to Cloud Run service [mycurryapp] in project [<PROJECT_ID>] region [us-central1]
✓ Deploying... Done.
  ✓ Creating Revision...
  ✓ Routing traffic...
  ✓ Setting IAM Policy...
Done.
Service [mycurryapp] revision [mycurryapp-00001] has been deployed and is serving traffic at https://mycurryapp-qyobofecda-uc.a.run.app

GCPコンソールで確認してもちゃんとデプロイできてそうです!

image.png

なお、512MB以下ではメモリ不足で500エラーになってしまったので、今回はメモリを1GBに設定しています。
image.png

最後にseedデータを流し込みます。

$ bundle exec rails db:seed

https://mycurryapp-qyobofecda-uc.a.run.app にアクセスしてみると無事にデプロイされていることが確認できました!

image.png

本番での動作確認もしてみて、基本的に問題はなかったのですが、唯一Twitter認証ができなくなってしまったので、それについては追々調査・修正していこうと思います💡

Cloud Runを使ってみた感想

  • Cloud Runにデプロイするまでの道のり(Cloud SQL ProxyでCloud SQLへの接続&マイグレーションするところ)でやや躓きましたが、Cloud Run自体は特にハマることもなく扱えたので、コンテナをデプロイできるサービスとしては大変使いやすいサービスなのではないかと思いました!
  • レスポンスはやや遅い気もしますが、元々Herokuの無料プラン上で動いていたものをCloud Runに移行した感じなのでそこまで変化はないかもしれません。
  • 安価・簡便なので、勉強用として使うには十分なのではないでしょうか!

参考URL

その他

今回の記事の続きとして、勉強のために、CircleCIでの自動デプロイ化や、プチデータ分析基盤の構築(Stackdriver→PubSub→Fluentd→BigQuery→re:dash)などにもちょこちょこ挑戦しているところなので、完成したら記事にまとめたいと思います💡

また、今回はCloudRunでコンテナデプロイをしてみましたが、近いうちに下記記事を参考にしながらk8sにもトライしてみたいと思います!

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
ユーザーは見つかりませんでした