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

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

タグ:cli

自分はプログラミングする時、特に制約事項がない場合は Node.js を使います。サーバーサイドアプリだけでなく、CLI 系のコマンドラインアプリも作れるし、React を併用してフロンドエンドアプリも作れます。HTML 内で JavaScript を使うケースも含めると JavaScript 系の言語は使い道が幅広くて便利、と思ってます。

そんな自分が最近作った CLI アプリ(コマンドラインアプリ)がそこそこ便利だったので公開したかったのですが、Node.js アプリは Node.js がインストールされた環境で動くのです。ある意味で当たり前のことを言っているのですが、プログラミングなどに詳しくない人からすると「プログラミング言語をインストールしないと動かない」というのはハードルが高いものです。なんとかして「ダウンロード→即実行可能」な形にできないかと調べてみました。今回のブログエントリはそんな経緯をまとめたものです。

ランタイムが Node.js の場合、このようなアプリケーションのパッケージ化ツールには nexe はじめ、いくつかの選択肢があるようですが、今回自分が使ったのは pkg というツールです※
2014072100


※ Git リポジトリは現在アーカイブされているので、今後のアップデートはされない可能性が高いと思っています。逆に言うと、良くも悪くも今後の変更はほぼないものだと思って使ってください。

自分が Linux(WSL2) で作った CLI アプリが Linux および Windows 向けにクロスコンパイルして実行可能バイナリ化できる所まで確認しました。自分の用途としてはここまでできれば実用的と言えるので、今後はこれを使っていこうかな、と考えています。


【pkg のインストール】
既に Node.js が導入済みの環境であれば、pkg のインストールは以下のコマンドを実行するだけなので超簡単です:
$ sudo npm install -g pkg

npm で pkg をグローバルインストールします。これで pkg コマンドが使えるようになります。


【pkg の使い方】
例えば 64bit Linux(WSL2) で app.js というファイル名の CLI アプリを作ったとします。この CLI アプリは Node.js を使う場合は以下のように実行できるものとします:
$ node app.js

ここに pkg をインストールして、3つの環境(64bit Linux, 64bit MacOS, 64bit Windows)向けの実行可能バイナリを作る場合は以下のコマンドを(特にオプションを付けずに)実行します:
$ pkg app.js

この場合は同じフォルダに app-linux, app-macos, app-win.exe という3つのファイルが生成され、それぞれ 64bit Linux, 64bit MacOS, 64bit Windows 向け実行可能バイナリになっています。

現在の環境と同じ環境(64bit Linux)向けの実行可能バイナリ(app)を作るのであれば、以下のコマンドを実行します("-o" オプションで出力する実行可能バイナリファイル名を指定します):
$ pkg -o app app.js

出力するファイルをクロスコンパイルしたい場合、つまり現在の環境とは異なる環境(例えば 64bit Windows)向けの実行可能バイナリ(app.exe)を作る場合は "-t" オプションでクロスコンパイル先の対象プラットフォームを指定します("node16" は "Node.js v16 でコンパイルする" の意味です):
$ pkg -t node16-win-x64 -o app.exe app.js

対象プラットフォームは node16 までは使えることを確認したのですが(ドキュメントも node16 までは書かれているのですが)、これ以上のバージョンがサポートされているかどうかは未確認です。

この pkg を使って作った実行可能バイナリは小規模なアプリでも 30MB 以上になります。私が作ったアプリも package.json には2つのライブラリしか登録していないのですが、それでも 45MB ほどになりました。おそらく node 本体とライブラリを全部まとめてパッケージングしてるんだろうな・・・と想像しています。


 

ちょっとした UI 系トラブルに巻き込まれた結果とある機会に CLI 操作だけで IBM Cloud 上に Openshift クラスタ(いわゆる "ROKS")を作る必要が生じて、実際に試してみました。以下、その時に実行したコマンド群を順に紹介します。


【前提条件】
やりたかったことは単純に「VPC(Virtual Private Cloud)環境内に OpenShift クラスタを1つ作る」ことでした。既に VPC 自体はサブネット含めて作成済みで、バックアップストレージの Cloud Object Storage インスタンスも作成済みです。 後はこの VPC 内に OpenShift クラスタをスペックやワーカーノード数を指定して作るだけ、という状況です。


【CLI コマンド】
以下 CLI コマンドを記載します。ここまでに "ibmcloud login" は実行済みであるとします。

