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

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

タグ:cloud

IBM Cloud から提供されている IDaaS サービスである AppID を使っています。AWS の Cognito や Auth0 のようなユーザーログインの(オンラインサインアップやオンラインパスワード変更、多要素認証なども含めた)機能全般をまとめて提供するサービスです。IBM Cloud のライトアカウントを使って無料枠内で利用することも可能です。単なるログイン機能程度であれば自分で実装してもいいのですが、オンラインサインアップや多要素認証まで考慮すると実装は面倒だし、(実在しないアドレスを使うこともできますが)メールアドレス含めて管理するのは個人情報管理の観点からも、特に試験的/実験的な段階では可能であれば避けたいという運用側の事情もあるため、こういったマネージドサービスを使うメリットは大きいと考えています。事実、自分は公私でよく使っています。
2021082902


で、この AppID を使ってサービス開発をしている中で「ユーザーを無効化したい」という意見をいただきました。実験的な意味合いのあるサービスを作って、ある特定の期間だけはそのサービスを使ってもらい、その期間が完了した後は使わせたくない。でもユーザーを削除してしまうと、何かの際に再度ログインしてもらうこともできなくなったり、再作成しても ID のデータ紐付けが切れてしまってたまったデータとの関連が切れてしまうのでそれは都合が悪い。そのため「いったん無効化してログインできないようにしたい」という、運用上ごもっともな意見でした。

「ユーザーの無効化ね。まあ機能的に用意されてるんだろうな」と楽観視していたのですが、AppID のダッシュボードを調べてみると、どうも「ダッシュボード画面からはユーザーの削除はできるが無効化はできなそう」でした。おっと、これはまずいぞ。。。
2021082903


このユーザー無効化を実現するためのワークアラウンドとして考えたのが「パスワードの強制変更」でした。要は管理者の権限で該当ユーザーのパスワードを強制的に変更することで事実上ログインできなくしてしまう、という方法です。アカウントは残るのでデータやその紐付けも消えることはなく、再度初期パスワードに変更すれば、万が一の時にもう一度ログインしてもらうこともできるようになる、と考えました。

で、じゃあダッシュボードからどのようにパスワードを強制変更するのかというと・・・実はパスワードの強制変更もダッシュボードからできるわけではありません。2021年8月現在、AppID ダッシュボードからはユーザーを削除することはできますが、ユーザーを無効化することも、パスワードを強制変更することもできません。

ただ、AppID SDK にはパスワードを強制変更する API がありました(文字通りの「無効化」は SDK からでもできなさそう・・):
https://www.npmjs.com/package/ibmcloud-appid

2021082901
(↑このページ内の "Set User New Password" 欄参照)


つまり AppID SDK のこの setUserNewPassword 関数を使って自分でプログラムを作れば、ユーザーのパスワードを強制的に変更すること自体は実現できそうでした。それによって実質的な無効化も可能にできるのでは・・と考え、挑戦してみました。以下はその成果の共有です。


【サンプルソースコードの紹介】
AppID のユーザーパスワードを変更するサンプルの Node.js アプリケーションを作って、ソースコードを公開しました:
https://github.com/dotnsf/appid_changepassword

このリポジトリを git clone するかダウンロード&展開してください。

使い方は後述しますが、まずは依存ライブラリをまとめてインストールしておいてください(この手順はソースコードを用意した後に1回だけ行ってください):
$ cd appid_changepassword

$ npm install

このフォルダには4つの JavaScript コードファイルが含まれていますが、それぞれ以下のような役割です(create_users_to_appid.js とか、何気に便利なツールだと自負してます):
ファイル役割
app.jsApp ID の動作確認用ウェブアプリケーション。AppID にログイン&ログアウトするだけ
settings.js動作設定用ファイル
create_users_to_appid.jsCSV ファイルから AppID のユーザーをまとめて作成する CLI アプリ
change_password.jsユーザーIDと新パスワードを指定して、パスワードを強制変更する CLI アプリ


パスワードの変更処理を行うのは change_password.js ですが、app.js や create_users_to_appid.js も含めて、この3つのファイルを動かすためにはあらかじめ settings.js に AppID の接続情報を記載しておく必要があります。以下、一通りの使い方を紹介します。


