kbigwheelのプログラミング・ソフトウェア技術系ブログ

プログラミング・ソフトウェア技術系のことを書きます

Universal Keymapシリーズ③ 自作キーボードの Raise / Lower / Adjust レイヤーの起源と推奨される使い方の調査

tl;dr

  • Raise / Lower / Adjustの起源はPlanck, 引いてはその製作者のjackhumbert (Jack Humbert)
  • Raise / LowerにはXXXを置く、といった方向性はないため自由に使ってOK
    • 強いて言えば本家Planckに合わせてRaiseに数字キー・ファンクションキー、Lowerに記号・算術演算キーを置くケースが多いかも(私見)
  • Adjustに関してはキーボードのLEDの調整などメタな機能や滅多に使わない機能を置くのが基本

目次

レイヤーとは

自作キーボードのキーマップではよくレイヤーという機能を使用します。

Layers - QMK

レイヤーとは一般的なキーボードのShiftやCtrlなどと同様に特定のキーを押しながら別の、例えばJキーを押した場合、本来のJキーの役割ではなく左矢印キーを押したことにする、といったことができる機能です。 さながらキーボードの上へ全く別の層を敷いてすべてのキーの動作を変えてしまうような機能だからレイヤーと呼んでいるのだと思います。

このレイヤー、特にキー数が少ない自作キーボードではほぼ使用が必須の機能です。 例えばNomu30 という自作キーボードを設計した – recompile.netなどのキーボードはキー数が31個しかないため、そのままでは数字キーはおろか矢印キーすら押せません。 これほどキー数が少ないものでなくとも、80%キー = いわゆるテンキーレス未満のサイズではファンクションキーなどに専用のキーを割り当てられないためレイヤーが必要となります。

レイヤーの呼び名

このレイヤー機能、qmk_firmware上は単なる定数で0から1, 2, ...と振られるだけの数です。 一方で人間が使いやすいように大抵の場合レイヤーには適当な名前がつけられることが多いです。 以下は僕が調査した中で見たレイヤー名の一例です。

  • Function
  • Arrow
  • Dvorak
  • Numpad
  • Raise
  • Lower
  • Adjust

これらの中でも準標準の呼び名となっているのが Raise / Lower / Adjust の3つです。これらはよく使われるため、カスタムキーキャップの中でもこのRaise / Lowerキーを特別に用意しているものが一定数あります(Adjustは大抵RaiseとLowerの同時押しで表現するため専用のキーはありません)。

ここで一つ疑問が浮かびます。これらのレイヤーの呼称はだれが決めたのでしょうか?またこれらのレイヤーそれぞれに置くキーの方針などはあるのでしょうか?

Raise / Lower / Adjust レイヤーの起源

ネットで検索してみたところ、私と全く同じ疑問を持った人がIssueを立てていました。

Raise? Lower? Adjust? · Issue #3533 · qmk/qmk_firmware

そこでのコメントいわく、これらのレイヤーはPlanck, Preonicなどの開発者のJackさんが開発したものだそうです。 しかし、それはいつ開発されたのでしょうか?またRaise / Lower / Adjust はそれぞれ指向性を持ったレイヤーとして準備されたものなのでしょうか?(例えばUpperには数字キーを置く、など)

この疑問の回答を探して、git logの海へ潜ることにしました。

qmk_firmwareリポジトリを掘り起こす

Gitの良いところは過去全てのコミットを簡単に、かつ高速で検索できることです。 Raise / Lower / Adjustというレイヤーが出てきたコミットも簡単に検索することができます(クエリが間違っていなければ)。 実際に検索してみたところ、すぐにそれぞれのレイヤーが出てきた最初のコミットが見つかりました。

開発されたレイヤー コミット コミッター 日付
Raise / Lower Update keymap_planck.c · qmk/qmk_firmware@4a8b9b8 jackhumbert (Jack Humbert) 2014/12/13
Adjust new defaults · qmk/qmk_firmware@ddbe430 jackhumbert (Jack Humbert) 2016/04/15

両方ともスレッド内で推測されていたとおりJackさんによって行われていました。 また新たにわかったこととして、これらのレイヤーはすべてPlanckのために開発されていたことが挙げられます。

これでqmk_firmware内でのRaise / Lower / Adjustレイヤーの源流はわかりました。 また、qmk_firmwareリポジトリ外で先にこれらのアイデアがあった可能性を考慮してgoogleで2014/12/13以前などを指定して検索しましたが、それに該当する情報はまったく出てきませんでした。

これらを勘案すると少なくともこのレイヤーの命名をモダンキーボードの世界へ導入したのがJackさんであることはほぼ確実だと思われます。

Raise / Lower / Adjustレイヤーの推奨される使い方

