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

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

タグ:ibm

IBM Cloud の旧 Bluemix に代表される、いわゆる PaaS 環境ではアプリケーション・サーバーなどのミドルウェアまでをベンダー側が提供し、利用者はサーバーやその管理を意識せずにアプリケーションのみ提供して動かすことができます。

これはこれで便利な環境なのですが、一方で実際に動かしている環境の詳細(ミドルウェアや言語ランタイムのバージョンなど)を知りたくなることもあります。知らなくても使えるんだけど知りたい、という要望です。そういった情報が公開されていればいいのですが、多くのケースで常に最新バージョンが使われるようにメンテナンスされており、公開情報が最新版の内容を反映していないケースもあります。というわけで IBM Cloud の SDK for Node.js ランタイムを対象に実際に動いている環境からバージョンを調べる、という方法で調べてみました。

SDK for Node.js ランタイムで以下のコードをデプロイして動かします:
// app.js

var cfenv = require( 'cfenv' );
var express = require( 'express' );
var app = express();

var appEnv = cfenv.getAppEnv();

app.get( '/', function( req, res ){
  var process_version = process.version;
  res.write( JSON.stringify( { version: process_version }, 2, null ) );
  res.end();
});


var port = appEnv.port;
app.listen( port );
console.log( 'server started on ' + port );

↑プログラムコード内から process.version を参照して、現在動いている node のバージョンを取り出しています。

ちなみに package.json は以下のように Node.js v6.x の最新版が使われるように指定しています:
{
    "name": "node-v",
    "version": "0.0.1",
    "scripts": {
        "start": "node app.js"
    },
    "dependencies": {
        "cfenv": "~1.0.0",
        "express": "4.1.x"
    },
    "repository": {},
    "engines": {
        "node": "6.x"
    }
}

このコードを cf push でデプロイし、(2018/10/27 時点で)GET / を実行した結果がこちらです:
2018102701

v6.x の最新版、が使われた結果がこのようになりました。

また、package.json の同部分を "node": "8.x" に変えて再デプロイして実行するとこうなりました:
2018102702


動的に Node.js の実行バージョンを取得することができました。


IBM CloudantApache CouchDB をベースとしたマネージド NoSQL DB サービスです。IBM Cloud のライトアカウントを利用することで無料枠内で利用することも可能です。

そんな便利な IBM Cloudant ですが、IBM Cloud では(特に無料枠で使った場合)では、どのようなクラスタリング構成で運用されているのか気になりました。もともと NoSQL DB はスケーリングに優れていて、大規模運用向きと言われています。ではこの(特に無料のライトプランで提供されている)IBM Cloudant はどのような運用構成で提供されているのでしょう? 理論上は1サーバーノードで(クラスタリングなしで)提供することも可能だし、無料プランということを加味して、クラスタリング無しだったとしてもまあそうだよね・・とも考えられます。一方で無料プランと有償プランで(わざわざ)差をつけて運用しているのか?という疑問もあります。このあたりをそっと調べてみました。


まず、調べる方法は CouchDB REST API の GET /db を使うことにしました。この REST API を実行すると指定したデータベースの情報を得ることができ、その中には(クラスタリング構成になっていれば)クラスターに関する情報が含まれていることになっています。この方法で自分がローカルで構成した単一構成の CouchDB と、IBM Cloud のライトプランで契約した IBM Cloudant の2つのデータベースに対して実行し、その結果を比較してみることにします。

まず前者の単一構成 CouchDB のデータベースに対してこのコマンドを curl で実行しました(実行結果は青字):
$ curl 'http://localhost:5984/ccdb'