【サンプルソースコードを動かすための事前設定項目】
このアプリを動かすには、何はともあれ App ID のインスタンスが必要です。IBM Cloud にログインしてダッシュボードから App ID を選択してリソースインスタンスを作成してください。その際に「ライト」プランを選択すると 1000 ユーザー&(一ヶ月)1000 ログインまで無料で利用することが可能です(ログインアカウントがライトアカウントの場合は、ライトプランしか選択できません):
2021082904


作成した AppID インスタンスの「サービス資格情報」から(必要であれば1つ作成した上で)サービス資格情報を選択して、資格情報の内容を確認します:
2021082905


資格情報の中は以下のような JSON フォーマットのテキストになっています:
{
  "apikey": "xxxxx",
  "clientId": "xxxxxxxxxxxxxxxxxxx",
  "secret": "xxxxxxxxxxxxxxxxxxx",
  "tenantId": "xxxxx",
  "profilesUrl": "https://jp-tok.appid.cloud.ibm.com",
    :
    :
}

この中の以下5つの情報が必要です:
・apikey
・secret
・clientId
・tenantId
・region(profilesUrl の "https://" と ".appid.cloud.ibm.com" の間の文字列(上例だと "jp-tok"))

これら5つの値を見つけたら、settings.js 内の該当する変数値として入力し、保存します:
//. IBM App ID
exports.region = '(region の値)';
exports.tenantId = '(tenantId の値)';
exports.apiKey = '(apikey の値)';
exports.secret = '(secret の値)';
exports.clientId = '(clientId の値)';

exports.redirectUri = 'http://localhost:8080/appid/callback';
exports.oauthServerUrl = 'https://' + exports.region + '.appid.cloud.ibm.com/oauth/v4/' + exports.tenantId;

準備の最後に App ID のコールバック URL を登録します。今回のアプリケーションはローカルホストで動かす想定をしていて、その場合はローカルホストで動かす際の URL を事前に AppID に登録しておく必要があります。

IBM Cloud ダッシュボードで作成した App ID を開き、「認証の管理」、「認証設定」を選択して、「Web リダイレクト URL の追加」から "http://localhost:8080/appid/callback" を追加します(ローカルホストで動かすのではなく、公開アドレスで動かす場合はその URL を使って "http(s)://ホスト名/appid/callback" を追加してください):
2021082906


これでアプリケーションを動かす準備ができました。


【サンプルソースコードを使ってユーザーを登録】
このブログエントリの目的は「AppID のユーザーパスワードを変更する方法の紹介」です。そのため実際にログインできるユーザーを使って動作確認する必要がありますが、まずはそのユーザーを登録する必要があり、そのためのツールを紹介します。 既に AppID にユーザーが登録されていて、そのユーザーのパスワードを変更してもいい場合、ここは無視しても構いません。

まずは登録するユーザーを以下のようなフォーマットの CSV ファイルで用意します(newusers.csv 参照):
ユーザー表示名,ユーザーID,ユーザーパスワード
ユーザー表示名,ユーザーID,ユーザーパスワード
    :
    :


1列目のユーザー表示名はログインしたユーザーをアプリケーション内で表示する際に表示される文字列です。AppID の場合、ここはなぜか「8文字以上」という制約があるので、8文字以上の表示名を指定します。

2列目のユーザーIDは「メールアドレス形式のログインID」です。App ID の設定によってはオンラインでパスワードリセットを行ったりすることも可能で、その場合のメール送信先にもなるものです(オンラインパスワードリセットを無効にして運用する場合は実在しないメールアドレスでも構いません)。

3列目のユーザーパスワードは、2列目の ID でログインするユーザーのログイン時に指定するパスワードです。 この3列を一組とした行を必要なユーザーぶんだけ用意します。 なおサンプルで newusers.csv というファイルが含まれていて、その内容は以下のような2行になっています(以下、このファイルを使って紹介しますが、自分でこのファイルに相当する内容のファイルを用意した場合は、そのファイル名に置き換えて読み進めてください):
User0001,user0001@mydomain.com,password1
User0002,user0002@mydomain.com,password2

ではこのファイルを使って AppID にユーザーを登録します。コマンドの最後に CSV ファイル名を指定して、以下のように実行します:
$ node create_users_to_appid newusers.csv

画面に色々出力された後にプロンプトに戻ります。成功していると CSV ファイル内で指定されたユーザーが AppID に登録されているはずです。ダッシュボードから「クラウド・ディレクトリー」、「ユーザー」を選択し、CSV ファイルで指定したユーザーが追加されていることを確認します:
2021082907


