debug-node について
SSHで接続できない、またはプロビジョニングが停止したHetzner Cloudノードを調査するスキルです。レスキューモードでディスクを確認し、Leap Microのトランザクション型ファイルシステムも考慮して診断・修復します。
主な機能
- 01レスキューモードでのファイルシステム検査
- 02cloud-initとSSHの診断
- 03KubernetesとSELinuxのログ確認
- 04アクティブスナップショットの修復案内
- 05Hetzner CLIによる復旧手順
活用シーン
- 01到達不能なHetznerノードの調査
- 02cloud-initのプロビジョニング失敗の診断
- 03k3sまたはRKE2の起動障害の確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ mysticaltech/ terraform-hcloud-kube-hetzner --skill debug-nodeインストール後、Claude Code で/debug-nodeを入力して読み込まれたか確認します。
詳細
Rescue Console を介した Hetzner ノードのデバッグ
概要
Hetzner Cloud サーバーに接続できない場合(SSH が応答しない、プロビジョニングが停止している、cloud-init が失敗しているなど)、このスキルでは Hetzner のレスキューモードを使用してノードのファイルシステムをマウントし、外部からすべてを調査します。SSH が機能している必要はありません。
使用方法
/debug-node
実行時に、次の情報を尋ねます。
- サーバー名または IP(
hcloud server listで確認できます) - 発生している症状(SSH のタイムアウト、プロビジョニングの停止など)
前提条件
hcloudCLI がインストールされ、有効なトークンで設定されていること- サーバーが Hetzner Cloud に存在すること
hcloud server list
Leap Micro のファイルシステムモデル
Leap Micro は、btrfs 上でトランザクショナルアップデートシステムを使用します。以下のすべてを理解するための基本モデルです。
| レイヤー | 書き込み可能? | 再起動後も保持? | Hetzner のスナップショット後も保持? |
|---|---|---|---|
/usr(スナップショット) | いいえ(読み取り専用) | はい | はい |
transactional-update shell 経由の /etc | はい(新しいスナップショット) | はい(再起動後) | はい |
実行中のシステムで直接編集した /etc | はい(現在のスナップショットのオーバーレイ) | はい。ただし、保留中のスナップショットが同じパスを変更していない場合 | 現在のスナップショットの内容のみ |
/var(独立したサブボリューム) | はい | はい | はい |
ルール: 通常の /etc の編集内容は、通常の再起動後も保持されます。ただし、transactional-update が保留中のスナップショットを作成した後は、そのスナップショットが同じパスを変更している場合、現在のスナップショットの /etc に対する同時編集内容はマージされません。イメージのビルドやパッケージに関連する /etc の変更は、transactional-update --continue shell の中で行うか、編集前に保留中のスナップショットで再起動してください。
Packer のビルドフェーズ:
- レスキューモード: qcow2 をディスクに書き込み、再起動します
install_packages:transactional-update内で実行 — 変更は保持されますclean_up: 揮発性オーバーレイ —/etcの変更は Hetzner のスナップショットで失われます
ステップ 1:サーバーを特定する
hcloud server list -o columns=id,name,status,ipv4 | grep <pattern>
ステップ 2:レスキューモードを有効にして再起動する
hcloud server enable-rescue <SERVER_ID> --type linux64
hcloud server reboot <SERVER_ID>
sleep 30
出力に表示されるレスキュー用 root パスワードを保存してください(通常は鍵認証で接続できますが、念のため保存しておきます)。
ステップ 3:レスキュー環境に SSH 接続する
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null root@<SERVER_IP>
ステップ 4:ファイルシステムをマウントする
Leap Micro / MicroOS(btrfs)
# Mount btrfs top-level
mount -o subvolid=5 /dev/sda3 /mnt
# List snapshots — highest number is active
ls /mnt/@/.snapshots/
レイアウト:
/mnt/@/.snapshots/N/snapshot/ latest active snapshot — /etc lives here
/mnt/@/root/ /root home (/root/.ssh/authorized_keys)
/mnt/@/var/ /var (logs, cloud-init state, journal)
ポイント: /etc はスナップショット内にあります。/var と /root は、それぞれ @/var と @/root にある独立したサブボリュームです。
サポート対象外/カスタムの ext4 イメージ
mount /dev/sda1 /mnt
ステップ 5:診断チェックリスト
ここで一度設定し、以降はそのまま使用します。
SNAP=/mnt/@/.snapshots/N/snapshot # replace N with highest snapshot number
5a. Cloud-Init の状態
ここから確認してください。プロビジョニングの失敗の多くは cloud-init に起因します。
cat /mnt/@/var/lib/cloud/data/result.json
cat /mnt/@/var/lib/cloud/data/status.json
cat /mnt/@/var/lib/cloud/instance/datasource
# What Terraform actually sent
cat /mnt/@/var/lib/cloud/instance/user-data.txt
zcat /mnt/@/var/lib/cloud/instance/user-data.txt.i 2>/dev/null
# Logs
tail -100 /mnt/@/var/log/cloud-init.log
tail -100 /mnt/@/var/log/cloud-init-output.log
期待される状態: DataSourceHetzner で、エラーがないこと。
確認する内容: Skipping modules — cloud-init がこの instance-id に対してすでに実行されたことを意味します。
Hetzner と Leap Micro における cloud-init の情報:
- データソース:
DataSourceHetzner(メタデータ API) - Terraform の
cloudinit_config→ gzip+base64 の multipart MIME →user_data disable_root: falseは cloud-init による root の無効化を防ぎますが、ロックされたアカウントのロックは解除しませんssh_authorized_keysは鍵を/root/.ssh/authorized_keysに書き込みます
5b. SSH 鍵
cat /mnt/@/root/.ssh/authorized_keys
ローカルの公開鍵と比較してください。見つからない場合は、cloud-init による鍵の注入に失敗しています。5a のログを確認してください。
5c. SSHD の設定
設定の読み込み順(最初に一致した設定が適用されます):
1. /etc/ssh/sshd_config.d/40-kube-hetzner-authorized-keys-command.conf
2. /etc/ssh/sshd_config.d/50-cloud-init.conf
3. /etc/ssh/sshd_config.d/kube-hetzner.conf (MaxAuthTries 2)
4. /usr/etc/ssh/sshd_config.d/40-suse-crypto-policies.conf
5. /usr/etc/ssh/sshd_config (UsePAM yes)
ls $SNAP/etc/ssh/sshd_config.d/
cat $SNAP/etc/ssh/sshd_config.d/*.conf
cat $SNAP/usr/etc/ssh/sshd_config
ls -la $SNAP/etc/ssh/ssh_host_*
5d. アカウントの状態
grep '^root:' $SNAP/etc/shadow
| パターン | 意味 | SSH 公開鍵は機能する? |
|---|---|---|
root:*:... | ロック解除済み、パスワードなし | はい |
root:!*:... または root:!:... | ロック中 | いいえ(UsePAM yes では PAM に拒否されます) |
これは packer で修正されています(transactional-update 内で usermod -p '*' root を実行)。cloud-init の bootcmd はセーフティネットとして機能します。新しいノードでアカウントがロックされている場合は、packer のスナップショットを再ビルドする必要があります。
5e. Journal ログ
journalctl -D /mnt/@/var/log/journal/ -u sshd --no-pager | tail -50
journalctl -D /mnt/@/var/log/journal/ -u k3s --no-pager | tail -30
journalctl -D /mnt/@/var/log/journal/ -u rke2-server --no-pager | tail -30
journalctl -D /mnt/@/var/log/journal/ -u rke2-agent --no-pager | tail -30
5f. ネットワーク
ls $SNAP/etc/NetworkManager/system-connections/
cat $SNAP/etc/NetworkManager/system-connections/*.nmconnection 2>/dev/null
5g. Kubernetes
cat $SNAP/etc/rancher/k3s/config.yaml 2>/dev/null
cat $SNAP/etc/rancher/rke2/config.yaml 2>/dev/null
cat /mnt/@/var/lib/rancher/k3s/server/token 2>/dev/null
5h. SELinux
cat $SNAP/etc/selinux/config
chroot $SNAP rpm -qa | grep -iE 'selinux|k3s|rke2'
grep -i 'avc:.*denied' /mnt/@/var/log/audit/audit.log | tail -50
journalctl -D /mnt/@/var/log/journal/ --no-pager | grep -i 'avc:.*denied' | tail -50
ワークロードが拒否された場合は、docs/selinux.md に従ってください。上流のポリシー変更を提案する前に、AVC の行、ワークロード名/バージョン、k3s/RKE2 のディストリビューション、OS イメージ、udica の結果を収集してください。最初の対応として SELinux をグローバルに無効化しないでください。ポリシーの下で実行できないワークロードまたはノードプールに限り、最後の手段としてプールごとの selinux = false を使用してください。
ステップ 6:修正を適用する
@/ のベースではなく、アクティブなスナップショット($SNAP)内のファイルを編集してください。
# Unlock root account (if locked)
sed -i 's/^root:!*/root:*/' $SNAP/etc/shadow
# Fix authorized_keys
mkdir -p /mnt/@/root/.ssh
echo "ssh-ed25519 AAAA..." > /mnt/@/root/.ssh/authorized_keys
chmod 700 /mnt/@/root/.ssh && chmod 600 /mnt/@/root/.ssh/authorized_keys
# Regenerate host keys
mount --bind /proc $SNAP/proc && mount --bind /sys $SNAP/sys && mount --bind /dev $SNAP/dev
chroot $SNAP ssh-keygen -A
umount $SNAP/proc $SNAP/sys $SNAP/dev
注: レスキューモードでの編集は、その場で適用される修正です。長期的な適切な修正は、packer テンプレートまたは cloud-init に加えてください。
ステップ 7:通常モードで再起動する
umount /mnt 2>/dev/null
exit
hcloud server disable-rescue <SERVER_ID>
hcloud server reboot <SERVER_ID>
sleep 60
ssh -o StrictHostKeyChecking=no -o IdentitiesOnly=yes -i ~/.ssh/id_ed25519 root@<SERVER_IP> 'echo ok'
よくある診断
| 症状 | 考えられる原因 | 確認方法 | 修正方法 |
|---|---|---|---|
| SSH がタイムアウトする | ファイアウォールまたはネットワーク | Hetzner のファイアウォールルール | ポート 22 を開く |
| SSH「Connection refused」 | sshd が実行されていない | ジャーナルログ | sshd の設定構文を修正する |
| SSH キーが拒否される | キーが注入されていない | authorized_keys が空 | cloud-init のログを確認する |
| SSH「Too many auth failures」 | エージェントが多すぎるキーを提示している | MaxAuthTries 2 | -o IdentitiesOnly=yes を使用する |
| SSH「unable to authenticate」 | root がロックされている、またはキーが一致しない | /etc/shadow、authorized_keys | packer スナップショットを再ビルドする |
| プロビジョナーが「Still creating」で停止する | SSH で接続できない | 上記すべて | 根本的な SSH の問題を修正する |
| cloud-init がモジュールをスキップする | インスタンス ID に対してすでに実行済み | cloud-init.log | /var/lib/cloud/instance をクリーンアップする |
| k3s/rke2 が起動しない | 設定または SELinux | ジャーナルと audit.log | 設定またはポリシーを修正する |
| ワークロードが SELinux に拒否される | ワークロードポリシーが不足している | audit/journal 内の AVC の行 | docs/selinux.md に従い、プールを無効化する前に udica を試す |
| ネットワーク/サブネットの削除が停止する | Autoscaler が作成したサーバーが Terraform の状態外にある | クラスター名または残存する kh-ci-* について hcloud server list を実行する | コントロールプレーンが停止してから削除するか、先に autoscaler の min_nodes = 0 に設定する |
トランザクショナル再起動後に /etc の変更が消える | 現在のスナップショットと保留中のスナップショットで同じパスが変更された | transactional-update status と /var/lib/overlay を調べる | アクティブなスナップショットで再適用するか、イメージビルド時の変更をトランザクション内に移す |
SSH を手動でデバッグする
# Verbose with specific key (avoids agent key spray hitting MaxAuthTries 2)
ssh -vvv -o IdentitiesOnly=yes -i ~/.ssh/id_ed25519 root@<SERVER_IP>
# In -vvv output:
# "Offering public key: ..." → key was offered
# "Server accepts key: ..." → success path
# "Authentications that can continue: ..." → key was REJECTED
# "Too many authentication failures" → agent sent too many keys
プロのヒント
subvolid=5を指定してマウントする — 実際の btrfs ルートにアクセスし、@/.snapshots/N/snapshot/に移動できます- 最も番号の大きいスナップショットがアクティブ —
/etcがあるのはそこです /varは別です — ログと cloud-init はスナップショット内ではなく、/mnt/@/var/にあります- システムが稼働していなくてもジャーナルを参照できる —
journalctl -D /path/to/journal/ -o IdentitiesOnly=yesを使用する — kube-hetzner はMaxAuthTries 2を設定します- 揮発性オーバーレイの落とし穴 — レスキューモードで表示される内容が稼働中のシステムと異なる場合、コミットされない揮発性オーバーレイ上で実行されていました
- packer を修正したらスナップショットを再ビルドする — ビルドログの
transactional-update出力内に変更が表示されていることを確認します - レスキューモードは非破壊的 — ディスク上のファイルを読み書きするだけです
- ラッパーを使って破棄する — クラスター全体を削除するには、Terraform のルートから
scripts/destroy.shを実行します。既知の ingress-LB 切り離し競合のみを再試行し、読み取り専用の孤立リソースレポートを表示します。scripts/cleanup.shは強制的な最終手段としてのみ使用してください。これはトークンの HCloud プロジェクト全体をクラスター専用として扱うため、まずドライランを確認してください。