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

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

2017/02

IBM Bluemix には Watson や IoT など、数多くの IBM /サードパーティ API がサービスとして登録されており、利用者はこれらを活用してアプリケーションの高速な開発ができるようになります:
2017021301


またランタイム(アプリケーションサーバー)とサービスをバインド(紐付け)することで、ランタイムの環境変数からサービスへの接続情報(ユーザー名、パスワードなど)を動的に参照することができるようになります。これによって接続情報を外出しする必要がなくなり、非常にセキュアな外部 API 連携が可能になる仕組みが提供されています:
2017021302


さて、このような IBM Bluemix 環境において、「標準では登録されていない API をサービスとして利用したい」という要望を(少なくとも自分が知る限りでも何件か)いただいています。もちろんサードパーティ製 API として正規に登録する手順というものもあります。ただこちらは Cloud Foundry Service として最低限必要ないくつかの API(後述)を実装した上で申請し、条件や審査といったプロセスを経て IBM Bluemix サードパーティサービスとして登録されるものになります。この正規の手順について、詳しくはこちらを参照ください:
https://developer.ibm.com/marketplace/docs/vendor-guide/how-do-i-integrate/integrating-bluemix/


一方、いくつかの制約事項がある中で、この申請や審査といった手順を通さずに自身のサービスを IBM Bluemix のカスタムサービスとして利用可能にする方法もあり、以下のブログで紹介されています:
https://www.ibm.com/blogs/bluemix/2017/01/extend-bluemix-service-broker/


上記エントリによると、その制約事項の中で大きなものは以下の4つです:
(1) IBM Bluemix の Web UI には 100% 統合されない
(2) 同一組織の中だけで利用可能なサービスとして登録される
(3) Web API 自体は https で運用されている必要がある(オプション)
(4) 課金の仕組みが必要であれば別途実装する


(1) について補足すると、IBM Bluemix はオープンソース製品である Cloud Foundry をベースに IBM が機能拡張したものなのですが、(あまり知られていませんが)Web ブラウザから操作できるようにしているのも IBM の拡張によるものです(CloudFoundry 自体は cf というコマンドラインツールを使って操作するものです)。この Web の UI は Bluemix のサービスラインナップとも密に統合されており、以下で紹介するカスタムサービスで追加したサービスは Bluemix Web UI には反映される部分とされない部分が出てきてしまいます(例えば追加したカスタムサービスはカタログ画面には表示されません)。基本的には cf ツールを使ってサービスを追加してバインドして・・という手順をとって実行する前提の方法になります。

(2) はカスタムサービスの利用可能な範囲についてです。通常の Bluemix サービスは Bluemix ユーザー全員に公開されて利用可能になりますが、以下の方法で追加したカスタムサービスの場合は同一の組織内(または同一のスペース内)だけで利用可能なサービスになります。異なる組織のユーザーからは利用できない方法であることをご了承いただきます。

(3) はその API 自体が http に加えて https でも稼働する必要がある、という条件です。この (3) に関しては(動くか動かないかだけの条件であれば、http だけでも動くので)必須ではないのですが、Bluemix の Web UI から参照するページの一部が https 必須になっており、https 非対応だとそのページが正しく表示されない、という問題が生じるのでした。繰り返しますがそのページがブラウザから正しく表示されるかどうかと、API として実行できるかどうかは別の問題なので、その意味では必須ではない(が、対応されている方が好ましい)とお考えください。要するにピュアな Cloud Foundry サービスとして認識させるための要件ではなく、IBM Bluemix 統合のための要件であるという意味です。

(4) は今回作成するカスタムサービスは(複数のプランを実装することはできますが)Bluemix 上では無料サービスとして動く、ということを意味しています。課金が必要な場合は正規の手順でサードパーティサービスとして登録いただくか、あるいは別途課金の仕組みをサービス側に実装する必要がある、ということです。


