资讯动态

Kubernetes入门:声明式编排、服务部署与故障排查实战

发布时间:2026/10/26 12:01:54 来源:尧图企业网站定制
1. 先把 Kubernetes 说清楚它到底是个什么东西1.1 从“三台机器手工部署”的崩溃现场说起我第一次接触到 Kubernetes 这个词的时候正在一台跳板机上开三个终端窗口一边scp传 jar 包一边手写nohup启动脚本一边盯着tail -f看日志有没有报端口被占用。那次上线业务方临时要求把服务从 2 个实例扩到 6 个我花了将近四十分钟才把配置改完、机器挑好、端口分配好、Nginx 反代加上最后还因为其中一台机器的 Java 版本不一致白折腾了一轮。后来我明白了这四十分钟里做的事情几乎没有一件是有创造性的——挑机器、分端口、改配置、起进程、挂负载均衡全是重复劳动而且是那种“人一累就容易出错”的重复劳动。Kubernetes 就是把这些重复劳动固化成一套被反复验证过的自动流程。用一句不绕弯的话讲Kubernetes 是一个容器编排系统你把“我想要什么状态的集群”写进配置文件它负责把现实世界扭到这个状态上去并且一旦现实偏离了它自己再扭回来。这句话里有两个重心——“声明式”和“自愈”后面会反复提到先把这两个词记住。它不负责打包镜像那是构建阶段的事它也不负责写业务代码。它管的是“镜像做好了之后怎么在一堆机器上稳定、可伸缩、可滚动地跑起来”。所以你会看到真正用 K8s 用得顺的团队往往已经先解决了容器化这一关镜像构建、配置外置、无状态化这些都理顺了才轮到编排上场。如果你连 Dockerfile 都还在纠结先别急着上 K8s这是我踩过的第一个顺序错误。适合谁看刚接触容器想往集群方向走的运维和开发、手上有几台服务器想把部署流程规范化的中小团队、以及面试前需要把概念串成一条线的人。下面我会尽量用“我实际怎么做的”这个视角来写而不是抄一遍官方概念列表。1.2 它解决的问题其实就藏在你每天的重复操作里把日常部署里的糟心事列一列K8s 的能力基本是一一对应的这个映射关系搞清楚了后面学任何对象都不会觉得是凭空冒出来的。日常痛点人工做法K8s 对应的解法服务挂了没人知道靠监控告警 人工重启探针检测 自动重建 Pod流量涨了要扩容手动开机器、改配置、重启调整副本数或配置自动伸缩新版本上线要停机停服、替换包、启动滚动更新逐批替换机器坏了服务全挂祈祷那台机器别挂调度器把 Pod 挪到健康节点配置散落在各台机器手工改配置文件ConfigMap / Secret 统一注入不知道服务之间怎么调靠文档和口口相传Service 提供稳定的虚拟地址这套对应关系背后有一个共同的思路把“怎么做”从人手里拿走只保留“要什么”。传统运维脚本是命令式的——先拉代码再编译再起进程第一步失败了整个流程就断在那儿K8s 是声明式的——我告诉它“我要 6 个副本镜像版本是 v2暴露 8080 端口”至于先起哪个、在哪台机器上起、起失败了重试几次它自己决定。这个转变的价值在你集群规模超过十台机器之后会突然变得极其明显。三台机器的时候你还能记住每台跑什么三十台的时候靠人脑已经不可能了必须有一个东西替你做“状态对齐”。2. 选型思考为什么最后落到 Kubernetes 上2.1 容器化之后必然会撞上的四道坎很多人以为容器化是终点其实它只是起点。容器跑起来之后四个问题会立刻冒出来这四道坎是我判断“要不要上编排”的实际依据。第一道坎是调度。一个镜像要跑得找一台有足够 CPU 和内存、并且能拉得到镜像的机器。手工挑机器的时候你会本能地避开那些已经跑满的但机器一多这个“本能”就失效了。第二道坎是服务发现。容器重启之后 IP 会变A 服务怎么找到 B 服务早期我们用 Nginx 上游列表 一个定时刷新脚本脚本挂了就全乱。第三道坎是生命周期管理。滚动发布、回滚、扩缩容、优雅停机每一件事单独写脚本都不难难的是它们互相之间的状态冲突——比如正在滚动更新的时候突然要扩容两个脚本同时改同一份配置结果就是灾难。第四道坎是资源的实际利用率。我见过太多团队按峰值给每台机器预留资源结果是平时 CPU 利用率不到 10%机器全在那儿闲着。编排系统能把这些碎片资源重新拼起来用。这四道坎里只要同时踩中两道以上自研脚本的维护成本就会开始指数级上升。判断标准很简单如果你的部署脚本里开始出现“等三秒再检查”这种补丁就该考虑换方案了。2.2 几个方案的实际对比别只看宣传材料当时我也比较过几种路子把真实的取舍摆出来比列一堆参数表有用得多。方案上手成本弹性能力生态成熟度适合的场景手工脚本 固定机器极低靠人无三台以内、变更极少单机容器管理工具低单机范围一般单机开发、小规模验证轻量编排方案中中等一般边缘节点、资源受限环境Kubernetes高强极强多团队、多服务、需要长期演进Kubernetes 的代价是实打实的概念多、组件多、出问题时排查链路长。我见过有团队为了跑两个小服务硬上了一个完整集群结果运维成本比业务本身还高。规模不匹配是最常见的浪费。但从另一个角度看K8s 最大的优势其实不是功能多而是它把很多问题的解法标准化了。你遇到“如何做灰度发布”社区里有成熟的方案你遇到“如何做多集群容灾”也有成熟方案。选它某种程度上是在选一个庞大的、经过大量生产环境检验的“参考答案库”。这个价值在长期演进中会越来越明显。2.3 声明式 API 这个设计值得单独说一段K8s 里几乎所有东西都是通过 YAML 描述的很多人一开始觉得这只是“配置文件换了个格式”其实差别很大。命令式的思路是“执行一个动作”创建 3 个实例。执行完动作就结束了系统里发生了什么变化你得自己去看。声明式的思路是“描述一个期望状态”这个服务应该有 3 个实例镜像用这个端口是这个。系统会持续对比“期望”和“现实”不一致就动手修。这个过程是循环的不是一次性的。这个设计的三个实际好处我在用了一段时间之后才真正体会到。一是可重复。同一份 YAML在测试集群和线上集群跑出来的结果是一致的不会因为“当时手抖多敲了个参数”而出现环境差异。二是可回滚。因为期望状态是被记录下来的回滚就是把期望状态改回上一版而不是“把刚才的操作反向执行一遍”——后者在复杂系统里几乎不可能做对。三是可审计。谁在什么时候把副本数从 3 改成 10全都有记录。这个在出故障复盘的时候能省下大量扯皮时间。注意声明式不代表你可以不看结果。kubectl apply返回成功只说明配置被接受了不代表 Pod 真的跑起来了。这一点是新手最容易误解的地方。3. 核心概念拆解把集群当成一家公司来理解3.1 控制面和工作节点管理层与干活的人一个 K8s 集群在物理上分成两部分我习惯用公司来类比。控制面是管理层不直接干活只负责决策。里面几个组件的分工很清晰API 服务器唯一的对外入口所有请求都从这里进。你可以把它理解成公司的前台加档案室任何人要查东西、改东西都得走这里。键值存储集群的唯一事实来源所有对象的状态都存在这儿。它挂了整个集群就等于失忆。调度器负责“派活”看哪个节点资源够、约束满足就把 Pod 分过去。它只做决策不动手。控制器管理器一堆控制循环的集合每个循环盯着一类对象发现现实和期望不一致就去修。这才是“自愈”真正发生的地方。工作节点是干活的人每个节点上跑着容器运行时真正负责拉起容器的组件。节点代理节点上的“班长”向控制面汇报本节点状态同时接收指令去管理本节点的 Pod。网络代理组件负责把 Service 的虚拟地址转换成实际的后端 Pod 地址。这个分层带来的一个实际好处是控制面挂了已经跑着的业务不会立刻停。节点上的 Pod 还在跑只是没法做新的调度和变更了。我遇到过控制面因为磁盘写满而不可用的情况业务侧完全无感知修好存储之后一切恢复正常。这个容错设计在关键时刻很值钱。3.2 四个最常用的对象别一上来就啃全部K8s 的对象有几十种新手一口气看完肯定懵。实际项目里百分之八十的场景只需要这四个Pod、Deployment、Service、Ingress。Pod是最小调度单元一个 Pod 里可以有一个或多个容器它们共享网络和存储。这里有个容易搞混的点大家平时说的“一个服务跑几个实例”指的是几个 Pod不是几个容器。一个 Pod 里塞多个容器通常只用于“主容器 辅助容器”这种紧耦合场景比如主程序旁边挂一个日志收集的边车。Deployment是管 Pod 的负责副本数、滚动更新、回滚。生产环境里几乎不会直接创建 Pod都是通过 Deployment 间接管理。原因很简单裸 Pod 挂了没人管Deployment 挂了会被重建。Service给一组 Pod 提供一个稳定的访问入口。它的核心是标签选择器——只要 Pod 带着匹配的标签就会被自动纳入后端列表。Pod 重建换 IP 了没关系Service 会自动更新。这一层抽象解决的就是前面说的“服务发现”那道坎。Ingress管的是集群外部的流量怎么进来通常做七层路由比如按域名或路径分发到不同 Service。它需要一个具体的入口实现来承载这部分选型要结合你的实际环境。这四个对象的关系可以这样串Ingress 收到外部请求转发给 ServiceService 按标签找到对应的 PodPod 由 Deployment 保证数量。对象管什么常见误用Pod最小运行单元直接创建裸 Pod 用于生产Deployment副本与发布拿它管有状态服务Service稳定访问入口忘记 selector 标签对不上Ingress外部七层路由以为它自己就能监听端口3.3 标签和命名空间这套系统的索引机制标签是我认为 K8s 里最被低估的设计。它是一组键值对挂在你想要的任何对象上然后各种选择器通过标签来圈定范围。Service 找 Pod 靠标签Deployment 管 Pod 靠标签很多运维操作也是按标签批量执行的——比如“把所有带envtest的 Pod 全删了重来”。标签设计得好不好直接决定了后期运维是轻松还是痛苦。我的一般约定是至少打三类标签应用名、环境、版本。应用名用于聚合环境用于批量操作时隔离版本用于灰度时精确圈定。这三类标签一旦定下来就不要随便改因为改标签意味着所有引用它的选择器都要跟着改。命名空间则是逻辑隔离的手段可以理解成“集群内部的分区”。不同环境、不同团队、不同项目放在不同命名空间里资源配额、权限控制都可以按命名空间来划分。注意命名空间不是网络隔离。默认情况下不同命名空间的 Pod 是可以互相访问的需要网络策略才能限制。这个误解我见过太多次了。4. 一次完整的实操从零跑起一个可访问的服务4.1 环境准备本地验证用轻量方案别一上来就搞生产集群学 K8s 最容易犯的错是第一天就去折腾多节点集群安装。装到一半卡住热情全没了。我的建议是分两步走先用本地轻量方案把概念跑通再考虑真实集群。本地方案里容器里跑集群的工具是最省事的一条命令起来一条命令删掉不污染宿主机。# 检查本机是否具备条件 docker version kubectl version --client # 创建一个本地集群单控制面 kind create cluster --name demo # 确认集群可用 kubectl cluster-info kubectl get nodes这里有个细节值得说kubectl的版本最好和集群版本差距不超过一个小版本差太多会出现某些资源字段不识别的问题。我第一次用的时候就是客户端太新apply 一个旧版本才有的字段报错信息还很含糊查了半天。验证集群正常看节点状态是Ready就够了。如果卡在NotReady八成是容器运行时或网络插件没起来先看节点的事件描述。kubectl describe node 节点名describe这个命令要养成习惯它输出的 Events 部分往往直接告诉你问题在哪比翻日志快得多。4.2 写一份能用的 Deployment每个字段都想清楚为什么下面这份 YAML 是我平时起步用的模板字段不多但每个都是有意加的。apiVersion: apps/v1 kind: Deployment metadata: name: demo-web labels: app: demo-web env: dev version: v1 spec: replicas: 2 selector: matchLabels: app: demo-web strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 maxUnavailable: 0 template: metadata: labels: app: demo-web env: dev version: v1 spec: containers: - name: web image: nginx:1.25-alpine ports: - containerPort: 80 resources: requests: cpu: 100m memory: 128Mi limits: cpu: 500m memory: 256Mi readinessProbe: httpGet: path: / port: 80 initialDelaySeconds: 3 periodSeconds: 5几个字段的选择理由值得展开说说。maxUnavailable: 0加maxSurge: 1这个组合意思是更新过程中先起新的新 Pod 就绪之后才删旧的全程可用副本数不低于期望值。代价是需要额外的资源余量好处是用户完全感知不到发布。如果集群资源紧张可以放宽到maxUnavailable: 1但要做好瞬间容量下降的心理准备。requests和limits不能省。不写 requests调度器就没法准确判断节点还剩多少资源可能把 Pod 塞到一个已经吃满的节点上不写 limits一个内存泄漏就能把整台机器拖垮。这两个值怎么定下一节单独讲。readinessProbe是我认为最容易被忽略但最该加的一项。它决定了一个 Pod 什么时候被纳入 Service 的后端。没有它Pod 容器一起来就被判定为可用但应用可能还在初始化流量打进去就是一堆 502。4.3 暴露服务并验证顺手做一次滚动更新和回滚kubectl apply -f deployment.yaml kubectl get pods -l appdemo-web -w-w是 watch实时看状态变化。这是我最常用的命令组合之一比反复执行get直观得多。确认 Pod 都是Running且READY是1/1之后创建 ServiceapiVersion: v1 kind: Service metadata: name: demo-web-svc spec: selector: app: demo-web ports: - port: 80 targetPort: 80 type: ClusterIPClusterIP是集群内部访问用的类型先从最简单的验证起别一上来就用需要外部依赖的类型。验证方式是在集群里起一个临时 Pod 去访问kubectl run curl-test --imagecurlimages/curl --rm -it --restartNever -- \ curl -s -o /dev/null -w %{http_code} http://demo-web-svc返回 200 就说明 Service 这一层通了。这个排查顺序很重要先确认 Pod 本身正常再确认 Service 能找到 Pod最后才查外部入口。反过来的话问题出在哪一层根本判断不了。滚动更新和回滚# 触发一次更新 kubectl set image deployment/demo-web webnginx:1.25.3-alpine # 观察进度 kubectl rollout status deployment/demo-web # 出问题就回滚 kubectl rollout undo deployment/demo-web # 看历史版本 kubectl rollout history deployment/demo-webrollout history里如果想看到每次变更的具体内容需要在 Deployment 上加一个注解记录变更原因。这个习惯我强烈建议养成不然回滚的时候只能靠猜。kubectl annotate deployment/demo-web \ kubernetes.io/change-cause升级基础镜像到 1.25.34.4 资源参数怎么算别拍脑袋填requests和limits填错是生产事故的高发区我讲讲自己的估算方法。requests 定的是“保证量”调度器按这个值找节点。所以它应该接近应用平稳运行时的实际占用不能按峰值填否则资源全部被预留利用率极低。limits 定的是“上限”超过 CPU 上限会被限流超过内存上限会被直接终止。CPU 超限只是变慢内存超限是致命问题所以这两个值的策略应该不同。我的经验做法类型requestslimits理由CPU平稳期用量的 1.0~1.2 倍requests 的 2~3 倍允许短时突发超了限流但不致命内存平稳期用量的 1.2 倍平稳期的 1.5~2 倍留出波动空间同时防泄漏拖垮节点对于运行时自带堆管理的语言还有一个坑要特别注意堆上限必须小于容器内存 limit。如果堆上限设成和 limit 一样堆还没到上限容器就已经被终止了。我一般把堆上限设成 limit 的 70% 左右剩下 30% 留给运行时自身和堆外内存。这个比例不是绝对但方向上一定要留余量。实际用量的数据从哪来跑一段时间之后看监控取 P95 值作为参考基准比凭空估要靠谱得多。刚上线没数据的时候先按保守值给观察一周再调。5. 新手最容易踩的坑和排查路径5.1 Pod 状态码速查表先看图再动手排障的第一步不是登机器是看状态。状态码本身就是最直接的线索。状态常见原因第一步看什么Pending资源不足、亲和性不满足、无可用节点describe里的 EventsImagePullBackOff镜像名错、凭证缺失、仓库不可达镜像地址与拉取凭证CrashLoopBackOff应用启动即退出、配置错误容器日志Running 但 0/1就绪探针未通过探针配置与健康检查路径OOMKilled内存超过 limit实际内存曲线与 limit 值Evicted节点资源压力节点状态与驱逐记录这张表我贴在工位上很长一段时间因为绝大多数问题看一眼状态就能缩小到两三个方向。5.2 我踩过的几个具体坑都是文档里不会写的坑一镜像地址写对了但拉不下来。有一次配了私有仓库的凭证Secret 建了也在 Deployment 里引用了但就是拉不到。查了很久才发现Secret 和 Deployment 不在同一个命名空间跨命名空间引用是不生效的。这类问题最难查因为表面上一切配置都对。养成习惯创建 Secret 时就和目标 Deployment 放在同一个命名空间。坑二Service 存在但访问不通。最常见的原因是 selector 和 Pod 的标签对不上哪怕差一个字符都不行。排查方法很直接kubectl get endpoints demo-web-svc如果 ENDPOINTS 是空的说明一个 Pod 都没匹配上问题肯定在标签这一层不用往下查网络。这个命令能省掉大量无效排查。坑三滚动更新卡住不动。rollout status一直挂着最后超时。原因通常是新的 Pod 起不来而maxUnavailable: 0又要求新 Pod 就绪之后才删旧的于是一直等。这时候不要急着手动删 Pod先看新 Pod 的事件和日志找到根因。卡住本身是保护机制在起作用它避免了把可用副本删光。坑四配置改了但应用没生效。从 ConfigMap 挂载到容器里的配置更新之后需要一定时间才会同步到文件而且应用自己不一定重新读取。我一般会在应用里加一个文件变更监听或者干脆把配置的哈希值写进 Pod 的注解里配置一改注解变自动触发滚动更新。这个技巧比手动重启优雅得多。坑五节点磁盘写满导致所有 Pod 被驱逐。容器日志没有做轮转一个刷日志刷得凶的服务几天就把节点磁盘吃干净了。这个问题的可怕之处在于它影响的是整台节点上的所有服务不是单个应用。日志轮转必须在集群层面统一配置别指望每个应用自己管好。提示排障的顺序永远是“由外到内、由粗到细”——先看对象状态再看事件再看日志最后才登容器。反过来做的话时间全花在无关的地方了。5.3 网络不通的排查顺序按这个顺序走少走弯路网络问题是最让人头疼的因为它涉及好几层。我总结了一个固定顺序按这个走基本不会漏。第一步确认 Pod 本身是否在监听。进容器执行一次本地访问能通说明应用没问题问题在网络上。第二步确认 Service 的后端列表是否为期望的 Pod 集合。get endpoints一看便知。第三步确认从另一个 Pod 访问 Service 地址是否通。这一步区分“集群内部服务发现”和“外部入口”两类问题。第四步如果前面都通才看入口层的配置。这个顺序的价值在于每一步都能明确地划掉一整类可能性而不是东一榔头西一棒子。我见过太多人一上来就怀疑网络插件折腾半天最后发现是应用只监听了回环地址——外部根本访问不到。6. 一点个人体会和下一步的方向Kubernetes 这东西概念看着多但如果把“声明式 控制循环”这条主线抓住剩下的都是这条主线在不同场景下的具体形态。Deployment 是控制循环Service 的端点维护也是控制循环节点健康检查还是控制循环。理解了这一层再去学新的对象速度会快很多。我个人的建议是入门阶段别贪多。先把 Pod、Deployment、Service、ConfigMap 这四样用熟能独立把一个多副本服务部署、更新、回滚、看日志、查问题就已经能覆盖大量实际场景了。有状态服务、自动伸缩、网络策略这些等真的遇到需求再学那时候带着具体问题去学效率完全不一样。下一步如果要继续往下走我觉得优先级最高的是两件事一是把资源配额和探针配好这是稳定性的地基二是把日志和监控接上没有可观测性的集群出了问题只能靠猜。这两件事做完集群才算真正能托付业务。至于更复杂的部分比如多环境隔离、灰度发布策略、集群升级方案那些是在真实运维过程中一点点磨出来的很难靠看文档一次性学会。我到现在也还在踩坑只不过踩的位置越来越靠里了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