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

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

タグ:openwhisk

IBM Cloud から提供されているサーバーレス環境である Cloud Functions は、オープンソースの Apache OpenWhisk をベースとした FaaS(Functions as a Service)となっており、数あるサーバーレス環境の中でもよりオープンなものとなっています:
2018082301


IBM Cloud の Cloud Functions の場合、専用のダッシュボードが用意されており、手元に開発環境を用意することなく、ここから(ブラウザから)簡易的なアクションを記述/保存/実行することもできるようになっています:
2018082302
 ↑Node.js でアクションを記述している様子


Cloud Functions はこのブラウザからアクションを記述して実行することができる、というメリットがあるのですが、個人的にはここで記述できるアクションはかなり限定的なものだと思っています。理由はここで編集できるものはアクションとして実行する関数の中身だけであって、関連する設定ファイルを変更したりすることはできません。Node.js では npm という強力な外部モジュール連携の仕組みがあり、npm を使って標準の Node.js では提供されていない機能を呼び出して利用することができます。が、この仕組を使うには npm コマンドであらかじめ利用する機能を導入しておくか、 package.json と呼ばれる設定ファイルを変更して利用を宣言しておく必要があります。実行する関数の記述を変更するだけでは対応しきれない仕組みがあり、この機能に関してはこのウェブブラウザからのアクション記述だけでは使えないのでした。


前置きが長くなりましたが、ここからが本エントリの本番です。この外部パッケージを使ったアクションの記述は CLI(Command Line Interface)を使うことで実現できます。その手順を紹介します。

まず利用したい外部パッケージを明示した package.json を用意します。この例では "request" という HTTP クライアントパッケージ(のバージョン 2.88.0)を利用することと、エントリーポイントとなる JavaScript ファイル名(main.js)を指定しています:
{
  "name": "requestAction",
  "version": "0.0.1",
  "main": "main.js",
  "dependencies": {
    "request": "^2.88.0"
  }
}

次にエントリーポイントとなる JavaScript ファイル main.js を以下の内容で用意します。この中で request パッケージを require して使っている点に注目してください:
// main.js
async function myAction( params ){
  try{
    const result = await getHtml( params );
    return result;
  }catch( err ){
    return err;
  }
}

function getHtml( params ){
  return new Promise( function( resolve, reject ){
    if( params.url ){
      const request = require( 'request' );

      var options = {
        method: 'GET',
        url: params.url,
        encoding: null
      };
      request( options, function( err, res, buf ){
        if( err ){
          reject( { status: false, error: err } );
        }else{
          var html = buf.toString( 'utf-8' );
          resolve( { status: true, html: html } );
        }
      });
    }else{
      reject( { status: false, error: 'parameter url is needed.' } );
    }
  });
}

exports.main = myAction;

このアクションでは実行時に url パラメータを指定します(未指定の場合はエラー)。指定された url の HTML コンテンツを request パッケージを使って取得し、その結果を return します。 なお request() は非同期に実行される関数のため、HTML コンテンツを取得する部分だけを関数化(getHTML())し、この関数は Promise オブジェクトを(実行結果が得られたら)返すようにしています。 なお、このアクションは Node.js V8 で実行することを想定しているので、getHTML() 関数を実行する際に async/await を使って非同期に呼ぶようにしています。

この2つのファイル(package.json と main.js)でアクションに必要な設定と内容が用意できました。なお、同じファイルをこちらに用意しておいたので、興味ある方は参照ください:
https://github.com/dotnsf/requestAction


ではこのアクションを実際に IBM Cloud Functions にデプロイして実行するまでの手順を紹介します。今回用意したスクリプトはブラウザからデプロイするのではなく、 CLI (ibmcloud コマンド)を使ってデプロイする必要があります。というわけで、まだ CLI の導入ができていない場合で最初に CLI をデプロイする必要があります。

【ibmcloud コマンド CLI のインストール&セットアップ】
Windows や MacOS、Linux 向けの ibmcloud コマンドは以下のページからインストールできます:
https://console.bluemix.net/docs/cli/reference/ibmcloud/download_cli.html

Windows はダウンロードしてインストール、MacOS や Linux であれば以下のコマンドを実行してインストールします:
$ curl -sL http://ibm.biz/idt-installer | bash

ibmcloud コマンドが導入できたら IBM Cloud Functions を使うためのプラグインをあわせて導入/更新しておきます:
$ ibmcloud plugin install cloud-functions -r Bluemix

また、このあとのコマンドを実行する際に IBM Cloud にログインしている必要があるため、この段階で ibmcloud コマンドによる IBM Cloud へのログインとターゲットの設定を済ませておきます:
$ ibmcloud login

$ ibmcloud target --cf