上記のような制約事項がある中で、自分で作った Web サービスの API を IBM Bluemix のカスタムサービスとして統合する手順を紹介します。全体的に長くなりそうなので紹介は3回に分け、第一回目である今回は上記の解説に加えて「自分で作った(IBM Bluemix に統合するためのカスタマイズをする前の) Web サービス API 」の紹介をすることにします。もちろん最終的には皆さん自身で作った API をカスタマイズしていただくことになると思いますが、そのカスタマイズ前の題材の紹介であると理解してください。


Web API は非常にシンプルな、「四則演算を行う API 」とします。今回用意したサンプルは Node.js で実装されています:
https://github.com/dotnsf/dotnsfOperation/blob/master/app0.js


今回対象とする上記コードは以下のような内容です。まあ普通の Node.js + Express による API 定義(/:operation/:x/:y)に加え、/ にアクセスがあった場合の表示と、/doc に簡易ドキュメントを用意しています:
//. app0.js
var express = require( 'express' ),
    cfenv = require( 'cfenv' ),
    appEnv = cfenv.getAppEnv(),
    app = express();


//. Service APIs
app.get( '/', function( req, res ){
  var html = '<title>My Broker</title>';
  html += '<h1>My Broker</h1>';
  html += '<p>ドキュメントは<a target="_blank" href="./doc">こちら</a>を参照ください</p>';
  res.writeHead( 200, [ { 'Content-Type': 'text/html; charset=UTF8' } ] );
  res.write( html );
  res.end();
});

app.get( '/:operation/:x/:y', function( req, res ){
  var operation = req.params.operation;
  var x = parseFloat( req.params.x );
  var y = parseFloat( req.params.y );
  var z = 0;

  if( operation == 'plus' ){
    z = x + y;
  }else if( operation == 'minus' ){
    z = x - y;
  }else if( operation == 'multiply' ){
    z = x * y;
  }else if( operation == 'divide' ){
    z = x / y;
  }

  res.writeHead( 200, [ { 'Content-Type': 'text/plain' } ] );
  res.write( '' + z );
  res.end();
});

app.get( '/doc', function( req, res ){
  var html = '<title>My Operation Document</title>';
  html += '<h1>My Operation Document</h1>';
  html += '<p>This is a document for my operation</p>';
  html += '<hr/>';
  html += '/plus/x/y => return ( x + y )<br/>';
  html += '/minus/x/y => return ( x - y )<br/>';
  html += '/multiply/x/y => return ( x + y )<br/>';
  html += '/divide/x/y => return ( x / y )<br/>';
  res.writeHead( 200, [ { 'Content-Type': 'text/html; charset=UTF8' } ] );
  res.write( html );
  res.end();
});

var service_port = appEnv.port ? appEnv.port : 1337;
app.listen( service_port );
console.log( "server starting on " + service_port );

肝となる API (/:operation/:x/:y)は4つの機能をもち、それぞれ以下のような URI で実装されます:
URI関数の処理内容
/plus/x/yx + y を実行した結果を返す/plus/2.3/3 → 5.3
/minus/x/yx - y を実行した結果を返す/minus/2.3/3 → -0.7
/multiply/x/yx * y を実行した結果を返す/multiply/2.5/-3 → -7.5
/divide/x/yx / y を実行した結果を返す/divide/5/4 → 1.25


実際に上記の URI に数値パラメータを入れてアクセスすると、実行結果を(text/plain で)画面に表示します:
2017021303


また、API とは別にドキュメントルートにアクセスがあった場合のページと、
2017021401


API の簡易ドキュメントページが用意されています:
2017021402


ここまでのアプリケーション(というか API)は上記の app0.js をそのまま Node.js で実行すれば動かすことができますので、興味がある方は実際に試してみてください。

そして次回は、この app0.js に対して IBM Bluemix のカスタムサービスとして統合するための変更を加える、その内容や手順を紹介します。IBM Bluemix(Cloud Foundry) のサービスとして利用するためには(サービスのインスタンス化や削除、バインド/アンバインドの仕組みを実現するためには)どのような実装が必要になるのか、といった辺りを紹介する予定です。


(追記)続きはこちら:
http://dotnsf.blog.jp/archives/1064347464.html


