← シリーズに戻る

pull 型: 外から入れない機械と3つの通知

Part 2 / 6 Raspberry Pi デプロイ5方式を7軸比較

前の章の push 型と向きが逆になります。配る側はストレージに置くだけ。機械が自分で取りに来ます。

向いているのは、機械に入れない環境です。店舗やオフィスの LAN に置いた Raspberry Pi、4G 回線の先にある端末、NAT 配下に並んだ機器。「外から SSH できない」を前提にできるなら、この方式が第一候補になります。

置き場所は HTTP で読めればどこでも構いません。機械側に「取ってきて展開して反映する」プログラムを常駐させておき、それが定期的に、あるいは通知を受けて動く形になります。

ここまで読むと、SSH が要らなくなるぶん push 型より全面的に良いように見えます。そうではありません。7軸で上がるのは到達性の1つで、常駐コストと観測性は下がります。機械の上で動き続けるプログラムが1つ増え、そのプログラムは特権を持ったまま動き続け、取りに来なかった機械を配る側から見つけられなくなります。置き場所そのものも、書き込み権限を取られたら全機械に任意のコードが流れる一点になります。

問題が消えたのではなく、場所が移っただけです。 どこに移ったのかを、以下7軸で追います。

この方式の検証状態: 実機では動かしていません。以下は公式ドキュメントとプロトコル仕様から言える範囲です。実測が済んだセルは表を更新します。

pull 型の経路。配る側はストレージに置くだけで、機械の上のエージェントが HTTPS で取りに行く。起こし方は定期ポーリング・長時間ポーリング・MQTT 通知の3つで、遅れと常時接続のどちらを取るかが変わる。NAT の内側でも届く代わりに、エージェントが常駐し、取りに来なかった機械は配る側から成功と区別がつかない

到達性: この方式を選ぶ最大の理由

機械がどこに居ても動きます。家庭の LAN の内側でも、モバイル回線の先でも同じで、外向きに HTTPS が出られればそれで足ります。

モバイル回線がとくに効きます。通信事業者は加入者をまとめて NAT の内側に入れることがあり(キャリアグレード NAT と呼ばれます)、この下に居る機械にはグローバル IP がそもそも割り当てられません。契約者が設定を変えて解決できる話ではないので、外から入る前提の方式はここで完全に閉じます。

push 型が VPN や踏み台で解いていた問題が、ここではそもそも発生しません。工場出荷したあと顧客のネットワークに入ってしまう機械のように、こちらから触れない前提の相手には、実質この方式しか選べません。

通知: webhook が使えない場所での起こし方

到達できないということは、更新を知らせる手段も限られるということです。webhook は相手に接続しに行く仕組みなので、機械側から見て外から入れない時点で最初から候補に入りません。

起こし方は3つです。

起こし方反映までの遅れ常時つないでおくもの
定期ポーリング間隔ぶん(数分〜数時間)なし
長時間ポーリングほぼ即時HTTP 接続を張りっぱなし
機械側から張る常時接続(MQTT など)ほぼ即時ブローカーへの接続

3つ目のブローカーは、配る側と機械の間に立って通知を中継するサーバーです。機械のほうから接続を張りに行くので、外から入れない機械にも通知が届きます。webhook で解けなかった問題を、接続の向きを変えて解いている形です。

台数が数十までで、反映が数分遅れても構わないなら、定期ポーリングで足ります。実装が一番単純なうえに、失敗しても次の周期で勝手に再試行されるので、壊れにくさという一点では他の2つを頭一つ抜いています。

即時性が要る、あるいは台数が数千に増えると話が変わります。全機械が5分ごとに叩けばストレージ側から見えるのは分散しない山なので、ここで常時接続型に切り替える判断が出てきますが、ブローカーという常駐物が1つ増えるぶん、常駐コストの軸に跳ね返ります。

中間として、更新の有無だけを軽い問い合わせで確かめて、あったときだけ本体を取りに行く形もよく使われます。If-None-Match を付けて、変わっていなければ 304 が返る、というだけの仕組みでも、転送量はかなり削れます。