これでこれらレイヤーの源流はわかりました。 残る疑問はこれらのレイヤーに推奨される使い方・キー配置があるかどうかです。

Raise? Lower? Adjust? · Issue #3533 · qmk/qmk_firmwareのコメントではRaise / Lower / Adjustに何を置くのかは自由で、ただしAdjustについては入力時にRaiseキーとLowerキーの両方を入力する手間があるため「ワンタッチ」キー(オン/オフ、レイヤー選択など)に適しているとコメントされています。 実際にJackさんが最初にそれらのレイヤーを導入したPlanckにおいて、どのようにレイヤー内のキーが設定されたのかを見ていきましょう。

まず、Raise / Lowerがコミットされた2014年12月の時点では以下のようになっています。

[0] = KEYMAP( /* Jack */
  TAB,  Q,    W,    E,    R,    T,    Y,    U,    I,    O,    P,    BSPC,
  ESC,  A,    S,    D,    F,    G,    H,    J,    K,    L,    SCLN, QUOT,
  LSFT, Z,    X,    C,    V,    B,    N,    M,    COMM, DOT,  SLSH, ENT,
  RSFT, LCTL, LALT, LGUI, FN2,    SPC,     FN1, LEFT, DOWN, UP,  RGHT),
[1] = KEYMAP( /* Jack RAISE */
  GRV,  1,    2,    3,    4,    5,    6,    7,    8,    9,    0,    BSPC,
  TRNS, TRNS, TRNS, TRNS, TRNS, TRNS, TRNS, MINS, EQL,  LBRC, RBRC, BSLS,
  TRNS, TRNS, TRNS, TRNS, TRNS, TRNS, TRNS, TRNS, TRNS, TRNS, MINS, TRNS,
  TRNS, TRNS, TRNS, TRNS, TRNS,    TRNS,   FN1,  MNXT, VOLD, VOLU, MPLY),
[2] = KEYMAP( /* Jack LOWER */
  FN22, FN10, FN11, FN12, FN13, FN14, FN15, FN16, FN17, FN18, FN19, BSPC,
  TRNS, TRNS, TRNS, TRNS, TRNS, TRNS, TRNS, FN20, FN21, FN23, FN24, FN28,
  TRNS, TRNS, TRNS, TRNS, TRNS, TRNS, TRNS, TRNS, TRNS, TRNS, TRNS, TRNS,
  TRNS, TRNS, TRNS, TRNS, FN2,    TRNS,    TRNS, MNXT, VOLD, VOLU, MPLY),

Raiseレイヤーでは数字キーとボリューム上下、メディア再生などのキーが乱雑に置かれています。 Lowerレイヤーにはファンクションキーの10 ~ 28がこちらも並べられています。 ただ、ファンクションキーの3 ~ 9はどこにもないことなどからもまだまだ作業中であることが伺えます。

2015年の1月になると大分まとまっています。が、ファイルタイトルを見るにこれjackさん自身のキーマップやdefaultのキーマップではなくnathanさんのもののようです。配置もthey固有の特徴(Visual Studio用の配置)などが見られて個性的。特に指向性などは見れません。

   /* 1: fn left/lower layer
    * The top row are Visual Studio combos:
    *   'Run', 'Breakpoint', 'Step over', 'Step into', 'Set cursor to line'
    * 2nd row are key combos:
    *   'ctrl-alt-delete', 'ctrl-shift-escape' 
    * 3rd row are macros keys:
    *   'P0' - 'P5' execute a script on Windows machines 
    * ,-----------------------------------------------------------------------.
    * | ESC | F5   | F9 | F10 | F11 |S+F11|CSF10|NLock|Num7 |Num8 |Num9 | Del |
    * |-----------------------------------------------------------------------|
    * |     |C/A/D|C/S/E| Ins |Print|Pause|SLock|Num0 |Num4 |Num5 |Num6 |Num= |
    * |-----------------------------------------------------------------------|
    * |     | P0  | P1  | P2  | P3  | P4  | P5  |Num. |Num1 |Num2 |Num3 |Num/ |
    * |-----------------------------------------------------------------------|
    * |     |User |     |     |     |     |     |     |Home |PgDn |PgUp | End |
    * `-----------------------------------------------------------------------'
    * 2: fn right/raise layer
    * ,-----------------------------------------------------------------------.
    * | F1  | F2  | F3  | F4  |F5   | F6  | F7  | F8  | F9  | F10 | F11 | F12 |
    * |-----------------------------------------------------------------------|
    * |     | !   | @   | #   | $   | %   | ^   | &   | *   | -   | +   | =   |
    * |-----------------------------------------------------------------------|
    * |     | _   | '   | "   | `   | ~   | ,   | .   | ]   | )   | }   | >   |
    * |-----------------------------------------------------------------------|
    * |     |NextT|PrevT|     |     |     | Esc |     |Mute |Vol- |Vol+ | P/P |
    * `-----------------------------------------------------------------------'
    */

