Sinatra で試す Google App Engine / Ruby


さて、GCP Next 2016で発表があって、Google App Engine で ruby がサポートされましたね。

- [［速報］Google App EngineがRubyとNode.jsのサポートを発表。GCP Next 2016](http://www.publickey1.jp/blog/16/google_app_enginerubynodejsgcp_next_2016.html)

ざっと簡単に試してみたので共有しておきます。

# Google Cloud Platformのプロジェクトの準備

## App Engine のリージョンはUSにする

今のところ、USにしなければならないようです。ヨーロッパではダメ。デフォルトはたぶん、us-central なので問題はないでしょう。

![スクリーンショット 2016-03-26 11.33.56.png](https://qiita-image-store.s3.amazonaws.com/0/17911/d13597bf-300c-fd51-fb6f-b62347726f9a.png "スクリーンショット 2016-03-26 11.33.56.png")

## billing を有効にする

だいぶ安価ですし、ほぼアカウント作成初期に提供されるクーポン範囲内だと思いますが、課金有効にしないと試せません。

Google App Engine Flexible Environment はつまるところ、「8080ポートでhttpリクエストをさばくDockerコンテナを<アプリケーション>として扱う、オートスケール、Docker Engine、ロードバランサ、プロファイラのセット」です。Docker Engineは普通にCompute Engine 上で実行されるインスタンスです。したがって、Compute Engine 一台分は基本的に費用がかかってきます。


# ローカルにGoogle Cloud SDKをインストール

[公式サイト](https://cloud.google.com/sdk/?hl=ja#download)通りにインストールします。

```bash
$ curl https://sdk.cloud.google.com | bash
$ exec -l $SHELL
$ gcloud init
```


すでにインストール済みの場合、念のためにコンポーネントをアップデートしておきましょう。　

```bash
$ gcloud components update


Your current Cloud SDK version is: 99.0.0
You will be upgraded to version: 102.0.0

┌─────────────────────────────────────────────────────┐
│          These components will be updated.          │
├──────────────────────────────┬────────────┬─────────┤
│             Name             │  Version   │   Size  │
├──────────────────────────────┼────────────┼─────────┤
│ BigQuery Command Line Tool   │     2.0.24 │ < 1 MiB │
│ Cloud SDK Core Libraries     │ 2016.03.22 │ 3.9 MiB │
│ gcloud app Python Extensions │     1.9.34 │ 7.2 MiB │
└──────────────────────────────┴────────────┴─────────┘

...... 略 .....


Update done!
```

# アプリケーション作成

## app.yaml を作成

以下のような app.yaml を作成します。app.yaml は App Engine の定義ファイルです。拡張子は慣習的に「.yml」ではなく 「.yaml」です。

```yaml
runtime: ruby
vm: true
entrypoint: bundle exec rackup -p 8080 -E production config.ru

resources:
  cpu: .5
  memory_gb: 1.3
  disk_size_gb: 10

automatic_scaling:
  min_num_instances: 1
  max_num_instances: 5
  cool_down_period_sec: 60
  cpu_utilization:
    target_utilization: 0.5
```

なお、app.yaml　がない状態で `gcloud preview app deploy` しようとすると、そのディレクトリの状態を見て自動的に app.yaml を作ってくれます。手元で動作確認した感じ、Gemfileと config.ru 　が置いてあれば ruby じゃないかな? って感じで app.yaml を生成してくれました。

## Gemfile と bundle install

ここでは最高に簡素な Sinatra アプリを作りますので、以下のような Gemfile を用意して

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

gem 'sinatra'
```

インストールします。

```bash
bundle install
```

## config.ru を作成

簡単のために config.ru で直接アプリケーションを作成します。

```ruby:config.ru
require 'sinatra/base'

class App < Sinatra::Base
  get '/' do
    "hello sinatra"
  end
end

run App.new
```

## ファイルレイアウト

```bash
$ tree
.
├── Gemfile
├── Gemfile.lock
├── app.yaml
└── config.ru
```

こうなっています。


# デプロイ

app.yaml のあるディレクトリで `gcloud preview app deploy` コマンドを実行することで、デプロイできます。

特にオプションを設定しなければ、このコマンドは

1. 新しいバージョンをデプロイする
2. 新しいバージョンにリクエストを割り振る
3. 全てのトラフィックが新しいバージョンのアプリケーションに向いたら古いバージョンのインスタンスを破棄する

という動作をします。この動作は `--promote` または `--no-promote` オプションで変更できます。詳しくは `gcloud preview app deploy --help` を参照してください。

## デプロイしてみる

最初に出てるワーニングは preview 状態のコマンド使ってるけどちゃんとドキュメント見ないとダメよという警告ですのでここはスルーします。

```bash
$ gcloud preview app deploy

WARNING: The `gcloud preview app` surface is rapidly improving. Look out for
changing flags and new commands before the transition out of the `preview`
component. These changes will be documented in the Cloud SDK release notes
<https://goo.gl/X8apDJ> and via deprecation notices for changing commands.

If you would like to avoid changing behavior, please pin to a fixed version of
the Google Cloud SDK as described under the "Alternative Methods" section of the
Cloud SDK web site: <https://cloud.google.com/sdk/#alternative>.

You are about to deploy the following modules:
 - hogehoge-funfun/default (from [/Users/ma2saka/tech-dev/default/app.yaml])
     Deployed URL: [https://hogehoge-funfun.appspot.com]

Do you want to continue (Y/n)?  Y
```

最初のデプロイの場合、

```
If this is your first deployment, this may take a while...-
```

でだいぶ待たされます。App Engine のロードバランサをセットアップしたり、Docker Engine をセットアップしたりといろいろ時間がかかるのでしょう。気長に待ちましょう。

少しすると、以下のようにビルドのログが流れてきます。

```bash

... 略 ...

Verifying that Managed VMs are enabled and ready.
Building and pushing image for module [default]
Started cloud build [55XXa2XX-fXXXX-XX-XXXXX-7XXcbXXXXXX4].
To see logs in the Cloud Console: https://console.developers.google.com/logs?project=hogehoge-funfun&service=cloudbuild.googleapis.com&key1=55XXa2XX-fXXXX-XX-XXXXX-7XXcbXXXXXX4&logName=projects/hogehoge-funfun/logs/cloudbuild
----------------------------- REMOTE BUILD OUTPUT -----------------------------
starting build "55XXa2XX-fXXXX-XX-XXXXX-7XXcbXXXXXX4"

FETCHSOURCE
Fetching storage object: gs://staging.hogehoge-funfun.appspot.com/hogehoge-funfun.default.20160326t114542#1458960451575000
Copying gs://staging.hogehoge-funfun.appspot.com/hogehoge-funfun.default.20160326t114542#1458960451575000...
Downloading file:///tmp/source-archive.tgz:                      0 B/1.23 KiB  Downloading file:///tmp/source-archive.tgz:                      1.23 KiB/1.23 KiB
FETCHBUILDER
Using default tag: latest

... 略 ...
```

Dockerのビルドをしているんですが、これ、[Google Cloud Container Builder](https://cloud.google.com/container-builder/docs/) で実行されているのがわかります。これは手元にイメージを持ってきてビルドしてアップロードする方式と比べて圧倒的に通信回線に優しくて嬉しいです。デザリング勢としては。

```bash
33ea738d3459: Image successfully pushed
50f67d2b5587: Pushing
50f67d2b5587: Image successfully pushed
f76fbdd2abab: Pushing
f76fbdd2abab: Image successfully pushed
Pushing tag for rev [f76fbdd2abab] on {https://appengine.gcr.io/v1/repositories/gcloud/hogehoge-funfun.default.20160326t114542/tags/latest}
DONE
-------------------------------------------------------------------------------

Updating module [default]...done.
Deployed module [default] to [https://hogehoge-funfun.appspot.com]
```

デプロイが終わりました。最終的に、Docker のコンテナイメージが gcr.io にアップロードされています。

## 動作確認

最後にアクセスするためのURLが表示されるので、叩いてみましょう。

```bash
$ curl -i https://hogehoge-funfun.appspot.com
HTTP/1.1 200 OK
Content-Type: text/html;charset=utf-8
X-Xss-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
Via: inde199:14908
Date: Sat, 26 Mar 2016 03:00:38 GMT
Server: Google Frontend
Content-Length: 13
Alternate-Protocol: 443:quic,p=1
Alt-Svc: quic=":443"; ma=2592000; v="31,30,29,28,27,26,25"

hello sinatra
```

動いたー。

Sinatraが動くということはだいたいRailsも動きそうです。というか動きます。実際に[公式のGetting Started with Ruby](https://cloud.google.com/ruby/)のサンプルアプリは普通に Rails アプリになっています。

- https://github.com/GoogleCloudPlatform/getting-started-ruby

## コード修正して再アップロードする

状態の更新と環境変数の確認をしてみる。

```diff
git diff
diff --git a/config.ru b/config.ru
index e042580..a2486fc 100644
--- a/config.ru
+++ b/config.ru
@@ -3,7 +3,7 @@ require 'sinatra/base'

 class App < Sinatra::Base
     get '/' do
-        "hello sinatra"
+        ENV.collect {|x| "#{x[0]} = #{x[1]}"} .join("\n")
     end
 end
```

あとはデプロイして、動きを見てみます。

```bash
$ gcloud preview app deploy
```

で、結果。
 
```bash
$ curl -s https://hogehoge-funfun.appspot.com

RBENV_VERSION = 2.3.0
APPENGINE_LOADBALANCER =
MEMCACHE_PORT_11211_TCP_PROTO = tcp
HOSTNAME = 9647520bdc61
NOKOGIRI_USE_SYSTEM_LIBRARIES = 1
GAE_APPENGINE_HOSTNAME = hogehoge-funfun.appspot.com
PREINSTALLED_RUBY_VERSIONS = 2.1.8 2.2.4 2.3.0
MEMCACHE_NAME = /gaeapp/memcache
MEMCACHE_PORT_11211_TCP_ADDR = 172.17.X.X
GAE_MODULE_NAME = default
GAE_AFFINITY = true
FOREMAN_VERSION = 0.78.0
RBENV_ROOT = /rbenv
GAE_LONG_APP_ID = hogehoge-funfun
RACK_ENV = production
GAE_MODULE_VERSION = 20160326t120623
RBENV_HOOK_PATH = /rbenv/rbenv.d:/usr/local/etc/rbenv.d:/etc/rbenv.d:/usr/lib/rbenv/hooks
MEMCACHE_PORT_11211_TCP_PORT = 11211
GAE_MODULE_INSTANCE = 0
APPENGINE_LOADBALANCER_IP =
PATH = /rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/bin:/rbenv/versions/2.3.0/bin:/rbenv/libexec:/rbenv/plugins/ruby-build/bin:/rbenv/shims:/rbenv/bin:/nodejs/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
RUBY_CONFIGURE_OPTS = --disable-install-doc
GAE_MINOR_VERSION = 391637041054208257
PWD = /app
MEMCACHE_PORT = tcp://172.17.X.X:11211
MODULE_YAML_PATH = app.yaml
SHLVL = 0
HOME = /root
MEMCACHE_PORT_11211_TCP = tcp://172.17.X.X:11211
USE_MVM_AGENT = true
RBENV_DIR = /app
SERVER_SOFTWARE = Google App Engine/1.9.35
GAE_VM = true
DEBIAN_FRONTEND = noninteractive
PORT = 8080
MEMCACHE_ENV_PORT = 8080
MEMCACHE_ENV_DEBIAN_FRONTEND = noninteractive
RUBYLIB = /rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/bundler-1.11.2/lib:/rbenv/rbenv.d/exec/gem-rehash
GAE_PARTITION = s
BUNDLER_VERSION = 1.11.2
RUBYOPT = -rbundler/setup
GEM_PATH = /rbenv/versions/2.3.0/lib/ruby/gems/2.3.0:/root/.gem/ruby/2.3.0
GEM_HOME = /rbenv/versions/2.3.0/lib/ruby/gems/2.3.0
BUNDLE_BIN_PATH = /rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/bundler-1.11.2/exe/bundle
BUNDLE_GEMFILE = /app/Gemfile
```

向こう側も rbenvで動いてるとか、アプリは `/app`に、rbenvは `/rbenv` に入ってるとか、rubyはデフォルトで 2.3.0だとか memcached が提供されているとかがわかります。

## インスタンスが立ち上がっていることとダッシュボードの確認

ブラウザからDeveloper Console に入ると、App Engine のダッシュボードができているのと、Compute Engine でインスタンスが一つ立ち上がっているのがわかります。

```bash
$ gcloud compute instances list
NAME                             ZONE          MACHINE_TYPE PREEMPTIBLE INTERNAL_IP EXTERNAL_IP    STATUS
gae-default-20160326t120623-1r0h us-central1-f g1-small                 10.128.X.X  104.197.XXX.XX RUNNING
```

![スクリーンショット_2016-03-26_12_17_06.png](https://qiita-image-store.s3.amazonaws.com/0/17911/969201d8-7579-5124-ff18-c98e245afff2.png "スクリーンショット_2016-03-26_12_17_06.png")

ダッシュボードからは、アクセス解析へのリンクとか貼られています。

# ログの確認

アプリをアップロードできたらあとはログの確認です。

ログなどはまとめて Stackdriver と呼ばれるシステムで集約管理されています。GCPの中ではそっけなく「ログ」と表記されています。

Stackdriverは新しいサービスなので、ここに書かれていることはすぐに古くなることは予想されますがそもそも Flexible Environment とかも含めて全部ベータなのでいいでしょう。やや分かりづらいですが、以下のように独立したサービスとしての Stackdriver もありますし、Google Cloud Platformに統合されたStackdriverシステムもあるようです。

- [Google、競合のAWSもモニタリングできる「Google Stackdriver」公開](http://www.itmedia.co.jp/news/articles/1603/24/news067.html)
- [Stackdriver](http://www.stackdriver.com)

ログ(Stackdriver)はApp Engineのリクエストだけでなく、もっと広い範囲でログ集約しています。

![スクリーンショット_2016-03-26_12_23_01.png](https://qiita-image-store.s3.amazonaws.com/0/17911/a1d1f31f-1702-b776-f470-1ba180324599.png "スクリーンショット_2016-03-26_12_23_01.png")

Google Cloud Platform を利用している全体のアクティビティログや、App Engine を実行しているホストの syslog 周り、コンテナの出力などもまとめて集約されています。

# アプリケーションの無効化

さて、検証が終わったらアプリケーションを止めたいのですが、コンテナのホストを落としてもGoogle によって勝手に再起動されてしまいます。
そんなときは、App Engine の「設定」から「アプリケーションを無効にする」を選択しましょう。



![スクリーンショット_2016-03-26_12_28_35.png](https://qiita-image-store.s3.amazonaws.com/0/17911/2ab51596-22b5-ae8d-3f57-fc2cdbde02d1.png "スクリーンショット_2016-03-26_12_28_35.png")

これで、勝手に Compute Engine のコンテナも消えてくれます。


# まとめ

パッケージ化された実行環境としては非常に完成度が高いと思いました。上では試してないですが、gen-config で Dockerfile を生成してローカルの Docker 環境でまったく同じコンテナを作って実行できたりするので、開発環境としても使い勝手がよいです。

Herokuの無料枠の使い勝手に対して、Cloud SQLと App Engine /ruby ではどうしたって$16/月くらいかかる気がするので、そのあたりは悩みどころ。スケールしやすさとか環境の柔軟さとかは確実に GCP だと思うんですが。やはり GAE なんだから [Cloud Datastore](https://cloud.google.com/datastore/?hl=ja) を使えということなんでしょうか。

あと、たとえば Rails アプリケーションのコンテナとして使うことを考えるとマイグレーションなどの実行タイミングどうしようかなとかそのあたりが気になってしまいます。タスクキューに詰めて実行するイメージなのかなぁ。ちょっとそのあたりは今後の確認課題ということで一つ。