詳しくは下記参考ページを参照いただきたいのですが、VPC 内に OpenShift クラスタを作るための CLI コマンドは以下のようになります:
$ ibmcloud oc cluster create vpc-gen2 --name (クラスタ名) --zone (作成先ゾーン名) --vpc-id (作成先 VPC ID) --subnet-id (作成先サブネット ID)  --flavor (ワーカーノードのフレーバー) --version (OpenShift バージョン) --cos-instance (Cloud Object Storage の CRN) --workers (1ゾーンあたりのワーカーノード数)

で、このコマンドを実行するためには(上記コマンド内にも括弧がたくさんあるように)指定する必要がある多くのパラメータ情報を事前に集めておく必要があります。というわけでまずはパラメータ情報を収集するための CLI コマンドから説明します。

まず "--name" パラメータで指定する「クラスタ名」は自分で自由に指定することができるので説明は不要だと思います。次に "--zone" パラメータで指定する「作成先ゾーン名」ですが、これは目的のゾーンが例えば「大阪3」であったとして、この「大阪3」を指定するための文字列です。これを調べるには次のコマンドでゾーン一覧を取得して、そこから目的のゾーンの ID を取り出します(青字が入力コマンドです):
$ ibmcloud oc zone ls --provider vpc-gen2
OK
ID           Name         Metro             Flavors
au-syd-1     au-syd-1     Sydney            -
au-syd-2     au-syd-2     Sydney            -
au-syd-3     au-syd-3     Sydney            -
br-sao-1     br-sao-1     Sao Paulo         -
  :
eu-gb-3      eu-gb-3      London            -
jp-osa-1     jp-osa-1     Osaka             -
jp-osa-2     jp-osa-2     Osaka             -
jp-osa-3     jp-osa-3     Osaka             -
jp-tok-1     jp-tok-1     Tokyo             -
jp-tok-2     jp-tok-2     Tokyo             -
jp-tok-3     jp-tok-3     Tokyo             -
us-east-1    us-east-1    Washington D.C.   -
  :

この結果から「大阪3」を使う場合に指定するゾーン名が "jp-osa-3" であることが分かります。

次に作成先の「VPC ID」です。VPC が決まっていても、その ID を取り出して指定する必要があります。これは以下のコマンドを実行することで取り出せます:
$ ibmcloud oc vpcs --provider vpc-gen2
OK
Name              ID                                          Provider
xxxxxxx-vpc-osa   ****-********-****-****-****-************   vpc-gen2
  :

目的の VPC 名と照らし合わせることで ID を確認することができます("****-********-****-****-****-************" という値であったと仮定します)。

作成先の「サブネットID」も調べる必要があります。普段は名称で扱っていて、ID を意識することがあまりないのですが CLI 操作時には必要な情報です。これは以下のコマンドで "xxxx-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" であることが確認できます:

$ ibmcloud oc subnets --provider vpc-gen2 --vpc-id (VPC ID) --zone (ゾーン名)
OK
Name                ID                                          Public Gateway Name                        Public Gateway ID                           IPv4 CIDR Block   Available IPv4 Addresses
sn-xxxxxxxxxxx-03   xxxx-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx   pgw-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx   xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx   10.xxx.xxx.0/24   244

ワーカーノードのフレーバー(スペック)も実行時に指定する必要のある情報です。これは以下のコマンドでフレーバーの一覧を取得し、目的のフレーバーの ID を取り出します。今回は "bx2.16x64" というスペックのフレーバーを使うことにします:
$ ibmcloud oc flavors --zone (ゾーン名) --provider vpc-gen2
OK
For more information about these flavors, see 'https://ibm.biz/flavors'
Name           Cores   Memory   Network Speed   OS             Server Type   Storage
  Secondary Storage   Flavor Class   Provider
bx2.16x64      16      64GB     24Gbps          UBUNTU_20_64   virtual       100GB     0B, *               bx2            vpc-gen2
bx2.2x8†       2       8GB      4Gbps           UBUNTU_20_64   virtual       100GB     0B                  bx2            vpc-gen2
bx2.32x128     32      128GB    25Gbps          UBUNTU_20_64   virtual       100GB     0B, *               bx2            vpc-gen2
b
  :
  :

OpenShift のバージョンも指定する必要がある項目ですが、これは普通に "4.11_openshift" などと指定できます。またゾーンあたりのワーカーノード数も普通に "2" などと数字で指定可能です。

最後に Cloud Object Storage の CRN を取得します。これは取得が面倒なのですが、作成済みリソースの一覧を取得し、そこから目的の Cloud Object Storage サービスを探して、その ID を見つける、という方法で取得します:
$ ibmcloud resource service-instances --long
OK
   :
   :
