TT Lab
博客

한국어 English 日本語 中文

开始
博客

博客

闭网 k0s 多节点实测 2:2 台节点,在控制器停止 90 秒期间工作节点持续响应

状态:实测(2026-10-31)。 是在 TT Lab 实验 VM 中使用 k0sctl v0.33.1 搭建 k0s v1.37.1+k0s.0 测得的数据。2 台场景新建了 VM 从头运行了两次,两次的数据均已填入表中。闭网的搭建方法与 第 1 篇相同。节点地址已替换为示例网段(192.0.2.0/24)。编写规则:实测数据为基础,无法测量的标记为“未实测”,原因未确认的解释标记为“推测”。

目录

1. 先说结论

2. 为什么 2 台是“1 个控制器 + 1 个工作节点”

k0s 的多控制器架构使用 etcd。etcd 要求成员过半数同意才能接受写入。引用 etcd FAQ中的表格如下。

成员数 半数 可承受故障
1 1 0
2 2 0
3 2 1

2 个成员可承受的故障数与 1 个相同,但可能宕机的服务器却增加了一台。因此,“两台都是控制器”甚至不如 1 台控制器。为了检查而关掉一台时,剩下的一台的 API 连写入也会停止。在 2 台方案中实际需要抉择的是 控制器 1 + 工作节点 1 ,剩下的问题是控制器是否要兼任工作节点。

这次没有选择兼任。兼任通常在有 3 个节点时使用(第 3 篇)。关于在数据库和搜索引擎中测量相同问题的文章,有 为什么 2 台服务器实现不了 HA 系列。

3. k0sctl 搭建

1 台时是手动安装的,但从 2 台开始使用 k0sctl。它通过单个文件声明:从导入服务器通过 SSH 将二进制文件和包上传到各个节点、写入配置、生成加入令牌并连接工作节点。在闭网中使用的配置如下。

apiVersion: k0sctl.k0sproject.io/v1beta1
kind: Cluster
metadata: {name: k0s-two}
spec:
  hosts:
  - role: controller
    ssh: {address: 192.0.2.3, user: root, keyPath: /root/.ssh/id_ed25519}
    hostname: ctrl01
    privateInterface: eth1
    privateAddress: 192.0.2.3
    uploadBinary: true
    k0sBinaryPath: /opt/airgap/bin/k0s-v1.37.1+k0s.0-amd64
  - role: worker
    ssh: {address: 192.0.2.4, user: root, keyPath: /root/.ssh/id_ed25519}
    hostname: worker01
    privateInterface: eth1
    privateAddress: 192.0.2.4
    uploadBinary: true
    k0sBinaryPath: /opt/airgap/bin/k0s-v1.37.1+k0s.0-amd64
    installFlags: [--kubelet-extra-args=--node-ip=192.0.2.4]
    files:
    - {name: k0s-airgap-bundle, src: /opt/airgap/images/k0s-airgap-bundle-v1.37.1+k0s.0-linux-amd64.tar, dstDir: /var/lib/k0s/images/, perm: "0644"}
    - {name: apps-bundle, src: /opt/airgap/images/apps-bundle.tar, dstDir: /var/lib/k0s/images/, perm: "0644"}
  k0s:
    version: v1.37.1+k0s.0
    config:
      apiVersion: k0s.k0sproject.io/v1beta1
      kind: ClusterConfig
      metadata: {name: k0s}
      spec:
        api: {onlyBindToAddress: true}     # NIC 가 둘이면 필요하다(3.1)
        network: {provider: kuberouter, podCIDR: 172.20.0.0/16, serviceCIDR: 172.21.0.0/16}
        images: {default_pull_policy: Never}
        telemetry: {enabled: false}

只挑出对闭网有意义的行。

在导入服务器上运行 k0sctl apply 后,两次运行分别在 1 分 58 秒、2 分 44 秒(k0sctl 报告)内完成。即使使用相同的配置,每次运行也有这样的差异,原因未做深入探究。对于纯工作节点,由于 k0sctl 会通过“waiting for node to become ready”一直等到其变为 Ready,因此 apply 结束时工作节点已经处于 Ready 状态。控制器兼任工作节点时没有这种等待,这一点将在第 3 篇中再次提到。

3.1 坑:加入令牌指向了错误的地址

最初没有写 onlyBindToAddress ,只写了 privateInterface·privateAddress 。明明明确写了要使用内网地址,以为会成功,但工作节点加入却挂起了 172 秒,并以如下方式结束。

