☰
Kubernetes节点为何需要不可变操作系统:极简、安全、无SSH的节点管理
2026/10/5 7:49:14 网站建设 项目流程

很多年前我刚接触 Kubernetes 时,一直有个疑问:Kubernetes 明明已经把服务封装成了不可变的容器,为什么承载它的 Linux 主机还是一台可以登录、可以随便改系统的“活机器”?后来在维护几十台节点的集群时,这个疑问变成了切肤之痛。今天要聊的,就是专门为 Kubernetes 打造的一类 Linux 操作系统——开源、极简、不可变,把安全当成第一优先级。它不再是一个可以随便操作的通用系统,而是一个被规格化、产线化的一次性基础设施。先理解它为什么出现,再动手用它,很多运维习惯都会发生改变。这篇文章适合正在为节点一致性发愁的 SRE、平台工程师,还有被安全审计追着跑的运维朋友。

1. Kubernetes 节点为什么不应该再跑通用 Linux

1.1 通用发行版把 kubelet 装进了一个充满变量的环境

很多团队从 Ubuntu 或 CentOS 起步,依赖包管理器把 kubelet、容器运行时和一堆工具装进节点。这个方式在单机、两三台节点时完全可行,规模上去之后问题才开始显现。通用发行版出于兼容大众场景的目的,预装了一大堆 Kubernetes 根本用不到的东西:计划任务、邮件组件、编辑器、开发工具链、各种驱动。这些东西不参与容器调度,却要和 kubelet 一起抢占内存和磁盘,更要命的是它们同样要打补丁、要维护。如果你的安全团队要求每月修复漏洞,那么面对几十台节点,你可能每个周期都要在“不重启影响运行”和“补丁必须生效”之间反复摇摆。

真正让通用发行版难以为继的,是“可变”。包管理器给了任何一个有 root 权限的人修改系统的能力,今天 A 管理员装个 tcpdump,明天 B 同事顺手把内核参数改了。这些改动大多没有沉淀成文档,也不会被版本化,于是集群里每一台节点都逐渐变成一件“孤品”。当你定位一个诡异问题,往往发现它只在某几台节点上出现,而这恰好就是那几台被改过某些东西的机器。kubelet 本身是设计成声明式的,你期望每个节点行为一致,但底下的操作系统却在不断漂移。一次普通的系统升级牵扯到依赖库变化,就可能破坏 kubelet 的启动链路,这种“例行维护搞挂节点”的案例,我遇到过不止一次。

1.2 不可变架构:把操作系统当镜像而不是当宠物

不可变基础设施这个词,最早是从服务端配置管理社区传出来的。它把一个重要经验推到极致:如果一台服务器是可以登录、可以修改、可以长期“生活”的,那你永远无法保证它的真实状态和你的配置定义一致;如果你根本不修改运行中的服务器,每次变更都通过替换镜像和重建实例去完成,那系统的行为就像打印出来的文件一样可预期。容器世界已经贯彻了这个理念,一个镜像打上 digest,谁拉下来运行都是同样的产物;不可变 Linux 操作系统就是把这个理念从工作负载进一步下沉到宿主机。

在这类系统上,根文件系统默认只读,没有我们习惯的包管理器,也不允许你在上面持续安装软件。你要做的事情是定义一份“我所期望的节点最终状态”,比如网络、存储、角色,然后让节点照着这份定义自己组装自己。后续所有调整都通过生成新配置或者新镜像来触发,而不是登录节点去就地修补。这样处理集群的好处很明显:节点不会因为某次人工操作而变得跟旁边那台不一样,也不会因为忘记某个补丁而留下后门。坏了一台节点,最常见也最合理的操作不是修,而是直接踢掉重新引导一台,让它变成和模板完全一致的副本。

因为操作对象从“这台活机器”变成了“这份配置和这套镜像”,日常运维里最耗费心神的几类问题就会被消解:配置漂移、漏更某个依赖、为了排查临时改动系统文件。集群的一致性第一次变得可以被版本化和自动化完整覆盖。这正是 Kubernetes 使用方在基础设施层缺失的最后一环。

2. 从设计上看,极简、API 驱动与安全默认

2.1 极简到什么程度才算合适