Create keymap_matthew.c · qmk/qmk_firmware@1e0ae293 2015年1月には他のキーボードのキーマップにもraise / lowerのアイデアが輸出されています。

引き続き徐々にRaise / Lowerのアイデアは広がっていきます。Adding my initial layout · qmk/qmk_firmware@073a6c2ではRaiseは数字とファンクションキー、LowerはColemak配列など割と大胆な割り当てになっています。この時点ですでに指向性はほぼない雰囲気ですね。

一気に飛んで、では最新のPlanck用defaultキーマップはどうなっているかというとこうです。 qmk_firmware/keymap.c at 50835bb13875843cac0236995afe86508744e595 · qmk/qmk_firmware

/* Lower
 * ,-----------------------------------------------------------------------------------.
 * |   ~  |   !  |   @  |   #  |   $  |   %  |   ^  |   &  |   *  |   (  |   )  | Bksp |
 * |------+------+------+------+------+------+------+------+------+------+------+------|
 * | Del  |  F1  |  F2  |  F3  |  F4  |  F5  |  F6  |   _  |   +  |   {  |   }  |  |   |
 * |------+------+------+------+------+------+------+------+------+------+------+------|
 * |      |  F7  |  F8  |  F9  |  F10 |  F11 |  F12 |ISO ~ |ISO | | Home | End  |      |
 * |------+------+------+------+------+------+------+------+------+------+------+------|
 * |      |      |      |      |      |             |      | Next | Vol- | Vol+ | Play |
 * `-----------------------------------------------------------------------------------'
 *
 * Raise
 * ,-----------------------------------------------------------------------------------.
 * |   `  |   1  |   2  |   3  |   4  |   5  |   6  |   7  |   8  |   9  |   0  | Bksp |
 * |------+------+------+------+------+------+------+------+------+------+------+------|
 * | Del  |  F1  |  F2  |  F3  |  F4  |  F5  |  F6  |   -  |   =  |   [  |   ]  |  \   |
 * |------+------+------+------+------+------+------+------+------+------+------+------|
 * |      |  F7  |  F8  |  F9  |  F10 |  F11 |  F12 |ISO # |ISO / |Pg Up |Pg Dn |      |
 * |------+------+------+------+------+------+------+------+------+------+------+------|
 * |      |      |      |      |      |             |      | Next | Vol- | Vol+ | Play |
 * `-----------------------------------------------------------------------------------'
 *
 * Adjust (Lower + Raise)
 *                      v------------------------RGB CONTROL--------------------v
 * ,-----------------------------------------------------------------------------------.
 * |      | Reset|Debug | RGB  |RGBMOD| HUE+ | HUE- | SAT+ | SAT- |BRGTH+|BRGTH-|  Del |
 * |------+------+------+------+------+------+------+------+------+------+------+------|
 * |      |      |MUSmod|Aud on|Audoff|AGnorm|AGswap|Qwerty|Colemk|Dvorak|Plover|      |
 * |------+------+------+------+------+------+------+------+------+------+------+------|
 * |      |Voice-|Voice+|Mus on|Musoff|MIDIon|MIDIof|TermOn|TermOf|      |      |      |
 * |------+------+------+------+------+------+------+------+------+------+------+------|
 * |      |      |      |      |      |             |      |      |      |      |      |
 * `-----------------------------------------------------------------------------------'
 */

LowerとRaiseについては大分変わっています。Adjustについても大きく変わっていますが、ここについてはレイヤーの切り替えやキーボード自体のRGB操作など、Adjust(調整)という名前に沿ってキーが配置されているように見えます。 簡単に他のadjustレイヤーを調べてみたところでも、同傾向があるようでした。

まとめ

というわけで、Raise / Lower / Adjustの起源とそれの推奨される使い方があるのかの調査を行いました。 それらレイヤーの起源はわかったものの、RaiseとLowerレイヤーの使い方は当時からかなりバラバラで特に定まった使い方はないようです。 そもそもRaise / Lowerという名前自体とても汎用的なものなので、どうとでも使えるように、というかどうとでも使えるために誰でも使いよく広まったと考えられるかもしれません。 ただの数字だけのレイヤー1, レイヤー2よりもRaiseレイヤー、Lowerレイヤーという物理的なメタファの意味合いを含んでいたためより多くの人にイメージしやすかったという側面もあると思います。 一方でAdjustレイヤーについてはキーボード自体の設定を変更する目的で普段のタイピング目的とは違う、単なるON/OFFスイッチなどとして使う傾向があるようです。

Universal Keymapシリーズ② 分割型40%キーボード(など)のキーマップを列挙してみる

