Docker与K8S关系全解:Kubernetes架构、核心组件与调度实践

发布时间:2026/10/27 5:02:58

Docker与K8S关系全解:Kubernetes架构、核心组件与调度实践 说个特别常见的场景某团队把一个跑了两年多的服务从物理机搬进容器环境终于统一了大家很开心结果第二周服务高峰扩容运维同事凌晨三点手动起容器、配端口、改上游转发改完发现新容器跑在了另一台机器上本地缓存全空接口直接超时。第二天复盘问题不在容器而在于他们只用了 Docker没有用任何编排层。这类事我听过的版本不下十种本质上都是同一个认知缺口Docker 解决的是单个容器怎么跑起来K8SKubernetes解决的是成百上千个容器怎么在几十台机器上稳定地跑下去。前者是造积木的手艺后者是让积木自动搭成房子的规则体系。这篇我想把 Docker 和 K8S 的关系彻底讲清楚重点拆 Master 节点和 Node 节点上到底跑着什么、每个组件守着什么职责再带你把架构图在本地真实跑起来而不是停在背诵组件名称的层面。1. Docker 到底是什么把运行环境打包成一块可搬运的积木1.1 从在我机器上是好的这个老问题说起要理解 Docker 存在的意义得先理解它替代了什么。传统部署一个 Java 服务你需要一台机器、装对的 JDK 版本、配好的环境变量、对的时区、对的字体库、对的 glibc 版本以及一堆因为历史原因装在系统里的依赖包。这些东西没有任何一份文件完整记录它们它们只存在于那台机器的磁盘上。开发在本地跑通测试环境跑不通因为测试环境的 JDK 是 1.8 而本地是 17生产又跑不通因为生产系统的中文编码配置不一样。Docker 给出的答案非常直接别去适配环境了把应用和它依赖的整个文件系统一起打包。产物就是一个镜像镜像在任何装了容器运行时的机器上解开里面看到的目录结构、库文件、环境变量完全一致。这跟虚拟机不一样——虚拟机连内核和硬件都模拟一遍启动要几十秒一个虚拟机镜像动辄几个 G容器只是宿主机上的一个被隔离的进程共用宿主内核启动毫秒级镜像可以做到几十兆。我通常用一个比喻解释虚拟机是自己盖了一整栋房子水电管网全独立容器是住进一栋公寓楼里的一间墙和门是自己独立的命名空间隔离但水电网内核是共享的。共享带来轻量也带来约束——你不能在容器里跑一个和宿主内核版本冲突的东西Windows 容器镜像没法直接跑在 Linux 宿主上。1.2 镜像分层与写时复制Docker 快在哪镜像不是一个大 tar 包而是一层层叠加起来的只读层。写一个 DockerfileFROM ubuntu:22.04 RUN apt-get update apt-get install -y curl COPY app /opt/app CMD [/opt/app/run.sh]每条会改动文件系统的指令产生一层。FROM那一层是基础镜像RUN apt-get那一层只记录新增了哪些文件、改了哪些文件COPY又是一层。最终镜像是一个层列表加上一份配置JSON每层有自己的内容哈希。分层设计带来两个非常实际的好处。第一是复用你本地有十个服务都基于ubuntu:22.04那基础层在磁盘上只有一份拉取时也只需要拉一次。第二是构建缓存改了COPY app这一层的代码前面几层直接命中缓存构建只需要几百毫秒。这也解释了一条非常经典的 Dockerfile 优化原则——把变化频率低的内容写在前面变化频率高的写在后面。很多新手把COPY . .放在RUN npm install之前结果每次改一行代码就得重新装一遍依赖构建时间从 30 秒变成 5 分钟。容器启动时Docker 在所有只读层之上再挂一个可写层所有写操作都落在这个可写层里这就是写时复制Copy-on-Write。底层镜像文件被读取时不复制只有第一次被修改时才复制一份到可写层。所以容器删掉可写层就没了数据全丢——这是新手最常见的数据丢失事故来源需要持久化的东西必须挂卷不能指望容器自己的文件系统。1.3 Docker 已经不是唯一的运行时了这里有个必须澄清的认知很多人以为K8S 底层就是 Docker这个说法从 2022 年之后就不准确了。Docker 引擎的架构在早期就做过拆分最上层是面向用户的 docker CLI 和守护进程中间是 containerd负责镜像和容器生命周期管理最下层是 runc真正调用内核的 namespace 和 cgroup 去创建进程。K8S 为了不绑定某一家实现定义了一套叫CRIContainer Runtime Interface的 gRPC 接口只要一个运行时实现了这套接口kubelet 就能用它。Docker 本身没有实现 CRI所以早期 K8S 里塞了一个叫 dockershim 的适配层去做转换这个适配层在 1.24 版本被正式移除。现在主流的做法是 kubelet 直接对接 containerd 或 CRI-O。对使用者来说这意味着什么你在 K8S 节点上用docker ps是看不到 Pod 容器的即使那个节点上恰好装了 Docker。要查容器得用crictl ps或者用kubectl get pods。这个坑我在现场见过太多次有人 SSH 上节点敲docker ps一片空白以为容器没起来慌了半小时。概念定位典型工具常见误用镜像只读的应用打包产物docker build / buildkit用 latest 标签上生产容器镜像的一次运行实例docker run把数据写在容器可写层容器运行时负责拉镜像、起进程containerd、CRI-O节点上docker ps找不到容器编排系统管理大量容器的调度与自愈Kubernetes把编排器当单机 Docker 用2. 单机 Docker 撑不住的时候编排需求是怎么长出来的2.1 三种典型的手工运维崩塌现场Docker 在单机上非常好用但业务一旦变成十几台机器、几十个服务、每天几十次发布手工操作就会以三种方式崩掉。第一种是调度崩掉。你手上有 5 台机器A 机器 CPU 满了B 机器空着你得自己判断该把新容器放哪台。机器少的时候凭感觉机器多了就得写脚本脚本一写就发现要考虑资源余量、亲和性、端口冲突、机器健康状态很快变成一个没人敢改的怪物脚本。第二种是自愈崩掉。容器进程挂了谁去重启它你可以写一个 systemd 单元或者一个守护脚本但如果整台机器宕机呢如果容器卡死不退出呢如果重启后又挂了 5 次呢这些策略组合起来就是一个小型编排器的雏形。第三种是发布崩掉。滚动更新时你要控制先起 2 个新的、等健康检查通过、再停 2 个旧的中途出错要能回滚。手工做一次能成做一百次几乎必然出错。这三种崩塌就是编排系统存在的全部理由。K8S 不是凭空设计出来炫技的它是把这三件事抽象成了三个核心机制调度器、控制器、声明式发布策略。2.2 声明式 API从我要执行什么到我要什么状态Docker 的命令行是命令式的docker run、docker stop、docker rm你告诉系统要执行什么动作动作执行完就结束了系统不记得你的意图。K8S 是声明式的你提交一份 YAML内容大概是我要 3 个副本镜像用这个占 100m CPU然后就不管了。K8S 内部会持续对比期望状态和实际状态不一致就动手修正。这个对比修正的循环叫调谐reconcile。这个思维切换是新手最大的坎。命令式思维的人会问我怎么让 K8S 重启这个 Pod 声明式的答案是你不用让它重启你删掉它副本控制器会发现少了一个自动补一个。 又有人问我怎么把一个节点上的 Pod 全停掉做维护 答案是 cordon drain让调度器别再往上放让驱逐控制器负责搬家而不是自己去docker stop。理解这一点之后很多行为就顺了为什么 K8S 的 YAML 里经常要写一些看起来重复的东西比如 selector 和 labels 要对应因为控制器就是靠标签选择器去认领它该管哪些对象的。2.3 编排器真正要解决的四个硬问题抛开一堆名词任何容器编排系统都得回答四个问题K8S 的架构其实就是这四个答案的物理落地。第一个问题这个容器该放在哪台机器上答案是 scheduler它负责看资源、看约束、打分选优。第二个问题放上去之后谁来保证它一直活着答案是 kubelet 加各种控制器kubelet 盯着本机的容器控制器盯着集群层面的副本数量。第三个问题多个副本之间怎么互相发现、怎么被外部访问答案是 Service 加 DNS给一组会变的 Pod 一个不变的虚拟 IP。第四个问题整个集群的状态存在哪、怎么保证一致答案是 etcd一个强一致的键值存储是集群唯一的事实来源。你在任何一份 K8S 架构图里看到的组件几乎都能对上这四个问题中的某一个。带着这个框架去看架构图比死记组件名有用得多。3. Master 节点拆解控制面这四件套各自守着什么3.1 apiserver唯一入口与它的无状态设计控制面最容易理解也最容易被低估的组件是kube-apiserver。它是整个集群的唯一入口所有组件之间的通信都走它没有任何组件绕过它直接跟 etcd 说话除了 apiserver 自己。apiserver 干的事可以拆成四件认证你是谁、鉴权你能不能干这事、准入控制这个请求要不要改写或拒绝、读写 etcd。这四步的顺序是固定的很多排错场景就卡在这条链路上——比如 Pod 创建被拒先分清是 RBAC 不给权限还是准入 webhook 把它拦了两者的报错完全不一样。apiserver 本身是无状态的它不缓存业务状态所有状态都在 etcd。这个设计是它能水平扩展的前提你可以起三个 apiserver 实例前面挂负载均衡任意一个挂了都不影响。但也因为无状态它的性能瓶颈通常在 etcd 的写入能力和 watch 连接的规模上而不是 apiserver 自己的 CPU。还有一个关键机制叫list-watch几乎所有组件跟 apiserver 交互都是先 list 拿全量然后 watch 拿增量。这比轮询高效太多但 watch 连接数是个实打实的资源集群规模大了之后 apiserver 的内存和连接数要重点盯。3.2 etcd存的是期望状态不是现状etcd是基于 Raft 协议的分布式键值存储它存的是整个集群的所有对象定义每个 Deployment 的 spec、每个 Pod 的 spec、每个 Service 的规则、每个 Secret 的内容。这里有个非常反直觉但极其重要的点etcd 里存的主要是你要什么而不是现在是什么。你提交一个 3 副本的 Deploymentetcd 里存的是 spec.replicas3 这个期望。至于现在到底有几个 Pod 在跑是由各个控制器算出来之后再以 Pod 对象的形式写回 etcd 的。Pod 的 status 字段确实也存回 etcd但它更新的频率远低于 spec。理解这一点能解释很多现象。比如你手工kubectl edit改了一个被控制器管理的 Pod 的副本数改完马上又变回去了——因为控制器的期望值没变调谐循环一跑就给你改回来。etcd 的运维有几个硬要求必须定期快照备份因为整个集群的记忆都在这里面必须用 SSDRaft 的写入对磁盘 fsync 延迟极其敏感集群规模建议奇数节点三个或五个Raft 靠多数派选举。我在现场见过控制面卡顿查了两天最后发现是 etcd 的数据盘和日志盘共用一块低性能云盘写入延迟抖动到了几百毫秒。3.3 scheduler两阶段调度与打分插件kube-scheduler的职责单一而关键给每个还没绑定节点的 Pod 挑一台 Node。它的工作分两个阶段。过滤阶段Filter先把不满足硬性条件的节点全排除掉资源不够的排除、节点有污点且 Pod 不容忍的排除、节点亲和性不匹配的排除、端口冲突的排除。跑完这一轮剩下的叫可行节点。打分阶段Score给每个可行节点打 0 到 100 分选出最高分。打分的维度包括资源均衡度尽量让各节点使用率接近、镜像本地性节点上已经有这个镜像的加分省一次拉取、Pod 拓扑分布同一 Deployment 的副本尽量分散到不同节点等等。如果所有节点分数一样就随机选一个。调度器是插件化的K8S 把它拆成了一套调度框架在 PreFilter、Filter、PostFilter、PreScore、Score、Reserve、Permit、Bind 这些扩展点上可以插自定义插件。这意味着你可以写自己的调度策略比如这个服务必须调度到带 GPU 标签的节点——虽然更常见的做法是直接用 nodeSelector 或 nodeAffinity 表达。一个实操细节如果没有任何节点通过过滤阶段Pod 会一直处于 Pending。这时候kubectl describe pod的 Events 里会明说原因比如0/3 nodes are available: 3 Insufficient cpu。看到 Pending 第一件事就是看这个 Events比猜快一百倍。3.4 controller-manager永不停止的调谐循环kube-controller-manager是一个进程里打包了几十个控制器的集合最核心的几个Deployment 控制器管 ReplicaSet管滚动更新。ReplicaSet 控制器保证实际 Pod 数等于期望数少了补、多了删。Node 控制器监控节点心跳节点失联超时后把上面的 Pod 标记为待驱逐。EndpointSlice 控制器把 Service 和 Pod IP 的对应关系算出来供 kube-proxy 消费。Job / CronJob 控制器管批处理任务的生命周期。所有这些控制器共享一个模式读期望状态读实际状态做差采取动作重复。这个循环每秒都在跑集群越大每个控制器的队列越长。这也是为什么大集群里控制器的处理延迟会成为一个需要关注的点。Node 控制器有个参数值得记住默认节点心跳失联 40 秒标记 NotReady5 分钟后开始驱逐 Pod。这个 5 分钟是可以通过启动参数调的但不要随便调小因为网络抖动几秒就驱逐会导致大规模误迁移。3.5 生产环境控制面高可用的三处关键配置单 Master 的集群只能用于学习和测试生产必须多副本。高可用有三处容易配漏关注点单节点做法生产做法漏配的后果apiserver 副本1 个3 个前挂负载均衡 VIP单点故障集群管理接口全挂etcd 部署与控制面同机单实例3 或 5 节点独立集群数据丢失风险恢复困难控制器选举默认开启多实例靠 Lease 选举单活多实例同时调谐产生冲突控制器选举这一点特别容易被忽略controller-manager 和 scheduler 都是多副本部署但同一时刻只有一个在干活靠的是 etcd 里的 Lease 对象做租约抢占。如果这个机制出问题你会看到两个实例同时动作产生一些很诡异的现象比如 Pod 被反复创建删除。4. Node 节点拆解真正干活的那一层4.1 kubelet 与 CRI容器到底是谁拉起来的Node 节点上最核心的组件是kubelet。它是一个跑在每个节点上的代理进程以系统服务的形式存在不是容器职责是把分配到本节点的 Pod spec 变成真正运行的容器。它的工作流程大致是watch apiserver 拿到分配给自己的 Pod 列表逐个对比本地实际状态然后通过CRI 接口调用容器运行时去拉镜像、创建沙箱、启动容器。健康检查livenessProbe、readinessProbe也是 kubelet 在跑——它直接向容器发 HTTP 请求或者执行 exec 命令不是通过网络中转。这里要强调一下 CRI 的意义kubelet 不关心你用的是什么运行时它只认接口。你在节点上装 containerdkubelet 的配置里写--container-runtime-endpointunix:///run/containerd/containerd.sock装 CRI-O 就换一个 socket 路径。这个解耦让运行时可以独立演进也让换运行时变成一件不需要重装集群的事。还有一个常被问的问题镜像拉取失败怎么办答案取决于imagePullPolicy。默认策略是标签是 latest 时用 Always其他标签用 IfNotPresent。生产上强烈建议不要用 latest 标签因为你根本不知道某个时刻拉到的到底是哪个版本回滚也无从谈起。用明确的版本号或者镜像 digest是基本功。4.2 kube-proxy 与 Service 的流量转发kube-proxy解决的是 Service 的实现问题。Service 给你一个固定的 ClusterIP但 Pod 的 IP 是会变的所以需要一个东西把访问 ClusterIP:Port翻译成访问某个 Pod IP:Port。kube-proxy 有三种工作模式iptables 模式当前默认在每个节点写 iptables NAT 规则流量到达时由内核直接做 DNAT转发到后端某个 Pod。优点是稳定成熟缺点是规则是线性的Service 数量上万之后规则链会很长匹配变慢。IPVS 模式用内核的 IPVS 做四层负载均衡用哈希表而不是链式规则大规模下性能更好支持更多调度算法。需要内核模块支持。userspace 模式早期实现流量要经过用户态性能差现在基本不用了。iptables 模式下有个细节值得知道默认的负载均衡是随机的靠statistic模块做概率匹配不是轮询。所以你在客户端看到的分发不完全均匀是正常的连接数一多就会趋于平均。另外kube-proxy 只负责服务发现和转发它不做健康检查本身——它消费的是 EndpointSlice 里被 readinessProbe 筛过的就绪 Pod。一个 Pod readiness 不过它会被从 EndpointSlice 里摘掉新流量就不会打到它。这条链路是理解为什么我的服务有一段时间返回 502的关键。4.3 Pod 为什么不是容器pause 容器的秘密新手最容易混淆的概念就是 Pod 和容器。Pod 是 K8S 的最小调度单元它是一组共享网络命名空间和存储卷的容器的集合。每个 Pod 起来的时候运行时首先创建一个叫pause也叫 sandbox的容器这个容器里只跑一个几乎不干事的进程作用是持有网络命名空间。后续所有业务容器加入这个命名空间于是它们共享同一个 IP、同一个端口空间可以用 localhost 互相访问。这个设计的直接后果是同一个 Pod 里的两个容器不能监听同一个端口会冲突。另一个后果是Pod 重建时 IP 会变因为 pause 容器是新的。这也解释了为什么 Pod IP 不能当稳定地址用。那什么时候该在一个 Pod 里放多个容器典型场景是边车模式主容器跑业务边车容器做日志采集、做代理转发、做配置同步。它们的生命周期必须一致共享网络和卷这种耦合关系用 Pod 表达最自然。反面例子是把前端 后端 数据库塞进一个 Pod这就完全跑偏了——它们需要独立扩缩容、独立发布、独立故障隔离塞一起等于放弃了 K8S 的所有好处。4.4 Node 状态上报与心跳超时kubelet 会定期向 apiserver 上报节点状态CPU 和内存容量、已分配量、镜像列表、节点条件Ready、MemoryPressure、DiskPressure、PIDPressure。上报分两种一种是定期更新 Lease 对象默认 10 秒一次这算轻量心跳另一种是定期更新 Node 的 status 字段默认 5 分钟一次。节点被判定 NotReady 的默认阈值是 40 秒没收到心跳。这时候调度器不再往这个节点放新 Pod但已有 Pod 还不会立刻被驱逐——要等 5 分钟的容忍期过去Node 控制器才把它们标记为待删除然后由 ReplicaSet 控制器在其他节点重建。这个机制在真实故障中表现如何假设一台机器断电上面的 Pod 大概 40 秒后节点变 NotReady5 分钟后开始在其他节点重建。也就是说故障恢复的实际时间窗口是 5 分钟级别不是秒级。如果你的业务要求更快的切换得靠应用层多活或者更激进的探针策略不能指望 K8S 的默认参数。5. 把架构图跑起来一次 kubectl apply 的完整链路5.1 写入阶段从 YAML 到 etcd 的那几百毫秒架构图看懂了但真正的理解发生在你能把一次请求的路径完整走一遍的时候。我们跟着kubectl apply -f deploy.yaml这条命令走。kubectl 先读本地 kubeconfig拿到集群地址、证书、当前上下文然后把 YAML 转成 JSON向 apiserver 发起 PATCH 或 POST 请求apply 走的是服务端 apply 或者 patch 逻辑取决于写法。请求到了 apiserver先过认证它验证客户端证书确认你确实是这个集群里的某个身份。再过鉴权RBAC 会检查这个身份有没有对apps/deployments资源的 create 权限。再过准入控制Mutating webhook 可以改写对象比如注入边车容器、加默认标签Validating webhook 可以拒绝对象比如检查镜像仓库白名单。全部通过之后apiserver 把对象序列化写入 etcd拿到一个 revision 号。到这一刻写入阶段就结束了。注意这时候集群里还没有任何容器被创建etcd 里只是多了一条我要 3 个副本的记录。5.2 生效阶段调度器、kubelet 与运行时接力etcd 写入触发 watch 事件Deployment 控制器收到通知发现期望副本数是 3但对应的 ReplicaSet 还不存在于是创建一个 ReplicaSet。ReplicaSet 控制器收到 ReplicaSet 创建事件发现期望 3 个 Pod 实际 0 个于是创建 3 个 Pod 对象写入 etcd。这 3 个 Pod 的spec.nodeName是空的所以调度器 watch 到它们开始跑过滤和打分选出一台节点把 nodeName 写进去这一步叫 Bind。目标节点的 kubelet watch 到有 Pod 被绑定到自己开始干活通过 CRI 调用 containerd 拉镜像如果本地没有创建 sandbox 容器pause配置网络调用 CNI 插件分配 IP、设置路由然后依次启动业务容器。每个容器起来后kubelet 开始跑 probes。容器真正 run 起来之后kubelet 把 Pod 的 status 更新回 apiserver写入 etcd。这时候kubectl get pods才能看到 Running。整条链路里任何一环出问题都会表现出不同的症状。这个对应关系我整理成了一张表排错时非常省时间现象大概率卡在哪一环排查入口apply 报错提示 forbidden认证或 RBAC 鉴权kubectl auth can-iapply 报错提示被 webhook 拒绝准入控制看 webhook 服务日志Pod 一直 Pending调度器找不到可行节点kubectl describe pod看 EventsPod 卡在 ContainerCreatingCNI 或镜像拉取kubectl describe pod节点上看 crictlPod 卡在 ImagePullBackOff镜像地址、凭据、网络kubectl describe pod检查 imagePullSecretsPod Running 但服务不通readiness 未过未进 EndpointSlicekubectl get endpointslice5.3 访问阶段ClusterIP 背后的 iptables 规则Pod 跑起来之后Service 的链路才开始工作。EndpointSlice 控制器把就绪的 Pod IP 写进 EndpointSlicekube-proxy watch 到变化在本节点上更新 iptables 规则。规则的形态大致是进入的流量如果目标是 ClusterIP:Port就跳转到一条KUBE-SVC-xxxx链这条链里按概率把流量分到若干个KUBE-SEP-xxxx链每个 SEP 链做一次 DNAT把目标地址改成某个 Pod IP:Port。所以本质上Service 就是一个分布式的 iptables 规则集没有中心化的负载均衡器在处理流量转发是内核在做。这也解释了为什么 Service 的 ClusterIP 是虚拟的——没有任何网卡上真的配了这个 IP它只是规则里的一个匹配条件。外部访问有三种常见方式NodePort 在每个节点开一个高端口映射到 ServiceLoadBalancer 依赖云厂商的负载均衡器Ingress 则是在集群里跑一个七层网关比如基于 nginx 或 envoy 的实现按域名和路径转发到不同的 Service。生产环境最常用的是 Ingress 或者更上层的 Gateway APINodePort 一般只作为临时调试手段。6. 手把手搭一个能看清架构的本地集群6.1 用 kind 起一个控制面加两个工作节点要看架构最好的办法是自己造一个。本地起 K8S 有几种选择minikube 适合单节点体验k3s 适合资源受限环境kind 的优势是每个节点就是一个容器能模拟多节点而且用的是标准 kubeadm 部署流程控制面组件都是静态 Pod观察起来特别直观。先准备配置文件# kind-arch.yaml kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 name: arch-lab nodes: - role: control-plane - role: worker - role: worker执行kind create cluster --config kind-arch.yaml kubectl get nodes -o wide你大概会看到三个节点名字是arch-lab-control-plane、arch-lab-worker、arch-lab-worker2角色分别是 control-plane 和 。这里的none不是说它不是工作节点而是没有打上node-role.kubernetes.io/worker这个标签看角色要看kubectl get nodes的 ROLES 列没标签的都算普通工作节点。有意思的是因为 kind 的每个节点本身是一个 Docker 容器你可以用docker ps看到它们docker ps --format table {{.Names}}\t{{.Image}}\t{{.Status}}这一层关系很值得玩味宿主机跑 DockerDocker 里跑着三个充当节点的容器节点里跑着 containerd 和各种 K8S 组件containerd 里才是你的业务容器。Docker 和 K8S 的关系在这里变成了一个可以亲手触摸的层级结构比看一百张架构图都直观。6.2 逐个核对控制面组件的静态 Podkubeadm 部署的控制面组件不是以系统服务形式跑的而是静态 Pod——kubelet 会监视/etc/kubernetes/manifests/目录目录里有 YAML 文件就自动创建对应 Pod文件删了 Pod 就没了。这个设计让控制面组件也能被 kubelet 统一管理不需要额外的守护进程。进控制面节点看一眼docker exec -it arch-lab-control-plane ls /etc/kubernetes/manifests/你会看到etcd.yaml、kube-apiserver.yaml、kube-controller-manager.yaml、kube-scheduler.yaml四个文件。再看 Podkubectl -n kube-system get pods -o wide四个组件的 Pod 名字后面都带节点名后缀比如kube-apiserver-arch-lab-control-plane状态是 Running而且 RESTARTS 计数应该是 0 或者很小。如果某个组件反复重启可以直接看它的日志kubectl -n kube-system logs kube-scheduler-arch-lab-control-plane顺手把 apiserver 的启动参数看一眼你会看到一堆--enable-admission-plugins、--authorization-mode、--etcd-servers之类的配置正好对上前面讲的认证、鉴权、准入、存储四件事docker exec arch-lab-control-plane cat /etc/kubernetes/manifests/kube-apiserver.yaml工作节点上的组件就不在 manifests 目录里了。kubelet 是系统服务kube-proxy 是以 DaemonSet 形式跑的普通 Podkubectl -n kube-system get daemonsets kubectl -n kube-system get pods -l k8s-appkube-proxy -o wide你会发现 kube-proxy 在每个节点上都有一个包括控制面节点——因为控制面节点默认也允许跑普通 Pod除非你打了污点。6.3 做一个能被观察的调度实验光看组件不动手理解还是浅的。我们来做一个能观察到调度行为的实验。先造一个 6 副本的 Deployment每个副本声明 100m CPU 的请求apiVersion: apps/v1 kind: Deployment metadata: name: probe spec: replicas: 6 selector: matchLabels: app: probe template: metadata: labels: app: probe spec: containers: - name: probe image: nginx:1.25 resources: requests: cpu: 100m memory: 64Mikubectl apply -f probe.yaml kubectl get pods -l appprobe -o wide --watch观察分布。你会发现调度器不是简单轮询它会让两个工作节点的负载尽量接近但如果两个节点的可分配资源本身不一样分布就可能不均。再试一下把其中一个节点封住kubectl cordon arch-lab-worker2 kubectl scale deployment probe --replicas10 kubectl get pods -l appprobe -o wide新起的 Pod 全都落在arch-lab-worker上了arch-lab-worker2上的 Pod 还在跑但不会再增加。cordond 的作用就是给节点打一个unschedulable标记调度器过滤阶段会直接跳过它。再狠一点把节点上的 Pod 赶走kubectl drain arch-lab-worker2 --ignore-daemonsets --delete-emptydir-data这条命令会逐个体面地驱逐 Pod同时遵守 PodDisruptionBudget。驱逐完成后ReplicaSet 控制器发现副本数不足会在剩下的节点上补。你可以用kubectl get pods -w看到整个迁移过程。做完记得kubectl uncordon arch-lab-worker2恢复。关于--ignore-daemonsets这个参数有个常见疑问为什么必须加因为 DaemonSet 的 Pod 是每节点一个的语义它不能迁移到别的节点去drain 遇到它只能跳过所以你必须显式声明我知道并且同意跳过。6.4 模拟节点故障看调谐循环比 cordon 更真实的是直接让节点失联。kind 里最简单的做法是停掉那个充当节点的容器docker stop arch-lab-worker2 kubectl get nodes --watch大概 40 秒后arch-lab-worker2的状态会从 Ready 变成 NotReady。但注意上面的 Pod 不会立刻被驱逐还是显示 Running虽然实际上已经不可达了。要等大约 5 分钟Node 控制器才把 Pod 标记为待删除然后 ReplicaSet 在其他节点重建。这 5 分钟你可以干点别的也可以手动观察 Pod 的状态变化。等它真的开始重建了恢复节点docker start arch-lab-worker2 kubectl get nodes节点会重新变成 Ready但上面原来的 Pod 已经被删了新的 Pod 分布在健康节点上。这时候如果节点恢复了资源后续扩容又会往上放。这个实验的价值在于让你对故障恢复的时间尺度有体感。很多人以为 K8S 是秒级自愈实际上默认配置下是分钟级。如果你的业务 SLA 要求更严需要调整的参数不止一个而且要考虑误判风险。7. 我踩过的那些坑几个流传很广的误解7.1 一个 Pod 塞多个容器这是新手最普遍的误解根源是把 Pod 当成了虚拟机觉得一个 Pod 里装的应用越多越省事。实际上一个 Pod 里的所有容器是同生共死的它们一起被调度到同一台节点一起被创建一起被销毁。你把前端、后端、缓存塞进去结果是前端要扩 10 个副本数据库也跟着扩了 10 个而且每一个都是完整的一份——数据一致性直接崩掉。判断标准很简单这两个容器需不需要独立扩缩容需不需要独立发布只要有一个答案是需要就该拆成两个工作负载。真正该合在一起的是那些生命周期强绑定、必须共享网络或文件的东西比如日志采集边车、服务网格代理。顺带说一个技术细节同一个 Pod 内的容器共享网络命名空间所以端口不能冲突。有人把两个都要监听 8080 的服务塞进一个 Pod结果第二个一直起不来报 address already in use还以为是镜像问题。7.2 以为 NodePort 能上生产NodePort 用起来很爽随便一个 Service 改成 type: NodePortkubectl get svc看到一个 30000-32767 的端口浏览器直接就能访问。于是有人就把它当成生产方案了。问题在哪NodePort 暴露的是每个节点的端口客户端得知道至少一个节点 IP。节点扩缩容、IP 变化客户端配置就得跟着改。而且它工作在四层没有域名路由、没有路径路由、没有 TLS 终止多个服务要共享 80 端口根本做不到。安全层面它也绕过了集群边界任何能访问节点 IP 的人都能打进来。生产上的标准做法是 Ingress 或者云厂商的 LoadBalancer 类型 Service。Ingress 在集群里跑一个七层网关外部流量只进这一个入口按 Host 和 Path 分发证书统一在这一层管。NodePort 的正确用途是临时调试、或者给 Ingress 控制器自身暴露入口。7.3 不写 resources.requests 的后果YAML 里resources那段很多人直接省掉能跑就行。短期确实没感觉集群规模一上来问题就全冒出来了。requests是调度器做决策的唯一依据。你不写调度器就认为这个 Pod 不需要任何资源于是往一个已经很满的节点上继续塞。塞到实际资源耗尽节点开始 OOMkubelet 触发驱逐你可能看到一堆 Pod 莫名其妙被干掉日志里就一句 evicted。limits则是运行时的硬约束CPU 超了会被 cgroup 限流表现为响应变慢但不会被杀内存超了会直接被 OOMKilled表现为容器反复重启退出码 137。这两个语义完全不同配的时候要分开想CPU 的 limit 可以设得比 request 高一些允许突发内存的 limit 建议贴近实际峰值因为内存不像 CPU 可以借用。我一般建议的起步值是先用压测或者历史监控数据估一个峰值request 设在 P50 到 P70 的水平limit 设在峰值往上留 20%。然后观察一段时间再调而不是一开始就拍脑袋。7.4 docker ps 看不到 kubelet 的容器这个坑前面提过但值得再强调一次因为它的误导性太强。节点上装了 Dockerdocker ps空空如也人就开始怀疑人生。原因是 kubelet 根本不跟 Docker 说话它对接的是 containerd。容器确实在跑只是不在 Docker 的视野里。要用 crictl# 在 kind 的 worker 节点容器里执行 docker exec -it arch-lab-worker crictl ps docker exec -it arch-lab-worker crictl images docker exec -it arch-lab-worker crictl logs container-idcrictl 是 CRI 的命令行客户端命令风格跟 docker 很像但语义不同——它操作的是 CRI 层面的沙箱和容器没有网络和卷的管理能力。排错时crictl ps -a能看到所有容器包括退出的crictl inspect能看详细配置crictl pods能看沙箱。这几个命令在节点层面排查问题时是刚需。还有一个相关问题节点磁盘被镜像占满。kubelet 有镜像回收机制默认磁盘使用率到 85% 开始回收但如果你手动docker pull了一堆镜像那些不在 containerd 的视野里不会被回收白占空间。用 crictl 管镜像别混着用。最后分享一个我自己一直用的理解方法把 K8S 想象成一个大型的、分布式的施工调度系统。etcd 是图纸档案室apiserver 是唯一收件窗口scheduler 是派工员controller-manager 是一群盯着进度表的监理kubelet 是每块工地上真正带工人干活的工头kube-proxy 是电话交换台。你提交一份需求单YAML剩下的活儿全由这套系统自己流转。每次遇到诡异问题我都会问自己一句这个请求现在走到哪个角色手上了大多数时候答案能直接把排查范围缩小一大半。
延伸阅读