讲这类系统之前,我建议你先看一眼自己的节点现状:一个全新安装的通用发行版,启动后有多少个处于运行状态的服务?多半在 50 个以上。一个不可变 Kubernetes 专用系统的目标,是把宿主机上的活跃项压缩到一只手数得过来:内核、平台初始化服务、容器运行时、kubelet、网络插件。除此之外,没有多余的用户态服务,没有图形栈,没有编译器,甚至没有常规意义上的包管理工具。

有人会在第一眼看到一个小体积镜像时犹豫:“这么少的东西,真的够用吗?”恰恰是这种少,保证了你不需要为不需要的功能付出安全代价。举个实际感受:专用系统的内核在编译时就去掉了大量用不上的驱动和模块,默认只放开容器运行必需的能力;这意味着某天网络上爆出一个仅有少数发行版默认模块受影响的内核漏洞时,你的集群可能从结构上就不被打到。少,不只是为了省内存和磁盘,更是在攻击面这个维度上做了减法。

还有一点常被忽略:这类系统把“该留的不该留的”考虑得很清楚。磁盘分区通常划分成系统分区和存储分区,系统分区只读运行,存储分区才交给 etcd、容器镜像层这类运行时数据。你不需要操心“这台机器上到底哪些文件被改过”,因为系统分区根本没有可写权限。

2.2 用声明式配置管理节点,而不是 SSH

我们把目光放到日常管理入口。传统 Linux 的管理入口是 SSH,是不可或缺的。而这类 Kubernetes 原生系统的管理思想完全不同:节点启动时读取一份机器配置,这份配置被系统工具生成、校验、签名,之后决定节点的最终状态。节点加入集群后,管理员面对的是一个固定不变的 Kubernetes API,而不是一台随时可登录的 Linux。

作为示例,我拿这类系统中很典型的一份控制平面节点配置来演示。生成配置的工具会先从命令参数里接收集群名称和控制面地址,然后输出类似下面的 YAML:

machine: type: controlplane network: hostname: k8s-control-01 nameservers: - 10.0.0.2 interfaces: - interface: eno1 dhcp: true install: disk: /dev/sda cluster: controlPlane: endpoint: https://10.0.0.10:6443 network: dnsDomain: cluster.local podSubnets: - 10.244.0.0/16 serviceSubnets: - 10.96.0.0/12

我把这些字段换成人话:machine.type决定这台节点是控制平面还是普通工作节点;cluster.controlPlane.endpoint是你在 kubectl 配置里填的访问地址,可以是负载均衡器的地址,也可以是第一个控制面节点的地址;podSubnets和serviceSubnets是集群内部网段,必须提前规划好,不能和现有物理网络冲突。这份配置还能表达更细的内容,比如静态 IP、磁盘分区方式、内核参数、证书轮换等。在 GitOps 团队里,它们会被直接放进仓库,走代码评审流程。

节点的引导方式也很直接:把配置通过网卡的 PXE 启动、云平台的 user-data 或本地启动介质交给节点,节点首次通电后会自动应用配置并完成加入。整个过程不需要你像过去那样在机器旁边敲命令或者手工配置网络。

2.3 高度安全是从系统设计长出来的,不是靠事后加固

很多安全措施是装到系统之后再加的:装防火墙、关闭密码登录、加固 SSH、定期扫描漏洞。这套路没有错,但它经不起一个自然追问:既然一套通用系统需要这么多补偿动作,为什么不把危险入口直接去掉?这类系统给的标准答案很干脆:默认没有 SSH,也没有可登录的 shell 环境。我理解很多人听到这里心里发怵——没有 SSH 还怎么排查集群?我的实际体验是,操作入口被压缩成 kubectl 之后,绝大多数宿主机操作反而变得更简单、更规范了。想确认某台节点的状态,用kubectl get nodes -o wide和kubectl describe node;想看到某一个 Pod 的错误日志,直接kubectl logs;这些操作全都走经过鉴权的 Kubernetes API,连节点私钥都不再需要出现在运维同学的电脑里。