マルチプラットフォーム対応の Python IDE である Rodeo を CentOS/RHEL にインストールする手順を紹介します:
https://www.yhat.com/products/rodeo

2017021300


といっても手順として特別なことはなく、リポジトリを追加して yum でインストールするだけです:
$ sudo wget http://rodeo-rpm.yhat.com/rodeo-rpm.repo -P /etc/yum.repos.d/
$ sudo yum install rodeo

インストール後、アプリケーションメニューの「その他」から起動できます:
2017021301


はい起動しました。簡単ですね~(※CentOS/RHEL 7 の場合)
rodeo_centos6


機械学習や数値解析に便利なライブラリが充実している Python をより便利に使うための Python IDE も充実してきてるんですね。。


なお、上でわざと(※CentOS/RHEL 7 の場合)と強調しているのには意味があります。ご覧のように上記のスクリーンショットは CentOS 6 上で動いている Rodeo の画像なのですが、この環境を作るのは一筋縄ではいかなかった、という背景があります(このスクリーンショットを撮るまでの作業が、まあ大変でした・・・)。別の機会に詳しく書くかもしれませんが、とりあえず Rodeo は CentOS/RHEL 7.x 上で動かすのが無難、と付け加えておきます。


IBM Bluemix で Context Path Route(以下、「コンテキストルーティング」)を行う方法が紹介されていました:
Context path routes for your Bluemix Cloud Foundry apps
https://www.ibm.com/blogs/bluemix/2017/01/context-path-routes-hour-bluemix-cloud-foundry-apps/



コンテキストルーティング」とは URL のパス部分を使ったルーティングの仕組みです。以下の例を使って紹介します。 "myapp.yourbluemix.net" というホストが稼働しており、ここでは色々な機能が稼働しています。例えばメインのポータル機能は http://myapp.yourbluemix.net/ で、ドキュメント管理機能は http://myapp.yourbluemix.net/docs/ 以下で、サーバー管理機能は http://myapp.yourbluemix.net/admin/ 以下で実装されている、といった具合です。ユーザーからは同じホスト名(myapp.yourbluemix.net)にアクセスしており、同一のサーバーを使っているように見えています:
2017020901


コンテキストルーティングを使うと、実際にはより強固&柔軟な構成で実現できます。例えば上記で紹介した3つの機能を全て別々のサーバー上に実装した上で、ユーザーにそれぞれのホストに(異なるホスト名へ)アクセスさせるのではなく、同じホスト名でのアクセスを可能にします。そして URL のパス部分でどのサーバーを使うかのルーティングを定義します。以下の例では /docs 以下にアクセスする場合は mydocs.yourbluemix.net へ、/admin 以下にアクセスする場合は myadmin.yourbluemix.net へ、それ以外は myapp.yourbluemix.net へ、といった具合です:
2017020902


これがコンテキストルーティングです。これを IBM Bluemix 環境のアプリケーションでも行う方法が上記ブログにて紹介されていました。ブログで紹介されていたのは Node.js ランタイムと Python ランタイムを使った方法でした。自分は Node.js と PHP で試してみたところ少し挙動が変わっていたようでした。それがランタイムの種類の違いに起因しているのかどうかは分からないのですが、、自分で動作確認した方法を以下に紹介します。なお実際に以下の手順を実行するには Bluemix アカウントと cf コマンドラインツールが必要になるので、事前に自分の環境にあった cf をダウンロードしてインストールしておいてください。

まずシステム構成、およびルーティングルールを以下のようなものとします:
2017020903

- 2台のランタイムおよびアプリケーションを用意する:
  - dotnsf-cr.mybluemix.net (Node.js)
  - dotnsf-cr-php.mybluemix.net (PHP)
- ユーザーがアクセスする URL は http://dotnsf-cr.mybluemix.net/ 以下のみ
- dotnsf-cr.myblutemix.net/phpapp/ 以下へのアクセスを dotnsf-cr-php.mybluemix.net/phpapp 以下の PHP アプリケーションにルーティングする。それ以外は dotnsf-cr.mybluemix.net の Node.js アプリケーションが処理する
- PHP アプリケーションが稼働する dotnsf-cr-php.mybluemix.net への直接アクセスはエラーとして処理する


