ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Kubernetes生命周期管理与YAML编写实战:从Pod状态到滚动更新回滚

Kubernetes生命周期管理与YAML编写实战:从Pod状态到滚动更新回滚 很多朋友在学习Kubernetes时都会有这样一个感觉基础概念看文档都能懂一到了自己动手写资源配置YAML或者想把一个应用完整地走一遍发布、更新、回滚、删除的生命周期就开始手足无措。我自己的经历也是这样。早年间做容器化改造面对一堆报错不知道从何下手后来慢慢摸清楚门道才发现整个Kubernetes的使用体验本质上就围绕两件事在打转一个项目从进入集群到退出集群中间会经历哪些状态以及用什么语言YAML去描述这个项目期望的状态。这两件事互为表里理解透了绝大部分日常运维问题都能自己找答案。这篇文章是我Kubernetes系列笔记的第四篇专门把这两个核心话题放在一起拆开讲项目生命周期管理到底管的是什么以及YAML文件编写时那些文档里不会明说、但实战中一定会遇到的细节。1. 项目生命周期管理从提交到销毁K8s是怎么“管”的1.1 核心需求解析你提交的不只是“一个文件”而是一个“期望状态”很多新手会把kubectl apply -f deployment.yaml理解成“把文件传给集群让集群跑起来”。这样理解其实不够准确。Kubernetes的控制循环Control Loop机制决定了你提交的是一份声明式的期望状态而集群里的各个控制器Controller会持续工作让实际状态无限逼近这个期望状态。从这个角度去理解项目生命周期就会清晰很多。一个项目的生命周期绝不只是kubectl create然后kubectl delete这么简单。它会经历以下完整链条提交阶段提交/创建你要把项目做成一个或多个YAML文件通过kubectl apply或kubectl create提交给API Server。调度阶段ScheduleScheduler组件负责决定Pod应该落在哪一台Node上。启动阶段Startkubelet从API Server获取Pod定义调用容器运行时如containerd拉取镜像、启动容器。运行阶段RunPod处于Running状态期间如果容器崩溃、被误删、节点宕机控制器会按照你的期望状态自动拉起副本或者触发重启策略。更新阶段Update你修改了镜像版本或者配置Kubernetes会触发滚动更新Rolling Update在不中断服务的前提下把旧版本一批批换成新版本。回滚阶段Rollback如果更新后发现有问题可以一键回滚到历史版本。销毁阶段Delete你显式删除资源或者触发控制器清理Pod会经历终止Termination流程优雅退出。这里最核心的一个认知是Kubernetes并不关心你要跑什么业务它只关心你声明的期望状态和当前实际状态之间有没有差异以及差异怎么消除。所以生命周期管理的一切行为本质上就是对这个差异的控制。1.2 Pod的生命周期阶段Pending、Running、Succeeded、Failed、Unknown我在排查问题的时候第一眼永远先看Pod处于什么Phase阶段。Pod生命周期里的状态机是理解整个项目生命周期的最小单元。PendingPod已经通过API Server的校验但还没有被调度到合适的节点或者镜像还在拉取中。这个阶段停留太久大概率是资源不足、节点不可调度、镜像拉取失败或者存储卷挂载不上。RunningPod已经至少有一个容器处于运行状态。这个阶段只代表容器进程活着不代表业务可用。Succeeded所有容器正常运行完毕并退出且退出码为0。通常是一次性Job任务跑完后的状态。Failed至少有一个容器以非0退出码终止。Unknownkubelet与API Server失去通信API Server无法获取Pod的真实状态。这里有个常见误区Running不等于健康。很多人看到kubectl get pod显示Running就觉得万事大吉结果业务请求全部超时。因为Running只代表容器进程起来了至于你进程内部是不是有死锁、端口是不是真的在监听、依赖的服务是不是就绪Pod状态是不知道的。所以Kubernetes又引入了ReadinessProbe就绪探针和LivenessProbe存活探针这就属于生命周期管理里的“健康维护”范畴。我自己的习惯是凡是要写进生产环境的Deployment存活探针和就绪探针必须写。后面讲YAML的时候会专门演示怎么写。1.3 控制器的复核机制Deployment、ReplicaSet、StatefulSet在生命周期里各司其职项目生命周期不是靠一个组件“死盯”就能完成的。Kubernetes里有一群“监工”叫控制器Controller。它们各自盯着不同的资源确保实际状态符合期望状态。Deployment无状态应用的首选。它管理ReplicaSetReplicaSet再管理Pod副本数。你改了镜像版本Deployment会创建一个新的ReplicaSet然后逐步缩掉旧的ReplicaSet的副本完成滚动更新。StatefulSet有状态应用专用。它为每个Pod维持稳定的网络标识稳定的hostname和稳定的存储Pod重建后身份不丢。DaemonSet保证每个Node上至少跑一个Pod一般用来做日志采集、监控Agent这类基础设施。Job / CronJob一次性任务、定时任务跑完即Succeeded。我见过有人为了图省事手动去创建Pod而不通过Deployment。这样做的后果就是Pod挂了没人拉起来节点宕了没人重建完全失去了生命周期管理的意义。任何需要长期运行的服务都应该通过Deployment等控制器来管理而不是裸Pod。2. YAML文件编写K8s世界的“通用语”到底该怎么写2.1 YAML基础缩进就是语法拼错字段靠报错教你做人YAML本身不是Kubernetes发明的但它被Kubernetes选作资源描述语言之后每一个缩进、每一个横杠就都有了严格的意义。很多初学者写YAML时第一道坎不是记不住字段而是排版混乱导致解析失败。YAML文件解析规则里最核心的就几条缩进必须统一推荐两个空格不要用Tab。键值对用key: value表示冒号后面必须有空格。数组项用- item表示横杠后面必须跟空格。字符串可以不加引号但如果有特殊字符冒号、井号、大括号最好加引号。---表示文档开始可选。#是注释这个一定要善用。不信邪的朋友可以试试全程用Tab缩进跑一下API Server保准给你一屏幕密密麻麻的解析错误。Kubernetes的YAML解析器对缩进极其敏感这是入门阶段最痛的踩坑点没有之一。2.2 YAML四个顶层字段apiVersion、kind、metadata、spec写任何Kubernetes资源文件顶层结构都是固定的四段式。把这四个字段搞清楚就等于拿到了万能钥匙。字段作用典型值关键点apiVersion指定API版本v1、apps/v1、batch/v1不同资源属于不同API组版本选错直接报NotFoundkind资源类型Pod、Deployment、Service、ConfigMap类型名严格区分大小写首字母大写metadata元数据name: my-nginx; namespace: prod; labels: app: nginxname在集群内唯一labels和annotations都放这里spec期望状态取决于kind每个资源有自己的spec结构这是YAML的主体也是最容易写错的部分apiVersion的复杂性来自于Kubernetes的API分组机制。举个例子Deployment的apiVersion是apps/v1而Pod是v1核心组Job是batch/v1ServiceAccount是v1Ingress在networking.k8s.io/v1。千万不要凭记忆硬背写之前用kubectl explain查是最稳妥的做法。2.3 实例讲解写一个Deployment Service的完整YAML我直接给一个我在实际项目中经常使用的模板带注释大家抄作业的时候可以对照注释看每一段的意思# deployment.yaml apiVersion: apps/v1 # Deployment 属于 apps 组 kind: Deployment # 资源类型无状态应用 metadata: name: my-web # Deployment 名称集群内唯一 namespace: prod # 部署到哪个命名空间 labels: app: my-web # Deployment 自己的标签 spec: replicas: 3 # 副本数量期望始终保持3个Pod selector: matchLabels: app: my-web # 选择器精确匹配 labels 为 appmy-web 的 Pod template: metadata: labels: app: my-web # Pod 的标签必须和 selector 一致 spec: containers: - name: my-web # 容器名在 Pod 内唯一 image: nginx:1.25.3 # 镜像名 标签 imagePullPolicy: IfNotPresent # 镜像拉取策略本地有就不拉 ports: - containerPort: 80 # 容器监听端口 resources: # 资源配额写清楚才能让调度器合理分配 requests: cpu: 100m memory: 128Mi limits: cpu: 200m memory: 256Mi livenessProbe: # 存活探针心跳异常会重启容器 httpGet: path: /healthz port: 80 initialDelaySeconds: 3 periodSeconds: 5 readinessProbe: # 就绪探针检测通过才把流量打进来 httpGet: path: /ready port: 80 initialDelaySeconds: 3 periodSeconds: 5# service.yaml apiVersion: v1 kind: Service metadata: name: my-web-svc namespace: prod spec: type: ClusterIP # 服务类型集群内访问 selector: app: my-web # 关联哪个 Pod 集合 ports: - port: 80 # Service 对外端口 targetPort: 80 # 转发到容器端口 protocol: TCP这套组合在项目中非常常见。Deployment负责应用本身的发布和副本管理Service负责把流量稳定地分发到后端的Pod集合上。二者通过labels和selector进行关联这种关联方式非常松散但极其稳定。3. 实操过程从一条命令到文件落地YAML“抄作业”的三种捷径3.1 捷径一用kubectl create --dry-runclient -o yaml生成基础模板我一直跟身边的人强调不要从零手写YAML那是效率最低的方式。Kubernetes官方给了一条非常实用的命令可以根据你的命令参数自动生成规范的YAML模板你只需要在这基础上微调。# 生成 Deployment 模板 kubectl create deployment my-web --imagenginx:1.25.3 --replicas3 \ --dry-runclient -o yaml deployment.yaml # 生成 Service 模板 kubectl create service clusterip my-web-svc --tcp80:80 \ --dry-runclient -o yaml service.yaml执行--dry-runclient的妙处在于它只做客户端侧的对象构造不会真的提交到集群。-o yaml把生成的对象以YAML格式输出重定向到文件里就得到了一份基本完整的清单。这等于官方帮你把字段结构、缩进格式都排好了你再去做修改极大降低入门门槛。我实际用下来之后发现这种模板唯一的遗憾是没有自动加上livenessProbe和readinessProbe因为这两个探针的路径、端口完全取决于业务本身K8s没法猜。此外资源配额也需要自己手填。但骨架完全是正确的比对着文档手写快得多。3.2 捷径二kubectl explain是随时可查的“字典”很多朋友记不住字段就一次次打开浏览器去搜文档。其实命令行里就自带一份极其完整的字段说明就是kubectl explain。# 查看 Deployment 的顶层字段 kubectl explain deployment # 逐级查看 spec.template.spec.containers 下有哪些字段 kubectl explain deployment.spec.template.spec.containers # 查看某个字段的具体含义 kubectl explain deployment.spec.template.spec.containers.resources这个命令会明确告诉你字段的类型string、integer、boolean、list、object、是否必填Required、默认值以及字段的作用。可以说只要会kubectl explainYAML字段拼写错误这种事能减少八成。我平时写资源清单的习惯是先--dry-run生成骨架然后用kubectl explain去确认每个不确定的字段。整个流程下来几乎不会返工。3.3 从集群已有资源中逆向导出YAML还有一种非常实用的方式就是从集群里已经跑着的资源反向导出YAML用来作为参考或者备份。kubectl get deployment my-web -o yaml这行命令会输出当前资源的完整YAML包括Kubernetes自动填入的一些状态字段。需要注意导出结果里会有status、metadata.creationTimestamp这类期望状态之外的字段直接拿来apply可能会报冲突。实战中我的做法是把导出的结果作为参考模板去掉status和metadata里的运行时信息只保留spec部分。另外要特别提醒的是直接用kubectl edit去修改线上资源或者用kubectl get -o yaml导出的文件去重新apply如果设置不对会变成“覆盖式修改”而不是“声明式核对”容易把资源搞乱。正确的做法是改好原始YAML文件再统一kubectl apply -f。4. 项目生命周期实操从发布到回滚的完整命令序列4.1 创建资源apply 和 create 到底用哪个在Kubernetes里创建资源有两种主要方式create和apply。这个区别困扰过很多人但关键就一句话create是“创建式”如果同名资源已存在会直接报错提示已存在。apply是“声明式”如果资源不存在就创建如果已存在则对比期望状态和实际状态并更新。生产环境我一律建议用apply。因为你要保证自己的YAML文件始终是事实的唯一描述后续任何变更都通过同一个文件去修改、再apply。用create的话更新时就容易依赖kubectl edit或kubectl patch这类临时手段长期维护会越来越混乱。# 创建或更新一个命名空间下的所有资源 kubectl apply -f deployment.yaml -f service.yaml # 或者一次性读取目录下的所有YAML kubectl apply -f ./manifests/执行结束后用以下命令验证资源状态kubectl get deployment my-web -n prod kubectl get replicaset -n prod kubectl get pods -n prod -o wide kubectl get svc -n prod4.2 滚动更新改镜像版本的正确姿势项目上线后总有迭代需求。以Deployment为例滚动更新的标准做法是修改YAML中的镜像版本然后重新apply# 将 nginx:1.25.3 改为 nginx:1.26.0 image: nginx:1.26.0kubectl apply -f deployment.yaml此时Deployment会新建一个ReplicaSet并把Pod副本数先提升到3新版本1个、旧版本3个的过渡阶段然后逐批缩掉旧ReplicaSet的Pod直到新版本Pod全部就绪。整个过程可以通过以下命令随时观察kubectl rollout status deployment/my-web -n prod滚动更新过程中有个“升级策略”的概念有两个关键参数maxUnavailable和maxSurge。maxUnavailable滚动过程中允许有多少个副本不可用。默认是25%对于replicas3的Deployment就是允许1个Pod处于不可用状态。maxSurge滚动过程中允许额外多出的副本数。默认也是25%也就是最多能多出1个临时副本。这两个参数直接决定了更新时的性能和稳定性取舍。如果追求更新期间完全没有容量损失就把maxUnavailable设置为0maxSurge设置为1或者一个适当的值保证先扩再缩。代价是需要额外的Node资源来承载临时副本。4.3 回滚操作再也不用“人肉救火”了我发现很多团队做K8s迁移后仍然保留着“出问题赶紧登录服务器手动改”的原始习惯。其实Kubernetes自带版本历史机制回滚只需要两条命令# 查看历史版本 kubectl rollout history deployment/my-web -n prod # 回滚到上一个版本 kubectl rollout undo deployment/my-web -n prod # 回滚到指定版本 kubectl rollout undo deployment/my-web -n prod --to-revision2回滚的实现机制是Deployment把旧的ReplicaSet对应旧版本重新拉起来按滚动更新的方式把新版本缩掉。所以历史记录里保留多少个版本由spec.revisionHistoryLimit控制默认是10。如果你希望保留更多历史以便多次快速回滚可以适当调大这个值反之就调小。4.4 删除资源不只影响当前还要注意级联影响删除资源是生命周期管理里最容易被低估的一环。我见过有人执行了kubectl delete -f deployment.yaml之后发现Pod掉了一批惊出一身冷汗但实际上这正是级联删除的正常表现。kubectl delete -f deployment.yaml这条命令会删除Deployment以及与之关联的ReplicaSet并触发ReplicaSet去删除所有Pod。如果Pod下面挂了PVC持久化存储卷PVC会不会被删除取决于StorageClass的reclaimPolicy配置。这就是一个很容易埋雷的点删除PVC后如果reclaimPolicy是Delete云端磁盘会被一起删掉数据无法找回。所以删除前一定要先想清楚是临时下线还是彻底销毁临时下线可以先把replicas缩到0保留Deployment定义彻底销毁再执行delete。另外删除资源时的顺序也有讲究一般来说先删Service再删Deployment比较稳妥避免外网流量还在打后端Pod却已经没了。5. YAML编写避坑与常见问题排查5.1 高频报错缩进错了、字段拼错了、Selector不一致YAML编写阶段的报错主要集中在下面几种情况。我整理成了一张速查表方便大家排查现象根本原因处理方式error: error parsing ... yaml: line X: mapping values are not allowed in this context缩进错误或冒号后缺空格检查第X行附近的冒号和缩进统一用两个空格error: unable to recognize ... no matches for kind Deployment in version apps/v1beta2apiVersion写错或者API版本已废弃用kubectl explain deployment查询当前集群适配的版本The ReplicaSet xxx is invalid ... selector mismatchDeployment的selector和template里的labels不一致保证spec.selector.matchLabels与spec.template.metadata.labels完全一致container xxx is waiting to start ... ImagePullBackOff镜像不存在、私有仓库未认证确认镜像名和标签正确私有仓库用imagePullSecretsError creating: pods xxx is forbidden ... exceeded quota命名空间的资源配额ResourceQuota不足查看当前命名空间的quota和已用资源调整请求量或申请扩容最让我印象深刻的一次排错是某个环境里Pod一直处于ContainerCreating看事件日志写的是FailedMount结果排查半天发现是PV的persistentVolumeReclaimPolicy设成了Retain但PVC被误删后数据找不到导致新Pod挂载失败。后来把存储类清理和PVC创建规范化这个问题才根除。5.2 镜像拉取策略的三个典型坑imagePullPolicy是YAML里很容易被忽略的字段但生产上踩的坑非常多。如果镜像tag是latest则默认策略是Always意味着每次启动都会尝试拉取。这在发布频繁的测试环境有时候是好事但到了生产环境就会造成大量无效拉取。如果tag是具体版本如v1.2.3默认策略是IfNotPresent本地已存在就直接用不拉取。对于私有仓库的镜像如果节点上没有缓存而imagePullPolicy又设成了IfNotPresent会导致Pod一直拉取失败。这时候需要确认有没有配置imagePullSecrets。我当年做私有仓库部署时就因为没有配置imagePullSecretsPod一直报401 Unauthorized卡了整整半天后来才发现不只是设置镜像仓库地址那么简单还需要在命名空间里创建对应的docker-registry类型的Secret并在Deployment的spec.template.spec.imagePullSecrets里引用它。# 创建私有仓库拉取凭据 kubectl create secret docker-registry registry-key \ --docker-serverregistry.example.com \ --docker-usernameyour-user \ --docker-passwordyour-password \ -n prod # 在 Deployment 中引用 spec: template: spec: imagePullSecrets: - name: registry-key5.3 生命周期排查心法先看状态、再看事件、最后看日志本地的K8s问题成堆的时候一定要有一个固定的排查路径我自己的顺序是先kubectl get pods看状态。Pending说明调度没完成CrashLoopBackOff说明启动即崩溃ImagePullBackOff说明镜像拉取失败。再看kubectl describe pod pod-name -n namespace的Events部分这里记录了调度器、kubelet、容器运行时留下的所有关键事件包括失败原因。最后才是kubectl logs pod-name -n namespace看应用日志确认崩溃原因。很多人一上来就kubectl logs但如果是Pending状态Pod还没启动日志根本调不出来。正确顺序能帮你省掉大把时间。另外如果容器是CrashLoopBackOff说明进程起来后立刻退出。此时可以先kubectl logs --previous查看上一次容器的输出很多初始化脚本的报错都藏在那里。6. 把生命周期和YAML串起来一个完整的日常发布流程如果不把生命周期和YAML编写串成一条线很容易学一步忘一步。我这里给出一套自己在项目管理中最常用到的发布流程可以作为一个执行模板来参考编写或更新YAML文件Deployment Service如果涉及配置则加ConfigMap。kubectl apply --dry-runclient -f deployment.yaml做本地校验。kubectl apply -f deployment.yaml -f service.yaml推送期望状态。kubectl rollout status deployment/my-web -n prod等待滚动更新完成。kubectl get pods -n prod -o wide确认Pod全部Running并分布在预期节点。验证业务接口通过Service的ClusterIP或Ingress域名。如需回滚kubectl rollout undo deployment/my-web -n prod。这套流程里的每一步都对应生命周期里的一个阶段而每个阶段的操作对象都是YAML文件所描述的资源。可以说你对YAML的理解有多深你对生命周期的掌控就有多强。最后再分享一个个人经验线上环境永远不要手改别人的YAML。哪怕只是改一个副本数也一定要走“修改本地文件→apply→观察状态”这条正路。手改一时快事后全还债。这个教训我是真金白银换来的希望大家不用再踩一遍。
返回列表