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

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

2021/09

本ブログエントリは IBM Cloud アドベントカレンダー 2021 に参加しています。12/4 ぶんのネタです。


IBM Cloud から提供されているマネージド NoSQL データベースである IBM Cloudant をよく使っています。最大の魅力は無料アカウントでも容量 1GB まで使えるストレージで、かつデータが分散管理されているという安全性です。仕様が曖昧な状態からアジャイルに作り始める際は(テーブル定義などを意識する必要がないこともあって)むしろ NoSQL データベースの方が便利だったりもします。

この IBM Cloudant を Node.js プログラムから使う場合のライブラリとして長く @cloudant/cloudant を使ってきましたが、2021/12/31 を以って End of Support を迎えることになり、現在は deprecated なライブラリ、という扱いになっています。2021年8月の今は「まだ使えるけど、もうすぐ使えなくなるよ」って所でしょうか。可能であれば、これから新たに作り始める時には利用を避けたほうが安全だと思います。

で、その後継ライブラリとして公開されているのが @ibm-cloud/cloudant (IBM Cloudant Node.js SDK)です。IBM Cloudant だけでなく、そのベースとなっている Apache CouchDB に対してももちろん使うことができるものです。現在 @cloudant/cloudant ライブラリを使っているアプリケーションはなるべく今年中にこの新しい @ibm-cloud/cloudant に移植することをおすすめします。

・・・と言うのは簡単ですが、実際この @ibm-cloud/cloudant は @cloudant/cloudant と比べてどのくらい似ていて/同じで、移植はどの程度簡単/難しいのでしょう? というわけで、まずは @ibm-cloud/cloudant を使ってみることにしました。


【サンプルソースコード】
以下で紹介するサンプルアプリケーションのソースコードを github で公開しています:
https://github.com/dotnsf/cloudant-node-sdk_sample


【接続方法の決定】
@ibm-cloud/cloudant を使って Cloudant(CouchDB) にアクセスする場合の接続方法には3通りあります:
(1)IAM
(2)COUCHDB_SESSION
(3)BASIC


(1)の IAM は IBM Cloudant を IAM 接続サポート形式で作成した場合に利用できる方法です。公式ドキュメントでも「この方法が使える場合はこの方法で」と紹介されています。

(2)の COUCHDB_SESSION を使った方法でも接続が可能です。

(3)の BASIC はいわゆる「ベーシック認証」です。この3つの中では「この方法しか使えない場合の選択肢」と紹介されています。CouchDB を自分でインストールして使う場合など、この方法で接続する前提のセットアップをしているとこの方法でしか接続できないことになります。もちろん Cloudant でもベーシック認証をサポートした形式で作成している場合は利用可能です。

(1)、(2)、(3)のいずれの方法でアプリケーションとデータベースを接続するかを決めておく必要があります。正確には @ibm-cloud/cloudant は「環境変数を設定して接続する」のですが、どの方法で接続するかによって、設定が必要になる環境変数が変わる点に注意が必要です。


【Node.js から接続する際に必要な環境変数】
@ibm-cloud/cloudant を使って Cloudant/CouchDB に接続する際に設定が必要な環境変数は以下です(横のカッコ付き数字は、上述の(1)、(2)、(3)のどの方法を使った時に必要な環境変数か、を表しています)。また変数名の頭の "CLOUDANT" 部分は別の値でも構いませんが、同じ文字列に統一して設定する必要があります:

変数意味
CLOUDANT_AUTH_TYPE上述の接続方法。"IAM"(1), "COUCHDB_SESSION"(2), "BASIC"(3) のいずれか。デフォルト値は "IAM"
CLOUDANT_URLデータベースの URL((1)、(2)、(3))
CLOUDANT_APIKEYAPI キーの値((1))
CLOUDANT_USERNAMEユーザー名((2)、(3))
CLOUDANT_PASSWORDパスワード((2)、(3))


環境に合わせてこれらの値を用意して実際に接続してみます。


