☰
把K8s看作云原生时代的操作系统:核心概念、集群部署与排错实践
2026/10/7 11:16:23 网站建设 项目流程

把 K8s 叫做“云原生时代的操作系统”,我第一次听到这话的时候,觉得充其量就是个宣传用的比喻。直到我自己从零开始搭集群、一度被the api server is not healthy after 4m0.00747357s这类报错折磨到凌晨,才意识到这根本不是比喻,而是对 Kubernetes 工作方式的一种相当贴切的描述。操作系统的本质是管理硬件资源、调度进程、提供稳定的运行环境;K8s 干的事情本质上也一样,只不过它管理的不是一块 CPU、一条内存条,而是一批物理机或虚拟机组成的集群,调度对象也不是普通进程,而是容器。

这篇文章我会用“操作系统”这个视角去重新拆解 K8s 的核心概念,再把从初始化 Master 节点到加入 Worker、再到部署 Redis 集群时踩过的坑一条条摆出来。无论你是刚接触 k8s 的小白,还是已经看过一堆文档但还没完全串联起来的人,这篇文章大概都能帮你在脑子里建立一套清晰的地图。

1. 为什么说 K8s 是云原生时代的操作系统

1.1 从单机操作系统到集群“分布式内核”

我们先想想一台 Linux 操作系统的组成:内核负责 CPU 调度、内存管理、文件系统、网络协议栈,上层再跑各种进程、服务、用户态程序。你在单机上装了 Docker,其实只是有了运行容器的基础条件:Docker 负责把容器起起来、把镜像拉下来,但它管不了“如果这台机器挂了,容器去哪跑”“流量怎么分摊到多个容器上去”“容器多了怎么不互相抢资源”这类问题。

K8s 补上的正是这一层。它可以理解为一个跨越多台机器的分布式内核:把所有 Node 的 CPU、内存、磁盘、网络抽象成一个大的资源池,然后通过 API Server 统一分配容量、调度容器、维持状态。对上层应用来说,你不需要关心某个 Pod 到底落在哪台机器上,就像普通进程不需要关心自己的代码跑在哪个 CPU 核上一样。这种抽象能力,就是“操作系统”最核心的气质。

所以再回头看“云原生”这个词就顺了:云原生应用要求可弹性伸缩、可自愈、可平滑发布,K8s 这套“集群操作系统”正好把这些问题从应用层下沉到了基础设施层。应用只需要描述“我要跑几个实例、需要多少资源、如何暴露服务”,剩下的调度、重启、扩容、滚动更新都由 K8s 接管。

1.2 一次性讲清楚 k8s 和 docker 的区别

这是被问了最多次的问题,也是初学者最容易混淆的地方。Docker 是容器运行时,它解决的是“单个容器怎么构建、怎么运行”;K8s 是容器编排平台,它解决的是“成百上千个容器怎么协作、怎么调度、怎么保证不挂”。如果借用操作系统角色:Docker 更像是那个把程序打包成可执行文件并提供进程启动能力的运行时组件,K8s 则是进程管理器、资源调度器、服务发现系统三合一的“内核”。

实际使用中,两者并不是竞争关系,而是上下游关系。K8s 自己并不会直接运行容器,它通过 CRI(Container Runtime Interface)调用 containerd、CRI-O 这类容器运行时。Docker 更像是其中的一个兼容选项。也就是说,你仍然可以保留 Docker 的镜像构建流程,但容器在集群里的生命周期由 K8s 接管。

我见过不少新手把这两个混在一起排查问题,明明 Pod 一直没起来,还在反复用docker ps看容器状态。这类问题后面我会专门展开。一句话总结:Docker 管单机,K8s 管集群;Docker 是“进程”,K8s 是“操作系统”。

2. 核心概念分层拆解:像认识一个新操作系统一样学 K8s

2.1 集群骨架:控制平面和 Worker 组成一台“大电脑”

把 K8s 集群当成一台大电脑,很多概念瞬间就有位置了。控制平面(Control Plane)相当于这台大电脑的大脑和中枢,它由 API Server、Scheduler、Controller Manager、etcd 组成。API Server 是唯一入口,相当于内核对外提供的系统调用接口;etcd 是存储所有集群状态的数据库,相当于内存里的“进程表 + 注册表”,记录着哪个 Pod 在哪台机器上、哪个 Deployment 期望几个副本。

每个业务机器叫 Node,也可以叫 Worker。每个 Node 上跑着 kubelet、kube-proxy 和容器运行时。kubelet 相当于这台机器的守护进程,它跟 API Server 保持心跳,负责拉起和销毁本机上的容器;kube-proxy 负责维护网络转发规则,解决 Pod 与 Pod、外部与 Pod 之间的访问问题。用 Linux 做类比:kubelet 接近 init/systemd 的角色,kube-proxy 接近 iptables/netfilter 那一层。