更多相关文章

2026/10/27 4:57:58

YOLO模型演进:从v5到v11的技术突破与应用实践

1. YOLO模型演进全景图:从v5到v11的技术跃迁目标检测领域近年来最引人注目的技术路线莫过于YOLO系列的持续迭代。作为一名计算机视觉工程师,我完整经历了从YOLOv5到v11的每个版本升级过程。这个系列最令人惊叹的是其始终保持"速度-精度"双优特…

2026/10/27 4:57:58

CSS与JavaScript实现全屏图片展示技术详解

1. 全屏图片展示的核心实现思路全屏图片展示是现代网页设计中常见的交互效果,它能最大化利用屏幕空间,为用户提供沉浸式的视觉体验。实现这一效果需要同时运用CSS和JavaScript技术栈,两者各司其职又相互配合。CSS主要负责静态样式的定义&…

2026/10/27 4:57:58

Hadoop在碳排放分析中的分布式计算实践

1. 项目背景与核心价值碳排放分析正成为全球关注的焦点领域。随着各国"双碳"目标的推进,企业需要准确量化自身的碳排放情况。传统单机处理方式在面对TB级能源消耗数据时显得力不从心,这正是Hadoop分布式计算框架大显身手的场景。我去年指导的一…

2026/10/27 6:03:26

