
做运维这几年我对K8s最深的感受不是它有多强而是默认状态太裸了。裸到什么程度想知道集群整体情况得把kubectl get node、kubectl get po --all-namespaces一个个敲过去线上流量突然涨起来得人肉盯着再手动kubectl scale部署一套应用明明只是改了镜像版本却要把十几个YAML从头到尾贴一遍。这三件事恰恰就是Dashboard、HPA、Helm分别解决的。这篇是我K8s系列的第十篇前面的基础篇已经把Pod、Deployment、Service这些核心概念都过完了这一篇开始进入运维工具层装Dashboard做可视化配HPA让应用自己感知负载用Helm把YAML做成能打包、能回滚的Chart最后用一个完整部署示例把它们串起来。适合已经能熟练操作kubectl、开始想把运维能力真正落地的读者。1. 先说说这三个工具在我日常运维里的分工很多人容易把这三种工具混在一起觉得都是管理K8s的其实它们的职责边界非常清楚。我自己的理解是Dashboard是眼睛让人能看清集群HPA是手让应用能自动干活Helm是大脑加仓库让一堆散装YAML变成可复用、有版本、能回滚的成品。它们配合起来刚好覆盖了日常发布、观察、伸缩三条主线。1.1 从复制粘贴YAML到组件化部署我踩过的弯路我刚接触K8s的时候部署一个服务全靠复制别人的YAML改改名字和镜像就往上扔。每天最常干的活儿是打开编辑器翻之前的文件找image:那一行改版本号然后kubectl apply -f。坏了就再改改完再apply。这种模式在环境少、服务少的时候勉强能用一旦你有一个开发环境、一个测试环境、一个生产环境每个环境还独立命名空间纯手动维护十几个YAML早晚要出事。出过的最大一次事故是我给生产环境的某个服务升级镜像版本改完image:之后忘了同步改资源配额结果新版本Pod带着不合理的limits直接起不来整个服务断了将近二十分钟。后来我复盘这件事发现问题的根源不在于操作失误而在于YAML根本没有结构化管理版本号、资源量、副本数全散落在不同文件里没有一个统一的参数入口。这就是后面为什么Helm对我冲击那么大——它不改变K8s资源本身但它改变了人管理资源的方式。1.2 三件工具覆盖的运维场景图谱先说Dashboard。它解决的问题很朴素你不是随时都能记住几十个资源的状态尤其是非技术同事或者临时顶班的同学他们用命令行去看复杂资源列表很不现实。Dashboard把节点状态、工作负载、存储、日志这些信息汇总成可视化的界面操作还能直接落到API Server上。再说HPA。很多应用并非一直平稳早晚高峰流量差异很大。传统做法是人盯监控、手动扩容但半夜三点流量涨了靠人肉去执行kubectl scale根本不现实。HPA就是让K8s自己盯着指标超过阈值加Pod低于阈值收Pod。最后是Helm。它解决的是K8s资源怎么打包、怎么复用、怎么发布的问题。比如一套应用由Deployment、Service、ConfigMap、HPA五个资源组成这五个文件放一起打成一个包这个包就能在不同环境里用不同参数反复安装——这种能力无论kubectl还是Dashboard都给不了你。2. Dashboard上手指南部署、安全登录与常见故障这一章我直接讲实操。Dashboard的部署在官方仓库里有整合好的YAML但实际落地时要注意几个点不然很容易装完打不开或者打开了有各种毛病。2.1 两行命令装好面板但镜像源要提前想清楚我用的是v2.7.0版本的推荐部署文件命令很简单kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.7.0/aio/deploy/recommended.yaml这条命令会在kubernetes-dashboard命名空间下部署好所有相关资源包括Deployment、Service、ServiceAccount和一些默认的RBAC授权。装完检查一下kubectl -n kubernetes-dashboard get pods如果状态显示Running说明控制面已经起来了。这里要提醒的是镜像拉取问题。Dashboard的镜像仓库在kubernetes.gcr.io国内网络环境经常拉不动你可能会看到Pod卡在ImagePullBackOff状态。这种情况我一般有三种处理方式登录到能访问外网的机器用docker pull把镜像拉下来再docker tag改成目标仓库地址推送到自己内网仓库直接把Deployment镜像地址改到可用的国内镜像源很多云厂商有同步镜像如果集群本来就有代理能力配置好节点的/etc/docker/daemon.json中的镜像代理。我自己的习惯是第二种最省事改一下kubectl -n kubernetes-dashboard edit deployment kubernetes-dashboard里的image:字段即可。2.2 创建只读或管理员账号用Token登录而不是绕过登录Dashboard默认不对外开放也不会自动创建带权限的用户这是好事安全上不建议图省事直接开--enable-skip-login或者是匿名登录。正确做法是创建专门的服务账号。我日常用的账号分两种一种是管理员账号方便全集群排查问题一种是只读账号给过来观察环境的同事用。管理员账号的创建方式如下kubectl -n kubernetes-dashboard create serviceaccount admin-user kubectl -n kubernetes-dashboard create clusterrolebinding admin-user --clusterrolecluster-admin --serviceaccountkubernetes-dashboard:admin-user然后获取登录Tokenkubectl -n kubernetes-dashboard create token admin-user这里注意新版本Kubernetes里kubectl -n kubernetes-dashboard get secret那套老办法在很多环境已经不适用了因为create token是更受推荐的方式。拿到那一长串Token在Dashboard登录页选Token方式粘贴进去就能进。访问地址方面由于Dashboard的Service默认是ClusterIP类型我通常用三种方式之一访问临时用kubectl -n kubernetes-dashboard port-forward svc/kubernetes-dashboard 8443:443或者在自己维护的Ingress网关上加一个域名或者改Service为NodePort。个人建议是开发环境用port-forward最干净不往集群上多开端口。2.3 CPU和内存图表为空多半是集群里没有metrics-server第一次打开Dashboard的人经常发现节点和Pod的CPU、内存图表全是空的页面右上角还会显示Metrics未提供相关的提示。这个坑的原因很明确集群里没有部署metrics-serverDashboard拿不到监控数据。解决办法是装metrics-serverkubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml装好之后用kubectl top nodes验证如果能看到每个节点的CPU和内存使用量Dashboard图表也就会跟着有数据了。这一步不仅Dashboard需要第三章讲HPA也完全依赖它建议提前装好。3. HPA的完整链路指标怎么来、副本数怎么算、为什么不能乱配参数HPAHorizontalPodAutoscaler是K8s里最容易被照着文档配置但没吃透的功能之一。很多人配完压测时发现半天不扩容或者扩容了过一会儿又疯狂缩容就是因为没理解它背后那条完整链路。3.1 HPA不是玄学它只是不断执行一个公式的控制器HPA从本质上看是一个循环控制器每15秒做一次下面的流程通过Metrics API拿到当前Pod的指标数据根据当前副本数、当前指标值、目标指标值算出期望副本数如果期望副本数和当前副本数不同就更新Deployment的replicas字段剩下的活由ReplicaSet去干。期望副本数的核心公式是desiredReplicas ceil(currentReplicas * (currentMetric / desiredMetric))举个例子当前有2个Pod每个Pod CPU使用率为80%目标阈值是50%那么期望副本数就是ceil(2 * (80 / 50)) ceil(3.2) 4。这个公式看起来很简单但实际生产环境里指标数据的时延、默认采样窗口、缩容稳定窗口等参数都会影响最终结果这也是我们说配置参数不能乱来的原因。3.2 从零到一配置HPA注意requests是前提条件要让HPA的CPU指标生效有一个常被忽略的前提Deployment里的容器必须设置requests.cpu。没有requestsHPA拿不到百分比数据TARGETS列会显示unknown。我见过不少人只设了limits忘了requests结果HPA配了等于没配。先部署一个带requests的测试服务apiVersion: apps/v1 kind: Deployment metadata: name: php-apache spec: selector: matchLabels: app: php-apache template: metadata: labels: app: php-apache spec: containers: - name: php-apache image: registry.cn-hangzhou.aliyuncs.com/xxx/php-apache:latest ports: - containerPort: 80 resources: requests: cpu: 200m memory: 128Mi然后创建HPAkubectl autoscale deployment php-apache --cpu-percent60 --min1 --max5查看状态kubectl get hpa上面版本默认走的是autoscaling/v2显示信息更全包括目标值、当前值、最小/最大副本数。3.3 压测验证扩容和缩容注意冷却窗口创建完HPA后我习惯用kubectl get hpa -w盯实时变化同时起一个压测工具打流量。这里以ab为例ab -n 500000 -c 300 http://service-ip/压测起来后HPA的TARGETS列会逐渐显示CPU百分比超过阈值比如260%/60%然后REPLICAS从1变成2、3、4、5。这个过程不是瞬时的一个是因为HPA控制器默认同步周期是15秒另外每次变更副本数后要等指标重新采集、计算K8s还加了一个扩容稳定窗口避免抖动。说到缩容大家更要心里有数HPA默认的缩容稳定窗口是5分钟。意思是说就算你的CPU使用率已经降到0它也会先观察5分钟确认持续低负载才逐步减少副本数而且每次缩容也是逐步收。这样做是因为如果流量是周期性的快速缩下去又快速扩上来对系统来说比多留着几个Pod成本更高。所以当你看到CPU已经很低但Pod数量迟迟没降下来别慌那是正常设计。3.4 自定义指标与ClusterAutoscaler的边界CPU和内存指标是HPA最常用的但有些场景下CPU并不能反映真实压力比如网关类服务主要看QPS、连接数这时候就需要自定义指标了。做法一般是在集群里部署Prometheus Adapter让它把Prometheus里的业务指标暴露成custom.metrics.k8s.io接口HPA就能通过type: Object或type: Pods引用这些指标。这里必须提一个容易搞混的边界HPA只管应用层的Pod副本数量不管集群节点数量。如果你的业务高峰期需要40个Pod但集群总共只有3台节点每台节点只能跑8个Pod那么HPA最多把Pod扩到上限24个就扩不动了。想增加节点这是ClusterAutoscaler的职责。两者一个管Pod一个管Node互为补充但没有替换关系。4. Helm的Chart骨架与模板渲染机制拆解Helm对没接触过包管理概念的K8s使用者来说刚开始会觉得这不就是多了一层封装吗。对它确实是一层封装但这层封装解决的问题恰好是原生kubectl最薄弱的环节。4.1 Chart就是应用说明书加可执行模板一个Helm Chart从结果来看就是一个安装包里面包含了描述应用该用什么镜像、几个副本、什么端口、什么环境变量的说明书也包含把这些说明变成K8s真实资源YAML的模板。你拿到一个Chart不管内部多复杂执行helm install就自动把整套资源创建好。不同环境之间的差异通过values.yaml动态注入不需要你手动去改模板代码。我类比一下kubectl apply像是拿着一堆散装零件清单去仓库自己拼一台机器Helm则是一个完整的产品安装包里面带安装脚本、配置文件和回滚工具。生产环境中没有人会愿意手工维护两三百个YAML而没有版本管理能力。4.2 Chart目录结构十个文件里我们真正需要的几个helm create生成的Chart结构里有一些默认文件刚上手容易看懵。拆解来看my-chart/ ├── Chart.yaml ├── values.yaml ├── charts/ ├── templates/ │ ├── deployment.yaml │ ├── service.yaml │ ├── hpa.yaml │ ├── _helpers.tpl │ └── NOTES.txt └── .helmignoreChart.yaml是Chart的身份证声明名字、版本、依赖等values.yaml是所有可变参数的默认值比如镜像地址、副本数、资源配额templates/是资源模板真正所在的地方_helpers.tpl是模板辅助函数用于生成统一的标签、名字之类的公共部分NOTES.txt是安装完成后打印给用户看的提示信息比如怎么访问服务。初学阶段最需要操作的就是Chart.yaml、values.yaml和templates下的三个文件。charts/目录是放置子Chart依赖用的不是直接用。4.3 templates目录里的语法values、with、default、quote很多人学Helm卡在模板语法上。我的建议是别背理解几个核心动作就够了。最常用的是{{ .Values.xxx }}这表示把values.yaml里的xxx字段的值插入到这个位置。例如apiVersion: apps/v1 kind: Deployment metadata: name: {{ .Values.name }} spec: replicas: {{ .Values.replicaCount }} template: spec: containers: - name: {{ .Chart.Name }} image: {{ .Values.image.repository }}:{{ .Values.image.tag }}再看几个常用函数。default用于提供默认值比如{{ .Values.image.tag | default latest }}意思是如果values.yaml里没定义tag就用latest。quote用于强制把值渲染成字符串比如{{ .Values.nodeSelector.zone | quote }}。with用来缩小变量作用域让后面一段内部不用反复写前缀。管道符|类似于命令行里的管道把前面的值传给后面的函数处理。调试模板最常用的命令是这套组合helm template ./my-chart helm template ./my-chart --debug helm install my-release ./my-chart --dry-run --debughelm template直接渲染出最终YAML但不执行部署加--debug能看到渲染过程里的详细信息--dry-run则会在API Server上做一次模拟能检查出很多纯本地渲染发现不了的校验问题。我编写任何Chart都会先跑一遍这三条确认无误之后再真的install。4.4 Release、Revision、RollbackHelm比kubectl多出来的能力Helm把一次安装称作一次Release同一个Chart可以装成很多个Release比如web-app-prod、web-app-test。每次helm upgrade都会生成一个新的修订版本也就是Revision。这些历史版本可以查看也可以一键回滚。这是原生kubectl做不到的因为它根本没有发布单元的概念。实际操作里我常用这套命令helm install web-app ./my-chart --namespace default helm upgrade web-app ./my-chart --set image.tagv2 helm history web-app helm rollback web-app 1 helm uninstall web-apphelm history能看到每次发布的时间、版本号、状态和说明helm rollback web-app 1直接把整个Release回退到第一次安装时的状态比手动改YAML再apply快得多也安全得多。5. 实战把一个Web应用和HPA一起打包成Chart并完成发布回滚前面对原理做了拆解这一章我用一个完整的过程演示怎么从零做一个Chart把Deployment、Service、HPA一起参数化然后走一遍安装、升级、回滚。这里选的应用只是一个简单的Web服务你替换成自己的业务镜像即可。5.1 用helm create生成骨架先删掉默认模板helm create demo-app这条命令会创建demo-app目录里面自带了nginx相关的默认模板。我一般会清掉templates/里用不到的文件只保留_helpers.tpl、hpa.yaml、deployment.yaml、service.yaml再改Chart.yamlapiVersion: v2 name: demo-app description: A demo web application with HPA enabled type: application version: 0.1.0 appVersion: 1.0.0version是Chart的版本appVersion是业务应用的版本两者可以不同。5.2 values.yaml把镜像、副本数、资源、伸缩参数全部抽出来我把所有可能要按环境变化的参数都放进values.yaml这样不同环境部署时只需要维护一份配置值而不需要改模板代码replicaCount: 1 image: repository: nginx tag: 1.25 pullPolicy: IfNotPresent name: demo-app service: type: ClusterIP port: 80 targetPort: 80 resources: requests: cpu: 200m memory: 128Mi autoscaling: enabled: true minReplicas: 1 maxReplicas: 5 targetCPUUtilizationPercentage: 60这里把HPA的开关、上下限、CPU阈值都参数化了。某些环境如果你不想启用自动伸缩把autoscaling.enabled设为falseHPA模板就不会被渲染出来。5.3 编写deployment.yaml、service.yaml和hpa.yaml模板templates/deployment.yaml的关键部分这样写apiVersion: apps/v1 kind: Deployment metadata: name: {{ .Values.name }} labels: app: {{ .Values.name }} spec: replicas: {{ .Values.replicaCount }} selector: matchLabels: app: {{ .Values.name }} template: metadata: labels: app: {{ .Values.name }} spec: containers: - name: {{ .Chart.Name }} image: {{ .Values.image.repository }}:{{ .Values.image.tag }} imagePullPolicy: {{ .Values.image.pullPolicy }} ports: - containerPort: {{ .Values.service.targetPort }} resources: requests: cpu: {{ .Values.resources.requests.cpu }} memory: {{ .Values.resources.requests.memory }}Service模板apiVersion: v1 kind: Service metadata: name: {{ .Values.name }} spec: type: {{ .Values.service.type }} selector: app: {{ .Values.name }} ports: - port: {{ .Values.service.port }} targetPort: {{ .Values.service.targetPort }}HPA模板{{- if .Values.autoscaling.enabled }} apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: {{ .Values.name }} spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: {{ .Values.name }} minReplicas: {{ .Values.autoscaling.minReplicas }} maxReplicas: {{ .Values.autoscaling.maxReplicas }} metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} {{- end }}这里值得强调的是{{- if ... }}和{{- end }}的语法横杠的作用是去掉左右两侧的空白行避免渲染出无意义的空行。HPA的开关逻辑就靠这个条件块。5.4 本地渲染检查再正式安装写完后先检查再安装helm lint ./demo-app helm template ./demo-app helm install demo ./demo-app --namespace demo --create-namespace --dry-run --debughelm lint会检查Chart里的格式和常见问题helm template直接看渲染的YAML有没有错误第三行是在API Server上做模拟安装。确认没问题后去掉--dry-run真正执行helm install demo ./demo-app --namespace demo --create-namespace查看结果helm list -n demo kubectl -n demo get pods,svc,hpa正常情况下Pods运行Service创建好HPA自动绑定到Deployment上。5.5 发布v2镜像发现故障后快速回滚现在模拟一次升级。假设业务新版本镜像tag是1.26我用--set直接覆盖参数不动文件helm upgrade demo ./demo-app --namespace demo --set image.tag1.26升级完成后我故意查看Pod状态发现新版本镜像启动失败Pod处于CrashLoopBackOff状态。这时候回滚helm history demo -n demo helm rollback demo 1 -n demohelm rollback demo 1会把Release回退到第一次安装的修订版本。执行后等十几秒再看kubectl -n demo get podsPod恢复Running服务恢复正常。整个发布、发现故障、回滚的过程不超过两分钟。这在手动维护YAML的工作流里是难以想象的。6. 三个工具混合使用的经验提醒这套组合我实际用下来有个体会Dashboard、HPA、Helm不是孤立的它们之间有很多隐性的配合关系。比如Dashboard上的数据来自metrics-serverHPA的CPU指标也来自metrics-server所以如果你在Dashboard上看到的Pod CPU为0而HPA却显示CPU超标多半不是两边的数据矛盾而是采样窗口不同导致的先看集群里有没有多套指标服务再排查具体应用。再比如用Helm管理Dashboard本身也很常见。你可以把Dashboard作为一个Chart装到集群里这样升级、回滚都和业务应用用同一套Helm流程。我个人在管理多个集群时会把所有基础组件包括metrics-server、Dashboard、Ingress Controller都做成Helm Chart放进统一的Git仓库用版本号追踪每次变更。这样配合HPA的自动伸缩策略再加上Dashboard的可视化排障K8s集群的日常运维压力会小很多。最后分享一个我踩过的坑HPA阈值设得太低应用高峰时副本数直接冲上最大值每个Pod又设置了很高的requests.cpu结果节点总CPU容量打满Pod调度不上去页面反而更慢。后来我把阈值调整到60%到70%同时给HPA的maxReplicas设置了一个合理的业务上界观察了几轮高峰期系统才稳定下来。所以不要照搬文档参数阈值和副本数上下限都要结合自己业务的真实流量曲线去调调完记得用压测或者自然流量验证一个完整周期。