← シリーズに戻る

deb 配布: 200台の状態を数える

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

配るものを、ファイルの集まりからパッケージに変えます。deb を作って、apt リポジトリに置いて、機械側の apt に取りに来させます。

取得の向きは pull 型と同じです。機械側から取りに行きます。ではなぜ別の章にするかというと、パッケージという単位を導入すると、他の4方式には無い性質が1つ手に入るからです。

その性質は、機械が何をどの版で持っているかを問い合わせられることです。もう1つ、署名の鎖が仕組みとして最初から入っているので、改ざん耐性を自分で書かずに済みます。代わりに作る手間は5方式で一番重く、apt が root で動くぶん権限を絞る余地も小さくなります。

この方式の検証状態: 実機では動かしていません。以下は dpkg の man ページと Debian リポジトリ仕様から言える範囲です。conffile の挙動と署名の鎖は一次資料で確認しました(末尾に URL)。

パッケージ型の経路と署名の鎖。配る側は deb をビルドしてリポジトリに置き、Release ファイル1点に署名する。InRelease から索引のチェックサム、Packages、各 deb のチェックサムへ鎖が繋がるので、1点の検証が全体に効く。機械側は apt が取得して署名を検証し、インストール後は dpkg -l が何をどの版で持っているかを返す。conffile は対話で止まるため方針の固定が要り、取りに来たかどうかは配る側から見えない

手に入る性質: 状態を問い合わせられる

dpkg -l を打つと、いま何がどのバージョンで入っているかが返ります。

これは当たり前に見えて、他の4方式には無い性質です。push 型で配ったファイルが本当に置かれているかを知るには自分で確認する仕組みを書き、pull 型なら報告経路を作ることになりますが、パッケージ型では機械が自分の状態を構造化して持っているので、聞けば答えます。

依存関係も同じです。「この設定を使うにはこのバージョン以上のプログラムが要る」という関係を宣言しておけば、満たされないときにインストールが止まってくれますが、他の方式ではこの整合性を保証しているのは人間の頭の中だけです。

台数が増えるほど効いてきます。200台のうち何台が新しい版になっているかを、追加の仕組みなしで数えられる状態は強い武器になります。

署名が最初から入っている

pull 型の章で、置き場所を取られると全機械に任意のコードを配れる、と書きました。パッケージ型はこの問題に仕組みとして答えを持っています。

署名されるのは個々のパッケージではなく、リポジトリの Release ファイルです。そこから鎖が繋がります。

InRelease (署名済み)
  └─ 索引ファイルのチェックサム
       └─ Packages (索引)
            └─ 各 .deb のチェックサム

署名された1点を検証すれば、索引が本物であることが分かり、索引の中のチェックサムで各パッケージが本物であることが分かります。途中のどれか1つが差し替わると、鎖のどこかで検証が落ちます。

結果として、リポジトリを置いているサーバーが取られても、署名鍵が無ければ有効なものは配れません。経路を信じる型から、中身を信じる型への切り替えが、自分で暗号処理を書かずに手に入ります。

これは pull 型に自前で署名を足すのと同じことをしていますが、すでに書かれていて、クライアント側の実装も配布済みという差は大きく効きます。

conffile の罠

ここが、設定ファイルを配るときの最初の壁です。

deb の中で設定ファイルを conffile として登録すると、dpkg はそれを特別扱いします。機械側で編集されている可能性があるものとして扱い、パッケージ側でも中身が変わっていると、どちらを採るかを対話で聞いてきます

人間が座っている端末なら親切な機能です。自動デプロイではそこで止まります。しかも apt の出力を最後まで見ていないと、止まったことにも気づけません。

方針を固定する必要があります。

apt-get -o Dpkg::Options::="--force-confold" install myapp-config

dpkg の man ページには、それぞれこう書かれています。

オプション挙動
--force-confold変更されている場合、聞かずに古いほうを残す
--force-confnew変更されている場合、聞かずに新しいほうを入れる
--force-confdef既定の動作を選ぶ。既定が無ければ聞く(他のオプションと併用が前提)

自動デプロイで設定を上書きしたいなら --force-confnew です。ただし機械側で手で直した内容が黙って消えます。逆に --force-confold にすると、パッケージを更新しても設定が古いままという状態が起きえます。

どちらを選んでも、片方の期待は裏切られます。

もっとも安全なのは、そもそも手で編集される設定ファイルを conffile にしない設計です。パッケージが配るのはテンプレートや既定値だけにして、機械固有の値は別ファイルに分けます。設定を配ること自体が目的のパッケージなら、--force-confnew で固定したうえで「ここは手で触らない場所」と決めきってしまうほうが、後から起きる事故はずっと少なくなります。

ロールバック: バージョンを指定して戻せる

apt install myapp-config=1.2.3 と書けば前の版に戻ります。戻す単位がバージョンという名前で扱えるのは、他の方式には無い明快さです。

ただし条件が2つあります。

古い版がリポジトリに残っていること。新しいものを置いたとき古いものを消す運用にしていると、戻り先がありません。

インストール時に走るスクリプトが、逆向きに走っても壊れないこと。deb には設置前後に実行されるスクリプトを入れられますが、これが「新しい版でしか通らない移行処理」を含んでいると、ダウングレードで壊れます。戻せる設計にするには、スクリプトを何度走らせても同じ結果になるように書く必要があります。ここは自分で担保する部分です。

常駐コストと観測性: pull 型と同じ弱さ

apt は常駐しません。定期実行の仕組みで叩くことになるので、構造としては pull 型の一種です。

したがって、pull 型で書いた弱点をそのまま引き継ぎます。配る側はリポジトリに置いた時点で終わり、機械が取りに来たかどうかを知りませんdpkg -l で状態を問い合わせられると書きましたが、それはこちらから機械に届く場合の話であって、届かない機械には聞けません。

「状態を持っている」ことと「その状態を配る側が見られる」ことは別です。ここは混同しやすいので分けておきます。

帯域: 単位が粗い

パッケージ単位で丸ごと入れ替わります。設定ファイル1行の修正でも、パッケージ全体が転送されます。

設定ファイル数十 KB なら誤差です。バイナリを含む大きなパッケージだと効いてきます。

7軸で見ると

評価補足
到達性pull 型と同じ。機械が外に出られれば足ります
常駐コスト常駐プロセスは不要。定期実行の仕組みだけ
権限の粒度apt は root で動きます。絞る余地が小さい
ロールバックバージョン指定で戻せます。スクリプトの冪等性は自前
帯域パッケージ単位。差分は基本的にありません
改ざん耐性署名の鎖が仕組みとして入っています。5方式で最強
観測性pull 型と同じ。報告経路は別に要ります

改ざん耐性が◎になるのは、この方式と A/B 型の2つです。どちらも署名と検証がすでに仕様として書かれていて、受け取り側の実装も配布済みだからです。残り3方式で同じ水準に届かせるには、署名と検証を自分で書くことになります。

作る手間は5方式で一番重くなります。deb を作り、リポジトリを立て、鍵を管理する。設定ファイルを1台に配るためにやることではありません。台数が数十を超えて、機械ごとに何が入っているかを問い合わせる必要が出てきたあたりから、ようやくこの手間が見合い始めます。

次の章では、そもそも差分を配るのをやめる方向に振ります。

参考