Google App EngineのRuby/スタンダード環境を試してみた

# はじめに

Google App Engineスタンダード環境でRubyがサポートされました。
https://cloud.google.com/appengine/docs/standard/ruby/?hl=ja
サポートされたんだよね、本記事執筆時点（2019/9/9）で「ベータ版です」とか書いてあるけどドキュメントが追い付いてない？

まあそれはともかくGAEスタンダード環境とあれば私の出番だということで（？）、GAEスタンダード環境でのRubyの使い方について見ていきたいと思います。見ていくポイントは以下の3点です。

- 依存ライブラリはどうやって入れる？
- DatastoreとかのGCPサービス使うAPI（Rubyライブラリ）ある？
- Rails動かせる？

# 依存ライブラリの指定方法

まずは依存ライブラリをどうやって入れるのかです。
結論から先に言うとGemfileを書いておけば普通にBundlerを使ってインストールしてくれます。ここら辺はPythonやNode.jsと同じですね。

[Quickstart](https://cloud.google.com/appengine/docs/standard/ruby/quickstart?hl=ja)で紹介されている[Hello World app](https://github.com/GoogleCloudPlatform/ruby-docs-samples/tree/master/appengine/standard-hello_world)（Sinatraで書かれています）を見るとGemfileで依存ライブラリが指定されていることが確認できます。

## 起動方法と待ち受けるポート

起動するプログラムはapp.yamlに`entrypoint`で指定します。先のHello World appの場合は以下のようになっています。

```yaml:app.yaml
runtime: ruby25
entrypoint: bundle exec ruby app.rb
```

このアプリの場合、Sinatraデフォルトのサーバ起動が行われ、Sinatraしか入れてないので標準ライブラリのWEBrickがサーバとして使われます。

production環境でWEBrickはあかんやろm9(^Д^)ﾌﾟｷﾞｬｰ、という場合はすぐ横にHello WorldアプリをPuma（Rack）使って起動している例があります[^1]。
こちらを見ると待ち受けるポートは環境変数`PORT`で渡されてくることがわかります[^2]。またこの例を見るとstaticファイルはGAEにserveさせる方法もわかります。
https://github.com/GoogleCloudPlatform/ruby-docs-samples/tree/master/appengine/standard-hello_configs

[^1]: ここで起動しているアプリサーバが直接リクエストを受けるわけではなくロードバランサがいてリクエストがフォワードされるわけですが、まあWEBrickよりはPumaの方がいいですよね。
[^2]: 素のSinatraの場合は環境変数PORTを見ているのでentrypointで指定はされていませんでした。


# GCPサービスのAPI

Rubyがスタンダード環境で動かせると言ってもGCPの各種サービスにアクセスできなければ意味がない、まあ究極的にはWeb APIなので自力で書くことはできますがライブラリがあるなら使いたい。
はい、あります。これも公式サンプルに用意されています。フレキシブル環境使ってますがスタンダード環境を使うように直すのは容易です。
https://github.com/GoogleCloudPlatform/ruby-docs-samples/tree/master/appengine/datastore

app.yamlを以下のように直して`gcloud app deploy`するだけです。

```yaml:app.yaml
runtime: ruby25
#env: flex
entrypoint: bundle exec ruby app.rb
```


# Railsの動かし方

SinatraはいいからRailsはどう動かすんだという声が聞こえてきそうですが、あらかじめ言っておくと結構大変です(笑)

Railsなんだから当然データベース使いますよね。
ここで「無課金で動かす」は諦めてください。
GCPにはCloud SQLというRDBMS（MySQL or PostgreSQL）動かすサービスが提供されていますがお金かかります。
Cloud SQLはGCEのインスタンス立ててDB専用にするみたいな感じ、値段はそこそこのお値段になります[^3]。

[^3]: MySQLはインスタンスのスペックを落とせますがPostgreSQLは最低スペックがメモリ3.75GBのようです。asia-northeastだと$64/月かかります。

現時点ではフレキシブル環境のサンプルしかありません。スタンダード環境とフレキシブル環境の違いがわかっていればこれを参考にして動かすことは可能です。ということで以下その方法です。
https://cloud.google.com/ruby/rails/using-cloudsql-postgres

## Rails / GAEスタンダード環境デプロイ方法

基本的に上記のチュートリアルに沿って話を進めていきます。

### 1. Cloud SQLインスタンスを作成する

先に説明した、Railsで使うデータベースを作成します。
インスタンス作成→データベース作成の順番になります。インスタンス作成時のpostgresユーザパスワードはもちろん覚えておくように。

### 2. Railsアプリの用意

私は普段Windowsで生きてるのですがデプロイできるバージョン構成が作れなかったので結局Cloud ShellでRailsアプリを作成・デプロイを行いました。
2019/9/9時点でRailsは6.0.0が入っているのですが6.0.0で作ってデプロイしたところ動かせなかった[^4]ので以下のようにGemfile書いて5.2.3入れて5.2.3でアプリを作りました。

[^4]: tmp/pids/server.pidがない、ていうか、tmp/pidsがないというエラー。もう少し調べれば動かせるのかもしれませんが。

```rb:Gemfile
source 'https://rubygems.org'

gem 'rails', '5.2.3'
```

```
$ bundle install
$ bundle exec rails new cat_sample_app
```

以降、scaffoldしてroute.rbを設定するところは普通のRailsアプリ通りです。

### 3. GAE用の設定を書く

チュートリアルにあるようにpgとappengineのgemを足します。

```
$ bundle add pg
$ bundle add appengine
```

次にconfig/database.ymlのproductionを設定します。「インスタンス接続名」はコマンドか管理画面で確認できます。
パスワード直書きとかm9(^Д^)ﾌﾟｷﾞｬｰという方は環境変数`DATABASE_URL`で指定できると思うので（未検証）、そちらで指定してください。ちなみに「/cloudsql/インスタンス接続名」とあるように接続はUNIXドメインソケットで行われます（実際にApp EngineとDBが同じマシン内で動くわけではないと思うのですが）

```yaml:config/database.yml
production:
  adapter: postgresql
  encoding: unicode
  pool: 5
  timeout: 5000
  username: "postgres"
  password: "パスワード"
  database: "cat_list_production"
  host:   "/cloudsql/インスタンス接続名"
```

チュートリアルにはありませんが（フレキシブル環境だと必要ない？）、これ以外にGemfileでRubyのバージョンを指定しているのをコメントアウトする必要がありました。
スタンダード環境は2.5.5なので、それ以外が書いてあるとデプロイ時にエラーになります。

```rb:Gemfile先頭
source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }

#ruby '2.6.3'
```

### 4. app.yamlを書く

チュートリアルに書かれているのはフレキシブル環境用のapp.yamlなので、これをスタンダード環境用に書き換えます。`cloud_sql_instances`は必要ありません。

```yaml:app.yaml
entrypoint: bundle exec rackup --port $PORT
runtime: ruby25

env_variables:
  SECRET_KEY_BASE: 「bundle exec bin/rails secret」の結果
```

### 5. .gcloudignoreを用意する

.gcloudignoreはデプロイしないファイルを指定するものです。これがない場合ドット始まりのファイルが送信されないという仕様？なのかsprocketsが正常動作しませんでした。

```
.gcloudignore
#!include:.gitignore
.git
.gitignore

!/public/assets
```

### 6. デプロイ（まだ終わりじゃないよ）

App Engineアプリを作り、アセットをコンパイルし、デプロイします。チュートリアルでは--no-promoteが付いていますが初デプロイの場合は結局それがメインのバージョンになるので違いはありません。

```
$ gcloud app create
$ bundle exec bin/rails assets:precompile
$ gcloud app deploy
```

### 7. データベースマイグレーション

上で一度デプロイをしているのはCloud Buildのサービスアカウントを作るためです。Cloud Buildとは何ぞやということは末尾の参考記事をご参照ください。

プロジェクトIDだったりプロジェクト番号だったりでうっとうしいですが以下のようにして必要な権限を追加します。

```
$ gcloud projects add-iam-policy-binding プロジェクトID --member=serviceAccount:プロジェクト番号@cloudbuild.gserviceaccount.com --role=roles/editor
```

さてとデータベースマイグレーションです。ここで先ほど入れたappengine gemが出てきます。
何をしているのかというと、テンポラリのapp.yamlを作って、entrypointに指定されたコマンドを書いて実行して、デプロイされたテンポラリのバージョンを消すという超絶なことがされています(笑)
このappengine gemが2.5で追加されたメソッド使っているので[^5]、デプロイする環境のRubyも2.5以上である必要があります。

[^5]: 具体的にはSecureRandom.alphanumeric。他にもあるかもしれません。

```
$ bundle exec rake appengine:exec -- bundle exec rake db:migrate
```

以上でデプロイは終了です。

### 二回目以降のデプロイ

二回目からは以下のコマンドを打てばデプロイが行えます。

```
$ bundle exec bin/rails assets:precompile
$ gcloud app deploy
$ bundle exec rake appengine:exec -- bundle exec rake db:migrate
```


# おわりに

以上、Google App Engineのスタンダード環境でRubyを使う場合を見てきました。
Node.jsと大体同じでした（広く使われている依存ライブラリの指定方法、起動プログラムは自分で指定、ポートは環境変数PORT）

触ってみた感想としては、Rails動かすのは特に無課金で使うつもりだったのなら諦めてHerokuを使え、GAE使うならDBはDatastore使ってフレームワークはSinatra使うのが楽だぞというところです。

# 参考というかこちらもどうぞの記事

[Google App EngineのNode.js/Standard環境の仕組みについて調べてみた](https://qiita.com/junjis0203/items/c3779e7e488ae547fb5a)
Node.jsがスタンダード環境にデプロイされる仕組み（デプロイプロセスは何を行っているのか）を調べた記録です。Rubyでも大体同じ仕組みでデプロイが行われています。

[GoogleAppEngineでDjangoを3年ほど動かしてわかったこと](https://qiita.com/junjis0203/items/ca1e69c3ddc5ea0871a8)
思い出話ですがこの記事と関連する箇所としてはCloud SQLの話があります。