“默认没有 SSH”带来的连锁收益非常可观:没有可用的 shell,root 弱口令、密钥泄露、管理员误执行命令这些常见风险来源就没有附着点;根文件系统只读,即使某个容器被攻破并逃逸到宿主机,攻击者也很难改写系统分区、植入持久化后门或篡改后续的启动过程。需要把安全落地情况落在纸面上时,这份配置天然就可以提交给审计方,讲起来比一串手工加固命令有力得多。证书方面也不需要人去登录刷新,节点的 kubelet 证书会由系统组件在过期前自动轮换,时间同步一旦配置好,证书过期这种常见的集群断连事故基本可以从清单里划掉。

3. 实操:从裸金属到可交付集群的完整过程

3.1 开工前的准备动作

如果只看宣传语,你会觉得这套东西很神秘,但落地过程比传统方案还要线性。开工前我习惯先花半小时把准备工作列清楚。

第一,机器规划。控制平面至少一台,正式环境建议三台组成高可用;工作节点按业务量加。每台节点要有固定可达的地址,最好在 DHCP 里为它们的物理地址预留固定 IP,否则节点重启后地址漂移会给集群带来访问层面的麻烦。

第二,网络规划。集群内部两个网段要提前定好:Pod 网段和 Service 网段。这两个网段只在集群内部可见,但必须和公司的物理网络、云上的私有网络分隔开,不然路由会产生冲突。DNS 地址建议至少给两个,并且保证它们与节点实际能用的解析路径一致。

第三,时间同步。这里我要特意划重点。Kubernetes 内部大量使用双向 TLS 证书做身份认证,证书验证依赖各节点的时间,如果某台节点的时间偏差超过几分钟,它和其他组件之间的握手就会失败。不可变系统出厂时时间同步服务基本是开着的,但如果你所在的机房没有放行默认的 NTP 端口,需要提前在机器配置里指向内网时间服务器。我在不少集群里排查过一种现象:所有节点看起来正常,但某一台上面的 Pod 偶尔连接异常,如果节点时间偏了,就大概率能复现出这种异常。

第四,命令行工具和镜像准备。在管理机上安装与目标系统配套的 CLI 工具,把 ISO、云镜像或裸金属镜像下载到本地,同时规划好镜像仓库的访问方式。三台节点都用同一个镜像文件,配置差异交给机器配置去表达。

3.2 配置生成、初始化与加入节点的全流程

我以这类系统里一个比较有代表性的发行版为例,命令行工具生成配置的过程非常直观。下面是初始化一个集群的两条核心动作。

生成集群配置,命令大致是:

k8sos gen config k8s-demo https://10.0.0.10:6443

执行后会输出两个文件,一个是控制平面配置,一个是工作节点配置。控制平面配置用第一个控制面节点去引导,工作节点配置则由剩下的机器共用。不同产品的命令名可能不同,但思路一致:把集群名称、控制面访问地址和网段参数交给工具,工具替你生成带签名的机器配置。

拿到配置后,我把控制平面配置塞进第一台节点的启动介质,例如云平台上的 user-data 字段,或者裸金属环境下通过管理控制器挂载的虚拟介质。节点第一次通电后会自动去拉取镜像、按配置写盘,然后重启进入正式系统。当第一个控制面节点初始化完成,集群的 Kubernetes API 就已经对外提供服务了。这时候我会用集群配置文件验证一下状态:

kubectl get nodes

此时应该能看到第一个控制面节点,状态大概率是 NotReady,这是一个预期现象,因为网络插件还没有部署。你想让节点真正进入 Ready,需要接着安装网络插件,比如 Cilium、Calico 这一类组件。很多第一次接触不可变系统的人会被这一步吓到,其实它和你在普通 Linux 机群上安装网络组件没有任何区别。

工作节点加入就更省心了。我要做的就是把之前生成的工作节点配置同样塞进剩余的机器,逐台上电。因为这些机器的用途一致,配置完全一样,我会把它们当成一批“商品”,坏了任何一台,直接拿同样配置重新引导,不需要做任何手工初始化。节点加入后,在控制面执行kubectl get nodes就能确认已有多少节点进入 Ready,并看到每个节点上被分配的 Pod 网段。

3.3 升级、回滚与日常维护的心得

一句话总结不可变系统的升级:它不是原地打补丁,而是把整台节点的系统分区整体替换成新版本。和传统发行版相比,它更像把服务器的启动盘从旧版本换成了新版本,同时保留一部分运行时数据。我用这类系统的升级命令举例,它一般长这样:

