Bluemix Pythonマイクロサービスの開発


Pythonは、IoT、Analytics, Deep Learning,自然言語処理などの分野でライブラリが充実したプログラム言語です。この様な分野の専門特化した処理は、専門チームがPythonでマイクロサービスとして開発して、PHPのウェブ画面では高い生産性があるフロントエンド・アプリから、REST-APIとして呼び出して利用すると、相互に高い生産性や保守性が期待されます。 

また、WatsonのAPIは、JSON形式の応答を返しますから、JSONの処理が得意なNode.jsのランタイムに、JSON形式の文書型DBであるCloudantを接続してコーパスを構築することで、ワトソンの応用システムを生産性高く開発出来ます。そして、同様にPHPのフロントエンドからRESTサービスとして利用することで、機能を追加していくことができます。

次の図の様に、フロントエンドのPHPのアプリから、Node.jsとPythonで書かれたマイクロサービスを呼び出す構造によって、ウェブ画面のアプリに大きな影響を与えることなく、機能を追加することができます。 言い換えると、この様なマイクロサービスのアーキテクチャを採用することで、ウェブ画面はPHP、数値計算はPython、ワトソンはNode.js といった様に、それぞれ最も得意とするプログラミング言語で、相互に良好な生産性を維持しながら、システムの機能を拡充していくことができます。