机器人隐式世界建模:FLARE框架原理与应用实践

1. 项目概述:当机器人学会"脑补"世界在机器人学习领域,我们正见证一场从显式编程到隐式建模的范式迁移。FLARE框架作为GR00T 1.5版本的核心前导技术,提出了一种革命性的思路:让机器人通过隐式世界建模(Implicit World M…

2026/10/27 6:03:26

OpenMontage 解析:基于 Remotion 与 Veo 的 agentic 视频自动化流水线

1. 从 OpenMontage 这个名字说起:它到底想解决什么问题第一次看到 OpenMontage 这个词,我脑子里蹦出来的第一反应是"开源蒙太奇"。Montage 在影视剪辑里指的是把一堆零散镜头按某种逻辑拼在一起,形成有节奏、有叙事的段落。把这个词…

2026/10/27 6:03:26

JIT即时编译与JVM性能调优:从热点探测到代码缓存实践

1. 从一段“越跑越快”的代码说起第一次对JIT产生实感,是在排查一个服务启动后前几分钟响应特别慢的问题。当时现象很怪:同一段业务逻辑,压测跑到第3分钟,平均耗时从80多毫秒降到了20毫秒出头,CPU占用反而还降了一点。…

2026/10/27 6:03:26

2026科研必备:6大AI工具提升10倍效率

1. 2026届科研人的AI工具生存指南实验室的灯还亮着,隔壁工位的博士生第3次把咖啡泼在了键盘上。这是我在剑桥做访问学者时常见的场景——全球顶尖学府的科研人员,依然在用石器时代的方式处理数据。直到去年我开始系统测试AI科研工具,才发现我…

2026/10/27 6:03:26

SpringBoot+Vue民宿管理系统源码:前后端分离毕业设计实战

简介:这是一套面向计算机相关专业学生与开发者的Java毕业设计完整项目包,采用SpringBoot与Vue前后端分离架构,实现民宿管理系统,涵盖房源信息、商家管理、用户管理、聊天互动、房源推荐等核心业务模块,适合作为毕业设计…

2026/10/27 5:53:02

Transformer架构解析:从自注意力到工业应用

1. 从信息过载到技术深挖:为什么我们需要理解Transformer那天晚上我第37次卸载抖音后,突然意识到一个残酷的事实:我们这代人正面临前所未有的信息过载危机。每次打开手机,算法就像读心术一样精准投喂内容,让人陷入无尽…

2026/10/26 0:20:20

全国计算机二级C语言真题PDF高效刷题指南

简介:这份PDF是全国计算机二级C语言考试的试题与答案解析,专为备考二级C语言的考生准备。内容涵盖C语言特点与设计理念、程序基本组成单位、main函数执行流程、函数嵌套调用规则、标识符命名规范、整型实型字符型数据类型、八进制十六进制数合法形式、运…

2026/10/26 1:53:47

requests+BeautifulSoup:爬虫抓取h1标题的入门实战指南

简介:一份以实战为导向的Python爬虫入门资源,面向具备基本编程基础、希望快速掌握网络数据抓取的初学者,可解决从零编写简单爬虫的常见困惑。实例以requests库发送HTTP请求、BeautifulSoup解析HTML文档,完整演示从网页中提取 标题…

2026/10/27 0:59:21

递推最小二乘参数辨识MATLAB编程:二阶系统与渐消记忆实现

简介:这是一篇面向控制系统、信号处理与机器学习等领域学习者的最小二乘参数辨识程序说明文档。资源共包含一个docx格式文档,压缩包大小七十八KB,内容围绕递推最小二乘算法和渐消记忆最小二乘算法展开:先构造一个极点位于单位圆内…

2026/10/27 0:02:28

WorkBuddy_新建任务与工作模式19_“模式选错”的典型代价

“模式选错”的典型代价 摘要 在软件工程中,设计模式、架构模式、并发模式乃至团队协作模式的选择,往往在项目早期就决定了后续的开发效率和维护成本。本文通过多个真实场景,剖析“模式选错”带来的典型代价——包括返工成本、技术债务累积、…

2026/10/27 0:02:28

WorkBuddy_新建任务与工作模式20_新手推荐路径

新手推荐路径 摘要 在 AI 辅助开发工具快速普及的今天,新手面对最多的困惑往往不是"工具够不够强",而是"我该从哪一步开始用"。本文提出一条经过实践验证的三阶段学习路径:先从 Ask(问答式交互)起…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