关于 debug-node
用于排查 SSH 无法连接或配置卡住的 Hetzner Cloud 节点。该技能指导运维人员通过救援模式检查磁盘、诊断并修复问题,涵盖 Leap Micro 的事务式文件系统布局。
主要功能
- 01救援模式文件系统检查
- 02cloud-init 与 SSH 诊断
- 03Kubernetes 和 SELinux 日志检查
- 04活动快照修复指导
- 05Hetzner 命令行恢复步骤
适用场景
- 01排查无法访问的 Hetzner 节点
- 02诊断 cloud-init 配置失败
- 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
调用时,请询问:
- 服务器名称或 IP(可通过
hcloud server list查找) - 遇到的症状(SSH 超时、配置卡住等)
前置条件
- 已安装
hcloudCLI,并配置了有效令牌 - 服务器必须存在于 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 构建阶段:
- Rescue 模式: 将 qcow2 写入磁盘,然后重启
install_packages: 在transactional-update内执行——更改会保留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 未启动 | 配置或 SELinux | Journal + 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
专业提示
- 使用
subvolid=5挂载 — 获取真正的 btrfs 根目录,然后导航到@/.snapshots/N/snapshot/ - 编号最高的快照 = 活动快照 —
/etc就在其中 /var是独立的 — 日志和 cloud-init 位于/mnt/@/var/,不在快照内- 在系统未运行时查看 Journal —
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 项目视为专用于集群,因此请先检查试运行结果。