10
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Cloud Foundry(Diego)でPython Buildpackを利用しFlaskアプリをデプロイする勘所

Posted at

この記事について

都合で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より一部抜粋)

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上にデプロイして動かすところまでを自分なりにやってみてまとめてみました。
運用ステージ(デプロイや、ログの収集、監視などなど)になるとまた課題がたくさんあってきたりすると思うのですが、長くなるので機会があれば別記事でまとめてみたいと思います。

参考資料

  1. MacはHomebrewでも出来たということをこの記事を書きながら知りました...

10
12
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?