mcpField
D
debug-node

debug-node

github.com/mysticaltech云与 DevOps3.9k73 次安装7 周前更新
代码仓库

关于 debug-node

用于排查 SSH 无法连接或配置卡住的 Hetzner Cloud 节点。该技能指导运维人员通过救援模式检查磁盘、诊断并修复问题,涵盖 Leap Micro 的事务式文件系统布局。

主要功能

  1. 01救援模式文件系统检查
  2. 02cloud-init 与 SSH 诊断
  3. 03Kubernetes 和 SELinux 日志检查
  4. 04活动快照修复指导
  5. 05Hetzner 命令行恢复步骤

适用场景

  1. 01排查无法访问的 Hetzner 节点
  2. 02诊断 cloud-init 配置失败
  3. 03检查 k3s 或 RKE2 启动故障

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/mysticaltech/terraform-hcloud-kube-hetzner --skill debug-node

安装后在 Claude Code 中输入/debug-node确认已加载。

详细介绍

通过 Rescue 控制台调试 Hetzner 节点

概述

当 Hetzner Cloud 服务器无法访问时(SSH 卡住、配置停滞、cloud-init 失败),此技能会使用 Hetzner 的 rescue 模式挂载节点文件系统,并从外部检查所有内容——无需可用的 SSH 连接。

用法

/debug-node

调用时,请询问:

  1. 服务器名称或 IP(可通过 hcloud server list 查找)
  2. 遇到的症状(SSH 超时、配置卡住等)

前置条件

  • 已安装 hcloud CLI,并配置了有效令牌
  • 服务器必须存在于 Hetzner Cloud 中
hcloud server list

Leap Micro 文件系统模型

Leap Micro 在 btrfs 上使用 transactional-update 系统。以下所有操作都基于这一模型。

层可写?重启后保留?Hetzner 快照中保留?
/usr(快照)否(只读)是是
通过 transactional-update shell 操作的 /etc是(新快照)是(重启后)是
在运行中的系统上直接编辑 /etc是(当前快照覆盖层)是,除非待处理快照更改了相同路径仅当前快照中的内容
/var(独立子卷)是是是

规则: 普通的 /etc 编辑会在正常重启后保留。但是,transactional-update 创建待处理快照后,如果待处理快照更改了相同路径,则对当前快照中 /etc 的并行编辑不会合并。请将镜像构建和与软件包相关的 /etc 更改放在 transactional-update --continue shell 中进行,或者先重启进入待处理快照,再进行编辑。

Packer 构建阶段:

  1. Rescue 模式: 将 qcow2 写入磁盘,然后重启
  2. install_packages: 在 transactional-update 内执行——更改会保留
  3. clean_up: 临时覆盖层——/etc 更改不会保留在 Hetzner 快照中

步骤 1:识别服务器

hcloud server list -o columns=id,name,status,ipv4 | grep <pattern>

步骤 2:启用 Rescue 模式并重启

hcloud server enable-rescue <SERVER_ID> --type linux64
hcloud server reboot <SERVER_ID>
sleep 30

请保存输出中的 rescue 根密码(通常密钥认证可用,但以防万一)。

步骤 3:SSH 连接到 Rescue

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 已针对此实例 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:!:...已锁定否(PAM 在 UsePAM yes 时会拒绝)

此问题已在 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 未运行Journal 日志修正 sshd 配置语法
SSH 密钥被拒绝未注入密钥authorized_keys 为空检查 cloud-init 日志
SSH 报错 "Too many auth failures"Agent 提供的密钥过多MaxAuthTries 2使用 -o IdentitiesOnly=yes
SSH 报错 "unable to authenticate"Root 被锁定,或密钥不匹配/etc/shadow、authorized_keys重新构建 packer 快照
Provisioner 卡在 "Still creating"SSH 无法连接以上所有项目修复底层 SSH 问题
cloud-init 跳过模块已针对该 instance-id 运行cloud-init.log清理 /var/lib/cloud/instance
k3s/rke2 未启动配置或 SELinuxJournal + audit.log修复配置或策略
工作负载被 SELinux 拒绝缺少工作负载策略audit/journal 中的 AVC 行遵循 docs/selinux.md;尝试 udica,再考虑禁用某个池
网络/子网销毁卡住Autoscaler 创建的服务不在 Terraform 状态中使用 hcloud server list 查找集群名称或遗留的 kh-ci-*仅在控制平面停止后删除,或先将 autoscaler 的 min_nodes = 0
transactional 重启后 /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

专业提示

  1. 使用 subvolid=5 挂载 — 获取真正的 btrfs 根目录,然后导航到 @/.snapshots/N/snapshot/
  2. 编号最高的快照 = 活动快照 — /etc 就在其中
  3. /var 是独立的 — 日志和 cloud-init 位于 /mnt/@/var/,不在快照内
  4. 在系统未运行时查看 Journal — journalctl -D /path/to/journal/
  5. 使用 -o IdentitiesOnly=yes — kube-hetzner 将 MaxAuthTries 设为 2
  6. 注意易失覆盖层陷阱 — 如果救援环境显示的内容与实时系统中的不同,说明系统运行在从未提交的易失覆盖层上
  7. 修复 packer 后,重新构建快照 — 确认构建日志显示 transactional-update 输出中包含变更
  8. 救援模式不会破坏数据 — 你只是在磁盘上读取/写入文件
  9. 使用封装脚本销毁 — 完全拆除集群时,从 Terraform 根目录运行 scripts/destroy.sh;它只会重试已知的 ingress-LB 分离竞态,并输出只读的孤立资源报告。仅将 scripts/cleanup.sh 用作强制回退方案;它会将令牌所属的整个 HCloud 项目视为专用于集群,因此请先检查试运行结果。

评论

支持 Markdown