TT Lab
开始
学习 学习路径 课程

Kubernetes 发行版 — 自己搭

想用 SSH 登录,却发现没有 shell

在 TT Lab 中继续学习

目标

只用 talosctl 和 API 来查看既没有 shell 也没有 SSH 的 Talos Linux 节点,在不重启的情况下修改并回退机器配置,并通过日志区分验证能拦住的错误和拦不住的错误。

为什么重要

k3s、k0s、kubeadm 是在 Linux 之上安装 Kubernetes 的方式,所以出了问题就登录节点看日志、改文件的习惯行得通。Talos 把操作系统本身重写成 Kubernetes 专用,没有 shell、SSH 和包管理器,根文件系统是只读的。 取而代之的是每个节点都有 gRPC API(apid),所有的确认和变更都通过这个 API 进行,机器配置则以带版本的资源形式保留。这种设计从根本上杜绝了“只有那个节点被人动过手脚的配置”这种雪花服务器,代价是登录进去修复的应急处理变得不可能。 因此,运维人员必须学会通过 API,区分配置在验证中被拒绝的情况,以及验证通过了服务却崩溃的情况。本实验会故意制造这两种情况。

步骤

  1. 对控制平面节点容器 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(布尔值)。
  2. 用 talosctl 读取两个节点(控制平面 10.5.0.2、工作节点 10.5.0.3)的服务列表,在 /root/talos-lab/services.json 中写入 controlplane、worker(各节点服务 ID 排序后的数组)和 controlplane_only(只存在于控制平面的服务 ID 排序后的数组)。
  3. 用 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,数字)。
  4. 用 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)。
  5. 在 /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 上必须带上标签。
  6. 用 --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 标签必须保留。
  7. 在 /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。
  8. 在 /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。
  9. 在 /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 个字符)。

参考

想用 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 调用,每一次成功的调用都会在日志中留下一行。评分器会在集群中重新统计这些数字。