{"db_name":"ccdb","update_seq":"6-

g1AAAAEzeJzLYWBg4MhgTmHgzcvPy09JdcjLz8gvLskBCjMlMiTJ____PyuRAYeCJAUgmWSPX40DSE08WA0TLjUJIDX1eM3JYwGSDA1ACqhsPiF1C

yDq9uO2E6LuAETdfULmPYCoA_khCwCKxmL8","sizes":{"file":58598,"external":615,"active":1730},"purge_seq":0,"other":

{"data_size":615},"doc_del_count":2,"doc_count":2,"disk_size":58598,"disk_format_version":6,"data_size":1730,"com

pact_running":false,"instance_start_time":"0"}

指定したデータベース(上例では ccdb)の現在の状態が表示されています。各項目の意味は上述の GET /db API のリンク先で説明されているのでそちらを参照いただきたいのですが、この実行結果にはクラスタリング情報が含まれていません。実際クラスタリング構成ではなく単一構成で動いているので、この実行結果もその運用状態を正しく表しています。

次に同じコマンドを IBM Cloudant のライトプランで作成したデータベースに対して実行しました。その結果がこちらです:
{"status":true,"info":{"update_seq":"20-

g1AAAAQneJzLYWBgEMhgTmHQTElKzi9KdUhJMtFLytVNTtYtLdYtzi8tydA1NNBLzskvTUnMK9HLSy3JAWphSmRI4v___39WIgNIsxZcs6EhMbqTB

IBkkjzYAGZU24nTrwDSr49NvzlR-g1A-u0RHiDR90kOIP3-

CPtJDoAAkAHx2BxAnP4EkP58bPqJC4ACkP56sgMgjwVIMjQAKaAR_VmJTOQEAsSQCRBD5pMXEBAzFkDMWE9eYEDM2AAxYz_UM2QFyAGIGeezEhnJD

5ALEEPuUxIgDyBmvCcve0DM-AAxA5TEswCIaVtl","db_name":"statedb","sizes":

{"file":11774513,"external":10511286,"active":10543509},"purge_seq":0,"other":

{"data_size":10511286},"doc_del_count":7,"doc_count":2,"disk_size":11774513,"disk_format_version":6,"data_size":1

0543509,"compact_running":false,"cluster":{"q":16,"n":3,"w":2,"r":2},"instance_start_time":"0"}}

↑特に赤字部分に注目してほしいのですが、先程の実行結果には存在しなかったクラスタリングに関する情報が含まれています。そしてこの結果を見ると、このデータベースは
 ・シャード数: 16(!)
 ・1つのドキュメントの分散数: 3
 ・書き込みコマンドを実行した場合、2つ以上に書き込めたら書き込み成功とする
 ・読み取りコマンドを実行した場合、2つ以上から結果が返ってきたら読み取り成功とする

という条件でクラスタリングが構成されていることがわかります。無料のライトプランでも結構な好条件でクラスタリングされていたんですね、へぇ~。


 

Node-RED の HTTP ノード(HTTP in ノードと HTTP Response ノード)を使うと簡単に REST API を作ることができて便利です。自分もデータベースへの CRUD 操作を作る際などによく使っています。

が、この方法で作った REST API にはクロスオリジン制約(いわゆる CORS)が付きます。例えば https://xxxx.mybluemix.net/ というホストで Node-RED を動かしている場合、作成する REST API のエンドポイント URL は https://xxxx.mybluemix.net/getdata とかになるわけですが、この API を AJAX などのブラウザ上の JavaScript から呼ぼうとすると、同一サーバー上の( https://xxxx.mybluemix.net/**** というアドレスのページの) HTML からでないとエラーになってしまうのでした。サーバーサイドのプログラムから実行することはできるのですが、ブラウザ上の JavaScript から実行するには同一ホストからでないといけない、という制約が付くのでした(ま、この制約自体はある方が一般的ですけど)。

この CORS の制約を外して、外部の(https://xxxx.mybluemix.net/ 以外の)ページやローカルシステム上ページの JavaScript からでもこの API を呼べるようにする、そのための設定方法と手順を紹介します。

まず Node-RED で REST API を作成します。今回は以下のような HTTP in ノードと、Function ノードと、HTTP Response ノードをつなげただけのシンプルな REST API を用意しました:
2018101801


HTTP in ノードの設定は以下のように GET /corstest で呼び出せるような設定にしています:
2018101802


Function ノードは以下のような JavaScript を記述し、実行時のタイムスタンプ値を JSON で返す、という関数にしています:
msg.payload = { timestamp: ( new Date() ).getTime() };
return msg;

2018101803


HTTP Response ノードにはこの段階では特に手を加えません。配置しただけの状態のまま接続してデプロイします。これで REST API 側は準備できました。

次に HTML ファイルを用意します。今回はサーバー上ではなくローカルシステム上に以下のような内容の HTML ファイルを用意しました:
<html>
<head>
<meta charset="utf8"/>
<title>CORS テスト</title>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script>
function corstest(){
  $.ajax({
    type: 'GET',
    url: 'http://xxxx.mybluemix.net/corstest',  // 上記で作った REST API のエンドポイントURL
    success: function( result ){
      console.log( result );
    },
    error: function( err ){
      console.log( "error" );
      console.log( err );
    }
  });
}
</script>
</head>
<body>
<input type="button" value="CORS" onClick="corstest()"/>
</body>
</html>

この HTML ファイルをブラウザから(Ctrl+O などでファイルを指定して)開くと、"CORS" と書かれたボタンが1つだけ配置されたページが開きます:
2018101807


HTML を見るとわかるのですが、このボタンをクリックすると GET https://xxxx.mybluemix.net/corstest という API が実行され、成功するとその結果が、失敗すると "error" というメッセージに続いてエラーメッセージが、それぞれ表示される内容になっています。なおこのエンドポイント URL の xxxx 部分が実際に作成した Node-RED 環境のホスト名にあわせて変更してください。


ブラウザのコンソールを開いて(F12)、この CORS ボタンをクリックします。現状は CORS の対策を何もしていないので当然のようにエラーになります。エラーの内容はコンソールに表示され、原因はクロスオリジン制約のようです。これをどうにかしたい、というのが今回のテーマです:
2018101804


では、この REST API の実行が成功するよう API 側をカスタマイズします。Node-RED のフロー画面に戻って、HTTP Response ノードをダブルクリックして編集状態にします。そして「ヘッダ」と書かれた欄の「+追加」という部分をクリックし、HTTP Response ヘッダを追加します。そして左側(ヘッダ名)の欄には Access-Control-Allow-Origin と、そして右側(ヘッダ値)の欄には *(どのドメインからのリクエストでも許可するの意)とそれぞれ入力し、最後に「完了」→「デプロイ」します:
2018101805


この設定によって REST API の実行結果を返す際のヘッダに Access-Control-Allow-Origin: * という一行が追加されて返るようになり、このヘッダによってクロスオリジンが許可されているとブラウザ側からも判断され、期待通りの結果が得られるようになります。再度 CORS ボタンをクリックして REST API を実行するとコンソールにはリクエストが成功した時の結果が表示されるようになりました:
2018101806


CORS の制約を理解した上で外す(あるいは特定のドメイン名やホスト名を指定した上で許可する)、という点に注意してください。





IBM Cloudant のバイナリ・アタッチメント機能を使って、簡易的なファイルサーバー代わりに使う方法を紹介します。具体的には curl コマンドを使って手元の画像ファイル(でなくてもよい)をアップロードし、URL を指定して画像を表示できるようにする、というものです。

とりあえず以下の作業を行うには IBM Cloudant のデータベースと curl コマンドが必要です。前者は IBM Cloud のライトアカウントを使って無料で入手することも可能です。IBM Cloud にログイン後、IBM Cloudant のインスタンスを(無料であれば Lite プランで)作成しておいてください:
2018101601


また、以下のコマンド実行時に必要になるため、この Cloudant サービスのアクセス情報を確認しておきます。サービスを選択し、「サービス資格情報」と書かれたタブを選んで、「資格情報の表示」をクリックします(「資格情報の表示」がなかったら新規に作成してからクリックします):
2018101603


すると以下のような表示が画面下に出てきます:
2018101604


この中の username の値と、password の値を後でコマンド実行する際に指定することになります。どこかにメモしておくか、コピー&ペーストできるようにしておいてください。


また curl コマンドは使う方のシステムにあわせて適宜入手してください。Linux や MacOS であればたいてい標準で入っているはずですが、Windows の場合は別途インストールが必要です。私はこちらのをダウンロードしてインストールしました:
https://curl.haxx.se/download.html

実際に以下の作業を行う前に、IBM Cloudant 内に今回の作業の対象となるデータベースを用意します。今回は testdb という名称のデータベースを1つ用意しました。このデータベースを簡易ファイルサーバー代わりに使う方法を紹介します:
2018101602


まずはファイルを保存する手順です。今回は仮に cloudant.png という名前の以下のような画像ファイルを保存することにします:
cloudant1


コマンドプロンプト(Linux や MacOS の場合はターミナル)を開き、この cloudant.png が存在しているフォルダに移動して、以下のコマンドを入力します:
$ curl https://(username の値):(password の値)@(username の値).cloudant.com/testdb/(作成するドキュメントの _id)/(作成するアタッチメントの名称) -X PUT -H "Content-Type: image/png" --data-binary @cloudant.png

仮に username の値が "user1"、password の値が "pass1"、ドキュメントIDを "doc001"、アタッチメント名を "att001" とすると以下のようなコマンドになります:
$ curl https://user1:pass1@user1.cloudant.com/testdb/doc001/att001 -X PUT -H "Content-Type: image/png" --data-binary @cloudant.png

このコマンドが成功すると以下のような JSON が返されます:
{ "ok": true, "id": "doc001", "rev": "1-xxxxxxxx" }


コマンド実行が成功すると同じディレクトリにある cloudant.png ファイルを Cloudant の testdb にアップロードします。なお Content-Type の異なるファイルをアップロードする場合は -H オプションで指定する Content-Type を変えて指定してください。


Cloudant に保存したファイルを取り出す場合は以下の URL をブラウザで指定します:
https://(username の値):(password の値)@(username の値).cloudant.com/testdb/(作成するドキュメントの _id)/(作成するアタッチメントの名称)

同様に username の値が "user1"、password の値が "pass1"、ドキュメントIDを "doc001"、アタッチメント名を "att001" とすると以下のような URL になります:
https://user1:pass1@user1.cloudant.com/testdb/doc001/att001

ブラウザのアドレス欄に指定すると、こんな感じで表示できます:
2018101605



Cloudant のアタッチメント機能を使った簡易的なファイル保存のやりかたを紹介しました。Node-RED でウェブページを作る際の静的画像をどうするか、という問題を比較的簡単に解決する方法の1つだと思っています。


IBM Cloud から提供されている IBM CloudantApache CouchDB をベースとしたマネージドな NoSQL データベースのサービスです:
2018090300


ベース製品が同じなので、例えば REST API レベルでは互換性があります。注意が必要な点として自分が気づいた限りでは IBM Cloudant は標準で Apache Lucene ベースの検索機能が有効になっており、インデックスとなる Design Document を用意することでテキスト検索が可能になる、ということが挙げられますが、それ以外に大きな差はありません。 一方で IBM Cloud から提供されているライトアカウント(無料プラン)でも IBM Cloudant を利用することができるので、わざわざ Apache CouchDB を用意しなくても気軽に使うことができる DBaaS としてとても手軽で便利だと思っています:
2018090301


さて、自分は業務のプログラミングでは主に Node.js を使うのですが、Node.js のパッケージライブラリには IBM Cloudant 用のものと、Apache CouchDB 用のもの、両方が存在しています:
(IBM Cloudant)
2018090302

(Apache CouchDB)
2018090303


仮に対象となるデータベースが IBM Cloudant であれば前者の方が簡単に使えるという印象を持っています。ただし IBM Cloudant 用ライブラリは IBM Cloud 上の IBM Cloudant を想定していることもあり、例えばオンプレミス上の Apache CouchDB に対して使えるものではありません。

一方、Apache CouchDB 用ライブラリはローカルや社内サーバー、クラウド上にある Apache CouchDB データベース全般に対して利用することが可能です。この対象はクラウド上の IBM Cloudant であっても構いません。要するにこちらのライブラリを使えば Apache CouchDB だけでなく IBM Cloudant にも接続できる、ということです。


実際にこういった需要がどれだけあるのかわからないのですが、例えばあるシステムを作る際に、そのデータストアとして、
(1) 試しに動かす場合は IBM Cloud 上の IBM Cloudant を使って気軽に開発/テストを行い、
(2) 本番運用ではオンプレミスな Apache CouchDB を利用する(IBM Cloudant の独自機能は使わない想定)

といったことが接続先の切り替えだけでできると便利です。ただこれを実現するためには IBM Cloudant 用の便利なライブラリを使って開発しまうと (2) の本番の時に問題が起こってしまいます。以下では IBM Cloudant に対しても Apache CouchDB 用ライブラリ(以下 node-couchdb)を使ってアクセスするように実装してみたコードを紹介します。ベースが同じ製品なのでできることはできるんですが、そのための手順と注意点を含めて紹介します。


【準備】
まず Node.js のコードを記述する前に上述の node-couchdb を npm install しておきます:
$ npm install node-couchdb


【データベース接続】
node-couchdb を使って IBM Cloudant に接続します。こんな感じのコードを記述します:
var dblib = require( 'node-couchdb' );

var option = {
  auth: {
    user: 'username',
    pass: 'password'
  },
  'host': 'username.cloudant.com',
  'protocol': 'https',
  'port': 443
};
var db = dblib( option );

usernamepassword の部分にはそれぞれ IBM Cloudant の username と password を指定します(localhost の Apache CouchDB に接続する場合は option = {} で接続できます)。これで IBM Cloudant との接続ができました。ここで取得した db を使って、以下の主要な操作を行うことができます。


【主要な操作】

ドキュメント追加
insert() メソッドにデータベース名(以下の例では 'testdb')を指定して、ドキュメントを追加します。取得前に db.uniqid() でユニーク ID を取得し、_id に設定している点に注意してください:
var doc = { name: 'dotnsf', height: 170.0 };  //. 追加するドキュメント
db.uniqid().then( function( id ){
  doc._id = id[0];
  db.insert( 'testdb', doc ).then( function( body, headers, status ){
    console.log( body );
  }).catch( function( err ){
    console.log( err );
  });
});

ドキュメント読み取り
同様に get() メソッドにデータベース名と id を指定してドキュメントを取得します:
db.get( 'testdb', id ).then( function( doc, headers, status ){
  console.log( doc );
}).catch( function( err ){
  console.log( err );
});

ドキュメント削除
del() メソッドにデータベース名と id と rev を指定して、データベースからドキュメントを削除します。以下の例では一度 get() メソッドを実行して id 値から rev 値を取り出してから del() を実行しています :
db.get( 'testdb', id ).then( function( doc, headers, status ){
  db.del( 'testdb', doc.data._rev ).then( function( data, headers, status ){
    console.log( data );
  }).catch( function( err ){
    console.log( err );
  });
}).catch( function( err ){
  console.log( err );
});

ビューを指定してドキュメント一覧取得
あらかじめ作成したビューを指定して、そのビューに含まれるドキュメントの一覧を取得します。以下の例ではデザイン名 : library, ビュー名 : byname というビューを指定して文書一覧を取得しています :
db.get( 'testdb', '_design/library/_view/byname', {} ).then( function( data, headers, status ){
  if( data && data.data ){
    var docs = data.data.rows;
    console.log( docs );
  }
}).catch( function( err ){
  console.log( err );
});


IBM Cloudant の npm だと最初にデータベース名を指定してそのデータベースのオブジェクトを取得した上で各種操作を行う、、という流れなんですが、Apache CouchDB 版だと毎回データベース名と一緒に各種操作を行う、、という点が大きな違いだと思いました。ただその辺りさえ理解していればまあ大丈夫かな。。


 

このページのトップヘ