まだプログラマーですが何か?

プログラマーネタ中心。たまに作成したウェブサービス関連の話も https://twitter.com/dotnsf

IBM Bluemix からも提供されている、オープンソースのイベント駆動型サービス OpenWhisk が正式版としてリリースされました:
2016121401


この OpenWhisk は最近流行りの "Function as a Service" をオープンソースで実装したものです。Bluemix の機能の1つとしても提供されていますが、オープンソース版をこちらから入手して利用することもできます:
https://github.com/openwhisk/openwhisk


OpenWhisk ではイベント等のきっかけ(Trigger)に対する個別の処理が、アクション(Action)として紐付け(Rule)られています。この Trigger - Action - Rule の組み合わせでアプリケーションを実装し、その処理が実行されるタイミングで必要なだけのマシンリソースが必要なだけ割り当てられて実行されます。そしてその際に利用したリソース(CPU 時間やメモリ量)に応じて料金が発生します。すなわち CPU を使わない待ち時間などには料金が発生しないことになるので、その意味でもリーズナブルな料金体系になっていると言えます。

なお 2016/Dec/14 時点での料金表はこちらです:

https://console.ng.bluemix.net/openwhisk/learn/pricing
2016121402


↑128MB メモリを使って、0.5 秒実行するようなアクションを月 5,000,000 回実行するまでであれば無料枠内で収まる、ということになります。ある程度は無料でも使えそうです。 また上の表をみてもわかりますが、時間のかかるアクションや多くのメモリを必要とするアクションは(比例的とは言えないくらいに)割高になります。個々のアクションを小さく分割して実行した方が割安な運用ができそうです。


さて、このアクションですが、2016/Dec/14 現在の OpenWhisk では初めからシステムで用意されているアクション以外に以下のプログラミング言語を使って実装することが可能です:
  • JavaScript
  • Python
  • Swift
  • Java

このうち最も新しいのが Java です。他の比較的新しめの言語は苦手というエンジニアもいらっしゃると思いますが、最近になって Java でも OpenWhisk のアクション実装が可能になりました。以下にその手順を紹介します。

まず前提の準備として OpenWhisk のコマンドラインインターフェースである wsk コマンドをセットアップします。Bluemix ダッシュボードからの指示にしたがって、以下から自分の環境にあった wsk コマンドをダウンロードし、インストールします(UNIX 系システムでは wsk バイナリをダウンロードして、パスを通して、実行権限を付けます):
https://openwhisk.ng.bluemix.net/cli/go/download/
2016121404


そして名前空間と許可キーの初期セットアップを行います。具体的には以下のサイトの 2. の中にあるコマンドを「コピー」し、wsk をインストールしたシステムのコマンドラインにペーストして実行するだけです:

https://console.ng.bluemix.net/openwhisk/learn/cli
2016121405


これで wsk コマンドの設定は完了です。

更に今回は Java を使ってアクションを実装/実行します。Java で OpenWhisk のアクションを実装するには JDK 1.8 が必要です。またコンパイル時に Google GSON ライブラリが必要になります。以下のサイトからソースを入手してビルドするなどして gson-2-X-X.jar ファイルを入手し、CLASSPATH に登録しておきます:
https://github.com/google/gson


改めて、実際に処理を行うアクションを Java で記述します。サンプルとして以下を用意しました(JavaHello.java)。必要に応じて処理内容を変更してください。最後に実行結果をcom.google.gson.JsonObject 型の JSON オブジェクトで返すような内容にしてください:
import com.google.gson.JsonObject;

public class JavaHello{
  public static JsonObject main( JsonObject args ){
    String name = "World";
    if( args.has( "name" ) ){
      name = ( String )args.getAsJsonPrimitive( "name" ).getAsString();
    }
    JsonObject obj = new JsonObject();
    obj.addProperty( "greeting", "Hello " + name + "!" );
    return obj;
  }
}

↑ちなみにこのサンプルを実行すると { "greeting": "Hello XXXX!" } という JSON を返します。XXXX 部分には実行時の name パラメータで与えた文字列が入ります(デフォルトは World)。

このアクションソースファイルを JDK 1.8 でコンパイルして JAR アーカイブします:
$ javac JavaHello.java
$ jar cvf JavaHello.jar JavaHello.class

OpenWhisk アクションとして(JavaHello という名前で)登録します:
$ wsk action create JavaHello JavaHello.jar --main JavaHello

OpenWhisk アクションとして実行してみます(青字は実行結果):
$ wsk action invoke --blocking --result JavaHello  (パラメータなし)
{
  "greeting": "Hello World!"
}

