search
LoginSignup
13

More than 3 years have passed since last update.

posted at

updated at

FlaskアプリをIBM Cloudにデプロイする

はじめに

仕事柄、Watson Machine Learning / Watson APIを呼び出すアプリをIBM Cloud上でよく作っています。
対象がWatson APIの時は Watson APIを使ったNode.jsアプリの標準実装パターンのとおり、サーバーサイドをNode.jsで実装することがほとんどだったのですが、相手がWatson Machine Learningになると、トークン取得、予測API呼出しの二段階のAPI呼出しが必要になり、非同期通信しか使えないNode.jsでの実装が面倒になってきました。
そんなこともあり、最近はひたすらPython + Flaskでサーバーサイドを実装するということをしています。
そこで、PythonをIBM Cloudで動かすためのお作法、クライアントサイドのJavaScriptからサーバーサイドのFlaskにパラメータを渡すときのお作法をメモとしてまとめました。

サンプルコード

サンプルコードはいつものようにGithubにあげてあります。
アプリの画面イメージはこちら。

flask-web1.png

ブラウザから引数1、引数2を読み込んで、/sendメソッドでサーバーサイドのFlaskに送り、サーバー側で足し算した結果を返すというシンプルなものです。

IBM Cloudへの導入手順はリンク先のGithub README.mdに記載していますが、

$ cf login
$ cf push <service name>

とするだけで可能です。

IBM Cloudにデプロイするためのお作法

Githubにアップしているソースのトップレベルのリストを以下に示します。

-rw-r--r--@  1 makaishi  staff      5  2  8 20:01 .cfignore
drwxr-xr-x  13 makaishi  staff    416  6  2 15:47 .git
-rw-r--r--@  1 makaishi  staff     15  4 30 09:26 .gitignore
-rw-r--r--   1 makaishi  staff      6  6  2 15:03 .python-version
-rw-r--r--@  1 makaishi  staff  11357  2  8 09:06 LICENSE
-rw-r--r--@  1 makaishi  staff     22  2  8 09:06 Procfile
-rw-r--r--@  1 makaishi  staff   3674  6  2 15:31 README.md
-rw-r--r--@  1 makaishi  staff     43  6  2 14:43 manifest.yml
drwxr-xr-x   5 makaishi  staff    160  6  2 15:30 readme_images
-rw-r--r--@  1 makaishi  staff     15  6  2 14:42 requirements.txt
-rw-r--r--@  1 makaishi  staff    970  6  2 15:42 server.py
drwxr-xr-x   5 makaishi  staff    160  5 29 14:08 static
drwxr-xr-x   5 makaishi  staff    160  6  2 14:49 templates

この中で重要なものについてそれぞれ説明します。

manifest.yml

Cloud Foundryにデプロイする際に必要な情報を定義するファイルです。
今回のサンプルでは利用していませんが,IBM Cloudの他のサービスをバインドする際には、このファイルにバインド先定義名を記載します。

---
applications:
- path: .
  memory: 128M

例えば、Pythonの中でWatson Machine Learningを呼び出している場合は、次のような形になります。

---
declared-services:
  machine-learning-1:
    label: pm-20
    plan: lite
applications:
- path: .
  memory: 128M
  services:
  - machine-learning-1

requirements.txt

Cloud Foundry標準のPython環境で不足するモジュール名を列挙して、デプロイ時に追加導入するモジュールを指定します。今回のサンプルでは以下の2行があるのみです。

requests
flask

例えば、Watson Machine Learningを呼び出すアプリの場合、こんな設定になっています。

cfenv
requests
flask
Image
python-dotenv
numpy

Procfile

WEBサービス起動時のコマンドを指定します。今回の例では以下の通りです。

web: python server.py

server.py

サーバーサービスのPythonプログラムです。今回の例では以下のようになっています。

server.py
#!/usr/bin/python
# -*- coding: utf-8 -*-
import urllib3, requests, json
import os
from os.path import join, dirname
from flask import Flask, request, render_template

app = Flask(__name__)

@app.route('/')
def top():
    name = "Top"
    return render_template('sample.html', title='Flask Sample Web', name=name)

# 「送信」ボタンが押された時の処理
@app.route('/send', methods=['POST'])
def send():
    # パラメータの受け取り
    req_json = request.json
    arg1 = req_json['ARG1']
    arg2 = req_json['ARG2']

    # ダミーのサーバー処理
    sum = arg1 + arg2
    print(arg1, arg2, sum)

    # 戻り用変数の定義
    ret = {'ARG1': arg1, 'ARG2': arg2, 'SUM': sum}

    # json.dumps の結果を戻り値とする
    return(json.dumps(ret))