【Node.js から Cloudant に接続】
(1)IAM 接続を行う場合は、上述の表より以下の環境変数を設定します:
process.env['CLOUDANT_AUTH_TYPE'] = 'IAM'; //(デフォルト値なので設定しなくてもよい)
process.env['CLOUDANT_APIKEY'] = 'xxxxx'; //(API Key の値)
process.env['CLOUDANT_URL'] = 'https://xxxxxxx-bluemix.cloudantnosqldb.appdomain.cloud'; //(Cloudant のURL)

そして @ibm-cloud/cloudant ライブラリを読み込んで、以下のコードを実行します:
var { CloudantV1 } = require( '@ibm-cloud/cloudant' );

var client = CloudantV1.newInstance( { serviceName: 'CLOUDANT' } ); 

このコード実行が成功すると、Cloudant に接続したインスタンスが client という変数に格納され、実際のデータの CRUD 処理が可能になります。

なお上記コードの serviceName 値として 'CLOUDANT' を指定していますが、この部分は変更可能です。ただ変更する場合は環境変数として指定した変数名の頭の CLOUDANT 部分をここで指定する値と同じものに変更してください。

(2)COUCHDB_SESSION 接続を行う場合は、上述の表より以下の環境変数を設定します:
process.env['CLOUDANT_AUTH_TYPE'] = 'COUCHDB_SESSION';
process.env['CLOUDANT_USERNAME'] = 'username'; //(ユーザー名の値)
process.env['CLOUDANT_PASSWORD'] = 'password'; //(パスワードの値)
process.env['CLOUDANT_URL'] = 'https://xxxxxxx-bluemix.cloudantnosqldb.appdomain.cloud'; //(Cloudant/CouchDB のURL)

そして @ibm-cloud/cloudant ライブラリを読み込んで、以下のコードを実行します:
var { CloudantV1 } = require( '@ibm-cloud/cloudant' );

var client = CloudantV1.newInstance( { serviceName: 'CLOUDANT' } ); 

(3)BASIC 接続を行う場合は、上述の表より以下の環境変数を設定します:
process.env['CLOUDANT_AUTH_TYPE'] = 'BASIC';
process.env['CLOUDANT_USERNAME'] = 'username'; //(ユーザー名の値)
process.env['CLOUDANT_PASSWORD'] = 'password'; //(パスワードの値)
process.env['CLOUDANT_URL'] = 'http://xxx.xxx.xxx.xxx:5984'; //(Cloudant/CouchDB のURL)

そして @ibm-cloud/cloudant ライブラリを読み込んで、以下のコードを実行します:
var { CloudantV1 } = require( '@ibm-cloud/cloudant' );

var client = CloudantV1.newInstance( { serviceName: 'CLOUDANT', disableSslVerification: true } ); 

なお(CouchDB の場合に多いと想像していますが) SSL 接続が不要の場合は上述のように接続時のパラメータに disableSslVerification: true を追加してください。

参考までに、@cloudant/cloudant の場合は、
var Cloudantlib = require( '@cloudant/cloudant' );
var cloudant = Cloudantlib( { account: "username", password: "password", url: 'https://xxxxxxx-bluemix.cloudantnosqldb.appdomain.cloud' } );
var db = cloudant.db.use( "dbname" );

といった感じでデータベースまでを取得した上で、
db.get( "id", function( err, result ){
  if( err ){
    console.log( err );
  }else{
    console.log( result );
  }
});

のようにして特定データを取得したりしていましたが、この @ibm-cloud/cloudant ではまだデータベースが特定されていない点にご注意ください。


【接続後の CRUD 処理の例】
では接続後の Cloudant クライアントを使って、IBM Cloudant のデータを読み書きしてみましょう。まず対象データベースと id がわかっている特定データを取得してみます。@ibm-cloud/cloudant では以下のような getDocument() メソッドを使うコードとなります:
client.getDocument( { db: "dbname", docId: "id" } ).then( function( result ){
  console.log( result );
}).catch( function( err ){
  console.log( err );
});

client.getDocument() を実行し、そのパラメータ内でデータベースと id を指定する、という方式になります。この点からして @cloudant/cloudant とは異なってますね。