$ wsk action invoke --blocking --result JavaHello --param name DOTNSF  (パラメータ指定)
{
  "greeting": "Hello DOTNSF!"
}

動きました! これで Java でも OpenWhisk のアクションを実装できることが確認できました。



(参考)
https://github.com/openwhisk/openwhisk/blob/master/docs/actions.md
 

JavaScript で AJAX を多く使ったことがある人にとって、「クロスドメイン問題」は一度は体験する、避けて通れない問題だと思います。

簡単に補足すると、例えば異なる aaa.com と bbb.net という2つのドメイン間で AJAX の実行を許可しない、という問題です。以下の例では aaa.com ドメイン内に www.aaa.com というアプリケーションサーバーと、api.aaa.com という API サーバーがあり、bbb.net というドメイン内に api.bbb.net という API サーバーがあることを想定しています。www.aaa.com と api.aaa.com は同一ドメインなので、何の制約もなく AJAX で API を実行できるのですが、ドメインの異なる api.bbb.net に用意された API を www.aaa.net から AJAX で実行しようとすると、このクロスドメイン問題の制約に引っかかって実行できない、というものです:
2016121303

ちなみにこのクロスドメインの制約はあくまで「ウェブブラウザの制約」です。ドメインをまたいだ API を実行するような JavaScript はウェブブラウザが許可しない、という意味です。なので、例えば www.aaa.com 上の Java アプリケーションや PHP アプリケーション、はたまたサーバーサイド JavaScript である Node.js アプリケーションなどから api.bbb.net 上の API を実行する、という場合は何の制約もなく動きます。

またこの問題は api.bbb.net 側で「他のドメインからの AJAX 実行を許可する」という設定をしておけば回避することができます。具体的には API サーバーの HTTP レスポンスヘッダに Access-Control-Allow-Origin を含めるようにして、そこに "*"(どこからでも許可する)とか "*.aaa.com" (aaa.com からのリクエストは許可する)とかいった値が返されるような設定になっていれば使える、ということです(つまり API 側で許可するよう設定されている必要があります):
2016121304


さて、先日 curl で IBM WatsonVisual Recognition API (画像認識 API)を使っていてふと気付いたことがあります。curl は -i オプションを使うと HTTP レスポンスヘッダが表示されるようになるので、これを ON にしてデバッグしていました・・・:
2016121301


ん??
2016121302


上記で説明した "Access-Control-Allow-Origin" が HTTP レスポンスヘッダに含まれてます。しかも値が * ということは、この API はどこからでも AJAX で呼べるということ!?

以前にこの記事を作ってた頃は、このヘッダはなかったと記憶してる(Chrome Extension なので回避できている、という認識でした)。いつ仕様が変わったんだろ・・・

