セットアップ時にSSHをトンネルアドレスに束縛すると動作するが、次回の再起動後は誰かが手動でサービスを再起動するまで誰もログインできない。認証情報が問題ではない。デーモンはまだ存在しないアドレスをカーネルに要求し、バインドは失敗し、そのメッセージは起動ログを流れていった。認証の問題に見えるものは、起動順序の問題である。

バインドエラーが実際に語っていること

待ち受けソケットは、その瞬間に存在するアドレス上に作成されなければならない。VPNインターフェースが後から現れると、トンネルアドレスだけにバインドするよう設定されたデーモンには、取り付く先がない。設定は構文的には有効なままであり、単に環境が準備できていなかった。手動起動から動き続けているサービスは起動経路を決して通らないため、障害は次回の再起動で初めて現れる。バインド失敗の時刻とアドレス割り当ての時刻を比較すれば、順序は明らかになる。

設定の有効性と実行時の準備状態は別の性質であり、両方を試すべきだ。片方は何も言わずに失敗するからだ。

どのプロセスがポートを所有しているか

デーモンが常にリスナーを作る構成要素とは限らない。ソケットアクティベーションはその責任をユニットに移し、その場合デーモンを再起動しても何も変わらない。

ss -ltnp 'sport = :22'
systemctl cat ssh.socket

ソケット一覧はどのプロセスがどのアドレスでポートを保持しているかを示し、ユニットのダンプはソケットユニットが存在するかどうかを示す。アクティベーションが関与している場合、アドレスはソケットユニットに属し、デーモンのファイルは権威ではない。症状から推測するのではなく、実際のバインドエラーについて起動ログを読むこと。ソケットユニットがない場合は単に従来の構成、つまりデーモンが直接バインドする構成を意味する。

準備状態を明示せよ、スリープではなく

固定の遅延は、高速なハードウェアと低速なネットワークを同じように扱う推測である。代わりに依存関係を表現し、アドレスを待ってから起動する。

ip -4 addr show dev tun0
systemctl edit ssh.service

アドレスが現れるまで繰り返すExecStartPreコマンドを追加するか、インターフェースを所有する特定のネットワークターゲットの後にユニットを並べる。どちらの方法も、次の管理者が読める場所に依存関係を記録する。ループにはタイムアウトが必要であり、さもなければ失敗したVPNが障害を報告する代わりにハングしたユニットを残す。その後、通常のインターフェースがフィルタルールの管理下にあるまま、デーモンがトンネルアドレスをバインドすることを確認する。

クリーンな起動と再接続を試す

すべてがオンラインになった後の手動再起動は、起動順序について何も証明しない。トンネルが自動的に立ち上がるよう設定した状態でマシンを再起動し、介入なしにリスナーが現れるか観察する。

systemctl status ssh.service
ss -ltnp 'sport = :22'

再接続はアドレスを変えうるし、ハードコードされたバインドはそれに追従しないため、トンネルを落として再確立する。テスト中は帯域外の復旧経路を維持し、悪い順序変更でホストから締め出されないようにする。

成功の基準は、文書化された依存関係の下で現れるリスナーであり、誰かが再起動を覚えていたリスナーではない。

関連記事