Python
PHP
Bluemix

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

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

この記事は、Bluemixアプリ開発と管理#21.5 ユーザー提供サービスインスタンスの具体的な実装例です。

実装内容

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

スクリーンショット 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サンプル・コード

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 フレームワークを利用しています。 インポートしている3つのモジュールは、次のURLで詳しく知ることができます。

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" に書き換えます。

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クライアントのコードの解説

最初にコード全体を提示して、ポイントになる箇所を補足したいと思います。

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へデプロイするためのファイルは、以下の4つのファイルです。

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のバージョン指定
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 のマニュアルを参照ください。

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行目を修正してテストします。

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

例えば、他のアプリから接続して、次の様に使うことができます。
スクリーンショット 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を使った機械学習のモデルを活用するなどの、アプリケーションに応用できるものと思います。

画面コピー1 入力画面

スクリーンショット 2017-08-23 11.03.56.png

画面コピー2 REST処理結果の表示画面

スクリーンショット 2017-08-23 11.04.12.png

まとめ

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

それから本番運用では、マイクロサービスのパフォマンスが不足すれば、インスタンスの数を簡単にインスタンスを増やせるという利点もあります。 マイクロサービスのルーティングに、負荷分散を受け持つ gorouter が入っているため、図の様に+をクリックするだけで、インスタンスを増やして、スケールアウトする事ができます。
スクリーンショット 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