はじめに
今回は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へデプロイするまでの流れと今回の記事の内容
具体的に、本記事では以下の内容について触れます。
- 実務未経験時に転職活動用に作成したポートフォリオ(GitHub)をコンテナ化
- Cloud RegistryにDockerイメージをpush
- Cloud SQLインスタンスおよびユーザー・データベースの作成
- ローカルでCloud SQL Proxyを使ってCloud SQLに接続
- Cloud Runにアプリケーションをデプロイ
(2020.3.20更新)
1.についてはメンテナンスしなくなったのでCloudRunアプリ毎クローズしました。
前提
- 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を用意します。
特に凝った部分はなく、シンプルな感じにしてます。
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とバッティングしてしまったのかなとか思ってます。(お詳しい方がいれば是非ご教示いただければ幸いです。)
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と対応するように書き換えていきます。
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
でブラウザでアクセスするとちゃんと表示されることが確認できました。
ただ、やや起動が遅かったりするのでその辺は今後詰めていきたいと思います。
2. Cloud RegistryにDockerイメージをpush
さて、ここから徐々にCloud Runで本番環境にデプロイする準備をしていきます。
まずは、Cloud Registry(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 Registry 認証方法に従ってCloud Registryを認証してあげて下さい。
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コンソールで確認すると、きちんと作成されていることが確認できます。
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> # 好きなパスワードを設定
上手く行けば以下のようにインスタンスが作成されます。
次にデータベースとユーザーを作成します。
# データベースを作成、<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 についてを見ると分かりやすいです。
(上図の参照元:Cloud SQL Proxy について)
4-1. Cloud API Adminの有効化
GCPコンソールでCloud API Adminを有効化しておきます。
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の記述をしています。
#!/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の最後尾に以下を追記します。
・
・
・
# 以下を追記
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にも修正を加えておきます。
修正点としては、app
のrm -f tmp/pids/server.pid
とbin/rails s -p 3000 -b 0.0.0.0
の部分です。
start.shで実行するようにしたので重複となりエラーが発生するようになるのでコメントアウトしておきます。
・
・
・
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コンソールで確認してもちゃんとデプロイできてそうです!
なお、512MB以下ではメモリ不足で500エラーになってしまったので、今回はメモリを1GBに設定しています。
最後にseedデータを流し込みます。
$ bundle exec rails db:seed
https://mycurryapp-qyobofecda-uc.a.run.app にアクセスしてみると無事にデプロイされていることが確認できました!
本番での動作確認もしてみて、基本的に問題はなかったのですが、唯一Twitter認証ができなくなってしまったので、それについては追々調査・修正していこうと思います💡
Cloud Runを使ってみた感想
- Cloud Runにデプロイするまでの道のり(Cloud SQL ProxyでCloud SQLへの接続&マイグレーションするところ)でやや躓きましたが、Cloud Run自体は特にハマることもなく扱えたので、コンテナをデプロイできるサービスとしては大変使いやすいサービスなのではないかと思いました!
- レスポンスはやや遅い気もしますが、元々Herokuの無料プラン上で動いていたものをCloud Runに移行した感じなのでそこまで変化はないかもしれません。
- 安価・簡便なので、勉強用として使うには十分なのではないでしょうか!
参考URL
- Rails アプリケーションを Cloud Run にデプロイする
- Cloud Runを使うならこれだけ知ってると得した気分になる
- Cloud SDK
- Cloud Registry
- Cloud SQL
- Cloud Run
- Cloud Run Pricing
- Cloud SQL Proxy を使用して MySQL クライアントを接続する
その他
今回の記事の続きとして、勉強のために、CircleCIでの自動デプロイ化や、プチデータ分析基盤の構築(Stackdriver→PubSub→Fluentd→BigQuery→re:dash)などにもちょこちょこ挑戦しているところなので、完成したら記事にまとめたいと思います💡
また、今回はCloudRunでコンテナデプロイをしてみましたが、近いうちに下記記事を参考にしながらk8sにもトライしてみたいと思います!