このエントリーはGoogle Cloud Platform Advent Calendar 2015の14日目です。
10月に入社しましてブログには初めて投稿します石井です。休日はJavaScriptやGoで趣味の開発を行っていたり、家に知人を集めてボードゲーム会をしてたりします。
さて、ALBERTはAWSメインの開発を行っているのですが、GCP (Google Cloud Platform) も最近盛り上がって来ている!という事で、新しい案件をGCPで構築してみることにしました。その際にGAE (Google AppEngine) 周りの調査を色々と行ったので、今回は特にManaged VMとCustom Runtimeについて紹介できればと思います。
GAE Managed VM, Custom Runtimeは簡単に言うと、GAEが裏で管理していたコンテナを触れることができるようになったり、自分で定義したDockerのコンテナをGAE上で動かすことがでるようになるものです。
GAE sandbox
予めGCPのコンソールから適当なプロジェクトを作成しておきます。
まずは基本のGAEプロジェクトから段階的にVM使うようにしてみます。
app.yamlとそこから呼び出してるmain.pyを作成、デプロイします。
runtime: python27 api_version: 1 threadsafe: false handlers: - url: / script: main.app
#!/usr/bin/env python # -*- coding: utf-8 -*- import webapp2 class MainPage(webapp2.RequestHandler): def get(self): self.response.headers['Content-Type'] = 'text/plain' self.response.write('Hello, World!') app = webapp2.WSGIApplication([ ('/', MainPage), ], debug=True)
$ gcloud preview app deploy app.yaml –promote
でデプロイします。
ブラウザでhttps://プロジェクト名.appspot.com/を見てみるとHello, World!が出ます。
ここまではよくあるsandboxのhello worldですね。
GAE Managed VM (MVM)
次に、このpythonアプリケーションをMVMに変更してみます。
runtime: python27 vm: true api_version: 1 threadsafe: false handlers: - url: / script: main.app manual_scaling: instances: 1
さっきと比べ、app.yamlに
vm: true
manual_scaling: {instances: 1}
が追加されただけです。
MVMで重要なのはvm: trueの方で、これを追加するとMVMモードでアプリケーションが動作するようになります。
manual_scalingはスケーリング・負荷分散の設定値です。MVMはデフォルトで2つのインスタンスを立ち上げ分散するようになっているのですが、今回は特に必要ないので1つのインスタンスで処理するように設定しています。
先ほどと同じように
$ gcloud preview app deploy app.yaml –promote
でデプロイします。
MVMモードでVMを立ち上げているのでsandboxの時より少し時間が掛かるかと思います。気長に待って、先ほどと同じhttps://プロジェクト名.appspot.com/にアクセス。ちゃんと動作しているのが確認できます。また、GCEのコンソールへ移動すると今デプロイしたアプリケーション用のインスタンスが起動しているのが確認できると思います。sandboxで隠されていたインスタンスの中身があれこれ確認できるのですね。感激です。
注意する点として、MVMで立ち上がったインスタンスは新しいバージョンのアプリケーションがデプロイされても削除されず動き続けます。消したい場合はGAEコンソールから過去のバージョンを削除しないといけません。GCEから直接インスタンスを削除しても、しばらくするとオートスケーリングの設定により自動的にインスタンスが立ち上がってしまいます。このへん新しいバージョンのアプリケーションがデプロイされたら自動で消えるようにしたかったのですが調べてもよくわかりませんでした。何かご存じの方はご教授頂ければと思います。
さて、せっかく立ち上げたインスタンスですが、これだけじゃVMの何が嬉しいのかわからないのでちょっとアプリケーションをいじってみます。と言ってもsqliteでローカルのディスクへ書き込む簡単なものですが。
GAE sandboxだとfsへのアクセスができないのですが、vmにすればfsにも触れる用になるので、sqliteでローカルにDBファイルを作って読み書きすることも可能なはずです。
#!/usr/bin/env python # -*- coding: utf-8 -*- import webapp2 import sqlite3 from time import time class MainPage(webapp2.RequestHandler): def __init__(self, *args, **kwargs): super(MainPage, self).__init__(*args, **kwargs) self.conn = sqlite3.connect('mydb') self.cur = self.conn.cursor() self.cur.execute(''' CREATE TABLE IF NOT EXISTS welcome (time NUMBER DEFAULT 0) ;''') def update_count(self): self.cur.execute('INSERT INTO welcome(time) VALUES (?);', (time(),)) self.cur.execute('SELECT count(*) from welcome') self.conn.commit() return self.cur.fetchone() def get(self): count = self.update_count() self.response.headers['Content-Type'] = 'text/plain' self.response.write('''Hello, World! You are %sth visitor! ''' % count) app = webapp2.WSGIApplication([ ('/', MainPage), ], debug=True)
シンプルすぎるカウンター。
どうしようもないアプリケーションですがそこは気にせず、これを今までのようにデプロイするとアクセスのたびにカウンターがちゃんとインクリメントされているのが確認できます。
Image may be NSFW.
Clik here to view.
何気ないアプリケーションですがGAEでローカルファイルへ書き込むことができるようになりました!
さて、MVMで立ち上がったインスタンスですが、GCEのインスタンスとして立ち上がっているのでもちろんSSH接続して中身を確認することができます。MVMで立ち上がったインスタンスはgoogle管理のインスタンスとなり、「途中で中身をいじって変更したまま動かし続ける」事はできませんが、弄ることだけはできます。試しにGCEインスタンスの一覧からsshボタンを押すと「ユーザー管理への切り替え」とダイアログが出てきて、変更は全て失われますと表示されます。
Image may be NSFW.
Clik here to view.
何はともあれsshしてみるとそれらしいコンテナが動いているのが確認できますので、それに接続してみます。
$ sudo su - $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 9d2f45e52d31 gcr.io/google_appengine/mvm-agent "/usr/local/bin/gunic" 10 minutes ago Up 10 minutes condescending_allen ca653eb06a3e gcr.io/google_appengine/nginx-proxy "/var/lib/nginx/bin/s" 10 minutes ago Up 10 minutes 0.0.0.0:8080->8080/tcp, 8090/tcp evil_meninsky a68171a218ac appengine.gcr.io/498347750172306472/プロジェクト名.default.20151214t123834 "/usr/bin/python2.7 /" 10 minutes ago Up 10 minutes 8080/tcp gaeapp 73cb013cc55b gcr.io/google_appengine/memcache-proxy "/home/memcache/memca" 10 minutes ago Up 10 minutes 11211/tcp memcache 40812862abb0 gcr.io/google_appengine/fluentd-logger "/opt/google-fluentd/" 11 minutes ago Up 11 minutes boring_tesla $ docker exec -it a68171a218ac bash --login
コンテナの中に入れました!
$ pwd /home/vmagent/app $ ls Dockerfile app.yaml main.py main.pyc tmpTIqZW9 $ cat Dockerfile # Dockerfile extending the generic Python image with application files for a # single application. This is only used for pre- env: 2 deployments. # The name of this image is for historical reasons 'python-compat', but it # refers to a version of the runtime separate from the 'runtime: python-compat' # image. FROM gcr.io/google_appengine/python-compat ADD . /app $ Ctrl+p $ Ctrl+q
最後はCtrl+p→Ctrl+qでコンテナから抜けます。豪快にexitするとコンテナが終了してアプリケーションが死んでしまうので気をつけてください。
いろいろと面白いものが見れましたね。ログインしたディレクトリにあったDockerfileが今まさにGAEで動いているコンテナのイメージファイルになります。
GAE MVM Custom Runtimes
実はこのDockerfileを自分で書いてグリグリするのもCustom Runtimeという形で公式にサポートされています。
GoogleCloudPlatform/appengine-nginx-hello
公式のサンプルではnginxを動かしています。これを真似てpythonのflaskを動かすコンテナを書いてみたのがこれです。以下はこれの説明です。
airtoxin/gae-mvm-custom-runtime-sample
runtime: custom vm: true api_version: 1 threadsafe: false handlers: - url: / script: dynamic manual_scaling: instances: 1
nginxのyamlファイルを参考にしました。runtime: customとしてDockerfileを同梱するとそれを動かせるようになります。
handelrsのscript: dynamicは公式のマニュアルをどれだけ探しても出てこないのですが…おそらくリクエストをGAE側でハンドリングせず、サーバーに直接届けてくれるようなオプションかと思います。
で、動かすDockerfileですが、せっかくなのでpython3を動かしてみます。
FROM python:3.4.3 ADD requirements.txt requirements.txt RUN pip install -r requirements.txt ADD . . ENTRYPOINT ["python3", "main.py"]
#!/usr/bin/env python # -*- coding: utf-8 -*- from flask import Flask app = Flask(__name__, static_folder='static') @app.route("/") def hello(): return app.send_static_file('index.html') @app.route("/_ah/start") def _ah_start(): return "ok" @app.route("/_ah/health") def _ah_health(): return "ok" if __name__ == "__main__": app.run(host='0.0.0.0', port=8080)
こんな感じのDockerfileとpythonのスクリプトを用意して、doker runするとmain.pyでサーバーが起動するようにしてやります。
サーバーはflaskで立ててみます。sandboxだとdjangoかwebapp2くらいしか呼び出せず少々めんどくさかったのが、好きなライブラリを導入できるので軽量なライブラリでさっと書けるのがいいですね。
/へのルーティングはstatic/index.htmlを返すようにしていますので、これは適当に配置しておきます。/_ah/startと/_ah/healthはGAEのライフサイクルイベントで、GAEが定期的にリクエストを送り、コンテナがreadyなのかhealthyなのかを確認するのでそれに応答するようにしています。healthの方はステータス200で何かメッセージを送ればよいようです。
あとはrequirements.txtに依存ライブラリを記述しておきます。これは先程のDockerfile内で、コンテナ内のpythonにインストールするようにしています。
Flask==0.10.1 itsdangerous==0.24 Jinja2==2.8 MarkupSafe==0.23 Werkzeug==0.11.2 wheel==0.24.0
ここまできたらデプロイはお馴染み
$ gcloud preview app deploy app.yaml –promote
ですね。
Image may be NSFW.
Clik here to view.
来ました!勝手に定義したDockerfileがGAEで動いてます!
まとめ
GAE sandboxはVMの管理をGAEがやってくれる代わりにfsを触れないなどの制限がある。
そこでManaged VMにするとVMの管理をユーザーが行う必要がある代わりにそれらの制限を取っ払える。
さらにCustom RuntimeにするとVM自体をユーザーが定義したものを使用できる。
Custom RuntimeまでやるともはやGCEでやるのとほぼ同じ環境が手に入れられますね。スケーリング周りを引き続き面倒見てくれるので、この辺とリクエストのハンドリング周りの自由度で使い分ける感じでしょうか。