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

# この記事について
都合でCloud Foundry (Diego) ベースのPaaSを触ることがあったので、そこで普段よく利用しているFlaskを使ってアプリをデプロイしてみました。
真面目にCFを触るのが初めてであったため、何点かひっかかった点がありましたのでそれを簡単な勘所として記載します。PaaSをこれから初めて使ってみる、使ってみたいけどよくわかってないという方のお役に立てれば幸いです。

CF詳しい方は間違っている点のご指摘など、むしろいろいろ教えて下さい・・・!


# CF環境のあれこれについて学ぶためのサンプルコード
下記にサンプルコードを置いておきました。
CF用アプリが初めての方用にコメントてんこもり & 機能超シンプルですがご容赦いただければ。

- [Github: flask-cloudfoundry-sample](https://github.com/yuta-hono/flask-cloudfoundry-sample)


## 今回の環境
Cloud FoundryのDiegoがデプロイされているNTTコミュニケーションズの[Enterprise Cloud PaaS](http://www.ntt.com/business/services/cloud/iaas/enterprise-cloud.html)を使って試しています。
Pivotal Web ServiceもDiegoがデプロイ済みなようなので、動くと思います。

- 利用したクライアント
  - `cf version 6.14.0+2654a47-2015-11-18`
- 利用したフレームワーク
  - Flask (バージョン指定なし、PaaS側pip任せ)

## サンプルコードの動かし方
### cf_cliのインストール、設定
まずはCloud Foundryを利用するためのcf_cliをインストール、設定します。
このあたりは[公式](http://docs.cloudfoundry.org/cf-cli/install-go-cli.html)を見ていただくのが早いかと思いますが、Max OS 64 bit, Windows 64 bit, Linux 64 bit用パッケージが配布[^1]されているので、ダウンロードして開くだけです。

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

インストールができたらcfコマンドでログインをします。

`cf login [-a API_URL] [-u USERNAME] [-p PASSWORD] [-o ORG] [-s SPACE]`

ここらへんはサービスプロバイダより提供されたアカウント情報を使って設定します。SpaceやOrgは1つしかなければ省略可能です。

ログインに成功すると、以下の様な情報が返されます。

- APIエンドポイント、およびAPIバージョン
- ユーザ
- Org
- Space 

```bash
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`環境変数で場所を変更することが可能です。

ログイン出来ていることを念のためユーザ一覧を取得して確認。

```bash
$ cf org-users econ1*********1 -a
Getting users in org econ1*********1 as 2*****************P8****nV...
USERS
2*****************P8****nV
```

### サンプルコードを取得
gitよりコードを取得して

```bash
$ git clone git@github.com:yuta-hono/flask-cloudfoundry-sample.git
```

該当のディレクトリで`cf push`を実施。これでカレントディレクトリをアプリとしてCFにデプロイが行われます。今回は`-b`オプションでPythonビルドパックを外部URL、CFのコミュニティビルドパックから呼び出しています。

```bash
$ cf push <アプリ名> -b https://github.com/cloudfoundry/python-buildpack
```

しばらく経つとビルド完了し、以下の様な結果が返されます。runnningになっていれば成功です！

```bash
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ドキュメント](https://docs.cloudfoundry.org/devguide/deploy-apps/prepare-to-deploy.html# "Prepare to Deploy")からの引用ですが、自分なりにまとめて上記のサンプルコードに出してみました。
全部を実装出来ているわけではありませんが、基本的な部分をシンプルなコードにまとめましたので、サンプルコードを見ながら読み進めていただくと更に理解が早まるかと思います。



## アプリのListenポートはどうすればいいのさ
CFにおいては[コンテナ](https://docs.cloudfoundry.org/concepts/diego/diego-architecture.html#cell-components)においてデプロイ時ポートを動的にアサインし、
一応アサインされたポートやIPを見ることも可能ですが、デプロイのしなおしや、その他インフラやコンテナの障害時にアプリ再起動がかかるので、その際にはポートやIPは変更され得ます。
つまり、Staticに記述をすることは出来ません。

CFはアプリがデプロイされたコンテナ内部で`PORT`という[環境変数](http://docs.cloudfoundry.org/devguide/deploy-apps/environment-variable.html#PORT "Cloud Foundry Environment Variables
") を容易してくれているので、これをアプリポートとして動的に設定する必要があります。

サンプルコードでは、以下の様な書き方をしています。(`hello.py`より一部抜粋)

```python:hello.py
import os

~snip~

cf_port = int(os.getenv("PORT"))

~snip~

app.run(port=cf_port)
```

参考までに、サンプルコードをCFにデプロイしてみると、`http://<yourapp>/vars`でデプロイ先コンテナの環境変数一覧が確認できますので、CFの提供する環境変数を確認するとわかりやすいと思います。

## アプリのログはどうやって見るの
アプリケーションログは、標準出力`STDOUT`もしくは標準エラー出力`STDERR`に出すようにすることで取得が可能です。 (必ず[どちらかに出力されるようにする](http://docs.cloudfoundry.org/devguide/deploy-apps/streaming-logs.html#app)というのがキモのようです)
CFでは、運用中の[アプリケーションのログをtailする機能](http://docs.cloudfoundry.org/devguide/deploy-apps/streaming-logs.html)があり、この機能を利用することでログを確認可能です。

```bash
$ 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](http://docs.cloudfoundry.org/devguide/deploy-apps/streaming-logs.html)を参照いただければよいかと思います。

運用していくうえでは、もうすこしいろいろと考慮していく必要がありそうですが、ここらへんは機会があれば別途まとめてみたいと思います。
＃むしろ詳しい人教えて下さい


## スケジュールリングジョブ (cron) を使いたいんや！
CFネイティブの機能には無いようです。CFを提供するサービスプロバイダによってはタスクスケジューラを外部モジュールとして独自に実装しているところもあるみたいですが、CFの大きな開発者であるPivotalによれば[Cronがないならそのジョブをアプリとして動かせばいいじゃない](https://blog.pivotal.io/labs/labs/scheduling-tasks-on-cloud-foundry "Scheduling tasks on Cloud Foundry
") (だいぶ意訳)ということでした。


## 環境変数を自分で設定したいけど、できる？
出来ます。
サンプルコード中, `.profile.d` がこのためのディレクトリです。
このディレクトリ内部に`.sh`拡張子でシェルスクリプトを配置することで、デプロイ時にCF側でこのスクリプトを実行してくれるので、それを利用して環境変数を設定していくことになります。

例として、このサンプルコードでは、`MY_OWN_ENV_VAR`を設定しており、それ以外はCFにより提供された環境変数が存在することになります。

参考までに、サンプルコードのアプリの場合、`http://<yourapp>/vars`で設定できていることが確認できます。
もしくは、`$ cf ssh <yourapp>`でアプリのデプロイされたコンテナにログインが可能なので、ログイン後、`$ printenv`で環境変数の一覧が確認可能です。


## データはアプリから吐き出してローカルに置いてもいい？
**ダメです。**
CF環境ではアプリケーションのデプロイ先であるコンテナ内部に置かれたデータはデプロイ時にふくまれるものを除き揮発性です。よって、アプリが別のコンテナで再起動されるなりすると簡単に消えます。
永続データは外部ストレージを利用することになります。staticなものはs3なりのオブジェクトストレージやその他のストレージを利用することになり、データは外部のRDBサービスを使う形になりそうです。プロバイダによってはここらへんもインテグレーションして提供しているところもありますので利用前に確認を行いましょう。

具体的な利用の方法についてはすこしだけサンプルコードより複雑になるので割愛しますが、[サービスをバインドして利用](http://docs.cloudfoundry.org/devguide/services/user-provided.html)することで、ここらへんは楽にできそうです。


## デプロイ時に無視したいファイルをレポジトリに含めたいのだけど
README.mdや、.git以下とか無視したいですよね。
そんな時には`.cfignore`を使います。

これは`.gitignore`とほぼおなじ仕組みです。
デフォルトでは、[以下のファイルやディレクトリを無視](https://docs.cloudfoundry.org/devguide/deploy-apps/prepare-to-deploy.html#exclude)してくれます。

- `.cfignore`
- `_darcs`
- `.DS_Store`
- `.git`
- `.gitignore`
- `.hg`
- `/manifest.yml`
- `.svn`

サンプルコードでは、`README.md`および`LICENSE`を無視するように設定されています。
例として、デプロイ後のアプリケーションに`cf ssh <app_name>`としてみると、

```bash
vcap@gisruph1uo0:~$ ls
app  logs  staging_info.yml  tmp
```

こんな感じになっています。`app`内部にデプロイされたアプリがあるので見てみましょう。

```bash
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などでもおなじみですね。）

サンプルコードでは、以下のように行っています。（該当部分のみ抜粋/編集）

```python
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://flask.pocoo.org/docs/0.10/api/#flask.Flask.before_request)を参照してください。

サンプルコード環境では、`http://<yourapp>/vars`の画面下部HTTPリクエストヘッダを確認可能です。

# 最後に
今回はアプリをとりあえずCF上にデプロイして動かすところまでを自分なりにやってみてまとめてみました。
運用ステージ（デプロイや、ログの収集、監視などなど）になるとまた課題がたくさんあってきたりすると思うのですが、長くなるので機会があれば別記事でまとめてみたいと思います。

# 参考資料
- Cloud Foundry公式ドキュメント (http://docs.cloudfoundry.org/)