また特定データベースの全データをまとめて取得(@cloudant/cloudant だと db.list() )する場合は以下のような postAllDocs() メソッドを使うコードです:
client.postAllDocs( { db: "dbname", includeDocs: true } ).then( function( result ){
  console.log( result );
}).catch( function( err ){
  console.log( err );
});

データを一件追加する場合は以下のような postDocument() メソッドを使うコードになります:
client.postDocument( { db: "dbname", document: { name: "Kimura" } } ).then( function( result ){
  console.log( result );
}).catch( function( err ){
  console.log( err );
});

以上、あくまでいくつかのメソッドを紹介しただけですが、全般的に行うオペレーションとメソッド名の関係がわかりやすく整頓されているように感じて、比較的慣れやすいのではないかと感じています。



IBM Cloud から提供されている Knative ベースのフルマネージドランタイム環境 "Code Engine" を使って cron ライクなスケジュールジョブを実現してみました。


Code Engine については以前にこの環境を使って、コンテナ化されたウェブアプリケーションをデプロイして使う、という内容を紹介したことがあります。準備手順もこの中で紹介していたので、後述の内容に興味を持っていただき、実際に試してみようと感じていただけた場合はこちらのページで紹介する内容も参照し、環境の準備を行っておいてください:
IBM Cloud の新サーバーレス環境 Code Engine を試してみる


今回は上述のリンク先で紹介しているようなウェブアプリケーションではなく、単発実行のスタンドアロンアプリケーション(コマンドラインから指定して実行し、処理が終了したら終わり。GUI なし)を Code Engine で実行できるように設定する方法を紹介します。あわせてこのスタンドアロンアプリを手動で実行するのではなく、cron ライクなフォーマットで実行タイミングをあらかじめ指定しておき、そのタイミングで自動実行させる方法も紹介します。


【ibmcloud CLI のセットアップ】
まず準備段階として ibmcloud CLI をセットアップする必要があります。上述の Code Engine 利用時はコンテナ化されたウェブアプリケーションを Code Engine 上にデプロイする手順を紹介しましたが、この内容であればリンク先のように IBM Cloud のダッシュボード画面を利用した GUI 操作だけで行うことができました。が、今回の内容は現時点のダッシュボード画面からだけでは設定できない内容が含まれており、一部の操作を CLI で行う必要があります(2021年8月8日時点)。そのための ibmcloud CLI のインストールおよびセットアップが必須となります。

ibmcloud CLI のインストールは以下のリンク先を参照し、お使いのシステム(Windows, Linux, MacOS)に合わせたインストールを行ってください:
IBM Cloud CLI の概説


ibmcloud CLI のインストールに続いてセットアップを行います。以下 Windows のコマンドプロンプト環境を使った手順を紹介しますが、基本的にはすべてのシステムでほぼ同様の手順を実行可能です。

まずはコマンドプロンプト画面やターミナルを起動し、ibmcloud CLI を使って IBM Cloud にログインします:
> ibmcloud login -u (ログインID) -g (リソース名:通常は default)

ibmcloud CLI で Code Engine のリソースを操作するには Code Engine 用のプラグインをインストールする必要があります。すでにインストール済みの場合は飛ばしてかまいませんが、まだプラグインをインストールしていなかったり、自信がない場合はログイン後に以下のコマンドを実行して Code Engine プラグインをインストールしてください:
> ibmcloud plugin install code-engine

これで ibmcloud CLI のセットアップは完了しました。


【IBM Code Engine に登録するジョブアプリケーションコンテナを開発】
次に Code Engine にスケジュールジョブとして登録するアプリケーションを開発します。

これはもちろんどのようなアプリケーションでもいいですが、今回は最終的に実行スケジュールを指定して「1時間おきに実行」といった実行を指示することになります。そのためウェブアプリケーションのような常駐型のアプリケーションではなく、実行してそのまま終了するスタンドアロン型のアプリケーションを用意します。アプリケーション自体はどのような言語で作られていてもかまいませんが、x86_64 の Linux で実行できるものを用意してください。