検証用のユーザーが AppID に登録できました。まずはこのユーザー ID(と CSV ファイルで指定したパスワード)でログインできるか一度確認しておきます。


【サンプルソースコードを使って登録したユーザーでログイン確認】
では今登録したユーザーが登録したパスワードでログインできるかどうかを確認するため、サンプルのウェブアプリケーションを起動します:
$ node app

App ID のメッセージが数行表示された後に "server starting on 8080 ..." と表示されれば起動完了です。ウェブブラウザを開いて http://localhost:8080/ にアクセスします。すると(ログイン前なので) AppID のログイン画面に転送され、以下のような画面が表示されます:
2021082901


この Email 欄と Password 欄にそれぞれユーザーのメールアドレスとパスワードを入力して "Sign in" ボタンをクリックします。先程 CSV ファイルで作成したユーザーのメールアドレスとパスワード(例えば user0001@mydomain.com と password1)を入力してみます:
2021082902


正しく入力されている状態で "Sign in" ボタンをクリックするとログイン処理が成功し、画面が遷移して以下のような画面に切り替わります。ユーザーの ID、表示名、メールアドレスが表示されています。アドレスはログイン前に指定したものと同じ http://localhost:8080/ ですが、ログインが完了しているのでログイン画面には転送されず、この画面が表示されています。"Logout" ボタンでログアウトできます:
2021082903


ログアウトするとログイン画面に戻ります。試しにもう1人のユーザー情報(ID: user0002@mydomain.com, パスワード: password2)でもログインしてみます:
2021082904


User0002 でもログインできました。CSV ファイルから作成したユーザーで正しくログインできる所まで確認できました:
2021082905


ここまで確認できたらいったんサンプルウェブアプリケーションを終了しておきます。"node app" を実行した画面で Ctrl+C を押してアプリケーションを強制終了させておきます。


【サンプルソースコードを使ってユーザーのパスワードを変更する】
さていよいよ本ブログエントリの本題となる「ユーザーパスワードの強制変更」の箇所です。change_password.js ファイルを使って以下のように実行します:
$ node change_password user0001@mydomain.com Password1

実行時に指定するパラメータは2つあります。1つ目がパスワードを変更したいユーザーのログインID(メールアドレス)、2つ目が変更後のパスワードです(つまり上の例だと user0001@mydomain.com ユーザーのパスワードを Password1 に変更します)。旧パスワードを指定せずに強制的に変更する、という点に注意してください。実行すると色々画面に表示されますが、これも処理が完了するとプロンプトに戻ります。

パスワード変更が正しく行われたかどうかを再度サンプルウェブアプリケーションで確認します:
$ node app

ウェブブラウザで https://localhost:8080/ にアクセスしてログイン画面に移動したら、最初は変更前のパスワード(password1)を指定して user0001@mydomain.com でログインを試みてください。先程はログインできましたが、今はパスワードが変更されているのでログインできないはずです(メールアドレスとパスワードが一致しない、という旨のエラーメッセージが表示されます):
2021082906


改めて変更後のパスワードを指定してログインすると、今度はログインが成功してログイン後の画面が表示されます:
2021082907


これで AppID SDK を使ってログインパスワードを強制的に変更することができることが確認できました。

ちなみにソースコード上でのこのパスワード変更処理箇所はこのようになっています:
var SelfServiceManager = require( 'ibmcloud-appid' ).SelfServiceManager;

  :
  :

selfServiceManager.setUserNewPassword( uuid, password, "en", null, null ).then( function( user ){
  resolve( user );
}).catch( function( err ){
  reject( err );
});

  :
  :


ibmcloud-appid ライブラリを呼び出して SelfServiceManager をインスタンス化し、(省略した部分で)アクセストークンを取得してメールアドレスで指定したユーザーの ID(上記コード上では uuid)を特定します。特定できたら selfServiceManager.setUserNewPassword( uuid, password, "en", null, null ); を実行してパスワードを変更しています(password が新しいパスワード)。3番目のパラメータは言語情報らしいですが現在使われていないらしく、既定値の "en" を指定しています。このあたり、詳しくは SDK の説明もご覧ください。


ともあれ、これで当初の目的である「App ID ユーザーの無効化」に近いオペレーションが実現できそうでした。めでたしめでたし。



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 などのレガシー資産でもこういう使い方ができると便利ですよね。


