この記事について
都合でCloud Foundry (Diego) ベースのPaaSを触ることがあったので、そこで普段よく利用しているFlaskを使ってアプリをデプロイしてみました。
真面目にCFを触るのが初めてであったため、何点かひっかかった点がありましたのでそれを簡単な勘所として記載します。PaaSをこれから初めて使ってみる、使ってみたいけどよくわかってないという方のお役に立てれば幸いです。
CF詳しい方は間違っている点のご指摘など、むしろいろいろ教えて下さい・・・!
CF環境のあれこれについて学ぶためのサンプルコード
下記にサンプルコードを置いておきました。
CF用アプリが初めての方用にコメントてんこもり & 機能超シンプルですがご容赦いただければ。
今回の環境
Cloud FoundryのDiegoがデプロイされているNTTコミュニケーションズのEnterprise Cloud PaaSを使って試しています。
Pivotal Web ServiceもDiegoがデプロイ済みなようなので、動くと思います。
- 利用したクライアント
cf version 6.14.0+2654a47-2015-11-18
- 利用したフレームワーク
- Flask (バージョン指定なし、PaaS側pip任せ)
サンプルコードの動かし方
cf_cliのインストール、設定
まずはCloud Foundryを利用するためのcf_cliをインストール、設定します。
このあたりは公式を見ていただくのが早いかと思いますが、Max OS 64 bit, Windows 64 bit, Linux 64 bit用パッケージが配布1されているので、ダウンロードして開くだけです。
インストールができたらcfコマンドでログインをします。
cf login [-a API_URL] [-u USERNAME] [-p PASSWORD] [-o ORG] [-s SPACE]
ここらへんはサービスプロバイダより提供されたアカウント情報を使って設定します。SpaceやOrgは1つしかなければ省略可能です。
ログインに成功すると、以下の様な情報が返されます。
- APIエンドポイント、およびAPIバージョン
- ユーザ
- Org
- Space
API endpoint: https://paas-uk1-ecl.api.ntt.com (API version: 2.47.0)
User: ecid1*********0@econ1*********1
Org: econ1*********1
Space: demonstration
一度ログインをすると、デフォルトではホームディレクトリに~/.cf/config.json
が作成され、保存されます。これはCF_HOME
環境変数で場所を変更することが可能です。
ログイン出来ていることを念のためユーザ一覧を取得して確認。
$ cf org-users econ1*********1 -a
Getting users in org econ1*********1 as 2*****************P8****nV...
USERS
2*****************P8****nV
サンプルコードを取得
gitよりコードを取得して
$ git clone git@github.com:yuta-hono/flask-cloudfoundry-sample.git
該当のディレクトリでcf push
を実施。これでカレントディレクトリをアプリとしてCFにデプロイが行われます。今回は-b
オプションでPythonビルドパックを外部URL、CFのコミュニティビルドパックから呼び出しています。
$ cf push <アプリ名> -b https://github.com/cloudfoundry/python-buildpack
しばらく経つとビルド完了し、以下の様な結果が返されます。runnningになっていれば成功です!
requested state: started
instances: 1/1
usage: 128M x 1 instances
urls: <yourapp>.uk1.eclpaas.com
last uploaded: Tue May 23 10:50:05 UTC 2016
stack: cflinuxfs2
buildpack: python_buildpack
state since cpu memory disk details
#0 running 2016-05-24 10:50:52 PM 0.0% 18.8M of 128M 147.6M of 256M
普通の自分で作る環境と違ういろいろ
アプリケーションをPaaSに載せたいけど何を気をつければいいかわからないという質問と解決策を、ほぼほぼ本家CFドキュメントからの引用ですが、自分なりにまとめて上記のサンプルコードに出してみました。
全部を実装出来ているわけではありませんが、基本的な部分をシンプルなコードにまとめましたので、サンプルコードを見ながら読み進めていただくと更に理解が早まるかと思います。
アプリのListenポートはどうすればいいのさ
CFにおいてはコンテナにおいてデプロイ時ポートを動的にアサインし、
一応アサインされたポートやIPを見ることも可能ですが、デプロイのしなおしや、その他インフラやコンテナの障害時にアプリ再起動がかかるので、その際にはポートやIPは変更され得ます。
つまり、Staticに記述をすることは出来ません。
CFはアプリがデプロイされたコンテナ内部でPORT
という環境変数 を容易してくれているので、これをアプリポートとして動的に設定する必要があります。
サンプルコードでは、以下の様な書き方をしています。(hello.py
より一部抜粋)
import os
~snip~
cf_port = int(os.getenv("PORT"))
~snip~
app.run(port=cf_port)
参考までに、サンプルコードをCFにデプロイしてみると、http://<yourapp>/vars
でデプロイ先コンテナの環境変数一覧が確認できますので、CFの提供する環境変数を確認するとわかりやすいと思います。
アプリのログはどうやって見るの
アプリケーションログは、標準出力STDOUT
もしくは標準エラー出力STDERR
に出すようにすることで取得が可能です。 (必ずどちらかに出力されるようにするというのがキモのようです)
CFでは、運用中のアプリケーションのログをtailする機能があり、この機能を利用することでログを確認可能です。
$ cf logs blue
Connected, tailing logs for app blue in org econ1*********1 / space demonstration as 2*1...
2016-05-24T22:51:21.02+0900 [RTR/0] OUT demonstration.uk1.eclpaas.com - [24/05/2016:13:51:21 +0000] "GET / HTTP/1.1" 200 0 12 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36" 10.0.0.7:35858 x_forwarded_for:"192.168.0.43, 10.0.0.7" x_forwarded_proto:"http" vcap_request_id:8d5086b2-9ffd-4901-4ce5-80bcef39557c response_time:0.005617514 app_id:ae1b5d8f-2159-4259-947e-c4b01e1cd513
2016-05-24T22:51:21.02+0900 [APP/0] ERR 10.0.50.151 - - [24/May/2016 13:51:21] "GET / HTTP/1.1" 200 -
ログには複数のタイプのログがあり、APPタイプがアプリケーションタイプとなり、それ以外はCFが提供するログとなります。詳細はApplication Logging in Cloud Foundryを参照いただければよいかと思います。
運用していくうえでは、もうすこしいろいろと考慮していく必要がありそうですが、ここらへんは機会があれば別途まとめてみたいと思います。
#むしろ詳しい人教えて下さい
スケジュールリングジョブ (cron) を使いたいんや!
CFネイティブの機能には無いようです。CFを提供するサービスプロバイダによってはタスクスケジューラを外部モジュールとして独自に実装しているところもあるみたいですが、CFの大きな開発者であるPivotalによればCronがないならそのジョブをアプリとして動かせばいいじゃない (だいぶ意訳)ということでした。
環境変数を自分で設定したいけど、できる?
出来ます。
サンプルコード中, .profile.d
がこのためのディレクトリです。
このディレクトリ内部に.sh
拡張子でシェルスクリプトを配置することで、デプロイ時にCF側でこのスクリプトを実行してくれるので、それを利用して環境変数を設定していくことになります。
例として、このサンプルコードでは、MY_OWN_ENV_VAR
を設定しており、それ以外はCFにより提供された環境変数が存在することになります。
参考までに、サンプルコードのアプリの場合、http://<yourapp>/vars
で設定できていることが確認できます。
もしくは、$ cf ssh <yourapp>
でアプリのデプロイされたコンテナにログインが可能なので、ログイン後、$ printenv
で環境変数の一覧が確認可能です。
データはアプリから吐き出してローカルに置いてもいい?
ダメです。
CF環境ではアプリケーションのデプロイ先であるコンテナ内部に置かれたデータはデプロイ時にふくまれるものを除き揮発性です。よって、アプリが別のコンテナで再起動されるなりすると簡単に消えます。
永続データは外部ストレージを利用することになります。staticなものはs3なりのオブジェクトストレージやその他のストレージを利用することになり、データは外部のRDBサービスを使う形になりそうです。プロバイダによってはここらへんもインテグレーションして提供しているところもありますので利用前に確認を行いましょう。
具体的な利用の方法についてはすこしだけサンプルコードより複雑になるので割愛しますが、サービスをバインドして利用することで、ここらへんは楽にできそうです。
デプロイ時に無視したいファイルをレポジトリに含めたいのだけど
README.mdや、.git以下とか無視したいですよね。
そんな時には.cfignore
を使います。
これは.gitignore
とほぼおなじ仕組みです。
デフォルトでは、以下のファイルやディレクトリを無視してくれます。
.cfignore
_darcs
.DS_Store
.git
.gitignore
.hg
/manifest.yml
.svn
サンプルコードでは、README.md
およびLICENSE
を無視するように設定されています。
例として、デプロイ後のアプリケーションにcf ssh <app_name>
としてみると、
vcap@gisruph1uo0:~$ ls
app logs staging_info.yml tmp
こんな感じになっています。app
内部にデプロイされたアプリがあるので見てみましょう。
vcap@gisruph1uo0:~$ cd app
vcap@gisruph1uo0:~/app$ ls
hello.py Procfile requirements.txt runtime.txt templates
きちんと無視されています。.cfignore
を編集することで効き目を試せますのでご利用ください。
ネットワーク的にアクセスを制限したいのだけど
ネットワーク的には0.0.0.0/0
にListenさせることになります。また、アプリケーションのbindするポート自体も0.0.0.0/0
にListenさせることになります。
なので、ネットワーク的アクセス制限は(ほぼ)利用できません。
しかしながら、CF側でクライアントIPをX-Forwarded-For
というHTTPリクエストヘッダにいれてくれているので、これを用いることでアプリケーション側でのソースIPによるアクセス制限が可能です。(X-Forwarded-For
はAWSのELBなどでもおなじみですね。)
サンプルコードでは、以下のように行っています。(該当部分のみ抜粋/編集)
from flask import request
srcIp = request.access_route[0]
# List of IP addresses to allow the access
allowed_ip = ['192.0.2.1', '192.0.2.2']
@app.route('/ip')
def showIp():
if srcIp not in allowed_ip:
abort(403)
return 'Your IP %s is allowed' % srcIp
flask.request.access_route
でX-Forwarded-Forを取得できるのですが、Flaskは配列としてX-Forwarded-Forに追加された値を格納してくれています。ここで指定しているaccess_route[0]
は、X-Forwarded-For
の値が192.0.2.1, 192.0.2.2
だった場合に最初の1つ、つまり192.0.2.1
をいい感じに取得してくれます。
CF的にはX-Forwarded-For
にクライアントソースIPをいれているだけなのですが、プロバイダによってはさらに手前にロードバランサなどが挟まっており複数の値が格納されている場合があるので、場合に応じて調整が必要です。
サンプルコードではhttp://<yourapp>/ip
でクライアントソースIPによるアクセス制限が実装されています。allowed_ip
のリストを変更すると許可するIPを変更可能なのでお試しいただければ。
補足 (Flaskのお話)
なおサンプルコードではシンプルにするために/ip
リソースに対してメソッドを定義してアクセス許可の判定を行っていますが、Flaskであればbefore_request
に対しデコレータでリクエスト受け付け前の動作を設定出来、すべてのページでシンプルにアクセス制限をかけることが可能です。
詳細はFlask公式ドキュメント、APIの章を参照してください。
サンプルコード環境では、http://<yourapp>/vars
の画面下部HTTPリクエストヘッダを確認可能です。
最後に
今回はアプリをとりあえずCF上にデプロイして動かすところまでを自分なりにやってみてまとめてみました。
運用ステージ(デプロイや、ログの収集、監視などなど)になるとまた課題がたくさんあってきたりすると思うのですが、長くなるので機会があれば別記事でまとめてみたいと思います。
参考資料
- Cloud Foundry公式ドキュメント (http://docs.cloudfoundry.org/)
-
MacはHomebrewでも出来たということをこの記事を書きながら知りました... ↩