ID:                 crn:v1:bluemix:public:cloud-object-storage:global:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx::

GUID:               xxxxxxxxxxxxxx


Name                Cloud Object Storage for me


Location            global
   :
   :

これで OpenShift クラスタを作成するために必要な最低限の情報が揃いました:
情報
ゾーン名jp-osa-3
VPC ID****-********-****-****-****-************
サブネット IDxxxx-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
ワーカーノードのフレーバーbx2.16x64
OpenShift バージョン4.11_openshift
1ゾーンあたりのワーカーノード数2
Cloud Object Storage の CRNcrn:v1:bluemix:public:cloud-object-storage:global:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx::



これらの情報を使って以下のコマンドを実行します:
$ ibmcloud oc cluster create vpc-gen2 --name (クラスタ名) --zone jp-osa-3 --vpc-id ****-********-****-****-****-************ --subnet-id xxxx-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx  --flavor bx2.16x64 --version 4.11_openshift --cos-instance crn:v1:bluemix:public:cloud-object-storage:global:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx:: --workers 2
Creating cluster...
OK
Cluster created with ID ***************************

成功すると上述のようにクラスタ ID("***************************")が表示され、「デプロイ中」のステータスとなります。


なお、マルチゾーンで作成する場合は上記の作業を行ってシングルゾーンでクラスタを作成した上で、追加するゾーンのゾーン名とサブネット ID を取得してから以下のコマンドを実行してワーカープールにワーカーノードを追加します:
$ ibmcloud oc zone add vpc-gen2 --zone (追加するゾーン名) -c (クラスタ名) --worker-pool (追加先のワーカープール名) --subnet-id (サブネットID)
OK

これで IBM Cloud のダッシュボード画面にアクセスできなくてもしなくても、CLI だけで Openshift クラスタを作ることができそうです。


【参考】
https://cloud.ibm.com/docs/openshift?topic=openshift-cluster-create-vpc-gen2&interface=cli


 

タイトルどおりの内容のエラーが発生したので、その原因と解決策を探った記録をブログにまとめました。

環境としては以下の図のようなものです。1台の Windows 10 PC の中に Docker Desktop を導入・起動し、MySQL サーバーのイメージからコンテナを作って 3306 番ポートで公開起動しました。この環境に WSL(2) の MySQL CLI を使って、localhost:3306 の MySQL サーバーにログインする、というだけの内容なのですが、ログイン時にコネクションエラーが発生してしまう、という症状が出ていました:
20210612


具体的に紹介します。今回使った MySQL のコンテナイメージはこちらです。厳密には mariadb を使っています。同様の現象が発生すると思うので、他の MySQL 系イメージでも構いません:
https://hub.docker.com/r/linuxserver/mariadb


Docker Desktop を使ってこのイメージからコンテナを作成します。WSL を起動するなどして docker コマンドが使える状態で以下のコマンドを実行します(目的は Docker Desktop 内のコンテナとして起動することなので、このコマンド自体は WSL から実行しなくても構いません):
$ docker run -d --name=mariadb -e PUID=1000 -e PGID=1000 -e MYSQL_ROOT_PASSWORD=root -e TZ=Asia/Tokyo -e MYSQL_DATABASE=mydb -e MYSQL_USER=user -e MYSQL_PASSWORD=P@ssw0rd -p 3306:3306 --restart unless-stopped linuxserver/mariadb

こんな感じで、Docker Desktop 内のコンテナとして MySQL が起動します:
2021061301


指定したオプションはコンテナイメージのドキュメントを参考に指定しています。上の例では root のパスワードは root 、利用ユーザーは user 、利用ユーザーのパスワードは P@ssw0rd 、利用データベース名を mydb、コンテナ名は mariadb、などを指定しています(適当に変更いただいても構いません)。ポート番号に -p 3306:3306 を指定しているので、ローカルホストの 3306 番ポートからも(公開されているので)アクセスできるはずです。事前準備はここまで。

ではこの方法で Docker Desktop 内に起動した MySQL(mariadb) に、WSL から MySQL CLI で接続してみます。上述のオプションであれば以下のコマンドで接続できるはずです:
$ mysql -u user -pP@ssw0rd -h localhost mydb

しかし実際には以下のようなエラーが表示されてしまいます:
$ mysql -u user -pP@ssw0rd -h localhost mydb
ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2 "No such file or directory")

ユーザー名やパスワード、データベース名は間違っておらず、接続先も localhost だから間違えようがないはずです。ポート番号もデフォルトの 3306 のままなので特別なオプションも不要のはず・・・ ではなぜエラーになってしまうのでしょうか?