理解了这套骨骼之后,你再看kubectl get nodes的输出就不会慌:Ready状态表明 kubelet 与 API Server 正常通信,NotReady 说明这台节点已经掉线或 CNI 网络没有装好。这也是我在实操中遇到的最常见起步问题,后面会提到。

2.2 最小调度单元:Pod 不是“容器”,是“容器组”

K8s 里最小的调度单位不是容器,而是 Pod。Pod 是深度绑定的一批容器,它们共享同一个网络命名空间、共享存储卷、并放置在同一个 Node 上。为什么这样设计?因为有些应用场景里,主进程和它的辅助进程必须挨在一起跑,比如一个 Web 服务和一个日志采集 sidecar,如果分开部署在不同机器上,就失去了“共享 localhost 网络”的优势。

可以这样理解:容器是进程,Pod 就是由多个进程协作组成的一个系统服务单元。Pod 相对于单容器多了一种被调度、被迁移、被健康检查的抽象。Pod 里每个容器的 CPU、内存请求加在一起,就是 Pod 的配额。调度器在看资源的时候也是以 Pod 为单位来操作。

但默认情况下,Pod 的 IP 是临时的,重启后可能就变了;这也是为什么你几乎不会直接创建 Pod,而是创建 Deployment 这类控制器。Deployment 负责告诉你:副本数保持 3 个,镜像版本是多少,如果某个 Pod 挂了立刻重建一个。这处世机制就很像 Linux 里的“服务守护”概念,只不过它守护的单位是 Pod。

2.3 Controller、Service、Namespace:保障、入口和隔离

再来一组和日常操作最相关的核心对象。

Controller(控制器)是一类对象的统称,Deployment、StatefulSet、DaemonSet、Job 都算。它们的作用就是“持续确保实际状态回归期望状态”。比如 Deployment 控制无状态应用,StatefulSet 控制需要稳定标识和稳定存储的有状态应用,Redis 集群就适合用 StatefulSet。你不用手动去删挂掉的 Pod,控制器会按声明式配置把集群状态拨回正轨。

Service 是稳定访问入口。Pod 会漂移、会重建,Service 提供了一个固定 VIP 和 DNS 名,让你无论 Pod 怎么变都能稳定地访问服务。它同时负责负载均衡,把转发给后端的多个 Pod。如果用操作系统的概念套一下:Service 有点像服务的“端口 + 路由规则”,让进程对外暴露得干净利落。

Namespace 则是隔离单位,相当于操作系统里的“用户组”或“多租户目录”。同一个集群内不同项目、不同环境可以用 Namespace 做资源配额、网络策略和权限边界。你以后看 K8s 生态时,几乎所有对象都有一个 namespace 字段,面试和排错都经常围绕它展开。

3. 实操:从初始化集群到跑通第一个服务

3.1 环境准备:版本搭配和运行时选型

我这次按平时比较稳的路径复现了一个集群,用的是 Rocky Linux 9 作为节点系统,Kubernetes 以 1.28 系列为例。老读者都知道我有一个习惯:先确定版本矩阵再动手,K8s、kubeadm、kubelet、kubectl 版本必须接近一致,containerd 和 CNI 插件也要和 K8s 版本兼容。

操作系统基础配置有一堆刚需,我会直接用命令做掉:关闭 swap(kubelet 运行期间如果开启 swap 会导致资源统计混乱,很多初始化报错由此而来),加载br_netfilter内核模块,开启 IPv4 转发,设置好sysctl参数。这些步骤对应的其实是操作系统底层的网络配置问题,很多人跳过之后就出现了 Pod 之间网络不通的怪象,所以必须重视。

3.2 初始化控制节点:一个关于 api server not healthy 的故事

接下来是重头戏,kubeadm init。我见过大量初学者在这里碰到同一类报错:kubeadm init执行到一半,提示the api server is not healthy after 4m0.00747357s。这不是一段多新鲜的报错,几乎每个人都栽过,但它背后的原因不止一种。

最常见的情况是控制面的几个静态 Pod 没有被正常拉起。K8s 控制面组件其实也是容器,kubeadm 会把它们以 static pod 的方式交给 kubelet 启动。如果 kubelet 和 containerd 之间的 CRI 配置不一致,比如 containerd 的 cgroup driver 还是cgroupfs,而 kubelet 要求systemd,那 kubelet 就死活拉不起 API Server 容器,等到超时就会打出“not healthy”的结论。