このアプリケーションはご自身で用意していただいてもかまいませんが、一応サンプルを用意・公開しました。以下ではこのアプリケーションを Code Engine 上でスケジュール実行する手順を紹介します:
https://github.com/dotnsf/cejob_sample


Node.js を使ったシンプルなアプリケーションです。メインファイルの内容は以下の実質1行だけで、「実行した瞬間のタイムスタンプを ISO フォーマットで標準出力に表示」するというだけのシンプルなものです:
//. app.js
console.log( ( new Date() ).toISOString() );

Code Engine は Knative ベースということもあり、アプリケーションはコンテナイメージ化されている必要があります。 このアプリケーションをコンテナ化するために、以下のような Dockerfile を用意しています(特にポートを EXPOSE することもなく、単に "$ node app.js" を実行しているだけの Dockerfile です):
# https://nodejs.org/ja/docs/guides/nodejs-docker-webapp/

# base image
FROM node:12-alpine

# working directory
WORKDIR /usr/src/app

COPY package*.json ./

RUN npm install

COPY . .

CMD ["node", "app.js"]

作業として必須ではありませんが、自分でもコンテナイメージを作ってみたい場合は docker をインストールして、docker hub にアカウントを作成後に以下のコマンドを実行してイメージをビルドし、docker hub にイメージをプッシュすることで外部から利用可能なコンテナイメージが用意できます(yourname の部分は自分の docker hub アカウント名に置き換えてください):
> docker login

> docker build -t yourname/cejob .

> docker push yourname/cejob


このイメージは私がすでにビルドして公開済みなので、自分でビルドしなくても使える状態になっています:
https://hub.docker.com/r/dotnsf/cejob


以下はこのイメージを使って Code Engine 上で実行できるようにする方法を紹介しますが、自分でビルドしたイメージを使う場合は "dotnsf/cejob" 部分を自分のコンテナイメージ名(上述の "yourname/cejob" 部分)に置き換えて捜査してください。


【IBM Code Engine にジョブを登録】
ここからは実際に Code Engine を使った操作を行います。まずはスケジュール実行させるジョブアプリケーションを Code Engine に登録します。

ウェブブラウザで IBM Cloud にログインします。Code Engine を使うにはライトアカウントでなく、スタンダート以上のアカウントでログインする必要がある点に注意してください。ログイン後の画面で「リソースの作成」ボタンをクリックします:
2021081501


検索バーに "Code Engine" と入力して、Code Engine を選択します:
2021081502


Code Engine に登録するアプリケーションの準備方法を選択します。(docker 環境や docker hub アカウントの準備がなく)ソースコードのみ手元にある場合は右側の「ソース・コードから始める」を選択することで自動的にイメージをビルドして利用することもできますが、今回はすでにイメージが公開されているので、左側の「コンテナー・イメージの実行」の「作成の開始」ボタンを選択します。なお右側の「ソース・コードから始める」の「作成の開始」を選択した場合、コンテナイメージを作ってプライベートリポジトリを利用することになりますが、その際の CPU やメモリ利用量は課金の対象となりますのでご注意ください(既にイメージがある場合は左側から行うことを推奨します):
2021081503


ここからは実際に稼働させるアプリケーションを指定していきます。まずはアプリケーションの種類。以前に紹介したウェブアプリケーションでは「アプリケーション」を選択しましたが、今回のように1回実行して(常駐するでも、待ち受けるでもなく)そのまま終了するアプリケーションの場合は「ジョブ」を選択します。続けてアプリケーションの名前(下図では "mycejob" )を入力します:
2021081504


そのまま下にスクロールして続きを入力します。Code Engine のアプリケーションはプロジェクトという単位でまとめて管理します。すでにあるプロジェクトを選んで使っても(そのプロジェクト内で管理しても)かまいませんが、初めて使う場合はプロジェクトを新規に作成する必要があります。作成する場合は「プロジェクトの作成」ボタンをクリックします:
2021081505


画面右に表示されるダイアログ内で、プロジェクトのサーバーロケーション(下図では「東京」)、プロジェクト名(下図では "my_ce_project")、そしてリソースグループ(下図では「default」、これは選択肢がない場合が多いと思います)を選択して、最後に「作成」ボタンをクリックします:
2021081506