k8sos upgrade --nodes=10.0.0.11 --target-image=registry.example.com/k8sos:latest

执行升级前我会做三件准备工作。第一,确认当前集群整体是健康的,比如所有节点状态正常、etcd 正常;第二,给 etcd 打一个快照,备份到节点故障后可以恢复的位置;第三,看一遍官方升级路径,确认当前版本之间不需要做额外迁移。升级过程会先拉取新镜像,写入另一个分区,然后重启节点并切到新分区。整个过程有一个特征值得你适应:重启是正常操作,不需要害怕。

一旦新版本有问题,回滚同样简单。系统保留了至少一个旧分区,你只要让节点回退到上一个分区启动,就能回到升级前的版本。这种 A/B 分区机制在传统包管理里几乎看不到,但它天然适合容器集群:你就是把宿主机也当成一份可以回滚的部署单元。日常维护中,我不会再关心某台机器上装了哪些软件包,我关心的是镜像仓库里保存的节点版本是否一致,以及工作节点模板有没有跟上最新安全修补。所有这些信息都可以通过命令行批量核对,而不再需要逐台登录去收集。

4. 常见问题与排查实录

4.1 高频问题速查表

我把实际维护中容易碰到的问题整理成了速查表,按现象分类。它未必覆盖所有场景,但能解决一大半急症。

现象可能原因处理思路
控制面节点初始化后 NotReady网络插件未部署安装对应组件,检查 Pod 网段是否冲突
工作节点加入后反复重启引导配置与控制面地址不匹配检查 endpoint 地址与端口,确认配置未被篡改
节点状态正常但 Pod 调度异常节点标签或污点不符合预期用 kubectl describe node 查看标签、污点和可用资源
证书相关握手失败节点时间同步失效校准时间服务器配置,让节点证书自动轮换
DNS 解析时好时坏机器配置中的 DNS 上游不一致统一配置 DNS 列表,将解析路径收敛到内网 DNS
升级后集群版本混用多台节点一起重启导致中间状态让所有节点版本对齐,必要时从备份恢复

表格里的问题其实可以归成两类:一类是网络没有按预期打通,一类是时间和证书问题。Kubernetes 的故障里证书相关的比例相当高,而在不可变系统里,解决证书问题的方式通常是检查时间同步和机器配置,而不是登录节点去手动生成证书。

4.2 调试方式的转变

没有 SSH 之后,我调试习惯的变化值得单独聊一段。以前遇到 CPU 被打满,我的第一反应是登录上去开个监控工具;现在我会先看这个节点上跑了哪些 Pod,再通过 Kubernetes 的指标接口在控制面统一观察。过去登录节点随意执行命令,其实常常把现场破坏了,等查完问题,这台机器已经不是出事时的样子;现在所有排查入口都经过 Kubernetes API,至少我能保证“看”和“改”是分离的,且每一步都有审计记录。

需要看节点上的容器运行时状态时,系统通常也会提供对应的命令行工具,比如标准接口crictl,你完全可以用它列出容器、拉取日志。因为默认没有登录 shell,你反倒更愿意把日志集中到外部系统,比如把节点日志和业务日志统一推送到日志平台。我有一条实操建议:部署这类集群之前,先把日志、指标和告警通道搭好,别等出事再考虑。线上出问题时,你会发现控制面能拿到的事件、状态、指标比你在某台机器上敲命令拿到的信息可靠得多。

4.3 我踩过的三个坑

第一个坑是时间同步。某次我图省事,机器配置里的 DNS 写了,但时间服务器没有改成内网地址,节点用的是默认公共时间源,而机房防火墙把它拦了。结果控制面一切正常,某个工作节点的 kubelet 和 API Server 之间的证书验证隔三差五失败,很长一段时间里我都在怀疑网络插件有问题。后来打开节点事件才发现,时间差已经跑到了几分钟。这事之后,我把时间同步检查放进了生成配置前的强制清单。

第二个坑是升级操作太激进。以为是传统包升级,顺手把三个控制面节点一起提版,结果 etcd 成员之间出现短暂的不一致,差点把控制面带挂。后来我学会克制:控制面节点逐个升级,每升完一个就观察几分钟,确认集群具备仲裁能力再动下一台。工作节点倒是可以分批来,但分批的颗粒度也不要太大,一次一批,观察完再继续。

