(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アプリケーションを作って下さい!