Bluemix らしく、2つの異なるランタイムで仮想的な1つのシステムを作ってみることにしました。で、こんなソースコードを用意しました:
https://github.com/dotnsf/BluemixContextRoutingSample


上記サイトよりソースコードをダウンロード&展開するか git clone して入手してください。展開後に manifest.yml をテキストエディタで開き、以下の赤字部分を自分用のものに変更してください:
applications:
# Node.js app
- name: dotnsf-cr
  memory: 256M
  routes:
  - route: dotnsf-cr.mybluemix.net
  path: ./node_app/
# PHP app
- name: dotnsf-cr-php
  memory: 256M
  routes:
  - route: dotnsf-cr.mybluemix.net/phpapp
  path: ./php_app/

Node.js ランタイムには dotnsf-cr 、PHP ランタイムには dotnsf-cr-php という名前を付けており、最終的にはどちらも dotnsf-cr.myblutemix.net という名前でアクセスできるようにコンテキストルーティングを設定する、という内容です(つまりこの1つの manifest.yml で2つのランタイムの定義をしています)。上記の赤字部分を皆様のアプリケーション名に合わせて変えて使ってください。また US-SOUTH 以外のデータセンターを使う場合はドメインを(例えば eu-gb.mybluemix.net などに)適宜変更してください。


こうして作成したソースコードを cf ツールでプッシュします。manifest.yml 内に(2つのランタイムそれぞれを作るための)必要な情報が全て記述されているので1回の "cf push" だけで実行されます:
2017020901

↑まずは Node.js アプリケーションである dotnsf-cr のプッシュから始まります


2017020902

↑Node.js アプリケーションのビルドが行われている様子です


2017020903

↑ Node.js アプリケーションのプッシュが完了した様子です。そして間髪をいれずそのまま PHP アプリケーションである dotnsf-cr-php のプッシュが始まります。


2017020904

↑途中でルーティング処理が行われている様子が確認できます。


2017020905

↑PHP アプリケーションのプッシュも終わりました。dotnsf-cr-php という名前のアプリケーションですが、その URL が dotnsf-cr.myblutemix.net/phpapp に設定されていることがわかります。


この状態でダッシュボードを確認するとこのようになっているはずです。Node.js と PHP 2つのランタイムが追加されて実行中になっています。これらのルーティング先はどちらも同じものになっていることを確認してください。
2017020909


この状態で http://dotnsf-cr.mybluemix.net/ にアクセスすると Node.js 内の public/index.html が表示されます:
2017020906


また https://dotnsf-cr.mybluemix.net/about にアクセスすると、Node.js の app.js 内で定義された内容に従ったメッセージが表示されます:
2017020907


一方、http://dotnsf-cr.mybluemix.net/phpapp/ にアクセスすると、このパスの場合は PHP ランタイムにルーティングされ、PHP ランタイム内の index.php が表示されるはずです:
2017020908


というわけで、Bluemix でもコンテキストルーティングが実現できることが確認できました。他のランタイム環境でも、3種類以上であってもルーティングの設定だけで同様に実現できると思います。

まだ同期/非同期処理の違いに戸惑いながら Node.js を使っています。で、先日こんな実行時エラーに遭遇しました:
  :
  :
events.js:141 throw er; // Unhandled 'error' event ^ Error: EMFILE: too many open files, open '/home/kkimura/XXX/images/*****.jpg` at Error (native)

その時のコード(抜粋)がこちらです:
var fs = require( 'fs' );
fs.readdir( './images', function( err, files ){  // ./images/ フォルダを開く
  if( err ) throw err;
  
  files.forEach( function( jpgfile ){  // ./images/ フォルダの全ファイルを1つずつ取り出して、、、
          :
    (各ファイルに対する処理)
          :
  });
});

