
简介这是一套面向云原生开发者、运维及架构师的Kubernetes应用编排实践PPT聚焦微服务架构下服务数量激增带来的依赖管理、环境配置与部署更新难题。资源共1个文件为pptx演示文稿压缩包大小约1.41MB。内容围绕容器服务应用编排展开先梳理Kubernetes社区中Helm的发展现状与不足再系统讲解配置管理、应用模板、基于应用的服务组管理三大核心模块并给出多环境部署、快速回滚与环境复制的实现思路。PPT中还涉及ConfigMap挂载、GoTemplate模板渲染、服务间依赖关系管理等关键细节便于读者理解从开发、测试到预发布、生产环境的完整部署链路。适合希望掌握K8s应用编排原理、需要落地服务编排方案的读者。已有86人学习下载可作为团队内部培训或技术方案设计的参考资料。1. Kubernetes应用编排到底编排了什么三个必须想清楚的问题先说一个反直觉的结论在 Kubernetes 上做应用编排最耗时的不是敲 kubectl apply而是把“应用有几个进程、谁先启动、谁连谁、谁可以丢、谁不能丢”翻译成 Deployment、Service、StatefulSet 和存储声明。第一次上手看到“编排”容易以为要写调度算法实际上 K8s 的核心是声明式终态你描述目标状态apiserver、controller-manager 和 scheduler 会不断把现实扭向终态。这篇适合正在从 docker run 迁移到集群、想把多个服务一起交付的人。如果你接触过 Dify 一类业务流程编排先切换概念在 K8s 里编排的是进程、网络、存储和副本量不是 API 调用链。我按裸机到多服务上线的顺序讲命令和 YAML 直接给。2. 先把应用拆成编排对象Deployment、Service、ConfigMap 的选型逻辑2.1 为什么几乎所有无状态服务都能从 Deployment 起步一个应用能不能用 Deployment判断标准就一条这个进程被删掉后换一个节点、换一个 IP 重建业务能不能不丢数据继续跑。能就用 Deployment不能就需要后面提到的 StatefulSet。绝大多数 Web API、Worker、前端静态服务都属于前者所以 Deployment 是应用编排里出现频率最高的对象。# 原来的单机启动方式 docker run -d --name user-service \ -p 8080:8080 \ -e DB_HOSTmysql \ registry.local/user-service:1.2.0同样的应用放到 K8s 里最小编排长这样apiVersion: apps/v1 kind: Deployment metadata: name: user-service namespace: demo spec: replicas: 3 selector: matchLabels: app: user-service template: metadata: labels: app: user-service spec: containers: - name: user-service image: registry.local/user-service:1.2.0 ports: - containerPort: 8080 env: - name: DB_HOST value: mysql resources: requests: cpu: 250m memory: 256Mi limits: cpu: 1 memory: 512Mi这段 YAML 要表达的核心是集群里始终存在 3 个带appuser-service标签的副本。replicas: 3是终态数量selector.matchLabels只识别这个 Deployment 创建的 Podtemplate 里的 labels 必须与 selector 一致否则控制器会直接报错。env对应 docker run 里的-e DB_HOSTmysqlresources不是摆设requests 是调度器分配节点的依据limits 决定容器最多能用多少内存。实际操作里我会把 memory limit 压住避免个别请求把整个节点的内存打爆导致同节点其它 Pod 一起被驱逐。端口这里要注意containerPort: 8080是声明容器内监听端口不是宿主机端口。外部访问需要 Service 暴露。镜像 tag 也要写完整不要把latest带进生产编排否则回滚时根本不知道旧版本是哪一个。Deployment 滚动更新的前提是改 template 里的镜像 tag 或环境变量如果只是修改replicas那只是扩缩容不会触发滚动。2.2 Service 才是应用对外可访问的入口Pod 只是可替代的执行单元Deployment 管副本但 Pod 的 IP 是动态分配的滚动更新时老 Pod 销毁、新 Pod 重建IP 一定会变。Service 把一组 Pod 抽象成一个固定入口用 ClusterIP、DNS 名对外提供稳定地址。请求打到 Service 后kube-proxy 通过 iptables 或 IPVS 转发到某一个后端 Pod。这里的稳定不是指网络质量而是 IP 不随 Pod 重建变化。apiVersion: v1 kind: Service metadata: name: user-service namespace: demo spec: type: ClusterIP selector: app: user-service ports: - port: 8080 targetPort: 8080 protocol: TCP name: httpService 的 selector 必须和 Deployment 模板里的 labels 保持一致。port是 Service 自己暴露的端口targetPort是 Pod 内容器监听的端口如果容器端口写的是containerPort: 8080这里 targetPort 就填 8080。经常有人把 targetPort 写成别的值导致 Endpoint 为空。Service 有三种常用访问类型选型依据很直接类型访问方式适合场景ClusterIP集群内 DNS ClusterIP服务间调用NodePort节点 IP 端口无云负载均衡器、临时调试LoadBalancer云厂商负载均衡器生产外部入口单集群教学环境先用 ClusterIP需要临时测试再改成 NodePort。很多团队一上来就建 LoadBalancer结果云账号没权限还以为是 YAML 写错其实先确认一下 Service 类型和平台能力比改 YAML 更省事。2.3 把配置从镜像里拿出来ConfigMap 的挂载方式配置不写进镜像环境切换时才不用重新构建镜像。ConfigMap 可以整包注入环境变量也可以挂载成文件。环境变量适合短参数多行配置文件更适合挂载。apiVersion: v1 kind: ConfigMap metadata: name: user-config namespace: demo data: application.yml: | server: port: 8080 db: host: mysql --- # 在 Deployment 中声明 volumes volumeMounts spec: template: spec: volumes: - name: config configMap: name: user-config containers: - name: user-service image: registry.local/user-service:1.2.0 volumeMounts: - name: config mountPath: /app/config/application.yml subPath: application.ymlmountPath是容器内的完整文件路径subPath指定只挂载 ConfigMap 里的application.yml这一个 key。不用 subPath 时会把整个 ConfigMap 作为目录挂载如果目标目录里原本有其它文件就会被覆盖。特别是 Java 的config/目录下面往往十几个文件覆盖后启动直接报 class not found这种问题排查起来很迷惑。提示ConfigMap 修改后已经运行的 Pod 不会自动重启。即使 kubelet 最终同步了新文件进程也未必重新读取。需要主动执行kubectl rollout restart deployment/user-service触发滚动。3. 单集群先把第一个编排跑通kubeadm 初始化和最小应用上线3.1 初始化版本选择与 preflight 检查部署编排前要先有一个集群。单节点测试机我建议直接用 kubeadm发行版差异小也方便还原排障过程。机器最低 2C2G内核模块br_netfilter要加载流量转发开关要打开具体可以看 kubeadm 自己的 preflight 检查。kubeadm init \ --kubernetes-versionv1.26.0 \ --apiserver-advertise-address192.168.10.10 \ --pod-network-cidr10.244.0.0/16 \ --ignore-preflight-errorsSwap当终端打印出[init] Using Kubernetes version: v1.26.0和[preflight] Running pre-flight checks时kubeadm 开始检查系统依赖。失败最多的情况是 swap 没关、容器运行时 socket 找不到、6443 端口被占用。参数里我显式忽略 Swap是因为测试机经常为了省事保留 swap生产环境还是关闭更稳。--apiserver-advertise-address要填节点主 IP--pod-network-cidr决定 Pod 网段必须和 CNI 插件一致。用 Flannel 就填 10.244.0.0/16用 Calico 就填 192.168.0.0/16。这个参数不要拍脑袋改否则网络组件起来后 Pod 总是 CrashLoopBackOff。初始化成功后按提示执行mkdir -p $HOME/.kube cp -i /etc/kubernetes/admin.conf $HOME/.kube/config然后安装 CNI。下载 Flannel 官方清单到本地后应用kubectl apply -f kube-flannel.yml不需要着急查看 Pod先等两分钟再确认kube-system命名空间里 coredns 和 flannel 都 Running。节点还没 Ready 就急着部署业务调度会一直 Pending排查起来容易误判。3.2 用 kubectl apply 应用一份完整编排文件有了集群写一份最小编排文件。我用 nginx 做示例因为它只有 80 端口目标端口写错一眼就能看出来。apiVersion: apps/v1 kind: Deployment metadata: name: demo-web namespace: demo spec: replicas: 2 selector: matchLabels: app: demo-web template: metadata: labels: app: demo-web spec: containers: - name: nginx image: nginx:1.24 ports: - containerPort: 80 --- apiVersion: v1 kind: Service metadata: name: demo-web namespace: demo spec: selector: app: demo-web ports: - port: 80 targetPort: 80先创建 Namespace 再 apply 整份文件或者直接在文件里加一个 Namespace 对象。不要图省事把所有服务都塞进default后面环境和权限隔离全是麻烦。kubectl create namespace demo kubectl apply -f demo-web.yamlkubectl apply不是提交一个 YAML 到某个机器去执行而是把这份期望状态写入 etcd。Deployment 控制器看到期望副本数后会创建 ReplicaSetReplicaSet 控制器再创建 Pod调度器把 Pod 放到某个 Nodekubelet 拉镜像、启动容器。整条链路只要有一环没日志Pod 就会一直 Pending、ContainerCreating 或 CrashLoopBackOff。排查顺序建议是kubectl get events --sort-by.lastTimestamp看全局事件再看kubectl describe pod的 Events。不要一上来就摸日志很多问题在日志之前就已经被 kubelet 挡住了。3.3 验证编排结果滚动更新的第一个触发点kubectl get deployment demo-web -n demo -o wide kubectl get pods -n demo -w kubectl rollout status deployment/demo-web -n demo kubectl get svc -n demo-w会一直监听变化看到 Pod 从 Pending 变成 Running 再变成 Running 且 Ready 1/1。rollout status会一直等到 Deployment 完成滚动。滚动更新的触发点不是改 YAML 里的任意字段都行只有改 template 里的内容才会创建新的 ReplicaSet。最常见的触发方式是改镜像 tagkubectl set image deployment/demo-web nginxnginx:1.26 -n demo kubectl rollout history deployment/demo-web -n demo默认maxUnavailable是 25%maxSurge也是 25%意思是滚动时可以多建少量新 Pod同时允许少量旧 Pod 下线。发布风险高的时候我会把这两个参数改成maxUnavailable: 0、maxSurge: 1先起一个新的等 Ready 再杀一个旧的保证发布过程中请求不中断。4. 编排不止 DeploymentStatefulSet、Job、HPA 何时切换4.1 有状态应用到底怎么排StatefulSet 和它绕不开的存储Deployment 把 Pod 看作可丢弃的临时资源但有状态应用要的是固定身份和持久数据。StatefulSet 给每个 Pod 固定序号比如redis-0、redis-1重启后序号不变PVC 也能稳定挂在同一个副本上。apiVersion: apps/v1 kind: StatefulSet metadata: name: redis namespace: demo spec: serviceName: redis replicas: 1 selector: matchLabels: app: redis template: metadata: labels: app: redis spec: containers: - name: redis image: redis:7.0 ports: - containerPort: 6379 volumeMounts: - name: data mountPath: /data volumeClaimTemplates: - metadata: name: data spec: accessModes: [ ReadWriteOnce ] resources: requests: storage: 5GiserviceName必须指向一个 headless Service也就是 ClusterIP 为 None 的 Service。headless Service 不承担负载均衡只为 Pod 提供redis-0.redis.demo.svc.cluster.local这样的 DNS 名。volumeClaimTemplates是 StatefulSet 特有的字段每个副本都会自动生成一个 PVC不需要手动逐个创建。删除 StatefulSet 时 PVC 不会被自动删除这是设计出来的保护防止误删数据。如果你确定要清数据只能手动kubectl delete pvc>apiVersion: batch/v1 kind: CronJob metadata: name: db-backup namespace: demo spec: schedule: 0 2 * * * jobTemplate: spec: template: spec: restartPolicy: OnFailure containers: - name: backup image: registry.local/backup:1.0.0 command: [/bin/sh, -c, backup --dbmysql --buckets3://backup]schedule是标准 crontab 格式0 2 * * *表示每天凌晨两点执行。restartPolicy只能写OnFailure或Never这是 Job 与普通 Pod 最大的区别写Always会直接校验失败。任务跑完会变成 Completed但 Pod 对象会留下来日志还有查。如果备份任务每天一次却不清理时间长了会积累一堆 Pod。可以给 Job 加ttlSecondsAfterFinished: 3600让它完成后一小时自动被回收。我见过一个团队把数据同步任务直接做成 Deployment靠应用里的定时器自我调度。结果节点重启、任务堆积同一时刻跑了几百个线程库直接被打挂。这种场景换成 CronJob 之后至少发布和回滚都是标准对象集群层面能控制并发不会全乱。4.3 HPA 是“流量驱动”的编排开关HPA 是 Kubernetes 里的弹性伸缩控制器根据 CPU、内存或自定义指标调整 Deployment 的副本数。它的位置在编排链路的最上层HPA 改 Deployment 的 replicasDeployment 再改 ReplicaSet 的期望副本数。可以先装 metrics-server再开放行命令kubectl autoscale deployment demo-web \ --cpu-percent70 \ --min2 \ --max10 \ -n demoYAML 方式可读性更好也容易进 GitapiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: demo-web namespace: demo spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: demo-web minReplicas: 2 maxReplicas: 10 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70target用的是 Utilization 百分比它的计算分母是 Pod 的 requests。如果 Deployment 没有给容器写 CPU requestsHPA 算不出利用率只有不扩容。这也是前面说 resources 不能省的原因。测试 HPA 不要直接在生产打流量我一般用kubectl run起一个临时 Pod 去压被测服务观察副本变化后立刻删掉压测 Pod。HPA 的扩容和缩容不是实时的默认有 5 分钟左右的评估窗口缩容还会更保守避免抖动。要知道这个预期否则排障时会以为 HPA 坏了。5. Kubernetes应用编排的五个高频踩坑现象、原因、修复以下五条是我在不同环境里反复遇到过的每条都让线上发布断过。按现象、原因、修复记遇到类似情况直接对照处理。5.1 滚动更新卡在 Progressing新版本起不来发布永远不完成现象执行kubectl rollout status deployment/user-service一直不结束kubectl get deploy显示Progressing新 Pod 反复 ContainerCreating 或 CrashLoopBackOff旧 Pod 却一直没被替换。原因Deployment 默认滚动策略是 maxUnavailable25%、maxSurge25%。当新的 ReplicaSet 无法 Ready 时控制器不会继续删旧 Pod所以服务看似还活着发布却永久堵住。修复先回滚再排查。查看历史版本回滚到上一个可用版本kubectl rollout history deploy/user-service -n demo kubectl rollout undo deploy/user-service -n demo然后看新 Pod 的事件kubectl describe pod user-service-xxxx里的 Events 会给出真正原因。ImagePullBackOff 就检查镜像仓库地址和凭据OOMKilled 就检查 limits 是否太小CrashLoopBackOff 需要结合容器日志判断启动报错。别忘了 Deployment 默认只保留 10 个 ReplicaSet 历史发布频繁的话旧版本可能已经不在 history 里回滚失败只能重新指定镜像 tag。生产环境我一般会在 YAML 里显式设置revisionHistoryLimit: 5或更高并且把maxUnavailable: 0、maxSurge: 1作为发布安全基线。5.2 Pod Ready但Service访问超时targetPort与selector不匹配现象Pod 显示 Running 且 Ready 1/1Service 也存在但curl ClusterIP一直超时。kubectl get endpoints -n demo要么没有 IP要么 IP 地址和 Pod IP 对不上。原因Service 的 selector 没有匹配到任何 Pod或者 targetPort 写到了容器没监听的端口。最常见的是 Deployment 模板 labels 写的是app: user-serviceService selector 却写成了name: user-service其次是 containerPort 是 8080Service targetPort 却写了 9090。修复先看 Pod 的真实 labelskubectl get pod -n demo --show-labels kubectl get svc user-service -n demo -o yaml对比 selector 之后再检查 Service 的ports.targetPort是否等于容器实际监听端口。Pod Ready 只代表容器启动了不代表监听端口一定正常。用kubectl exec进 Pod 内部curl 127.0.0.1:8080或者netstat -lntp确认省得在 Service 层面反复试。Endpoints 是 Service 匹配 Pod 的直接结果所以排查顺序应该是 labels → targetPort → 容器监听端口不要一上来就抓包。5.3 请求量突增后节点被驱逐Pod没设requests和limits现象某个服务流量一上来Pod 频繁重启节点内存持续走高甚至同一节点上别的服务也被一起杀掉。原因没有设置 resources 的 Pod 属于 BestEffort QoS调度器不为它做资源保证节点出现内存压力时kubelet 会优先驱逐这类 Pod。即使没有设 limits容器也可能把宿主机内存吃光最终系统 OOM。修复给所有工作负载补 requests 和 limits尤其要限制 memory limit。requests 是调度依据limits 是运行时上限。一般我按这个原则CPU requests 给常态负载比如 100m500mmemory requests 给常驻内存基线memory limit 给基线的 1.52 倍但要小于节点内存。设置了 ResourceQuota 的对象空间能防止有人忘写 resources。如果应用已经本地开发不了临时排查可以用kubectl describe node看MemoryPressure但根子还是 resources 没写。5.4 ConfigMap更新后Pod配置不生效kubectl rollout restart才是后悔药现象修改 ConfigMap 后执行kubectl apply -f configmap.yamlPod 没有重启容器内挂载的配置也变了但应用还在用旧配置。原因ConfigMap 以 volume 方式挂载时kubelet 会通过更新机制把新文件同步到容器但不会重启 Pod。很多应用只在启动时读取配置运行中的进程不会主动加载新值。如果配置是用 env 方式注入的那种更新永远不会进入已经运行的 Pod。修复主动触发滚动重启kubectl rollout restart deployment/user-service -n demorollout restart是专门为这种场景设计的它会修改 Deployment 模板的 pod-template-hash触发一次滚动而不是把名字相同的 Pod 杀一次再启动。更稳的做法是给 ConfigMap 文件名或对象名加版本号比如user-config-v2然后在 Deployment 模板里引用新名字。这样 Pod 模板变了滚动更新自然发生不需要人工敲 restart。我自己的习惯是配置变更必须走 Gitkubectl diff确认后再 apply别在服务器上编辑生产 ConfigMap。5.5 用Deployment管理单副本有状态中间件重建对象丢数据还没地方找后悔药现象团队图省事用 Deployment 运行 Redis、MySQL 或者仅本地磁盘写数据的服务。某天节点重启后 Pod 漂移到另一台节点内存数据全没了本地文件也找不回来客户端连接缓存全部失效。原因Deployment 的 Pod 重建后是一个全新的临时容器本地写入只在当前 Pod 存活期间有效。Pod 的 IP、主机名都会变PVC 也没有自动绑定所以违背了有状态应用的两个基本要求持久存储和稳定标识。修复单副本中间件也应该用 StatefulSet配套 volumeClaimTemplates 自动申请 PVC。迁移步骤不复杂先把数据库数据做一次全量备份在 K8s 里用 StatefulSet 声明同样的镜像和环境参数再恢复数据到 PVC 路径。测试时主动kubectl delete pod redis-0等它重建后检查kubectl get pvc是否还是同一个以及/data里内容是否还在。如果 PVC 没有自动绑定先确认 StorageClass 是否存在、是否支持 ReadWriteOnce自建集群没有云盘时可以先用 NFS 这类外部存储兜底。6. 更稳的版本化编排用 Helm 把一套应用升降级成一整个 Release裸 YAML 直接 apply 适合一两个应用应用多了以后环境差异、配置项、历史版本都会变成负担。Helm 是 Kubernetes 生态最常用的打包和发布方式本质是模板引擎加 Release 管理。它把 Deployment、Service、ConfigMap 写成模板用 values.yaml 控制差异每次安装或升级都会生成一个 Release 版本号。先创建 Charthelm create user-service生成出来的模板有很多演示内容我会把多余文件删掉只保留 deployment.yaml、service.yaml、configmap.yaml 和 values.yaml。核心的 values 长这样replicaCount: 3 image: repository: registry.local/user-service tag: 1.2.0 resources: requests: cpu: 250m memory: 256Mi limits: memory: 512Mi安装时指定环境文件helm install user-service ./user-service -n demo --values values-demo.yaml升级只改 taghelm upgrade user-service ./user-service -n demo --set image.tag1.3.0Helm 会把整个 Chart 渲染成一批对象统一提交到集群。发布失败时可以用 history 和 rollback 找回上一版helm history user-service -n demo helm rollback user-service 1 -n demo回滚的版本号 1 是第一次 install 生成的 Revision不是镜像版本。Helm 回滚本质上是做一次新的 Deployment 模板应用所以滚动过程仍然需要时间不是瞬间切换。我的习惯是升级前先执行helm template ./user-service --values values-demo.yaml --debug把渲染出来的 YAML 再次 diff确认副本数、镜像 tag、环境变量都没问题再执行helm upgrade --atomic --timeout 5m。--atomic会在失败时自动回滚这个选项救过我很多次。有一次线上发布我忘记检查 values 里的 replicaCount渲染出来是 0如果直接 apply服务流量会在几秒内全部断掉。这件事之后我强制把每次 helm template 后的关键字段检查变成固定动作。希望这次讲到的对象拆解、发布命令和踩坑排查能帮你在自己的 Kubernetes 编排里少走一段弯路。本文还有配套的精品资源点击获取