試しに、curl で実行したのと同様の Visual Recognition API を実行する HTML ファイルを作ってみました(実行する場合、ソースコード内の (APIKEY) 部分は自分の Visual Recognition API サービスインスタンスの API キーを指定してください:
<html>
<head>
<title>DHTML</title>
<script src="https://code.jquery.com/jquery-2.0.3.min.js"></script>
<script>
function classify(){
  var imgurl = $('#imgurl').val();
  var url = "https://gateway-a.watsonplatform.net/visual-recognition/api/v3/classify?api_key=(APIKEY)&version=2016-05-20&url=" + encodeURI( imgurl );
  $.ajax({
    type: "GET",
    url: url,
    success: function( data ){
      var html = "<img src=\"" + imgurl + "\"/><br/><table border=\"1\">";
      var classes = data.images[0].classifiers[0].classes;
      for( i = 0; i < classes.length; i ++ ){
        var obj = classes[i];
        var class_name = obj['class'];
        var class_score = obj['score'];
        html += ( "<tr><td>" + class_name + "</td><td>" + class_score + "</td></tr>" );
      }
      html += "</table>";
      $('#result').html( html );
    },
    error: function( XMLHttpRequest, textStatus, errorThrown ){
      console.log( "error: " + textStatus );
    }
  });
}
</script>
</head>
<body>
<input type="text" name="imgurl" id="imgurl" value=""/>
<input type="button" value="classify" onClick="classify();"/>
<hr/>
<div id="result"></div>
</body>
</html>

これをウェブブラウザからローカルファイル扱いで開き(URL は file:/// で始まる)、画像 URL をテキストボックスに入れて classify ボタンをクリックします。すると・・・
2016121305


↑ 全く異なるドメイン(というかローカルファイルシステム)からの HTTP リクエストであるにも関わらず、AJAX 内で呼び出している Visual Recognition API が正しく実行され、画像認識結果を表示することができました。こりゃ便利!

IBM Bluemix の利用料金は Bluemix ログイン後にアカウントメニューの「使用状況ダッシュボード」から確認することができます:
2016120801


組織とデータセンターの地域を指定して課金対象を絞りこむことができます。以下は月毎の推移グラフ:
2016120802


下にスクロールすると現在の月の、現時点での消費額を確認することもできます:
2016120803


さて、これらは利用料金を Bluemix のウェブコンソールにログインした上で確認する、という手順です。この内容を Bluemix のウェブコンソールを使わずに API で取得する方法は・・・ ありました!以下に Billing API を使って外部から利用料金を確認する手順を紹介します。

Billing API を実行する上で、以下の情報を指定する必要があります:
(1) 対象組織の GUID
(2) 対象地域(データセンター)
(3) 対象年月(YYYY-MM)
(4) OAuth トークン

(2) と (3) は指定するだけですが、(1) の組織 GUID と (4) の OAuth トークンはあらかじめ調べておく必要があります。仮に (2) はアメリカ南部("us-south")、(3) は 2016-11 を指定するものと仮定しておきます。


(1) の GUID は cf ツールを使って調べます。まずは利用料金を調べたい地域の API サーバーに cf ツールでログインします(下の例では US データセンターを指定しています):
$ cf login -a https://api.ng.bluemix.net/ -u (ユーザーID)

対象とする組織の名称(ID)がわかっている場合は不要ですが、組織 ID を確認する場合は "cf orgs" コマンドを実行します。ログインした ID で利用中の組織 ID の一覧が表示されます:
$ cf orgs

組織 ID が確認できている場合は、以下のコマンドでその組織の GUID を取得できます:
$ cf org (組織ID) --guid


次に (4) の OAuth トークンを取得します。これは同様に cf ツールで "cf oauth-token" コマンドを実行します。すると "bearer " から始まるトークン文字の羅列が戻ってきます。これが OAuth トークンです:
$ cf oauth-token
bearer XXXXXXXXXX....(文字の羅列)....XXXXXXXXXX

これで (1), (2), (3), (4) 全ての情報が揃いました。これらの情報を使って目的月(今回は 2016-11)の利用料金をコマンドラインから調べてみます。実行コマンドと、その実行結果(の一部)は以下のようになります:
$ curl -v -X GET -H "Authorization: bearer (OAuth トークン文字列)" "https://rated-usage.ng.bluemix.net/v2/metering/organizations/us-south:(組織 GUID)/usage/2016-11" | python -m json.tool

{
  "organizations": [
    {
      "billable_usage": {
        "spaces": []
      },
      "country_code": "JPN",
      "currency_code": "JPY",
      "id": "********-****-****-****-************",
      "name": "dotnsf@jp.ibm.com",
      "non_billable_usage": {
        "spaces": [
          {
            "applications": [],
            "containers": [],
            "id": "********-****-****-****-************",
"name": "********-****-****-****-************",
"services": [ { "id": "********-****-****-****-************",
"instances": [ { "id": "********-****-****-****-************",
"name": "IBM Insights for Twitter-5b", "plan_id": "********-****-****-****-************",
"usage": [ { "applicationId": "********-****-****-****-************",
"cost": 0, "name": "dotnsf-php-20161026", "quantity": 22066, "unit": "TWEET", "unitId": "TWEETS_PER_MONTH" } ] } ], "name": "twitterinsights" } ] }, { "applications": [ { "id": "********-****-****-****-************",
"name": "dotnsf-cloudant", "usage": [ { "buildpack": "********-****-****-****-************",
"cost": 2557.8000000000002, "quantity": 348, "runtime": { "id": "********-****-****-****-************",
"name": "liberty-for-java_v3_2-20160822-2200" }, "unit": "GB-HOURS", "unitId": "GB_HOURS_PER_MONTH" } ] }, : :

青字部分が実行コマンドで、その下が実行結果です。実行結果のところどころに "cost": ***** という形で各ランタイムやサービスごとの料金が表示されていることがわかります。

というわけで、この API を使うことで Bluemix の利用料金をコマンドラインから参照する、ことが実現できそうです。


(参考)
http://theblasfrompas.blogspot.jp/2016/02/invoking-billing-api-for-bluemix-public.html

このページのトップヘ