
做运维这些年K8s 算是绕不过去的一座山。很多人第一次接触 Kubernetes 的时候觉得它就是个“容器编排工具”装上、跑起来、部署几个 Pod 就算完事。但项目一旦进入生产环境体量上来、流量一大各种“幺蛾子”就全冒出来了——Pod 起不来、节点 NotReady、API Server 无响应、Etcd 频繁超时……这时候你回头看大部分问题的根源其实都埋在最初的配置和优化环节里。这篇文章不聊 K8s 基础概念也不抄官方文档。我会围绕实际部署和运维中真正需要动手的配置项、参数调优、常见故障排查思路展开把我踩过的坑、实测下来有效的优化手段以及为什么这样做、原理是什么都系统地整理出来。不管你是刚把集群搭起来正在做优化的新手还是已经在生产环境里背锅救火的运维老哥这篇文章都能给你一些可以直接落在现网上的参考。1. 部署前的配置选型把基础打好1.1 版本和发行版怎么选K8s 社区版本迭代非常快每个版本支持周期大约只有 14 个月左右。很多人图省事直接装了最新版结果踩了一堆组件兼容性的坑也有人图“稳定”守着两年前的旧版本不动结果 K8s 版本过老又遇到镜像仓库地址变更、API 资源版本废弃导致各类插件无法安装的问题。我的经验是在非必要不追新的前提下优先选择当前社区的稳定版本最好是上一个 GA 版本发布后的次版本或次次版本同时关注各组件CNI、CSI、Ingress Controller的兼容性矩阵。例如当前生态已经全面拥抱 containerd 运行时、EndpointSlice、Server-Side Apply 等特性。如果集群刚起步我建议直接用部署工具如 kubeadm 或 sealos安装原生 K8s而不是上来就套一层发行版的“壳”。发行版固然有配套的运维工具但它在参数的可见性和配置自由度上反而受限出了问题排查起来也会多一层隔阂。自主可控的裸集群配合 kubeadm更适合你理解这套系统的运行逻辑。发行版选择上还有一个容易忽略的点不同发行版对 CNI 和 CSI 的默认配置差异很大例如有些发行版默认使用 Calico 的 IPIP 模式有些默认用 VXLAN 封装还有的直接上 eBPF 数据面。这直接决定了节点之间的网络延迟和吞吐量后期再改数据面是很痛苦的。建议部署前就明确数据面模式生产环境至少考虑 BGP如 Calico 的 BIRD 模式或 eBPF如 Cilium 的 kube-proxy replacement 模式尽量不要用全封装模式跑大规模集群。1.2 节点的资源规划与预留K8s 有一个经常被忽视的细节节点上的系统进程sshd、systemd、监控 agent 等和 K8s 的系统组件kubelet、容器运行时、Pod 内的 pause 容器等同样要占用资源但这些占用不会体现在节点的可分配Allocatable计算中。如果你把节点的 CPU 和内存全部分配给业务 Pod一旦出现负载尖峰系统组件会先被“饿死”节点就会进入 NotReady 状态这是个非常隐蔽的故障源头。所以K8s 在计算节点可分配资源时预留了三个部分--system-reserved给系统进程、--kube-reserved给 K8s 系统组件、--eviction-hard给驱逐 Pod 留出的内存下限。我在实际部署中建议这样预留控制平面节点Master内存至少要预留 2-4GBCPU 预留 1-2 核。如果 etcd 和 API Server 在同一节点内存预留至少要放到 6GB 以上因为 etcd 在写密集场景下的内存抖动非常剧烈。工作节点Worker内存预留建议在 1-2GB 基础之上再叠加每 8GB 内存额外预留 1GB。CPU 预留按节点核心数的 5%-10% 划出比较稳妥。可选的临时存储预留现在大量业务使用 emptyDir而 emptyDir 是写在节点本地磁盘的临时存储的预留如果没有做好Pod 很容易被 Eviction。建议同时配置--eviction-hardnodefs.available10%,imagefs.available10%避免磁盘被镜像和临时文件写满。这一套规划在部署前就要写进 kubelet 的启动参数里因为节点已经注册到集群之后再改需要重重启 kubelet 甚至 drain 节点影响面很大。1.3 容器运行时与操作系统调优容器运行时从 Docker 切换到 containerd 已经成为事实标准K8s 1.24 之后默认也不再支持 Dockershim。这里有一个很多人忽略的点镜像的 storage driver 决定了 container 的写性能和 Pod 启动速度。overlay2 在 xfs 文件系统上的行为和 ext4 上有细微差别如果底层磁盘是 LVM 或 RAID建议底层分区格式化为 xfs且挂载参数加上pquota这样临时存储的配额限制才能生效。操作系统层面我强烈建议关闭 swap。K8s 自 1.22 之后虽然通过NodeSwap特性门控支持 swap但生产环境开启 swap 会让内存回收行为变得非常不可控etcd 和 API Server 这类对内存延迟极度敏感的组件一旦发生 swap 抖动表现的往往不是“慢”而是直接超时。所以稳定优先swapoff -a并且注释/etc/fstab里的 swap 条目这是最省心的做法。另外systemd的 cgroup driver 和容器运行时的 cgroup driver 必须保持一致这是 kubelet 初始化时的硬校验。现在主流都推荐 kubelet 的cgroupDriver: systemdcontainerd 配置里也要对应设置SystemdCgroup true。这个不一致导致的报错很常见症状是节点可以注册但所有 Pod 都卡在 ContainerCreating查 kubelet 日志会看到failed to run Kubelet ... misconfiguration: kubelet cgroup driver: cgroupfs is different from docker cgroup driver: systemd。2. 集群初始化阶段的配置细节与排错2.1 kubeadm init 的参数设置初始化集群的命令里最常见的错误就是直接kubeadm init不指定参数。虽然它能“跑起来”但默认参数会导致--apiserver-advertise-address绑定到节点的内网 IP在有多网卡的机器上会绑定错--pod-network-cidr不指定后续装网络插件时十有八九要冲突--service-cidr默认是/12如果和公司内部网段冲突后面所有 Service 访问都变成不可达。推荐我实际用于生产环境的初始化参数kubeadm init \ --kubernetes-versionv1.28.2 \ --control-plane-endpointk8s-master.example.local:6443 \ --apiserver-advertise-address \ --pod-network-cidr/16 \ --service-cidr/16 \ --image-repositoryregistry.aliyuncs.com/google_containers \ --cri-socket/run/containerd/containerd.sock \ --upload-certs注意--control-plane-endpoint建议用负载均衡器的域名或内网域名而不是单点 IP。这样后续如果控制平面要扩容或者做高可用不需要重新生成证书和配置走 kubeadm join 时直接指定同一个 endpoint 就行。--upload-certs的作用是把证书分发给后续加入的 Master 节点多控制面部署时非常省事。--pod-network-cidr这个参数要根据你选用的 CNI 插件去配置。例如 Calico 默认可以使用/16但如果你要在 Calico 里启用 BGP 模式做跨网络路由这个 CIDR 要和物理交换机的路由表整体规划不能随便填一个。规划错了跨节点 Pod-to-Pod 通信直接不通排查过程极其痛苦。2.2 “API Server is not healthy”排查实录热词里那个the api server is not healthy after 4m0.00747357s是所有新集群初始化时最常遇到的报错。这个报错背后的逻辑很简单kubeadm 在初始化完成后会做一次健康检查如果 API Server 在超时时间内没有返回/healthz就直接判定初始化失败。但问题在于它只告诉你“API Server 不健康”没有告诉你“为什么不健康”。我遇到过的原因有两类第一类是pod 网络和容器运行时异常。例如 kube-apiserver 因为无法启动原因可能是--etcd-servers指向的地址不对或者 etcd 本身起不来。这个时候第一时间要去看容器运行时的状态crictl ps -a journalctl -u containerd -n 100如果看到 etcd 容器反复 CrashLoopBackOff优先检查 etcd 的数据目录/var/lib/etcd是不是因为上次初始化失败留下了脏数据。解决方法是rm -rf /var/lib/etcd kubeadm reset -f kubeadm init --...第二类是资源不足导致 API Server 容器被杀。Master 节点只有 2G 内存的话API Server 启动后做证书签署和 controller-manager 周期性 list-watch内存瞬间能飙到 1GB 以上只要触发了系统 OOM整个集群初始化就彻底失败。而且因为 cgroup 参数有问题有时容器显示退出了但在 dmesg 里能看到oom-killer的记录这个细节容易被忽略。排查务必遵循顺序先看crictl ps -a里 API Server 容器是否存在存在则看日志/var/log/pods/.../kube-apiserver/...或crictl logs容器没有则查 kubelet 日志日志中没有明显错误再看内核 OOM 记录和磁盘空间。不要一上来就kubeadm reset否则日志全没了排查等于从零开始。2.3 网络插件选型与配置CNI 插件的选型和配置很大程度上决定了集群性能和稳定性。我在生产环境主要推荐 Calico 和 Cilium 二选一。Calico 的优势是 BGP 模式支持大规模网络路由可以做到无封装转发性能和物理网络没有数量级差距。配置上注意两点POD 网段和 Service 网段不能重叠否则会产生路由黑洞如果要启用跨集群的 BGP 互联需要在 calico-node 的环境变量里配置IP_AUTODETECTION_METHODinterfaceeth.*避免在多网卡机器上选错网卡。Cilium 的优势是 eBPF 数据面可以完全替换 kube-proxyService 负载均衡在内核态完成延迟低且 connect 建立快。但它对内核版本要求高。如果你的节点内核低于 5.10eBPF 的有很多特性起不来会退回 tc 模式性能优势大打折扣。Cilium 安装时通过 Helm 配置helm install cilium cilium/cilium \ --namespace kube-system \ --set kubeProxyReplacementtrue \ --set routingModenative \ --set ipv4NativeRoutingCIDR/16注意routingModenative和 IPIP 封装的差异。native 模式要求节点之间具备三层路由如果底层网络隔离了容器网段就还是得用隧道模式。很多人在云环境用 Cilium发现跨节点通信不通十有八九就是底层 VPC 路由表没有放行容器 CIDR。2.4 工作节点加入集群的配置校验kubeadm join其实比 init 更容易出问题。最常见的是 token 过期或证书不同步。默认 token 有效期是 24 小时如果你把初始化输出丢了加入节点时再跑kubeadm join就会提示This node has already been created或 token 无效。解决办法是重新生成 token 和证书哈希kubeadm token create --print-join-command openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2/dev/null \ | openssl dgst -sha256 -hex | sed s/^.* //加入节点之前还需要校验工作节点的机器参数和 Master 保持一致性。这里最容易忽略的一项kubelet 的 node-ip 参数。如果工作节点有多网卡且没有指定--node-ipkubelet 会默认注册第一个网卡地址。如果这个地址和集群网络上其他节点不可达Pod 调度到这个节点上就永远不会 Running。推荐在加入节点时使用显式的配置kubeadm join k8s-master.example.local:6443 \ --token xxx --discovery-token-ca-cert-hash xx \ --cri-socket/run/containerd/containerd.sock kubeadm join 之后手动修改 /var/lib/kubelet/kubeadm-flags.env 和 /etc/systemd/system/kubelet.service.d/10-kubeadm.conf很多人的理念是“能 join 成功就行”忽略了 join 成功之后节点状态、功能是否正常。加入后建议立刻执行以下检查kubectl get nodes确认节点Ready。kubectl get pods -A确认有没有 Pending 或 CrashLoopBackOff。在 Master 上登录到新节点上手动牛刀小试创建一个测试 Pod 并调度到该节点上确认网络连通。3. 性能与稳定性优化的几个刀口3.1 内核参数与系统层面的优化K8s 的很多网络问题根源不在 K8s 本身而在 Linux 内核的网络栈参数。尤其在高并发、大量短连接如 HTTP 健康检查、频繁 Service 跳转场景下连接表满、超时回收缓慢、拥塞窗口收缩都会导致请求偶发超时。我常用的一套内核优化参数如下cat EOF /etc/sysctl.conf net.ipv4.tcp_max_syn_backlog 8192 net.core.somaxconn 65535 net.core.netdev_max_backlog 65536 net.ipv4.tcp_tw_reuse 1 net.ipv4.ip_local_port_range 1024 65535 net.ipv4.tcp_fin_timeout 15 net.ipv4.tcp_max_tw_buckets 4096 net.ipv4.tcp_slow_start_after_idle 0 net.ipv4.neigh.default.gc_thresh1 2048 net.ipv4.neigh.default.gc_thresh2 4096 net.ipv4.neigh.default.gc_thresh3 8192 EOF sysctl -p其中tcp_max_syn_backlog和somaxconn是应对瞬时高并发连接的关键参数。默认 128 的 backlog 很容易在 Pod 快速伸缩、滚动更新时出现连接拒绝。tcp_tw_reuse和tcp_fin_timeout缩短 TIME_WAIT 状态的时间能释放更多端口尤其在 Service 端口模式是 ClusterIP 时请求从节点到 Pod 再返回一个请求可能会产生多次连接复用端口数量不够直接表现为“间歇性 connection timed out”。这里有一个特别注意的点net.ipv4.ip_local_port_range不要盲目调到最大值。虽然范围越大并发端口越多但端口范围扩大后conntrack表项也会随之膨胀如果nf_conntrack_max没有同步调大反而会触发 conntrack 表溢出引发更诡异的丢包问题。我建议同步调整echo 1048576 /sys/module/nf_conntrack/parameters/hashsize sysctl -w net.netfilter.nf_conntrack_max1048576conntrack 满的问题在 K8s 环境中非常典型。症状是节点上的访问偶尔不通ping 却通tcpdump 能看到 SYN 包有发出但无回包查dmesg | tail有nf_conntrack: table full的日志。这个问题排查成本极高参数要提前预留好。3.2 etcd 的运行参数优化etcd 是整个集群的状态中枢所有 Pod、Service、ConfigMap 等对象的数据都保存在 etcd 里。它的性能直接决定了集群的响应延迟。etcd 最敏感的三个指标磁盘写延迟、网络往返时延、Raft 心跳间隔。如果你的 etcd 使用的是普通云盘或共享存储fsync 延迟高那么每次写提交都要等待磁盘确认整体 API 请求延时会暴增。硬件选择优先是本地 NVMe SSD如果必须使用网络盘至少要保证单盘 IOPS 高于 3000且延迟低于 2ms。软件层面etcd 有一些非常实用的配置# /etc/etcd/etcd.config.yml heartbeat-interval: 100 election-timeout: 1000 snapshot-count: 10000 max-snapshots: 5 max-wals: 5 quota-backend-bytes: 8 auto-compaction-mode: periodic auto-compaction-retention: 1heartbeat-interval默认 100mselection-timeout默认 1000ms这两个参数在大多数情况下不需要调整。但如果你有跨数据中心部署多节点 etcd网络 RTT 超过 50ms就需要把heartbeat-interval调到 200ms 甚至更大否则节点间心跳频繁超时会不断触发 Leader 选举集群状态持续抖动。quota-backend-bytes默认 2GB在对象数量巨大超过 10 万个对象的集群里很容易打满。建议生产环境设置 8G 以上配合auto-compaction-mode: periodic每周做一次 compact控制总存储体积。另外一个很隐蔽的优化点是etcd 不建议使用默认的 2379 端口暴露给整个集群最好只允许 API Server 访问。通过安全组或防火墙规则把 2379 限制在 Master 节点网段内既降低暴露风险也减少外部端口扫描带来的无效连接。3.3 kubelet 资源分配与驱逐策略kubelet 负责管理节点上的 Pod 生命周期它对资源的管理策略直接影响异常场景下的恢复能力。驱逐策略的配置要结合业务容忍度和节点容量。默认的--eviction-hard是memory.available100Mi这个阈值太低了当节点内存掉到 100Mi 以下时很多 Pod 已经处于不可用状态kubelet 才开始驱逐这其实已经晚了。生产中我更倾向提前预留缓冲--eviction-hardmemory.available500Mi,nodefs.available10%,imagefs.available10% \ --eviction-softmemory.available1Gi \ --eviction-soft-grace-periodmemory.available1m30s \ --system-reservedcpu500m,memory2Gi \ --kube-reservedcpu200m,memory1Gi \ --enforce-node-allocatablepods,system-reserved,kube-reserved注意--enforce-node-allocatablepods会让 kubelet 强制将 Pod 的 cgroup 限制在Allocatable内。这意味着即使 Pod 申请的内存实际超过 limit也不会影响系统组件。这一点对于混合部署业务和高延迟敏感系统的场景尤其关键。还有一个日常维护高频踩坑点--max-pods默认值是 110。很多节点规格大、业务量多想提高这个值但忽略了 Pod 数量增加意味着节点上 iptables 规则数量同步增长如果使用 kube-proxy 的 iptables 模式而 iptables 是链表结构规则越多匹配速度越慢。如果你的节点上 Pod 数超过 100建议升级到 IPVS 模式。kube-proxy 启动参数加上--proxy-modeipvs可以保持高吞吐和低延迟。我之前遇到过一台 16C64G 的节点上跑了 130 个 Pod网络延迟从平均 0.3ms 飙到 5ms排查下来就是 iptables 规则太长。换成 IPVS 后延迟直接回到 0.4ms。这个感受极其明显。3.4 镜像拉取与私有仓库配置K8s 集群中镜像拉取是非常影响 Pod 启动速度的。默认的 imagePullPolicy 是IfNotPresent但如果镜像 tag 是latest会强制每次拉取如果 tag 固定到某个版本本地已有镜像就不会重复拉取。这里有个坑很多人习惯用latest作为生产 tag结果每次滚动更新都会从远端仓库拉取一旦仓库限流或网络抖动更新直接失败。生产环境我坚持用“语义化版本号 精确 tag”的方式构建和发布镜像。同时配置imagePullSecrets让 Kubernetes 能拉取私有仓库的镜像。配置方式比较简单kubectl create secret docker-registry regcred \ --docker-serverharbor.example.com \ --docker-usernamerobot \ --docker-passwordxxx \ --docker-emailopsexample.com创建之后在 Deployment 的 Pod 模板里加上imagePullSecrets: - name: regcred我还建议在节点层面提前缓存常用基础镜像如pause:3.x因为在初始化集群时kubeadm 会在 Master 节点上拉取 pause 镜像如果拉不到后面所有 Pod 都完蛋。这个镜像非常小如果确认拉不下可以从国内镜像源手动ctr -nk8s.io image pull拉取。4. 生产环境常见故障与排查实录4.1 故障速查表我整理了实际运维中最常见的一批 K8s 故障现象和排查方向做成速查表方便大家现场快速定位。故障现象常见根因快速排查命令/方法Pod 一直 Pending节点资源不足 / NodeSelector 不匹配 / 污点未容忍kubectl describe pod pod看 EventsPod 一直 ContainerCreating镜像拉取失败 / CNI 配置错误 / volume mount 错误kubectl describe pod podcrictl ps -a kubelet 日志节点 NotReadykubelet 假死 / CNI 插件异常 / 系统内存耗尽systemctl status kubeletjournalctl -u kubelet -n 100Service 访问不通Endpoints 为空 / kube-proxy 异常 / CNI 路由问题kubectl get endpointsiptables -L/ipvsadm -LnDNS 偶尔超时CoreDNS 副本数不足 / conntrack 满 / 节点 UDP 缓冲区小kubectl -n kube-system logs -l k8s-appkube-dnssysctl net.netfilter.nf_conntrack_maxPod 反复重启LivenessProbe 配置太严 / 进程 OOM / 配置中心变更kubectl logs pod --previouskubectl describe pod查 Exit CodeAPI Server 延迟高etcd 磁盘慢 / 大对象请求 / 大量 List-watchkubectl get --raw/healthz etcd metricsPV 挂载失败存储类的 provisioner 问题 / 底层 NFS/块设备不可达kubectl describe pv pv StorageClass 日志4.2 Pod 一直 Pending 是怎么回事Pod Pending 是最常见的故障之一原因通常非常直白。kubectl describe pod的 Events 部分会直接告诉我们0/4 nodes are available: 1 node(s) didnt match node selector, 3 Insufficient memory之类的原因。但有一类 Pending 很容易误解——资源请求虽然小于节点 Allocatable但节点上的 Pod 数量已经达到阈值。kubelet 的--max-pods限制住之后即使还有资源余量调度器也无法往这个节点调度 Pod。这时候用kubectl describe node node查看Allocatable和现有 Pod 数对比一下就明白了。还有一个隐藏很深的问题调度器对 PVC 的 zone 感知调度。如果你的 StorageClass 定义了拓扑约束例如只允许在特定可用区创建 PV而 Pod 刚好被调度到没有对应可用区 PV 的节点上就会卡在 Pending。查看 Events 能看到scheduler: 0/2 nodes available: 2 node(s) didnt match plugin: WaitForFirstConsumer这类信息这种问题要修改 StorageClass 的allowedTopologies或调整节点选择策略。4.3 节点 NotReady 的排查套路节点 NotReady 的状态从 K8s 1.24 之后很少是因为 kubelet 进程挂掉大部分情况是 kubelet 上报 Heartbeat 失败或 CNI 插件异常。我先看systemctl status kubelet和journalctl -u kubelet --since 5 minutes ago如果 kubelet 运行正常但报的是Misconfiguration: kubelet cgroup driver: cgroupfs is different from docker cgroup driver: systemd这类错误直接统一容器运行时和 kubelet 的 cgroup driver。如果 kubelet 日志里出现PLEG is not healthy这是很多节点 NotReady 的深水区。PLEGPod Lifecycle Event Generator是 kubelet 用来同步 Pod 生命周期事件的模块它通过定期 relist 容器状态来感知 Pod 变化。如果 relist 超时默认超过 3 分钟kubelet 会认为自己“失聪”然后停止对 Pod 的更新节点就会置为 NotReady。PLEG 不健康的常见根源是容器运行时响应慢。比如 containerd 的 metadata 存储锁竞争、镜像 gc 卡住、或磁盘 I/O 饱和。第一步先crictl info看 containerd 的响应速度然后查系统 I/O 指标例如iostat -x 1。如果是磁盘 I/O 问题把该节点 drain 掉修复磁盘后再恢复是最快的方案。4.4 DNS 解析问题为什么总是偶发超时K8s 集群里 DNS 问题是最讨厌的故障之一因为偶发、难以复现、影响面还大。典型表现是业务 Pod 访问 Service 时有时候成功有时候报could not resolve host或connection timeout。我遇到的第一类常见原因是CoreDNS 副本数不足。默认你装 K8s 时CoreDNS 的副本数是 2 个但在高并发集群里这 2 个副本的 CPU 很容易被查询打满。查看指标coredns_dns_request_count_total和coredns_dns_request_duration_seconds_bucket如果 P99 超过 100ms就要扩容。我通常直接把 CoreDNS 的副本数调到 3-5 个并且开启autopath插件减少额外的search domain查询次数。第二类常见原因是节点 conntrack 表满这个问题上节已经提过。症状非常像 DNS 超时因为所有 DNS 查询是 UDP 包也会走 conntracktable 满后新连接建立的 packet 会被直接丢弃没有响应包回程。解决方法是调大 conntrack 表并缩短 TIME_WAIT 状态时间。第三类也是我踩得最深的坑CoreDNS 配置里的forward . /etc/resolv.conf指向了上游 DNS 服务器而上游服务器对单节点 QPS 有限的限制。每次 CoreDNS 的查询量一大上游就丢弃部分请求表现为爬坡后偶发超时。这种情况下建议在上游 DNS 和 CoreDNS 之间加一层本地缓存 Dnsmasq或者直接把上游 DNS 从云厂商默认 DNS 切换成公司内部 DNS。4.5 存储类问题PVC 无法挂载PVC 无法挂载的问题大部分和 StorageClass 的 provisioner 或底层存储网络有关。一个常见的现象PVC 一直是 Pending查看kubectl describe pvc显示waiting for a volume to be created, either by external provisioner or by manual provisioning。第一步确认 StorageClass 的 provisioner 是否正常。如果是 NFS CSI 驱动检查 NFS 服务端 export 的权限和路径客户端节点上手动挂载一次验证底层连通性mount -t nfs4 nfs-server:/data /mnt/nfs-test如果挂载成功但 PVC 还是 Pending查看 CSI 驱动 Pod 的日志这一步定位最快。我遇到过一种情况CSI 驱动的 controller 和 node 插件运行在旧版本和 K8s 1.28 的 API 不兼容报invalid argument。这种问题升级 CSI 驱动版本即可。容器存储接口CSI在 K8s 中地位很高但版本兼容性是最大痛点。如果节点内核更新或系统升级后PV 挂载失败且日志出现operation not permitted或unknown filesystem type的提示检查节点是否缺少对应的文件系统驱动或 NFS 客户端工具。4.6 控制面组件故障API Server、Scheduler、Controller Manager控制面三组件在生产环境中的故障往往不是“完全不可用”而是可用性下降、延迟上升。API Server 延迟高的原因除了 etcd 磁盘慢还有大量大对象请求。比如有人把几 MB 的 ConfigMap 塞进了集群每次 Get/List 都会拖慢 API Server 的响应速度。排查时可以看一下 API Server 的指标比如apiserver_request_duration_seconds和apiserver_request_total抓出耗时最高的资源的操作类型。Scheduler 的问题常表现为 Pod 长时间不被调度。如果你发现 Pod Pending但kubectl describe node显示资源充分检查 scheduler 的 leader election 状态。如果 scheduler 的 Pod 被 evict 或内存被打满Leader 选举会反复触发调度决策自然就停滞了。Controller Manager 的问题相对隐蔽常见的是某个 controller 的 workqueue 长度暴涨导致 Deployment 副本数长时间和预期不一致。这个情况去查看kubectl get --raw/metrics里的workqueue_depth结合日志定位是哪个 controller 卡住多数时候和实际资源对象数量太多、relist 超时有关。5. 安全加固与日常维护体检5.1 RBAC 权限最小化配置K8s 集群部署完成后很多人直接用管理员账户cluster-admin来操作所有资源这在开发环境问题不大生产环境就是事故源头。风险很高。一次误删 namespace 或误改 CRD都会造成不可逆的灾难。我建议从一开始就规范化 RBAC。为运维团队配置一个“部分读写”的角色就够了apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: namespace: default name: ns-admin rules: - apiGroups: [, apps, batch, networking.k8s.io] resources: [pods, pods/log, services, deployments, statefulsets, configmaps, secrets, ingresses, jobs] verbs: [get, list, watch, create, update, patch, delete]开发团队的权限则要再降一级通常只给get/list/watch和日志查看权限写操作通过 CI/CD 的自动化流水线完成避免人工登录集群。这里有一个很多人忽略的小细节pods/log和pods/exec是子资源在 RBAC 规则中必须在resources里单独列出否则即使你给了pods的get权限kubectl logs仍然会被拒绝。5.2 资源配额ResourceQuota与 LimitRange资源配额是防止“某个团队或 namespace 的资源被无限制占用”的关键。没有配额的时候一个开发环境里的测试 Pod 可能把整个集群的内存打爆直接影响生产级业务。配置一个最基本的 ResourceQuotaapiVersion: v1 kind: ResourceQuota metadata: name: dev-quota namespace: dev spec: hard: requests.cpu: 4 requests.memory: 8Gi limits.cpu: 8 limits.memory: 16Gi persistentvolumeclaims: 10 pods: 20配合 LimitRange 给 namespace 内每个 Pod 设置默认 request/limitapiVersion: v1 kind: LimitRange metadata: name: default-limit namespace: dev spec: limits: - default: cpu: 500m memory: 512Mi defaultRequest: cpu: 100m memory: 128Mi type: Container我踩过这样一个坑开发人员写的 YAML 里只写了image没写resources导致 Pod 的 request 为 0、limit 为无穷大。一旦业务流量上来系统无法控制它的资源占用最终把整节点内存打满。有了 LimitRange 之后这类裸奔 Pod 会自动带上默认资源限制安全系数大幅提升。5.3 监控与告警配置的优化监控体系是集群稳定性的“眼睛”但光有监控还不够告警阈值配置得合理才能发挥真正的作用。Prometheus kube-prometheus-stack 是目前最主流的组合。实际过程中我建议优先关注这几个指标kubelet_volume_stats_available_bytes / kubelet_volume_stats_capacity_bytesPVC 容量使用率超过 85% 告警。node_filesystem_avail_bytes / node_filesystem_size_bytes节点磁盘超过 90% 告警。kube_node_status_condition{conditionReady,statusunknown} 1节点状态异常。apiserver_request_duration_secondsP99 超过 1s 就告警。etcd_server_leader_changes_seen_total短时间内 Leader 切换次数大于 1 次说明 etcd 不稳定。告警最重要的是避免“噪音”。默认的 kube-prometheus-stack 中有很多规则是非常“灵敏”的比如KubePodCrashLooping和KubeDeploymentReplicasMismatch这些规则在负载正常波动时会频繁触发告警看多了反而失去警觉。我会把规则调整成更贴近业务的阈值例如 CrashLoopBackOff 只告警持续超过 10 分钟Deployment 副本不匹配只告警超过 5 分钟。5.4 定期维护检查清单K8s 集群不是部署完就一劳永逸的日常维护体检很关键。我个人的定期惯例是每周做一次以下检查用kubectl get nodes -o wide检查所有节点的状态和内核版本确保没有节点内存泄漏。用kubectl get events --sort-by.metadata.creationTimestamp -A检查近期的事件关注有没有 Warning 级别的系统事件。查看节点上的镜像数量定期清理无用镜像避免 imagefs 爆掉。检查 kube-apiserver 的审计日志看看有没有异常的 API 调用。看 Control Plane 的证书有效期确保提前 3 个月左右处理证书轮换需求。证书过期是我碰到过的最容易忽略的生产事故。K8s 默认证书有效期是一年一年之后 API Server 的证书过期整个集群所有kubectl命令都会报x509: certificate has expired or is not yet valid。提前在kubectl get --raw/metrics里加入证书过期时间的监控指标能在过期前就收到通知。平时通过kubeadm certs check-expiration来看证书剩余时间。还有一个维护细节需要特别注意不要在集群运行高峰期做节点的系统级升级。内核升级后节点必须重启而重启会触发 Pod 重新调度。如果没有配置 PodDisruptionBudgetPDB所有 Pod 可能在极短时间内同时被重启造成业务中断。生产环境建议给核心服务配置 PDBapiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: web-pdb spec: minAvailable: 2 selector: matchLabels: aweb写在最后的一些个人体会K8s 的配置和优化其实没有一个“黄金参数组合”能通吃所有场景更多是理解每一层组件的工作机制结合具体的业务特征来做取舍。我在实际操作中最大的体会就是K8s 是一个耦合度非常高的分布式系统所谓优化大多数时候是在做“削峰填谷”和“提前打补丁”——比如把 conntrack 表调大、把 etcd 放到 SSD 上、给 kubelet 预留足够的系统资源、给关键服务加上 PDB——这些看起来都不是什么惊天动地的操作但正是这些细节叠加起来才让集群在业务流量冲击和节点故障时还能保持稳定响应。最后再分享一个小技巧每次对集群做配置变更时无论是内核参数、etcd 配置还是 kubelet 参数建议先在 staging 环境完整跑一遍压测和故障注入至少把节点的 CPU、内存打满一次看驱逐策略是否符合预期再上生产。K8s 的坑大多是一次次“小变更”堆出来的减少每次变更的粒度、提升可回滚性比任何调优参数都重要。