![スクリーンショット 2017-08-21 22.14.08.png](https://qiita-image-store.s3.amazonaws.com/0/42829/427ab8ee-2ebe-b0c7-31dc-442c7b59d987.png "スクリーンショット 2017-08-21 22.14.08.png")



この様なマイクロサービス・アーキテクチャをサポートする機能として、Bluemixには、ユーザー提供のRESTサービスを、他のアプリからバインドして利用できる機能があります。これを用いることで、機能や組織の単位で疎結合された大規模なシステムとして、構築していくことが出来ます。


*この記事は、[Bluemixアプリ開発と管理#21.5 ユーザー提供サービスインスタンス](http://qiita.com/MahoTakara/items/9d03414689fe1b2b9cd2)の具体的な実装例です。*



## 実装内容

この記事では、PHPアプリから接続して利用するPythonのRESTful Webサービスを作ってみたいと思います。 もちろん、この開発の過程ではローカル開発環境からRESTをコールして、動作を確認するといった環境を想定して、HTTPSと認証を考慮します。 また、PHPのアプリに、Pythonのサービスをバインドすることで、アクセス先URL、認証情報を取得できる様にして、PHPアプリにハードコーディングすることを回避します。


![スクリーンショット 2017-08-21 22.14.16.png](https://qiita-image-store.s3.amazonaws.com/0/42829/d7ba0b18-33a6-e311-e8d4-c81445f9cc8f.png "スクリーンショット 2017-08-21 22.14.16.png")


## Pythonのプログラム環境の作成
開発環境のスタックの構築は、Vagrant + VirtualBoxでローカルに構築します。 GitHub https://github.com/takara9/bluemix-dev のアドレスに Vagrantfile を用意してありますので、これを利用することで、自分のPCの仮想サーバー上に、Pythonの開発環境を準備することができます。また、この Vagrantfile を利用すると、PHP, Ruby, Node.js の開発環境も同時に作ることができます。

* GitHubからVagrantfile をローカル環境にクローン

~~~
$ git clone https://github.com/takara9/bluemix-dev bluemix-rest
~~~

* ディレクトリを移動して、仮想マシン起動

~~~
$ cd bluemix-rest/
$ vagrant up
~~~

* sshでログイン 

~~~
$ vagrant ssh
~~~

* Python 2.7系の最新バージョンをインストールします。 pyenvコマンドで3.4系も選択できますが、本記事のRESTサーバーのコードは3.4系のPythonでテストしていません。

~~~
$ pyenv install 2.7.13
~~~

* イントールしたPythonを利用する様に設定

~~~
$ pyenv versions
* system (set by /home/vagrant/.pyenv/version)
  2.7.13
$ pyenv global 2.7.13
$ python --version
Python 2.7.13
~~~

以上で、Pythonの開発環境が準備できました。


## Python RESTサーバーのプログラム開発

Pythonの最小限のRESTサーバーを書いていきます。 このコードの機能は、次の内容です。

* 認証なしで GET / に応答します。 Bluemix にCFアプリとしてデプロイした場合にヘルスチェックに応答するために使用します。
* /calc でPOSTを受け取ります。 この/calc は BASIC認証をパスしなければなりません。
* HTTPサーバーのポート番号は、環境変数 PORT を参照して決定します。 環境変数が存在しない場合のデフォルトは5000番です。
* /calc のPOSTされるデータは、フォームエンコーディング application/x-www-form-urlencoded 形式を対象とします。
* POSTデータの "a" と "b" の数値を乗算して、テキストJSON形式 { ans: 結果 }で応答します

これから例とするコードは、GitHub https://github.com/takara9/REST_Sample_Python から入手できます。

### RESTサーバーのPythonサンプル・コード

~~~lang:python
     1	#!/usr/bin/env python
     2	# -*- coding:utf-8 -*-
     3	import os
     4	from flask import Flask
     5	from flask_restful import Resource, Api, reqparse
     6	from flask_httpauth import HTTPBasicAuth
     7	
     8	# for Health Check
     9	class HelloWorld(Resource):
    10	    def get(self):
    11	        return {'message': 'Hello World'}
    12	
    13	# for POST
    14	class Calc(Resource):
    15	    auth = HTTPBasicAuth()
    16	    @auth.login_required
    17	    def post(self):
    18	        args = parser.parse_args()
    19	        ans = float(args['a']) * float(args['b'])
    20	        return {'ans': ans }
    21	
    22	    @auth.verify_password
    23	    def verify_password(username, password):
    24	        return username == 'user' and password == 'pass'
    25	
    26	
    27	if __name__ == '__main__':
    28	
    29	    app = Flask(__name__)
    30	    api = Api(app)
    31	    api.add_resource(Calc, '/calc')
    32	    api.add_resource(HelloWorld, '/')
    33	
    34	    parser = reqparse.RequestParser()
    35	    parser.add_argument('a')
    36	    parser.add_argument('b')
    37	
    38	    bx_port = os.getenv("PORT")
    39	    listen_port = int(bx_port if bx_port else 5000)
    40	    app.run(host='0.0.0.0', port=listen_port, debug=True)
~~~


### プログラムの解説

#### 1〜2 行目 

最初の行は、インタープリターとしてpythonを利用する宣言です。 /usr/bin/envは pyenvで設定されたPythonを実行するための指定です。 man env でマニュアルを参照できます。 2行目は UTF-8 を利用するための宣言です。


#### 3〜6 行目

RESTサーバーを作るために、Python の FLASK フレームワークを利用しています。 インポートしている３つのモジュールは、次のURLで詳しく知ることができます。 

* [Flask マイクロフレームワーク](http://flask.pocoo.org/)
* [Flask RESTful] (https://flask-restful.readthedocs.io/en/0.3.5/)
* [Flask-BasicAuth] (https://flask-basicauth.readthedocs.io/en/latest/)


#### 8〜11 行目

Bluemixへデプロイした場合に、ヘルスチェックに応答するためのコードです。 応答する内容は特に指定はありませんから、'Hello World'を返しています。このクラスへのコールバックの指定は、32行目にあります。


#### 13〜24 行目

RESTサービスの本体コア部分です。 15と16行で、BASIC認証が必要であることを指定します。 17〜20行で POSTで受けたデータを変数に変換(パース）、乗算して、返信します。 コールバックからreturn する変数で応答を返すというPythonのフレームワークは、拍手ものと思います。

22〜24行で、ユーザーIDとパスワードを照合しています。 本当のアプリであれば、ここからID管理のシステムやDBへアクセスすることになりますね。

#### 27行目以降

メイン・ルーティン部分です。 フレームワークをインスタンス化、コールバックのクラスを設定、パースするパラメータを指定、環境変数からポート番号を取得して、サーバーをスタートさせます。



## ローカル環境へのインストールとテスト

このセクションでは、GitHubからクローンしたコードをローカル環境でテストする方法について、書いていきます。
いきなり、Bluemixへデプロイして実行しても良いのですが、開発環境はローカルにあるのが便利なので、一旦、自分のMacの中で動作を確認してから、デプロイします。


最初に次の様にコードをクローンします。

~~~
git clone https://https://github.com/takara9/REST_Sample_Python
~~~

Pythonの必要なモジュールのリスト requirements.txt が置いてある RESTのサーバーのディレクトリへ移動します。

~~~
cd REST_Sample_Python/restServerPython
~~~

次のコマンドを実行して、Pythonの前提パッケージを一括インストールします。

~~~
pip install -r requirements.txt
~~~



### RESTサーバーの実行（ローカル）

次の方法で RESTサーバーが起動します。 事前に、chmod +x app.py としておけば、直接 ./app.py としても実行できます。

~~~
vagrant@vagrant-ubuntu-trusty-64:~/REST_Sample_Python/restServerPython$ python app.py
 * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 267-639-962
 * 
~~~


### RESTクライアントの実行（ローカル）

もう一つターミナルを開いて、vagrant ssh で開発環境の仮想サーバーにログインしてから、RESTのクライアントを実行します。
RESTのクライアントも、GitHubからクローンしたコードに含まれていますから、ディレクトリを移動します。

~~~
cd REST_Sample_Python/restClientPython
~~~


前述の図の通り、PHPのアプリケーションから、Pythonで書かれたサービスを呼び出すことを進めているので、クライアントは、PHPで書いてあります。 クライアントのコードは、接続先のURIや認証情報を vcap-local.jsonから取得する様になっていますので、このファイルを書き換えて、際ほどローカルに起動したRESTサービスへ繋いでみます。

変更は7行目のuriで、 "http://localhost:5000/calc" に書き換えます。 

~~~file:vcap-local.json
     1	{
     2	 "VCAP_SERVICES": {
     3	  "user-provided": [
     4	   {
     5	    "credentials": {
     6	     "password": "pass",
     7	     "uri": "https://pycalcxx.mybluemix.net/calc",  <-- この行を修正
     8	     "username": "user"
     9	    },
    10	    "label": "user-provided",
    11	    "name": "pycalcxxu",
    12	    "syslog_drain_url": "",
    13	    "tags": [],
    14	    "volume_mounts": []
    15	   }
    16	  ]
    17	 }
    18	}
~~~


次に、phpenvでPHPをインストールして、バージョンを確認します。

~~~
phpenv install 5.6.31
~~~

~~~
phpenv global 5.6.31
~~~

~~~
vagrant@vagrant-ubuntu-trusty-64:~/REST_Sample_Python/restClientPython$ php -v
PHP 5.6.31 (cli) (built: Aug 14 2017 05:28:21) 
~~~

これで、PHPのコードが実行できる環境ができたので、実行します。 

~~~
vagrant@vagrant-ubuntu-trusty-64:~/REST_Sample_Python/restClientPython$ php client.php 
Result = 2130.951794
~~~

クライアントのコードの中で、配列変数 a と b の値を適当に変えて実行してみると良いでしょう。


### PHP RESTクライアントのコードの解説
最初にコード全体を提示して、ポイントになる箇所を補足したいと思います。

~~~lang:php
     1	<?php
     2	include "cfenv.php";
     3	
     4	$ch = curl_init();
     5	$vcap = new Cfenv();
     6	$vcap->byInstName('pycalcxxu');
     7	
     8	// POST
     9	$form = array(
    10	   'a' => 391.345,
    11	   'b' => 5.4452
    12	);
    13	
    14	$options = array(
    15	    CURLOPT_POST => 1,
    16	    CURLOPT_HEADER => 0,
    17	    CURLOPT_URL => $vcap->uri,
    18	    CURLOPT_FRESH_CONNECT => 1,
    19	    CURLOPT_RETURNTRANSFER => 1,
    20	    CURLOPT_FORBID_REUSE => 1,
    21	    CURLOPT_TIMEOUT => 4,
    22	    CURLOPT_USERPWD => $vcap->user.":".$vcap->pass, 
    23	    CURLOPT_POSTFIELDS => http_build_query($form)
    24	);
    25	
    26	curl_setopt_array($ch, $options);
    27	$resp = curl_exec($ch);
    28	$result = json_decode($resp);
    29	print "Result = ".$result->{'ans'}."\n";
    30	
    31	curl_close($ch);
    32	?>
~~~


#### 2行目 cfenv.php

このモジュールは、Bluemixにデプロイした場合は、環境編集によって接続先情報を取得し、ローカル環境でテストする場合は、vcap-local.jsonから接続先情報を読み取れるためのモジュールです。  

#### 8〜12行目 
POSTするためのデータで、サンプルのPython RESTサーバーは、a と b の乗算結果を返します。

#### 14〜24行目
POSTするために必要なパラメータで、URIやベーシック認証のユーザーとパスワード、フォームデータなどがセットされます。

#### 26行目〜
HTTP POSTを実行して結果を取得、画面へ表示します。



## BlemixへのRESTサーバーのデプロイ

Pythonの実行モジュールをBluemixへデプロイするためのファイルは、以下の４つのファイルです。

~~~
vagrant@vagrant-ubuntu-trusty-64:~/REST_Sample_Python/restServerPython$ ls
app.py	manifest.yml  requirements.txt	runtime.txt
~~~

* app.py : RESTサービスのPythonプログラム
* manifest.yml : Bluemixへデプロイするためのマニフェスト
* requirements.txt : Pythonのアプリを実行するために必要なパッケージ
* runtime.txt : Pythonのバージョン指定 



~~~file:manifest.yml
     1	---
     2	applications:
     3	- buildpack: https://github.com/cloudfoundry/python-buildpack
     4	  name: python-rest-svc
     5	  memory: 64MB
     6	  disk_quota: 256MB
     7	  random-route: false
     8	  domain: mybluemix.net
     9	  name: pyCalcxx
    10	  command: python app.py
~~~

ポイントは、3行目のビルドパックで、Cloud Foundry の最新のビルドパックを指定しています。 それから、10行目で pythonを開始するために、スタートコマンドとして、python app.py を指定します。


追加のモジュールが発生した場合など、最後に次のコマンドを実行して、Bluemixが、デプロイ時に必要なモジュールを導入できる様にします。

~~~
pip freeze > requirements.txt
~~~

Pythonのバージョンを指定するファイルです。 ビルドパックで利用できる範囲がありますので、詳しくは、Cloud Foundry のマニュアルを参照ください。

~~~file:runtime.txt
python-2.7.13
~~~


Blumiex CLIでログインして、アプリをデプロイします。

~~~
bx login
~~~

~~~
bx cf push
~~~

正常にデプロイが完了したら、テストクライアントのURIを変えてテストしてみます。 デプロイしたRESTサービスのURLを知るには、次の様にします。

~~~
vagrant@vagrant-ubuntu-trusty-64:~/REST_Sample_Python/restServerPython$ bx cf a
Invoking 'cf a'...

name          requested state   instances   memory   disk   urls
pyCalcxx      started           1/1         64M      256M   pycalcxx.mybluemix.net
~~~

このケースでは、pycalcxx.mybluemix.netと解りましたので、クライアントのvcap-local.jsonの7行目を修正してテストします。

~~~file:vcap-local.json
     7	     "uri": "https://pycalcxx.mybluemix.net/calc", 
~~~     

ローカルで実行した時の同じ様に、結果が得られれば、RESTサーバーのデプロイ完了です。

~~~
vagrant@vagrant-ubuntu-trusty-64:~/REST_Sample_Python/restClientPython$ php client.php 
Result = 2130.951794
~~~


## ユーザー提供のサービスとして定義する

これで、やっと、ユーザー提供のサービスが実行できる様になりました。 ユーザー提供のサービスとして設定していきます。


このPython RESTサービスの名前とアドレスを確認します。

~~~
vagrant@vagrant-ubuntu-trusty-64:~/REST_Sample_Python/restClientPython$ bx cf a
Invoking 'cf a'...

name          requested state   instances   memory   disk   urls
pyCalcxx      started           1/1         64M      256M   pycalcxx.mybluemix.net
~~~


前述のアプリの名前とは別に、サービス名を設定して、-p 認証情報のアドレスを指定します。

~~~
vagrant@vagrant-ubuntu-trusty-64:~/REST_Sample_Python/restClientPython$ bx cf cups pycalcxxu -p '{"username":"user", "password":"pass", "uri": "https://pycalcxx.mybluemix.net/calc"}'
~~~

これで、サービスをバインドした時にアプリへ与えられる認証情報が提供される様になります。　そして、Bluemixのコンソール画面から見ると、次の様に、Bluemixのサービスとユーザー提供サービスが同列に見える様になります。

![スクリーンショット 2017-08-23 10.38.06.png](https://qiita-image-store.s3.amazonaws.com/0/42829/d94a8982-7c40-769f-7140-55f3535f1700.png "スクリーンショット 2017-08-23 10.38.06.png")

例えば、他のアプリから接続して、次の様に使うことができます。
![スクリーンショット 2017-08-23 10.25.06.png](https://qiita-image-store.s3.amazonaws.com/0/42829/471d78a0-00c2-4d3d-2f1a-51ae3a949cd8.png "スクリーンショット 2017-08-23 10.25.06.png")

もちろん ウェブのGUI画面からだけでなく、Bluemix CLIコマンドからも、バインド、アンバインドができます。

~~~
bx cf bs phpSamplexx pycalcxxu
~~~





アプリ側からサービス資格情報を参照すると、次の様にアプリケーションへ、提供されている事が解ります。

~~~
bx cf env phpSamplexx
<省略>
System-Provided:
{
 "VCAP_SERVICES": {
  "user-provided": [
   {
    "credentials": {
     "password": "pass",
     "uri": "https://pycalcxx.mybluemix.net/calc",
     "username": "user"
    },
    "label": "user-provided",
    "name": "pycalcxxu",
    "syslog_drain_url": "",
    "tags": [],
    "volume_mounts": []
   }
  ]
 }
}
~~~

## ユーザー提供のRESTサービスを利用するサンプルコード

このPythonのRESTサービスと連携するPHPのコードを GitHubに置きましたので、合わせて参照いただくと、理解が深まるのではないかと思います。 https://github.com/takara9/php_sample_apl/tree/db2 

これはサンプルとしての簡単な内容ですが、Pythonを使った機械学習のモデルを活用するなどの、アプリケーションに応用できるものと思います。

### 画面コピー１ 入力画面
![スクリーンショット 2017-08-23 11.03.56.png](https://qiita-image-store.s3.amazonaws.com/0/42829/53d9fe85-2fa4-6c32-583d-bbaab373b276.png "スクリーンショット 2017-08-23 11.03.56.png")

### 画面コピー２  REST処理結果の表示画面
![スクリーンショット 2017-08-23 11.04.12.png](https://qiita-image-store.s3.amazonaws.com/0/42829/bfa95c43-7a45-985b-4d5f-9fe013ef816e.png "スクリーンショット 2017-08-23 11.04.12.png")



## まとめ

今回は、一つのステージに限定した話ですが、DevOpsなど開発と本番運用が並行する様なケースでは、環境側からアプリへ、マイクロサービスの接続先情報を提供できるので、開発の忙しい最中に、誤って接続先を間違えて本番データを壊すといった事故を防ぐ事ができるため、安心安全なDevOps環境として利用できると思います。

それから本番運用では、マイクロサービスのパフォマンスが不足すれば、インスタンスの数を簡単にインスタンスを増やせるという利点もあります。 マイクロサービスのルーティングに、負荷分散を受け持つ gorouter が入っているため、図の様に+をクリックするだけで、インスタンスを増やして、スケールアウトする事ができます。
![スクリーンショット 2017-08-23 11.14.03.png](https://qiita-image-store.s3.amazonaws.com/0/42829/d0c77595-e44a-3b50-8482-138781282007.png "スクリーンショット 2017-08-23 11.14.03.png")


もうちょっと、便利な機能が欲しくなってきますよね。 次はBluemix APIコネクトの出番ですかね。


# 参考資料
(1) Flask マイクロフレームワーク http://flask.pocoo.org/
(2) Flask RESTful https://flask-restful.readthedocs.io/en/0.3.5/
(3) Flask-BasicAuth https://flask-basicauth.readthedocs.io/en/latest/
(4) Python Buildpack http://docs.cloudfoundry.org/buildpacks/python/index.html
(5) PHPマニュアル curl exec http://php.net/manual/ja/function.curl-exec.php
(6) Cloud Foundry(Diego)でPython Buildpackを利用しFlaskアプリをデプロイする勘所 http://qiita.com/yuta_h3/items/4798ec83a26391c5627f
(7) Cloud Foundry Documentation Python Buildpack https://docs.cloudfoundry.org/buildpacks/python/index.html
(8) Bluemixアプリ開発と管理#21.5 ユーザー提供サービスインスタンス http://qiita.com/MahoTakara/items/9d03414689fe1b2b9cd2