另一个常见原因是控制面所需镜像没有拉全或被网络问题卡住,导致 API Server 容器一直处于ImagePullBackOff。我个人的排错惯例是分三步走:

  1. 先看 kubelet 日志:journalctl -u kubelet -f,日志里通常能直接看到容器启动失败的真实原因。
  2. 再用crictl ps -a看容器运行时视角下的容器状态,确认 API Server 容器是根本没创建,还是创建了不断重启。
  3. 最后确认 containerd 的 CRI 配置和 kubelet 的 cgroup driver 一致。

如果确认是配置不匹配,直接改 containerd 的配置文件(通常位于/etc/containerd/config.toml),把SystemdCgroup设置为true,重启 containerd,然后执行kubeadm reset再重新 init。我踩过几次之后形成了肌肉记忆:任何和 kubelet 拉不起容器相关的报错,都不要先怀疑代码,先怀疑底层运行时连接。

这里有一段在我多次实操中比较关键的初始化取舍:--apiserver-advertise-address要填一个稳定可达的 IP,尤其是有多网卡的机器,不指定很容易选错内网 IP,导致后续 kubectl 连不上 API Server。--pod-network-cidr需要和后续装的 CNI 插件一致,比如用 Flannel 就用10.244.0.0/16,用 Calico 就用192.168.0.0/16之类,不一致会直接导致 Pod 网段冲突。

3.3 让工作节点真正干活:CNI 与加入集群

Master 初始化完毕之后,第一时间要装 CNI 网络插件。没有 CNI,Node 的状态大概率停留在NotReady,Pod 分配了 IP 也没法通信。可以理解为,容器有了独立网络设备,但还需要一个组件把各个节点的容器网络桥接起来,这相当于给“集群操作系统”配置好虚拟网卡。

Flannel 是我学习期用得最多的方案,命令就一条:

kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml

装完以后用kubectl get pods -n kube-flannel确认 Flannel Pod 运行正常,再kubectl get nodes,一般一两分钟内节点状态就会变成Ready。

Worker 节点加入集群时,kubeadm init 成功输出的末尾会有一段 join 命令模板,也可以在 Master 上重新生成:

kubeadm token create --print-join-command

在 Worker 上执行这段命令时,需要确保三件事:Master 的 6443 端口可达、Worker 的 kubelet 参数和集群一致、Token 尚未过期。Worker 加入后,kubectl get nodes应该能看到两个节点,状态都变成 Ready。此时这台由 Master 加 Worker 组成的“迷你集群操作系统”才真正能干活。

3.4 部署第一个应用:从镜像到对外暴露

集群就绪后,先部署一个简单的无状态应用,验证整个链路完整。我会用 Nginx 做例子:

apiVersion: apps/v1 kind: Deployment metadata: name: nginx-demo spec: replicas: 2 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.25 ports: - containerPort: 80

执行kubectl apply -f nginx.yaml后,系统会自动创建 2 个 Pod。注意,此时应用还只能在集群内部访问,需要再创建一个 Service 暴露它:

apiVersion: v1 kind: Service metadata: name: nginx-svc spec: selector: app: nginx ports: - port: 80 targetPort: 80

到这里,你已经体会到了“声明式操作系统”的本质:你只描述期望状态,而不是告诉它具体怎么做。K8s 自己负责把 Pod 安排到合适的 Node 上,自己负责对外暴露端口,运用即插即用的方式完成了操作系统本该干的事。

4. 高频问题和排错技巧实录

4.1 K8s 初始化失败:api server 不稳定的三类原因

我在前面已经讲了一个典型场景,这里干脆把它整理成一份速查手册。遇到api server is not healthy或者类似控制面组件一直起不来的情况,优先排查三个方向:

  1. CRI 不匹配:containerd 的 cgroup driver 和 kubelet 不一致,这是最常见的原因。
  2. 静态 Pod 没被正确生成:kubeadm init会在/etc/kubernetes/manifests下生成一组 yaml 文件,kubelet 会持续监听这个目录,如果 kubelet 没有文件系统权限、或者目录权限被改过,控制面组件永远不会被拉起。
  3. 镜像拉取问题:控制面组件镜像并没有在本地,容器运行时的镜像是从远端拉下来的,如果本地没有缓存且环境无法访问镜像源,容器就会一直等待固有超时。

排错不要死盯着那条 4 分钟超时日志,它只是一个“结果”。真正的原因在更早的日志里,通常是journalctl -u kubelet -f | tail -100里倒数几十行,耐心看。使用kubeadm reset之后重新 init 之前,务必确认节点上的/etc/kubernetes/manifests目录已经清干净,否则残留的静态 Pod 配置会影响二次初始化。

