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

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

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 にしてオブジェクトストレージに入れておけば、条件を指定した取り出しは後からできる」ことになって便利だと感じました。




Node.js + Express の環境でウェブアプリケーションを開発していると、いくつかの方法で URL のパラメータを受け取る方法があります:
6dnng3pre04xxdebia1g


例えば、 "/xxx?name0=value0&name1=value1" というパス /xxx への GET アクセス時に URL パラメータである name0 及び name1 の値(それぞれ "value0" と "value1" に相当する部分)を取得するには以下のように req.query オブジェクトから取り出すという処理で実現できます:
var express = express();
var app = express();

app.get( '/xxx', function( req, res ){
  var name0 = req.query.name0;
  var name1 = req.query.name1;
    :
});

  :
  :

また、"/yyy/name2" というパスへの GET アクセス時における "name2" 部分を可変にしたい(例えば "/yyy/1" にアクセスした場合は name2 = "1" で、"yyy/2" にアクセスした場合は name2 = "2" として取り出したい)場合は、以下のように req.params オブジェクトから取り出すことで実現できます:
var express = express();
var app = express();

app.get( '/yyy/:name2', function( req, res ){
  var name2 = req.params.name2;
    :
});

  :
  :

ここまでは express の標準機能として実装されています。


で、今回挑戦したいのは "/zzz/name0/name1/name2/.../name9" というパスへの GET アクセス時における name0, name1, name2, ..., name9 の値を取り出すことです。この例ではパラメータが10個ですが、実際にはいくつ存在しているかわからないものとします(1個かもしれないし、10個以上かもしれない)。つまり "/zzz/" で始まるパスへの GET アクセス時に "/zzz" 以降のパス部分をまとめてパラメータ化して取得したい、という要望実現への挑戦です。

パラメータの個数が固定であれば(例えば3つであれば)、上述の "/yyy/:name2" の時の応用で、"/zzz/:name0/:name1/:name2/" のハンドリングを行って、req.params.name0, req.params.name1, req.params.name2 の値をそれぞれ取り出すことで実現できます。ただ今回はこのパラメータの数を可変にしたい場合の実現方法を考えたいのです。

その実現例の1つがこちらです:
var express = express();
var app = express();

app.use( function( req, res, next ){
  if( req.url.startsWith( '/zzz/' ) ){
    // '/zzz/' で始まるパスへのアクセスだった場合は、5文字目以降をパラメータとして取り出し、
// '/zzz?params=' の後ろに URL パラメータとしてくっつけてリダイレクトする var params = req.url.substr( 4 ); res.redirect( '/zzz?params=' + params ); }else{
// '/zzz/' で始まらないパスへのアクセスだった場合はそのまま処理する next(); } });
app.get( '/zzz', function( req, res ){ // params URL パラメータの値を取り出して、'/' で分割する var params = req.query.params.split( '/' );

// params[0] に "name0" が、params[1] に "name1" が、・・それぞれ格納されている : }); : :

可変階層のパスをハンドリングすることはできないので、該当部分を URL パラメータ(params)として処理するようにしています。そして /zzz/ で始まるパスへのアクセスがあった場合は app.use() による前処理として5文字目(2つ目の '/')以降を取り出して params パラメータの値に指定してリダイレクトするようにしています。こうすることで /zzz/name0/name1/name2/../name9 へのアクセスは /zzz?params=name0/name1/../name9 へアクセスするようリダイレクトされ、リダイレクトされた先で元のパラメータを残さず処理することができるようになります。

リダイレクトしての処理なので、ずるい(?)やり方ではあるんですが、一応これなら /zzz/name0/name1/name2/... へのアクセス全般を可変階層でも(リダイレクト先で)処理できそうです。


1台程度のマシンで、小規模な k8s(Kubernetes) 環境を構築できる MicroK8s をラズベリーパイ(以下「ラズパイ」)に導入する手順を確認してみました。MicroK8s は Ubuntu を開発&提供している Canonical 社が開発する k8s パッケージです:
MicroK8s_logo


まずラズパイ本体を用意します。後述しますが k8s はスワップメモリを無効にして稼働させる必要があるため、ラズパイ3以前のメモリ 1GB 環境だと少し厳しいかもしれません。今回はメモリ 4GB モデルのラズパイ4を使って確認しました。

次に 64bit 版ラズパイ OS をインストールします(MicroK8s は 64bit OS 向けに提供されています)。64bit 版のラズパイ OS は 2021/01/08 の時点では正式リリースされているわけではありませんが、こちらからテスト版をダウンロードすることができます:
http://downloads.raspberrypi.org/raspios_arm64/images/

2021010801


私は 2020-08-24 という日付が入ったモジュールをダウンロードしました(この記事を記述している時点では最新の 64bit モジュールです。公式の 32bit 版と比べるとたまに知らない間に再起動してたりして、少し不安定な印象はあります)。ダウンロード後に展開してマイクロ SD カードに書き込みます(このあたりは通常の手順と同様)。そしてラズパイ(4)にセットして初期セットアップを済ませておきます。

この後に MicroK8s をインストールするのですが、その前に準備作業を2つほどしておきます。まず k8s はスワップメモリが存在していると正しく動作しないため、まずはスワップを無効にします:
$ sudo swapoff -a

続けて、これも通常の k8s を導入する際にも行う必要のある手順ですが、cgroups のメモリーサブシステムを利用するため、/boot/cmdline.txt に記述されている内容の(同じ行の)最後に以下の赤字部分を追記して保存し、この段階で一度リブートします:
console=serial0,115200 console=tty1 root=PARTUUID=a3ba36bd-02 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait quiet splash plymouth.ignore-serial-consoles cgroup_enable=memory cgroup_memory=1

これで k8s を導入するための準備が完了しました。続けて MicroK8s をインストールします。今回は公式サイトでも紹介されている、Snapd を利用する方法でインストールします。まずは Snapd (とこの後で使う iptables-persistent)をインストールして一度リブートします:
$ sudo apt install snapd iptables-persistent 

$ sudo reboot

再起動後に再度ログインし、改めて Snap を使って MicroK8s をインストールします(この手順で少し時間がかかります):
$ sudo snap install microk8s --classic

MicroK8s のインストールが完了したら以下の3つのコマンドを実行後に(一度 exit してから)再ログインします:
$ sudo usermod -a -G microk8s $USER

$ sudo chown -f -R $USER ~/.kube

$ sudo iptables -P FORWARD ACCEPT

ここまでの作業で MicroK8s がインストールされて、MicroK8s が起動されているはずです。 以下の "get nodes" コマンドを実行して STATUS 欄が "Ready" と表示されることを確認します:
$ microk8s.kubectl get nodes

NAME          STATUS   ROLES    AGE     VERSION
raspberrypi   Ready    <none>   7h25m   v1.20.0-37+6252d1e153c00f

またサービスの一覧は "get services" コマンドで確認できます:
$ microk8s.kubectl get services

NAME         TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.152.183.1   <none>        443/TCP   9h

なお、MicroK8s の起動/終了のコマンドはそれぞれ以下の通りです:
$ microk8s start  (起動)

$ microk8s stop  (終了)

Snap パッケージになっているので、インストール時の手間が非常に簡略化されている印象でした。一方で、(これは MicroK8s というよりも 64bit OS の問題なのかもしれませんが)全体的に不安定な挙動が見受けられるように感じました。"microk8s.kubectl get nodes" コマンドの結果が READY ではなく NotReady になることがあったり、システムがいつの間にか再起動していたりして、イマイチ安定していないような気もします。

このページのトップヘ