尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

Kubernetes生产运维实战:从集群搭建到故障排查

发布时间:2026/9/15 9:09:26

资讯中心
01
ARTICLE

Kubernetes生产运维实战:从集群搭建到故障排查

Kubernetes生产运维实战:从集群搭建到故障排查
1. 从裸机到容器编排运维为什么要迈过Kubernetes这道坎先讲个真实的事故。早年间我在一个业务团队做运维线上服务已经容器化了用 Docker Compose 管理几个核心应用。表面看一切都好镜像统一、启动快、回滚也方便。但有一次凌晨三点一个业务模块发生了内存泄漏容器把宿主机的内存吃满整台机器直接 OOM连带上面跑着的其他容器一起被内核杀掉。那一刻我意识到容器化只是把应用打成了包并没有给我调度能力、故障隔离能力和自愈能力。那次事故之后我下了决心把整个生产环境迁到 Kubernetes 上。做这个决定前我也不是没犹豫过——Kubernetes 的学习曲线陡、组件多、概念抽象圈子里甚至有人说“小公司上 K8s 是自找麻烦”。但我实操下来的真实感受是只要你想让多台机器上的容器按照一定规则协同工作想实现自动恢复、滚动发布、容量伸缩Kubernetes 就是目前最靠谱的答案没有之一。这篇文章不会去复制官方文档也不打算把每个概念面面俱到地讲一遍。我想把这些年从安装一个测试集群到真正把业务跑在 Kubernetes 生产环境里的完整路径梳理出来包括版本选型、初始化注意点、工作负载对象怎么映射、生产加固、日常运维和故障排查。如果你正打算上手 Kubernetes或者已经装好了集群但不知道下一步怎么走这篇文章应该能省下你很多自己踩坑的时间。1.1 容器解决了部署却把麻烦留给了调度先对齐一个认知Docker 解决的是“应用怎么打包和运行”Kubernetes 解决的是“成百上千个容器如何协作、如何被管理”。我见过不少团队用 Docker 用得很溜但所有容器都是手工 docker run 或者靠 docker-compose 一把梭。单机跑几个容器没问题一旦节点数量变多你会立刻遇到这几个问题容器挂掉了没人负责把它拉起来某台机器负载已经很高但新容器还是会被调度上去发版时只能按顺序手动操作不能自动滚动升级和回滚服务之间的流量路由、负载均衡靠手工记 IP 和端口Kubernetes 的核心思路是“声明式管理”。你告诉它“我希望最终有 3 个副本在运行”剩下的事情由控制器不断比对当前状态和期望状态然后通过调谐逻辑朝期望状态逼近。Pod 挂了ReplicaSet 发现副本数不对重新创建节点挂了调度器把 Pod 调度到其他可用节点上。这套机制一旦跑起来能明显减少人工介入的次数。所以要理解 Kubernetes第一件事不是背概念而是理解“控制器模式”。整个系统里到处是控制器小到一个 Deployment大到集群的 Node 生命周期管理全都在循环做同一件事观察、分析、行动。这个心智模型建立起来后面排障也会顺手很多。1.2 Swarm、Compose 与 Kubernetes选型背后的运维成本对比很多人在入门时会问Docker Swarm 不是更简单吗为什么一定要学 Kubernetes我的答案是看你的业务规模和运维精力。如果是三五台机器、十几个容器Swarm 或者单机 Compose 完全够用别为了追新强行上 K8s。但一旦规模上来Swarm 的短板就很明显——它没有像 Deployment、HPA 这样细粒度的负载管理模型也没有丰富的自定义资源扩展机制滚动更新的控制能力和回滚策略都比较基础。Kubernetes 虽然重但这些生产级能力都已经内建好了生态也非常完整。从运维成本角度看Kubernetes 初期搭建确实有门槛但长期看它把很多“不确定的事情”变成了“确定的事情”。比如应用崩溃后自动重建、发布时按策略灰度、配置变更后统一生效这些在传统环境里要靠脚本和人工在 Kubernetes 里都是声明式配置的一部分。权衡下来只要你的服务数量超过两位数或者你希望让应用具备快速伸缩能力把运维重心放到 Kubernetes 上是值得的。2. 安装落地版本选型、运行时准备与集群初始化安装一个 Kubernetes 集群途径有很多kind、minikube、k3s、kubeadm、二进制部署还有云厂商托管的托管集群。我个人的建议是测试环境随便选生产环境用 kubeadm 或者托管集群。使用 kubeadm 能让你真正理解集群的组成部分排障时不会两眼一抹黑。我这次分享的是基于 kubeadm 在两个节点上搭建集群的完整过程。之所以选择多节点而不是单节点是因为生产环境至少要避免控制面单点问题。哪怕一开始不用 HA也要从一开始就按多节点的姿势去练。2.1 版本选型不是越新越好关键是生态适配Kubernetes 的版本迭代非常快每年通常有 3 个大版本。选版本时不要盯着最新版追建议选择当前社区还在维护周期内的、且经过一段时间验证的版本。我实操时比较稳妥的做法是选择上一代或上上代的最新补丁版本。这部分很容易被忽略的是kubeadm、kubelet、kubectl 三者必须使用相同版本否则会出现 API 版本不匹配的诡异问题。另外还要确认你选的 Kubernetes 版本与容器运行时的兼容性。现在主流的容器运行时是 containerdDocker 作为底层运行时在 Kubernetes 1.24 之后已经不被直接支持但 containerd 本身就是从 Docker 剥离出来的操作逻辑差别不大。我搭建时的版本组合是 Kubernetes 1.28.x containerd 1.7.x。选择 1.28 的原因有几个它当时处于成熟期相关文档和社区踩坑记录最多主流 CNI 插件和监控组件对它的兼容性都很好。如果你的网络环境允许可以用阿里云或腾讯云的软件源加速下载避免安装过程卡在拉包这一步。2.2 初始化前要踩平的硬件与内核参数很多人装集群上来直接 kubeadm init结果各种 preflight 报错其实大多是系统层面没有准备好。Kubernetes 对节点的要求有几样是硬性的初始化前必须处理好。关闭 swap。Kubernetes 从设计上要求节点关闭交换分区因为 kubelet 的资源管理基于 cgroup开启 swap 后内存回收行为不可控容易导致调度判断失真。关闭命令是 swapoff -a同时还要注释掉 /etc/fstab 里的 swap 挂载项否则重启后又会恢复。加载内核模块和调整系统参数。需要启用 br_netfilter 和 overlay 模块否则容器网络无法转发流量overlay 文件系统也起不来。执行完成后还需要调整 sysctl 参数关键项如下表参数推荐值作用net.bridge.bridge-nf-call-iptables1确保经过网桥的 IPv4 流量能被 iptables 规则过滤net.ipv4.ip_forward1允许节点转发流量Pod 网络通信的基础net.bridge.bridge-nf-call-ip6tables1IPv6 场景下同样生效fs.inotify.max_user_instances8192防止大量 Pod 运行后 inotify 句柄耗尽vm.max_map_count262144避免 Elasticsearch 等应用启动时报内存映射不足配置 cgroup 驱动。Kubernetes 和 containerd 的 cgroup 驱动必须一致。现在主流做法是两者都使用 systemd 驱动因为 systemd 已经是几乎所有发行版的 init 系统cgroup 的接管方式与之匹配更好。很多初始化失败就是这里不一致cgroup 驱动一冲突kubelet 起来后会发现节点一直 NotReady。2.3 kubeadm init 与节点加入的关键点系统准备好之后在控制面节点上执行初始化命令。一个比较标准的命令长这样kubeadm init \ --control-plane-endpoint192.168.1.10:6443 \ --pod-network-cidr10.244.0.0/16 \ --image-repositoryregistry.cn-hangzhou.aliyuncs.com/google_containers \ --kubernetes-versionv1.28.2这个命令里有几个参数要特别注意--control-plane-endpoint如果是单控制面就写当前节点的 IP 和控制面端口如果是 HA 架构这个值应该指向负载均衡器的虚拟 IP。--pod-network-cidr这个网段决定了 Pod 的 IP 池必须和后面安装的 CNI 插件要求一致。比如 Flannel 默认要求 10.244.0.0/16Calico 则通常使用 192.168.0.0/16如果你先用 10.244 初始化后面装 Calico 时要注意改成对应的 IPPool。--image-repository因为某些网络原因官方镜像仓库拉取可能受限换成国内镜像源会顺利很多。初始化成功后控制台会输出一串 token 和证书哈希值。这串信息要妥善保存它是工作节点加入集群的凭证。如果当时没保存可以后续在控制面节点执行 kubeadm token create --print-join-command 重新生成。工作节点要做的操作相对简单安装好 kubelet、kubeadm也关闭 swap 和调整内核参数后直接执行控制台输出的 kubeadm join 命令即可。如果配置了 containerd 的镜像加速注意在 /etc/containerd/config.toml 里把 sandbox_image 改为国内可拉取的 pause 镜像地址否则 Pod 会一直卡在 ContainerCreating。2.4 CNI 插件的生产取舍集群初始化完成但没有任何 CNI 插件时节点会一直处于 NotReady 状态。CNIContainer Network Interface负责给 Pod 分配 IP、打通跨节点通信。主流的方案有三类我做选型时对比了很久插件网络模式优点需要注意的点FlannelVXLAN/Overlay简单、资源占用低、部署快性能有一定损耗不支持 NetworkPolicyCalicoBGP/Overlay性能好支持 NetworkPolicy可走纯 BGP 路由配置复杂一些对内核有要求CiliumeBPF性能强可观测性好支持丰富网络策略对内核版本要求高运维门槛高我生产环境选的是 Calico。原因是它既能跑 Overlay 模式方便上手又能在网络规模扩大后切换到 BGP 模式减少封装开销而且安全策略可以直接用不用额外装一套网络策略组件。安装 Calico 时执行官方 manifest 即可kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.27.0/manifests/tigera-operator.yaml装完之后 watch kubectl get pods -n calico-system 等待 Pod 起来节点状态会从 NotReady 变成 Ready。如果你发现节点的 Ready 状态反复横跳优先检查 CNI 镜像是否能拉取成功以及节点之间的 6443 端口是否互通。3. 从 docker run 到 Kubernetes 对象部署心智模型重建集群搭好了接下来就是怎么把业务往里放。这一步对很多 Docker 老手来说反而是最难的地方因为 Kubernetes 不直接管理容器它管理的是更高层的资源对象。你不再说“我启动一个容器”而是说“我声明一个 Deployment它管理 3 个副本”。3.1 Deployment、ReplicaSet 与 Pod 的层级关系举个例子这是我在生产环境里一个典型后端服务的部署清单apiVersion: apps/v1 kind: Deployment metadata: name: order-service namespace: production spec: replicas: 3 selector: matchLabels: app: order-service strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 maxUnavailable: 0 template: metadata: labels: app: order-service spec: containers: - name: order-service image: registry.internal/order-service:2.4.1 ports: - containerPort: 8080 resources: requests: cpu: 250m memory: 512Mi limits: cpu: 1 memory: 1GiDeployment 是面向应用的入口它负责管理 ReplicaSetReplicaSet 再负责维护指定数量的 Pod 副本。发版的时候Deployment 会创建一个新的 ReplicaSet然后按 strategy 里设定的策略滚动替换旧 Pod。我把 maxUnavailable 设置为 0意思是升级过程中不能同时出现可用副本减少的情况确保服务始终有 3 个副本在处理流量。代价是 maxSurge 需要至少为 1因为滚动升级过程中要多占用一个临时 Pod 的资源。如果你的核心业务不能接受中断这个配置方式很有参考价值如果只是内部系统可以放宽 maxUnavailable 来加速发布。这里顺手提一个很容易踩的坑Deployment 的 selector 一旦创建就不可修改。如果你想修改 matchLabels只能删除 Deployment 再重建这会导致服务短暂中断。所以一开始设计标签时就要想清楚别在后面加需求时顺手改 selector。3.2 Service 与 Ingress流量怎么走进来Pod 的 IP 是动态的重启之后就会变。Service 就是解决这个问题的一层抽象它给一组 Pod 提供一个固定的虚拟 IP 和 DNS 名字再通过 label selector 将请求转发到对应的容器端口。Service 的访问类型主要分三种ClusterIP集群内部访问默认方式适合服务间调用。NodePort通过节点的 IP 加固定端口访问适合基础调试。LoadBalancer对接云厂商负载均衡适合直接对外暴露。生产环境里我通常不让 Service 直接暴露到公网而是通过 Ingress 统一入口。Ingress 承担了路由规则和 TLS 终止的职责可以把它理解成 Kubernetes 世界的 Nginx 配置中心。下面是一个典型的配置apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: gateway namespace: production spec: ingressClassName: nginx rules: - host: api.example.com http: paths: - path: /order pathType: Prefix backend: service: name: order-service port: number: 8080装了 ingress-nginx 之后这个 Ingress 资源会被 nginx-ingress-controller 监听到自动生成对应的反向代理配置。有件事要提醒Ingress 使用 pathType 时Prefix 和 Exact 的匹配规则并不是想当然的前缀匹配路径边界处理很严格。比如 /order 用 Prefix 匹配时可以匹配 /order/xxx但不一定会匹配 /orderXYZ因为斜杠是一个边界分界符。我见过不止一次因为路径配置不当导致部分接口 404 的情况。3.3 配置管理ConfigMap 与 Secret 不能只当环境变量用业务应用的配置五花八门Kubernetes 给出的方案是 ConfigMap 保存非敏感配置、Secret 保存敏感信息。这两类资源都可以挂载到 Pod 里或者注入成环境变量。但环境变量注入有个隐藏问题环境变量变更不会触发 Pod 重建。我最早用 ConfigMap 管理应用配置时把配置存进去、改了 ConfigMap 内容然后以为应用会自动生效结果等了十分钟业务还是旧配置。原因是 ConfigMap 的变更不会自动滚动更新 Deployment你必须在清单里加一个 config checksum 注解当 ConfigMap 内容变化时通过 SHA 值的改变来触发滚动更新。如果你要我给个实用建议对于运行时可能变动的配置优先用挂载文件的方式Pod 里的应用如果监听了文件变化就能热加载对于基本不变的环境变量直接写死在 Deployment 清单里即可不要绕经过 ConfigMap 反而增加排查难度。Secret 也是同样逻辑只是数据会被 base64 编码注意它不能提供真正意义上的加密防护敏感度极高的数据还是应该交给专业的密钥管理工具。4. 生产部署的核心加固资源、伸缩与高可用一个集群能跑起来和能抗住线上流量中间差着一大截。这一大截的核心在于你有没有给应用设定资源边界有没有在流量高峰时让系统自己扩容以及控制面和数据面是否足够健壮。4.1 requests 和 limits保护的是邻居也是自己在 Kubernetes 集群里多个 Pod 会被调度到同一个节点上共享物理资源。如果某个容器不设置资源限制它的内存占用会无限增长最终触发宿主机 OOM把同节点的所有进程拖下水更可怕的是 OOM 发生后 kubelet 可能重启整个节点。这个场景我经历过不止一次所以现在部署任何工作负载必须带上 resources 字段。requests 与 limits 有着截然不同的作用requests 是调度依据。调度器在看“这个节点能不能塞下这个 Pod”时比较的是节点剩余可分配资源与 Pod 的 requests 之和它不关心实际使用量。limits 是运行时约束。kubelet 会根据 limits 控制容器的 CPU 配额和内存上限内存超过 limits 后容器进程会被 OOM Kill。CPU 与内存还有一个不对称性CPU 可以超卖内存不能。一个容器即使持续占满 CPU limits也只会被限流不会被杀死但内存一旦超限就是被杀掉。所以内存的 limits 一定要根据应用的真实内存画像来设定宁可放宽 20% 冗余也不要设得过紧。给业务容器设置资源参数时我不建议拍脑袋定数。比较好的路径是先不设 limits、只设 requests让应用跑几天通过监控看它的实际内存高位水平和 CPU 用量长尾再回来把 limits 补上。这个“先测量后限流”的顺序能避免因为限制过小导致应用频繁重启的问题。4.2 HPA 弹性伸缩生效的前提条件Kubernetes 的弹性伸缩最常用的是 HPAHorizontalPodAutoscaler它根据 CPU 利用率、内存指标或自定义指标自动调整 Deployment 的副本数。一个标准的 HPA 清单如下apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: order-service-hpa namespace: production spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: order-service minReplicas: 3 maxReplicas: 10 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 60但请注意HPA 要真正生效有三个前置条件集群里必须装了 metrics-server否则 HPA 连 CPU 数据都读不到。Deployment 里容器必须设置了 requests因为 CPU 利用率计算使用的是“实际使用量 / requests”的比值。应用本身要支持多副本并发运行比如有状态服务需要确认是否具备分布式启动能力。我在生产里踩过的一个教训是某个业务模块是单线程处理文件任务的副本数从 2 扩到 8吞吐量完全没变化反而因为多个进程抢占同一批文件导致任务互相冲突。所以 HPA 不是装上就完事你要先确认应用是真的能水平扩展再考虑弹性策略。另外建议给 HPA 加上缩容阈值的安全垫。默认指标下来之后它会很快缩容而缩容时如果流量又涨回来Pod 又要重建很容易形成震荡。我一般会在 behavior 里配置一段 stabilizationWindowSeconds把缩容的冷却时间调长到 300 秒左右给系统留出判断的空间。4.3 高可用控制面与 etcd 备份策略单控制面集群一旦控制面节点宕机整个集群的调度和 API 服务都会中断业务 Pod 虽然还在跑但你已经无法做发布和变更了。生产环境建议至少要有 3 个控制面节点组成 HA 集群。kubeadm 提供两种 HA 方案一种是 etcd 堆叠在控制面节点上也就是每个控制面节点同时跑一个 etcd 实例另一种是 etcd 独立部署在专门的节点上。对小规模集群来说堆叠 etcd 更省机器运维也更简单。在 kubeadm init 时指定多个 control-plane-endpoint然后通过 kubeadm join --control-plane 加入其他控制面节点就可以把控制面高可用搭起来。不管是否为 HAetcd 的备份都是不能省的。etcd 里保存了集群的全部状态包括所有资源对象的定义、集群配置、证书信息。一旦数据损坏影响的不只是某个业务而是整个集群。我现在的备份策略是这样每天凌晨 2 点通过 cron 任务执行 etcd 快照保留最近 7 份同时把快照文件同步到对象存储。备份命令很简单ETCDCTL_API3 etcdctl snapshot save /backup/etcd-snapshot-$(date %Y%m%d).db \ --endpointshttps://127.0.0.1:2379 \ --cacert/etc/kubernetes/pki/etcd/ca.crt \ --cert/etc/kubernetes/pki/etcd/server.crt \ --key/etc/kubernetes/pki/etcd/server.key等真的发生数据损坏时恢复的步骤是停止 api-server 和 etcd把旧的 etcd 数据目录移走再用 etcdctl snapshot restore 恢复数据然后重启 kubelet。恢复过程中时间线最容易出错的是证书和数据的一致性所以备份时最好连同 /etc/kubernetes/pki 目录一起备份。5. 线上运维的日常监控、日志与故障排查经验集群真正跑起来之后运维的日常就不再是“重启容器”了而是保障三件事看得见、查得到、恢复得快。下面这部分我结合自己维护生产集群的实际经验梳理一套可以直接落地的日常运维模型。5.1 监控体系先解决“有没有”再解决“优不优”监控是运维的眼睛。没有监控的集群就像没有仪表盘的飞机飞起来全靠感觉。我在这块使用的组件是 kube-prometheus-stack它集成了 Prometheus、Grafana、Alertmanager 以及一系列导出器能覆盖节点、Pod、容器三个层面的监控。安装后第一个要看的不是花哨的 Grafana 大盘而是确认这几个核心指标能否采集到节点 CPU、内存、磁盘使用率Pod 的 CPU、内存的实际使用量与 requests/limits 的对比控制面组件api-server、etcd、scheduler、controller-manager的存活与延迟容器重启次数有了数据之后我再补充告警规则。这里不建议一上来就堆几十条规则告警多了人会产生疲劳最后真的关键告警也可能被忽略。我的告警规则从少到多经过一个逐步沉淀的过程。比较有共性的规则大致如下告警项触发条件处理动作节点不可用NodeReady 状态为 False 超过 3 分钟立即查看机器负载和网络容器频繁重启某一 Pod 重启次数超过 5 次/10分钟查看日志判断是 OOM 还是探针失败磁盘空间不足节点根分区使用率超过 85%清理镜像缓存和日志文件PVC 容量接近饱和使用率超过 80%扩容或迁移数据5.2 日志收集的三个层次日志排障是运维的基本功。Kubernetes 集群里容器的标准输出默认写到了宿主机上如果容器重建之前的日志文件会跟随旧容器一起消失。所以要实现可追溯的日志体系必须把日志统一采集到集中存储中。我现在的方案是 Promtail Loki Grafana也就是常说的 PLG 栈。选择它的原因是资源占用相对轻而且和 Grafana 大盘天然集成排障链路是先看大盘发现异常再点进日志面板查对应服务的记录。采集方式上遵循三个层次应用日志写到标准输出由 Promtail 采集 stdout。应用日志写到文件且路径固定通过 Promtail 的 configuration 指定挂载目录采集。需要更高检索性能的场景直接让业务组件的日志写入消息队列再由下游管道消费。如果你的服务已经在跑日志突然中断优先检查三件事Promtail 对应的 DaemonSet 是否正常、日志路径是否因为 Pod 被调度到新节点而变化、以及 Loki 后端的存储容量是否已满。5.3 我踩过的三个典型故障故障排错是最能体现运维经验的部分。把三个我实际遇到过的问题拿出来讲一讲这些问题在文档里都不会写得太细但实战中非常容易遇到。第一个节点卡在 NotReady。现象是某天新增了一个工作节点kubectl get nodes 显示这个节点状态一直是 NotReady。我第一反应是 kubelet 挂了但登录到节点上查看kubelet 服务是 running。再用 journalctl -u kubelet 查日志发现狂刷“failed to get sandbox image”的错误。原因是这个节点上 containerd 的 sandbox_image 没有替换为国内镜像地址pause 镜像拉取失败导致 Pod 无法启动进而节点一直不能就绪。这个案例说明节点初始化时系统的每一项检查都是有意义的尤其是镜像源配置这种看似不起眼的部分往往就是最后没准备好的一环。第二个滚动发布时部分 Pod 一直处于 Pending。一次发版把副本数从 3 扩到 6新扩容的 Pod 一直 Pending。执行 kubectl describe pod 查看事件显示的是 “0/3 nodes are available: insufficient cpu”。原因很直接集群总的 CPU requests 已经接近节点容量调度器找不到能容纳新 Pod 的节点。这个事让我意识到扩容之前必须看节点剩余可分配资源而不是只盯着当前实际使用率。requests 决定的是调度水位实际使用率只反映短时负载两者并不等价。第三个证书过期导致整个集群 API 不可用。Kubernetes 控制面的各类证书默认有效期是一年。某天同事们说 kubectl 连不上集群我登录控制面节点执行 kubectl get nodes提示证书过期。解决办法是执行 kubeadm certs renew all 重新生成证书然后重启相关组件和 kubelet。这个坑提醒我之后一定要把证书过期时间写入监控告警证书有效期少于 30 天就提醒运维介入。Kubernetes 的证书体系不像我们平时用浏览器访问 HTTPS 网站那样会自动续签它需要主动处理这一点很多新运维会忽略。综合来说Kubernetes 集群运维的重心从来不是把所有命令背得滚瓜烂熟而是理解每一层组件的职责知道出问题时从哪里看起。节点不行看 kubelet网络不行看 CNIPod 调度不行看事件应用崩溃看日志和探针。这套排查链路一旦建立起来你会发现 K8s 虽然复杂但它的每个组件都在告诉你它正在做什么只是你需要学会去听。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。