LoginSignup
10
19

More than 5 years have passed since last update.

IBM Cloud上のWatsonアプリをローカル環境で開発・デバッグする際のTips

Last updated at Posted at 2017-10-10

(2018-07-01 新しい方式の認証について追記)

はじめに

最近、IBM Cloud上でサンプルのWatsonアプリケーションを自分で作ることが何度かあったのですが、表題のことに関してまとまった情報がなかなかなくて、調べるのに苦労したので、自分の備忘録を兼ねて記載します。

認証情報

WatsonなどほとんどのIBM Cloudサービスは通常サービス(正確にはインスタンス)固有のuserid, passwordを持っていて、それをどのように設定するかという問題です。

まず、以下はIBM Cloudにおける基礎的な知識

IBM CloudではCloudFoundaryアプリケーションに、Watsonなどのサービスを「バインド」することがGUIからもコマンドラインからも可能です。
これを行うことにより、CloudFoundaryアプリケーション側では、次のような値が環境変数VCAP_SERVICESに設定されます。(usernameとpasswordは伏せ字にしています)

{
    "conversation": [
        {
            "credentials": {
                "url": "https://gateway.watsonplatform.net/conversation/api",
                "username": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
                "password": "xxxxxxxxxxxx"
            },
            "syslog_drain_url": null,
            "volume_mounts": [],
            "label": "conversation",
            "provider": null,
            "plan": "free",
            "name": "conversation-1",
            "tags": [
                "watson",
                "ibm_created",
                "ibm_dedicated_public",
                "lite"
            ]
        }
    ]
}

IBMがWatson Developers Cloudで提供しているライブラリを利用してWatson APIを呼び出すと、自動的にこの認証情報を見つけ出してAPI呼出し時の引数とするようになっています。なので、IBM Cloud上で動かすことが前提で、かつライブラリ経由でWatson APIを呼び出すのであれば、基盤的にサービスのバインドだけ気にしておけば、コードにその記載をする必要はありません。
また、API呼出しをライブラリ経由でなく直接行う場合も、次のようなコードで同じように環境変数から認証情報を取得することが可能です。
Pythonのアプリケーションから、ライブラリを使わずにDiscovery Newsを呼ぶ出すためのサンプルコードイメージを下記に示します。

import requests
environment_id = 'system'
collection_id = 'news'
vcap = json.loads(os.getenv("VCAP_SERVICES"))['discovery']        
username = vcap[0]['credentials']['username']
password = vcap[0]['credentials']['password']    

endpoint = "https://gateway.watsonplatform.net/discovery/api/v1/environments/"+environment_id+"/collections/"+collection_id+"/query?version=2017-09-01&"
    try:
        get_url = endpoint+"query=title:("+combo+")|enriched_title.entities.text:("+combo+")&count=50&return=title,url"
        results = requests.get(url=get_url, auth=(username, password)) 
        response = results.json()

問題は、この環境はあくまでIBM Cloud上で利用可能なものであるため、ローカル環境では別の方法を考える必要がある点です。
ここはいろいろな流儀があるのですが、試行錯誤の結果、私がたどり着いたのは次のように方法でした。以下に、Node.jsのサンプルコードを記載します。

// 構成情報の取得
const fs = require('fs');
if (fs.existsSync('local.env')) {
  console.log('構成情報をlocal.envから取得します');
  require('dotenv').config({ path: 'local.env' });
}

// Discoveryインスタンスの生成
const watson = require('watson-developer-cloud');
const discovery = new watson.DiscoveryV1({
    version_date: '2017-08-01'
});

ポイントは次のような点です。

  • npmのモジュールとして"fs"と"dotenv"を利用します。

  • リポジトリの中に以下のような雛形ファイル local.env.sampleを含めておいて、ローカル環境テスト時にはこれをlocal.envにコピーして利用します。
    (デフォルトではローカルの構成ファイル名は ".env" なのですが、こうするとMacのFinderのデフォルト設定で見えないなど、ガイド書くのが面倒になるので、操作を簡単にする目的で設定ファイル名はあえて "local.env"としています。)

CONVERSATION_USERNAME=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
CONVERSATION_PASSWORD=xxxxxxxxxxxx
  • npmモジュールのdotnevのconfigメソッドは、上のような書式の設定ファイルを読み込み、環境変数CONVERSATION_USERNAME, CONVERSATION_PASSWORDに値を設定します。
  • DevelpersCloudのライブラリ new watson.XXXXはインスタンス生成時に環境変数XXXX_USERNAME, XXXX_PASSWORDの設定があると、そこから認証情報を取得し、API呼出し時のパラメータとします。 XXXXの部分は、どのAPIを呼び出すかにより変わります。現在利用可能なAPIでのXXXXのそれぞれの名称は次のとおりです。 例えばSTTであればSPEECH_TO_TEXT, TTSであればTEXT_TO_SPEECHとなります。
CONVERSATION
DISCOVERY
DOCUMENT_CONVERSION
LANGUAGE_TRANSLATOR
NATURAL_LANGUAGE_CLASSIFIER
NATURAL_LANGUAGE_UNDERSTANDING
PERSONALITY_INSIGHTS
RETRIEVE_AND_RANK
SPEECH_TO_TEXT
TEXT_TO_SPEECH
TONE_ANALYZER
VISUAL_RECOGNITION

