想用 SSH 登录,却发现没有 shell
目标
只用 talosctl 和 API 来查看既没有 shell 也没有 SSH 的 Talos Linux 节点,在不重启的情况下修改并回退机器配置,并通过日志区分验证能拦住的错误和拦不住的错误。
为什么重要
k3s、k0s、kubeadm 是在 Linux 之上安装 Kubernetes 的方式,所以出了问题就登录节点看日志、改文件的习惯行得通。Talos 把操作系统本身重写成 Kubernetes 专用,没有 shell、SSH 和包管理器,根文件系统是只读的。 取而代之的是每个节点都有 gRPC API(apid),所有的确认和变更都通过这个 API 进行,机器配置则以带版本的资源形式保留。这种设计从根本上杜绝了“只有那个节点被人动过手脚的配置”这种雪花服务器,代价是登录进去修复的应急处理变得不可能。 因此,运维人员必须学会通过 API,区分配置在验证中被拒绝的情况,以及验证通过了服务却崩溃的情况。本实验会故意制造这两种情况。
步骤
- 对控制平面节点容器
talos-default-controlplane-1用docker exec执行sh,并确认节点 IP 的 TCP 22 端口和 50000 端口是否开放。把结果写入/root/talos-lab/noshell.json,字段为container、node_ip(Docker 网络 talos-default 中的地址)、exec_sh_exit(docker exec 的退出码,数字)、exec_sh_error(错误消息的一行)、port22_open、port50000_open(布尔值)。 - 用
talosctl读取两个节点(控制平面 10.5.0.2、工作节点 10.5.0.3)的服务列表,在/root/talos-lab/services.json中写入controlplane、worker(各节点服务 ID 排序后的数组)和controlplane_only(只存在于控制平面的服务 ID 排序后的数组)。 - 用
talosctl get members读取成员,在/root/talos-lab/members.json中写入talos_version(10.5.0.2 服务器的 Talos 标签,例如 v0.0.0 形式)、members(主机名 →{"type": 머신 종류, "addresses": 주소 배열}(占位符依次为机器类型和地址数组))、worker_mc_version(工作节点 10.5.0.3 的 MachineConfig 资源v1alpha1的 metadata.version,数字)。 - 用
talosctl kubeconfig从控制平面(10.5.0.2)获取 kubeconfig 并保存到/root/talos-lab/kubeconfig(禁止使用符号链接),然后用这个文件运行 kubectl,在/root/talos-lab/cluster.json中写入api_server(该 kubeconfig 的 server 地址)、nodes(节点名称 → InternalIP)、kubelet_version、pod_subnets、service_subnets(控制平面机器配置中 cluster.network 的值)、overlaps_host(两个网段中只要有一个与宿主集群的 10.244.0.0/16 或 10.96.0.0/12 重叠,就为 true)。 - 在
/root/talos-lab/05-labels.yaml中编写一个向工作节点的machine.nodeLabels加入lab.talos.dev/pool: blue的 strategic merge 补丁,并只对工作节点(10.5.0.3)用--mode=no-reboot应用。把应用命令的输出(标准输出和标准错误)保存到/root/talos-lab/patch-out.txt,并在/root/talos-lab/patch.json中写入node、mc_version_before、mc_version_after(应用前后工作节点 MachineConfig 的 version)、container_started_at(工作节点容器talos-default-worker-1的 State.StartedAt)。Kubernetes 的 Node 上必须带上标签。 - 用
--mode=try --timeout=30s给工作节点应用标签lab.talos.dev/canary: "on"。应用之后立即读取工作节点 MachineConfig 的 version,确认 Kubernetes 的 Node 上出现了标签,然后等待回退、标签消失,再次读取 version。在/root/talos-lab/try.json中写入timeout_sec(数字)、version_during、seen_on_node(布尔值)、version_after_revert。第 5 步的pool标签必须保留。 - 在
/root/talos-lab/07-bad.yaml中编写一个向工作节点的machine.nodeLabels加入名称为lab.talos.dev/team name(带空格的名称)、值为platform的标签的补丁,并试着应用到工作节点。把输出保存到/root/talos-lab/rejected.txt,并把应用前后工作节点 MachineConfig 的 version 以version_before、version_after写入/root/talos-lab/rejected.json。 - 在
/root/talos-lab/08-kubelet.yaml中编写一个向工作节点的machine.kubelet.extraArgs加入max-pod: "150"的补丁,并应用到工作节点。被接受之后立即读取 version,用 talosctl 查看 kubelet 服务的状态和日志以找到原因,并在/root/talos-lab/kubelet-diag.json中写入accepted_version(应用后立即读取的工作节点 MachineConfig 的 version)、service_state(当时看到的 kubelet 服务的 STATE)、error_line(写有原因的日志的一行)。然后在/root/talos-lab/08-fix.yaml中编写并应用用$patch: delete只删除那个参数的补丁,让 kubelet 恢复健康,并使工作节点的 Node 回到 Ready。 - 在
/root/talos-lab/report.json中写入shell_in_node(布尔值)、ssh_port_open(布尔值)、api_port(Talos API 端口的数字)、worker_config_changes(工作节点 MachineConfig 自最初以来发生变化的次数 = 当前 version - 1)、worker_restarts(工作节点容器在第 5 步之后被重新启动的次数)、rejected_field(第 7 步中被验证拦下的配置路径,例如 machine.x 形式)、broken_service(第 8 步中崩溃的服务 ID)、apply_log_line(工作节点talosctl logs machined中记录了配置应用 API 调用的一行)、summary(不可变操作系统和基于 API 的管理在本实验中意味着什么,不少于 80 个字符)。
参考
- VM 中用 Docker 启动了两个 Talos v1.13.10 节点(控制平面 10.5.0.2、工作节点 10.5.0.3)。talosconfig 已经位于
/root/.talos/config,kubeconfig 已经位于/root/.kube/config。 - 指定节点:
talosctl -n 10.5.0.3 services— 如果省略-n,会因“nodes are not set”之类的错误而失败。 - 读取资源:
talosctl -n <ip> get members、talosctl -n <ip> get mc v1alpha1 -o yaml,JSON 用-o json | jq -s - 常见错误:放入 JSON6902(op/path)补丁。这个集群的配置是多文档的,所以会被拒绝。请使用 strategic merge YAML。
- 常见错误:在等待 try 模式期间放入其他 patch。回退会被取消。
- 在这台 VM 上,
talosctl dmesg显示的是 VM 自身的内核日志(容器模式)。服务的原因要到talosctl logs <서비스>(占位符为服务)中去找。 - 在 Docker 上启动 Talos · 机器配置的编辑与应用模式 · 配置补丁
想用 SSH 登录,却没有 shell
对控制平面节点容器 talos-default-controlplane-1 用 docker exec 执行 sh,并确认节点 IP 的 TCP 22 端口和 50000 端口是否开放。把结果写入 /root/talos-lab/noshell.json,字段为 container、node_ip(Docker 网络 talos-default 中的地址)、exec_sh_exit(docker exec 的退出码,数字)、exec_sh_error(错误消息的一行)、port22_open、port50000_open(布尔值)。
节点 IP 在 docker inspect 的 NetworkSettings.Networks 中。端口检查,可以用 bash 的 /dev/tcp/<ip>/<port> 配合 timeout 来尝试打开。退出码就是命令之后的 $?。如果在 set -e 之下,请用 || rc=$? 的形式接收。
没有 shell,取而代之运行的是什么
用 talosctl 读取两个节点(控制平面 10.5.0.2、工作节点 10.5.0.3)的服务列表,在 /root/talos-lab/services.json 中写入 controlplane、worker(各节点服务 ID 排序后的数组)和 controlplane_only(只存在于控制平面的服务 ID 排序后的数组)。
talosconfig 中没有设置默认节点,所以必须用 -n 选择节点。服务同时也是 runtime 命名空间中的 Service 资源,所以可以用 talosctl get services -o json 以机器可读的形式获取。JSON 输出是一个接一个的对象,所以用 jq -s 把它们汇总起来。
谁是这个集群的成员
用 talosctl get members 读取成员,在 /root/talos-lab/members.json 中写入 talos_version(10.5.0.2 服务器的 Talos 标签,例如 v0.0.0 形式)、members(主机名 → {"type": 머신 종류, "addresses": 주소 배열}(占位符依次为机器类型和地址数组))、worker_mc_version(工作节点 10.5.0.3 的 MachineConfig 资源 v1alpha1 的 metadata.version,数字)。
成员信息即使只问一个节点,也会得到整个集群的(discovery)。服务器版本是 talosctl version 中 Server 一侧的 Tag。机器配置也是资源,所以可以用 get machineconfig 读取,每次配置变化,version 都会增加——后面的步骤会用这个值做比较。
kubeconfig 也通过 API 获取
用 talosctl kubeconfig 从控制平面(10.5.0.2)获取 kubeconfig 并保存到 /root/talos-lab/kubeconfig(禁止使用符号链接),然后用这个文件运行 kubectl,在 /root/talos-lab/cluster.json 中写入 api_server(该 kubeconfig 的 server 地址)、nodes(节点名称 → InternalIP)、kubelet_version、pod_subnets、service_subnets(控制平面机器配置中 cluster.network 的值)、overlaps_host(两个网段中只要有一个与宿主集群的 10.244.0.0/16 或 10.96.0.0/12 重叠,就为 true)。
机器配置的正文可以用 talosctl get mc v1alpha1 -o jsonpath='{.spec}' 获取 YAML,多个文档用 --- 连接。网段是否重叠,可以用 Python ipaddress 的 overlaps 来判断。如果向同一个文件获取两次 kubeconfig,它不会覆盖而是合并(名称会带上 -1),所以只获取一次。
不重启就加上节点标签
在 /root/talos-lab/05-labels.yaml 中编写一个向工作节点的 machine.nodeLabels 加入 lab.talos.dev/pool: blue 的 strategic merge 补丁,并只对工作节点(10.5.0.3)用 --mode=no-reboot 应用。把应用命令的输出(标准输出和标准错误)保存到 /root/talos-lab/patch-out.txt,并在 /root/talos-lab/patch.json 中写入 node、mc_version_before、mc_version_after(应用前后工作节点 MachineConfig 的 version)、container_started_at(工作节点容器 talos-default-worker-1 的 State.StartedAt)。Kubernetes 的 Node 上必须带上标签。
修改机器配置的途径只有 API 这一个。talosctl patch machineconfig(简写 mc)会取得当前配置,合并补丁,再发送回去。补丁文件用 @파일(占位符为文件)传入。这个集群的配置是多文档的,所以 JSON6902(op/path)格式会被拒绝。是否没有重启,可以通过容器启动时刻是否保持不变来确认。
30 秒后自己回退的标签
用 --mode=try --timeout=30s 给工作节点应用标签 lab.talos.dev/canary: "on"。应用之后立即读取工作节点 MachineConfig 的 version,确认 Kubernetes 的 Node 上出现了标签,然后等待回退、标签消失,再次读取 version。在 /root/talos-lab/try.json 中写入 timeout_sec(数字)、version_during、seen_on_node(布尔值)、version_after_revert。第 5 步的 pool 标签必须保留。
try 模式在应用之后,如果在规定时间内没有其他配置变更,就会回退到之前的配置。回退本身也是配置变更,所以 version 会再增加一次。请注意,在等待期间如果放入其他 patch,回退就会被取消。
被验证拒绝的补丁
在 /root/talos-lab/07-bad.yaml 中编写一个向工作节点的 machine.nodeLabels 加入名称为 lab.talos.dev/team name(带空格的名称)、值为 platform 的标签的补丁,并试着应用到工作节点。把输出保存到 /root/talos-lab/rejected.txt,并把应用前后工作节点 MachineConfig 的 version 以 version_before、version_after 写入 /root/talos-lab/rejected.json。
机器配置在写入节点之前会经过验证。如果被拒绝,就不应该有任何变化。--dry-run 只显示合并后的结果,并不做验证,所以不能作为证据。离线时,可以用 talosctl machineconfig patch 把合并后的文件放入 talosctl validate -m container,看到同样的错误。
被接受了,kubelet 却崩溃了
在 /root/talos-lab/08-kubelet.yaml 中编写一个向工作节点的 machine.kubelet.extraArgs 加入 max-pod: "150" 的补丁,并应用到工作节点。被接受之后立即读取 version,用 talosctl 查看 kubelet 服务的状态和日志以找到原因,并在 /root/talos-lab/kubelet-diag.json 中写入 accepted_version(应用后立即读取的工作节点 MachineConfig 的 version)、service_state(当时看到的 kubelet 服务的 STATE)、error_line(写有原因的日志的一行)。然后在 /root/talos-lab/08-fix.yaml 中编写并应用用 $patch: delete 只删除那个参数的补丁,让 kubelet 恢复健康,并使工作节点的 Node 回到 Ready。
验证只看配置文档的格式,并不知道 kubelet 是否认识那个标志。由于没有 shell,原因要到 talosctl services、talosctl service <id> 的事件以及 talosctl logs <id> 中去找(占位符为服务 ID)。删除时,不要删除整个 extraArgs,而只删除那一个键。
没有 shell 的节点是怎样运维的
在 /root/talos-lab/report.json 中写入 shell_in_node(布尔值)、ssh_port_open(布尔值)、api_port(Talos API 端口的数字)、worker_config_changes(工作节点 MachineConfig 自最初以来发生变化的次数 = 当前 version - 1)、worker_restarts(工作节点容器在第 5 步之后被重新启动的次数)、rejected_field(第 7 步中被验证拦下的配置路径,例如 machine.x 形式)、broken_service(第 8 步中崩溃的服务 ID)、apply_log_line(工作节点 talosctl logs machined 中记录了配置应用 API 调用的一行)、summary(不可变操作系统和基于 API 的管理在本实验中意味着什么,不少于 80 个字符)。
依据前面步骤留下的文件和当前集群来写。配置应用是进入 machined 的 MachineService 的 gRPC 调用,每一次成功的调用都会在日志中留下一行。评分器会在集群中重新统计这些数字。