常駐コスト: 機械側に1つ増える

push 型が◎だったところが、こちらでは○か△になります。取得して展開して反映するプログラムが、機械の上で動き続けます。

そのプログラムが落ちたら更新は止まり、止まったことに気づく仕組みがまた別に要ります。設定を配りたいだけなのに、監視しなければならないものが1つ増えます。

代わりに得るものもあります。このプログラムは起動時にも取りに行けるので、数日オフラインだった機械が復帰したとき、push 型なら配る側が再実行しないと追いつかないところを、pull 型は誰も何もしなくても勝手に追いつきます。台数が増えるほど、この自己回復性の価値が上がります。

権限の粒度: push 型と逆転する

見落としやすいところです。

push 型では、root 権限が使われるのは配っている数秒間だけでした。sudoers に列挙したコマンドが、CI が入っている間だけ実行されます。

pull 型では、常駐しているプログラムがその権限を持ったまま動き続けます。設定を /etc の下に置いてサービスを reload できる、ということは、そのプログラムが乗っ取られたら常にそれができるということです。

対策の方向は同じです。エージェント自体は一般ユーザーで動かし、特権が要る部分だけを sudoers で絞って呼ぶ。ただし「絞った権限を常に持っている」と「絞った権限を時々持つ」の差は残ります。この差は表の上では見えにくいので、意識して書き分ける必要があります。

ロールバック: 機械が自分で判断できる

ここは pull 型のほうが素直です。

取ってきたものを展開して、検証して、駄目なら自分で前の世代に戻せます。配る側との通信は要りません。世代をいくつか手元に残しておいて、シンボリックリンクを差し替える形にすれば、戻すのはリンクの張り替えだけで済みます。

push 型で「手続きの途中で死ぬ」と書いた問題が、こちらでは軽くなります。判断する主体が機械の上に居るからで、ネットワークが切れていても判断そのものは続きます。

取得の整合性: 半端なものを掴まない

前節の世代ディレクトリとシンボリックリンクは、戻すためだけの仕組みではありません。取得が途中で切れたときにも同じ形が効きます。

push 型では、転送してから配置するまでを CI が1つの手続きとして持っていました。pull 型は取得も配置も機械側なので、途中で切れたときに何が残るかを自分で決めることになります。素直に配置先へ直接書くと、回線が切れた瞬間の半端なファイルを、次に読むプロセスが掴みます。設定ファイルなら構文エラーで起動しなくなります。

形は決まっています。別の場所に落としきってから、最後に一手で入れ替える。

  1. 一時ディレクトリに取得する
  2. 大きさとハッシュを検証する
  3. 通ってから、所定の場所へ rename で移す

3が要点で、同じファイルシステム内の rename は POSIX で不可分と決められています。古いものか新しいものかのどちらかしか見えず、中間状態が存在しません。ファイルシステムを跨ぐと不可分ではなくなるので、一時ディレクトリは配置先と同じファイルシステムに置きます。

ファイルが複数あるときは、世代ディレクトリごと落としてから、新しいシンボリックリンクを別名で作って rename で被せます。こうすると「7個のうち3個だけ新しい」という状態を作らずに済みます。reload をかけるのはこの入れ替えの後です。順番を逆にすると、検証を通した意味がなくなります。

帯域: 固め方で決まる

zip や tar で固めて置くと、1バイト変わっても全部落とすことになります。設定ファイル数十 KB なら気になりませんが、100MB のアーカイブを1000台が取りに来ると効いてきます。

軽くする方向は2つです。ファイル単位で置いてハッシュで差分を判定するか、アーカイブごと版を切って変わったときだけ落とすか。前者は取得ロジックが複雑になり、後者は単純な代わりに転送量が減りません。単純さと転送量の交換になります。

改ざん耐性: 置き場所が取られたら終わる

この方式でもっとも注意が要るところです。