(2018-07-01追記)
シドニーにインスタンスを作ると、APIKeyによる新しい認証方式となります。
その場合、上記設定ファイルlocal.envは次のようにかわります。
利用するwatson-dveloper-cloudのバージョンは3.5.3以上を使うように注意して下さい。

CONVERSATION_IAM_APIKEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
CONVERSATION_URL=https://gateway-syd.watsonplatform.net/discovery/api

ということで、上に示したようなシンプルなNode.jsコードで

  • IBM Cloud環境でWatsonサービスとのバインドがされている場合、バインド情報から認証情報を取得
  • ローカル環境の場合、ローカルファイルのlocal.envファイル(ソースにある雛形local.env.sampleをコピーして作る)から認証情報を取得

という目的を実現することが可能となります。
あとは、.gitignoreと.cfignoreにlocal.envを記載しておいて、ローカル以外にこの設定ファイルが出ていかないよう注意して下さい。

(補足) IBM Cloud上でサービスの生成、アプリケーションのバインドを行う方法

上で説明したように、IBM Cloud環境であれば、サービスのバインドさえしてあればコード側でuserid, passwordについては何も考える必要なくなります。
しかし、初心者向けにアプリケーションのバインドの手順を示すとき、UIだと画面遷移が多くて結構面倒です。この点に関してもいろいろ試行錯誤の結果、以下の方式が一番シンプルだとの結論に達しました。

サービス、認証情報をCFコマンドで作成

(例) Discovery Serviceを生成する場合

$ cf create-service discovery lite discovery-1
$ cf create-service-key discovery-1 myKey

バインドはmanifest.ymlに記述

(例) 上記で生成したサービスをバインドしたい場合

---
declared-services:
  discovery-1:
    label: discovery
    plan: lite
applications:
- path: .
  memory: 128M
  services:
  - discovery-1

ここまでやっておくと、cf pushコマンド一発でアプリケーションバインドまでできるので、手順書がとてもシンプルになります。

その他のID情報設定

例えばConversation APIのWORDSPACE_IDのように、APIによってはuserid, password以外のID情報が必要な場合があります。
この場合、Developers Cloudのライブラリでも特別な方法は用意されていないので、環境変数などからID値を取得してAPI呼出し時にパラメータで設定する形になります。
ちなみに、環境変数の値をアプリケーションから使う場合、Node.jsでは次のようなコードになります。processという変数はNode.jsがフレームワークとして事前に用意している変数なので、アプリケーション側で何か特別な宣言などを行う必要はありません。

var classifier_id = process.env.CLASSIFIER_ID;

IBM Cloud上の場合

IBM Cloud上の場合は、管理機能で環境変数を設定できるので、それを使って設定する形になります。UIによる方法、コマンドラインによる方法の両方がありますが、コマンドラインの方がガイドがシンプルかと思います。コマンドの書式は次の通りです。

$ cf set-env <app_name> <env_name> <env_value>

ローカルの場合

ローカルの場合は、上に説明したlocal.envに、追加したい環境変数も定義すればいいです。

ホストがlistenするIPアドレス、ポート番号

IBM Cloud上でサービスを動かす場合、listenするポート番号については特別なお作法が必要です。具体的には、VCAP_APP_PORTで示されたポートを指定する形になります。
ローカルでも使えるコードの雛形はNode.jsの場合、以下のようになります。
下記のサンプルコードはローカル実行の場合、6010番固定で動く形になっています。

// VCAP_APP_PORTが設定されている場合はこのポートでlistenする (IBM Cloudのお作法)
var port = process.env.VCAP_APP_PORT || 6010;
app.listen(port, function() {
  // eslint-disable-next-line
  console.log('Server running on port: %d', port);
});

ListenするIPアドレスについても127.0.0.1などを明示的に指定しないようにして下さい。デフォルトは0.0.0.0(すべての宛先アドレスが有効)で、この形でIBM Cloud環境でも動かすようにします。

前提パッケージ・起動コマンド

前提パッケージ、起動コマンドは設定ファイルで指定する形になります。
Node.js と Pythonの場合、具体的には次のような手順です。

Node.jsの場合

Node.jsの場合、構成ファイルは package.jsonファイルとなります。
一番簡単にpackage.jsonファイルを作るには、カレントディレクトリにserver.jsファイルを用意した後、"npm init -f"コマンドを実行して生成します。

その後、追加モジュール導入時は

npm install <package_name> --save

という形で必ず --save オプションを付けるようにして下さい。これにより、pacckage.jsonファイル中のdependenciesに必要モジュールが記録され、IBM Cloudでのデプロイ時にもモジュール不足が起きないようになります。

Pythonの場合

Pythonの場合、動かすのに最低限必要な構成ファイルは次の2つです。

requirements.txt

Pythonでは、追加モジュールの導入は pip コマンドで行いますが、それで導入したモジュール名をこのファイルに列挙します。下記にサンプルを添付します。

requests
flask

Procfile

起動コマンドをこのファイルで指定します。サンプルは以下の通りです。

web: python server.py

これだけの規則を守れば、自由にローカル・IBM Cloud環境どちらでも動くアプリケーションが作れると思います。
面白いIBM Cloudアプリケーションを作って下さい!

10
19
0

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
19