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

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

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





このページのトップヘ