IBM Cloud から提供されている 30 日間無料 Kubernetes サービスIBM Kubernetes Service 、以下 "IKS")環境を使って利用することのできるコンテナイメージを1日に1個ずつ 30 日間連続で紹介してきました。

最終日というかおまけとして、31 日目は実際にこれらのイメージをデプロイしてきたの感想を含めたあとがきを記していきます。


【率直な感想】
改めてこの 30 日間を振り返ります。これだけの環境を作ってきました:

Dayカテゴリーデプロイ内容
0準備準備作業
1ウェブサーバーhostname
2Apache HTTP
3Nginx
4Tomcat
5Websphere Liberty
6データベースMySQL
7phpMyAdmin
8PostgreSQL
9pgAdmin4
10MongoDB
11Mongo-Express
12Redis
13RedisCommander
14ElasticSearch
15Kibana
16CouchDB
17CouchBase
18HATOYA
19プログラミングNode-RED
20Scratch
21Eclipse Orion
22Swagger Editor
23R Studio
24Jenkins
25アプリケーションFX
262048
27DOS Box
28VNC Server(Lubuntu)
29Drupal
30WordPress



自分はプログラマーなので、プログラマー視点というか開発者視点で客観的にこの表を見てまず思うのは、自分が開発環境として使いそうなデータベースやツール、ミドルウェア類は一通り揃っているという事実です。もちろん自分の PC にこの環境を用意することはできるし、ローカルだけで開発環境が整うのはそれはそれでメリットもあるわけですが、一方で無料のリモート環境にこれだけのツール類を揃えて使うことができるというのは充分すぎるというか、「使う予定はないかもしれないけど、この環境を使うならおまけに R Studio も入れちゃおうかな」とか、「念の為 IaaS 的な環境も」とか、「ちょっと気分転換にゲームを」とか余裕で考えるくらいの環境が用意できる、そんな太っ腹な無料サービスだと改めて感じました。

また単なる開発環境である以上に、コンテナや k8s の運用を勉強するための環境としてもかなり使えると感じています。Day 1 で紹介したようなレプリカセットを変更した上で挙動の変化を確認したり、Day 24 では k8s の管理ダッシュボードを使ってポッドの中でシェルを動かしたり、Day 29Day 30 では複数のコンテナを通信させて動く環境を作る、といったことも体験できました。普段データベースは使っているけど、データの解析はしたことがない人でも解析環境まで入手できるようになる、このような、実際に外部から利用可能な形で公開して体験できる形の環境が無料で提供されているという点を改めて素晴らしいと感じています。


【この環境の制約事項について】
もちろん制約事項を感じることもありました。この契約では k8s の Ingress が使えないためサービスは NodePort 公開しかできない(ワーカーノードの IP アドレスでしか公開できない)、そのためコンテナ側に SSL を使った公開方法が考慮されていてもこの環境では使えない、、といった制約を何度か経験することになりました。まあここは開発環境と割り切って使う中ではあまり気にならないとも言えますけど、外部連携時に SSL 接続を前提としているケースがあったり、(IP アドレスではなく)ドメイン名やホスト名を使う前提のサービスと連携するケースがあったりすると、そこで制約を受けることになる点が注意が必要だと思いました。

加えて、今回は 30 日間で紹介するサービスを上述の 30 種類にしていますが、実はこの中に含めたくて諦めたサービスもいくつかあります。それらの多くはこの無料環境でデプロイして使うのは難しい※と判断したものばかりでした。

※自分の誤解に基づくものや、うまく回避する方法を知らなかっただけというものも含まれているかもしれません。ただ今回の企画の時間的な意味も含めた制約の中では「難しい」と諦めざるを得なかった、という意味です。

理由としてもったいなかったのは「デプロイはできたようだが、確認する方法(専用クライアント)がない」というケースでした。超軽量データベースである Apache Derby などはその例で、IKS へのデプロイ自体は成功していたように見えているのですが、動作を確認するにはアプリケーションを作る必要があって、そこまで作業するのが時間制約的に困難だったというケースです。curl やウェブブラウザで動作確認ができず、動作確認用クライアントを作る必要がある、というパターンのは今回は諦めました。

この動作確認用クライアントが存在しない以外の理由が原因となったケースもあります。その1つを紹介すると IBM Db2 コンテナがあります。本環境で IBM Db2 コンテナを使うのが難しそうだと判断した理由は少し特殊な環境設定にありました。

