
企业项目里的k8s用了一段时间后你会发现每天写那一堆YAML简直是噩梦。同一个应用在开发、测试、生产环境不同配置变来变去一个服务从Deployment到Service、Ingress、ConfigMap、Secret加起来十几份清单要发一版改一遍。今天这个系列的第16篇咱们就聊Helm这套打包、分发、版本管理的机制以及怎么在企业里搭一个自己的Chart仓库。这套东西解决了我团队里最头疼的两个问题一是“应用部署定义”没法版本化二是新环境上线要靠人工回忆改参数。如果你也在维护多套环境、多个业务应用这篇文章应该能直接省下你不少熬夜时间。1. 为什么企业k8s项目需要Helm1.1 手动管理YAML的痛点说个我自己的经历。早年间做某个后台管理系统的上云改造十几个微服务每个服务一个目录里面放着deployment.yaml、service.yaml、configmap.yaml。每次要发布一个新版本我需要在服务器上执行一串kubectl apply -f然后自己盯着Pod状态。这还算好的更崩溃的是换一个环境比如从测试环境切到生产环境我得手工把镜像tag改掉、把副本数调高、把某些配置项换掉稍有不慎漏改一个字段线上就出幺蛾子。另一个痛点是用文件复制的方式来“备份”应用定义根本没法回溯。昨天改完的YAML到底和上周有什么差异靠肉眼diff整目录文件那是反人类的。一旦出问题想回滚只能寄希望于自己还记得之前是什么样。1.2 Helm的三层模型Chart、Release、RepositoryHelm的核心思想特别朴素把一组相关的k8s资源定义打包成一个应用包叫Chart这个包安装到集群里就产生一个Release一次发布的实例而包本身可以放到一个共享仓库Repository里供团队下载。这三个概念配合起来解决了我的痛点Chart就是一个目录/压缩包里面含有Chart.yaml元数据、values.yaml默认参数、templates/模板文件、charts/依赖的子Chart。Release一条记录表示“这个Chart在某一次安装/升级后生成的实际K8s资源集合”。每次升级都会生成新的版本记录可以随时回滚。Repository存放Chart包的HTTP服务器可以理解成YUM仓库、npm私服的翻版供helm search、install时拉取。1.3 Helm带来的企业级价值对团队协作来说Helm真正的价值不只是少写几个YAML而是把“应用部署定义”当成软件制品来管理。部署什么版本、什么参数、什么依赖全部被Chart文件化、版本化、可审计。后续不管谁来发布只要拿着Chart包和values文件就能在另一个集群完整复现一套应用。对于管理层来说“上线可回滚、变更可追溯”这种要求用Helm比用散装YAML容易落地得多。注意这里说的版本管理主要靠Chart包的版本和Release的版本记录而不是简单靠Git记录。两者要配合起来用。2. Helm环境准备与基础使用2.1 安装Helm客户端Helm 3是当前主流它去掉了Tiller客户端直接和kube-apiserver交互权限取决于你的kubeconfig安全性好了很多。安装方法很简单也可以从官方GitHub下载二进制然后放到/usr/local/bin下。我用的是v3.14.0wget https://get.helm.sh/helm-v3.14.0-linux-amd64.tar.gz tar -zxvf helm-v3.14.0-linux-amd64.tar.gz mv linux-amd64/helm /usr/local/bin/helm helm version装完以后确保你的kubeconfig能连到集群。测一下kubectl cluster-info helm env建议把HELM_NAMESPACE设置成你常用的命名空间避免每次都要写-n。不过在企业里不同业务最好还是显式指定namespace防止串环境。2.2 常用命令速览这些命令是我每天用到的建议先混个脸熟helm repo add 别名 地址添加Chart仓库。helm repo update更新本地仓库缓存。helm search repo 关键词搜索仓库里的Chart。helm install Release名 Chart -f values文件安装一个Chart成为一个Release。helm upgrade Release名 Chart -f values文件升级Release。helm history Release名查看Release的所有版本。helm rollback Release名 版本号回滚到某个历史版本。helm uninstall Release名卸载Release。helm template Chart本地渲染模板而不实际安装调试用。如果用的时候记不住helm help本身就是最好的速查手册。我强烈建议在实际操作前先拿一个简单Chart跑通install、upgrade、rollback这一条链路后面才能在企业里玩得转。2.3 第一个Chart的创建与安装演示用helm create可以快速生成一个标准Chart结构helm create my-demo tree my-demo生成的结构里有几个关键文件my-demo/ ├── Chart.yaml ├── charts/ ├── templates/ │ ├── NOTES.txt │ ├── _helpers.tpl │ ├── deployment.yaml │ ├── hpa.yaml │ ├── ingress.yaml │ ├── service.yaml │ └── serviceaccount.yaml └── values.yamlChart.yaml里最重要的是name和version这两个字段决定了这个Chart在仓库里的标识。比如apiVersion: v2 name: my-demo description: A Helm chart for Kubernetes type: application version: 0.1.0 appVersion: 1.16.0version是Chart版本appVersion是应用版本两者不要混。我习惯把模板文件精简一下只保留Deployment和Service再加个ConfigMap。values.yaml里设置镜像地址、副本数、端口这些变量。修改好以后直接试安装helm install my-demo-release ./my-demo --namespace dev如果模板有报错它会直接在终端提示你。安装完成后可以看一下Release状态helm list -n dev helm status my-demo-release -n dev实践中最常用的调试方式是helm template它会把模板渲染成最终的YAML输出到终端而且不会真的创建资源helm template my-demo ./my-demo -f values-dev.yaml这个命令在CI里特别有用先渲染出来检查一遍再apply能避免很多低级失误。3. Chart仓库搭建从零构建企业私有仓库3.1 仓库选型对比企业里搭建Chart仓库主流方案大概是这三种Harbor功能最全支持镜像仓库和Chart仓库带权限控制、审计、复制适合已经用了Harbor作为镜像仓库的团队。ChartMuseum专门的Chart仓库服务轻量、部署简单支持很多后端存储本地磁盘、OSS、S3等适合独立部署。Nginx 静态文件最简方案直接把Chart包放到目录里用helm repo index生成索引文件Nginx托管配合basic auth可做简单权限。适合国内网络环境或者内网小规模团队不想多维护组件。我建议如果团队已经有Harbor直接用它少一套系统。如果只是想快速搭一个让开发们能拉取Chart用Nginx方案最快。下面把这两种都写一下。3.2 基于Harbor搭建Chart仓库如果你的Harbor版本在2.x以上默认支持OCI的Chart仓库但传统Helm 3也兼容。操作上需要先开启Harbor的“Helm Chart”功能。我这边步骤大概这样首先在Harbor的web界面上创建一个项目例如叫k8s-charts。然后在这个项目底下把“Helm Chart”这个tab激活不同版本入口略有差异最新版在项目配置里打开“Helm Chart”开关。之后你就能得到一个仓库地址形如https://harbor.example.com/chartrepo/k8s-charts。在客户端侧添加仓库并认证helm repo add myrepo https://harbor.example.com/chartrepo/k8s-charts \ --username admin --password xxxx helm repo update然后打包并推送Chart。Helm 3有现成的helm push命令但需要先安装helm-push插件helm plugin install https://github.com/chartmuseum/helm-push cd my-demo helm package . helm push my-demo-0.1.0.tgz myrepo如果推送没问题你可以在Harbor页面上看到这个Chart包点开还能看到版本、依赖等信息。注意Harbor使用过程中如果走的是自签名HTTPS客户端需要在Trust store里放证书否则helm repo add会报x509证书错误。3.3 基于Nginx搭建轻量级Chart仓库如果你没那么多组件依赖用一个Nginx来托Chart仓库也很稳。原理很简单把Chart包和index.yaml放在同一个目录Nginx对外提供该目录的静态文件服务即可。具体步骤在服务器上建目录比如/opt/helm-repo/charts。把你打包好的*.tgz文件放进去。用helm repo index生成索引文件helm repo index /opt/helm-repo/charts --url https://charts.example.com/这会在目录下生成一个index.yaml里面记录了所有Chart包的名称、版本、URL和校验值。配置Nginxserver { listen 80; server_name charts.example.com; root /opt/helm-repo/charts; autoindex on; location / { try_files $uri $uri/ 404; } }如果要加访问控制可以用Basic Auth生成类似Caddy或者Nginx的账户配置文件。在企业里建议至少套一层HTTPS和认证否则任何人都能下载内网Chart包敏感信息容易泄露。在其他机器上添加这个仓库helm repo add corp http://charts.example.com --username dev --password xxxx helm repo update helm search repo corp这样就很方便了。我团队早期就是这么干的内网几十个ChartNginx一台小机器够用了。3.4 将Chart上传至仓库与下载验证仓库搭好后需要验证整个流转是不是通的。流程一般是本地打包 → push/复制到仓库 → 远程添加仓库 → install。以Nginx方案为例更常见的做法是在CI里用脚本把Chart包上传到目录然后再执行一次helm repo index更新索引。注意helm repo index不是自动增量的它会重新扫描目录生成新索引。如果目录里已有旧的tgz它会一并加进去。所以更新索引这个动作要小心别把旧包丟了。验证一下下载helm repo update helm search repo myrepo/my-demo --versions helm pull myrepo/my-demo --version 0.1.0能正常pull下来说明仓库OK。然后可以用helm install远程安装这个Chart确保index.yaml里记录的URL是客户端可达的否则会报“chart not found”或“failed to download”。4. Helm企业级实战部署、升级与回滚4.1 多环境Value管理企业里一个应用至少要在dev、test、prod三个环境各跑一套。如果每个环境都复制一份values文件会有一堆重复和容易漏改的参数。我的建议是一个Chart多个values文件用层次化组合。比如目录结构charts/my-app/ ├── Chart.yaml ├── values.yaml # 默认值 ├── values-dev.yaml # dev覆盖 ├── values-test.yaml # test覆盖 └── values-prod.yaml # prod覆盖安装的时候helm install my-app-dev ./charts/my-app -n dev -f values-dev.yaml helm install my-app-test ./charts/my-app -n test -f values-test.yaml helm install my-app-prod ./charts/my-app -n prod -f values-prod.yamlvalues.yaml里只放“所有环境都一样的公共配置”比如镜像仓库前缀、对外端口。而每个环境只写“跟别的环境不同”的那几项比如副本数、数据库地址、日志级别。这样以后加一个环境只需要新增一个values文件改动范围很小。还有一个细节我认为--set参数适合临时调试不适合长期维护。因为--set写在命令行里不留痕下次复制命令根本不知道上次设了哪些值。所以正式环境一定要用values文件并且把values文件纳入Git管理。4.2 企业级Chart设计规范在多人协作的场景里Chart的结构和命名最好约定好否则没过多久就会乱。我总结过几条经验Chart目录按业务域划分比如apps/order-center/、apps/user-center/。Chart中的模板要尽量参数化凡是会变化的字段都放进values.yaml不要硬编码在模板里。每个Chart都必须有README.md说明默认参数、端口、依赖、部署步骤。版本号遵守语义化Chart version: 0.1.0appVersion: 1.2.3升级应用时只改appVersion和version的patch位改动模板导致功能变化时升级version。依赖的外部资源数据库、消息队列不要直接内嵌在业务Chart里最好单独部署基础设施Chart然后在业务Chart里通过values传入地址。这里还要聊一下_helpers.tpl。这个文件用来生成通用模板片段比如资源名称、标签。我建议所有Chart都让模板调用include来构造名字而不是直接硬写。这样统一改名时只要动一个地方就不会出现Deployment名叫AService名叫B这类低级错误。4.3 升级与回滚操作实战升级一个Release到新版本最简单的命令是helm upgrade my-app-prod ./charts/my-app -n prod -f values-prod.yaml升级时Helm会对比values.yaml和模板渲染结果然后对K8s资源做patch或create。你可以用--dry-run先模拟helm upgrade my-app-prod ./charts/my-app -n prod \ -f values-prod.yaml --dry-run --debug确定没问题后去掉--dry-run执行。假如你改了values里的镜像tag那么Deployment期望的Pod模板变了k8s会自动滚动更新。但你改了ConfigMap内容时Deployment本身没变Pod不会自动重启这会让很多人迷惑。解决办法很经典在模板里给Deployment加一个基于ConfigMap内容哈希的annotation比如annotations: checksum/config: {{ include (print $.Template.BasePath /configmap.yaml) . | sha256sum }}这样只要ConfigMap内容变了Deployment的annotation就变k8s就会滚动。回滚操作也很直接helm history my-app-prod -n prod helm rollback my-app-prod 3 -n prod这里的3是REVISION序号不是Chart版本。回滚后Helm会生成一个新的REVISION第4个并尽可能恢复到第3版时的资源配置。要注意如果你在回滚后立即发现了问题还可以rollback回第4个版本所以别怕回滚它很安全。4.4 配合CI/CD的自动化发布流程企业里最爽的用法是把Helm集成进CI/CD流水线。我在某公司的流水线里大概是这样工作的代码提交触发CI编译并构建Docker镜像推送到镜像仓库。CI生成一个带Git commit hash的镜像tag。更新对应环境的values文件里的image.tag提交到Git。CD阶段调用helm upgrade把Chart和更新后的values一起安装到集群。伪代码差不多这样# CD 阶段 helm repo add corp http://charts.example.com --username $HELM_USER --password $HELM_PASS helm repo update helm upgrade --install my-app-prod corp/my-app \ --namespace prod \ -f values-prod.yaml \ --set image.tag${CI_COMMIT_SHA}这里用了--install参数意思是如果Release不存在就安装存在就升级非常省事。如果用了GitOps那么你也可以把values文件和Chart仓库都当成Git里的变量通过流水线自动执行。但不管哪条路基本套路都一样把镜像版本作为主要变更输入通过values文件或者--set传给Helm。我们还做了发布权限管控CI使用的ServiceAccount只拥有部署相关命名空间的写权限不能操作其他业务。这是Helm 3比Helm 2安全的地方不需要一个全局的Tiller所有权限都交给kube-apiserver来管。5. 常见问题与坑位实录5.1 仓库认证与Chart拉取失败常见报错是failed to fetch http://.../index.yaml : 401 Unauthorized。这种情况分两种你repo add时用户名密码不对重新添加即可。认证信息被缓存了错误内容特别是CI里环境变量没传对。可以用helm repo remove再重新add或者检查~/.config/helm/repositories.yaml里的配置。另一种情况是index.yaml里记录的Chart包的URL指向了一个不存在的地址。如果你用Nginx方案最常见的是你生成index时用的--url参数写错了比如写成了http://localhost:8080别人自然拉不到。解决办法是重跑helm repo index用正确的对外访问地址重新生成。5.2 升级后Pod不更新的诡异现象除了上面提到的ConfigMap变更不触发滚动外还有一个容易踩的坑你在模板里用了semverCompare或者复杂条件导致某些字段没有变化。用--dry-run能帮你看到最终渲染结果对比一下就能发现问题。我习惯用helm diff插件helm-diff来查看预期变更能在执行升级前直观列出哪些资源会被修改非常稳。如果安装的是别人写的Chart里面模板用了random函数生成Secret会导致每次升级都生成一个新Secret而引用Secret的Pod却因为引用值不变其实变了而不更新。这类问题只能靠排查模板逻辑或者指定helm.sh/resource-policy: keep等策略。5.3 Helm版本与K8s版本兼容性Helm 3本身要求K8s的API版本不能太老。比如老的k8s 1.14之前用Helm 3默认的apps/v1Deployment是没问题的但如果你用了很老的集群一些内置API可能不支持。我在某团队的旧集群上就遇到过Ingress资源版本不兼容的问题需要修改Chart模板里的apiVersion。另外helm 2到helm 3迁移是一道大坑主要是模板变量和label的变化。如果你的团队还在用Helm 2我还是建议尽早迁移Helm 2已经非常老了而且安全性堪忧。5.4 Hook与资源清理的坑Helm支持pre-install、pre-upgrade、post-install等Hook用来执行Job或者初始化动作。如果你定义了Hook要记得Hook完成的Pod在Release卸载时会被清理。但有时候我们想保留一些数据比如数据库初始化结果那就不该用Hook而是用一个独立Chart来管理初始化任务否则升级的时候Hook重跑可能把数据搞乱。还有一个坑如果Hook Job失败了Helm会默认一直重试直到失败次数达到backoffLimit然后才会标记Release失败。这个地方可能导致一次发布卡死很久。我建议在Hook Job模板里显式设置restartPolicy: Never和backoffLimit: 3避免死循环。结尾Helm这东西初期学起来简单但真正在企业里用得顺是需要一些沉淀的。我在实际使用过程中最深的体会是Helm的价值不在于“少写几个YAML”而在于它逼着你把应用部署这事儿做成一个标准化的交付物。把Chart仓库建好、把values分层规范起来之后新项目上线的时间从原来的半天压缩到了十几分钟而且再也不用担心有人“不小心改错了环境配置”。如果你还没有接触过Helm我建议先从一个小Chart跑通全流程再逐步推广到核心应用。这个系列后面我还会继续聊更细的实践比如在Helm里管理数据库迁移、多集群发布等咱们下次接着聊。