改めてエラーメッセージをよく見ると "Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock'" と表示されています。ちなみにこのファイルは存在していません(/var/run/mysqld というディレクトリが存在していません):
$ ls -la /var/run/sqld/mysqld.sock
ls: cannot access '/var/run/sqld/mysqld.sock': No such file or directory

このエラーメッセージでわかる人もいると思うのですが、MySQL コマンドは localhost が対象の場合はデフォルトで「ソケット接続」という方法で接続を試みます。そしてこのソケット接続をする場合の情報を /var/run/sqld/mysqld.sock というファイルで管理しているため、このファイルを読み込もうとしているのでした。

しかし、今回の環境では WSL 内には MySQL サーバーは起動していません。WSL 内からの docker コマンドで起動してはいますが、実体は Windows にインストールされた Docker Desktop のコンテナとして起動しています。要は「WSL と同じマシン上で動いてはいて、ポートも公開されているけど、WSL から直接ソケット接続できる状態で動いていない」のです。これがエラーの原因で、エラーメッセージの理由でもあります。

では、エラーなしに WSL から MySQL に接続するにはどのようなコマンドにすればいいでしょうか? 答はシンプルで「ソケット接続ではなく、TCP 接続するようなオプションを指定」することで解決できます。具体的には以下のように --protocol=TCP オプションを付けて実行します:
$ mysql -u user -pP@ssw0rd --protocol=TCP -h localhost mydb
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 11
Server version: 10.4.18-MariaDB-1:10.4.18+maria~bionic-log mariadb.org binary distribution

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [mydb]>

今度は無事に接続できました。「localhost の罠」にかかった時の話でした。


以前に Linux 版 Db2 を導入した時の手順を紹介したブログがありました:
http://dotnsf.blog.jp/archives/2960284.html


3年以上前のこの時の仮想環境が奇跡的に残っていたので久しぶりに使ってみました。で、折角の Db2 コマンドライン環境(?)なので、このコマンドラインインターフェースから IBM Cloud 上の DBaaS である Db2 に接続して使ってみました。以下、その手順の紹介です。

まずは IBM Cloud にログインして Db2 インスタンスを作成します。無料のライトアカウントでも使えるサービスが用意されているので、派手に使わない限りは無料で試すこともできます:
2018052302


無料のライトプランを使う場合はデプロイ先ロケーションを「米国南部」に設定します。加えて自分のメールアドレスを指定します:
2018052303


デプロイ先ロケーションが米国南部の場合、無料の Lite プランが選択可能になります(もちろん他のプランでも構いません)。プラン設定後に「作成」ボタンでインスタンスが作成されます:
2018052304



インスタンス作成後、「サービス資格情報」メニューから「資格情報の表示」を選択します(「資格情報の表示」が存在しない場合は、最初に「新規資格情報」をクリックして作成し、その後に改めて「資格情報の表示」を選択します)。すると画面下部に JSON フォーマットで接続先サーバー名やユーザー、パスワードといった情報を確認できます:
2018052301


この中で、今回使うのは以下の5つの情報です:
 - hostname(接続先のサーバー名)
 - password(接続時のパスワード)
 - port(接続先のポート番号)
 - db(データベース名)
 - username(接続時のユーザー名)


以下では、これら5つの値がこのような内容になっていたと仮定して説明を続けます:
{
  "hostname": "dashdb-txn-sbox-yp-dal09-03.services.dal.bluemix.net",
  "password": "xxxxxxxx",
  "port": 50000,
  "db": "BLUDB",
  "username": "lrz77612",
    :
}



ではこの DBaaS 上のデータベース(BLUDB)にコマンドラインから接続するための手順を紹介します。


db2inst1 ユーザーへの変更

ここから下のコマンドはすべて db2 インストール時に指定したユーザー(今回の例では db2inst1)に切り替えて行う必要があります。というわけでターミナル上でのユーザーを切り替えます:
$ su - db2inst1

ノードカタログの確認

接続先となる Db2 サーバーはサーバーノードとしてカタログに記録されている必要があります。というわけで、まず現在のノードカタログを確認します(青字は出力結果):
$ db2 list node directory
SQL1037W  The node directory is empty.  SQLSTATE=01606

この出力結果によるとノードカタログは空で何も登録されていないようです。


ノードのカタログ登録