IBM Db2 はデフォルトで 50000 番ポートを使って通信します。ポート番号自体はコンテナ公開時にポートフォワーディングで変更することもできるのですが、IBM Db2 の場合は /etc/services ファイル内にこのポート番号を指定するための設定を加える必要がありました。コンテナで動かす場合は、コンテナイメージ内のこの設定を変更して(かつこの変更が消えないようにして)再起動するか、設定を変更せず 50000 番ポートで公開するか、いずれかの方法が必要となります。

コンテナの紹介ページでは docker コマンドによるデプロイ方法が紹介されていました。docker であれば EXPOSE するポート番号を自由に指定できるのですが、今回は NodePort を使った公開しかできない k8s 環境、という制約があります。また今回は無料の範囲内で(ボリュームを使わずに)紹介するという目的もありました。この制約の中で色々な方法を試したのですが、結局うまく公開することができず、今回は 30 個の中から除外する、という判断をするに至った、という背景を記載しておきます。

ここに記載するのもどうかと思ったのですが、もしかすると詳しい人が引き継いでくれることを期待して、自分が(ちょっと)試してみた限りでは難しそうだと判断して諦めたコンテナイメージのリストを掲載しておきます。本当はこれらを 30 個に加えたかったのだけど、そもそも自分が普段使っているわけではないものもあったりして、設定そのものにあまり詳しくなかったりするものが大半です。もし 30 日無料版 IKS 環境で動かすことができたら教えてください(笑):

コンテナイメージあきらめた理由
Cassandra デプロイはできたが、稼働中にポッドが Pending 状態になる
CockroachDB デプロイ時に CrashLoopBackOff エラー
Apache Derby デプロイはできているっぽいが、動作確認ができていない
GitLab デプロイはできたが、稼働中にポッドが Pending 状態になる(HTTPS 必須で HTTP 接続不可?)
IBM Db2 50000 番ポートでのサービス公開が NodePort の範囲外だった
MediaWiki デプロイはできたが、稼働中に大量の deprecated メッセージ
Memcached デプロイはできたように見えたが、接続時にエラー
Minix デプロイはできたが、Minix のシェルにログインできない・・・と思っていたが、後から方法がわかりました(k8s のポッドにコンソールログインしてから "# ssh localhost")。これを 30 個に含めてもよかったかも。
Mosquitto デプロイはできているっぽいが、動作確認ができていない
phpMoAdmin デプロイはできているっぽいが、MongoDB に接続できない(docker 環境でしか動かないコンテナイメージ?)
Container Registry デプロイはできているっぽいが、動作確認ができていない(HTTPS 必須で HTTP 接続不可?)
SAMBA デプロイはできたように見えたが、接続時にエラー
Ubuntu デプロイ時に CrashLoopBackOff エラー


Minix イメージについては幻の 31 日目のイメージとして加えさせていただきます(笑)。


上述の IBM Db2 もそうだったのですが、コンテナ内のファイルを編集して再起動、を前提とするようなコンテナイメージや、そもそも動作に必要なスペックが高すぎるものは諦めました。自分の場合はあくまで開発環境という意味での「とりあえず動いて外部から利用できることが重要」という視点で見ていたので、上記 30 個のリストの中にもしかすると使っているうちに別の制約を受けることになる可能性は否定できません。


【最後に】
改めて書きますが、これだけの開発環境が連続して 30 日間無料で使えるというのはやはり魅力です。自分が業務や個人で作っているアプリケーションを作る際の環境と比較しても、ほぼ支障を感じることがありませんし、制約事項で挙げたサービスについても他の方法で無料で入手することができるものばかりと考えると、アプリケーション開発者にとっては無料で勉強も利用もできる環境がかなり広くなったと改めて感慨にふけることができるようになりました。

余談ですが、IBM Cloud にはこの 30 日無料版 k8s クラスタ環境以外にも、無料枠内で(期間制限なしで)使える多くのサービスが用意されています。この k8s 環境を通じて、興味が湧いたらぜひこちらも試していただきたいです。これは私自身の感想ですが、マネージドサービスで使えるデータベースは本当に便利です。

本 k8s 環境をできるだけ多くの人に知ってもらって、使ってみていただければと思っています。


最後に 30 日間の利用環境が残り1時間になった時の記念写真。1か月ありがとうございました。
mycluster1

mycluster2




