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

用 Kubespray 与 Terraform 搭建集群

Kubespray 会替你修复的,以及它并不知道的

在 TT Lab 中继续学习

一句话总结

kubespray 会自己调整 swap、内核模块和 sysctl,但对于被别人占用的端口,以及之后又把值改回去的配置文件,它一概不知。安装前检查,就是了解这条边界。

为什么需要它

一次 kubespray 安装需要几分钟。如果几分钟之后失败,原因通常在安装之前就已经存在了。而且这类失败的错误信息并不会指向原因。如果 10250 被其他进程占用,第一次 kubeadm init 就会被 preflight 的 [ERROR Port-10250] 拦住。然而 kubespray 认为第一次尝试可能已经启动了 kubelet,所以在重试时把 Port-10250 放进忽略列表后再次运行(roles/kubernetes/control-plane/tasks/kubeadm-setup.yml)。这样一来,说明原因的那一行只留在日志上方第一次尝试的输出中,而最后显眼的错误说的则是完全不同的话。首次重启之后,Pod 之间的通信中断了,而 kubespray 的日志却全是绿色。与其在安装之后反过来追查这样的事,不如在安装之前看看主机能接受什么,要便宜得多。

工作原理

Kubernetes 对节点的要求散落在官方文档中。汇总起来有四类。

스왑        kubelet 은 기본값(failSwapOn: true)으로 스왑이 켜져 있으면 시작을 거부한다
커널 모듈    overlay(containerd 의 기본 스냅샷터) · br_netfilter(브리지 트래픽이 iptables 를 거치게)
sysctl      net.ipv4.ip_forward = 1 · net.bridge.bridge-nf-call-iptables = 1
포트        6443(API) · 2379-2380(etcd) · 10250(kubelet) · 10257(controller-manager) · 10259(scheduler)

该代码块中的韩文说明依次为:swap 方面,kubelet 默认值(failSwapOn: true)是只要 swap 开着就拒绝启动;内核模块方面,overlay 是 containerd 的默认快照器,br_netfilter 用来让桥接流量经过 iptables;sysctl 方面是这两项网络参数;端口方面是 6443(API)、2379–2380(etcd)、10250(kubelet)、10257(controller-manager)、10259(scheduler)。

kubespray 通过 role 处理其中的前三项。roles/kubernetes/preinstall/tasks/0010-swapoff.yml 会删除 fstab 中的 swap 行,屏蔽(mask)swap.target 使其不会再次开启,并调用 swapoff -a。这项任务只在 kubelet_fail_swap_on 为真时运行,默认值为真。如果想使用 swap,也可以把这个值设为假,让 kubelet 以 swapBehavior: LimitedSwap 启动。br_netfilter 由 node role 加载,并保存为 /etc/modules-load.d/kubespray-br_netfilter.conf。ip_forward 之类的值写入 sysctl_file_path(默认 /etc/sysctl.d/99-sysctl.conf)。在 Ubuntu 上,这个文件是指向 ../sysctl.conf 的符号链接,所以 kubespray 会顺着链接写入 /etc/sysctl.conf(0080-system-configurations.yml),而开机时被读取的位置,仍然是名为 99-sysctl.conf 的那个顺序。

端口则不同。kubespray 中没有停止别人进程的任务,也不应该有。是什么占用了端口,是那台服务器自己的情况。所以端口要由人先看。ss -ltnp 会显示 PID,然后必须找到启动该 PID 的 systemd 单元,把它停止并 disable。如果只杀掉进程,Restart=always 的单元会在 2 秒内把它恢复。

sysctl 有顺序的陷阱。开机时 systemd-sysctl,以及人运行 sysctl --system 时的 procps,会按文件名顺序读取 /etc/sysctl.d、/run/sysctl.d、/usr/lib/sysctl.d 中的 *.conf,相同的键以后读取的值胜出。名称相同时,/etc 一侧胜出。所以即使在 k8s.conf 中写了 ip_forward = 1,只要 zz-hardening.conf 写了 0,现在是 1,重启之后就会变成 0。kubespray 使用的 99-sysctl.conf 也在同样的规则之内——数字排在字母之前,所以名称以字母开头的文件全都会在它之后被读取。

最后是 kubespray 自己的预检。0040-verify-settings.yml 用 Ansible 收集的事实(facts)来判定。如果控制平面节点的内存小于 minimal_master_memory_mb(1500MB)就会停止,如果是不受支持的发行版也会停止。而且如果不给出 ip 变量,就会把 API 服务器和 etcd 连接到 facts 的默认路径地址(default_ipv4)上。在有多个网络接口的服务器上,控制平面连接到错误网络的事故就出在这里。

在现场相遇的样子

本模块的实验 VM 中故意留下了三处痕迹。它们都是实际上很常见的形态。第一,在内存不足的年代,有人创建了 swap 文件并写进了 fstab。第二,旧的监控 agent 在 10250 上接收 HTTP——它与 kubelet 是同一个端口,所以安装之后 kubelet 会因 bind: address already in use 而反复重启。第三,安全检查以“禁止路由”为由,留下了把 ip_forward 写死为 0 的 sysctl 文件,它的名称以 zz- 开头,比任何 Kubernetes 配置都更晚被读取。

这三者中,kubespray 会自动解决的只有 swap 这一项。端口它不会动,sysctl 在安装的瞬间虽然会把值打开,但重启时 zz- 文件又会把它改回 0。所以现场的检查清单问的不是“设置了什么”,而是“重启之后也是这样吗”。

下一项实验要做什么

先记录修改之前的状态,把 swap 在现在和开机时都关掉。加载模块并应用 sysctl,确认因为 zz- 文件而使重启之后的值被改回去,然后修复它。找到占用 10250 的单元并停止它,收集 kubespray 所看的 facts,与最低内存比较。最后阅读 role 的代码,分辨这四个问题分别由谁来修复。