
把 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 自己并不会直接运行容器它通过 CRIContainer Runtime Interface调用 containerd、CRI-O 这类容器运行时。Docker 更像是其中的一个兼容选项。也就是说你仍然可以保留 Docker 的镜像构建流程但容器在集群里的生命周期由 K8s 接管。我见过不少新手把这两个混在一起排查问题明明 Pod 一直没起来还在反复用docker ps看容器状态。这类问题后面我会专门展开。一句话总结Docker 管单机K8s 管集群Docker 是“进程”K8s 是“操作系统”。2. 核心概念分层拆解像认识一个新操作系统一样学 K8s2.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 版本兼容。操作系统基础配置有一堆刚需我会直接用命令做掉关闭 swapkubelet 运行期间如果开启 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。我个人的排错惯例是分三步走先看 kubelet 日志journalctl -u kubelet -f日志里通常能直接看到容器启动失败的真实原因。再用crictl ps -a看容器运行时视角下的容器状态确认 API Server 容器是根本没创建还是创建了不断重启。最后确认 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 网络插件。没有 CNINode 的状态大概率停留在NotReadyPod 分配了 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或者类似控制面组件一直起不来的情况优先排查三个方向CRI 不匹配containerd 的 cgroup driver 和 kubelet 不一致这是最常见的原因。静态 Pod 没被正确生成kubeadm init会在/etc/kubernetes/manifests下生成一组 yaml 文件kubelet 会持续监听这个目录如果 kubelet 没有文件系统权限、或者目录权限被改过控制面组件永远不会被拉起。镜像拉取问题控制面组件镜像并没有在本地容器运行时的镜像是从远端拉下来的如果本地没有缓存且环境无法访问镜像源容器就会一直等待固有超时。排错不要死盯着那条 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 nodesjournalctl -u kubeletPod 一直 Pending资源不足、存储类不存在、节点亲和性不匹配kubectl describe pod pod看事件ImagePullBackOff镜像名写错/不存在kubectl describe podcrictl imagesCrashLoopBackOff启动命令错误、探针失败、持久化卷只读kubectl logs pod --previousapi 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 从一堆概念里真正捞出来。