ぼくの かんがえた さいきょうの ラズパイ クラスタ(物理(要資格 〜 その3 設計・施工編

3 回目の今回はいよいよ設計・施工を行ないます。

その 1 〜 構想編

その 2 〜 資格取得編

設計図

昨今は世界的な半導体不足の影響を受けたのか、ラズパイも手に入りにくくなってしまいましたね。

とはいえ、ラズパイクラスタを構築しようという気概をお持ちの方はすでに管理に困るくらいの数のラズパイをお持ちのはずです。今はラズパイをそんなにたくさん持っていないという方も、今から電気工事士の資格を取得したりしているうちにあっというまに半年くらい経ってしまいますので、その間に世界の半導体不足が少しは解消されたりするかもしれません(希望的観測)

ちなみに今回ご紹介する方法ですと、ラズパイに限らず Arduino や M5Stack、BeagleBone などのボードも同じ方法で整理できるというスグレモノです。

私の頭の中に妄想したラズパイクラスタの電気的な接続構成を図に起こすとこんな感じになります。 (頑張って Inkscape で書きました。本当は CAD とかが使えればもっとかっこよくて正確な図になると思いますが・・・😅)

f:id:bearmini:20220207182411p:plain
設計図

交流部分は黒い線は L、青い線は N のラインで緑はアースです。 直流部分は赤い線が + (VCC) で黒い線が - (GND) です。 水色の線は電力の線ではなく Ethernet ケーブルです。無線 LAN でも良かったのですがなんとなく無線 LAN のセットアップが面倒なイメージがあったのと、何より DIN レールに取り付けられるスイッチングハブを使ってみたかったので有線にしてみました。

3 つのスイッチング電源の入力電流は 1.8A + 0.55A + 0.55A = 2.9A なのでブレーカーの定格電流は 3A としました。 もっとラズパイを増設したくなってスイッチング電源を増設するとしたらブレーカーも取り替えないといけませんね。

AC 100V の部分の電線は 1.6mm の VVF ケーブルを使う予定なので許容電流は 15A はあるので問題なさそうですが、直流の部分の電線はどのくらいの太さのものを用いれば良さそうでしょうか。一番電流が流れるのは 5V 10A のスイッチング電源と端子台の間ですね。 この部分の電線は、交流と見分けが付けやすいように赤黒に色付けされた VFF ビニル平型コードを用いようと思います。1.25sq 以上の太さの芯線であれば許容電流的にも大丈夫そうです。

各ラズパイへの電源はスイッチング電源から端子台を経由して GPIO ピン経由で供給します。GPIO ピンから入力すると USB の規格以上の電流を流すことができますが、電源の保護回路をバイパスしてしまうようなのでピンの接続間違いなどには十分気をつけなければなりません。 USB モデムを接続して電波状況の悪い中で通信するといったような USB 端子経由での電源供給では電力不足になるような状況であっても、GPIO からの給電であれば十分な電力供給を行うことができます。

ちなみに Raspberry Pi 4 Model B の AC アダプタの定格は 5V 3A が推奨されているようですが、ネット上の情報などによると実際に流れる電流は 1〜2 A 程度とのことです。もちろんラズパイに接続する機器によってはもっと増えたりするかもしれませんし、アダプタの負荷率や USB ケーブルでの電圧降下などを考えて余裕を見ての推奨値になっているとは思いますが、やはりラズパイ 1 台あたり 3A 程度は流せるような構成にしていきたいと思います。

まず、GPIO ピンには QI コネクタ(デュポンコネクタ)というものを使ってピンヘッダーに接続します。QI コネクタは許容電流 3A のものを入手して利用しました(たまに小型サイズのものなどは許容電流 1A というものもあるようなので要注意です)。

また、この部分に用いる電線は UL1007 規格に準拠した AWG 26 のものにしました。これであれば許容電流的にも、QI コネクタのハウジングの物理サイズ的にもちょうど良さそうです。

Arduino や M5Stack はラズパイほどの電流は流れないのでそのあたりはもう少しお気楽に設計することができます。 スイッチング電源から Arduino や M5Stack の DC 入力に供給します。DC 入力のコネクタは Amazon などで購入可能です。

なお設計・施工の際にはこの内線規程という本を熟読して(すいません、嘘つきました。900 ページ以上もあるので全てを読んで理解するのはとてつもない時間がかかりますので実際のところは関係のありそうなところをつまみ食いして)参考にしました。参考にしたというか規程に従うようにしました。致命的な見逃しや勘違いなどは無いように気をつけたつもりではありますが、もし何らかの問題にお気づきになられた場合は優しく教えていただけますと幸いです。

この内線規程を読んでみると、電気工事士の筆記試験や技能試験で覚えたことの多くがこの本に書かれているということがわかります。 というか、逆にこの本に書かれていることのうち、本当に重要なごく一部の知識が筆記試験や技能試験で問われていたんだな、ということに気付かされます。

今後の電気工事士人生(大げさw)において重要なバイブル的な一冊になりそうです。

施工

さて、それではいよいよ施工です。

2x4 の SPF 材で枠を作り、そこに有孔ボードを固定しました。 有孔ボードに DIN レールをねじで止めて固定し、ブレーカーや端子台、スイッチング電源やスイッチングハブRaspberry PiArduino などを配置していきます。 大まかな配置を行ったらケーブルで接続していきます。

設計図では右側の DIN レールを 3 本にしていましたが、スイッチングハブの列の DIN レールはラズパイの列の DIN レールと共用としました。

というわけで出来上がったのがこちら!

f:id:bearmini:20220226130158j:plain

f:id:bearmini:20220226130129j:plain

f:id:bearmini:20220226130106j:plain

いかがでしょうか?

ちょっとラズパイの台数が少ないので若干迫力に欠ける気もしますが、今後ラズパイの台数を増やしていくにあたってもケーブル類のごちゃごちゃに悩まされずに整然と拡張していけそうな気がしませんか?

LAN ケーブルも自作してちょうどよい長さにするか、そもそも有線で接続するのはセットアップ時とか緊急時のみということにして普段は無線 LAN で接続するようにすれば、Ethernet ケーブル周りももっとすっきりできそうです。

電圧計とか電流計とかも常設したいなと考えているところです。

そんな風に夢が広がりますね😊

費用

計算するのが怖いほどお金がかかっています・・・

以下にご紹介する値段は、私の購入時の参考金額です。

まず部材編。

部材名 税込単価 個数 税込合計金額 備考
VVF 1.6mm 3C(公団用)100m 巻 ¥8,162 1 ¥8,162
コード 1.25sq 6m 赤/黒 ¥618 1 ¥618
フェルール端子(100 個入り) ¥2,750 1 ¥2,750 1.25sq のコードの両端に圧着してスイッチング電源や端子台に接続しやすく
UL1007 AWG 26 電線 赤 10m ¥541 1 ¥541
UL1007 AWG 26 電線 黒 10m ¥541 1 ¥541
QI コネクタ(1 極 20 個入) ¥351 1 ¥351 端子台側に使用
QI コネクタ用プラグコンタクトピン(オス)100 個入 ¥912 1 ¥912 端子台側に使用
QI コネクタ(2x3 極 20 個入) ¥1,099 1 ¥1,099 ラズパイ側に使用
QI コネクタ用コンタクトピン(メス)100 個入 ¥912 1 ¥912 ラズパイ側に使用
DC 電源コネクタ(L 型)x 10 ¥453 1 ¥453
ラズパイ DIN レールマウント ¥1,759 5 ¥8,795 現在在庫切れのようです。代替品
M5Stack Base15 産業用プロト基板モジュール ¥1,309 1 ¥1,309 M5Stack を DIN レールにマウントするためのパーツが入っています
DIN レール 110mm ¥1,075 1 ¥1,075 カット品なので定尺品より高いです。自分で切断できる方は定尺のものを買ったほうが安上がりだと思われます
DIN レール 500mm ¥774 2 ¥1,548
端子台(AC 100V 用) ¥2,298 1 ¥2,298
端子台(接地用) ¥1,507 1 ¥1,507
端子台(DC 5V/12V 用) ¥1,716 2 ¥3,432
スイッチングハブ 10/100Mbps 5 ports ¥7,678 1 ¥7,678
Ethernet ケーブル 0.5m ¥453 3 ¥1,359
ブレーカー ¥2,200 1 ¥2,200
ブレーカー用端子カバー(3 個セット) ¥782 1 ¥782
スイッチング電源 5V 10A ¥3,740 1 ¥3,740
スイッチング電源 12V 1.67A ¥2,750 1 ¥2,750
スイッチング電源 24V 1.0A ¥2,475 1 ¥2,475
小計 ¥57,278

これとは別に送料がかかっています。(複数のお店で複数回購入したので送料だけで結構な金額になっていると思われます)

このリストについて少し補足しますと、まずは電線ですが、VVF 1.6mm 3C の「公団用」といわれる絶縁被覆が黒白緑に色分けされているケーブルを購入しましたが、実際には 2〜3m しか利用しないのですがこれが一巻 100m 単位でしか販売されておらず、送料も合わせて 1 万円弱かかりました。 また直流部分の電線も最終的にはここに記載した 1.25sq のコードや UL1007 の AWG 26 の電線に落ち着きましたが、線の太さなどで他にもいろいろ試行錯誤したので数千円は追加でかかっているかもしれません。

また、ここに記載したもの以外に結束バンドやネジなどは自宅にあったものを利用しました。

それから、これらの部材を取り付ける土台となる 2x4 材やシンプソン金具、有孔ボード、ボード固定用の金具などは近所のホームセンターで購入しましたがレシートを捨ててしまったので正確な費用はわかりませんが1万円くらいはかかったかなと思います。コーススレッドはもともと持っていたものを使いました。

続いて工具にかかった費用です。 意外とお金がかかるのがこちらですね。

部材名 税込単価 個数 税込合計金額 備考
フェルール端子用圧着工具 ¥17,785 1 ¥17,785
ワイヤーストリッパー ¥2,890 1 ¥2,890
精密圧着ペンチ ¥3,435 1 ¥3,435
検電器 ¥2,763 1 ¥2,763
小計 ¥26,873

基本工具(ドライバー、ペンチ、プライヤー、VVF ストリッパーなど)はこの後にご紹介する資格取得費用のほうに含まれている「工具セット」のものを使いましたのでこちらには含めていません。

フェルール端子用の圧着工具は中華製のもっと安いものを一度購入したのですが、精度が全然ダメでかしめた端子が変な形になったりしてしまったので泣く泣くフェニックスコンタクト製の純正っぽい高級品を購入しました。

ワイヤーストリッパーはとてつもなく便利です。動作ギミックを見てるだけでワクワクします。 その次の精密圧着ペンチは QI コネクタの端子を圧着するための工具です。どちらも「エンジニア」という名前の会社の製品ですが、とても良いものですね。

最後は検電器です。これはまあこの規模の回路であれば無くても問題ないとは思いますがあると便利です。私はブレーカーの N の極にちゃんと中性線を入れられているかを確認するために使用しました。

このリストに上げた以外にも、2x4 材の枠を作るのに電動インパクトドライバ等を使いましたがそれらはもともと持っていたものを使用しました。 ノギスやピンセットなどもあると何かと便利だったりします。

最後に資格取得にかかった費用です。

費用名 税込単価 個数 税込合計金額 備考
第二種電気工事士試験受験費用 ¥9,300 1 ¥9,300
筆記試験過去問 ¥1,078 1 ¥1,078
電気工事士技能試験工具セット ¥11,483 1 ¥11,483
第二種電気工事士技能試験 練習用部材 ¥16,715 1 ¥16,715
免状発行用収入証紙 ¥5,300 1 ¥5,300
小計 ¥43,876

あとは細かいですがこれら以外にも試験会場を往復するための交通費や、複線図を書くのに便利なフリクションの 3 色ボールペンなども購入しました。

というわけで、新たに支出した分だけでも総額 13 万円近くかかった計算になりますね・・・😓 (試行錯誤した分も含めるとゆうに15万円は超えているかな)

あなたもラズパイクラスタを構築してみては?

電線などの部材はたくさん余っているし、工具も一通り買い揃えましたので本当は「私が作ってあげましょう!(有料で)」と言いたいところなのですが、そうするためには電気工事業法で定めるところの電気工事業者にならないといけなさそうで、そうなるためには最低 3 年間の実務経験が必要なのであった...。(電気工事業者になるには主任電気工事士という人を設定しなければならず、その人になるためには第一種電気工事士の資格を取るか第二種電気工事士になって 3 年の実務経験を積む必要があり、第一種電気工事士になるにも 3 年の実務経験が必要なのでいずれにせよ 3 年の実務経験が必要なようです。)

無償でならいいのかもしれませんが私もそんなに暇ではないですしやるからにはそれなりの対価は欲しいですしね😂

というわけで、みなさんも自分でがんばって「さいきょうの ラズパイ クラスタ」作ってみてくださいね!

ぼくの かんがえた さいきょうの ラズパイ クラスタ(物理(要資格 〜その2 資格取得編

その1 からの続きです。

電気工事士資格

妄想に任せて設計図面(と呼べるようなちゃんとしたものではないですが)を描いたりしていたとき、ふと 「スイッチング電源って、交流 100V の電源を入力するけど、コンセントにプラグを差し込んだりするだけの気軽な感じじゃなくて、電線をネジ止めしたりしないといけなさそうな気がするな?それってなんか資格持ってないとやっちゃいけないんじゃなかったっけ?」 という考えが頭をよぎりました。

少し調べると、どうやら「電気工事士」という資格が必要そうです。

電気工事士法やその施行規則を確認しますと、電気工事士の資格を持っていないとやってはいけない作業が定められています。 その中でも、電気工事士法施行規則に書かれている作業のうち、以下のような作業は実施しそうな感じがします・・・

イ 電線相互を接続する作業

:

ハ 電線を直接造営材その他の物件(がいしを除く。)に取り付け、又はこれを取り外す作業

:

ホ 配線器具を造営材その他の物件に取り付け、若しくはこれを取り外し、又はこれに電線を接続する作業

造営材というのは、平たく言うと家の壁や柱などのことです。配線器具とは、コンセントやスイッチのようなものです。

DIN レールを壁などどこかに固定し、その DIN レールにスイッチング電源や端子台、ラズパイなどを取り付けてそれらを電線で接続する作業は「ホ」に該当しそうな感じがします。

電線も宙ぶらりんは良くなさそうですので「ハ」の作業を実施するかもしれません。

電線同士を圧着したりして接続するような「イ」の作業はしなさそうな気もしますが、自分自身の安全のためにも、今後もっと高度な作業をしたくなった場合のことを考えても資格を持っておいて損はないだろうということで第二種電気工事士の資格を取ることにしました。自宅の外構部などに電灯やスイッチ類を増設したりしたいとも考えていたところでしたので、それももしかしたら自分でできるようになるかもしれません。

調べたところ、電気工事士の試験は年2回行われているようです。

電気工事士には「第一種」と「第二種」がありますが、自宅で 100V 程度の電圧を扱うのでよければ「第二種」で十分なようです。

試験は筆記試験と技能試験の2段階になっているようで、筆記試験の合格者が技能試験を受けることができます。

試験を実施している一般財団法人電気技術者試験センターのサイトで過去問が公開されているので見てみました。

電気工学、電磁気学などについては学生時代に学んでいましたので、電気の理論については過去問で練習すれば大体できそうな感じがしたのですが、それ以外の工事に関する知識がほとんどなく、これは難しそうだなと思いつつも、筆記試験は 60 点以上取れば(50問中30問正答すれば)合格するようでしたし、技能試験は事前に公表された 13 問のうちのいずれか 1 問が出題されるということで、事前にしっかり練習すれば大丈夫だろうと考えましたので頑張ってみることにしました。

インターネットで早速受験の申込みをしました。受験の申込時に支払った費用は 1 万円弱でしたが、教材(特に技能試験で使う工具や練習用の材料)や手袋などの作業用の道具、過去問の問題集、受験時の交通費、合格後にかかる費用なども合わせると合計で 4〜5 万円くらいはかかると思っておいたほうが良さそうです。

日本エネルギー管理センター事務局さんが公開されている YouTube のプレイリストで勉強させていただきました。 すごくわかりやすくて良かったです。

YouTube で動画を見て、過去問をやってという感じで 1 ヶ月半くらい勉強しました。

筆記試験

いよいよ試験当日です。

受験票のハガキに記載されている会場まで、公共交通機関を利用して移動しました。 私はとある私立大学が会場として指定されましたが、大学の最寄りの駅から臨時のバスが出ていて、大勢の受験者を運んでいました。

大学に着くと、受験番号に応じてどの教室に行けばよいかという情報が記載された紙が、大学の入口付近で配られました。

開場時刻の少し前に教室の前に到着したので、廊下でしばらく待ちました。

教室が開けられ、自分の席に名前と受験番号が書かれたシールが貼られているのを確認して着席しました。

筆記用具などを机の上に出しておき、試験開始時刻を待ちました。

試験監督の方から説明などがあり、ようやく試験開始。

試験は全部で 2 時間弱の時間が取られていたと思いますが、試験開始から半分くらいの時間が経過すると途中退出が可能になったと記憶しています。

私は途中退出が可能になった時刻にはすべての問題を解き終えていましたが、念のためもう一度すべての問題を見直して、1 問だけ回答を変えてから提出して退出しました。

行きのバスが混みすぎていたので帰りは駅まで歩こうと心に決めていました。30 分ほど歩いて駅まで戻りました。

試験の翌日には解答が公表されましたので自己採点してみたところ 50 問中 48 問正解でした。間違えた 2 問のうちの 1 問はうっかりミスで、問題文をよく読み直したら正解できていました。もう 1 問は最後に見直して回答を変えてしまったところでした。回答を変える前は合っていたのに変えてしまったことで間違ってしまったので惜しいことをしたなぁと思いましたが、いずれにせよボーダーラインは大きく超えているようでしたので安心しました。

この日から、次の技能試験の対策を開始しました。

技能試験の練習

技能試験も日本エネルギー管理センター様の公開されている YouTube のプレイリストを 1 から順番に見ていきながら、実際に自分でも回路を作ってみるという作業を繰り返して練習しました。

練習用の部材は以下のようなセットを買うと一式揃いますので、私のような右も左もわからなかったような初心者にはちょうど良かったです。ただ、たとえばランプレセプタクルは公表問題13問中12問で使われていますがこのセットには 1 つしか入っていません。ですので各問題を練習し終わる度にランプレセプタクルなどの部材を取り外して次の問題の練習に再利用するというようなやり方になります。ただ、ランプレセプタクルが 12 個もあっても置き場所に困るでしょうし、それでよいとは思います。差し込み型コネクタも最低限の数しか入っていないので、基本的には再利用しながら練習していく感じになると思います。

工具もホーザン様の出されているセットを購入しました。日本エネルギー管理センター様の動画の中で使われているのと同じ工具のセットですし、実際に資格取得後にも普通に使えそうなので、私のような初心者にはちょうど良いと思います。

動画を見たあとに実際に自分で回路を作ってみると、思ったよりも時間がかかることがわかります。 実際の試験では 40 分間で回路を完成させなければなりませんが、かなりテキパキやったつもりでも 35 分以上かかっていたりします。 もし本番でどこか少しでも間違えたりすると、やり直す時間はほとんどないのではないかと心配になります。 特に電線の圧着や差し込み型コネクタへの差し込みを間違えるとやり直しはかなり難しそうです。 また、複線図を書いている時間も無さそうなので 3 路 / 4 路スイッチのある回路などはスラスラと接続できるかどうか心配になります。 ただ、13 問もある公表問題を 1 つずつ練習していくにつれてだんだんとコツを掴むことができましたので、10 問くらいを練習した時点では時間に余裕もできるようになってきました。自分がミスしやすいポイントなどにも気付けるので、最低でも各問一回ずつは練習したほうがよいでしょう。

あと、素手で作業していると VVF のシースを剥いたりする作業で思いのほか指にダメージがあります。 私はホームセンターで軽作業用の手袋を購入して使用しました。

ついでにホームセンターでいままであまり寄り付いたことのなかった電気工事関連のコーナーを見てみたところ、VVF ケーブルや圧着コネクタ、差し込み型コネクタなど必要そうなものがすべて売られていましたので、もし公表問題の練習が 1 回ずつだけだと不安で 2 回めの練習をしたくなったとしてもここで部材を買い揃えることができそうだということがわかりました。

筆記試験からおよそ 1 ヶ月経ったころ、技能試験の受験票が届きました。筆記試験に合格した旨はそのハガキの中に小さく書かれていて、こんなものかと思いました。

技能試験当日

筆記試験とは別の会場でしたが、こちらもやはり私立大学の教室が会場でした。 今回の会場は最寄りの駅から徒歩で行くことができる近さでした。

教室に入るまでは筆記試験のときと大体同じでしたが、説明開始時刻になってからの雰囲気が筆記試験とはだいぶ違いました。 最初に問題用紙が配られましたが、その時点では表紙しか見てはいけないというのは筆記試験と同じです。表紙には材料の一覧が書かれています。 続いて、ダンボール箱に入った材料が配られます。配られても、指示があるまで箱を開けてはいけません。 全員に行き渡っているかどうか、何度も確認が行われました。

試験開始まであと 10 分くらいといったところでようやく箱を開ける許可が出て、材料が揃っているかどうかの確認を行ないました。 電線の長さを測るのに工具が使えません(メジャーなどは使えます)。練習のときはストリッパーに付いている目盛りを使って長さを図っていましたがそれは使えないので、ホーザンの工具セットに入っていた布尺を初めて使いました(笑)

材料の確認を終えてしばらく待っていると時間になり、そのまま試験が始まりました。

私は今回は公表問題の 6 番、露出型コンセントと 3 路スイッチのある回路でした。

露出型コンセントの出来に少し不安はありましたが、一応欠陥ではないはずです。そしてそれ以外は特に問題なく完成させられました。 しかし、これまで YouTube を見て独学で練習してきただけで第三者に判定してもらったことは無かったので自分としては問題ないと思っていても試験官には欠陥と判定されてしまうのではないかという不安は試験終了後もずっとつきまといました。

技能試験結果発表

年が明けて 1 月の末、いよいよ結果が発表される日がやってきました。 当日は午前 9 時 30 分ころから試験センターのホームページ上で番号が検索できるようになるということで、事前に受験番号を準備しておきました。発表の数日前に見てみたところ、合格者一覧から自分の番号を検索すること自体はできるようになっていたので、試しに検索してみましたが当然ながら番号は見つからない旨が表示されました。

なお、自分の受験票を見て手で写すと間違えるかもしれないと思ったので、試験の申し込みのときに使ったマイページから受験番号を見つけてコピーして使用したのですが、その受験番号の記載されている場所がすごく分かりづらかったです。まず、メインのメニューから「試験申し込み」を選択しなければいけません。さらにそのページから「試験会場案内」のボタンを押して表示される画面に受験番号があります。 普通こんなところに自分の受験番号があるとは思わないと思うので、これは改善を求めたいですね。

発表当日、9 時 30 分より少し前に私の番号を検索してみたら「合格者一覧にあります」と表示されました!

あとはハガキで正式な通知が来て免状の申請を行う必要がありますが、もうこの時点ですごくわくわくしながら「ぼくの かんがえた さいきょうの ラズパイ クラスタ」の構成を図面に起こしてみたり必要な部材を Web で探してみたりホームセンターをめぐってみたりし始めました。

免状の申請

試験に合格したら、いよいよ免状の申請です。免状を所持していないと電気工事を行ってはいけないので、さっそく電気工事をしたいというはやる気持ちを押さえて必要な書類を揃えます。

正確な情報は居住都道府県の免状窓口となる団体のホームページ等を参照していただきたいのですが、私の場合は以下のものを用意する必要がありました。

  • 電気工事士免状交付申請書
  • 格通知のハガキの原本
  • 住民票
  • 写真 (4cm x 3cm) x 2 枚
  • 都道府県の収入証紙(国の発行する収入 紙ではないので郵便局とかでは買えません。市町村の発行する収入証紙でもない)
  • 返信(免状送付)用封筒(切手なし)

郵送でも申請が可能ですが、たまたま居住している市に窓口となる団体の建物があり、しかも都道府県の収入証紙を購入できる場所が限られていて自宅から最も近い場所がその免状窓口だったので、直接赴いて申請しに行きました。

こういった外郭団体的な組織の事務所では引退間近のおっさんがめんどくさそう&上から目線で応対してそうな昭和なイメージを勝手に持っていましたが、実際に窓口に出向いてみると全くそんなことはなく、清潔感のある事務所で女性のスタッフ 3 名が働いており、とても丁寧に感じよく対応していただきました。

2 週間ほどで免状が届くとのことでしたので、それを楽しみに待ちながら「ぼくの かんがえた さいきょうの ラズパイ クラスタ」の設計をしたり部材の発注をしたりしていました。

免状が届いた!

申請を行ってからちょうど一週間後、書留で免状が届きました。窓口では 2 週間くらいと言われていたので、思っていたより早く届きました。 窓口の方は余裕を見て 2 週間とおっしゃっていたのでしょうね。

というわけで、これでいつでも電気工事を行うことができます。

いよいよ「ぼくの かんがえた さいきょうの ラズパイ クラスタ」の施工に入っていきたいと思います。

その 3 へつづく

ぼくの かんがえた さいきょうの ラズパイ クラスタ(物理(要資格 〜 その1 構想編

仕事だったり趣味だったりでラズパイやら Arduino やら M5Stack やらをたくさん稼働させている人も多いと思います。 私もそうです。

常時稼働させる台数が増えてくると AC アダプタやら USB ケーブルやら LAN ケーブルやらでごちゃごちゃになりがちですよね。 ラズパイクラスタを稼働させていたり、Arduino や M5Stack などを使っていろいろなプロトタイピングを並行して行っていたりするときっとみなさんも同じようなお悩みを抱えていらっしゃるのではないでしょうか。

そんなお悩みを解決するべく、こんな感じのものを作ってみました。

f:id:bearmini:20220226130158j:plain

f:id:bearmini:20220226130129j:plain

f:id:bearmini:20220226130106j:plain

なんでこんなものを作ろうと思ったか、どうやって作ったか、作るまでの紆余曲折などを(だいぶ長くなりますが)説明してみたいと思います。

そんな話には興味はない、ラズパイクラスタをもっと詳しく見せろ、という方は その 3 へ一気に進んでください。

ラズパイタワーの弱点

私が勤めている会社の運用チームでは「ラズパイタワー」なるものを作って運用していて、日々ネットワークの監視やサービスの正常性の確認に役立っています。

個人でも自宅用にラズパイタワーを作って Kubernetes やろうかなーとか思ったこともあったんですが、ラズパイタワーにはいくつか問題がありました。

中でも個人的に懸念したことのトップ 3 が以下の通りです。

  1. 電源まわりがカオスになる

    • AC アダプタがでかくて邪魔
    • ケーブル類の長さが中途半端だったりして持て余してごちゃごちゃになりがち
  2. USB からの電源供給だと電力不足になりがち

  3. タワー状に積み重ねると放熱が心配

放熱に関しては、なるべくならラズパイが水平方向に並ぶ状態(ラズパイ自体は平置きじゃなくても良い)がいいなぁと思っていました。(つまり下の図の左側ではなくて右側の状態)

f:id:bearmini:20220130160738p:plain
放熱のイメージ図
(緑色の四角がラズパイの基板のつもりですw)

Kiskstarter で見つけた CloverPI

そんなある日私は CloverPI というプロジェクトを Kickstarter で見つけて喜び勇んで Back しました。

電源ケーブルEthernet ケーブルそれぞれ 1 本ずつで 4 台の Raspberry Pi を動かすことができます。 これはスマートだと思いました。

・・・しかし、CloverPI の出荷が始まったという知らせを受けた直後くらいに、CloverPI を受け取って動作確認を行ったユーザーから「基板の電源周りから出火した」という問題が報告され、それ以降 Creator による更新が途絶えてしまい、連絡がつかなくなってしまったのです・・・

私の手元にはまだ現物は届いていません。😭

Turing Pi

その後私は Turing Pi という製品も見つけました。

これは Raspberry Pi Compute Module を使ってクラスタを構築するものです。 既に持っている Raspberry Pi を搭載させることはできませんが、今後購入する Raspberry Pi をすべて Compute Module にすればよいので次善の策かとも思いました。USB も(7 台中 4 台は)使うことができそうですので、USB タイプの LTE モデムを接続したりもできそうです。

Turing Pi は ITX マザーボードと同じサイズのようなので ITX 用ケースに入れられそうなのも魅力的でした。

私の心は半分くらい Turing Pi を購入する方向に傾きかけていて、ITX のケースや電源を Amazon で探したりしていました。

DIN レール

調べていくと、ITX ケースの中には、DIN レールというものにマウントすることができるものもあるようだということを知りました。

このときの私は恥ずかしながら DIN レールというものを知らなかったのですが、調べてみるとなかなか便利そうです。

さらに調べていくと、DIN レールにマウントできるスイッチングハブなんかもあるようです。

ラズパイを DIN レールに直接マウントするマウンタAmazon で見つけました。このマウンタ、実は Arduino UNO / Mega 等や BeagleBone などもマウントすることができます。

Web で検索してみると DIN レールにマウントできるスイッチング電源もたくさん種類があることがわかりましたので、それを使えば AC アダプタでテーブルタップまわりがごっちゃごちゃという状態も避けられるんじゃないかと思いました。(そのかわり電源ケーブルは自分で配線しなければならなくなりますが)

スイッチング電源の容量を大きいものにすれば、1 台のスイッチング電源で 3〜4 台のラズパイを駆動することも造作無さそうです。 さっと探してみた感じ、DC 5V / 10A で出力できるスイッチング電源が 3,000 円 〜 4,000 円くらいで購入できそうでした。 Raspberry Pi 4B では、5V / 3A 出力できる USB アダプタが必要ですがこれをラズパイの台数分そろえることを考えたら少し安い印象です。

ここまできたらもう私の心はスイッチング電源、スイッチングハブ、ラズパイをそれぞれ DIN レールにマウントして理想のラズパイクラスタを作るという妄想でいっぱいになりました。

その 2 へつづく

Ubuntu 20.04 で GUI 起動時に任意のプログラムを実行したい

表題の件ですが、簡単だろうと思っていたら意外とハマったのでメモしておきます。

まず前提条件の整理から。

自宅で仕事用に使っている Ubuntu Desktop 20.04 で、GUI にログインした直後に実行したいプログラムがありました。

仮想マシンとか WSL のような環境ではなく、ベアメタルにインストールされている以下のような環境です。

$ uname -a
Linux ogubuntu 5.4.0-94-generic #106-Ubuntu SMP Thu Jan 6 23:58:14 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 20.04.3 LTS
Release:    20.04
Codename:   focal

GUI にログインした直後に実行したいプログラムは具体的には xmodmap です。 以下の記事で説明しているような方法で、キーバインドの入れ替えを行ないたいのです。

bearmini.hatenablog.com

Ubuntu 起動直後に xmodmap コマンドを実行して自分好みのキーバインドを適用したいというシンプルな要求があったのですが、当然 .bashrc などに書いてもその要求は叶いません。おそらく GUI のシステムの起動時に実行されるスクリプトがあるはずだろうと思っていろいろ調べたのですが、「そもそも今の Ubuntu って X Window なの?なんか Wayland?とかいうのがあるらしいけど・・・」とかそのあたりから知識が足りません。

それでもなんとか以下のような Stack Exchange の質問にたどり着きました。

unix.stackexchange.com

どうやら GUI 起動時に読み込まれそうなスクリプトとしては .xinitirc .xsession .xsessionrc などがありそうだということがわかりました。

そこで以下のようなスクリプトを書いてみました。

$ cat ~/.xinitrc 
(
    if [ -s ~/.Xmodmap ]; then
        /usr/bin/xmodmap ~/.Xmodmap
    fi
) >~/.xinitrc.log 2>&1

(↑は .xinitrc の例ですが、.xsession.xsessionrc も同様の内容で作成しました。)

結果として、.xinitrc.xsession は実行されず、 .xsessionrc は実行されるのですが xmodmap コマンドがエラーを起こすという状況でした。エラーメッセージの内容的には、bad keysym in remove modifier list 'Alt_R', no corresponding keycodes といったようなもので、存在しているはずのキーコードが存在しないと言われているのでちょっとよくわからない感じです。 同じコマンドを、起動後に手で実行するとこのようなエラーは発生しないのでちょっと実行されるタイミングが早すぎたりするような感じなのでしょうか。

他に、.xprofile というファイルも実行されるらしいことがわかりましたので同様に試してみたのですが、こちらは実行されて xmodmap コマンドも正常に実行されるようでしたが、なぜかログイン後の GUI 環境では xmodmap の効果が現れません。(つまりキーの入れ替えが行われていない状態) ちょっとどういうことなのかよくわかりません。

ほかに有力な情報も見つかりませんでしたし、不便だなとは思いつつ Ubuntu を起動する度に真っ先に Terminal を立ち上げて xmodmap コマンドを実行することでなんとかしのいでいました。 再起動なんて月に 1〜2 回くらいしかしないので仕方ないと思いながらすでに何年も経ってしまっていたのですが、やはり毎回同じコマンドを手で入力し続けるのはさすがにプログラマとしての名がすたると思って更に調べていくと以下のような情報を見つけました。

GNOME を利用している場合は $HOME/.config/autostart/.desktop という拡張子を持つファイルを作り、その中に 仕様 に従って設定を書けば起動時に実行されるらしいということがわかりました。

たしかに $HOME/.config/autostart/ 以下には起動時に自動的に実行されるアプリの名前が並んでいました。

試しに $HOME/.config/autostart/xmodmap.desktop という名前で以下のようなファイルを置いてみました。

[Desktop Entry]
Name=run xmodmap at startup
Exec=xmodmap /home/bearmini/.Xmodmap
Type=Application

恐る恐る Ubuntu を再起動してみると・・・ログイン直後から xmodmap が実行された状態になっています! やりました!

一応ファイルの中身を説明すると、 Name はアプリケーションの名前です。一般に公開するアプリというわけではないので自分でわかる名前を付けましょう。一応 GNOME Tweak Tool の Startup Applications の一覧に表示されるときに使われる名前になります。 Exec は実行されるコマンドと引数です。ここでは直接 xmodmap コマンドを実行していますが、複雑な処理をしたい場合はスクリプトを用意してそれを実行するようにすると良いかもしれません。 TypeApplication Link Directory のうちのいずれかを指定するようで必須のフィールドですが、 LinkDirectory はよくわからないのでおそらく Application でよいでしょう。 たったこれだけの記述で自動実行できるようになっただなんて・・・これまでの数年間、Ubuntu を起動するたびに xmodmap コマンドを手で実行していた自分が馬鹿らしくなりました。

復刻版: 超初心者のための Unicode(ユニコード) 入門

優秀なエンジニアであっても、これまであまり直接関わってこなかった場合、案外文字コードのことについて詳しくは知らなかったりするものです。Unicode って何?UTF-8/16/32 の違いを説明できる?サロゲートペアって何?絵文字を UTF-8 で表現すると何バイトになる?そういった質問に自信を持って答えられるというエンジニアは実はそれほど多くなかったりするのではないでしょうか。

先日、Slack 上での同僚との会話の中から、

👨‍👩‍👧‍👦

という絵文字UTF-8 で何バイトか?という話題になり、調べてみたところ 25 バイトという結論になりました。

確かめてみるには、

f:id:bearmini:20211220173026p:plain

このようなコードを UTF-8 で emoji.rb というファイルに保存して、これを実行すると、

$ ruby emoji.rb 
25

このような結果になります。

なぜそうなるのかを同僚には簡単に説明しましたが、Unicode とか UTF とか UCS といったような言葉の意味だったり、ほかにも様々な概念を正確に把握していないと、正しく理解することが難しいようでした。

ちなみに上記の絵文字は

  • 男性の顔
  • 女性の顔
  • 女の子の顔
  • 男の子の顔

の 4 つの絵文字を、Zero width joiner という特殊な符号で連結して一つの「家族(男性・女性・女の子・男の子)」という絵文字として扱っているという特殊なパターンです。

少し脇道にそれますと、「家族」を表す絵文字はこれだけではなく、両親が同性のパターンや片親のパターンも絵文字として存在しています。子どもも男の子 x 2 の場合や女の子 x 2 の場合、男の子か女の子が 1 人だけの場合も存在します。さらに特殊な符号を使うことで skin-tone(肌の色味)の指定も可能ですので、膨大な組み合わせを表現可能です。子どもの人数が 3 人以上の場合には今のところ対応していないようですが、1文字分のスペースにあまり詰め込みすぎても絵文字として見づらくなってしまうのでそこは仕方ないかなと思います。 いずれにせよ、それらの組み合わせ一つ一つにユニークなコードポイントを割り当ててしまうとキリがないので、基本となるパーツの組み合わせで絵文字が生成できるようになっているんですね。

そんな話をしたときに、ずっと昔に自分が書いた「超初心者のための Unicodeユニコード)入門」というブログ記事のことを思い出しました。

当時(2006 年ころから 2010 年くらいまで)はブログシステムを自分で作っていて、レンタルサーバー(というか VPS)の上でホストしていたのでした。ブログ記事もブログシステムも更新しなくなってしまってドメイン名を expire させてしまったのと同時にそのシステムも撤収してしまったのでいまはその記事を読むことはできないのですが、今あの記事があったら同僚にも紹介できるし、もしかしたら今でもあの記事に少しは価値があって PV が稼げるかもしれないのになぁと思っていました。

そこで、ダメ元で Internet Archive で検索してみたところなんとその当時の記事が残っていました!

そんなわけでこの記事は当時の自分の書いた記事をサルベージして再掲するものです。

Internet Archive (Wayback Machine) には少しばかりですが寄付をしておきました。

では、復刻版の「超初心者のための Unicodeユニコード)入門」、今となってはちょっと古くさく感じられる記述もあるかもしれませんが、そういうところまで含めてお楽しみください!

超初心者のための Unicodeユニコード)入門

はじめに

XML ファイルの先頭(XML 宣言)にはとりあえず encoding="utf-8" って書いてみたり、C++Windows プログラムを書くときはなんとなく TCHAR とかを使ってみたり、「なんか Java とか C# とかは内部でユニコード使ってるらしいぞ」ということは知っていても実はそれがどういう意味なのかよくわかっていなかったり、「そろそろちゃんと Unicode を勉強しないといけないかな・・・でも、ネットで検索しても小難しい記事しか見つからなくてよくわかんないんだよな~」と思っていたりするそこのアナタ!

ていうか私自身が Joel on Software を読むまでその状態でした。Unicode のことをよく理解できていなかったというか、むしろ激しく誤解していました。

ただ、それは平易な日本語で書かれた Unicode 入門記事がネットに転がっていなかったからだと思います。(責任転嫁)

でも、大丈夫です。

Joel on Software にも書かれているとおり、

Unicode はそんなに難しくない」

のです。

以下に示す、基本的な事実だけまずはおさえておけば、小難しい理屈やら歴史的事情やらは置いておいて、Unicode のことを理解できるはずです。

Unicode に詳しい方へのお願い

以下の文章は、厳密には間違いを含んでいるところがあるかもしれません。私自身、Unicode 初心者には変わりありませんので。もし、著しく事実と異なるような内容がありましたら遠慮なく指摘してください。

※ それから、Joel on Software を読んで Unicode に開眼したという筆者の個人的体験により、以下の文章の内容に 「Joel on Software のパクリじゃねーの?」 というものが含まれてしまうかもしれません。

著作権的に問題がありそうな点がありましたら、遠慮なく指摘してください。

それでは、超初心者のための Unicode 入門、始まり始まり~

Unicode文字集合

Unicode 入門といいつつ、ここでまず、Unicode のことはしばし忘れて、概念的なことを考えてみましょう。

仮に、あなたが出版社かどこかに勤めていたとして、上司から「今度わが社で "世界の文字字典(完全版)" を作ることになったので、世界中のありとあらゆる文字を全部集めて来なさい」と指示されたとします。

日本語と中国語で似てる漢字は同じ文字とするのか?とか、旧字は常用漢字と同じ文字とみなすのか?とか、全角数字の4と半角数字の 4 は同じ文字なのか違う文字なのか?とか、そういう難しいことはエライ人たちに任せるとして、あなたはとにかく文字という文字を世界中から手当たり次第に集め尽くしたとします。

そうやって集められた文字のことを、文字集合と呼びます。

そして、実はこの文字集合を作るという作業を公共的な機関で正式に行ったものが Unicode なのです。

Unicode は、文字をひたすら集めたもの=文字集合なのです。

ここで大事なのは、この時点では UTF- なんとかとか、サロゲートペアとか、意味不明な用語がまだ出てきていないということです。それらは、Unicode に含まれる文字をコンピュータ上でどう表現するかといった話なので、分けて考えましょう。

大前提として、「Unicode とはコンピュータ上の表現を離れた論理的な文字体系のことなのだ」と理解すると、それ以外の難しい話題も、もつれた糸がほどけるように徐々に理解できるようになっていくはずです。

コードポイント=文字の通し番号

文字を集めるだけ集めたら、その文字一つ一つにユニークな通し番号を付けたくなるというものです。(その理由としては、たとえば管理上便利だからとかいろいろあると思います)

Unicode でも文字に通し番号を割り当てていて、その通し番号のことをコードポイントと呼びます。

たとえば、アルファベットの 'A' は 65 番目(16進数で 0x41 番目) で、ひらがなの 'あ' は 12354 番目(16進数で 0x3042番目)です。

これらをそれぞれ U+0041 とか U+3042 と表記します。(U+ のあとに 16 進数 4 桁~ 6 桁くらいで表記します)

エンコーディング

ここでようやく、このコードポイントをどうやってコンピュータ上で扱うかという話になります。

たとえば、あなたが "こんにちは" という文字列を電子メールで送信したい場合、相手にどのようなバイナリデータを送信すればよいのでしょうか?

(メールのプロトコルとか難しいことはとりあえずひとまず置いといて。)

それぞれの文字のコードポイントは以下のように決められています。

文字 コードポイント
U+3053
U+3093
U+306B
U+3061
U+306F

unicode.org の Unihan Database を参照しました)

各文字のコードポイントは、16 進数で 4 桁ずつなので、16bit で

3053 3093 306B 3061 306F

というデータを送ればよいような気がします。

トルエンディアンな環境だと、バイトオーダーを入れ替えて

5330 9330 6B30 6130 6F30

というデータを送ったほうがよいかもしれません。

このように、コードポイントをコンピュータで扱えるバイト列に変換すること、またはその変換規則を「エンコーディング」と呼びます。

エンコーディングには、UTF-8, UTF-16, UTF-32 などいくつかの方法があります。

UTF-16

そして、実は、上記のように単純に 16 bit 単位に変換する方法こそが UTF-16 というエンコーディング(の一部)なのです。

トルエンディアンのものを UTF-16 LE、ビッグエンディアンのものを UTF-16 BE と呼んだりします。

しかし、現在の Unicode は、U+10FFFF(16 進数で 6 桁)までコードポイントを定義している(それだけたくさんの文字を集めて、文字集合が大きくなった)ので、この単純な方法だと破たんしてしまいます。

この対策としてサロゲートペアという方法が出てきます。16bit のバイナリを二つ組み合わせて U+FFFF より大きいコードポイントを表現する方法です。

これについては、ちゃんと詳しく説明してくれている書籍やサイトがたくさんあると思うのでここでは割愛します。

それから、受け取り手の立場になると、UTF-16UTF-16 でも、リトルエンディアンで送られてきたデータなのかビッグエンディアンで送られてきたデータなのかを識別したいですよね。

たとえば、先ほどの 'こんにちは' のデータを受け取った側が、事前にビッグエンディアンで送られてくるのかリトルエンディアンで送られてくるのかがわかっていなければ、3053 というデータが U+3053 のことなのか、それとも U+5330 のことなのかわかりませんね。

そのような時に、データの先頭にバイトオーダーマーク(Byte Order Mark: BOM)を入れることがあります。

BOM は U+FEFF というコードポイントが割り当てられています。(大文字だと E と F が識別しづらいですが、小文字で書くと、U+ f e f f です)

さきほどの 'こんにちは' の前に BOM をつけると、

ビッグエンディアンなら:

FEFF 3053 3093 306B 3061 306F

トルエンディアンなら:

FFFE 5330 9330 6B30 6130 6F30

となります。

トルエンディアンの時に出てくる FFFE というバイナリですが、U+FFFE というコードポイントは Unicode では未定義とされていますので、U+FFFE ではなく U+FEFF (BOM)がリトルエンディアンで送られてきているのだと判断できます。

というわけで、受け取った側は最初の 16bit を読んで FEFF ならビッグエンディアン、FFFE ならリトルエンディアンと判断できるわけです。

注意点としては、データに BOM をつけたら UTF-16 LE とか UTF-16 BE とは呼ばず、単に UTF-16 と呼ばなければならないということが挙げられます。

逆に、UTF-16 LE とか UTF-16 BE とか書いたら、データに BOM をつけてはいけないということになっています。

UTF-8

今度は "Hello" という文字列を UTF-16 で表現するとしたらどうなるでしょうか?

文字 コードポイント H U+0048 e U+0065 l U+006C l U+006C o U+006F

ビッグエンディアンだと

0048 0065 006C 006C 006F

ですし、リトルエンディアンだと

4800 6500 6C00 6C00 6F00

となるでしょう。

でもこれだとなんだか 00 が途中にたくさん入っているのでもったいないですね。

特に、普段アルファベットしか使わないアメリカなど英語圏の人々にとっては、この 0 はとても余計なものに見えることでしょう。

そこで、あるルールにのっとって、アルファベットは 1 バイトで、それ以外の文字(漢字など)は 2 バイト~ 6 バイトくらいのデータに変換してバイナリにする変換規則(エンコーディング)も考えられました。

それが UTF-8 というエンコーディングです。

たとえばひらがなの 'あ' (U+3042) は E3 81 82 という 3 バイトに変換されます。

漢字や仮名をたくさん使う日本人にとっては、一つの文字が 3 バイト以上になってしまうのは記憶容量の無駄のようにも思えますが、UTF-8 にはもちろんメリットもいくつかあります。

たとえば、変換後のバイト列の中に0が全く現れないので、従来のヌル終端文字列用の処理(文字列のコピーなど)がうまく動く場合が多いというメリットがあります。

それから、バイトオーダーの問題がなくなります。

変換ルールがちゃんと考えられているので、文字と文字の境目がわかりやすくなっています。

また、'/' などの記号のコードが途中に表れないようになっているので、ファイル名やディレクトリ名として安全に使えます。

これらのメリットから、通信や記録の際には UTF-8 が使われることが多いようです。

UTF-8 にはバイトオーダーの問題がないのですが、UTF-8エンコーディングされている、ということ自体を示すために、U+FEFF (BOM) を UTF-8 の規則で変換した EF BB BF という 3 バイトを先頭につけることがあります。

つまり、テキスト処理をするプログラムが先頭の 3 バイトを読んでみて EF BB BF だったら UTF-8 の可能性が高い、と判断できるわけです。

BOM 付きの場合単に UTF-8 と呼び、BOM なしの場合 UTF-8N と呼びます。

それから、UTF-8 の変換規則は、現在の Unicode よりも大きな文字集合である 'UCS4' の範囲のすべての文字を表すことができるというのも特徴です。

UTF-16サロゲートペアを使っても U+10FFFF より大きなコードポイントを表現できないが、UTF-8 なら U+7FFFFFFF まで表現可能)

UTF-32

コードポイントを固定長で表現できるような簡単な方法もあるとうれしいですよね。各文字が固定長のバイナリに変換されると、文字列の長さをカウントしたりする処理がとても簡単になるなどのメリットがあります。

その場合には、UTF-32 というエンコーディングを使うとよいでしょう。

UTF-32 という変換規則にのっとると、先ほどの 'Hello' は

00000048 00000065 0000006C 0000006C 0000006F

というバイナリデータに変換されます。(各コードポイントを 32 bit で表現)

ちょっと贅沢な記憶領域の使い方に思えますが、記憶領域の経済性よりもデータとしての扱いやすさを重視する場合は有効な方法でしょう。

これにもリトルエンディアンとビッグエンディアンがあり、UTF-32 LE とか UTF-32 BE とか表記します。

BOM を入れたら LE や BE は付けず、単に UTF-32 と呼ぶのは UTF-16 と同様です。

UTF-32 の表現できる範囲は UTF-8 と同様、UCS4 という文字集合のすべてです。

その他のエンコーディング

実は、おなじみの Shift-JIS や EUC-JP も符号化方式の一種です。ただし、Shift-JIS や EUC-JP は Unicode よりも小さな文字集合にしか対応していない(Unicode 内のすべての文字をエンコーディングできるわけではない)と考えてください。(たとえば韓国語の文字やアラビア文字などが文字集合に含まれていない)

まとめ:

Unicode文字集合UTF-8, UTF-16, UTF-32, ... はコードポイントを符号化する方式。

これだけ覚えておけば、Unicode が意外とすっきりしたものに思えてくるのではないでしょうか。

ていうか Joel on Software にはもっと面白おかしくわかりやすく書かれているのでぜひ読んでみてください。

当時のブログ記事に寄せられていた あまの 先輩のコメントも非常に参考になりますので合わせて掲載しておきます。

あと一点だけ、超初心者にも押さえておいて欲しい点があります。

それは正規化形式についてです。

「一文字に見える文字」(書記素クラスタといいます)が複数のコードポイントからなる場合があります。 そして同じ文字でも、複数通りの表現方法があり得ます。

たとえば「ば」は、U+3070 というコードポイント1つで表すこともできるし、U+306F U+3099 という2コードポイントで表現も可能です。

複数通りの表現方法のうち、どれを採用するかのルールが正規化形式です。

それがわからないと Win32 API の ::MultibyteToWideChar() の dwFlag で言うなら、MB_PRECOMPOSED と MB_COMPOSITE の使い分けが理解出来ないので。。。(前者が NFC、後者がNFDと呼ばれている正規化形式です)

つまり、「どの正規化形式を使い」、そして「どのエンコーディング方式でエンコードしているか」の両方を意識するようにして下さい。

以上です。

soracom-cli のちょっと便利な Tips

この記事は 株式会社ソラコム Advent Calendar 2021 の 12/4 の記事です。

qiita.com

こんにちは。私はソラコムで soracom-cli を開発しています。soracom-cli は、IoT プラットフォーム SORACOM が提供している APIコマンドラインシェルスクリプトなどから簡単に呼び出せるコマンドです。

例えば、持っている SIM の一覧を取得するには

soracom sims list

というコマンドを実行するだけです。 結果は JSON で返ってくるので、jq コマンドなどでさらに処理をつなげることができて便利です。

インストール方法はとっても簡単で、README に書いてありますが、MacLinux であれば

brew tap soracom/soracom-cli
brew install soracom-cli

これだけでインストールできます。

ではここからは、すでに soracom-cli をお使いいただいている皆様のために、あまり知られていないけど知っているといざというときちょっと便利な Tips を 2 つご紹介したいと思います。

API Key や API Token が欲しい・・・」

何らかの理由で唐突に API Key や API Token が欲しくなることって、よくありますよね。

たとえば API リファレンスでちょっと API を試してみたくなったときとか(通常はそのページから認証を行って API Key や API Token を取得しますが、実はこのページに API Key / API Token / Operator ID を直接手で入力することも可能なのです!)、soracom-cli でたくさんのコマンドを実行したいときに実行時間を短縮させるために認証を一度だけ行って API Key と API Token を使いまわしたい時とか、あるいは API Token の JWT を無性に解読してみたくなったときとか、自分の API Key の UUID を占いに使いたくなったときとか、自分の Operator ID で語呂合わせを考えたくなったりしたような場合です。(まともな用途が一つしかありませんね・・・さてどれでしょう!?)

そんなときには soracom auth コマンドを実行していただくことでいつでも簡単に API Key や API Token が欲しいという欲求を満たしていただくことができます。

soracom auth --email <email> --password <password>

しかし、メールアドレスはまだしもパスワードを直接書くのはなんか嫌ですね。シェルから実行するとしても画面を後ろから覗き込んでる人がいないかとか心配になりますし、実行した後もヒストリに残ったりしてしまいます。ましてやスクリプトの中になんか絶対に書いてはいけませんね。

そもそも意識の高い皆様はパスワード認証ではなく認証キーによる認証を行われていることでしょうから、keyId-NIWXcjCh..... とか secret-pJHehi5i..... みたいな記号の羅列を記憶しておいて入力するなんてことは現実的ではありませんし、これもスクリプトに書いておいてはいけない秘密の情報です。

ではどうすればよいのか?

みなさん、soracom-cli を使い始める一番最初に soracom configure を実行されたかと思います。

そのときに、パスワードとか認証キーを入力しましたよね? それは安全な形で(自分自身しか読み書きできない権限で)コンピュータのストレージに保存されています。

これを使えばいいのです!

では soracom configure したときに入力したそれらパスワードや認証キー(いわゆるクレデンシャル)はどこに保存されているのでしょうか?

答えは $HOME/.soracom/ ディレクトリです。(MacLinux の場合。Windows の場合は %HOME%\.soracom\ もしくは %USERPROFILE%\.soracom\

soracom configure--profile 引数を指定せずに実行した場合はそのディレクトリに default.json という名前で保存されています。

試しに cat $HOME/.soracom/default.json を実行してみてください。

パスワードもしくは認証キー情報が見えましたか?

ではこれを使うにはどうしたら良いでしょう? JSON ファイルなので jq コマンドで取り出す?

email="$( cat $HOME/.soracom/default.json | jq -r .email )"
password="$( cat $HOME/.soracom/default.json | jq -r .password )"
soracom auth --email "$email" --password "$password"

いえいえ、実はもっと簡単な方法があるのです。以下のようにして、soracom auth コマンドに直接そのファイルを渡してしまえばよいのです。

soracom auth --body @"$HOME/.soracom/default.json"

SORACOM の API は HTTP によるいわゆる REST API ですので、--body 引数は、API を呼び出すときのリクエストの Body の内容を指定するための引数です。

--body 引数の最初の文字を @ にすることで、それ以後の文字列はファイル名の指定とみなされます。その指定されたファイルの中身を読み込んでそのまま body として渡すという動作になります。

そして $HOME/.soracom/default.json に保存されている JSON ファイルの形式は、実は Auth API のリクエストボディと互換性のあるフォーマットになっているのです!なのでこのファイルを直接引数に渡すことが可能となっているのです。

もし複数の profile を使いこなされている場合は、引数の JSON ファイルを使い分けることで、それぞれの profile のための API Key や API Token を簡単に入手できます。

cred1="$( soracom auth --body @"$HOME/.soracom/profile1.json" )"
cred2="$( soracom auth --body @"$HOME/.soracom/profile2.json" )"

soracom-cli が実際に送受信しているデータを盗み見たい

何らかの理由で唐突に soracom-cli がやり取りしている HTTP リクエスト・レスポンスの中身を見たくなることって、よくありますよね。

レスポンスは大抵の場合は soracom-cli が表示する JSON そのものですから、主に見たいと思うのはリクエストのヘッダーやボディ、それにレスポンスのヘッダーなどかと思います。

たとえば「自分が指定したパラメータは期待したとおりに送信されているのだろうか?」とか「この API を自分のプログラムから呼び出してみたいけど、パラメータの指定の仕方がよくわからないなぁ。soracom-cli はどうやって送っているのだろう?」といったような場合です。

そんなときはとても簡単です。

SORACOM_VERBOSE=1 soracom sims list

このように SORACOM_VERBOSE 環境変数に何らかの値を設定してあげればよいのです。 (上の例では 1 を指定していますが、今のところこの値に特に意味はありません。この環境変数が空でなければ verbose な出力が得られます。)

これを付けた状態で実行してみると、以下のような出力が得られると思います。(センシティブな情報は (snip) で置き換えてあります)

$ SORACOM_VERBOSE=1 soracom sims list
POST /v1/auth? HTTP/1.1
Host: api.soracom.io
Content-Type: application/json
User-Agent: soracom-cli/0.10.4
X-Soracom-Lang: en

{"email":"(snip)","password":"(snip)"}
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Cache-Control: no-cache
Connection: keep-alive
Content-Type: application/json
Date: Wed, 01 Dec 2021 06:46:59 GMT
X-Soracom-Cli-Version: v0.10.4


==========
GET /v1/sims? HTTP/1.1
Host: api.soracom.io
User-Agent: soracom-cli/0.10.4
X-Soracom-Api-Key: (snip)
X-Soracom-Lang: en
X-Soracom-Token: (snip)


HTTP/1.1 200 OK
Cache-Control: no-cache
Connection: keep-alive
Content-Type: application/json
Date: Wed, 01 Dec 2021 06:46:59 GMT
Vary: Accept-Encoding
X-Soracom-Cli-Version: v0.10.4


==========
[
        {
                "activeProfileId": "(snip)",
                "capabilities": {
                        "data": true,
                        "sms": false
                },
                "createdTime": 1445512349601,
                "expiryAction": null,
                "expiryTime": null,
                "groupId": null,
                ...

これを見ると、以下のようなことがわかります。

  1. まず最初に /v1/auth API が呼び出されていて認証処理が行われています。認証結果は表示されていませんが、実際にはレスポンスとして返って来ています。
  2. 次に /v1/sims API が呼び出されていてその結果が表示されています。/v1/auth API の結果で得られた API Key や API Token がリクエストのヘッダに指定されています。

次に、以下のような引数を渡すようなコマンドを実行してみましょう。どのように API が呼ばれるかがわかります。

$ SORACOM_VERBOSE=1 soracom sims update-speed-class --sim-id 89xxxxxxxxxxxxxxxxx --speed-class s1.4xfast
POST /v1/auth? HTTP/1.1
Host: api.soracom.io
Content-Type: application/json
User-Agent: soracom-cli/0.10.4
X-Soracom-Lang: en

{"email":"(snip)","password":"(snip)"}
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Cache-Control: no-cache
Connection: keep-alive
Content-Type: application/json
Date: Wed, 01 Dec 2021 07:10:42 GMT
X-Soracom-Cli-Version: v0.10.4


==========
POST /v1/sims/89xxxxxxxxxxxxxxxxx/update_speed_class? HTTP/1.1
Host: api.soracom.io
Content-Type: application/json
User-Agent: soracom-cli/0.10.4
X-Soracom-Api-Key: (snip)
X-Soracom-Lang: en
X-Soracom-Token: (snip)

{"speedClass":"s1.4xfast"}
HTTP/1.1 200 OK
Cache-Control: no-cache
Connection: keep-alive
Content-Type: application/json
Date: Wed, 01 Dec 2021 07:10:42 GMT
Vary: Accept-Encoding
X-Soracom-Cli-Version: v0.10.4


==========
{
        "activeProfileId": "(snip)",
        "capabilities": {
                "data": true,
                "sms": false
        },
        "createdTime": 1590234187495,
        "expiryAction": null,
        "expiryTime": null,

        ...
        "speedClass": "s1.4xfast",
        ...

--sim-id で指定した SIM ID は POST 時の path の一部として、--speed-class で指定した速度クラスは body の JSON の中に入ってリクエストとして送られていることがわかりますね。

なお、SORACOM_VERBOSE を指定することで表示されるようになった部分は標準エラー出力 (stderr) に出力されており、API のレスポンスは従来どおり標準出力 (stdout) に出力されていますので、標準エラー出力の方は人間が目で確認しつつ標準出力の方はパイプなどで他のプログラムに渡して処理を行うといったようなこともできるようになっています。(既存のスクリプトデバッグのときなどに役立つかもしれません)

また、レスポンスのヘッダに X-Soracom-Cli-Version が含まれていることにお気づきでしょうか? このヘッダには現在リリースされている最新版の soracom-cli のバージョンが含まれています。 SORACOM API 側は、リクエストの User-Agent を見て soracom-cli からのリクエストのようであると判断した場合このヘッダを返します。 soracom-cli 側はこのヘッダを受け取ったら、自分自身のバージョンと比較して、新しいバージョンがリリースされている場合に以下のようにお知らせしてくれます。

$ soracom subscribers list
The latest version v0.10.4 is released which is newer than current version v0.10.3. Please update to the latest version.
[
        {
                "apn": "soracom.io",
                "createdAt": 1463402658584,
                "createdTime": 1463402658584,
                "expiredAt": null,
                "expiryAction": "doNothing",
                "expiryTime": null,
                ...

日本語環境ですと、以下のように日本語のメッセージになります。

現在お使いのバージョン v0.10.3 より新しい v0.10.4 がリリースされています。アップデートしてください。

このメッセージも標準エラー出力に出力されますので、標準出力を利用している既存のスクリプトを壊してしまうことがないようになっているはずです。

さいごに

いかがでしたでしょうか。今後のみなさまの soracom-cli ライフが捗りそうな情報は一つでもありましたか?

soracom-cliオープンソースとして開発されていますので、フィードバック、リクエスト、いつでも(クリスマスの時期に限らず) お寄せください。

サポートが終了した Runtime を使っている AWS Lambda の関数を見つけるスクリプト

前回の記事にも書きましたが、先月末(2021年4月末)ころに AWS から AWS Lambda の Node.js 10.x Runtime のサポートが終了しますというお知らせが来ていました。

それで仕事や個人で使っている AWS アカウントの Lambda 関数の Runtime をちょっと見てみたところ、Node.js 10.x どころか nodejs8.10 だの nodejs6.10 だの、しまいには無印の nodejs(Node.js のバージョンは 0.10 らしいです)なんかも見つかり、これはいかんということで全アカウントの全リージョンの全 Lambda 関数を棚卸ししてみることにしました。

しかし仕事のアカウントでは Lambda 関数の数は(Runtime のバージョンが問題ないものも合わせて)1,000 をはるかに超えていて、各関数にも複数のバージョンがあります。アカウントもいろいろな用途等に応じて複数の本番環境があったり開発環境があったりなどして複数にまたがっていますし、それら複数のアカウントでそれぞれ複数のリージョンを利用していたりします。

そんなわけで、手作業で調べるのでは時間がいくらあっても足りなさそうです。

ということで AWS CLI の出番です。

AWS からのお知らせに書かれていた、aws lambda list-functions コマンドの例をベースにして改良を加えていった結果、以下のようなスクリプトが出来上がりました。

#!/usr/bin/env bash
set -Eeuo pipefail

profiles=(production staging development) # replace this with your aws account profiles

outdated_runtimes="$( curl -s https://docs.aws.amazon.com/lambda/latest/dg/runtime-support-policy.html 2>&1 | sed -n 's|.*<code class="code">\([^<]*\)</code>.*|\1|gp')"
select_outdated_runtime="$( echo "$outdated_runtimes" | sed 's/^\(.*\)$/"\1"/' | sed 's/^/.Runtime == /g' | sed ':a; N; $!ba; s/\n/ or /g' )"

for profile in "${profiles[@]}"; do
  regions="$( aws ec2 describe-regions --query "Regions[*].[RegionName]" --output text --profile "$profile" --region ap-northeast-1 )"
  for region in $regions; do
    >&2 echo -e "\\033[1;31m$profile - $region\\033[0m"
    functions="$( aws lambda list-functions --function-version ALL --profile "$profile" --region "$region" | jq -r '.Functions[] | select('"$select_outdated_runtime"') | [.Runtime, .FunctionName] | @csv' | sort | uniq )"
    for fn in $functions; do
      echo "$profile,$region,$fn"
    done
  done
done

実行には aws jq curl などのコマンドが必要です。

手元の Ubuntu 20.04 では動作していますが、mac だと sed のあたりの挙動の違いが原因で期待通りに動かない可能性があります。

このスクリプトをお使いになる際には 4 行目の profiles のカッコの中をご自身のご利用になっている AWS アカウントのプロファイル名(aws configure --profile <profile> で指定したプロファイル名)に置き換えてください。

「プロファイルってなんのこと?」という方はおそらく profiles=(default) とかすると良いかと思います。

このスクリプトを実行すると、以下のような感じでプロファイル名、リージョン名、ランタイム名、関数名を CSV 形式で出力してくれます。(進捗状況を示すために stderr に表示している プロファイル名 - リージョン名 は省略)

production,ap-northeast-1,nodejs10.x,hoge-fuga-func
production,ap-northeast-1,nodejs8.10,foo-bar-func
production,us-west-2,python2.7,test
development,ap-northeast-1,nodejs,hello-world

.csv な拡張子を持つファイルに結果をリダイレクトして保存すると、それを Excel などで開いて閲覧・編集ができます。

解説

半年後の自分のために備忘録的に一応解説を書き残しておきます。

1〜2 行目はボイラープレートというか、bash スクリプトを書くときのお決まりの書き出し部分ですね。 詳しいことは以下の記事を参考にするとよいのではないでしょうか。

betterdev.blog

4 行目の profiles= の行は先ほど書いたとおり AWS アカウントのプロファイル名を配列として列挙します。ここに書いた各プロファイルに対して Lambda 関数をチェックしていきます。

6 行目では、AWS の以下のサイトをスクレイピングして、サポートが終了した(もしくは近いうちに終了する)ランタイムの一覧を取得しています。(もっと良い方法があったらお知らせください)

docs.aws.amazon.com

なおこの方法は同僚の Jed が教えてくれました。jed++

もし上記サイトの URL や内容が変化してうまくスクレイピングできなくなったら

outdated_runtimes="python2.7
ruby2.5
      :
   (省略)
      :
dotnetcore1.0"

のように手で直接書くようにしてみてください。

6 行目で取得したランタイムの一覧は以下のような感じになります。

python2.7
ruby2.5
nodejs10.x
nodejs8.10
nodejs6.10
nodejs4.3-edge
nodejs4.3
nodejs
dotnetcore2.0
dotnetcore1.0

これを 7 行目で加工して、以下のような文字列に変換しています。

.Runtime == "python2.7" or .Runtime == "ruby2.5" or .Runtime == "nodejs10.x" or .Runtime == "nodejs8.10" or .Runtime == "nodejs6.10" or .Runtime == "nodejs4.3-edge" or .Runtime == "nodejs4.3" or .Runtime == "nodejs" or .Runtime == "dotnetcore2.0" or .Runtime == "dotnetcore1.0"

これはあとで jqselect で対象の Runtime を見つけるのに使っています。

9 行目から各プロファイルに対してループを開始します。

10 行目では、そのプロファイルで利用可能なリージョンの一覧を取得します。

11 行目から各リージョンに対するループを開始します。

12 行目は進捗状況を stderr(標準エラー出力)に出力しています。

13 行目は aws コマンドで関数の一覧を取得し、jq でサポート切れの Runtime のみを抽出して、その Runtime と関数名を CSV 形式で出力しています。aws lambda list-functions--function-version ALL を指定しているので、同じ関数の過去のバージョンもすべてチェックしますが、そのせいでバージョンが違うだけの同じ名前の関数が複数出力されます。それを | sort | uniq で重複排除しています。

14〜16 行目では、関数の一覧にプロファイル名とリージョン名も付けて CSV 形式で出力しています。

余談

私は以前 Go で Lambda のコードを書くために apex を利用していました。現在は Go の Runtime が AWS から公式に提供されていますが、数年前は Go の Runtime が無かったので Node.js を Runtime として用いて Node.js のコードから Go のプログラムを起動するというような方法で実行していました。apex はそのあたりをうまいこと隠蔽してくれていたので使っていたという背景があります。

Go のRuntime が正式にサポートされるようになってからは apex も Go の Runtime を直接使ってくれるようになりましたが、過去に Node.js ランタイムを使ってアップロードされたバージョンはそのまま残っていました。

今回検出された古い Node.js ランタイムの大部分はそのような経緯で作られたもので、すでに使われていない(Go Runtime に移行済みの)関数のバージョンばかりでした。

なお今は apex の開発が停止してしまったのでいろいろな代替ツールを試しましたが、私は個人的には AWS CDK に落ち着いています。

AWS CDK で Lambda 関数を作ったときに Log の Retention Period を指定したらちょっと面白かった話

先日、AWS CDK を使ってとある Lambda 関数をデプロイしました。 その Lambda 関数は Go で書かれたものですが、動作に必要な S3 や DynamoDB などのリソースを準備したり IAM Role に適切な Policy を設定してそれを使うようにしたり Lambda 関数自体のメモリサイズやタイムアウト時間などいろいろなパラメータを指定しなければなりません。しかもこの関数を開発用の AWS アカウントと本番環境用の AWS アカウント両方に作らなければなりません。このような場合には CDK のような IaC (Infrastructure as Code) の実現を支援してくれる仕組みはありがたいですね。

で、今回構築した Lambda の関数名を仮に FooFunc とでもしておきましょう。

CDK のコードは TypeScript で書きました。 lambda.Function コンストラクトを用いて以下のように構築しました。

    const fn = new lambda.Function(this, 'FooFunc', {
      functionName: 'FooFunc',
      runtime: lambda.Runtime.GO_1_X,
      handler: 'foo',
      code: lambda.Code.fromAsset('./foo.zip'),
      role: role,
      memorySize: 1024,
      timeout: cdk.Duration.seconds(30),
      logRetention: cwlogs.RetentionDays.ONE_WEEK,
      currentVersionOptions: {
        removalPolicy: cdk.RemovalPolicy.RETAIN,
      },
    });

これでデプロイすると FooFunc が出来上がっていて期待通りに動きました。

Lambda のログの保持期間 (logRetention) を 1 週間に指定してあります。 これを忘れると CloudWatch Logs に永遠にログが残り続けて地味に費用がかさんでしまうので、この設定をしっかりと行っておくのがよいですね。

で、この連休中に AWS からお知らせが来て、Node.js 10.x の Runtime はサポート終了ですよ、とのこと。4/30 に Node.js の本家が 10.x のサポートを終了したのに合わせ、AWS も 7 月末でサポートを終了するのでそれまでにより新しいバージョンに移行するように、とのことでした。

お知らせにはご丁寧に aws cli で Node.js 10.x の Runtime を使っている Lambda 関数の一覧を出力するためのコマンドラインまで書かれていましたのでそれを実行してみました。

すると、先日作ったばかりの FooFunc の名前が見つかったのです。 FooFunc の Runtime は Go のはずなのにおかしいな、と思いました。

Node.js 10.x を使っているとして見つかった関数の名前は、正確には FooFunc_LogRetention${UniqueID} のような長い名前でした。${UniqueID} の部分はランダムな英数字の 30 文字くらいの羅列です。長いので LogRetention 関数と呼ぶことにします。

自分ではそのような関数を作った覚えはありませんが、CloudFormation の Stack を見るとたしかに FooFunc の仲間です。

名前からすると Log の Retention Period を設定するための関数のようです。 コードを読んでみてもやはりそのような処理の内容が書かれていました。

で、この LogRetention 関数の Runtime が Node.js 10.x だったわけです。

僕が使っていた CDK のバージョンがちょっと古かったためでしょうか。すぐに CDK のバージョンを更新しました。

LogRetention 関数の Runtime のバージョンを試しに手動で最新の 14.x に上げてみて、その後に CDK で FooFunc をデプロイしてみましたが、Node の Runtime バージョンが巻き戻ったりすることもなく、logRetention の設定もきちんと反映されましたのでとりあえずはこの状態で運用していこうと思います。

Rust で書いたコードを AWS Lambda で動かす

Mac でクロスコンパイルしたバイナリを Lambda で動かすという記事はあったのですが、Linux でビルドして Lambda で動かすという記事が見当たらなかったのと、それなりに躓いたのでメモを残しておきます。

Rust 自体はインストールできてるとして、大まかな手順としては、

  1. rustup で target x86_64-linux-unknown-musl を追加する
  2. musl-gcc をインストールする
  3. cargo でビルド
  4. Lambda にデプロイ

ではそれぞれ詳細な手順をば。

1. rustup で target x86_64-linux-unknown-musl を追加する

これは以下のコマンドを実行するだけですね。

rustup target add x86_64-linux-unknown-musl

2. musl-gcc をインストールする

Mac だと brew で、Ubuntu だと apt で入れられるっぽいのですが、私の使っている Amazon Linux だと yum で入れる方法がわからなかったのでソースからビルドしました。

GitHub - richfelker/musl-cross-make: Simple makefile-based build for musl cross compiler

こちらのリポジトリをクローンしてきて、

TARGET=x86_64-linux-musl make

これにはしばらく時間がかかります。

ビルドが完了したら

make install

これで output ディレクトリ以下に成果物ができます。

成果物を適当なディレクトリにコピーします。

sudo cp -f output/* /usr

cargo でビルド

AWS Lambda で動く Rust のコードは AWS のオフィシャルなブログでサンプルコードが公開 されています。

まずはこちらのコマンドを実行して雛形を作っていきます。

cargo new my_lambda_function --bin

記事に書かれているように Cargo.toml[dependencies] セクションの内容を追加していきます。

[package] セクションに autobins = false を追加するのを忘れずに。

[[bin]] セクションも追加します。

.cargo ディレクトリを作成し、その下に config というファイルを作って以下のような内容を書きます

[target.x86_64-unknown-linux-musl]
linker = "x86_64-linux-musl-gcc"

そしてこれをビルドします。

CC_x86_64_unknown_linux_musl="x86_64-linux-musl-gcc" cargo build --release --target x86_64-unknown-linux-musl

ビルドが成功すると、以下の場所にファイルができています。

$ file target/x86_64-unknown-linux-musl/release/bootstrap
target/x86_64-unknown-linux-musl/release/bootstrap: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped

static link されていますね。

これを zip します。

zip -j rust.zip ./target/x86_64-unknown-linux-musl/release/bootstrap

4. Lambda にデプロイ

AWS の管理コンソールにログインして Lambda の関数を新しく作ります。

Auth from scratch を選択して Runtime は Provide your own bootstrap を選択します。

Function code で Upload a .zip file を選択して先程作成した rust.zip をアップロードします。

Save を押します。

以下のようなテストデータを作成してテストを実行してみましょう。

{
  "firstName": "Takashi"
}

実行が成功して以下のようなレスポンスが得られたら成功です!

{
  "message": "Hello, Takashi!"
}

Go 製 WebAssembly ホスト環境パッケージ wax のご紹介

みなさんこんにちは。

これは Go2 Advent Calendar 2019 の 12 月 24 日のための記事です。(少しフライング気味に投稿しておきます)

今日は個人的な趣味で作っている wax というライブラリのご紹介をしたいと思います。

3 行で

  1. WebAssembly のホスト環境(実行環境、ランタイムともいう)を Go で書いてみました。
  2. なので、WebAssembly だけど Web ブラウザは出てきません。フィボナッチ数の計算とかはできます。
  3. エミュレータを作ってるみたいで楽しいけど、一人で開発するのは辛いので仲間募集中です!

WebAssembly のホスト環境って?

WebAssembly というと、Go や Rust などの言語で書いたプログラムを Web ブラウザの上で実行させることができるということを思い浮かべられる方が多いと思います。それは WebAssembly の本来の目的というか、典型的な使い方だと思います。私はそういうメジャー路線にはいつも乗りそこねてしまうタイプであり、このところの WebAssembly のブームに関しても私はそこまで興味を持っていなかったというのが本当のところでした(「asm.js がバイナリになったんでしょー」(←多分違うw)とか「ブラウザで 3D ゴリゴリなゲームとかプレイしたい/作りたい人のためのものでしょー」くらいの認識でした)。

ところがある日、何かの拍子に WebAssembly の仕様書を拾ってしまったのです。

https://webassembly.github.io/spec/core/_download/WebAssembly.pdf

ちょっと目次などを眺めてみたらこれがまた面白そう。何が面白そうかって .wasm ファイルのバイナリフォーマットが定義されているんです。

バイナリを見るとパースせずにはいられない私としては、実物の .wasm ファイルをパースしてみたくなりました。そんなわけでいろいろな言語で簡単なプログラムを wasm に出力してみて、仕様書とにらめっこしながらバイナリをパースするコードを書き、.wasm ファイルの内容を JSON でダンプするツールなんか書いているうちに、仕様書に命令セットの定義とかが出てきてワクワクしてきてしまい、コードの実行もしたい欲が高まって、気づいたら wax という名前まで考えていました。

wax の wa は WebAssembly、x は execution から取っています。

ググラビリティの低さは懸念ですが、後悔はしていません。

少し話がそれましたが、WebAssembly とはスタックベースの仮想マシンの命令セットとその実行環境・実行方法(セマンティクス)などを定めた仕様ですが、そのバイナリフォーマットは比較的軽量(命令コードは1バイト固定、オペランドはたとえば整数リテラルが可変長なので、小さな整数は 1 バイトで表せるなど)であり、セキュリティを意識した仕様 -- たとえば明示的にスタックを操作する命令がなかったりスタックのメモリ空間がプログラムからさわれる領域にはなかったり(というかそもそもスタックにはメモリのアドレスなどの概念がない)などスタックオーバーフローを利用した攻撃が難しそうなどなど -- になっていたりと、まさに Web のために作られたものである印象を受けました。

で、Web ブラウザ上で実行することはもちろん WebAssembly の主目的だと思うのですが、WebAssembly の仕様書の冒頭には以下のような文章もありました。

1.1 Introduction

WebAssembly (abbreviated Wasm) is a safe, portable, low-level code format designed for efficient execution and compact representation. Its main goal is to enable high performance applications on the Web, but it does not make any Web-specific assumptions or provide Web-specific features, so it can be employed in other environments as well.

訳すると、

「1.1 イントロダクション

WebAssembly(Wasm と省略される)は安全、ポータブル、低レベルのコードフォーマットであり、効率的な実行とコンパクトな表現のためにデザインされた。そのメインのゴールは Web 上でハイパフォーマンスなアプリケーションを可能にすることだが、いかなる Web 固有の仮定をすることもなく Web 固有の機能を提供することもない。そのため他の環境でも同様に用いることができる。」

仕様書の冒頭のこの部分を読んだ時点で、私の WebAssembly に関するそれまでの狭い理解はぶち壊されました。

WebAssembly を自分のプログラムでホストする、すなわち WebAssembly の実行環境を自分のプログラムに組み込むことで、自分のプログラムでプラグインのような任意のサブプログラムを実行できるようになるということだと理解したのです。

たとえば Wireshark では独自の Dissector を Lua で書いて組み込むことができますが、Lua という言語を知らない人には敷居が高くなってしまいます。(Lua はシンプルな言語なので簡単に使えるようになるとは思いますが、それをメンテナンスしたりすることを考えると、たとえばチーム内でよく使われている言語を使いたくなったりすると思います。)

また、もし自分のプログラムのプラグイン機能を提供するとした場合、ユーザーにある特定の言語を利用することを強要するような感じになってしまうのは気が引けるというか、できればユーザーが自分の好きな言語でプラグインを書くことができるととてもよいと思いませんか?

それ(ユーザーが好きな言語でプラグインを書けること)を可能にするのが WebAssembly のもたらすもののうちの一つだと考えています。

WebAssembly の実行環境をホストすることで、自分のプログラムにユーザーのコードを安全に組み込むことができ、かつユーザーは自由な言語を利用できるようになる。

これまでその2つ(安全性の確保と自由な言語の利用)を両立させようとするとおそらく実質的には何らかのコンテナ技術を使うしかなかったと思うのですが、WebAssembly の登場によってより軽量に、さまざまな環境でそれを実現することができるようになると思います。

拙訳:

面白い事実:WebAssembly は Web でもなければアセンブリでもない。ブラウザの外でも実行できる。それは WASI (WebAssembly System Interface) と呼ばれる。とてもクレイジーであると同時にとてもパワフルである。Solomon Hykes (訳注:Docker の創業者兼 CTO)が言っていた:もし 2008 年に WASM と WASI が存在していたら、我々は Docker を作る必要はなかったかも🤯

実際、Envoy Proxy では WebAssembly で書かれた Extension をサポートするというニュース がつい最近ありました。

また CDN の Fastly は CDN の Edge でユーザーの任意のコードを実行できる仕組みとして WebAssembly を実験的に採用しました。(そのエンジンの Lucet は最近 Bytecode Alliance(WebAssembly の規格化団体)に移管されたようです)

今後、そのように、ユーザーのコードを実行することでプラットフォームやソフトウェアの機能拡張をするような場面では WebAssembly の採用がどんどん進んでいくのではないかと思います。

どうやって WebAssembly をホストするの?

wax のパッケージ(Go 言語の意味でのパッケージ)は WebAssembly ホスト環境のライブラリですが、そのパッケージを利用したその名も wax というコマンドも用意しています。

wax コマンドは .wasm ファイル(の中の指定された関数)を実行して結果を表示するプログラムですが、これは wax パッケージの具体的な利用例にもなっています。

基本的には wax.ParseBinaryModule() で .wasm ファイルをパースし、wax.NewRuntime() でランタイムを生成、runtime の FindFuncAddr() で関数を見つけ、同じく runtime の InvokeFunc() でその関数を実行するだけです。

InvokeFunc() の戻り値には、実行された wasm の関数の戻り値が含まれています。

なので、ホスト環境が Go 言語で書かれたプログラムであれば、wax を使うことで簡単に WebAssembly をホストすることができます。

現時点でできること・できないこと

できること:

  • 算術演算(整数型・浮動小数点数型)- 単項演算、二項演算、比較、変換、など
  • 関数呼び出し
  • 分岐(条件分岐、ジャンプ、ループなど)

できないこと:

  • ホスト環境の関数などのインポート
  • WASI などの ABI のサポート
  • 配列や文字列など、プリミティブ型(整数型、浮動小数点数型)以外を引数や戻り値に取る関数の実行 → これはそもそも仕様上無理かもしれません。ホスト環境のメモリ領域を Import してそれを介してやり取りする感じになるのが正解なのかも。というかその方法を定めてるのが WASI とかだと思うのでもっと勉強します

などなど

感覚的には、標準で用意されているテスト の正常系のうち、だいたい 7〜8 割くらいは通ってるかな、という感じです。

なので、まだまだできないことだらけではありますしおそらくバグとかも多いのですが、仕様書と格闘しながら少しずつできることを増やしていってる段階です。

プログラムを wasm 向けにコンパイルして実行してみよう

最近は いろいろな言語が wasm の出力をサポート しています。 今回は、その中から Go と Rust で書いたプログラムを wasm にコンパイルし、それを wax を使って実行してみたいと思います。

Go

みなさんの大好きな Go はもちろん wasm を出力できます。 しかし普通にやると GC や goroutine を含めた Runtime 全体が wasm になってしまい、出力されるバイナリのサイズが全然小さくなく wax で動かせる自身が全くないので、というかそもそも Go が吐き出す wasm のバイナリは JS 環境(ブラウザ)で使われることを前提としたものしか出力できなさそうなので wax ではきっと動かないので、今回の例では TinyGo を使って小さなバイナリを吐き出してみたいと思います。

TinyGo は Go のサブセットに対応したコンパイラで、組み込み向けの軽量なバイナリを出力します。

以下のようなコードを書いて、add.go というファイル名で保存してください。

package main

func main() {
}

//go:export add
func add(a, b int) int {
    return a + b
}

main 関数が空ですが気にしないでください。 普通の Go には無い //go:export という指定があります。 これはこの関数を add という名前で export するという指定です。

ではこのソースを TinyGo でビルドします。今回は Docker で tinygo のコンテナの tinygo コマンドを使います。 add.go をおいたディレクトリで、以下のコマンドを実行します。

docker run --rm -v "$(pwd)":/src tinygo/tinygo:latest tinygo build -target wasm -o /src/add.wasm /src/add.go

これで add.wasm というファイルができるはずですので、次はこれを wax を使って実行します。

go install github.com/bearmini/wax/cmd/wax
wax -f "add" -a "i32:1" -a "i32:2" add.wasm

go instal ~~ で wax コマンドをインストールしています。したがってこれは一度だけ実行すればよく、それ以降は wax コマンドを使えるようになります。($GOPATH/bin に PATH を通しておく必要があります)

wax コマンドの引数は -f で実行する関数の名前を指定しています。 -a は引数の指定です。型(i32)と値( 12)をコロン : でつなげて指定します。負の数や 16 進数でも指定できます。 最後に .wasm ファイル名を指定しています。

これを実行し、結果として以下のように表示されれば成功です!

0:i32:0x00000003 3 3

これは、関数の 0 番目の戻り値1i32 型で、0x00000003 という値だったことを示します。 その後の 2 つの 3 は、0x00000003 を 10 進数で表したもので、符号なしと符号付きでの解釈をそれぞれ示しています。

wax リポジトリには、.wasm ファイルを実行する wax コマンドの他に、.wasm ファイルの内容を JSON 形式で dump する wadump というツールや、.wasm ファイルに含まれるコードを逆アセンブルする wadisasm というツール、デバッグシンボルの情報などを削ぎ落として .wasm ファイルを軽量化 (strip) する wastrip というツールも含まれています。 これらも実行してみましょう。

まずは wadump から。

go install github.com/bearmini/wax/cmd/wadump
wadump add.wasm

すると以下のような結果が表示されるはずです。

{"Preamble":{"MagicNumber":1836278016,"Version":1},"Sections":[{"ID":1,"Size":30,"Content":"BmAAAX9gA39/fwF/YAAAYAF/AGACf38AYAJ/fwF/","FuncTypes":[{"ParamTypes":"","ReturnTypes":"fw=="},{"ParamTypes":"f39/","ReturnTypes":"fw=="},{"ParamTypes":"","ReturnTypes":""},{"ParamTypes":"fw==","ReturnTypes":""},{"ParamTypes":"f38=","ReturnTypes":""},{"ParamTypes":"f38=","ReturnTypes":"fw=="}]},{"ID":2,"Size":42,"Content":"AgNlbnYNaW9fZ2V0X3N0ZG91dAAAA2Vudg5yZXNvdXJjZV93cml0ZQAB","Imports":[{"Mod":"env","Nm":"io_get_stdout","DescType":0,"Desc":0},{"Mod":"env","Nm":"resource_write","DescType":0,"Desc":1}]},{"ID":3,"Size":17,"Content":"EAICAgICAgECAgMEAgUFBQU=","Types":[2,2,2,2,2,2,1,2,2,3,4,2,5,5,5,5]},
...(省略)

続いて wadisasm

go install github.com/bearmini/wax/cmd/wadisasm
wadisasm add.wasm

以下のような逆アセンブル結果が表示されます

func:0
0b end


func:1
10 84 80 80 80 00 call funcidx:00000004
0b                end


func:2
3f 00                   memory.size 0x00
41 10                   i32.const 00000010
74                      i32.shl
41 c6 80 84 80 00       i32.const 00010046
...(省略)

アセンブルリストの中に以下のような関数を見つけることができるでしょうか。

func:12
20 01 local.get 00000001
20 00 local.get 00000000
6a    i32.add
0b    end

func のあとの番号は使った TinyGo のコンパイラのバージョンなどによって変わってくるかもしれませんが、命令列の中に i32.add とありますね。この命令はスタックに積まれた 2 つの 32bit 整数を取り出し、加算を行って結果をスタックに戻す命令です。そしてこの関数こそが Go のソースコードadd() 関数から生成されたWebAssembly のコードです。

wastrip も同様に実行してみてください。実行前と実行後で、ファイルサイズや wadump の結果を比べるとその差が一目瞭然でしょう。

他にも、リポジトリ の examples/go ディレクトリの下にはフィボナッチ数を計算するサンプルコードなどがありますので実行したり逆アセンブルしてみてください。

Rust

最近人気のある Rust も WebAssembly の出力をサポートしています。

以下のようなコードを書いて add.rs というファイル名で保存します。

#[no_mangle]
pub extern fn add(a: u32, b: u32) -> u32 {
  a + b
}

これをコンパイルします。

rustc --crate-type=cdylib --target wasm32-unknown-unknown -O add.rs

これで add.wasm というファイルができるはずです。

実行方法等は Go の方で説明したのと同じですので割愛します。

ほかにも Emscripten を使うと C/C++ から wasm を出力することができると思いますし、他の言語も wasm をサポートするものが増えていると思います。 皆様もいろいろな言語からコンパイルして生成した wasm を実行してみてください。

他のホスト環境との比較

私の知る限りでは、以下のようなホスト環境がすでに世の中に存在するようです。

これらの有名所の実行環境はだいたい LLVM などをバックエンドに使って wasm を JIT もしくは AOT してネイティブコードとして実行するものが多そうです。

wax は今の所 JIT に対応する予定はありません。パフォーマンス的には JIT する実装に比べて見劣りすると思いますが、Pure Go なことで組み込みやすいとか、何かしら wax らしい特徴を出していけたらいいな、と思っています。

それから、GitHub - appcypher/awesome-wasm-runtimes: A list of webassemby runtimes にはその他の実装もたくさん列挙されています。いつかここに wax も載ることができるとうれしいですね。

お仲間募集中

一人でコツコツ開発するのは気ままでいいのですが、仕様をどうやって解釈したらいいのかがわからなさすぎるときなどにモチベーションの維持が難しかったりするのでそういうときに相談できたりするお相手がいるとうれしいな、という気持ちです。 あと、MVP という最小限の仕様はまだ独力でもなんとかなりそうなのですが、スレッドや SIMD のサポートなど MVP 以上の実装をしようと思うとやはり一人では限界がありそうと今から感じています。

もし wax の開発に興味のある方は Twitter @bearmini にぜひお声がけいただければと思います。 Github 上での Issue や PR も大歓迎です~!

ということで、メリークリスマス!🎅


  1. 0 番目の戻り値、ということは1番目・2番め・・・の戻り値もあるのか、ということですが、現時点の WASM の MVP (Minimal Viable Product) では関数の戻り値は1つまで、ということになっています。将来のバージョンでは複数の戻り値を返す関数もサポートされる可能性があります。

SORACOM LTE-M Button powered by AWS(通称 #あのボタン)を使って EC2 のインスタンス代を節約した話

この記事は SORACOM Advent Calendar 2019 ふたつめ の 12 月 3日分です。

はじまり

最近、開発用の PC の調子が悪かったのもあって EC2 インスタンス上にいろいろな開発環境を構築してみています。 そうするとどの PC を使っても同じ開発環境で作業を継続できると思ったからです。

近頃は VS Code のリモート接続拡張を使うことで、あたかもローカルにファイルがあるかのようにリモートホスト上のファイルが編集でき、統合ターミナルを使って自然にリモートホスト上でコマンドを実行できますので、リモート接続していることを忘れてしまうときがあるほどなので、これさえあればだいぶ行けるだろうと思ったのです。

最初はお試しと思って t3.micro インスタンスで始めたものの、自分の書いたプログラムを Fuzzing したいと思ってぶっ通しでプログラムを実行しようとしたら CPU クレジット不足になってしまったので c5.large にインスタンスタイプを変更し、Fuzzing が終わって今度はいろいろテストを実行していたらメモリ不足になってしまったので r5.large に更に変更し、という感じで今に至っています。

ソラコムでは社員の福利厚生(?)として AWS のサービス使い放題というのがあるのですが、貧乏性の私はさすがに r5.large インスタンスを起動しっぱなしはもったいないなぁと感じてしまい、弊社の リーダーシップステートメント にも Avoid Muda とあるように少しでもコストを削減しようと思い、開発業務をしていない間はインスタンスをちゃんと停止しようと考えました。

r5.large インスタンスは Tokyo リージョンでは $0.152/hour (2019年12月現在)ですので、使っていない時間が1日の半分(12時間)あったとして1ヶ月(30日)で $0.152 * 12 * 30 = $54.72 のムダが生じていることになります。(実際には休日などもありますし、1日12時間も働くことはまれですし、もっとムダが生じてしまうことになるはずです。)

しかしながら生来のめんどくさがりである私は、インスタンスを起動したり停止したりするために AWS コンソールにログインして EC2 のページから自分のインスタンスを検索して起動したり停止したりするのは非常に面倒であると思いました。 aws コマンドを使ってコマンドラインで実行するともう少し手間は少ないかもしれませんが。

というわけで、ここはテクノロジーの力で解決しよう!(大げさ)と思って取り出したのがこちらの商品です。

soracom.jp

そう、「AWS ボタン」とか「#あのボタン」と呼ばれているものです。

設計(というほどのものでもない、ただの思いつき)

目論見はこうです。

  1. 仕事を始めるタイミングでボタンを押す。インスタンスが起動する。
  2. 仕事を終えるタイミングでボタンを押す。インスタンスが停止する。

これだけです。 究極にシンプルです。

AWS ボタンは、1回押し(シングルクリック)、2回押し(ダブルクリック)、長押し(ロングクリック)の3タイプの押し方ができますので、これをそれぞれ以下のような機能に割り当てることにしました。

  • シングルクリック:現在のインスタンスの状況(稼働中か、停止中かなど)を Slack のチャンネルに報告
  • ダブルクリック:インスタンスが停止していたら起動。Slack のチャンネルに報告。
  • ロングクリック:インスタンスが起動していたら停止。Slack のチャンネルに報告。

実装

ではここから実装に入っていきます。

AWS IoT 1-Click の設定

まずは AWS IoT 1-Click で AWS ボタンを Claim(登録)して有効化します。 手順などはこちらを参考にしてください。

dev.soracom.io

Lambda で起動される関数を先に作成

今回は Go で Lambda 関数を作成しました。 Gist にコードを上げておきました。これをビルドして ZIP してアップロードします。

https://gist.github.com/bearmini/99e99ead08ed0e8aff39c6379f5aeeae

AWS IoT 1-Click に戻り、プロジェクトを作成

AWS IoT 1-Click で Project と Device template を作成します。

f:id:bearmini:20191203124552p:plain
このような設定をしました

起動する Lambda は先程作ったものを指定します。

プレイスメントの設定

そのボタンで起動/停止したいインスタンスインスタンス ID、リージョン、通知先の Slack の Incoming Webhook URL をプレイスメントで指定します。

実行!

ボタンを押してみます。

まずは恐る恐るシングルクリック。

ボタンの LED がオレンジ色に点滅して10秒ほど待つと緑色に光ります。 すると程なくして Slack に通知がくるはずです。

f:id:bearmini:20191203125817p:plain
シングルクリックした際の通知。現在の状態(running)が通知された。

インスタンスを停止する際は長押しします。

f:id:bearmini:20191203125931p:plain
インスタンス停止時の通知

インスタンスを起動する際はダブルクリックです。

f:id:bearmini:20191203130041p:plain
インスタンス起動時の通知

インスタンスを停止すると、瞬間的に VS Code は画面が暗くなって再接続を試み始めます。

インスタンスを起動すると VS Code は再接続に成功します。画面を一度リロードする必要がありますが、それ以降は何事もなかったかのように作業を継続できます。(開いていたターミナルなどは閉じてしまいますが、screen コマンドなどでセッションを維持するようにすればそれも問題なく継続できるでしょう)

おわりに

というわけで、ちょっとの投資とちょっとの工夫、そしてちょっとの実装でこんなに簡単に節約ができました。

みなさんも、何かアイディアを思いついたら、Just Do It! してみましょう。

Hyper の Extension の作り始め方 - とくにデバッグの仕方

Windows に乗り換えてからターミナルソフトとして何を使うかをずっと悩んでいます。 Mac のときはずっと iTerm2 一択で特に不自由なく使えていたのですが、Windows 用となると Windows Terminal なんかも気になりますが、現時点(2019年6月中旬時点)では GitHub の README

The Windows Terminal is in the very early alpha stage, and not ready for the general public quite yet.

と書かれているので、興味はありますが仕事で使うにはちょっとまだやめておいたほうが良さそうです。

ということで、ConEmu、ConsoleZ、MobaXterm をインストールしてしばらく試用してみました。それらの中では MobaXterm が一番しっくり来たのですが、一つだけどうしても欲しい機能が見つけられませんでした。 その機能は iTerm2 にはあってとても便利につかっていたのですが、それはコンソールに出力された文字列のパターンに反応して任意のコマンドを実行する、というものです。

そんな機能何に使うの!?というツッコミは甘んじて受けますが、これがないと仕事の効率がめちゃめちゃ下がるんです。

なのでもし拡張機能とかが自分で作れるなら、そのくらい作るんだけどなーと思っていました。

そんなときにふと同僚が Hyper を使っているのを見て、私も Mac 版は少し触ったことはあったのですが Windows 版もあるということを知って少し試してみました。

Hyper は標準のままでは私の欲しい上記の機能は存在していませんし、既存の Extension にもそのようなものは存在しないようですが、Extension を自分で作れるという点が他の Terminal と違うところです。

というわけで Extension を作ってみました。

前置きが長くなりましたが、例によっていろんな罠にハマりましたのでその時の試行錯誤の記録を残しておきます。

1. 準備

まず当然 Hyper をインストールします。インストール方法は割愛(公式からダウンロードするだけです)。

自作のプラグインはいきなり npm パッケージにして公開するわけにも行きませんから、Local plugin という形で開発しますので、その準備をします。

Windows の場合は %USERPROFILE%\AppData\Roaming\Hyper\.hyper_plugins\local の下に好きな名前でフォルダをひとつ作ります。 その下に index.js と package.json を作ります。

index.js にはプラグインのコード本体を書いていきます。まずは以下のような内容のボイラープレートを書いておきましょう。

console.log('hello');
exports.middleware = (store) => (next) => (action) => {
  console.log(action);
  next(action);
}

package.json は普通に name とか version とかを書いておきます。

hyper の Preferences を編集します。メニューから Edit > Preferences と選択するか、Ctrl+, を押すか、%USERPROFILE%\AppData\Roaming\Hyper\.hyper.js を直接編集して、一番下の方にある localPlugins の配列に先程作ったフォルダの名前(フォルダ名のみ)を指定して保存します。

2. Hyper から Hyper を起動してプラグインを開発・デバッグ

Hyper プラグインデバッグの仕方を誰も書いてくれていなかったのでかなり試行錯誤しましたが、プラグイン開発時は以下のようにすると良さそうです。

  1. まずは普通に Hyper を起動します。(スタートメニューとかから)
  2. 起動した Hyper のターミナルから、hyper コマンドでもう一つ Hyper を起動します。予め %USERPROFILE%\AppData\Local\hyper\app-x.y.z\resources\bin (x.y.z は適宜環境に合わせてください)に PATH を通しておく必要があるかもしれません。 最初に起動した方の Hyper を親 Hyper、親から起動した方を子 Hyper と呼ぶことにします。
  3. 親 Hyper にはプラグインがロードされたとかのメッセージが表示されます。これは子 Hyper 側でロードされたプラグインの情報です。プラグインに syntax error があったりするとここに表示されます。index.js に書いた、console.log('hello'); もここに出力されます。
  4. 子 Hyper 側で Developer tools を開きます。Ctrl+Shift+I で開くか、メニューから選択します。Hyper から呼ばれるプラグインの関数で console.log() するとこちらのコンソールに出力されます。console.log(action); の内容はこちらに出力されます。
  5. index.js を書き換えたら、子 Hyper で Ctrl+Shift+U を押して Plugin を更新します。親 Hyper にエラーなどが出力されていないことを確認したら、子 Hyper で Ctrl+Shift+R を押してリロードします。これで開発中のプラグインの新しいバージョンがロードされた状態になりますので動作確認等を行うことができます。

3. あとは自分の作りたい機能をバリバリ実装

見た目を変えたい場合は decorateXxx 系の関数を実装して export、コンソール上でやり取りされる文字列のデータが欲しいときは middleware() を実装して export という感じかと思います。 middleware() 内では基本的には 引数の action.type に応じて switch していろいろな処理を実装することになると思います。 表示する文字列をどうこうしたいときは SESSION_ADD_DATA など。 プラグイン固有の設定は .hyper.js の config 直下に プラグイン名 のキーを作ってそこに入れておき、ソース側では middleware() の action.type が CONFIG_LOAD もしくは CONFIG_RELOAD のときに action.config から読み取っておくと良いようです。 基本的には middleware() 関数を抜ける前に next(action) を呼び出します。state をいじってべつの action を起動することもできそう。

ここから先は検索すればいろいろ情報が出てきますし、公開されているプラグインなどのコードを参考にすれば大体のことはわかるのではないでしょうか。

我ながら説明が雑ですねw

WSL で Apex を使って Go の Lambda を作ったらエラーになった件とその対策

最近仕事用のメインマシンを数年ぶりに Windows にしたので WSL を使っていろいろとハマっています。

この記事の件もそのうちの一つです。

表題の通り、WSL 環境で apex を使って Go の Lambda をデプロイしたところ、デプロイ自体は問題なくできたのですが、その Lambda を実行すると以下のようなエラーが起こるようになってしまいました。

  ⨯ Error: function response: fork/exec /var/task/main: no such file or directory

どうやらデプロイされた実行ファイル(/var/task/main)が見当たらないというエラーのようです。 しかし、apex build で生成された ZIP ファイルの中身を見てみるとちゃんとファイルが存在しています。

上記のエラーメッセージをもとにネットを検索してみたところ、どうやら dynamic link されたバイナリだと、ビルド時の環境によっては実行できないことがあるようです。

解決策としては、static link するようにするとよいということです。

apex を使っているとコンパイルオプションなど気にしたことがありませんでしたが、project.json (もしくは function.json)に以下のような設定を追加することで static link 用のコンパイルオプションを指定できます。

  "hooks": {
    "build": "GOOS=linux GOARCH=amd64 go build -a -tags netgo -installsuffix netgo -ldflags='-extldflags=\"-static\"' -o main",
    "clean": "rm -f main"
  }

go build の後ろの -a -tags netgo -installsuffix netgo -ldflags='-extldflags=\"-static\"' が全て static link のために必要なオプションです。

WSL に限らず、alpine などでビルドしたバイナリも同様の問題が起こるようですので、これで直るのではないかと思います。

なお、Mac など従来このオプション指定なしでビルドしても動いていたような環境で上記オプションをつけてビルドしても問題なく static link になるようなので、私の手元では全て上記オプション指定に変更しました。

Slack アプリのフォントを変更する方法

[2019.07.22 追記] Slack 4.0.0 以降、ファイルの配置と適用方法が変更になりました。 以下の手順の実行には、Node.js と npx、asar というパッケージが必要です。 Node.js がインストールされている環境であれば npm i -g npx asar でインストールできます。

  1. Slack を閉じる

  2. %LocalAppData%\slack\app-x.y.z\resources\ ディレクトリに移動

  3. sudo npx asar extract app.asar app.asar.unpacked コマンドでアプリケーションパッケージを展開

  4. app.asar.unpacked\dist\ssb-interop.bundle.js というファイルを編集し、以下の内容を末尾に追加

document.addEventListener('DOMContentLoaded', function() {
        let s = document.createElement('style');
        s.type = 'text/css';
        s.innerHTML = '*{font-family:"Slack-Lato", "BIZ UDPGothic", appleLogo !important;}';
        document.head.appendChild(s);
});
  1. sudo npx asar pack app.asar.unpacked app.asar コマンドでアプリケーションパッケージを作成し直し

  2. Slack を起動

[2019.07.22 追記終わり]

Windows 10 の英語版を使っていると、Slack のアプリのフォントがいわゆる「中華フォント」状態になってしまいます。 Slack アプリの設定にはフォントを変更する機能は無いようなのですが、Slack アプリは Electron 製ということで CSS を無理やり適用することができるようです。

%LocalAppData%\slack\app-x.y.z\resources\app.asar.unpacked\src\static\ssb-interop.js を編集して、末尾に以下のような内容を追加します。

onload = function() {
        $("<style></style>").appendTo("head").html("*{font-family:'Slack-Lato', 'BIZ UDPGothic', appleLogo !important;}")
};

Slack-Lato は Slack の標準のフォントです。英数字などは標準のままのほうが違和感が少ないため、Slack-Lato をまず指定します。

次に指定しているのは、日本語表示用のフォントです。Windows に最初から入っていたフォントをいろいろ試してみて一番しっくり来たものを指定しました。

アプリではなくブラウザで Slack にアクセスした場合のフォント設定は font-family: NotoSansJP,Slack-Lato,appleLogo,sans-serif; となっているようですので、こちらに従ってみるのも良いかもしれません。

Slack のアプリのバージョンが上がるとデフォルトに戻ってしまいそうなので、自分用のメモでした。