機械は「その URL から落ちてきたもの」を信じて展開するので、ストレージの書き込み権限を取られると、それだけで全機械に任意のコードを配れます。HTTPS は通信経路を守ってくれますが、置かれた中身が正しいことまでは保証しないので、経路を暗号化しただけでは何も解決していません。

ここで署名が要ります。配る側が署名し、機械が公開鍵で検証してから展開します。そうすると、ストレージが取られても署名鍵が無ければ配れません。経路を信じる型から、中身を信じる型への切り替えです。

パッケージ型はこれを最初から仕組みとして持っています。次章で扱います。5章では5方式まとめて、どこに穴が残るかを整理します。

置き場所を誰が読めるか

改ざん耐性は書き込み側の話でした。読み取り側にも同じだけの判断が要ります。

「HTTP で読めればどこでも構いません」と書きましたが、これは誰でも読めていいという意味ではありません。配るのが設定ファイルなら、そこには接続先のホスト名、内部のエンドポイント、ときには資格情報そのものが入ります。公開バケットに置けば、URL を知っている人は全員読めます。URL は推測されないから安全、という考え方は、台数が増えて URL が構成管理に書かれ、CI のログに出るようになった時点で崩れます。

読める相手を絞るなら、機械側に読み取り用の資格情報を置くことになります。ここで1章と同じ形が出てきます。push 型では VPN に一時参加するための資格情報が新しい鍵になりました。pull 型では、それが機械の上に置きっぱなしになる鍵に変わります。

その鍵をどう配るかで、性格が2つに割れます。

配り方漏れたとき台数が増えたとき
全機械で同じ鍵1台抜かれると全機械ぶん漏れます配るのは楽です
機械ごとに別の鍵抜かれた1台だけ止められます発行と失効の仕組みが要ります

数台なら前者で足ります。数十を超えると、1台の紛失で全機械の鍵を配り直すことになるので、後者へ寄せる圧力がかかります。ここでも常駐コストの軸が効いてきます。鍵を発行して失効させる仕組みは、動かし続けるものがまた1つ増えるということです。

有効期限付きの URL を配る手もあります。機械に長期の鍵を置かずに済みますが、期限が切れる前に新しい URL を取りに行く経路が別に要るので、最初の資格情報をどこに置くかという同じ問題が、1段ずれた場所に残ります。

観測性: 置いた側から見えなくなる

配る側は、置いた時点で仕事が終わります。その後、機械が取りに来たかどうかを知りません。

機械が電源断していても、ネットワークが切れていても、取得に失敗していても、配る側から見た景色は成功と同じです。push 型なら SSH が繋がらない時点で CI が赤くなるのに対して、こちらは失敗を知らせる主体がどこにも居ないので、何も起きないまま時間が過ぎます。

なので、取得結果を機械側から報告させる経路を別に作ることになります。バージョンを添えた死活報告を定期的に投げさせて、一定時間報告が来ない機械を検出します。ここまで作って、ようやく push 型の観測性に並びます。

「配ったつもり」がもっとも起きやすい方式である、と覚えておくのが安全です。

push 型と pull 型の対比。push は配る側から機械へ矢印が向き、NATの内側に入れない代わりにroot権限は数秒だけで失敗はCIが赤くなる。pull は機械から置き場所へ矢印が向き、どこにでも届く代わりに常駐プログラムが権限を持ち続け、失敗が配る側から見えない

7軸で見ると

評価push 型との差
到達性逆転。どこに居ても届きます
常駐コスト逆転。エージェントが増えます
権限の粒度同じ絞り方でも、常時持つぶん危険度が上がります
ロールバック改善。機械が自分で戻せます
帯域固め方次第
改ざん耐性署名を足せば◎になります。足さないと置き場所が単一障害点
観測性逆転。報告経路を別に作って、ようやく並びます

到達性・常駐コスト・観測性の3つが、push 型と綺麗に反転しています。向きを変えて得たものと失ったものが、この3つに全部出ています。

次の章では、配るものを「ファイルの集まり」から「パッケージ」に変えます。すると、他の4方式には無い性質が1つ手に入ります。

参考