というわけで、(ダウンロード版)自作キーボードカタログ 2020 - 自キ温泉街販売所 - BOOTHに載っている分割型40%キーボードを中心にキーマップを列挙してみます。

目次

参考にするキーボード一覧

注意書き: 以下では基本的にqmk_firmwareリポジトリ内のキーマップ定義コードとqmk configuratorでの描画先リンクを書きますが、この両者が必ずしも対応しているとは限りません。というのもqmk configurator側の描画用定義はqmk firmwareとは別に管理されており、自動で更新されるような性質でもないためです。

分割型40%

ErgoDashMini
Caravelle-BLE
Claw44
Silverbullet44
Corne(Cherry)
uzu42

分割型60 ~ 80%

基本的には分割型40%をベースに設計しますが、60 ~ 80%のキーボードの中でも比較的有名なものを参考にします。

Fortitude60
Lily58
Ergodox Ez

一体型

基本的に(normal|row) staggeredなキーボードは今後使う予定はなく、treadstone48 のようなSymmetrical Staggeredなもの、分割型ベースで1枚のPCBに作ったbat43など分割型に似たものを使っています。 その他nomu30など、今持っているもの、将来購入したいものなどのキーマップを見ていきます。

Treadstone48
bat43
REVIUNG41
Nomu30

NEXT ACTION

次は上で列挙したキーマップを実際に比較して、最大公約数的なキーマップを探索します。

Universal Keymapシリーズ① 多彩な自作キーボードを使う上でのキーマップの問題

最近自作キーボードにハマっておりいろんなキーボードを作っています。 ただ、キーボードの数が増えるにつれてある問題が浮上してきました。そう、キーマップです。

目次

多数のキーボードを使うときのキーマップの問題

数年前にErgodox Ezを買ったときも結構時間をかけて決めていました( [Ergodox Ez] Apple JIS キーボードに寄せたキーマップを晒す - Qiita)。 ただErgodox Ezは80%キーボードであるため既存のキーボードとの差分は比較的少なくそれほどキー配置にこまることはありませんでした。そのとき私が持っていた非標準なキーボードはErgodoxだけであったこともあります。

しかし、今僕が持っているキーボードは以下です。

  • Ergo系80%
    • Ergodox Ez
  • Ergo系40%
    • Claw44
    • Caravele BLE
  • 一体型40%
    • Bat43
    • treadstone48
  • 一体型30%
    • nomu30

nomu30, Ergodox Ezを除いては概ね40%キーボードではあるものの、親指周辺のキー数、配置などは微妙に違います。また各キーボードのデフォルトのキーマップもLayer0はQWERTY・レイヤー数も3枚(いわゆるRaiseとLower)という点こそ共通であるものの、Layer1, 2は各々かなり異なります。 数字をQWERTY行に横へ並べたもの、QWERTASDFGへ1~0を割り当てたもの、矢印キーをvim風にHJKLへ割り当てたもの、JIKMへ割り当てたものなどなど・・・。これらのキーボードそれぞれのデフォルトキーマップを覚えるのはちょっと現実的ではないでしょう。

そこで、異なる物理キー配置のキーボード間でも一貫したキーマップを作る必要が出てくるわけです。

一貫したキーマップに求められるもの

キーマップが一貫するようにする

直近で持っている自作キーボードは前述の通り分割型40%ないし分割型40%に似たキーボードが多いです。 これに加えてErgoDox Ezという80%キーボードがあります。 これらをほぼ同時期に使うなら、キーマップがある程度揃っていることはほぼ必須です。おそらく一番利用する分割型40%キーボード(親指キーが3つずつ型)をベースにそれ以上のキーがあるものは1キーへ2つ以上のキーを割り当てているものを改めて1つの物理キーへ割り当てたり、数字キーの列があるものは単に追加の数字キーとして扱う、などのようにすれば良いと思います。逆に30%キーボードは親指へ割り当てているキーをZの行へ割り当てるなどをして極力基本とUpper / Lowerのレイヤーは揃えるようにします。

なるべく40%分割型の標準形に合わせる

今回決めるキーマップを長く使えるものにしたいと思っています。 長く使えるためにはなるべく突飛な配置ではなく、既存のキーマップの最大公約数的なものにしておいたほうがよいということが言えます。なるべく標準的なキーマップにしておけば新しいアイデアなどが出てきたときにその新しいアイデアだけを取り込みやすいです。突飛な配置にしていると、まずそれとそのアイデアの部分を自分一人で折り合わせなければならず、結果的にキーマップがobsoletedになるということが容易に想像できます。

また可能であれば他の人にも使ってもらえれば最高ですし、他人と共有・あわよくばpull requestがもらえるようになればメンテナンスを自分一人でやる必要がなくなります。1人ではなく複数人でメンテナンスすることによるクオリティの上昇は説明するまでもありません。