【Node.js および npm のインストール】
ブラウザからアクションを記述する場合はクライアントに Node.js をインストールする必要はなかったのですが、今回の CLI を使った手順ではローカルで npm コマンドを実行することになります。つまり Node.js や npm がインストールされた環境が必要です。まだ導入していない場合は、公式ページなどから(V8 以上の Node.js の)ダウンロード&インストールを済ませておいてください:
http://nodejs.org/


【ライブラリのインストール、zip ファイル化、デプロイ】
まず package.json の内容にしたがってパッケージ(今回のケースでは request パッケージ)とその依存ライブラリをインストールします:
$ npm install

次にこのディレクトリ内の全ファイルを zip ファイルにまとめます。IBM Cloud Functions では zip ファイルでまとめたアクションをデプロイすることができるため、ここで実行に必要な全てのファイルが揃った zip ファイル(myAction.zip)を作成します:
$ zip -r myAction.zip *

そして、この zip ファイルをアクションとして ibmcloud コマンド CLI でデプロイします:
$ ibmcloud wsk action create requestAction myAction.zip --kind nodejs:8

↑この例ではアクションの名前を "requestAction" として myAction.zip をデプロイしています(アクション名やファイル名は任意です)。なおこの方法でアクションをデプロイする場合、--kind オプションで実行ランタイムの種類を指定する必要があります。今回は Nodejs V8 を指定しています(V6 では未対応の機能を使っているためです)。 また新規にデプロイする場合はこのコマンドになりますが、アクションを変更して同名で上書きデプロイする場合は上記の create 部分を update に変更して実行してください。


【動作確認】
デプロイしたアクションは CLI でもダッシュボード画面からも、どちらでも動作を確認することができます。まずはダッシュボードで確認してみます。ダッシュボードから Actions メニューを選び、アクション一覧の中にデプロイしたアクション(requestAction)が含まれていることを確認します:
2018082401


デプロイしたアクションを選択します。zip ファイルという特殊(?)な方法でデプロイしたので、この画面から直接コードを参照したり変更することはできないのですが、実行(Invoke)したり、実行時の入力パラメータを編集することはできます。今回のアクションは url パラメータを受け取って動作するので、実行前にパラメータを指定します。"Change Input" をクリックします:
2018082402


入力パラメータを編集する画面が表示されます。今回は何らかの url パラメータを指定したいので、
{ "url": "http://dotnsf.blog.jp/" }
のように何らかの実在する(パスワード等なしで参照できる)URL を url パラメータに指定して "Apply" をクリックします:
2018082403
 ↑このブログの URL を指定した例


改めて "Invoke" をクリックして、このアクションを先程の入力パラメータで実行します:
2018082404


アクションが実行され、少し待つと結果の JSON が表示されます。正しく実行されていれば status = true と、指定した URL から取得した HTML が返ってくることが確認できます:
2018082405


全く同じことを ibmcloud CLI からも実行してみます。CLI の場合は以下のようにパラメータを指定して実行し、その実行結果を確認します(実行そのものは --result オプションなしでも行えますが、--result オプションをつけると実行結果が同じ画面に出力され、確認ができます)。期待していたような HTML が表示されれば確認成功です:
$ ibmcloud wsk action invoke requestAction --param url "http://dotnsf.blog.jp/" --result

2018082406


とりあえず、本来の目的であった「Cloud Functions で npm 外部パッケージを使ったアクションを実行する」方法については実現できそうだ、という目処がたちました。


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
 

IBM Bluemix のイベント駆動型ランタイムである OpenWhisk 環境をコマンドラインから利用する際に必要な wsk をラズベリーパイに導入してみました。なお最初に SSL でログインするか、ターミナルを開くなどして、ラズベリーパイのコマンドライン操作ができる状態にしておく必要があります。


まずは Python のパッケージ管理ツールである pip を導入します:
$ sudo curl -kL https://bootstrap.pypa.io/get-pip.py | python

そして pip を使って wsk コマンドを導入します:
$ sudo pip install --upgrade https://new-console.ng.bluemix.net/openwhisk/cli/download

wsk コマンドの設定を行います。ここで実行するコマンドは OpenWhisk 画面の②で表示されているコマンドをコピー&ペーストする形で実行してください:

2016080401

$ wsk property set --apihost openwhisk.ng.bluemix.net --auth ********* --namespace "dotnsf@jp.ibm.com_dev"

導入した wsk コマンドが正しく動くことを確認します。以下のコマンドを入力して、青字のような結果が返ってくることを確認してください:
$ wsk action invoke /whisk.system/samples/echo -p message hello --blocking --result
{
    "message": "hello"
}

導入できました!これでまたラズパイ環境が捗ります。

このページのトップヘ