failed to connect to kubernetes api using the join token ...
The connection to the server <NAT 주소>:6443 was refused
조인 토큰이 엉뚱한 주소를 가리킨 이유 k0sctl v0.33.1, 노드 주소는 예시 노드 VM 마다 NIC 가 둘 enp1s0(기본 경로) NAT 주소, 모든 VM 이 같다 eth1(사내망) 192.0.2.3 privateAddress 만 줌 k0sctl: etcd 피어·node-ip 에만 spec.api.address 는 비워 둠 k0s: 첫 비루프백 주소를 고름 → NAT 주소 조인 토큰도 NAT 주소:6443 → 172초 뒤 connection refused onlyBindToAddress: true k0sctl 이 호스트마다 address 를 privateAddress 로 → 192.0.2.3 API 는 eth1 주소에만 bind 조인 토큰도 사내망 주소 → 2대 설치 1분58초, rc=0
图:如果只提供 privateAddress,API 地址将由 k0s 在默认路由网卡中选择;如果提供 onlyBindToAddress,k0sctl 将为每个主机填充内网地址

<NAT 주소> 是这些测试 VM 默认路由网卡的地址。由于 KubeVirt 的 masquerade 网络为所有 VM 分配了相同的 NAT 地址,因此当工作节点访问该地址的 6443 端口时,会访问到 自己。那里没有 API,因此遭到拒绝。

为什么令牌中会包含那个地址,已在 k0sctl 源码中得到确认。 configure_k0s.go将 privateAddress 用于 etcd 对端地址(spec.storage.etcd.peerAddress),但仅当 spec.api.address 为 true 时才填充 onlyBindToAddress 。 如果为空,则由 k0s 决定。根据 配置文档,默认值是“节点上找到的第一个非回环地址”,在此 VM 中即为默认路由网卡的 NAT 地址。加入令牌中便包含了此地址。

spec.api.onlyBindToAddress: true 提供 address 会同时改变两件事。k0sctl 会为每个主机将 privateAddress 填充为该主机的

,并且 API 服务器将仅绑定到该地址,而不是所有接口。k0s 配置文档也指出,对于“多宿主控制平面节点”,可能需要此选项。修改后,2 台安装在 1 分 58 秒内以 rc=0 结束。 config 在第 3 篇的 3 个控制器方案中,此方法实际上是唯一的出路。k0sctl 的 address 在集群中只有一个,因此没有位置为每个控制器填写不同的 #1026(无法为每个 HA 控制器指定不同的地址), #1035(工作节点无法通过私有地址连接到 API), #760(privateInterface 无法选择正确的地址)。

4. 停止控制器 90 秒会怎样

4.1 测量方法

在工作节点上运行两份 nginx(Deployment web)和 NodePort Service(30080)后,停止了控制器的 k0scontroller 服务。在导入服务器上大约每 5 秒记录两项内容:

大约第 20 秒时,杀死了工作节点中 nginx 最旧的进程,即主进程(pkill -o -x nginx)。这是为了观察在没有 API 的情况下容器死亡时由谁来重新启动它。90 秒过后,重新开启控制器并测量了 API 恢复所花费的秒数。

4.2 结果

컨트롤러가 90초 멈춘 동안 컨트롤러 1 + 워커 1, 약 5초 간격 18번(최종 회차, 실측) 0초 30초 60초 90초 22초: 워커의 nginx 마스터 kill API 18/18 실패 nginx 18/18 = 200 다시 켜자 API 는 3초(첫 회차 4초) 만에 돌아왔다 계속 된 것 이미 뜬 파드의 응답 워커의 kube-proxy 규칙 kubelet 의 로컬 재시작 (첫 회차에서 관측) 멈춘 것 배포 · 스케일 · 설정 바꾸기 워커가 죽었을 때 재배치 상태 보고(재시작 횟수 등) API 가 돌아온 뒤 반영
图:控制器停止 90 秒期间测量的 18 次 API 全部失败,工作节点的 nginx 全部返回 200。在第 22 秒杀死了 nginx 主进程
轮次 API 工作节点 nginx nginx 主进程 kill 后重启次数 控制器重启 → API 恢复
1 18/18 失败 18/18 = 200 0 → 1(在 API 恢复后报告) 4秒
2(最终) 18/18 失败 18/18 = 200 0 → 0 3秒

工作节点的 k0sworker 服务自始至终处于 active 状态。

服务持续运行的原因。 请求经过的路径全都在工作节点内部。将 NodePort 转发到 Pod 的 kube-proxy 规则、运行容器的 containerd 以及保护 Pod 的 kubelet 全都在工作节点上运行,控制器仅在决定“把什么放在哪里”时才需要。因此,已经启动的 Pod 即使没有控制器也能响应。