IBM Cloud から提供されている 30 日間無料 Kubernetes サービスIBM Kubernetes Service 、以下 "IKS")環境を使って利用することのできるコンテナイメージを1日に1個ずつ 30 日間連続で紹介していきます。

環境のセットアップや制約事項については Day0 のこちらの記事を参照してください。

Day 25 からはアプリケーション系コンテナとその GUI ツールを中心に紹介してます。最終日である Day 30 は集大成として世の中の大半のウェブコンテンツを管理していると思われる WordPress イメージをデプロイする例を紹介します。
wp0


【イメージの概要】
ブログなどのウェブコンテンツを管理・編集・公開するシステムです。

個人的にも WordPress には思い入れがあります。業務とは関係のないところで WordPress を知って、勉強して、そこから自分のキャリアが変わるきっかけにもなりました。今の自分が今の立場でいるのは WordPress との関わりがあったからだと思っています。そんな背景もあって 30 日間連続ブログの最終日に紹介するコンテンツとさせていただきました。

WordPress の動作時にはデータベースが必要なのですが、今回は MySQL を使う方法を紹介します。MySQL 単体のデプロイについては Day 6 でも紹介しているので、必要に応じて参照してください。


【イメージのデプロイ】
まずはこれら2つのファイルを自分の PC にダウンロードしてください:
https://raw.githubusercontent.com/dotnsf/yamls_for_iks/main/mysql.yaml
https://raw.githubusercontent.com/dotnsf/yamls_for_iks/main/wordpress.yaml

前者が MySQL 用、後者が WordPress 用のデプロイファイルです(両方使います)。
前者の MySQL 用デプロイファイルは Day 6 で行ったものと同様の編集が必要です。mysql.yaml ファイルをテキストエディタで開いて、 "MYSQL_" で始まる4箇所の env.name の value 値を変更してください。それぞれの具体的な意味は以下の通りです(初期値として指定されている値のまま動かすことも可能ですが、安全のためなるべく変更してください):
・MYSQL_ROOT_PASSWORD : 管理者パスワード(初期値 P@ssw0rd)
・MYSQL_DATABASE : デプロイと同時に作成するデータベースの名前(初期値 mydb)
・MYSQL_USER : デプロイと同時に作成するデータベースを利用するユーザー名(初期値 user1)
・MYSQL_PASSWORD : デプロイと同時に作成するデータベースを利用するパスワード(初期値 password1)

また後者の WordPress 用デプロイファイルには4箇所の変種が必要です。wordpress.yaml ファイルをテキストエディタで開いて、 "WORDPRESS_" で始まる4箇所の env.name の value 値を変更してください。それぞれの具体的な意味は以下の通りです。特に WORDPRESS_DB_HOST の値は後述する IP アドレスと上述の MySQL に設定した値を両方参照して入力する必要がある点に注意ください:
・WORDPRESS_DB_HOST : MySQL のホスト名とポート番号(初期値 xxx.xxx.xxx.xxx:30306)
・WORDPRESS_DB_USER : MySQL に接続するユーザー名(初期値 user1)
・WORDPRESS_DB_PASSWORD : MySQL に接続するパスワード(初期値 password1)
・WORDPRESS_DB_NAME : MySQL の DB 名(初期値 mydb)

ではこのダウンロード&編集した2つの yaml ファイルを指定してデプロイします。以下のコマンドを実行する前に Day 0 の内容を参照して ibmcloud CLI ツールで IBM Cloud にログインし、クラスタに接続するまでを済ませておいてください。

そして以下のコマンドを実行します:
$ kubectl apply -f mysql.yaml

$ kubectl apply -f wordpress.yaml

以下のコマンドで MySQL 関連の Deployment, Service, Pod, Replicaset が1つずつ生成されたことと、サービスが 30306 番ポートで公開されていること、そして WordPress 関連も同様に Deployment, Service, Pod, Replicaset が1つずつ生成され、サービスが 30080 番ポートで公開されていることを確認します:
$ kubectl get all

NAME                            READY   STATUS    RESTARTS   AGE
pod/mysql-5bd77967b-z9lcl       1/1     Running   0          104s
pod/wordpress-67848cd6b-296kh   1/1     Running   0          62s

NAME                  TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
service/kubernetes    ClusterIP   172.21.0.1       <none>        443/TCP          27d
service/mysqlserver   NodePort    172.21.89.71     <none>        3306:30306/TCP   105s
service/wordpress     NodePort    172.21.140.192   <none>        80:30080/TCP     63s

NAME                        READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/mysql       1/1     1            1           106s
deployment.apps/wordpress   1/1     1            1           64s

NAME                                  DESIRED   CURRENT   READY   AGE
replicaset.apps/mysql-5bd77967b       1         1         1       106s
replicaset.apps/wordpress-67848cd6b   1         1         1       64s

この後に実際にサービスを利用するため、以下のコマンドでワーカーノードのパブリック IP アドレスを確認します(以下の例であれば 161.51.204.190):
$ ibmcloud ks worker ls --cluster=mycluster-free
OK
ID                                                       パブリック IP    プライベート IP   フレーバー   状態     状況    ゾーン   バージョン
kube-c3biujbf074rs3rl76t0-myclusterfr-default-000000df   169.51.204.190   10.144.185.144    free         normal   Ready   mil01    1.20.7_1543*

つまりこの時点で(上述の結果であれば)アプリケーションは http://169.51.204.190:30080/ で稼働している、ということになります。ウェブブラウザを使って、アプリケーションの URL(上述の方法で確認した URL)にアクセスしてみます:
wp1


WordPress をインストールしたことがある人にはお馴染みの初期セットアップ画面が表示されます。とりあえず無事に IKS 内で WordPress が稼働できているようです。

Drupal の時とは異なり、データベース接続情報などはデプロイ時に既に指定しているので、サイトの情報を入力するだけで使えるようになります:
wp2


セットアップが無事に成功しました。あとはセットアップ時に指定したユーザー&パスワードでログインすれば管理画面にアクセスできるようになります:
wp3


WordPress のコンテンツ管理画面にアクセスできました。IKS 内で WordPress を起動できました:
wp4


 Drupal の時と同様ですが、コンテンツフォルダを共有していないので、インスタンス数を2以上に増やして使うことはできませんが、とりあえず WordPress が動く環境を作ることができました。


【YAML ファイルの解説】
WordPress の YAML ファイルはこちらを使っています(MySQL の YAML ファイルについては Day 6 参照):
apiVersion: v1
kind: Service
metadata:
  name: wordpress
spec:
  selector:
    app: wordpress
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
    nodePort: 30080
  type: NodePort
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: wordpress
spec:
  replicas: 1
  selector:
    matchLabels:
      app: wordpress
  template:
    metadata:
      labels:
        app: wordpress
    spec:
      containers:
      - name: wordpress
        image: wordpress
        env:
        - name: WORDPRESS_DB_HOST
          value: "xxx.xxx.xxx.xxx:30306"
        - name: WORDPRESS_DB_USER
          value: "user1"
        - name: WORDPRESS_DB_PASSWORD
          value: "password1"
        - name: WORDPRESS_DB_NAME
          value: "mydb"
        ports:
        - containerPort: 80

Deployment 1つと、Service 1つ、環境変数の指定も不要で本シリーズで紹介する 30 個の中でも指折りにシンプルな YAML ファイルです。一応解説を加えておきます。アプリケーションそのものは 80 番ポートで動作するように作られているため、NodePort 30080 番を指定して、外部からは 30080 番ポートでアクセスできるようにしています(NodePort として指定可能な番号の範囲は 30000 ~ 32767 です、指定しない場合は空いている番号がランダムに割り振られます)。また ReplicaSet は1つだけで作りました。


デプロイしたコンテナイメージを削除する場合はデプロイ時に使った YAML ファイルを再度使って、以下のコマンドを実行します。不要であれば削除しておきましょう:
$ kubectl delete -f wordpress.yaml

$ kubectl delete -f mysql.yaml


【紹介したイメージ】
https://hub.docker.com/_/wordpress


【紹介記録】
ついに 30 日間 30 イメージ紹介を達成しました!

Dayカテゴリーデプロイ内容
0準備準備作業
1ウェブサーバーhostname
2Apache HTTP
3Nginx
4Tomcat
5Websphere Liberty
6データベースMySQL
7phpMyAdmin
8PostgreSQL
9pgAdmin4
10MongoDB
11Mongo-Express
12Redis
13RedisCommander
14ElasticSearch
15Kibana
16CouchDB
17CouchBase
18HATOYA
19プログラミングNode-RED
20Scratch
21Eclipse Orion
22Swagger Editor
23R Studio
24Jenkins
25アプリケーションFX
262048
27DOS Box
28VNC Server(Lubuntu)
29Drupal
30WordPress

このページのトップヘ