Kubernetes应用编排实践:从Helm多环境到GitOps回滚
简介一份面向云原生开发、运维及架构设计人员的Kubernetes应用编排实践讲解PPT围绕微服务架构下服务依赖关系管理、更新部署、多环境配置等核心问题展开内容先梳理Kubernetes社区编排现状深入分析Helm工具偏重包管理、语法复杂、学习成本高、不支持按服务更新以及启动顺序控制等局限随后系统介绍了容器服务应用编排方案。重点讲解三大模块一是配置管理支持通过Helm变量渲染或Kubernetes ConfigMap实现多环境差异化与依赖关系管理二是应用模板采用GoTemplate描述并兼容Helm支持应用快速克隆与环境复制三是应用管理基于服务组提供服务筛选、关联管理和状态可视化。整份PPT逻辑完整从问题驱动到方案解析并辅以具体目录式演示便于理解编排设计思路。资源为单个PPT文件大小约1.41MB适合直接阅读或内部技术分享。目前已有86人学习浏览可帮助中高级技术人员在实际业务中降低微服务部署的复杂性。1. 应用编排不等于一键部署Kubernetes 在编排什么把《基于Kubernetes的应用编排实践》这个主题拆开看表面上讲的是YAML怎么写内里其实是两件事应用由哪些部件组成以及这些部件的更新、扩容、恢复顺序由谁保证。不少团队第一次上Kubernetes时把编排理解成把docker run翻译成deployment.yaml结果发布时要手动改一堆资源、回滚靠人肉记版本、流量切到旧Pod还得靠玄学这就是还没真正进入编排状态。这篇笔记站在一线运维视角按最小编排模型、Helm多环境、容器生命周期和GitOps四个层次把方案讲清楚适合正在做容器化改造、被多环境发布和滚动更新折腾过的团队。你要做的不是背命令而是拿到一套能直接套用的落地路径和参数边界知道哪个对象管状态、哪个对象管流量、哪个对象管顺序。2. 把应用拆成 Kubernetes 对象Deployment、Service、Ingress 的最小编排模型2.1 为什么先谈工作负载Deployment 是所有编排的入口开始应用编排的第一步不是急着写YAML而是先回答一个问题这个应用在Kubernetes里用哪一种工作负载描述。常见的选择有三个Deployment适合无状态服务StatefulSet适合有稳定网络标识和存储的组件DaemonSet适合每个节点都必须跑一个的组件。绝大多数业务应用都落在Deployment上所以我先把Deployment当作编排入口来拆。很多人犯的第一个错是把Deployment当成部署的全部写完就以为应用对外可访问了实际上Deployment只定义了Pod的模板和期望数量流量入口是另一组对象的事这点在2.2和2.3里展开。从资源定义上看Deployment真正干的事是维护ReplicaSet再由ReplicaSet保证Pod数量。滚动更新发生时Deployment会新建一个ReplicaSet逐步把旧Pod缩掉这就是编排的最基本形态。下面是一个生产里常见的Deployment配置注释写在关键字段旁边apiVersion: apps/v1 kind: Deployment metadata: name: order-service namespace: trading spec: replicas: 3 selector: matchLabels: app: order-service template: metadata: labels: app: order-service spec: containers: - name: order-service image: registry.internal/trading/order-service:2024.05.01 ports: - containerPort: 8080 resources: requests: cpu: 250m memory: 512Mi limits: cpu: 1 memory: 1Gi这里的selector.matchLabels必须与template里的labels完全一致否则Deployment创建Pod时会直接报错这是新手最容易在几分钟内翻车的地方。replicas是期望副本数resources里的requests是调度依据limits是运行上限如果团队还没做过资源基线评估可以先按requests等于当前实测均值、limits为均值的两倍起步。环境版本低于v1.16时apiVersion要写成apps/v1beta2现在主流的v1.26.0这类版本直接用apps/v1就好不需要再纠结。2.2 用 Service 暴露 PodClusterIP 与 NodePort 怎么选Deployment描述了应用长什么样但Pod的IP是漂移的滚动更新一次就会换一批地址所以编排里必须有一个稳定的访问入口这就是Service。Service通过selector选择后端Pod并把流量转发给后端的targetPort。选型上默认的ClusterIP只在集群内部可访问适合服务间调用NodePort会在每个节点上开一个高位端口适合临时调试LoadBalancer则交给云厂商的负载均衡器生产环境常用。我一般会建议微服务之间的调用用ClusterIP对外暴露走IngressNodePort只在上线前排查问题时开一下用完就删。一个典型的ClusterIP Service配置长这样apiVersion: v1 kind: Service metadata: name: order-service namespace: trading spec: type: ClusterIP selector: app: order-service ports: - port: 80 targetPort: 8080port是Service对外提供服务的端口targetPort是Pod上的容器端口两者可以不一样但刚上手时建议保持一致少一层心智负担。写完Service后可以立刻用kubectl get endpoints检查后端是否挂上endpoints里有Pod地址说明selector匹配成功了如果endpoints为空基本可以断定是selector的标签和Pod标签对不上。还有一个容易忽略的点Service的port和Deployment里的containerPort必须形成通路也就是targetPort指向的端口要在容器里真正监听很多服务不通的排查最后都落在应用根本没监听这个端口上。2.3 Ingress 入口编排一条命令接入 HTTP 流量当服务数量多起来每个Service都开一个NodePort显然不现实HTTP流量应该统一从Ingress进入。Ingress做的事是按host和path把外部HTTP请求转发到集群内的Service它不是负载均衡器本体真正干活的通常是ingress-nginx或traefik这类Ingress ControllerIngress资源只是策略。有些人把Ingress当成Nginx配置的替代品其实它的价值在于把路由规则声明化随应用一起进Git、一起走发布流程这才是编排的一部分。一个支持按域名和路径转发的Ingress配置如下apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: trading-ingress namespace: trading spec: ingressClassName: nginx rules: - host: trade.example.com http: paths: - path: /orders pathType: Prefix backend: service: name: order-service port: number: 80这里有个边界要提前讲清楚pathType只有Exact和Prefix两种配置成Prefix意味着/orders、/orders/123都会命中同一个后端这在设计路由时要和前端路径规划对齐否则会出现接口死活404的假象。ingressClassName要和你实际部署的Controller对应集群里如果装的是traefik写nginx就完全无效。验证时用kubectl get ingress -n trading看ADDRESS列是否有值再用curl -H Host: trade.example.com http://Ingress地址/orders去试不要一上来就怪Ingress配置先确认Service和Pod这条链路是通的问题往往在链路的下一跳。3. 用 Helm 撑起多环境编排模板化与 values 覆盖3.1 什么时候该上 Helm三套环境的差异来自哪里应用编排做到第二步通常会被同一个问题卡住开发、测试、生产三套环境Deployment、Service、Ingress的YAML几乎一样只有镜像标签、副本数、资源限制和域名不同。团队常见做法是复制三份YAML目录改几个字段结果每次发版要同步改六七个文件漏改一个就是一次生产事故。我判断该上Helm的标准很简单同一套编排描述出现了两次以上的复制并且每次发版都要人工改字段就值得模板化。Helm本质上是把Kubernetes清单变成模板把变化的部分抽成values再用一条命令把模板渲染成实际资源。它解决的不是写更少的YAML而是让一套Chart在多个环境里复用同一份编排逻辑。这里要纠正一个常见误解Helm不是包管理器的替代品它打包的是应用编排不是容器镜像镜像依然由镜像仓库管理。使用Helm时发布动作叫release一个Chart可以同时部署多个release到不同namespace只要release名字不冲突即可这也是多环境用同一套Chart的基础。3.2 写一个最小 Chart模板、values 与 release 的关系创建一个Chart不需要从零写Helm自带脚手架helm create order-service执行后目录里会有Chart.yaml、values.yaml、templates/等文件。我拿到脚手架后会先删掉templates里的tests/和裸的serviceaccount只保留deployment.yaml、service.yaml、ingress.yaml和_helpers.tpl因为脚手架生成的模板默认带一堆release标记对多数业务应用是噪音。Chart.yaml里有一个字段值得注意version是Chart版本appVersion是应用版本发布流程里应该把appVersion和镜像tag联动而不是各改各的。values.yaml是最常改的文件我把它压到最小可维护状态replicaCount: 2 image: repository: registry.internal/trading/order-service tag: 2024.05.01 pullPolicy: IfNotPresent service: type: ClusterIP port: 80 resources: requests: cpu: 250m memory: 512Mi limits: cpu: 1 memory: 1Gi ingress: host: trade.example.com path: /orders配合values.yamltemplates/deployment.yaml里引用的方式是这样apiVersion: apps/v1 kind: Deployment metadata: name: {{ include order-service.fullname . }} namespace: {{ .Release.Namespace }} spec: replicas: {{ .Values.replicaCount }} selector: matchLabels: app: order-service template: spec: containers: - name: order-service image: {{ .Values.image.repository }}:{{ .Values.image.tag }} resources: {{- toYaml .Values.resources | nindent 10 }}这里要注意模板里的{{ .Values.xxx }}必须在values.yaml里有对应键否则渲染直接报错toYaml加nindent是把resources对象缩进转成YAML格式缩进错了Pod会被拒绝调度。渲染验证用helm template order-service ./order-service它能输出最终的YAML而不真正部署这一步是发布前必须做的自检比直接helm install稳得多。3.3 三个必调参数replicaCount、image.tag、resources.limits使用Helm之后日常发布里最常动的是replicaCount、image.tag和resources.limits这三个参数。replicaCount直接决定副本数生产环境至少3起步且要配合PodAntiAffinity才能把副本打散到不同节点否则节点宕机时所有副本可能同时在丢。image.tag是发版的核心每次发布必须改我建议用有意义的日期或流水号不要用latestlatest在镜像拉取策略为IfNotPresent时会导致新镜像不被拉取这是典型的改了镜像但没生效的翻车现场。resources.limits则是稳定性边界很多团队以为Helm只负责部署不负责资源治理实际上values里如果不显式定义limits线上应用可能把节点内存吃满。发布时用--set临时覆盖单个参数是Helm最常见的操作方式helm upgrade --install order-service ./order-service \ --namespace trading \ --set replicaCount5 \ --set image.tag2024.05.02 \ --values values-prod.yaml这里--values和--set会合并规则是--set的优先级更高。注意--reuse-values这个参数要慎用它会把上一次的values全部复用如果上一次在测试环境误用了--set改过参数生产这次就会继承那个覆盖值强烈建议不要在生产脚本里写--reuse-values老老实实把稳定配置写进values-prod.yaml。发布完成后用helm list -n trading和helm history order-service -n trading查看release状态和版本历史比单纯看Pod状态更能反映编排是否按预期执行。4. 编排里的状态与顺序InitContainer、探针与生命周期钩子4.1 用 InitContainer 解决依赖顺序Kubernetes的编排有一个经常被忽略的点它不解决应用之间的依赖顺序。比如订单服务启动前必须保证数据库Schema已迁移、配置中心已就绪靠Deployment默认行为是同时创建所有容器依赖方很可能在数据库没准备好时就启动了然后反复重试。这个时候InitContainer是编排顺序里最直接的手段它在主容器启动前按顺序跑完任何一个失败整个Pod就不会进入Running。一个典型的等服务就绪再启动配置长这样spec: initContainers: - name: wait-for-db image: busybox:1.36 command: [sh, -c, until nc -z db-service 5432; do echo waiting for db; sleep 2; done] containers: - name: order-service image: registry.internal/trading/order-service:2024.05.01这里的busybox镜像只是用来做网络探测nc -z的意思是只探测端口不发送数据db-service是数据库在集群内的Service名5432是数据库端口。InitContainer有几个边界要知道它不参与Pod的存活探针失败会重启整个Pod多个InitContainer按顺序执行不是并行它占用的资源要计入Pod总资源。适合放InitContainer的是等待外部依赖这一类场景不适合把真正的业务启动放进去否则每次Pod重建都要重新跑一遍耗时的初始化反而拖慢滚动更新。4.2 liveness 与 readiness 的分工探针参数怎么设探针是应用编排里最容易配错又最影响发布质量的部分。livenessProbe决定容器是否要被杀掉重启readinessProbe决定Pod是否要摘出Service流量。很多团队只配一个liveness结果发布时新Pod刚起来就被打流量一重试就触发liveness形成启动-被杀-再启动的死循环。正确做法是两个都配分工明确readiness管流量接入liveness管进程健康。下面这组参数是我在交易类服务上验证过的起步配置readinessProbe: httpGet: path: /health/ready port: 8080 initialDelaySeconds: 5 periodSeconds: 10 timeoutSeconds: 2 failureThreshold: 3 livenessProbe: httpGet: path: /health/live port: 8080 initialDelaySeconds: 15 periodSeconds: 20 timeoutSeconds: 3 failureThreshold: 3initialDelaySeconds是关键它告诉Kubernetes在容器启动后多久才开始探测给应用留出启动时间设太短会让启动稍慢的服务反复被误杀。periodSeconds是探测间隔越短越快发现故障但代价是额外请求压力生产环境建议readiness用10秒、liveness用20秒起步。failureThreshold表示连续失败多少次才判定挂掉设成1会非常敏感网络抖动都能触发重启我一般不低于3。还有一点很多人不知道探针路径必须是HTTP返回200才叫健康如果应用的健康接口返回的是302或没有独立健康端点探针会一直失败这是Pod永远不Ready最常见的根因。4.3 preStop 钩子在滚动更新里的作用滚动更新时Kubernetes先创建新Pod等它Ready后再给旧Pod发SIGTERM。问题在于应用收到SIGTERM后立刻退出已有的请求还没来得及处理完用户就会看到一个连接被重置的错误。编排里解决这个问题的标准组件是preStop钩子在发送SIGTERM前先执行一段脚本给应用一个优雅处理存量请求的窗口。lifecycle: preStop: exec: command: [/bin/sh, -c, sleep 10]这个配置的意思是收到终止指令后先睡10秒再触发SIGTERM。sleep的时间要参考两个值一是readinessProbe的periodSeconds因为流量摘除要等一次探测周期才能生效通常5到10秒二是应用自身的最大单请求处理时间如果接口最慢要8秒sleep就不能小于这个值。很多人想用一个更优雅的方案在preStop里调用应用的graceful shutdown接口前提是应用真的实现了否则不如sleep来得可靠。这里有个配合项terminationGracePeriodSeconds默认是30秒如果preStop睡了20秒而SIGTERM后应用还要10秒才退出就会超出默认值被强杀所以调整preStop时要把terminationGracePeriodSeconds一起加大我一般会设成preStop时间加15秒。5. 应用编排避坑我踩过的 4 个真实问题5.1 CrashLoopBackOff 且日志为空先查探针再看 OOMKilled现象Pod状态一直CrashLoopBackOffkubectl logs却看不到任何业务日志仿佛应用什么都没输出就死了。第一次遇到时很容易以为是镜像问题反复重建也没用。原因日志为空通常意味着容器不是被业务逻辑杀掉的而是被系统层判定死亡。最常见的有两个livenessProbe失败被Kubernetes强制Kill以及内存超出resources.limits被内核OOMKiller杀掉。这两种情况在kubectl describe pod里都会留下事件记录但刚上手的人很少去看Events段。解决先kubectl describe pod pod名 -n trading看最后几行Events如果是Liveness probe failed按4.2的探针参数重新调initialDelaySeconds和failureThreshold如果是OOMKilled看limits.memory和实际内存曲线把limits调大或者检查应用是否有内存泄漏。我自己的排查顺序固定是先Describe看事件再Logs看业务最后看监控曲线按这个顺序十次有八次不用看日志就能定位。5.2 滚动更新后流量仍打到旧 Pod检查 readiness 和 selector 标签现象helm upgrade后kubectl get pods看到新Pod已经Running但请求还是打到旧Pod新版本半天没有流量甚至出现新旧版本同时被调用的混乱。原因滚动更新是否切流不看Pod是否Running而是看它是否Ready。新Pod如果没有通过readinessProbeService的endpoints里就不会有它另一种情况是Deployment的selector或Service的selector在新版本里被改过比如有人给label加了版本号导致Service选不中任何新Pod。解决先kubectl get endpoints -n trading看新Pod地址在不在列表里不在就查readinessProbe在的话再对比Service的selector和Pod的labels用kubectl get pods --show-labels -n trading逐个核对。提示Selector 一旦创建就不可变改 label 前先想清楚宁可多写一个稳定的 app 标签也不要为每次发布新建 Selector。一个血的教训不要在模板里给selector加版本号标签这会让回滚变得非常痛苦。5.3 Helm 升级后 values 没生效--set 与 --reuse-values 的叠加陷阱现象helm upgrade用--set image.tag2024.05.02部署了新版本但Pod里跑的镜像还是旧taghelm list里显示的version倒是更新了。原因这个坑几乎都出在--reuse-values上。当脚本里写了--reuse-values又写了--set时--reuse-values会把上一次的values整体复用随后--set的覆盖在某些Helm版本里不会像预期那样生效结果就是镜像tag还是旧值。还有一种是前一次操作误用了--set把某个参数改成临时值这次没用--values指定完整文件导致遗留覆盖一直生效。解决生产发布脚本里禁止使用--reuse-values每次upgrade都显式给--values values-prod.yaml再用--set覆盖当次要改的字段。验证时不要只看Pod先helm get values order-service -n trading看最终的values渲染结果再kubectl get deployment -o yaml看镜像字段两层都对了才算发布成功。5.4 NodePort 不通但 ClusterIP 正常从端口范围查到安全组现象Service类型改成NodePort后集群内用ClusterIP访问正常但从外部通过节点IP加NodePort访问却超时端口在节点上明明能telnet通部分节点有些节点的端口却是closed。原因NodePort本身是集群级能力但外部访问路径上还有三个环节节点安全组或防火墙是否放行了端口段kube-proxy是否接管了这个端口以及Service的externalTrafficPolicy是否导致流量只在部分节点转发。默认端口范围是30000-32767云厂商安全组经常只放行80和443这里就会直接超时。解决先kubectl get svc -n trading确认NodePort端口号在节点上用ss -tlnp | grep 端口确认kube-proxy在监听然后在控制台检查安全组是否放行了对应端口段。externalTrafficPolicy默认是Cluster流量可能被转发到没有这个Pod的节点第二次访问就出现有时通有时不通这是NodePort调试里最迷惑的一个现象把externalTrafficPolicy改成Local可以减少一跳但也要接受Pod分布不均的事实。6. 把应用编排收进 GitOps用 Argo CD 让发布可回滚6.1 为什么从 Helm 命令发布升级到 GitOps团队成员一多发布就不能只靠某个人在本地敲helm upgrade。GitOps把Git仓库当成编排的唯一真源所有改动先进Git再由Argo CD把仓库状态同步到集群谁改了什么、能不能回退都有记录。这套做法的价值不在自动化而在可审计和可回滚。6.2 一个最小 Application 配置把 Git 仓库当作编排唯一真源apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: trading-app namespace: argocd spec: project: default source: repoURL: https://git.internal/trading/deploy-config.git path: environments/prod targetRevision: main destination: server: https://kubernetes.default.svc namespace: trading syncPolicy: automated: prune: true selfHeal: truesource定义了编排内容从哪个仓库哪个目录读取destination定义了同步到哪个集群的哪个namespace。prune负责清掉集群里多余的资源selfHeal会在集群状态偏离Git时自动拉回。生产环境我建议先关掉automated改成手动同步等团队熟悉了再开否则误提交会直接上生产。6.3 用 rollout 和 argocd rollback 做验证与回滚日常发布用两层检查先kubectl rollout status等Deployment滚动完再argocd app sync触发同步、argocd app get看状态。回滚也不再扒历史命令kubectl rollout status deployment/order-service -n trading kubectl rollout history deployment/order-service -n trading kubectl rollout undo deployment/order-service -n trading --to-revision2 argocd app get trading-app argocd app rollback trading-app 2注意rollout的revision和Argo CD的版本号是两套体系前者看Deployment后者看Application同步历史。我踩过的一个坑是rollback后selfHeal又把集群拉回Git里的新版本所以回滚前先确认Git仓库里的版本是否也要一并回退。建议每次发布前把helm template的输出和git commit记到发布备注里事后定位这一版是哪个commit渲染出来的会省很多时间。希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →