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

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

タグ:bluemix

いま自分の空き時間を使って、過去に Node.js + Cloudant を使って(普通のウェブアプリケーションとして)作っていたウェブサービスの Node-RED 環境への移植に挑戦しています。要はサーバーサイド JavaScript 実行環境である Node.js を使って過去に開発したウェブアプリケーション(画面や REST API)を、Node-RED の HTTP リクエスト/レスポンスノードや、HTML テンプレートノードを使っても動くように移植することに挑戦している、ということです。それなりに実績のある Node.js アプリケーションを Node-RED 上でも動かすことができれば、プラットフォームとしての Node-RED のポテンシャルを証明することができるのではないか、と考えています。

これを具体的に進めようとすると、まずウェブ画面は(i18n とかを考慮しなければ)HTML テンプレートノードを使えば一通りのことはできると思っています。要するに HTML テンプレートの中に HTML や CSS, フロントエンド JavaScript を含めてしまえば、見た目や挙動含めて一通りの画面を作ることはできると思っています。

問題は REST API 部分です。例えばデータベース(今回は Cloudant)のデータを読み書きするインターフェースを REST API で用意しておき、フロントエンドの画面から REST API を呼び出すことでデータの読み書き更新削除を行うことができるようになります(理論上は)。この REST API を Node-RED の HTTP リクエストノードと、HTTP レスポンスノードと、function などのノードを駆使して必要な機能を実装することができるかどうかが移植の可否になりそうだと思っています。まあ普通にデータを読み書き更新削除検索・・・する程度であれば標準の Cloudant ノードの機能範囲内でできそうな感触を持っています。


さて、今とある Node.js + Cloudant 製ウェブアプリケーションの Node-RED プラットフォームへの移植を設計している中で1つの壁に当たってしまいました。上述のように「普通の」データの読み書きの REST API 化はさほど問題にならないのですが、Cloudant が持つ特徴を使った部分が普通のデータのように扱うことができず、一筋縄ではいかない内容でした。結論としてはなんとなく解決の目処はたったと思っているのですが、その内容と解決までの経緯を含めて以下にまとめてみたので、興味ある方はご覧いただきたいです。


さて、問題となった Cloudant が持つ特徴を使った部分です。Cloudant は NoSQL 型(JSON 型)データベースですが、特殊な JSON フォーマットで格納することでバイナリデータを格納することができます。またその格納されたバイナリデータを(Content-Type 含めて)出力することもできます。この機能を使うことで、例えば画像データを Cloudant に格納して、画像データとして出力する、といったことも可能です。この機能は Cloudant の各種 SDK からも便利に使えるよう関数化されていたりします。

実は Cloudant のこのバイナリデータ格納機能を使っている場合が Node-RED 移植をする上でのネックとなります。Node.js などのプログラミング言語で Cloudant を利用する場合(特に上述の機能を使ってバイナリデータを Cloudant に格納する要件が含まれる場合)、Cloudant の REST API や各種 SDK を使ってデータの読み書きを実装することになります。上述のバイナリデータの読み書きも同様です。バイナリデータを書き込んだり、バイナリデータを Content-Type 含めて(つまり画像データであれば画像データとして)取り出して出力したりできます。実際にアプリケーション開発の中でこの機能を使って実装していることは(個人的にはバイナリデータの格納先に Cloudant を使うことが多いので)珍しくありません。

しかし、この部分を Node-RED に移植できるか? となると話は変わってきます。まず Node-RED からは Node.js 向けの Cloudant SDK を利用することはできません。function ノードの中でがんばって  Cloudant の REST API を呼び出すような JavaScript を書けば Node-RED でできるかもしれません(認証情報をどのように管理するかの問題は残ります。またどうせ JavaScript でゴリゴリ書くというのであれば、そもそも Node-RED をプラットフォームに選択しない方が正しいような気もします)。 この問題を標準の Cloudant in/out ノードだけでバイナリデータの読み書きを扱うことはできないか? と読み替えて考えることにしました。


【方法1 正攻法】
そもそも何が正攻法なのか、という問題もありますが、実は標準の Cloudant in ノード(Cloudant にデータを格納するノード)はバイナリデータを格納することもできます。上述の Cloudant のバイナリデータ格納機能は単に JSON データフォーマットをうまい具合に指定することで実現しているので、データを格納する点までは少しの工夫で実現できるのでした。

ただし、この方法の問題点は格納時ではなく取り出し時にあります。標準の Cloudant out ノード(Cloudant からデータを取り出すノード)は _id 値を指定してデータを取り出すことはできるのですが、肝心のこの部分がバイナリデータ格納を意識することなく、普通に JSON データとして取り出してしまうことしかできないのでした。特殊なフォーマットで格納することでバイナリデータ格納を実現しているのですが、この特殊なフォーマットに合わせた取り出しができないため、書き込むことはできても読み出せない、という問題が残ってしまうのでした。。


【方法2 BASE64 エンコードを利用して独自実装】
なんとなく解決の目処が立っているのがこちらの方法です。データの読み書きそのものは Cloudant の標準ノードを使うのですが、扱うバイナリデータは格納前に BASE64 でエンコードして(標準 Cloudant in ノードで)格納します。そして取り出す際も普通に標準 Cloudant out ノードで取り出した後に該当部分を BASE64 でデコードします。最後に HTTP レスポンスノードの属性で Content-Type ヘッダを指定して、デコード結果(画像バイナリ)を返信する、という方法です。プログラミングによるカスタマイズを駆使した、いかにもプログラマーらしい方法ですが、こちらの方法であれば格納時だけでなく取り出し時にも問題なく実現できそうです。

試しにフローを作ってみました。Github でも公開したので良かったらこちらからフローをダウンロードするなどして後述の手順で試してみてください:
https://github.com/dotnsf/nodered_cloudant_binarydata_io


【方法2 サンプルフローの使い方】
このサンプルを使って、実際にバイナリデータ(画像データ)を Node-RED で読み書きできることを確認してみます。

まずは Node-RED 環境を用意します。個別に用意していただいても構いませんが、最終的に Cloudant データベースを用意する必要もあるので、IBM Cloud を使って用意する方法がおすすめです。なお IBM Cloud を使ってここに書かれた方法で Node-RED 環境を構築した場合は、始めから Cloudant-in / Cloudant-out ノードがインストールされた状態になっているので、後述のこれら Cloudant 関連ノードのインストールは不要です。無料のライトアカウントを使って構築することもできる内容なので、Node-RED 環境がない人が試す上でおすすめの方法ではあります。

上記以外の方法で(普通にインストールするなどして)Node-RED 環境を用意した場合は node-red-node-cf-cloudant ノードを別途インストールする必要があります。右上のメニューから「パレットの管理」を選択し、「ノードを追加」から "node-red-node-cf-cloudant" を検索して追加してください:
2021032101

2021032102


併せて IBM Cloud にログインして Cloudant サービスを追加して利用できるようにしておいてください。繰り返しますが、このあたりあまり詳しい自信がない場合は上述の方法で IBM Cloud 環境内に Node-RED 環境を Cloudant データベースや Cloudant ノードなどとまとめて用意するのがおすすめです。


Node-RED が準備できたら、上述の Github リポジトリを使ってサンプルのフローを構築します。この flow.json ファイルがサンプルのフローそのものです。リンク先のテキスト内容をまとめてコピーし、Node-RED の右上メニューから「読み込み」を選択します:
2021032103


読み込みのダイアログで「クリップボード」を選択し、コピーしていた内容をペーストします。そして「新規のタブ」を選択し、最後に「読み込み」ボタンをクリックします:
2021032104


するとこのようなフロー画面が再現されるはずです:
2021032105


このままだとまだ2つの Cloudant ノード(画面上では "mydb" と表示されている2つの水色ノード)が未接続で使えません。どちらかをダブルクリックして設定ダイアログを表示します。すると Service 欄が一瞬だけ空のまま表示されますが、IBM Cloud の Node-RED 環境であれば接続済みの Cloudant サービスを見つけて接続してくれます。Service 欄に Cloudant サービス名が表示されたら「完了」ボタンをクリックします(もう1つの Cloudant ノードも同様にして Service 欄が埋まった状態にします):
2021032106


このように2つの Cloudant ノードの右上に表示されていた赤い印が2つとも消えればサンプルを動かすための準備は完了です。画面右上の「デプロイ」ボタンでデプロイして動作前の準備は完了です:
2021032101


改めてこのタブを見ると、3つの HTTP リクエストを処理するフローが定義されています:
#HTTP リクエスト処理内容
1GET /home画像ファイルアップロード画面
2POST /file画像ファイルアップロード処理
3GET /file(?_id=XXXX)アップロードした画像ファイルを画像として取り出す処理


1番目の GET /home は後述の 2 と 3 の動作を確認するための UI として、ファイルを指定してアップロードできる画面を表示するものです。実際に /home へアクセスすると、以下のような画面が表示されます:
2021032201


非常にシンプルなファイルアップロード機能を持ったページです。「ファイルを選択」ボタンを選んでローカル PC からファイル(今回は画像ファイル)を選択して「送信」ボタンをクリックです。「送信」すると、2番目 POST /file が実行されて、選択したファイルが Cloudant に格納される、というものです。

ここで試しに以下の画像ファイルを指定してアップロードしてみます(お好きな画像で試してください):
dotnsf_logo_200x200


画像ファイルを指定して「送信」します:
2021032202


こんな感じの HTTP POST の結果が表示されます(実際のアプリでは AJAX を使うなどしてこの結果をそのまま表示しないようにします):
2021032203


この後に Cloudant のダッシュボードなどから mydb データベースの中を確認するとデータが1件追加されているはずです:
2021032204


表示を JSON 形式などに切り替えると、格納されたデータファーマットも確認できます(type に画像フォーマット、data に base64 でエンコードした画像バイナリデータが格納されています):
2021032205


このデータの id 値を確認します(上図だと c7c3eb8e3b9ac0fffcd45c1beea6c62a )。この値と3番目の GET /file を使って格納された画像を表示してします。ウェブブラウザで /file?_id=(id の値) にアクセスして、アップロードした画像が表示されることを確認します:
2021032206
(↑アップロードした画像が復元できた!)


Node-RED を使ってバイナリ(画像)ファイルを Cloudant に格納し、また Node-RED から画像を復元することも実現できることがわかりました。


【方法2 解説】
Node-RED の HTTP リクエストでバイナリデータを格納したり、Node-RED の HTTP リクエストで格納したバイナリデータを取り出すことができる、ということがわかりました。以下はこれを実現している上記フローの解説です。

まず画面 UI である GET /home ですが、これはごく普通に enctype="multipart/form-data" を指定したフォームを定義しているだけです。テンプレートノードの中身は以下の内容の HTML です:
<html>
<body>
<form method="POST" action="/file" enctype="multipart/form-data">
<input type="file" accept="image/*" capture="camera" name="image" id="image"/>
<input type="submit" value="送信"/>
</form>
</body>
</html>

次に POST /file の各ノードを説明します。まず HTTP in ノード(POST /file と書かれたノード)はファイルアップロードに対応する処理を行うため「ファイルのアップロード」にチェックを入れている点に注意してください:
2021032207


また直後の function ノードの内容は以下のようになっています。アップロードされたファイルは msg.req.files に配列で格納されます(今回はファイル1つだけですが、配列の0番目に格納されます)。その mimetype と buffer を取り出し、buffer を base64 エンコードして msg.payload に格納し直して、最後にタイムスタンプを追加する、という処理を行っています(Cloudant データベースにはこのフォーマットで格納されていたはずです):
2021032208
//. アップロードしたファイルを base64 エンコーディング
var type = msg.req.files[0].mimetype;
var img64 = new Buffer( msg.req.files[0].buffer ).toString( 'base64' );

//. 独自フォーマット化
msg.payload.type = type;
msg.payload.data = img64;

//. タイムスタンプを追加
msg.payload.timestamp = ( new Date() ).getTime();

return msg;


この function ノードで処理された msg.payload の内容を Cloudant out ノードが受け取って格納します。このノードでは「Only store msg.payload object?」にチェックを入れて、ヘッダ情報などを格納しないようにしています。これで指定したバイナリファイルを(base64 エンコードして)Cloudant に格納する(同時に _id が割り振られます)、までの処理を実現しています:
2021032209


最後に GET /file(?_id=XXXX) のノードを紹介します。まず Cloudant in ノードでは特別な処理は行っておらず、パラメータとして与えられた _id を使って Cloudant の mydb 内を検索して結果を返す内容にしています:
2021032201


直後の function ノードでは mydb から取り出した結果を画像に戻す処理をしています。上述の function ノードの逆を行う形で、msg.payload.data の値を base64 デコードして画像バイナリに戻して msg.payload に代入し直しています:
2021032202
//. base64 エンコードされているバイナリデータをデコード
if( msg.payload && msg.payload.data ){
  msg.payload = new Buffer( msg.payload.data, 'base64' );
}

return msg;


その結果を HTTP レスポンスノードに渡して処理は終了です。が、このノードでは HTTP ヘッダをカスタマイズし、"Content-Type: image/png" を付けています。つまり直前の function ノードで取り出した画像のバイナリを画像(image/png)として送信するための処理を最後に加えています:
2021032203


これらのノードや処理を組み合わせることで Node-RED の HTTP リクエストからバイナリデータを Cloudant に格納したり、格納したデータからバイナリデータを取り出して Content-Type ヘッダを付けて返す、といった一連の処理を実現していました。


この例は Cloudant にバイナリデータを格納する場合のサンプルでしたが、おそらくほぼ同様の方法で他のデータストアにも応用できると思っています。



IBM Cloud から提供されているマネージドデータベースサービスの1つである PostgreSQL を使う機会がありました(MySQL は過去に使ったことありましたが、PostgreSQL でサービスを作ったのは初めてでした)。PostgreSQL をデータベースとする Node.js のウェブアプリケーションを開発するのが目的でしたが、最初の準備がちと独特だと感じたので次回戸惑わないようにその手順をまとめておきました:
2021020300



【サービス作成までの手順】
まずは IBM Cloud 内のダッシュボードで Database for PostgreSQL という名前のサービスを探して選択します。"PostgreSQL" という名前でいくつかのサービスが見つかりますが、他を選択しないように注意してください(なおこのサービスは有償サービスのみです。無料でのサービスプランは存在していません):
2021020301


"Standard" プランを選択し、必要に応じて容量などのパラメータを調整し、最後に "Create" ボタンでサービスを作成します:
2021020302


サービス作成が完了するとダッシュボードからも参照・選択できるようになります:
2021020303


【サービス作成後の作業、および手順】
Database for PostgreSQL サービスを実際に利用する(テーブルを作ったり、データを読み書きしたりする)ためには、その前にいくつかの準備段階が必要です:
1. 証明書のダウンロード
2. admin ユーザーのパスワード変更
3. 接続情報の確認


まずは証明書をダウンロードします(プログラムコードからデータベースに接続する際に必要です)。作成したサービスインスタンスの画面を開き、画面左の "Overview" メニューを選択します:
2021020304


"Overview" 画面を下にスクロールすると、エンドポイントに関する情報を参照できる画面が現れます。ここの "Quick start" タブ内に TLS 証明書の内容が表示されている箇所があります。その下の "Download Certificate" ボタンをクリックすると、同証明書の内容をテキストファイルでダウンロードできます:
2021020305

(後述の作業のため、ダウンロードしたファイルの名前を cert.crt と変更しておきます)

次に実際にデータベースを読み書きする際のログインユーザー(admin)のパスワードを設定します。画面左の "Setup" メニューを選び、"Change Password" 欄から新しいパスワードを設定できます。ここで設定したパスワードを使って PostgreSQL にログインできるようになります:
2021020306


最後に PostgreSQL へ接続するための接続情報を確認します。"Service credentials" メニューから "New credential" ボタンをクリックして新しい接続情報を作成します:
2021020307


作成した接続情報を展開して、以下3箇所の内容を確認しておきます:
2021020308

接続情報の場所値が意味するもの
connection.postgres.hosts.hostnameホスト名
connection.postgres.hosts.portポート番号
connection.postgres.databaseデータベース名


以上、この3点の作業を行うことで外部プログラムからデータベースに接続するための準備は完了しました。


【外部アプリケーションからデータベースに接続】
以下、Node.js を例として、プログラムから同データベースに接続してクエリーを発行するサンプルを紹介します。上述の準備段階で確認した内容を使って、以下のようなコードを記述して実行します:
var fs = require( 'fs' );
var PG = require( 'pg' );  //. node-postgres https://www.npmjs.com/package/pg

//. PostgreSQL
var pg_hostname = 'hostname';    //. connection.postgres.host.hostname の値
var pg_port = 35432;             //. connection.postgres.host.port の値
var pg_database = 'ibmclouddb';  //. connection.postgres.database の値
var pg_username = 'admin';       //. ユーザー名(固定値)
var pg_password = 'password';    //. 設定したパスワード

var connectionString = "postgres://" + pg_username + ":" + pg_password + "@" + pg_hostname + ":" + pg_port + "/" + pg_database;//+ "?sslmode=verify-full";
var caCert = fs.readFileSync( './cert.crt', 'utf-8' );  //. ダウンロードした証明書ファイル
var pg = new PG.Client({ 
    connectionString: connectionString,
    ssl: { ca: caCert, rejectUnauthorized: true }
});
pg.connect( function( err, client ){
  if( err ){
    console.log( 'err00', err );
  }else{
    var sql = 'select * from mytable where id = $1';
    var query = { text: sql, values: [ "123" ] };
    client.query( query, function( err, result ){
      if( err ){
        console.log( err );
      }else{
        console.log( result );
      }
    });
  }
});

今回は node-postgres という npm パッケージを使って PostgreSQL にアクセスしています。まず接続情報から取得した値を使って接続文字列を作成します。pg_username(ユーザー名)だけは "admin" で固定になりますが、それ以外の値は上述の作業で接続情報から参照したものや、自分で設定したパスワードを指定します。

次に接続する際に TLS 証明書が必要です。この証明書も上述の作業でダウンロードした TLS 証明書ファイル(cert.crt という名前でダウンロードしたと仮定しています)をファイルパスを指定して読み込み、接続時のパラメータとして含めています。こうすることで node-postgres 経由で証明書を使った接続が可能となります。

接続後は一般的な PostgreSQL 利用と同じですが、接続結果として得られた client オブジェクトを利用して SQL が実行できるようになる、というものです。


↑の「証明書ファイルを指定して接続する」という部分が(手順としては)少し特殊ですが、より安全な接続が実現できるようになるものです。



IBM Cloud の比較的新しい機能として、オブジェクトストレージ内の CSV ファイルに対して SQL を実行してレコードを抽出することができるようになりました。その新機能 SQL Query を紹介します。なお以下で紹介する手順は IBM Cloud のライトアカウント(無料版)の範囲内でも実施可能なので、興味ある方はぜひ本記事を参考に使ってみてください。
2021011600



この SQL Query はオブジェクトストレージ内の CSV ファイルを対象として実行するので、まずは(まだ利用していない場合は)クラウドオブジェクトストレージサービスを作成します。プランが「ライト」のものを選択して作成すれば一定条件内で無料で利用可能です:
2021011601


作成したオブジェクトストレージサービスの "Integrations" メニューを表示すると、このオブジェクトストレージサービスに統合されたサービスを確認できます。作成直後ではここには何もないはずですが、画面下部に「SQL Query を新規に作成して統合可能」と案内されています。この SQL Query のアイコンを選択することで、SQL Query (の無料版)を新規に作成して、このオブジェクトストレージと紐付けて利用することができるようになります:
2021011602


作成する SQL Query サービスの属性を設定する画面が表示されます。このプランとして「ライト」を選択すれば一定条件内で無料利用が可能です。最後に「作成」をクリック:
2021011603


SQL Query サービスが追加され、オブジェクトストレージと統合されて利用できるようになりました:
2021011604


また「バケット」メニューを参照すると、SQL Query 用のバケットが1つ追加されていることが確認できます。このバケットは必ずしも利用する必要はありませんが、SQL を実行する CSV ファイルや、SQL の実行結果をファイルとして残したい場合のファイル格納場所として利用できます。

今回はこの作成されたバケット内に CSV ファイルを置いて SQL を実行し、その実行結果ファイルもこのバケット内に作成する手順を以下で紹介します。というわけで、まずはこのバケット内に CSV ファイルを格納するため、バケット名部分を選択します:
2021011605


選択した(作成したばかりの)バケット内のファイル一覧が表示されますが、この時点ではまだ CSV ファイルは格納されていません:
2021011606


もし自分でお持ちの CSV ファイルを利用したい場合は、その CSV ファイルをこの一覧部分にドラッグ&ドロップしてアップロードしていただいても構いません。以下のサンプルでは以前に自分が全く別の目的でラズベリーパイの機器温度等を1秒おきに取得した際の CSV ファイルがあったので、このファイルを使って紹介することにします。

同 CSV ファイルは以下に公開しているので、以下ページを表示して、マウス右クリックから「名前を付けて保存」し、RPDATA.csv という名前で保存してください:
https://raw.githubusercontent.com/dotnsf/RPDATA/master/RPDATA.csv

2021011607
(1行目は列定義、2行目以降がデータで、左から ID、CPU 温度(℃)、CPU 負荷(%)、サイン値)


ダウンロードした RPDATA.csv をオブジェクトストレージ内のバケットにドラッグ&ドロップ(または「アップロード」ボタンからアップロード)して同バケット内に格納しておきます:
2021011608


これで SQL を実行する準備ができました。では SQL Query のサービスに移動します。改めてオブジェクトストレージのサービス画面から Integrations メニューを選択し、統合されている SQL Query サービス名部分を選択します:
2021011601


SQL Query サービス画面が表示されたら、画面右上の "Launch SQL Query UI" ボタンをクリックして UI 画面に移動します:
2021011602


以下のような IBM SQL Query の UI 画面が表示されます(↓この画面ではエラーメッセージが表示されていますが気にしないでください(苦笑)):
2021011603


画面上部に SQL を入力します。その際の from 直後のテーブル部を指定する箇所では、アップロードした CSV ファイルを指定します。CSV ファイルは Target location に表示されている文字列の、最後の result/ を除いて変わりに RPDATA.csv を指定し、最後に Run ボタンをクリックします:
2021011601
("select * from cos://us-south/XXXXXX/RPDATA.csv limit 10" と入力しました。一般的な SQL SELECT 文のテーブル名を指定する箇所にオブジェクトストレージ内の CSV ファイルを URI で指定する形になっています)


すると画面下部に SQL クエリーの実行結果が表示されます。特にテーブル定義をすることなく、CSV の1行目にかかれていた列情報をそのまま使って SELECT 文の実行ができることがわかります:
2021011602


またこの実行結果はもとのオブジェクトストレージのバケット内の新 CSV ファイルとしても保存されています。必要に応じてダウンロードするなどして結果を確認できます:
2021011603


オブジェクトストレージに格納された CSV ファイルに対して直接クエリーを実行できるので、IoT システムなどによって記録された CSV ファイルをバッチ処理でオブジェクトストレージ内に格納できてしまえすれば、(SQL 型のデータベースにインポートすることなく)その CSV ファイルの中から条件にあった列だけを取り出すことができる、ということがわかりました。実行結果もオブジェクトストレージ内のファイルとして生成することができるので、IoT システムなどでセンサーデータを集める場合でも「とりあえずは CSV にしてオブジェクトストレージに入れておけば、条件を指定した取り出しは後からできる」ことになって便利だと感じました。




これらの記事の続きです:
IBM Cloud の無料版 Kubernetes サービスを使ってみた(1)
IBM Cloud の無料版 Kubernetes サービスを使ってみた(2)


IBM Cloud の Kubernetes サービスを使って、無料のワーカーノードにアプリケーションをデプロイして、(いったん)削除する所までを紹介しました。最終回である今回は実際にアプリケーションにアクセスしたり、デプロイしたアプリケーションをスケールさせてみたり、外部(インターネット)に公開してみたりします。

今回の作業も基本的にすべて Web Terminal から行います。というわけで、まず Web Terminal を起動します:
2019041109


改めてアプリケーションをデプロイします。が、今回は後述の RC(ReplicatoinController) としてデプロイするため、デプロイ用のファイル(rc.yaml)を作成します:
apiVersion: v1
kind: ReplicationController
metadata:
  name: kubernetes-bootcamp
spec:
  replicas: 1
  selector:
    app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
      - name: myk8x
        image: gcr.io/google-samples/kubernetes-bootcamp:v1
        ports:
        - containerPort: 8080

デプロイ時にはこのファイル(rc.yaml)を指定して、以下のコマンドを実行します:
$ kubectl create -f rc.yaml
replicationcontroller/kubernetes-bootcamp created

このコマンドで前回とほぼ同様に kubernetes-bootcamp アプリケーションを1つのポッドにデプロイします。また app: web というセレクターを指定していますが、この後で使います。

この時点で kubernetes-bootcamp アプリケーションが1つのポッドにデプロイされます。一応ポッドと、そして(詳しくは後述の)サービスの状態を確認しておきます(緑字はコメント):
$ kubectl get pod
NAME                        READY   STATUS    RESTARTS   AGE
kubernetes-bootcamp-jfswt   1/1     Running   0          41s  1ポッド

$ kubectl get svc
NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   172.21.0.1   <none>        443/TCP   3d21h  ここはクラスタサービスなので無視、つまり起動したサービスなし

この時点で1つのポッドにアプリケーションがデプロイされたことが確認できました。しかしこのポッドはまだコンテナの外部からアクセスすることができません。コンテナの外部からでもアクセスできるように紐付けるサービスを作成します。

作成するサービスのための設定ファイル(service.json)を以下の内容で用意します:
{
    "kind": "Service",
    "apiVersion": "v1",
    "metadata": {
        "name": "my-service"
    },
    "spec": {
        "selector": {
            "app": "web"
        },
        "ports": [
            {
                "protocol": "TCP",
                "port": 80,
                "targetPort": 8080,
                "nodePort": 30000
            }
        ],
        "type" : "NodePort"
    }
}

"spec" 内で app:web のラベルをセレクターに指定しています。これが先程作成したデプロイメント時の指定内容となっていて、kubernetes-bootcamp に紐づく形になります。また 8080 番ポートをターゲットに接続し、80 番ポートで公開する、という指定も行い、"my-service" という名前でサービスを作るよう指示しています。

ではこのファイル(service.json)を使ってサービスを作成します。同時に再びポッドとサービスの内容を照会してみます:
$ kubectl create -f service.json
service/my-service created

$ kubectl get pod
NAME                        READY   STATUS    RESTARTS   AGE
kubernetes-bootcamp-jfswt   1/1     Running   0          13m  1ポッド

$ kubectl get svc
NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   172.21.0.1   <none>        443/TCP   3d21h
my-service   NodePort    172.21.30.70 <none>        80:30000/TCP   94s 追加されたサービス

サービスの追加に成功しました。またその追加されたサービスはクラスター内の内部 IP アドレスである 172.21.30.70:80 上で稼働していることがわかりました。

ではこのサービスを使ってアプリケーションにアクセスしてします:
$ curl http://172.21.30.70/
Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-jfswt | v=1

アプリケーションにコンテナ外部からアクセスでき、正しく動作していることも確認できました。ただし、この時点ではまだあくまで内部ネットワーク内からのアクセスが確認できただけで、まだインターネットからはアクセスできません(後述します)。


コンテナの外部からアプリケーションにアクセスできることは確認できましたが、せっかくなので Kubernetes っぽい挙動もさせてみます。現在1つのポッドで動作しているこのアプリケーションをスケールアウトして複数の(今回は2つの)ポッドで起動するようにしてみます:
$ kubectl scale -f rc.yaml --replicas=2
replicationcontroller/kubernetes-bootcamp scaled

この状態で再びポッドとサービスの状態を確認します。ポッドが2つになっていることがわかります。(一方、サービスは何も変わっていません):
$ kubectl get pod
NAME                        READY   STATUS    RESTARTS   AGE
kubernetes-bootcamp-kqkhp   1/1     Running   0          14s スケールアウトして新しく増えたポッド
kubernetes-bootcamp-jfswt   1/1     Running   0          109s

$ kubectl get svc
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
kubernetes   ClusterIP   172.21.0.1      <none>        443/TCP        3d23h
my-service   NodePort    172.21.30.70    <none>        80:30000/TCP   5m16s

ではこのアプリケーションをインターネットに公開してみます。以下のコマンドを実行します:
$ kubectl expose rc kubernetes-bootcamp --type=NodePort --name=web
service/web exposed

$ kubectl get svc
NAME         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
kubernetes   ClusterIP   172.21.0.1       <none>        443/TCP          4d20h
my-service   NodePort    172.21.199.105   <none>        80:30000/TCP     4m30s
web          NodePort    172.21.64.253    <none>        8080:30703/TCP   4m9s

"web" という名称のサービスが追加され、kubernetes-bootcamp の rc が expose(外部公開)されました。公開先のポート番号が 30703 になっていることも確認できます。

また、公開先の IP アドレスはワーカーノードのパブリック IP アドレスになっています。ダッシュボードから確認するか、または以下のコマンドを実行します:
2019041601
$ ibmcloud cs workers mycluster (最後のパラメーターはクラスタ名)

ID                                                 Public IP       Private IP       Machine Type   State    Status   Zone    Version   
kube-mel01-pa351ddd2ea19441a689a49159389f9d45-w1   168.1.149.201   10.118.243.103   free           normal   Ready    mel01   1.12.7_1548*


この例だと、パブリック IP アドレスが 168.1.149.201 となっているので、この 30703 番ポートに対してウェブブラウザでアクセスし、期待通りの結果になることを確認します:
2019041602


最後に、サービスとポッドを削除する場合は以下のコマンドを実行します:
$ kubectl delete svc web
service "web" deleted

$ kubectl delete -f service.json
service "my-service" deleted

$ kubectl delete -f rc.yaml
replicationcontroller "kubernetes-bootcamp" deleted


IBM Cloud の(無料の)Kubernetes サービスを使って、アプリケーションのデプロイ、スケール、動作確認、ウェブ公開、といった一連の作業ができることが確認できました。すでに Kubernetes 環境を手元に持っているような人であればともかく、これから Kubernetes を使ってみようと思う人にとっては面倒な準備作業も不要で気軽に始められて、スケールやウェブ公開といった一通りの作業ができるところまで使える環境としてとても便利だと思いました。





この記事の続きです。

IBM Cloud の無料版 Kubernetes サービスで、Kubernetes クラスタを立ち上げる所までを紹介しました。 今回は(ベータ版の)Web Terminal 機能を使って、この Kubernetes サービスの状態を確認したり、アプリケーションをデプロイしてみます。

IBM Cloud を操作する場合、一般的には ibmcloud コマンドを使います。また Kubernetes クラスタの状態を確認する場合、一般的には kubectl コマンドを使うことが多いと思っています。普段使っているマシンに ibmcloud コマンドや kubectl コマンドがすでにセットアップされているような場合はそれらのコマンドを使えばいいのですが、まだ導入されていないようなケースでは別途セットアップしてから利用する必要があります。 今回紹介する Web Terminal はそういった導入をしなくてもウェブブラウザ内のコンソールから ibmcloud コマンドや kubectl コマンドを使って操作できる方法です。現時点(2019/04/15)でベータ版なので今後どういう形になるかわかりませんが、使ってみて便利だったので紹介させていただくことにしました。

では以下に実際の手順を紹介します。まず前回の記事の内容が正しく実行されて、"mycluster" という名称の Kubernetes クラスタが稼働している状態になっていることを確認します:
2019041107


"Worker Nodes" タブでワーカーノード(今回は1つ)が正しく動いている点も確認します(この画面では IP アドレスをモザイクにしていますが、実際にはモザイクなしに表示されます):
2019041108


ではまずはこのワーカーノードの状態を Web Terminal からも確認してみます。画面内の "Web Terminal" と書かれたボタンをクリックします:
2019041109


最初にクリックした時だけ、「Kubernetes Terminal をインストールする」という確認のダイアログが表示されます。「Install」ボタンをクリックすると Kubernetes Terminal がインストールされます(少し時間がかかります、十数秒くらい?):
2019041110


(Kubernetes Terminal をインストールした場合は十数秒程度待って)改めて "Web Terminal" ボタンをクリックします。するとブラウザ画面下部からターミナルが現れます:
2019041111


このターミナルのプロンプトで、画面内に書かれたセットアップ手順を順に実行していきます。ibmcloud コマンドは特にインストールしていませんが、この Web Terminal 内では導入済みなので普通にそのまま使うことができます(赤字はコメント):
2019041112
$ ibmcloud login -a https://cloud.ibm.com ログイン

$ ibmcloud ks region-set ap-south サービスのリージョンを(作成したリージョンに)指定

$ ibmcloud ks cluster-config mycluster Kubernetes の環境設定用ファイルを生成してダウンロード

最後のコマンドを実行すると KUBECONFIG 環境変数を設定するためのコマンドが表示されます。そこに書かれた内容をそのままコピー&ペーストして、Web Terminal 上で KUBECONFIG 環境変数を設定します:
$ export KUBECONFIG=/home/IBMid-31000086FP/.bluemix/plugins/container-service/clusters/mycluster/kube-config-mel01-mycluster.yml

 

ここまでの作業が完了すると Web Terminal から kubectl コマンドを実行できるようになり、コマンドを通じてワーカーノードの状態を確認することもできるようになります。この kubectl コマンドも特にセットアップすることなく、Web Terminal 上でそのまま実行できます:
$ kubectl get nodes

1つだけ動いているワーカーノードが、名称や状態とあわせて Web Terminal 上に表示されます:

2019041113


先程ウェブ画面で確認したものと同じ内容が表示されました。これでこの Web Terminal 上から Kubernetes クラスタを操作できることが確認できました。


では続けてこのノードにアプリケーションをデプロイしてみます。Kubernetes の公式チュートリアルで紹介されている内容と全く同じ操作を Web Terminal から行ってみます。

デプロイメントの名前(以下の例では kubernetes-bootcamp)とアプリケーションイメージの Docker Hub リポジトリ URL (同 gcr.io/google-samples/kubernetes-bootcamp:v1)、実行ポート番号(同 8080)を指定して kubectl run コマンドを実行し、アプリケーションイメージを Kubernetes クラスタにデプロイします:
$ kubectl run kubernetes-bootcamp --image=gcr.io/google-samples/kubernetes-bootcamp:v1 --port=8080

このコマンドが実行されると、アプリケーションが実行可能なノード(今回ははじめから1つしか用意してないので、その1つ)を探し、アプリケーションの実行がスケジュールされてデプロイが実行されます。

実行後に以下のコマンドでデプロイメントの状態を確認してみます。以下のような感じで、1つのアプリケーションが実行されている状態になればデプロイは成功しています:
$ kubectl get deployments
NAME                  DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
kubernetes-bootcamp   1         1         1            1           16s

また、この時点で Pod の状態も確認しておきます。デプロイ時に特にレプリカ数を指定していなかったので、1つの Pod が起動され、その上でアプリケーションが起動しているはずです:
$ kubectl get pod
NAME                                   READY   STATUS    RESTARTS   AGE
kubernetes-bootcamp-598f57b95c-w2k4b   1/1     Running   0          2m7s

次の段階に進む前に、いったん作成した Pod を削除しておきます。作成時に指定した名前(kubernetes-bootcamp)を指定してデプロイメントを削除します:
$ kubectl delete deployment kubernetes-bootcamp
deployment.extensions "kubernetes-bootcamp" deleted

$ kubectl get deployments
No resources found.

$ kubectl get pod
No resources found.

↑念の為、デプロイメントと Pod が存在しなくなっていることも確認できました。次回、最終回に改めてアプリケーションをデプロイして、スケールさせたりした上で使ってみることにします。

このページのトップヘ