4.2 部署有状态应用:以 K8s Redis 集群为案例

集群稳定以后,很多人会尝试跑有状态应用,而 Redis Cluster 是很有代表性的挑战。Redis 集群要求每个节点稳定标识,重启后主机名和网络身份不能乱变,所以不能直接丢进 Deployment,必须用 StatefulSet。StatefulSet 中的每个 Pod 都会得到固定序号和稳定 DNS 名,比如redis-cluster-0.redis-cluster.default.svc.cluster.local,这个稳定标识对 Redis 集群内部的节点发现很关键。

实际操作中我的配置文件包含三个部分:Headless Service 给 StatefulSet 提供 DNS 解析;StatefulSet 定义了 3 个初始节点的容器配置,镜像用redis:7.2,在启动命令里开启cluster-enabled yes、设置cluster-config-file;用的数据卷必须绑定 StorageClass,否则 PVC 创建失败,Pod 会一直 Pending。

下面是一个简化但能跑通的片段:

apiVersion: apps/v1 kind: StatefulSet metadata: name: redis-cluster spec: serviceName: redis-cluster-headless replicas: 6 selector: matchLabels: app: redis template: metadata: labels: app: redis spec: containers: - name: redis image: redis:7.2 command: ["/bin/sh","-c","redis-server --cluster-enabled yes --cluster-config-file nodes.conf --cluster-node-timeout 5000 --appendonly yes"] ports: - containerPort: 6379

构造集群关系时,需要先进入任意一个 Pod 执行redis-cli --cluster create,把所有主从节点的 DNS 名写上,才能完成握手组网。它和自娱自乐的单机 Redis 完全不同,幂等性、网络依赖、存储持久化全部要照顾到。这也是我会直接指出的一个常见误区:只是起了 6 个 Redis 容器并不等于 Redis 集群,必须完成cluster create这一步让节点间互相通信达成主从协议。

4.3 排错速查表:把“操作系统”里的常见故障记下来

我的经验是,把 K8s 当成操作系统来学以后,排错方式也会发生变化。平时用 Linux,出问题时你会想“哪个进程挂了”“内核日志说了什么”;到了 K8s 里,就要换成“哪个 Pod 不对”“kubelet 日志说了什么”“整体期望状态是什么”。下面这张表是我最常翻的,也分享给你。

现象大概率原因首选排错路径
节点状态 NotReadyCNI 未安装、kubelet 异常kubectl get nodes+journalctl -u kubelet
Pod 一直 Pending资源不足、存储类不存在、节点亲和性不匹配kubectl describe pod <pod>看事件
ImagePullBackOff镜像名写错/不存在kubectl describe pod+crictl images
CrashLoopBackOff启动命令错误、探针失败、持久化卷只读kubectl logs <pod> --previous
api server not healthy静态 Pod 未起、CRI 不匹配检查/etc/kubernetes/manifests和 kubelet 日志
Redis 集群节点发现失败未用 StatefulSet DNS 名称组网确保 Headless Service 存在且 DNS 可解析

4.4 关于 k8s 安装部署的三条廉价建议

第一个建议是永远先看事件,kubectl describe系列命令是排错第一入口,比随便看网上结论可靠得多。第二个建议是把版本锁死,K8s、kubeadm、kubelet、kubectl 版本不齐是初学者最容易忽略的坑,一旦出现“接口不支持”或者“字段找不到”的问题,先看版本。第三个建议是保存好 kubeadm init 的完整输出和 join 命令,后面加节点、做升级都要用,建议直接写到笔记里。

5. 一些用下来值得记的个人经验

我今天的体会是,学 K8s 最难的不是背 YAML、记住各种对象,而是转变思维方式:你不再是“登录到这台服务器执行命令”,而是“向集群声明一个目标状态,让它去实现”。这听起来很抽象,但一旦你把它当成操作系统的核心机制去理解,很多行为就顺理成章了。

控制平面是内核,etcd 是系统状态库,kubelet 是节点代理,Pod 是进程组,Deployment 是守护服务单元。你在单机 Linux 上普通进程宕了,systemd 帮你重启;在集群里 Pod 挂了,Deployment 控制器也会帮你拉起。这种层层映射的感觉,越用越默契。

最后分享一个小技巧:我习惯在工作目录里同时维护好一份k8s-notes.md和一组排错命令别名,比如把journalctl -u kubelet -f、kubectl describe pod、crictl ps -a这三个高频命令写成别名。集群不会永远风平浪静,但你的排查路径足够顺手时,问题往往熬不过五分钟。希望这篇内容能帮你少走一段弯路,把 K8s 从一堆概念里真正捞出来。

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

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

立即咨询