キーマップはUS配列をベースにする

JIS配列とUS配列、以前からUS配列には興味がありましたが移行コストやあえてUS配列のキーボードを買う動機がなかったためJISで満足していました。 しかし、自作キーボードの場合基本的にASCIIキーボードが多いこと、記号キーなどの点でASCII配列に利点があること、キーキャップの印字がUSキーボードのものがほとんどであることなど総合的に勘案してこの期にUS配列へ移行することにしました。 上記の標準形に合わせる話とも少し関係しています。

基本はQWERTY

DVORAKやOEAなどの選択肢もありますが、今の所それほど必要性を感じていないこともあり主たるキーの配列は一般的なQWERTYで行きます。

個人的な追加要件

一般的なMAC JIS配列、109配列キーボードとの併用が前提

業務上、MacBookのキーボードを使ったりとっさにその辺の109キーボードを使うことが一定あります。今後もそういった機会はずっとあるでしょう。なので新しいキーマップはそれらのキーボードと併用したときに混乱しないものでなければなりません。例えば自作キーボードの方のみにあるキー、具体的には親指周辺のキーに新しいキーを割り当てることは何ら問題ありませんが109キーボードでいう左Winキーや左Alt, 左Ctrlなどに矢印キーなどを割り当てるととても混乱するでしょう。

OS側でのキー設定は極力いじらないようにする

具体的には「カタカナ/ひらがな」ボタンとEscapeをスワップしたり、CtrlキーとCapsLockキーをSwapしたりなどです。 一般的なキーボードで使い勝手を良くするために実行するのですが、そういった設定をしているOSへファームウェア的にCapsLockとCtrlを入れ替えている自作キーボードを接続するとまたAの左がCapsLockへ戻ってしまったりします。 こういった設定はOSレベルでしかできず接続している特定のキーボードでのみ入れ替えるといったことはできないこと、今後は自作のキーボードをメインで使うためこういった設定が必要になることは多くないことなどからこういったキー設定は基本的にしないこととします。なので、もしキー配置を変える場合はキーマップ側で対応することになります。

備考: Windowsでは以下で説明されているように 変換 無変換IMEのON / OFFを設定する前提とします

101キーのすべてのキーを網羅する

様々なキーマップを見ていると以外なことに PageUpPageDown を配置していないキーがかなり見つかります。 特に InsertPrint Screen Scroll Lock Pause はめったに配置されていません。 これらのキーは確かに使用頻度が低いですがまれに必要になることがありその際自作のキーボードでは押せないとなると著しく汎用性が落ちることになります。 そこで、それらのキーについても1箇所以上へ必ず割り当てられていることが必要です。

日本語入力に関わるキーを配置する余地を残す

