去年我接手了一套早期搭建的 Kubernetes 集群,表面看一切正常,几十个服务在跑,监控面板上一片绿。但真正开始做 Kubernetes 操作管理之后我才发现,绿色只是表象。团队里每个人都能把一个应用部署上去,可"部署上去"和"在上面稳定运行一年半载"完全是两码事。这篇文章想聊的,就是我沉淀下来的一套操作管理方法论:从发布、扩缩容、排障到集群日常维护,每一步背后的逻辑是什么,以及那些踩过坑之后才真正理解的细节。
如果你是一个刚接触 Kubernetes 的后端开发,或者是一个开始独立负责微服务平台的运维工程师,这篇文章应该能帮你少走不少弯路。我不打算从头讲什么是 Pod、什么是 Deployment,这些官方文档里都有;我重点分享的是"怎么管"这件事——上线怎么发才不慌,资源怎么配才不浪费,出了问题怎么定位最快,平时又要做哪些功课才能不出大事。
1. Kubernetes 操作管理的真实边界:别把"能部署"当成"会运维"
1.1 从"开发态"到"运行态"的思维转换
我带过的很多后端同学,第一次接触 Kubernetes 时的感觉是:写好 YAML,kubectl apply一下,Pod 起来了,任务完成。这个阶段其实只是"会部署",离"会运维"还差得很远。
操作管理和开发最大的区别在于:开发关注的是"这段代码逻辑对不对",而操作管理关注的是"这个系统在长期运行中会不会出问题、出了问题能不能快速恢复"。代码逻辑不对,通常会在测试阶段暴露;但集群里的问题往往具有延迟性和隐蔽性——明明是上周改的一个配置,这周流量高峰才把问题引爆;明明只是某个节点内存紧张,结果把整条链路都拖垮了。
所以我把 Kubernetes 操作管理的边界划得很清楚:它不是写 YAML,而是负责应用从"提交到集群"到"退役下线"之间所有环节的稳定性、可控性和可恢复性。具体展开就是四个环节:
- 发布管理:怎么把新版本安全地放上去,出问题怎么退回来;
- 容量管理:资源怎么分配、怎么扩缩容,既能扛住高峰又不浪费钱;
- 可观测性:日志、指标、事件、链路,出了问题能在几分钟内定位,而不是靠猜;
- 故障恢复:节点挂了、集群升级失败了、数据要恢复了,每一步操作的兜底方案是什么。
这四个环节如果有人在认真做,那这套集群就是"被管理"的;如果只是靠大家自觉手工操作,那它就是"靠缘分运行"的。
1.2 对"变更"保持敬畏:操作管理的核心其实是变更管理
我把这一节单独拎出来说,是因为它是我踩过最大的坑之后才醒悟的。
Kubernetes 集群里绝大多数的故障,追根溯源都不是"环境坏了",而是"有人改了点东西"。改镜像 tag、改 ConfigMap、改资源配额、升级节点、调整探针参数……每一次变更都有概率引入事故。操作管理要做的事,不是禁止变更,而是让每次变更都有迹可循、可回退、可验证。
我在团队里立了几条规矩,看起来很简单,但真的坚持下来之后事故数量下降得非常明显:
- 所有变更必须走版本化配置,禁止直接对运行中的 Deployment 执行
kubectl edit改两下就完事; - 每次变更前先看当前状态,
kubectl rollout status、kubectl get events确认基线是健康的; - 凡是涉及配置文件的变更,必须有回退方案,至少要知道上一个稳定版本是哪个 tag、哪份配置;
- 重大变更(升级、迁移、扩容)必须有明确的操作窗口,不要在业务高峰期试水。
这套纪律听起来像"流程上的废话",但实际操作管理做得好不好,差别往往就在这里。集群本身是健壮的,失控的从来都是人对它的操作。
2. 发布策略与版本回滚:把上线变成一件可逆的事
2.1 三种主流发布策略的取舍
Kubernetes 原生 Deployment 最常用的发布方式是滚动更新(RollingUpdate),策略配置在spec.strategy里。核心参数就两个:maxSurge和maxUnavailable。
apiVersion: apps/v1 kind: Deployment metadata: name: order-service namespace: prod spec: replicas: 3 revisionHistoryLimit: 10 strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 maxUnavailable: 0 template: spec: containers: - name: order-service image: registry.example.com/prod/order-service:v2.3.1 ports: - containerPort: 8080 readinessProbe: httpGet: path: /health/ready port: 8080 initialDelaySeconds: 15 periodSeconds: 5maxSurge: 1表示发布过程中允许临时多出一个新 Pod,maxUnavailable: 0表示不允许旧 Pod 一次性全部下线。这套组合是最保守的:先起一个新 Pod,等它通过 readiness 探针之后,再停一个旧 Pod,保证整个发布过程中始终有完整的服务能力。缺点是发得慢,副本多的时候整体发布时间会拉长;但换来的是稳。
默认值其实是maxSurge: 25%和maxUnavailable: 25%,对小副本数的服务来说风险不小。比如只有 3 个副本,默认策略下发布瞬间可能同时只有一个旧 Pod 可用,流量一上来就 502。我在生产环境里基本都会显式改成1/0,牺牲一点速度,换来全程稳定。追求更快的团队也可以折中用1/1,至少保证新旧各占一半。
除了原生滚动更新,蓝绿发布和金丝雀发布也是操作管理中常用的手段。蓝绿发布相当于一次性把全部流量切到新版本,回退也快;金丝雀则是先放少量流量给新版本,观察指标没问题再逐步放大。这两个通常要借助服务网格或者接入层来控制流量比例,不是纯 Deployment 能力能覆盖的。我的建议是:没有流量控制能力时,老老实实用滚动更新 + 保守参数;有网格或网关做流量灰度时,才上金丝雀。
2.2 回滚机制:发布事故的止损操作
发布出问题不可怕,可怕的是发布出问题之后不知道怎么快速退回去。Kubernetes 的 Deployment 天然带版本历史,只要你不手贱删 ReplicaSet,每个发布版本都会留存。利用好这套机制,rollout undo就是你的后悔药。
# 查看发布历史 kubectl rollout history deployment/order-service -n prod # 回到指定版本 kubectl rollout undo deployment/order-service -n prod --to-revision=12 # 观察回滚进度 kubectl rollout status deployment/order-service -n prod实际操作中我养成了一个习惯:每次发布前先查一下当前是第几个 revision,心里有数,出问题需要回退时直接指定 revision 而不是凭感觉。另外revisionHistoryLimit一定要保留合理数量,默认 10 一般够用,但如果你发布的频率很高,又不确定哪一版是"最稳的那一版",可以把历史保留到 20 或者更多,代价只是多占一点 etcd 存储。
还有一个很多人忽略的点:kubectl rollout undo会把 Pod 模板回退到上一个版本,但如果问题是出在 ConfigMap 或者镜像仓库里的外部依赖上,光回滚 Deployment 是没用的。我后面会专门讲这个案例。
2.3 一次发布事故的复盘:配置不回滚引发的连锁反应
分享一次我亲手处理的线上事故,时间线很有代表性。
某天上午我们更新了配置中心的地址,把旧的域名切换到一个新网关。当时只更新了 ConfigMap,没有触发任何 Pod 重建——这是第一个错误,ConfigMap 变更不会自动热更新到已运行的容器里,除非应用自己实现了监听重载。结果就是一批旧 Pod 还拿着旧配置在跑。
到了下午,另一个同事发布服务的新镜像,触发了一次滚动更新。新 Pod 启动后读取的却是那份已经过时的 ConfigMap,直接导致服务依赖的下游地址全部不可达,readinessProbe 连续失败,新 Pod 一直起不来,滚动更新被卡死。这时候更麻烦的是:我们想rollout undo回退,但回退到旧版本镜像后,Pod 读的还是同一份错误的 ConfigMap,问题依旧。
最后我们是分两步解决的:先把 ConfigMap 改回正确配置,再重新执行rollout restart让所有 Pod 重建,等集群恢复健康之后,再重新走一次规范的配置变更流程。
这个事故让我学到三个教训:第一,ConfigMap 和镜像版本要当成一个整体来变更,最好通过 Helm 这类工具把配置和镜像打包成一次 release;第二,发布前必须确认当前集群的基线状态是健康的,不要在一个已经漂移的环境里再做变更;第三,回滚方案要针对"变更的源头"设计,而不是机械地回退 Deployment。操作管理的高下,往往就在这种细节里。
3. 资源配额与弹性伸缩:让集群稳定和成本同时可控
3.1 requests 和 limits 不是玄学:看懂 QoS 等级
集群里最常见的资源问题,不是"不够用",而是"用得不合理"。每个容器都能设置requests和limits,这两个字段看似简单,理解不透会造成两类典型事故:一类是requests设得过高,导致 Pod 永远调度不上去,集群利用率很低;另一类是limits不设或者设得过大,某个应用内存泄漏时直接把节点打爆,连累同节点的其他服务。
先讲清楚这两个词的本质。requests是调度器做决策时用的"预约"值,它承诺给这个 Pod 至少这么多资源;limits是运行时 kubelet 施加的"硬顶",超过这个值容器会被限制 CPU 或者被杀掉。两者一起决定了 Pod 的 QoS 等级,而 QoS 等级直接关系到节点资源紧张时的驱逐优先级。
| 配置情况 | QoS 等级 | 节点资源紧张时的命运 |
|---|---|---|
| 每个容器 requests 都等于 limits | Guaranteed | 最后被驱逐,最安全 |
| 只配了 requests,或 requests 和 limits 不相等 | Burstable | 中等优先级 |
| 完全没配 requests 和 limits | BestEffort | 最先被驱逐 |
我第一次带团队时纠正过一个很常见的误区:很多人以为 limits 不设就是"无限用"的福利,实际是在把风险转嫁给整个节点。一旦节点出现内存压力,kubelet 会优先杀掉 BestEffort 的 Pod,而这些 Pod 往往正是那些没有认真配资源的服务。
我的落地建议是:核心业务线全部按 Guaranteed 配置,也就是 requests 和 limits 保持一致;非核心任务给 Burstable,但 limits 一定要设,防止它失控。比如一个 Java 服务,我给它的起步配置是下面的样子,再根据压测和监控逐步微调:
resources: requests: cpu: 250m memory: 512Mi limits: cpu: "1" memory: 1Gi注意 CPU 的250m表示四分之一核,"1"表示一整核,写整数时加引号是为了避免 YAML 解析成数字类型导致歧义。内存方面我特别提醒一点:不要给 Java 服务设置一个比堆内存大一点点就完事的 limits,因为 JVM 本身还有堆外内存、元空间、线程栈,limits 设得太贴近堆大小,高峰期很容易触发 OOMKilled,而且是那种毫无预兆的杀进程。
3.2 HPA 配置与调优:让弹性伸缩真正跟得上流量
副本数是死的,流量是活的,中间必须有个弹簧,那就是 HorizontalPodAutoscaler(HPA)。由于不少集群还在用 v1 版本的 HPA(只支持 CPU),我这边建议直接上autoscaling/v2,它同时支持 CPU、内存和自定义指标:
apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: order-service-hpa namespace: prod spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: order-service minReplicas: 3 maxReplicas: 20 behavior: scaleDown: stabilizationWindowSeconds: 300 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 65 - type: Resource resource: name: memory target: type: Utilization averageUtilization: 70这里有一个新手很容易踩的坑:HPA 算的是 Pod 当前的 requests 与实测用量的比值,而不是"整机 CPU 使用了多少"。所以如果你把某个服务的 requests 设得很高,比如 4 核,那么即使它实际只用了几百毫核,HPA 看到的利用率也低得可怜,永远不会扩容。反过来,requests 设太小,又会造成频繁扩容。换句话说,HPA 是否好用,前提是资源 requests 设得是否合理,这两件事是绑定的。
stabilizationWindowSeconds是缩容的冷静期。默认 300 秒,意思是即便最近指标降到目标值以下,也要等 5 分钟才动手缩容,防止流量一抖就把 Pod 缩没了。这里我吃过亏:有一次为了省钱把这个值调成了 60,结果流量稍微波动一下,Pod 规模就在十几分钟内反复横跳,反而把系统搞得不稳定。之后我再也没动过这个默认值。扩容侧的scaleUp可以配得更激进一点,比如支持多批次并行扩容,因为流量上涨时扩得越快越好,而缩容反而要慢半拍。
3.3 集群级容量管理:ResourceQuota 与节点伸缩
光给单个 Deployment 配资源还不够,操作管理要站在"整个命名空间""整个集群"的视角来看容量。否则就会出现一种经典乱象:某个团队把所有资源都占了,别人发不了新服务,谁都不敢去动他们。
用ResourceQuota给命名空间设硬上限,是我在每个非开发环境都会做的事:
apiVersion: v1 kind: ResourceQuota metadata: name: prod-quota namespace: prod spec: hard: requests.cpu: "40" requests.memory: 200Gi limits.cpu: "80" limits.memory: 400Gi persistentvolumeclaims: 50配了配额之后,超限的资源申请会直接报错被拒绝。一开始大家会抱怨"怎么又建不了",但正是这种约束逼着每个团队去认真估算自己的需求,而不是随手写一个天大的数字。配合LimitRange给命名空间内的容器设默认的 requests/limits,还能防止有人忘了配导致 Pod 变成 BestEffort。
节点层面的伸缩,我建议按工作负载类型拆分节点池:核心在线服务池、批处理池、系统组件池。在线服务池配置好集群自动扩缩容插件(Cluster Autoscaler),让它根据 Pod 调度失败的情况自动加节点、空闲时自动缩容。这个插件我实际用下来的感受是,它比想象中保守,扩容触发条件比较苛刻,所以重要的活动(比如大促、新品发布)之前,我会提前手动把节点池扩到预估水位,而不是完全依赖自动扩缩。自动扩缩是日常兜底,关键活动提前规划才是靠谱的操作管理。
4. 排障第一现场:从 Pod 生命周期到事件日志的三段式排查链路
4.1 先看懂 Pod 的五种状态,再动手查
排障的第一步不是去看日志,而是先搞清楚 Pod 现在到底处于什么状态。状态本身就能过滤掉一半的问题。下面是我自己整理的高频状态速查表,已经不知道帮我在群里挡了多少次重复提问:
| 状态 | 常见原因 | 第一步动作 |
|---|---|---|
| Pending | 资源不足、节点污点、PVC 没绑定 | kubectl describe看 Events |
| ContainerCreating | 镜像拉取失败、存储卷挂载卡住 | describe 加 kubelet 日志 |
| CrashLoopBackOff | 容器启动就退出、探针失败 | kubectl logs --previous |
| OOMKilled | 内存超过 limits | 调大 limits 或排查内存泄漏 |
| Terminating 卡住 | 优雅退出超时、存储卷被占用、finalizer 问题 | 看事件,强制删要谨慎 |
举个最常见的例子:CrashLoopBackOff。这个状态说明容器启动后立刻崩溃,然后被 kubelet 反复重启,间隔时间是指数递增的,第一次 10 秒、第二次 20 秒、然后 40 秒……很多人第一反应是看实时日志,但容器每崩溃一次,日志就清空一次,kubectl logs拿到的经常只有最近几行。正确姿势是加--previous去看上一个实例留下的日志:
kubectl logs order-service-7b8c9d-abcde --previous -n prod这个参数我几乎每天都会用到。很多启动即崩溃的应用,问题往往不是出现在最新一次运行,而是上一次运行留下的报错信息才完整。
4.2 三段式排查链路:描述、日志、指标
面对一个不明确的故障,我给自己定了一个固定顺序,宁可慢一点,也不要东一榔头西一棒子。这个顺序是这样的:
第一段,看 Events。kubectl describe pod <name>的结果里,最下面有一块 Events,记录了调度、拉镜像、挂卷、启动、探针检测等等所有关键动作的结果。调度失败、镜像拉不到、卷挂不上、探针没通过,在这里全都会留下记录。很多人跳过 Events 直接看日志,结果查了半天,最后发现问题是调度器找不到满足资源条件的节点,日志里当然什么都看不出来。
第二段,看日志。结合 Pod 状态决定看当前日志还是--previous。如果 Pod 是 Running 但业务异常,直接kubectl logs -f跟实时日志;如果是 CrashLoop,用上面的--previous。有 InitContainer 时,记得也要看 Init 容器的日志,启动链路卡在哪一步,一看便知。
第三段,看指标。到了这一步,你已经知道 Pod 起来了、代码也在跑,但为什么表现不对?那就上指标。kubectl top pod能快速看资源实测用量,更细的历史曲线要到监控系统里看 CPU、内存、网络、磁盘的时序数据。我这句话想强调很多遍:一台机器卡不死问题,一段事件记录往往一条命令就能定位,先数据后猜测,永远不要在生产环境"猜着试"。
我还建议把集群事件做成一个常看的视图,定期执行:
kubectl get events --sort-by=.lastTimestamp -A | tail -50这条命令能帮你发现很多别人还没感知到的隐患——比如某个节点反复出现拉镜像超时、某个命名空间频繁触发驱逐警告。等故障真的发生时再看事件,往往已经错过了提前处理的机会。
4.3 高频故障复盘:探针配置和资源限制是两大主凶
如果给排障案例做统计,排在前面的故障里,探针配置不合理和资源限制不合理能占到一半以上。
探针的典型事故是:readinessProbe 的periodSeconds设得太短、failureThreshold又太小,导致 Pod 一启动就被疯狂探活,还没就绪就被打上新版不可用的标签,流量被切走。还有一种反向事故,initialDelaySeconds 设得比应用真实启动时间还长,探针迟迟不生效,流量提前打进来,全部报错。探针参数必须对齐应用的启动曲线,我的经验是先用kubectl logs和监控确认服务实际的启动耗时,再倒推initialDelaySeconds,而不是随便抄网上模板。
资源限制的典型事故就是前面说的 OOMKilled。有一次我们线上一个推荐服务频繁被杀,查监控发现内存曲线呈阶梯式上涨,很明显是缓存没有淘汰策略。当时最稳的处理是先把 limits 临时上调,保住服务,然后让开发排查代码里的缓存上限。这里我有一个原则:线上救火的第一动作永远是止血(回滚、扩容、调参),而不是就地修复代码。代码修复要经过完整测试才能上线,这是操作管理和开发直觉最大的不同。
另外,网络层面还有一个高频坑:Service 解析异常。表现是 Pod 都是 Running,但应用之间互相访问超时。排查时先确认 CoreDNS 是否正常,然后看 Service 后端的 Endpoints 是否为空。如果 Endpoints 是空的,多半是 Service 的 selector 写错了或者 Pod 的 label 没跟上,这是我在新团队里见过最多的"神秘故障"之一。
5. 集群日常维护:升级、备份与那些没人提前告诉你的坑
5.1 节点维护的标准流程:cordon、drain、uncordon
节点要重启、要换硬件、要打内核补丁,这是操作管理的家常便饭。很多人直接 SSH 到节点上reboot,结果正在上面运行的 Pod 全部被打断,有状态服务的灾难就此开始。正确流程是三板斧:先 cordon 再 drain 最后 uncordon。
# 第一步:标记节点为不可调度,新 Pod 不再上去 kubectl cordon node-worker-1 # 第二步:驱逐节点上的 Pod,让它们在其他节点重建 kubectl drain node-worker-1 --ignore-daemonsets --delete-emptydir-data # 第三步:维护完成后恢复调度 kubectl uncordon node-worker-1drain会优雅地驱逐节点上的 Pod,先把节点标记为不可调度,再逐一对 Pod 执行驱逐和终止流程,给应用留出优雅退出的时间。--ignore-daemonsets是必需的,因为 DaemonSet 在每个节点都要运行,drain 时不应该把它们也赶走。--delete-emptydir-data要慎用,它表示允许删除节点上的 emptyDir 临时数据,只有当你知道这些数据可丢时才加。
这里还有一个看不见的细节:drain 是否顺利,取决于 Pod 的 PodDisruptionBudget(PDB)。如果某个 Deployment 只有 2 个副本,而你从来没配过 PDB,一次 drain 就可能把两个副本同时赶走,服务瞬间不可用。配了 PDB 之后,驱逐会遵守"最多允许中断多少副本"的约束,不够额度就等,直到条件允许。我现在每个关键服务都会配上:
apiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: order-service-pdb namespace: prod spec: minAvailable: 2 selector: matchLabels: app: order-serviceminAvailable: 2的意思很直白:再怎么驱逐,也要保证至少两个副本活着。节点维护和 PDB 是配套的,只做 drain 不配 PDB,等于没做保护。
5.2 集群升级的节奏与降级预案
Kubernetes 本身也需要升级,不管是修复安全漏洞还是使用新特性。但集群升级比应用发布风险高一个量级:涉及 kube-apiserver、scheduler、controller-manager、etcd、kubelet、kube-proxy 一大堆组件,还有一个硬性约束——Kubernetes 只支持从当前小版本升级到相邻的小版本,跨版本升级必须先逐级升。
我的升级节奏是:先在测试集群完整走一遍,再升级预发集群,最后才轮到生产。生产升级从控制面开始(etcd、apiserver 等),再逐节点升级 worker 节点。升级 worker 节点的标准动作其实就是上一节说的 drain,然后升级 kubelet 和运行时,最后 uncordon。
升级前必须做的事,我列个清单:
- 备份 etcd 快照,并且验证快照可恢复(后面专门讲);
- 记录当前所有组件的版本,确认升级路径合法;
- 检查集群里是否用了废弃的 API 版本,提前迁移;
- 挑业务低峰窗口,最好挑那种有明确"如果失败就原地恢复"预案的时段;
- 准备好回退版本,不要抱着"升上去就行"的心态。
我见过一次集群升级事故,升级到一半发现某个旧版本的自定义资源定义和控制面组件不兼容,整个集群的 API 被拖慢。当时如果有人记得先备份 etcd 并验证恢复流程,其实半小时就能回滚,但因为没备份,最后花了一个晚上才恢复。自那以后,我的铁律是:任何一次集群级变更,没有验证过的备份就等于没有备份。
5.3 备份与恢复:etcd 快照是最后的兜底
Kubernetes 集群的所有状态都存在 etcd 里:Deployment、Service、ConfigMap、Namespace……节点组件可以重装,但 etcd 里的状态一旦损坏,整个集群的管理信息就没了。所以 etcd 备份是集群操作管理里最需要重视的兜底动作。
常用的备份方式是用 etcdctl 打快照:
ETCDCTL_API=3 etcdctl --endpoints=https://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 \ snapshot save /backup/etcd-snapshot-$(date +%F).db快照的恢复要特别小心:不是简单地放回去就行,通常要先停掉所有控制面组件,用etcdctl snapshot restore生成一个新数据目录,再配置 etcd 使用这个目录,最后把控制面组件拉起来。整个流程如果没提前演练过,真到出大事时必然手忙脚乱。我建议每三个月至少在测试环境完整演练一次备份恢复,这个过程还能逼着你把恢复文档写清楚。
除了 etcd,日常备份还包括有状态服务的持久化数据。这块没有统一方案,数据库可以走备份工具,对象存储可以走跨区域复制,只要原则一致就行:备份必须定期验证可恢复性,否则就是一张废纸。
5.4 日常巡检与几个容易忽略的坑
维护工作做得好不好,看的是"事故之前做了什么"。我给自己定了一个巡检清单,每周花半小时在集群里过一遍:
- 节点状态是否全部 Ready,是否有节点内存和磁盘持续走高;
- 是否有 Pod 反复重启,Events 里是否有异常频率的驱逐或拉取失败;
- 证书有效期是否充足,尤其是 apiserver 和 ingress 相关的证书;
- 镜像仓库里是否堆积了大量旧 tag,触发仓库侧的清理或限流;
- 日志组件的磁盘占用,journald 或容器日志目录有没有持续膨胀。
关于日志膨胀我要多说一句。容器 stdout 日志如果长时间不轮转,会把节点磁盘写满,节点磁盘满之后 kubelet 会直接把节点标记为 NotReady,这算是我见过的最"冤枉"的故障——应用本身没事,纯粹是被日志拖死的。现在集群一般默认有日志轮转配置,但如果你用的是自建节点,一定要检查/var/log和相关数据目录的落盘情况。
还有一个坑我踩过多次:Service 的端口分配。NodePort 类型的 Service 在集群里数量越来越多时,默认端口范围 30000-32767 会被逐渐耗尽。操作管理做到后期,如果发现新 Service 创建后kubectl get svc一直拿不到端口,多半就是这个原因。这种问题看起来像神秘故障,其实查一下 apiserver 的日志就能看到端口分配失败的记录。
写在最后的一点体会
做了一段时间 Kubernetes 操作管理之后,我最大的变化是:不再追求"会操作多少命令",而是开始追求"每次操作之前都知道最坏的结果是什么,并且已经准备好了应对手段"。集群本身是个相当健壮的系统,它有能力处理节点故障、网络抖动、应用崩溃;真正让它失控的,往往是没有纪律的变更、没有兜底的升级、没有验证的备份。
如果你刚接手一套集群,我建议先从三件事做起:第一,把命名空间配额和关键服务的 PDB 配上;第二,把 etcd 备份加进定时任务,并且真的演练一次恢复;第三,给每个核心服务建立发布 checklist,至少包含健康基线确认、回退版本确认和日志监控的观察窗口。这三件事做完,你的操作管理就从"凭感觉"变成了"有体系"。
最后再分享一个小习惯:我在每次发布或者任何对集群的变更操作结束之后,都会在终端里留下一句kubectl get events -A --sort-by=.lastTimestamp | tail -20的习惯性回车。这个动作看起来微不足道,但很多次都是靠它提前发现了即将到来的问题。操作管理没有多少惊心动魄的时刻,真正的功夫,都在这些看起来平平无奇的日常里。