第三个坑是安全感带来的松懈。不可变系统把节点层弄得非常干净,容易让人误以为整个集群从此高枕无忧。实际上工作负载还是可能因为镜像漏洞、凭据泄露被打穿,服务账号权限过大也是常见问题。安全运维的重心从“别登录节点”变成了“管好供应链和工作负载”,前者由架构解决,后者仍然要靠人持续跟进。

5. 什么场景适合切换,以及同类方案怎么选

5.1 适合切换的典型场景

动不动就几十台节点的集群,是最适合这套方案的。节点数量一旦上来,人工逐台维护的成本会快速上涨,而不可变系统把节点变成了同构的“耗材”,一致性由模板保证。追求 GitOps 的团队也会发现它和现有流程非常契合:从集群配置到机器配置都可以放进仓库,变更走评审与自动发布,不再依赖某个人的运维手感。安全要求比较严格的行业同样适合,少了 SSH 入口,基础设施审计自查的环节也会少很多。

资源受限的边缘场景也是它的主场。一个小体积的系统镜像,占用内存小、启动速度快,可以在很旧或很小的设备上跑起来。如果你需要同时交付很多个集群,比如给不同项目组各开一套环境,这套系统的价值就更明显了:节点模板一致、升级回滚统一、交付动作完全自动化,不需要为每一套环境重新写初始化脚本。

5.2 暂时别急着切换的情况

这不是万能药。如果你的业务里还有必须跑在宿主机上的非容器化进程,比如某个老旧的采集程序或专有数据库,不可变系统那种“只读、无包管理”的环境会让你非常难受。依赖厂商闭源内核模块的场景也要慎重,网卡驱动、GPU 驱动这类东西如果不能提前放进镜像,运行起来会很麻烦。团队如果严重依赖 SSH 长期远程维护节点,又缺乏把操作升级为配置的能力,切换成本会比其他团队高不少。

还有一类场景是合规层面指定特定发行版的。在这种情况下,不需要进行理念上的争论,把兼容性放在首位。我认为比较务实的做法是:先在边缘和测试环境跑这套方案,积累信心之后再评估正式环境,而不是一次性把历史包袱全部压上去。

5.3 对照参考:同类方案没有唯一标准答案

标题里描述的“开源、极简、不可变、安全”,并不是某一个项目的专属标签。我从实际项目里梳理出几个代表选项,放在一条线上。

方案与 K8s 集成深度管理接口适合谁
Talos Linux极深,面向 Kubernetes 原生设计无 SSH,靠 CLI 与 Kubernetes API纯容器化、追求自动化与高安全的团队
Flatcar Container Linux较深,自带容器运行时可选 SSH,常用 Ignition 配置需要保留一定宿主机控制力的运维团队
Fedora CoreOS中等,容器优先支持 SSH,Ignition 配置习惯红帽生态、需要灵活操作的团队

Talos Linux 是这类系统里比较有代表性的一个,它把节点管理完全收编进 Kubernetes 的视野,升级、重启都能通过命令行风格完成。Flatcar 保留了更多 Linux 痕迹,适合那些仍希望在宿主机上保留一定操作空间的团队。Fedora CoreOS 则更接近一个“容器化优先但依旧可登录”的通用底座。选型时不用追求最强的不可变,先问自己三个问题:团队能接受没有 SSH 吗?宿主机上还有没有必须存在的东西?安全审计希望我们交出什么样的一页纸?答案清楚了,选择自然就清楚了。

我自己的体会是,这类系统真正的收益要过一段时间才会大量显现。切换到不可变系统的最初两周,会觉得各种习惯被限制;等集群运行几个月后回头再看,你会发现已经很久没有因为“某台节点被人改过”而半夜起来排查了。把节点变成模板的一部分,让安全成为默认值,这是 Kubernetes 基础设施里很值得做的一次调整。如果你正计划新集群,不妨拿一台测试节点先跑起来,把机器配置放进版本库,感受一下“不用登录也能维护集群”的工作方式。我的最终建议是:先在非生产环境里跑通完整的升级与回滚演练,再决定要不要把这套模式推广到所有集群。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询