現在のディレクトリから、fs モジュールを使って images/ サブフォルダの中にある画像ファイル全てを取り出して、各ファイルに対して何らかの処理をする・・・という、よくあるとまで言えるかどうかはわかりませんが、ごく普通の内容だと思います。この処理中に上記のエラーが発生しました。エラーそのものもネイティブコードの中で発生しているので、どの画像ファイルを読み込んでいる時のエラーかはわかるのですが、何が原因のエラーなのかのヒントはほとんどありませんでした(結論としては画像ファイル側の問題ではないので、どの画像ファイルであったかは解決の上ではあまり重要な情報ではありませんでした)。

が、エラーメッセージそのものから原因はなんとなく推測できました。

まず、この処理は特定のフォルダ(./images)の中にある全てのファイルを取り出し、forEach() ループで各ファイル毎になんらかの処理をする、というものです。そしてエラーメッセージは "Too many open files(ファイルを開きすぎている)"。 実際、このフォルダ内には非常に多くのファイルが存在していたのですが、それらを同時に開きすぎて、処理の限界を超えてしまった、というエラーが発生していたようです。

というわけで原因はなんとなくわかりました。しかし本当の問題はここから。


Node.js(JavaScript) は非同期に処理を実行します。つまり上記のようなケースでは「ファイルを1つずつ開いて処理をして、終わったら閉じて次へ」ではなく、「同時に(非同期に)全ファイルを開いて、同時に(非同期に)全ファイルに処理を施して・・・」という形で実行されます。そしてその際に多くのファイルを開きすぎて "Too many open files" というエラーが発生していたのでした。このエラーは多くのファイルが保管されているディレクトリへの処理を非同期に実行する以上は解決しにくい問題のように思えますが、さてどうする・・・


これ、自分以外でも悩んでいる人が多かったらしく、StackOverflow などでも議論(後述)されていました。結果的には fs ライブラリそのものを改良したものを使う、という方法が紹介されていました。その改良モジュール(graceful-fs)はこちら:
https://github.com/isaacs/node-graceful-fs

この graceful-fs は fs の代わりに使うことができ、かつ "Too many open files" エラー時に発生する EMFILE イベントを検知したら、少し待ってからやり直し処理を行う(という方法でエラーを回避しながら全ファイルを処理する)、という処理が実装されたもののようです。

graceful-fs モジュールを使うには、まず npm 等でインストールを行います:
$ npm install graceful-fs

そしてコードを書き換えます。といっても require( 'fs' ) を require( 'graceful-fs' ) にするだけで、他は変更なしでそのままエラーもなく動きました:
var fs = require( 'graceful-fs' );
fs.readdir( './images', function( err, files ){
  if( err ) throw err;
  
  files.forEach( function( jpgfile ){
          :
    (jpgfile に対する処理)
          :
  });
});


それにしても同期/非同期処理の頭を素早く切り替え出来るのって、それだけで才能ではないかと思う。。。


(参考)
http://stackoverflow.com/questions/8965606/node-and-error-emfile-too-many-open-files

ビジュアルデータフローエディタの Node-RED は、IoT を始めとするデータの取り込みや加工、書き出しを視覚的に行う便利なツールです。更に IBM Bluemix 環境であれば、「ボイラープレート」と呼ばれるテンプレート機能を使うことで、サーバー管理とかミドルウェアインストールとかを意識することなく、簡単に Node-RED 環境を構築して、すぐに使い始めることができます。


が、簡単すぎるが故の課題もあります。典型的な例の1つが「バージョンアップ」です。まあクラウドの宿命といえなくもないのですが、ミドルウェアやアプリケーションのバージョン管理をどうするか、という課題です。クラウド環境の場合、バージョン管理含めてクラウド業者に手放したい人もいれば、バージョン管理は自分でやりたいという人もいるので、1つの正解というものが存在しない、難しい問題ではあります。Bluemix では新規にサーバーを作る際のミドルウェア/アプリケーションバージョンは原則最新のものが用意されますが、一度作ったサーバーのミドルウェア/アプリケーションバージョンが勝手に変更されることはありません。つまり使い続ける間は利用者が管理する必要があります。 