というわけで今回の接続先となる IBM Cloud 上の Db2 サーバーをカタログに登録します。以下のように接続先のサーバー名とポート番号を指定し、今回は "dashdb" という名前でカタログに登録しています(太字部分は各自の環境や希望に併せて変更してください):
$ db2 catalog tcpip node dashdb remote dashdb-txn-sbox-yp-dal09-03.services.dal.bluemix.net server 50000
DB20000I  The CATALOG TCPIP NODE command completed successfully.
DB21056W  Directory changes may not be effective until the directory cache is
refreshed.
成功したようです。この状態で再度ノードカタログを確認すると、今度は dashdb という名前のカタログが確認できるはずです:
$ db2 list node directory

 Node Directory

 Number of entries in the directory = 1

Node 1 entry:

 Node name                      = DASHDB
 Comment                        =
 Directory entry type           = LOCAL
 Protocol                       = TCPIP
 Hostname                       = dashdb-txn-sbox-yp-dal09-03.services.dal.bluemix.net
 Service name                   = 50000



ノードカタログを削除

今回は作業しませんが、登録したカタログを削除する場合はカタログ名を指定して以下のように実行します:
$ db2 uncatalog node dashdb


エイリアスの確認

DB2 でデータベースに接続するにはデータベースをエイリアスとして登録する必要があります。というわけで次に接続先となるリモートデータベースのエイリアスを作成します。まずは現在登録されているエイリアスの一覧を確認します:
$ db2 list db directory

 System Database Directory

 Number of entries in the directory = 2

Database 1 entry:

 Database alias                       = JSONDB
 Database name                        = JSONDB
 Local database directory             = /home/db2inst1
 Database release level               = 14.00
 Comment                              =
 Directory entry type                 = Indirect
 Catalog database partition number    = 0
 Alternate server hostname            =
 Alternate server port number         =

Database 2 entry:

 Database alias                       = MYDB
 Database name                        = MYDB
 Local database directory             = /home/db2inst1
 Database release level               = 14.00
 Comment                              =
 Directory entry type                 = Indirect
 Catalog database partition number    = 0
 Alternate server hostname            =
 Alternate server port number         =


この例では(いずれもローカルサーバー内の)JSONDB と MYDB という2つのデータベースエイリアスが登録されていました。この例ではリモートサーバーのエイリアスは登録されていません。


エイリアスの登録

では先程作成した dashdb ノード上の接続先データベース(BLUDB)を "remotedb" という名前でエイリアス登録します:
$ db2 catalog database BLUDB as remotedb at node dashdb
DB20000I  The CATALOG DATABASE command completed successfully.
DB21056W  Directory changes may not be effective until the directory cache is
refreshed.

成功したようです。念の為もう1回エイリアス一覧を確認します:
$ db2 list db directory

 System Database Directory

 Number of entries in the directory = 3

Database 1 entry:

 Database alias                       = REMOTEDB
 Database name                        = BLUDB
 Node name                            = DASHDB
 Database release level               = 14.00
 Comment                              =
 Directory entry type                 = Remote
 Catalog database partition number    = -1
 Alternate server hostname            =
 Alternate server port number         =

Database 2 entry:

 Database alias                       = JSONDB
 Database name                        = JSONDB
 Local database directory             = /home/db2inst1
 Database release level               = 14.00
 Comment                              =
 Directory entry type                 = Indirect
 Catalog database partition number    = 0
 Alternate server hostname            =
 Alternate server port number         =

Database 3 entry:

 Database alias                       = MYDB
 Database name                        = MYDB
 Local database directory             = /home/db2inst1
 Database release level               = 14.00
 Comment                              =
 Directory entry type                 = Indirect
 Catalog database partition number    = 0
 Alternate server hostname            =
 Alternate server port number         =


エイリアスが2つから3つに増え、REMOTEDB が新たに登録されました。


エイリアスを削除

この作業も今回は不要ですが、登録したエイリアスを削除する場合はエイリアス名を指定して次のように実行します:
$ db2 uncatalog db remotedb


エイリアスを指定して接続

では改めてエイリアスを作ったデータベースに接続します。接続時にはユーザー名とパスワードが必要になるので、以下のように指定します:
$ db2 connect to remotedb user lrz77612 using xxxxxxxx

   Database Connection Information

 Database server        = DB2/LINUXX8664 11.1.2.2
 SQL authorization ID   = LRZ77612
 Local database alias   = REMOTEDB

IBM Cloud 上のデータベースに接続できました。後はこのリモートデータベースに対して各種 SQL を実行してテーブルやデータの作成/更新/削除/検索といったコマンドが実行できます。


接続しているエイリアスから切断

なお、接続しているエイリアスから切断する場合は以下のコマンドを実行します:
$ db2 terminate




このページのトップヘ