qmk_firmwareで配布されているキーマップのほとんどは 半角/全角 変換 無変換 かな 英数 などのキーが配置されていません。これらがない場合日本語入力のためにAlt - `のように入力する必要が出てきます。これではとても不便です。 しかし、一方でこのキーボードはANSIベースを目指しており用途を日本人専用にもしたくありません。そこで、あくまでANSIキーボードベースでありつつ かな 英数 のような日本語入力・LANG1 / LANG2のような他の言語の言語辺関係キー入力もできるようにします。

具体的にはQMK Firmware で Raise/Lower と変換/無変換を同じキーに割り当てる - Okapies' Archiveを使ってレイヤー切り替えキーへそれらを割り当てる余地を残しておきます。 よってレイヤー切り替えキーは基本として M(...) を割り当て、言語系のキーを割り当てたいときのみ LT(...) を使います。

(want) MACとPCの両方で一貫して使用できるようにする

MAC用のキーボードとWindows用のキーボードでは特に左下のメタキーの位置が違ったりします。その辺りの挙動が一貫するようにしたいところ。

以上と、既存のmacbookや他人のキーボードをとっさに触る必要があるケースなども加味してキーマップを決める基準として以下を設定しました。

ネクストアクション

というわけで、まずは40%分割型キーボードのキーマップの最大公約数を求めてqmk firmwareのキーマップの海に潜ります。 幸い、分割型キーボードは親指部分のキー 2 ~ 4個、アルファベット部分36個前後に収束しつつあるので、それに該当するキーマップを調べていけば大多数の人間が合意できるキーマップが浮かび上がるんじゃないかと思います。

続く。

読書感想「Graphic Recorder ―議論を可視化するグラフィックレコーディングの教科書」

www.amazon.co.jp

議論を効率的に行うためのツールとして使えないかと思い読みました。 一通り読んだ感想としては期待通りのものですがやる人間の技術に効果が大きく依存するため、ワークショップや自主訓練を通じてある程度スキルを付けないとなかなかうまく行かないだろうなという感じです。 せっかくエンジニアなのでJam Boardなどデジタルホワイトボードでできないかと検討したのですが、初心者がやる場合はまずはアナログでしたほうがよさそうでした。 週末届くノートをもとにしばらく実践してみます。

エンジニア知識の盲点

Scala, AWS, スクラムマスター, マイクロサービス, KubernetesやDDDなど僕でも多少は知識的に自信がある領域があります。ただ、特にAWSKubernetesのような技術の進歩が早く領域自体もとても広いものはたとえ資格を持っていたとしても思わぬ抜け漏れがたまにあります。 AWSに関して、一応現在の会社ではそこそこ詳しい方だと自負しているのですがそれほどAWSに詳しくない同僚やGCPのほうが得意な同僚に自分の考えや設計の欠点・改善点を指摘されるということが一度ならずありました。それも主にAWSのあるサービス・機能を知っているだけでわかるようなものです。 そういったサービスや機能が自分の選択肢から抜け落ちていた理由を考えるとその領域(例えばAAAやネットワークなどが僕は弱いです)への関心が低かったり実際に使ったことがないため機能のイメージができておらず、new releaseの文章を読んだだけでは実際の利用イメージができていなかったことなどが挙げられます。

このことを僕は「知識の盲点」と呼ぶことにしています。

この概念の面白いところは個々人によって盲点の部分が違い、かつ他の人に盲点部分を指摘してもらえればそれが呼び水となって盲点部分が盲点でなくなる部分です。別の表現をすると、この盲点の部分というのはその人自身もそこが盲点だと気づいていないために盲点であるんですね。自分がわかっていないこと、それ自体に気づいていないわけです。 この状態の対策は簡単で、前述の通り複数人で盲点を補完し合うことです。この場合補完する人はその領域のエキスパートである必要はありません。せいぜい使ったことがあるかもしくは隣接領域のエキスパートなどで十分です1


  1. 例えばAWSの領域に対してサーバレスやGCPなどのエキスパートはよい補完になると思います

DockerベースのGithub Actionsの憂鬱

tl;dr

  • DockerベースのGithub Actionsは僕自身がGithub Actions 8ヶ月使ってみてわかったことまとめ - Qiitaの記事で絶賛した通りポータビリティ・独立性などの点で画期的でした
  • しかしリリースからほぼ1年たった現実ではNodejsベースのGithub Actionsのほうが好まれています
  • 原因はDockerベースのActionの重さにあります
  • CIの開始時に毎回docker pull / buildが走るため1つに付き10 ~ 数十秒かかるため、同じ機能のActionならDockerベースのActionよりセットアップが1~3秒で終わるnodejsベースのActionがよく選ばれます

長めの説明

Dockerベースの再利用可能なCIパーツ、という思想はGithub Actions 8ヶ月使ってみてわかったことまとめ - Qiitaに書いたとおり画期的でした。 僕はこの時点ではDockerが世を席巻したようにDockerベースのCIパーツという思想がCIサービスで多用されるのではないかと想像していました。 しかし、現実にはGithub Actionsサービス内ですらDockerベースのGithub ActionsよりNodejsベースのGithub Actionsの方が好まれており、Terraformの公式ActionsのようにDockerベースだったものをNodejsベースで置き換えるようなケースもちらほら出ています。

なぜDockerベースのGithub Actionsはだめなのか

だめな点1. Action実行の遅さ

NodejsベースのActionと比べてDockerベースのActionは明らかに遅いです。 Docker内での処理実行が遅いというわけではありません。そこは普通のDockerコンテナ実行と同じくほとんどノーペナルティと言って良いです。 遅いのはActionを開始するまでの準備です。 NodejsベースのActionがたかだか数十KBのzipのダウンロードと解凍なのに比べてDockerは数十 ~ 数百MBのDockerイメージをDockerhubその他からダウンロード・解凍する必要があります。 経験にこれは1つに付き数十秒かかります。DockerベースのActionが複数あればその分だけかかることになります。 またこの処理はワークフロー全体の一番最初に必ず実行するため、一連のワークフローの一番最初のAction実行でエラーが発生して以降のステップがまったく実行されない場合でもすべてのDockerベースアクションをダウンロード・解凍する必要があります。

だめな点2. 柔軟性の欠如

DockerベースのActionはActionへの入力(jobs.<job_id>.steps.with)を元に処理を行うことが基本ですが、柔軟性に乏しいと感じることが多いです。また $GITHUB_WORKSPACE のディレクトリもまたdockerコンテナ実行時にカレントディレクトリとしてマウントされるのですが、マウントポイントを $GITHUB_WORKSPACE ではなくそのサブディレクトリにすることなどもできません。 例えばpip installしてくれるDockerベースのGithub Actionsを作ったとすると、requirements.txtがリポジトリルートにあればよいですがサブディレクトリにある場合だと途端にそのGithub Actionsは使えないことになります。

考えてみるとCIを組む側としてほしかったのは相互に影響しあわない処理環境の独立性がメインであり、実行する処理内容が細かく規定されているのは余計かもしれません。

だめな点3. GitHub公式が別にDockerベースActionを押していない

DockerベースのActionが主流になれば、各種言語インタープリタコンパイラなどは個別のGithub Actionsになり、CIの実行ホストはさながらalpine linuxのようにminimumなOSになると思っていました。 しかし現実にはGithubvirtual-environments/Ubuntu2004-README.md at main · actions/virtual-environmentsを見ればわかるように古今東西あらゆる言語のあらゆるバージョンをインストールしたホストでGithub Actionsを実行しています。 これは最初からインストールされていれば新たにバイナリなどをダウンロードする必要がないからという理屈でこうしているのだと思われますが、この場合インストールされているバージョンや言語・ライブラリはすべてGithub Actions運用側の選択次第ということになります。

もしDockerベースのGithub Actionsに力を入れるのであれば、もっともネックとなっているイメージのダウンロード時間を改善するべくダウンロードしたイメージのデータを一定時間キャッシュできるようにする、直接Dockerhubからダウンロードするのではなくプロキシを通すことでキャッシングするなどの選択肢はあるはずです。そのような改善がいまだ入らず、結果的に全部入りのVMで実行する形が速度を考えると最善になっている今には正直失望を感じます。

ではNodejsベースのActionでいいんじゃないの?

と思われるかもしれませんが、NodejsベースのActionにも欠点はあります。 1つはjavascriptという1つの言語に実装が縛られることです。これはこの言語に慣れ親しんでいない人にとってはかなりのハードルになります。もう1つがnodejsが外部コマンドの呼び出しやファイル操作などいわゆるシェルスクリプト的な処理に向いていないことです。CIの処理というのは基本的にシェルスクリプトがベースになりますが、Nodejsの場合はそれをjavascriptの同等コードに置き換える必要が出てきます。置き換えられる場合はまだましで、現実的にはできないケースも多々あります。

それに対してDockerベースのActionはシェルスクリプトが基本であり、柔軟性も高いです。

まとめ

というわけで、現状DockerベースのGithub ActionsとNodejsベースのGithub Actionsはそれぞれ一長一短あります。ただしよく使われるものはCIの実行時間を短くして使い勝手を良くするためにNodejsベースで実装されるものが多いです。

自分でカスタムアクションを作成する場合は上記の使い勝手と実装のしやすさの間のトレードオフを踏まえてどちらを採用するか決めましょう。

クラウドインフラを構築するときの手順書とInfrasctucture as Codeの間のトレードオフ

tl;dr

インフラを構築するとき、構築方法を手順書で残すかInfrastructure as Codeで残すかの間にはトレードオフがある。

手順書 Infrastructure as Code
学習コスト
利便性

前置き

AWSなどのSaaS/IaaSでサービス基盤を構築するとき、手順書とInfrastructure as Codeのどちらを取るかが最近特に悩ましくなってきました。

手順書とはインフラ構築時の順序やコマンドなどを列挙した文書のことでpuppetやchefすらないときから用いられる由緒正しいインフラ屋の手法です。 一方Infrastructure as Code(IaC)はChef, Terraformなどに代表されるコードで定義されたインフラの定義であり言ってしまえば構築手順書を自動化したものです。 IaCを更に細分化するとMutable Infrastructureの視点によるもの(Chef, Ansible)とImmutable Infrastructureの視点によるもの(Terraform, Packer)に分けられると思います1

法名 手順書 MIaC(Mutable Infrastructure as Code) IIaC(Immutable Infrastructure as Code)
概要 書かれたコマンドを一つ一つTerminalで実行 継続して同じホストに対して自動化された処理を実行 毎回新しいホストに対して自動化された処理を実行 / ホストの元となるイメージを作成
代表的ツール txtファイル
Wiki
Confluence
Chef
Ansible
capistrano
Docker
Packer

上では代表的なツールを挙げましたが実際には各分野のツールもだいたいどれかの属性を帯びていることが多いです。例えばKubernetesの関連ツールであるhelmはそれ単体ですと手順書でしか形式知にできませんが、helmfileやArgo CDなどを使用することでIaCにすることができます。KubernetesやECSのようなコンテナクラスタは使い方によってどれにでもなると言えるでしょう。

Immutable Infrastructure as Codeの利点

時間が無限に使えるという仮定の元であればIIaCがほとんどの場合最良であることはある程度インフラ構築をやったことがある人であればほぼ合意できるかと思います。

手順書の場合、その手順の検証(テスト)のコストが大きくほとんどの場合テストがされません。また実行者の環境へ暗黙的に特定のコマンドがインストールされていることが前提となっているのに(jqなど)それが書かれていないこともよくあります。つまり再現性が低いのです。それに対してIaCでは文字通りコード化されているためテストのコストが圧倒的に低いです。例えばterraformであれば terraform init; terraform apply; terraform destroy で最低限のテストが実行できます。

MIaCについては既存のホストの状態によって実行結果が変わることがネックになります。例えばホスト内から特定のコマンドを削除したりディレクトリを移動することでchefやansibleの実行は失敗する可能性があります。それをカバーしようとして前提チェックや依存コマンド・環境などのレシピを積み上げていくと膨大な量となります。しかもそれでも本当に100%カバーできているかはわかりません。IIaCであれば実行したいアプリケーションの依存物はすべて同梱されており外部からそれを変更することは(それを明確に意図しない限り)できないので再現性が高くなります。

このように、基本的には手順書よりMIaC, MIaCよりIIaCのほうが特に再現性の点で優れていると言えます。 ではIIaCの欠点とはなんでしょうか。

Immutable Infrastructure as Codeの欠点

私が考えるIIaCの主な欠点は2つです。

  1. 計算資源の観点から見たコストの高さ
  2. 学習コストの高さ
1. 計算資源の観点から見たコストの高さ

ここで言う計算資源としては主にストレージ・デプロイ時間のことです。

PackerにしろDockerにしろ、作成したイメージはアプリケーションが依存するものをライブラリだけではなくOSのレイヤーまで含みます。結果的にビルドから実際の実行ホストまでのすべての経路においてMIaCと比べて多くのストレージや通信量がかかります。 実際Packerでビルドをやっていたときはアプリケーションはたかだか数十MBであるにもかかわらず作成したイメージは数GBになる話は珍しくありませんでした。 Dockerになってレイヤー化されたこと、OSのコア部分はホストOS側で担保されることなどによりPackerと比べては劇的に減りましたが結局最初のPull時点ではすべてのレイヤーをダウンロードする必要があるため減ったストレージはDocker Hubが担ってくれている部分のみと言えます。

またデプロイ時間も基本的には増えます。Packerの場合はVMインスタンスを新たに立ち上げる必要がありますし、Dockerの場合でも新たにイメージをpullする必要があります。MIaCによるデプロイはVMインスタンスをそのまま使い回せるため基本的にはIIaCよりも早いです。その仕組みの違い上、MIaCスタイルのデプロイとIIaCスタイルのデプロイそれぞれで最善を尽くすと絶対にIIaCは速度でMIaCスタイルを上回ることはできません。

2. 学習コストの高さ

IIaCという考え方はそれまでの試みや概念を土台としています。 また考え方自体が新しいため世の中に知見やノウハウ・それについて書かれた文書などがまだ多くありません。 IIaCのためのツールもまだ未熟で日本語でのドキュメントが少なかったり単純にバグがまだまだあったり機能が少なかったりします。

これらの欠点に対する私見

個人的に①の欠点はそれほど重大だとは思っていません。 というのは近年のストレージの価格は十分に低下しており、CPUが最も高価なIaaSでは相対的にそれほど気にならないからです2。 またデプロイ時間についてもMIaCスタイルのデプロイ(Capistrano, CodeDeploy)は工程が複雑になりがちで結果的にイメージをダウンロードしてきて実行するだけのIIaCスタイルの方が早くデプロイできるケースが多いです。

②については、確かにコストが高いことがあります。特にKubernetesの関連技術はまだ移り変わりが早く半年や一年で様相が一変しているようなことは少なくありません。

IIaCの覇権は明白

一方で、IIaCの覇権は2020/07現在かなり明白になっています。そもそもIIaCの成り立ちがMIaCなどの既存手法の問題・痛みを解決するものであり、すでに本番環境での採用・実践例も溢れています。 Dockerは本当にプロダクションユースなのか・Dockerのコンテナオーケストレーションツールのデファクトスタンダードはどれかといった業界全体による検証のフェイズはとっくに過ぎており Terraform / Docker / Kubernetes などといったデファクトスタンダードIIaCツールが揃った今、新しくIIaCを始める人にとってもツール選択に迷うことはかなり減りました。 このような背景のためにIIaCの導入は変化に適応する開発組織では確定路線であり、あとはいつ手順書やMIaCなどから移行するかを決めるのみです。


  1. この両方が選択できたりどちらかが曖昧なツールもたくさんあります。前者がAWS CodeDeploy、後者がTerraformなどです

  2. 実際のところはストレージが安くなりネットワークも十分に早くなったからIIaCのような手法が取れるようになった、発達したというのが本当のところだと思います