・・・と、ここまではいいのですが、問題は「最初の一歩が簡単すぎる&中で何がどう動いているか分からなくても使い始めることができる故、バージョンアップがやけに難しく感じる」ことです(苦笑)。

一応難しく(というか、ややこしく)なっている理由を解説すると、Bluemix 環境では Node.js サーバーがランタイムとして用意されます。そしてその上に Node-RED アプリケーションが導入されて動いているわけですが、このアプリケーション部分である Node-RED のバージョンアップをする必要があるわけです。この Node-RED のバージョンアップの際に、前提となる Node.js のバージョンも合わせて上げる必要が生じるケースもあります。また Node.js では npm というパッケージ管理の仕組みが使われていて、npm の作法でバージョンを管理する必要があります。 普通の Node.js 環境の場合、自分で npm を管理したり、npm に指示を出すような設定ファイルを用意したりするのですが、Bluemix はその辺りを全く知らなくても(事前に何も用意しなくても)インターネット上に Node-RED 環境が作れてしまうのです。で、バージョンアップの段階になってこれらの用意がないことが話をややこしくする要素になるのでした。


という背景の説明はここまでにして、以下は実際に(数ヶ月前のバージョンが古かった頃から動いているような)Bluemix 上の Node-RED をバージョンアップする方法を紹介します。

まずは Node-RED 環境にアクセスし、画面右上のハンバーガーメニューから Node-RED のバージョンを確認します。この図では "0.13.1" というバージョンになっていることが確認できます。2017/Feb/09 時点での Node-RED の最新バージョンは "0.16.2" なので、この環境はバージョンアップが可能な状態にある、ということになります:
2017020801


(Node.js や)Node-RED のバージョンアップのためには Node-RED のスターターコードと呼ばれるファイル一式か、IBM DevOps サービス等を使ったソースコード一式が必要になります。バージョンアップの対象となる Node-RED 環境を作った際にこれらの環境ごと用意されているのであれば、そのソースコードを用意してください。 以下はスターターコードも IBM DevOps サービスも使わず、ソースコードが手元にない状態からの入手方法になります。

改めて IBM Bluemix にログインし、対象の Node-RED ランタイムのプロジェクトを選択します。そして「開始」タブを開くと、Node-RED のカスタマイズの節内に "DOWNLOAD STARTER CODE" と書かれたボタンがあります(バージョンによって微妙に表現が異なるかもしれません)。ここをクリックしてスターターコードの zip ファイルをダウンロードします:
2017020802


ダウンロードした zip ファイルを展開します。この中に package.json というファイルがあるので、これをテキストファイルで開きます。もともとスターターコードをダウンロード済みであったり、IBM DevOps サービス等でソースコードを管理済みだった場合もお手元のコードの package.json を開いてください:
2017020803


package.json ファイルの中身を確認してみます:
2017020901


この中で利用する各コンポーネントモジュールとそのバージョンを管理しています。まず Node-RED 自体は
      :
  "node-red": "0.x"
      :

と指定されていました。これは「0.ナントカの中で最新のもの」を使うよう指定されていることになり、この指定であれば現時点での最新版 Node-RED である 0.16.2 が使われることになります。もしこのような指定になっていなかった場合はこのように変更してください。

次に Node.js のバージョンを確認します。Node-RED v0.16.0 からは Node.js のバージョンが 4.0 以上のみをサポートしており、Node.js のバージョンが古いと最新版の Node-RED は動きません。そこで Node.js のバージョンも合わせておく必要があります。こちらについては
      :
  "node": "4.x"
      :

と指定されている必要があります。もしこのような指定になっていなかった場合はこのように変更してください。


ここまでの変更・確認の上で(必要であれば他のモジュールや public フォルダ以下に静的ファイルを追加した上で)、cf コマンドでプッシュするか、IBM DevOps サービスを使って Deploy してください。

再デプロイ後に改めて Node-RED のバージョンを確認します:
2017020805


↑最新版になっていることが確認できれば成功です。

 

このページのトップヘ