
1. 为什么我最终选择了 Kubernetes1.1 容器化之后的甜蜜与负担先讲个真实经历。几年前我负责的一套业务系统已经全面容器化镜像打好了Docker Compose 也能在单机上一键起停。最初确实觉得很舒服——环境一致了部署变快了本地开发和线上终于不再打架。但等实例数从 3 个涨到 30 个事情就开始变味了。我需要手动维护一堆容器的启动顺序自己写脚本检查哪个容器挂了半夜被告警喊起来登录服务器去重启容器。那段时间我经常在想容器解决了我怎么打包的问题但完全没解决怎么维持大规模容器稳定运行的问题。我需要一个能把容器集群当成一台电脑来操作的东西。这时 Kubernetes 走进了我的视野。它不是一个简单的部署工具而是一套完整的容器编排系统。你可能听过一句很经典的话Kubernetes 本质上是数据中心操作系统。这个类比特别贴切——它把集群里所有服务器的 CPU、内存、磁盘抽象成资源池把容器抽象成进程然后像操作系统管理进程一样去调度、监控、重启这些容器。1.2 K8s 在架构里的真实定位我先给完全没接触过的朋友画个像Kubernetes简称 K8s中间 8 个字母省写是 Google 基于内部 Borg 系统开源出来的容器管理平台后来捐给了云原生计算基金会。它不是一个具体的软件功能而是一整套平台层的东西。你可以把业务代码打成镜像扔给它然后告诉它我要跑 3 个副本剩下的事——选机器、拉镜像、启动容器、健康检查、流量分发、滚动升级——全都交给它。那它到底解决了什么痛点我自己的体会是三层资源层面把一堆服务器变成逻辑上的一个资源池你不用关心容器具体落在哪台机器上。运行层面容器挂了自动拉起流量来了自动扩容发新版本自动滚动有问题自动回滚。接入层面提供统一的网络和存储抽象让服务之间的通信和数据的持久化不再依赖某台具体机器。换句话说K8s 解决的已经不是某个功能怎么实现而是大量服务怎么稳定地跑在一起。这篇文章我就结合自己从零搭集群、日常运维的真实经验把 K8s 的核心功能拆开讲清楚再附上一套可以直接照着用的实操方案。2. K8s 核心功能拆解每个设计都在解决一个真实痛点2.1 自动调度让 Pod 找到最合适的位置K8s 里最小的调度单元叫 Pod一个 Pod 里通常跑一个主容器可以再挂一些辅助容器。调度器要做的事就是决定每个 Pod 放到哪台节点上。我第一次接触调度逻辑时觉得这有什么难的不就是看哪台机器空就放哪台吗实际远没那么简单。调度器要同时考虑资源请求、节点污点、亲和性规则、数据本地性、端口冲突一堆因素。比如一个 Pod 声明需要 2 核 4G调度器就不会把它放到只剩 1 核的机器上。比如某个节点打了污点专用 GPU 节点普通 Pod 默认就不会被调度过去。我印象很深的一件事有次线上扩容HPA 触发后副本数从 4 涨到 12但节点资源已经接近 80% 水位新 Pod 一直处于 Pending。排查后发现当时集群只有 5 个节点其中一个还因为磁盘压力被打上了污点。后来我加了节点Pod 才正常调度上去。这个经历让我意识到调度不是越快越好而是在资源碎片化和调度效率之间做平衡。K8s 的调度器支持自定义调度策略比如节点亲和性可以把关键服务固定到高性能机器上Pod 间亲和性可以把频繁通信的服务放到同一台节点减少网络延迟。反亲和性则会把同服务的副本分散到不同节点避免单点故障导致整个服务不可用。生产上我建议核心业务至少配置一条反亲和规则。2.2 自愈机制不是玄学是控制器逻辑K8s 最有名的能力之一就是自愈。容器崩了它拉起来节点挂了它把上面的 Pod 赶到别的节点重建。很多人以为这是什么高深的智能算法其实底层就是控制器模式。Deployment 控制器负责维持期望副本数。它不断对比当前实际的 Pod 列表和你声明的期望状态有偏差就调协。比如你声明 3 个副本某个节点宕机导致 1 个 Pod 失联控制器发现实际只有 2 个立刻在其它可用节点上重新创建一个。你不需要任何脚本去监控并自愈只需要把期望状态告诉 K8s。这里我想强调一个容易误解的点K8s 的自愈只保证副本数量符合预期不保证你的业务逻辑不丢数据。如果容器因为内存溢出被杀掉再重启那内存里的状态是会丢的。所以有状态服务必须把数据落在持久卷或者外部存储上。很多初学者把 K8s 当成不会出问题的保险箱结果遇到重启丢数据其实是对自愈能力边界的理解有偏差。还有一个细节值得注意kubelet 会定期对容器做健康检查。如果配置了 livenessProbe探针连续失败几次kubelet 会杀掉容器并重启。如果配置了 readinessProbe探针失败时 Pod 会从 Service 端点列表里摘掉流量不会打到不健康的实例上。这两个探针一个管要不要重启一个管要不要接流量职责完全不同我见过不少团队只配存活探针不配备就绪探针导致服务启动慢的实例在被流量轰炸。2.3 弹性伸缩从 CPU 到业务指标弹性伸缩是 K8s 的另一大卖点也是最直接能省钱的功能。官方叫 HPAHorizontal Pod Autoscaler水平 Pod 自动伸缩。它做的事情很直观监控 Pod 的负载指标负载高了增加副本数负载降了减少副本数。默认最常用的指标是 CPU 使用率。比如你给每个副本设置了 500 毫核的 CPU 请求HPA 策略设成 50%意思是当所有副本的平均 CPU 使用率超过 250 毫核时就触发扩容。扩容的计算大致是期望副本数 当前副本数 x当前实际负载 / 目标负载。假设当前 4 个副本平均负载到了 100%目标是 50%期望副本数就变成 8。不过我只用 CPU 做扩缩容的早期阶段后来发现这有个明显缺陷很多业务是 IO 密集型或消息型负载CPU 指标往往不够灵敏。比如某个异步消费者队列堆积了几万条消息CPU 还是 20%HPA 根本不会动。后来我在生产环境配了自定义指标直接把队列长度暴露成 Prometheus 指标让 HPA 基于待处理消息数扩容效果立竿见影。这里必须提一下缩容的冷却问题。K8s 默认的缩容逻辑偏保守目的是防止抖动。如果你把稳定窗口设得太短流量一波动副本数就疯狂上下反而可能导致每秒钟都在创建销毁 Pod。我自己的经验是扩容激进一点没关系缩容一定要加稳定窗口至少等 5 到 10 分钟再缩。2.4 滚动更新与回滚发布不再提心吊胆在没上 K8s 之前我们发布流程是停服-上传包-重启-验证每次升级都有短暂的不可用窗口。K8s 的滚动更新彻底改变了这个体验。Deployment 支持声明更新策略。默认是 RollingUpdate它会先创建一个新 Pod等新 Pod 的就绪探针通过之后再摘掉一个旧 Pod如此反复。整个过程服务不中断流量从旧版本平滑切到新版本。你还可以控制 maxSurge 和 maxUnavailable分别决定更新过程中最多能多出多少个 Pod、最多允许多少个不可用。我通常这样配置更新时先多起一个 PodmaxSurge: 1同时最多允许 0 个旧 Pod 不可用maxUnavailable: 0极端保守但适合公网流量很大的系统。内网系统可以放宽到 maxUnavailable: 1更新速度更快。滚动更新最实用的是发布即回滚。如果新版本启动就崩就绪探针一直失败Deployment 会自动暂停更新不会把所有旧 Pod 都替换掉。即使发布完成之后发现接口异常一条 rollback 命令就能回到上一个版本而且回滚的镜像版本是自动记录的。我后来做发布演练时试过故意在新版本里加一个 panic 逻辑K8s 滚动更新会自动卡住线上服务一点没受影响这是传统部署方式完全无法想象的。2.5 服务发现与负载均衡集群内的网络基建容器是随时会死、随时会重建的IP 地址每换一次就变一次。如果 Pod 之间通过 IP 直连那集群根本没法稳定运作。K8s 的解决办法是引入 Service。Service 是一组 Pod 的稳定访问入口。它有一个虚拟 IPClusterIP流量进入 Service 后会被转发到后端的某个 Pod 上。Pod IP 怎么变都无所谓Service 通过标签选择器动态维护后端列表。Pod 重建后标签没变它就会被自动纳入 Service 的后端集合。在集群内部服务之间的相互调用不应该直接访问朋友的服务 IP而应该访问 Service 名字。K8s 内置了 DNS比如你在 namespace 为prod的环境里部署了一个服务名为order-service的 Service其它服务直接访问order-service.prod.svc.cluster.local就能找到它。这种内置的服务发现机制让新增服务、迁移服务都变得非常简单不再需要手工维护一份服务的 IP 列表。对外流量一般走 Ingress。Service 默认是四层负载均衡Ingress 能根据域名和路径做七层路由。比如同一个 IP 下api.example.com路由到后端 A 服务web.example.com路由到后端 B 服务这靠的是 Ingress 规则。配上 TLS 证书之后证书终止也交给 Ingress 统一处理后端服务不用各自管理证书。2.6 存储编排给容器一颗不被格式化的心容器天生是无状态的文件系统随容器销毁而消失。但数据库、缓存、文件上传这类服务必须有持久化存储。K8s 的存储抽象是整个系统里特别有设计感的部分。它的核心思想是把存储资源和使用存储的声明分离。PersistentVolumePV是集群里的存储资源可以由管理员预先创建也可以通过 StorageClass 动态创建。PersistentVolumeClaimPVC是业务方提出的存储申请比如我要 20G 的读写存储。业务部署时在 Pod 里声明挂载这个 PVCK8s 会自动把合适的 PV 绑定给 PVC并把存储挂载进容器的指定目录。这套抽象真正让我感到方便的地方是应用根本不需要关心底层存储到底是谁提供的。开发环境可以用本地目录测试环境可以用 NFS生产环境可以用云盘。业务配置完全一样底层存储怎么换PVC 的声明不用动。这种解耦让环境迁移变得异常轻松。有一点我必须反复强调StatefulSet 里每个副本的 PVC 是不能共享的。比如部署一个 3 节点的数据库集群如果你用一个公共 PVC 给所有副本三个节点会争抢同一份存储。正确做法是给每个副本单独创建 PVC或者使用支持动态创建且带有每个副本独立卷能力的 StorageClass。这个坑我亲眼见人踩过数据错乱恢复起来极其痛苦。3. 从一张部署清单开始实操 K8s 核心功能3.1 开发环境选型先用轻量方案跑起来如果你刚开始接触 K8s我强烈建议别一上来就部署生产集群。开发和学习阶段的痛点是环境太重、配置太复杂用轻量方案先跑通核心概念比什么都重要。我自己常用的两个工具是 minikube 和 kind。minikube 会创建一个单节点的 K8s 集群适合体验完整的 K8s 功能。kind 则用 Docker 容器来模拟节点启动速度比 minikube 还快适合做本地开发和 CI 测试。你只需要一条命令就能启动一个集群minikube start --cpus4 --memory4096 kubectl get nodes看到节点 Ready 之后你就拥有了一个完整的控制平面和调度能力。之后所有 K8s 概念都可以在这个环境里练。注意本地资源别给太少否则后面创建多个应用时调度器会因为资源不足把 Pod 一直卡在 Pending 状态。如果团队里想模拟多节点的效果可以用 k3d它能把多个 K3s 节点跑在 Docker 里模拟一个真正的多节点集群。多节点能让你体验节点故障时 Pod 如何漂移单节点是永远体会不到这一点的。3.2 工作负载核心Deployment 是主力在 K8s 里你很少直接创建 Pod而是通过工作负载资源来管理 Pod。最常用的就是 Deployment。同样一个服务你要跑 3 份副本并支持滚动更新用 Deployment 表达再合适不过。下面是一个实际部署配置的例子apiVersion: apps/v1 kind: Deployment metadata: name: nginx-demo labels: app: nginx-demo spec: replicas: 3 selector: matchLabels: app: nginx-demo template: metadata: labels: app: nginx-demo spec: containers: - name: nginx image: nginx:1.25 ports: - containerPort: 80 resources: requests: cpu: 100m memory: 128Mi limits: cpu: 500m memory: 512Mi readinessProbe: httpGet: path: /healthz port: 80 initialDelaySeconds: 3 periodSeconds: 5这里我把几个关键配置说明一下。replicas: 3是期望状态Deployment 会持续保证 3 个副本。selector.matchLabels必须和 Pod 模板里的标签一致Deployment 靠标签找到自己管理的 Pod这个一旦配错就管理不到任何 Pod只是创建了但没有任何控制器维护它。resources.requests是调度依据调度器看到每个副本要 100 毫核 CPU会预留这部分资源给 Pod。limits是运行上限超过上限时容器会被限制 CPU、被杀掉或触发内存回收。我建议给每个容器都设置 requests 和 limits避免某个应用把宿主机资源全吃光。readinessProbe指向/healthz路径只有接口返回 2xx/3xxPod 才会被标记为就绪流量才会打进来。如果业务启动需要 10 秒initialDelaySeconds 设 3 秒大概率会出现探针失败重试日志里全是 connection refused。要把启动时间算进去。创建命令很简单kubectl apply -f deployment.yaml kubectl rollout status deployment/nginx-demorollout status可以实时看更新的状态。如果更新卡住它会告诉你哪个 Pod 没有就绪这对排查发布问题帮助非常大。3.3 Service 与 Ingress把流量正确接进来Deployment 管理好 Pod 之后Pod 的 IP 是临时的外部没法稳定访问。创建 Service 是下一步。apiVersion: v1 kind: Service metadata: name: nginx-demo-svc spec: selector: app: nginx-demo ports: - port: 80 targetPort: 80这个 Service 会把集群内的虚拟 IP 的 80 端口转发到任意一个标签为app: nginx-demo的 Pod 的 80 端口上。port是 Service 的端口targetPort是 Pod 的容器端口。如果容器端口不是 80比如应用跑在 8080就把 targetPort 改成 8080。访问方式上如果你想从集群外部临时访问可以直接用 port-forwardkubectl port-forward service/nginx-demo-svc 8080:80浏览器打开http://localhost:8080就能访问到集群里的服务。这个方式只适合调试看效果不适合做长期对外暴露。如果要用域名访问多个服务可以配置 Ingress。下面是一个简单的 Ingress 示例apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: demo-ingress spec: rules: - host: nginx.example.com http: paths: - path: / pathType: Prefix backend: service: name: nginx-demo-svc port: number: 80在本地环境里你需要配合 ingress-nginx 控制器一起使用并且把nginx.example.com解析到入口 IP。生产环境里 Ingress 控制器通常通过负载均衡器暴露到公网外部所有流量先走 LB再到 Ingress最后按域名规则分发到各个 Service。3.4 配置管理ConfigMap 与 Secret 的正确用法很多团队写 K8s 配置时把环境变量写死在 Deployment 里这其实是个坏习惯。一旦环境变了——从测试切到生产——就得改镜像或改配置重新发布。K8s 提供了 ConfigMap 和 Secret 来解耦配置。ConfigMap 适合存非敏感配置。比如应用的配置文件内容、环境变量、命令行参数。Secret 适合存敏感信息比如数据库密码、API Key、TLS 证书。Secret 虽然只是 Base64 编码但它有独立的权限控制并且可以在 etcd 里开启加密存储比明文强太多。举一个实际用法apiVersion: v1 kind: ConfigMap metadata: name: app-config data: APP_ENV: production LOG_LEVEL: info --- apiVersion: v1 kind: Secret metadata: name: db-secret type: Opaque data: DB_PASSWORD: c3VwZXItc2VjcmV0在 Deployment 中引用env: - name: APP_ENV valueFrom: configMapKeyRef: name: app-config key: APP_ENV - name: DB_PASSWORD valueFrom: secretKeyRef: name: db-secret key: DB_PASSWORDCycle 起来之后应用可以通过标准环境变量拿到配置。每次配置更新后 Pod 不会自动重启你还需要手动滚动更新一次否则 Pod 里跑的进程还是旧的环境变量。这个问题很多新手会忽略以为自己改了 ConfigMap 就生效了。生产环境我建议把整套 YAML 放在 Git 仓库里管理配一个简单的 CI 流程推代码 → 构建镜像 → 更新 Deployment 镜像 tag → 自动滚动。这样每一份配置和代码一样有版本、有审计、能回溯。4. 常见故障与排查我把踩过的坑整理成一份清单4.1 Pod 一直 Pending卡在调度阶段这是新手最常遇到的问题。明明执行了 applyPod 却一直 Pending。最直接的办法是看 Pod 的事件kubectl describe pod pod-nameEvents 区域会直接告诉你原因。最常见的是 Insufficient cpu / Insufficient memory说明节点资源不够。解决办法要么扩容节点要么调低 requests。另一个常见原因是节点上有污点Pod 没有对应的容忍度。比如节点被标记成只给特定服务使用普通 Pod 永远调不上去。还有一个很隐蔽的原因PVC 无法绑定。如果你创建了 StatefulSet 或者挂载了 PVC但存储类没有配置PVC 一直处于 PendingPod 也会跟着 Pending。查看kubectl get pvc就能看到 PVC 的状态是否 Bound。如果长期间卡在 Pending多半是 StorageClass 没配好或存储插件有问题。4.2 CrashLoopBackOff定位逻辑要看日志和事件CrashLoopBackOff 意味着容器启动后立刻崩溃被 K8s 反复重启并退避等待。这个状态的名字容易吓到人但定位方法非常固定。第一步看容器日志kubectl logs pod-name --previous注意--previous参数非常有用。如果容器崩溃在被重启当前日志往往只记录上一次启动的输出而真正崩溃前的日志需要带上这个参数才能看到。日志里如果有明显的 panic、异常栈问题基本就能锁定了。如果日志没有明显错误再看 Pod 事件kubectl describe pod pod-name有时候崩溃原因是启动时连不上数据库、配置文件缺失这些会在描述里体现。我踩过的一个典型坑是启动命令依赖的目录没有创建权限导致容器每次启动都抛 permission denied。版本镜像的基础镜像更小更安全但也意味着缺少一些系统工具排查起来不方便。4.3 ImagePullBackOff镜像问题比你想的多ImagePullBackOff 表示镜像拉取失败K8s 在退避重试。原因通常不是网络断了这么简单。最常见的是镜像名写错比如把nginx:1.25写成nginx:l.25数字 1 和字母 l 很容易看走眼。其次是私有仓库认证问题集群所在节点如果没有配置拉取凭证访问私有仓库会被拒绝。还有一种情况是镜像标签不存在仓库里没这个 tag。排查思路是describe pod看具体错误信息。如果是 authentication required需要创建 docker-registry 类型的 Secret并在 Pod 里通过imagePullSecrets引用。如果是 manifest unknown检查镜像 tag 是否存在。如果是超时检查网络连通性和仓库服务状态。经验之谈生产环境尽量把镜像 tag 用具体的版本号或 commit 号别用 latest。latest 会导致每次发布拉到的镜像不一致而且回滚时你无法确定之前的 latest 是哪个版本。用不可变 tag 的好处是天然可追溯线上跑的是哪个代码版本一目了然。4.4 升级版本与组件兼容的教训K8s 升级带来的问题比业务本身的问题还要难缠。有一次我把集群从 1.24 升到 1.25结果发现所有 Pod 的网络都不通了。排查到最后是 CNI 插件版本不兼容新版的 kubelet 通信协议。这类问题往往不会直接显示在业务日志里而是网络组件、节点状态层面的错误。升级前必须做三件事第一看官方发布的弃用 API 变更提前检查集群里的资源对象是否用了旧版 API第二先在测试环境完整走一遍升级流程确认 CNI、Ingress 控制器、存储插件的兼容版本第三备份 etcd这是最后的救命稻草。K8s 控制平面的状态全在 etcd 里备份做扎实了升级失败也能快速还原。我见过有人图省事一次跳过多版本升级结果中间版本的使用说明完全对不上组件兼容问题扑朔迷离最后只能重建集群。K8s 官方支持的升级路径是逐版本升级每一步升完都要验证组件状态别贪快。4.5 排查利器日志、事件与临时调试容器最后分享一套我长期用的排查流程。当线上出现问题时第一步永远是kubectl get events --sort-by.lastTimestamp按时间倒序看集群里发生了什么异常事件很多问题在这一步就定位了七七八八。第二步是看资源状态。kubectl get pod -o wide可以看到 Pod 跑在哪个节点、IP 是多少、是否 Ready。kubectl get svc确认 Service 的 Endpoints 数量是否正常。如果 Endpoints 为空说明标签选择器匹配不到任何 Pod这时候检查 Deployment 和 Service 的标签是否一致。第三步是进入容器内部查。很多基础镜像里没有 shell这时候可以用 ephemeral container 调试kubectl debug -it pod-name --imagebusybox --targetcontainer-name这条命令会往目标 Pod 里附加一个临时容器不会影响原容器的运行。进入之后可以测试网络连通性、查看进程、检查文件系统。我靠这一招解决过不少日志看不出问题但行为异常的疑难杂症强烈推荐大家记下来。写在最后的一点个人体会从最初被 K8s 的复杂性吓到到后来能熟练地用它管理几十个服务我的感受是K8s 的学习曲线确实陡但它绝对值得投入。它不是银弹——不会自动让你的架构变好、不会解决代码质量问题、也不会让运维从此高枕无忧。它真正给到你的是一套把运维思路变成代码声明的能力发布是声明式的扩缩容是声明式的自愈是声明式的。所有基础设施的期望状态都在 Git 里版本可控、过程可审、结果可预测。如果让我给刚入门的同学一个建议别急着一次性掌握所有概念先把 Deployment、Service、ConfigMap 这三个用熟再慢慢扩展 Ingress、HPA、StatefulSet。多写几个 YAML、多部署几个真实应用远比只看文档有效。遇到问题也别慌记住那几个排查命令逐层剥开大部分问题都能自己解掉。K8s 的社区里有一句流传很广的话它不是最完美的系统但它是这个时代大规模分布式的事实标准——我个人对此深以为然。