作成したプロジェクトがセレクトボックスに含まれているので選択します。 続けて実行コードを指定します。繰り返しになりますが、今回はソースコードからではなく既存コンテナを使って実行するので「コンテナー・イメージ」を選択し、そのイメージに docker hub 上の dotnsf/cejob を使う場合は "docker.io/dotnsf/cejob" と入力します(自分のイメージを使う場合は該当部分を変更してください):
2021081507


そのまま下にスクロールして更に続きを入力します。ランタイム設定内のインスタンス・リソースでアプリケーションを実行する際の1インスタンスに割り当てられるマシンスペックを指定します(ここで指定するスペックが課金に影響します)。今回は超シンプルなアプリなので最小スペック(vCPU = 0.125 個、メモリ = 0.25 GB)を選択します。他に実行時に環境変数を指定する必要がある場合はここで指定します。最後に右下の「作成」をクリックします:
2021081508


これで Code Engine 上にジョブを作成することができました:
2021081501


この画面を下にスクロールすると、手動でこのジョブを実行することができます。「ジョブの実行依頼」ボタンをクリックして、一度実行させてみます:
2021081502


実行時のインスタンス数を1にして「ジョブの実行依頼」ボタンをクリックします:
2021081503


するとジョブが実行されます(正確にはジョブの実行依頼が実行されます)。この画面が表示された直後はステータスが「待機中」と表示されていて、まだ実行そのものがされていないことを示しています:
2021081504


しばらく待つと(Knative によって)ジョブが実行され、エラーなく正しく実行されていると「成功」ステータスに変わります:
2021081505


先ほどのジョブ一覧画面(ジョブ実行依頼をした画面)に戻ると、実行されたジョブが一覧に含まれる形で表示されていることを確認できます:
2021081506


ここまでの作業で Code Engine 上にスタンドアロンアプリケーションを登録して、手動実行できるようになりました。これだけでもクラウドに実行環境を用意することなく、アプリケーションジョブを(Pay per Use で)実行できる環境が用意できたことになります。この時点でかなり便利な環境ができました。


【IBM CodeEngine に登録したジョブをスケジュール実行する】
本ブログの目的がここです。クラウド上に用意されたアプリケーションジョブを手動実行ではなく、スケジュール実行できるようにします。なお、ここから先の手順は現状 IBM Cloud の GUI ダッシュボードからは行うことができず、CLI で操作する必要があります。

まずは上述の ibmcloud CLI セットアップの手順が済んで、"ibmcloud login" までが完了していることを確認してください。

ジョブにスケジュールを割り当てるには、まず操作する Code Engine プロジェクト(上の例だと "my_ce_project" )を指定する必要があります。ibmcloude CLI で以下のように実行します:
> ibmcloud ce project select --name my_ce_project

確認のため、選択したプロジェクト内のジョブ一覧を表示します(mycejob が存在していることを確認します):
> ibmcloud ce job list

ジョブをリスト中...
OK

名前     経過時間
mycejob  24m

ではこの mycejob を「5分おきに実行」するよう指定します。この「5分おき」というのは cron ジョブスケジュールでは --schedhle "*/5 * * * *" と表現します。またウェブアプリケーションではなくジョブアプリケーソンにこのスケジュールを指定する場合は --destination-type job オプションを指定する必要があります。結論としては以下のように入力します(mycesub という名前の ping サブスクリプションを作成し、mycejob ジョブに対して5分おきの実行を指示しています):
> ibmcloud ce sub ping create --name mycesub  --destination mycejob --destination-type job --schedule "*/5 * * * *"

成功していると5分おきに(毎時 5 分、10 分、15 分、20 分・・・のタイミングで)ジョブが実行され、実行結果が増えていく様子が確認できます:
2021081502


これでインターネットに常時接続している実行環境を用意しなくても、あらかじめ想定したタイミングでスタンドアロンアプリケーションを実行できる環境が用意できました。 COBOL などのレガシー資産でもこういう使い方ができると便利ですよね。


このページのトップヘ