重新启动死去的容器。 在第 1 轮中,杀死 nginx 主进程后,重启次数从 0 上升到 1。由于 kubelet 拥有已经接收到的 Pod 定义和重启策略,因此即使没有 API 也能重新启动容器。只不过它无法将该事实报告给 API,所以该数字直到 API 恢复后才显示出来。在最后一轮中执行了同样的操作,但重启次数保持为 0。在此期间,nginx 的响应 18 次全部为 200。为什么这次没有被计入重启次数,原因未能确认(原因未确认)。因此,“kubelet 在没有 API 的情况下进行重启”仅作为在两次中观测到一次的事实记录。

停止的内容。 在没有控制器的期间,以下操作全都无法进行:

2 台配置的风险不是“服务停止”,而是 “在停止期间无法更改任何内容,且在此期间若工作节点挂掉则没有主体来将其复活” 。因此,在 2 台方案中,控制器的快速恢复即备份(第 6 节)比高可用性更为优先。

5. 添加和删除工作节点

添加。 在 k0sctl.yaml 的 hosts 中添加了 worker02 并执行了 apply。由于 k0sctl 是声明式的,因此它没有触碰已有的节点,仅运行了“Install workers”阶段,耗时 1 分 42 秒 · 2 分 31 秒 后 worker02 就变成了 Ready 状态。这就是第 3 篇中进行对比的「控制器 1 + 工作节点 2」架构。

减。 顺序很重要。

  1. kubectl drain worker01 --ignore-daemonsets --delete-emptydir-data 将 Pod 迁移到 worker02。因为需要有可迁移的目标,所以先执行了加节点。
  2. 在 k0sctl.yaml 的 worker01 主机中 reset: true 并执行 apply。k0sctl 在「Reset workers」阶段删除了该节点的 k0s,节点列表中也找不到 worker01 了。 24 秒 · 39 秒 耗时。

两个 nginx 实例在 worker02 上一直可用。重置时 failed to remove existing configuration /etc/k0s/k0s.yaml: file does not exist 虽然会出现警告,但由于工作节点上原本就没有 k0s.yaml,因此该警告无害。

6. 备份

k0sctl backup --config k0sctl.yaml
次数 耗时 文件
1 16 秒 k0s_backup_<epoch>.tar.gz 416KB
2 32 秒 440KB

k0sctl 会在控制器上运行 k0s backup 并将结果拉取到导入服务器。 k0s 备份文档显示,内部包含 etcd 快照、证书以及密钥。由于其中也包含集群 CA 的私钥, 备份文件必须像机密信息一样妥善保管。 不能因为身处封闭网络,就将该文件随便放在某个共享文件夹中。

恢复(k0sctl apply --restore-from)这次并未进行测量(未实测)。相关文档指出,恢复操作必须在控制器上进行,并且当恢复到不同 IP 的节点时,有单独的地址匹配步骤。要想在 2 节点架构中用备份来替代高可用性,接下来的工作就是测量恢复时间。

7. 总结

检查项 原因(本文实测结果)
如果是 2 节点,则控制器只有一个 etcd 2 成员能承受的故障数为 0,只要宕掉一个,写入就会停止
如果网卡(NIC)有两个, onlyBindToAddress: true 如果没有,加入令牌将包含默认路由地址,并在 172 秒后失败
k0sBinaryPath 如果缺少 files 路径或使用了 URL,导入服务器将尝试访问外网
通过「不可变」来应对控制器故障 API 18/18 失败期间服务 18/18 正常,恢复需 3~4 秒
减节点应在 drain 之后进行,遵循 reset: true 顺序时两个 web 实例不会中断
备份文件应作为机密信息保管 其中包含 etcd 快照和 CA 密钥
恢复时间需单独测量 本次未实测,但在 2 节点架构中,这就是恢复计划的全部

本文的实测可以在 TT Lab 的实训课程「封闭网络 k0s:1、2、3 节点及其之上的 Ceph 与 PostgreSQL」(网站路径 /course/k0s-airgap-multinode)的第二个模块「2 节点」中,使用相同的版本和相同的导入清单进行上手演练。你可以在真实的虚拟机上进行控制器停止、工作节点加减以及备份操作,并且评分系统会在每个阶段验证结果。

TT 小结。 班长(控制器)短暂离开座位,课堂(服务)也会继续进行。只是在此期间,不能调换座位,也不能引入新同学。而且,虽然选两个班长似乎会更让人安心,但如果规则是必须两人一致同意才能做决定,那么只要有一个人生病,就什么也决定不了啦!

登录后即可点赞

评论

还没有评论。

登录后即可发表评论