@app.route('/favicon.ico')
def favicon():
   return ""

port = os.getenv('VCAP_APP_PORT', '8000')

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=int(port), debug=True)

この中で重要なのは、クライアントからのパラメータ受け取りの実装と、戻り値の受け渡し方法です。
それぞれ、次のようになっています。どちらもJSONで行うところがポイントです。

受け取り

# パラメータの受け取り
req_json = request.json
arg1 = req_json['ARG1']
arg2 = req_json['ARG2']

戻し

# 戻り用変数の定義
ret = {'ARG1': arg1, 'ARG2': arg2, 'SUM': sum}

# json.dumps の結果を戻り値とする
return(json.dumps(ret))

templates/sample.html

FlaskをWebサーバーとして動かす際のHTMLファイルです。
server.pyの中のrender_templare関数呼び出しで、ファイル名を指定します。
具体的なソースは次のとおりです。

<html>
<head>

{% extends "layout.html" %}
{% block body %}
</head>
<body>
<h2>Flask サンプルアプリ</h2>
<hr>
<div>
<label  for="arg1_id">引数1:</label>
<input type="text" value="123.45" name="arg1_id" id="arg1_id" />
<br>
<label  for="arg1_id">引数2:</label>
<input type="text" value="0.678" name="arg2_id" id="arg2_id" />
<br>
<label  for="sum_id">合計: </label>
<input type="text" name="sum_id" disabled="disabled" id="sum_id" />
<br>
<br>
<input type="button" name="button" value="送信" id="send"/>
<hr>

</body>
</html>

<script>

$(function(){
    $('#send').click(send) 
});

function call_flask( type, url, error ) {

    // 画面からパラメータを取得して辞書データにする
    data1 = {
        "ARG1": parseFloat($('#arg1_id').val()),
        "ARG2":  parseFloat($('#arg2_id').val())
    };
    console.log(data1);

    // JSON.stringifyで辞書データをJSON形式にシリアライズする
    json1 = JSON.stringify(data1);

    // ajax関数でサーバーサイド機能の呼出し
    $.ajax({
        type: type,
        url: url,
        dataType: "json",
        data: json1,
        processData: false,
        contentType: "application/json",
        cache: false,
        timeout: 600000,
        success: function (data2) {
            console.log('send callback')
            console.log(data2);
            sum = data2['SUM']
            console.log(sum)
            $('#sum_id').val(sum)
        }
    });
}

function send() {
    call_flask( 'POST', '/send', 
            function(XMLHttpRequest,textStatus,errorThrown){alert('error');} );
}

</script>

{% endblock %}

このHTMLで重要なのは、画面項目から値を取ってきて、ajaxで引数を渡すところです。
その実装は以下のとおりとなっています。
画面から値を取ってきて、Javascriptオブジェクトとして組み立てた後、JSON.stringify関数でデータをJSON形式にシリアライズしてからajax関数の引数dataで渡すところがポイントになります。

// 画面からパラメータを取得して辞書データにする
data1 = {
   "ARG1": parseFloat($('#arg1_id').val()),
   "ARG2":  parseFloat($('#arg2_id').val())
};

// JSON.stringifyで辞書データをJSON形式にシリアライズする
json1 = JSON.stringify(data1);

// ajax関数でサーバーサイド機能の呼出し
$.ajax({
    type: type,
    url: url,
    dataType: "json",
    data: json1,
    processData: false,
    contentType: "application/json",
    cache: false,
    timeout: 600000,
    success: function (data2) { // 以下略

templates/layout.html

sample.htmlから呼び出される雛形HTMLです。
2つのHTMLを合成するレンダリング処理はFlaskが行います。

<!doctype html>
<title>Python Flask サンプル</title>
<link rel=stylesheet type=text/css href="{{ url_for('static', filename='style.css') }}">
<link href='https://fonts.googleapis.com/css?family=Lato:300,400,300' rel='stylesheet' type='text/css'>
<script src="{{ url_for('static', filename='jquery.js') }}"></script>

<div class=page>
  {% block body %}{% endblock %}
</div>

LICENSE

ソースをWebで公開する際のライセンスの記述を行います。
私はApacheライセンスを使っています。

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
What you can do with signing up
13