ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

Helm实践指南:从YAML部署管理到企业私有Chart仓库搭建

Helm实践指南:从YAML部署管理到企业私有Chart仓库搭建 前两周刚帮某研发团队处理完一套部署流程改造他们线上服务每次发版都要手动维护二十多个YAML文件从deployment.yaml到configmap.yaml一长串改个镜像版本号要精确到第几个文件稍不注意就会遗漏。这种场景在K8s工程师的日常里太常见了而Helm的出现就是专门来解决这套配置地狱问题的。这篇文章是我在推进企业项目K8s落地过程中关于Helm部署使用以及chart仓库搭建的完整实践总结从基础概念讲到企业私有仓库适合已经能熟练操作K8s、但还没系统用过Helm的工程师或者正在为多服务多环境配置管理头疼的同学。1. 为什么企业里的K8s工程师最终都会走到Helm1.1 手动维护YAML的崩溃瞬间先说说我看到的普遍现状。很多人第一次接触K8s时都是从kubectl apply -f xx.yaml开始的单服务单环境勉强能对付。但一旦服务数量到了两位数问题就开始扎堆出现服务A和B共用一套部署逻辑只是镜像tag不同于是你复制一份YAML改改名字继续用线上环境加了一个环境变量测试环境也要同步加你开始对照着十几个文件逐个检查某个服务回滚你翻遍聊天记录才找到三天前部署时用的镜像版本配置混乱到没人敢动新同事接手第一周全在考古。这些症状的本质是K8s把运维复杂性从虚拟机转移到了YAML文件里而你仍然在用文本复制粘贴的原始方式管理它们。企业里服务一多这根本不是人力能扛住的。Helm解决的就是这个问题——把一系列K8s资源文件打包成一个可配置、可版本化、可复用的整体这个整体就是chart。1.2 Helm三板斧打包、模板渲染、版本管理Helm的核心理念并不复杂把它拆解成三个能力就够用了第一板斧是打包。你将一套服务需要的Deployment、Service、ConfigMap、Ingress等资源文件放进一个固定结构的目录这个目录就是一个chart。它与Docker镜像的逻辑很像——Docker打包应用运行环境Helm打包K8s部署资源。第二板斧是模板渲染。chart里的YAML文件不必写死可以使用{{ .Values.replicaCount }}这类模板语法再配合values.yaml提供的默认值。实际部署时不管是测试环境还是生产环境同一个chart通过传入不同参数就能渲染出不同配置这解决了多环境差异管理的核心问题。第三板斧是版本管理。每次helm install或helm upgrade都会生成一个release记录你随时可以查看部署历史一条命令回滚到任意历史版本。这直接解决了线上回滚靠回忆的尴尬。另外有必要提一下Helm 3相对旧版本架构上的变化。Helm 2时代有个叫Tiller的服务端组件它拥有集群权限负责执行安装操作这在多租户集群里有安全隐患。Helm 3直接干掉了Tiller客户端直接调用K8s API权限由你的kubeconfig决定逻辑上更清晰也更安全。现在新项目建议直接上Helm 3不用再碰Tiller那套东西了。2. 环境准备Helm 3的安装与首次上手体验2.1 安装方式和版本确认Helm的安装很轻量本质上就是一个下载到本地的二进制文件。Linux和macOS环境下最省心的方式是通过官方安装脚本curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 chmod 700 get_helm.sh ./get_helm.sh如果你对安全要求比较高不想执行远程脚本也可以直接到Helm官方Release页面下载对应平台的压缩包解压后把二进制放到/usr/local/bin下。我自己习惯用这种方式步骤虽然多一步但可控性更强灵魂拷问起来能明确回答我安装的到底是哪个版本的包。安装完成后验证一下helm version输出类似这样就可以了version.BuildInfo{Version:v3.15.0, GitCommit:..., ...}企业内部一般还会做一次helm repo add把官方或者自己私有的chart仓库配好。例如添加一个内部chart仓库的流程helm repo add internal-repo https://chartrepo.internal.example.com helm repo update这里有一个小提醒如果部分同学所在网络环境访问公共仓库不稳定企业内部一般都会搭自己的代理仓库或离线同步方案。实际工作里团队内会很早就约定好这些基础设施的获取路径而不是每个人各显神通。2.2 核心概念实操化理解在敲第一条部署命令之前有三个词必须彻底吃透后面所有操作都建立在它们之上Chart一个打包好的应用模板包含了一组描述K8s资源的文件。Releasechart被部署到集群中的一个运行实例。同一个chart可以被部署多次每次部署就是一个独立的release。Repository存放和分发chart的仓库。看一个最直观的例子假设我已经添加了某个公开chart仓库想快速部署一个nginxhelm install happy-nginx repo/nginx这条命令做了三件事读取repo/nginx这个chart根据默认配置渲染出K8s资源然后以happy-nginx这个名字部署到集群。这时候用kubectl get pod已经能看到对应的Pod了。用helm list可以看到当前的所有release及其状态。这是排查问题时第一个应执行的动作——确认release存在与否、处于什么状态、最近一次操作是什么。values的传递也很直观helm install happy-nginx repo/nginx --set replicaCount3这条命令只覆盖了replicaCount这一个参数其他配置全部走chart里的默认值。文件级别的参数管理放到后面讲多环境时再说记住--set与--values的差别即可一个适合快速调试一个适合保存整套环境配置。3. 从零手写一个企业级Chart以某内部订单服务为例光会用现成chart还不够企业内部业务是五花八门的你需要能把自己服务的部署逻辑沉淀成chart。下面拿一个后端服务来完整走一遍这个服务有标准的HTTP接口、需要连接Redis还对外暴露Ingress。3.1 Chart标准结构与每个文件的职责创建一个chart最规范的方式是用官方脚手架helm create order-service生成的目录结构是这样的order-service/ ├── Chart.yaml ├── charts/ ├── templates/ │ ├── deployment.yaml │ ├── service.yaml │ ├── ingress.yaml │ ├── _helpers.tpl │ ├── hpa.yaml │ └── ... └── values.yaml几个关键文件的职责先捋清楚Chart.yaml是chart的元数据包含名称、版本、联系方式等是chart的身份证values.yaml是默认配置所有可能在部署时变化的参数都在这里给出默认值templates/目录存放真正的K8s资源模板支持Go模板语法charts/目录用于存放依赖的子chart这属于依赖管理范畴。我的习惯是删掉脚手架生成的乱七八糟的测试资源按实际需要精简文件结构。这个东西不是生成的越多越好而是每个文件都有明确的存在理由。3.2 Chart.yaml和values.yaml的设计一个精简、可直接用的Chart.yaml长这样apiVersion: v2 name: order-service description: 内部订单查询服务 type: application version: 0.1.0 appVersion: 1.2.0注意apiVersion: v2这是Helm 3要求的格式v1是Helm 2的旧格式。values.yaml要按可配置项来组织我把常用结构整理如下# 镜像相关配置 image: repository: harbor.internal.example.com/backend/order-service tag: 1.2.0 pullPolicy: IfNotPresent # 副本数与资源配额 replicaCount: 2 resources: requests: cpu: 200m memory: 256Mi limits: cpu: 1000m memory: 1Gi # 环境变量配置 env: REDIS_ADDR: redis-service:6379 LOG_LEVEL: info # 服务端口与Ingress域名 service: port: 8080 ingress: host: order.example.internal设计values.yaml时有一个原则凡是因环境而异或可能随时调整的值全部抽出来。镜像tag、副本数、环境变量、域名这些基本必抽。而一些极少变化的Pod高级调度配置就没必要全部外放否则values文件本身会变成新的维护负担。3.3 模板语法与动态渲染详解模板文件是Helm的灵魂。看一个完整且常见的deployment.yaml模板apiVersion: apps/v1 kind: Deployment metadata: name: {{ include order-service.fullname . }} labels: app.kubernetes.io/name: {{ include order-service.name . }} app.kubernetes.io/instance: {{ .Release.Name }} spec: replicas: {{ .Values.replicaCount }} selector: matchLabels: app.kubernetes.io/name: {{ include order-service.name . }} template: metadata: labels: app.kubernetes.io/name: {{ include order-service.name . }} spec: containers: - name: {{ .Chart.Name }} image: {{ .Values.image.repository }}:{{ .Values.image.tag }} imagePullPolicy: {{ .Values.image.pullPolicy }} ports: - containerPort: {{ .Values.service.port }} env: - name: REDIS_ADDR value: {{ .Values.env.REDIS_ADDR }} resources: {{- toYaml .Values.resources | nindent 12 }}这里有几个语法点需要重点理解**.Values**是模板中访问values.yaml参数的方式类似于当前值上下文。image.tag的引用写成{{ .Values.image.tag }}。注意如果参数没定义Helm会渲染出一个空字符串而不是直接报错。include函数用于复用_helpers.tpl中定义的有名字符串片段一般用来生成统一的资源名称。推荐用include而不是template因为include的输出可以被管道继续处理用法更灵活。**toYaml与nindent**的组合很有趣。resources在values里是一个嵌套字典直接写在模板里很难对齐缩进这时用{{- toYaml .Values.resources | nindent 12 }}可以把整个字典序列化成YAML并缩进12个空格完美嵌入模板。{{-和-}}的作用是去掉前后多余的空格和换行避免渲染出奇怪的空白行。宁可多用helm template检查也不要凭肉眼猜渲染结果。这是唯一靠得住的校验方式。3.4 lint、template与实际部署调试写完模板文件后先做静态检查helm lint order-service/lint会检查chart目录结构、YAML语法等基础问题。它会提示从Chart.yaml到模板语法的一切可疑点先把这个错误全清掉再进入下一步。接着最值得养成的习惯是用helm template做一次本地渲染直接看最终生成的K8s资源长什么样helm template order-service/这样渲染出来的YAML不会真正部署到集群适合在写好模板后核对生成的Deployment是否符合预期。我第一次写模板时清一色toYaml对齐错误导致的缩进问题全靠这条命令一一抓出来的。确认无误后正式部署helm install order-prod order-service/如果想临时覆盖某个参数可以使用--set。比如紧急调小副本数helm upgrade order-prod order-service/ --set replicaCount13.5 依赖管理与公共库复用随着chart数量变多你会发现很多服务都在用几乎相同的Ingress模板、数据库连接配置逻辑代码复制的情况越来越严重。这时就需要借助依赖管理把公共部分抽出来做成一个公共chart其他chart通过依赖引用来复用。在Chart.yaml中添加依赖的方式非常直接apiVersion: v2 name: order-service dependencies: - name: common version: 0.4.7 repository: https://chartrepo.internal.example.com然后执行helm dependency update order-service/它会根据Chart.yaml声明去仓库拉取对应版本的chart放到charts/目录下。模板中的公共逻辑会用类似{{ include common.labels . }}这种方式调用。依赖管理引入的复杂度也不小比如传递依赖的版本冲突。我建议公共部分先从简单的通用标签、健康检查、镜像拉取逻辑开始抽不要一上来就把所有业务逻辑都塞进去。公共chart的变更要严格走版本发布流程避免某个服务升级依赖时被意外行为波及。4. 企业私有Chart仓库搭建两种落地方案的对比实践4.1 为什么要自建仓库以及Chart仓库的机制也许有人会问chart直接放到Git仓库每次部署用helm install ./本地目录不行吗只在个人开发场景下当然没问题但企业环境里至少有三个痛点解决不了多团队共享困难。A团队做好的通用chartB团队想用总不能把Git仓库链接丢过去让对方自己拉版本追溯没有标准。chart的版本管理需要与K8s资源的变更一一对应需要统一的版本存储权限与审计能力缺失。谁能发布chart、谁在什么时候用了哪个chart都需要留痕。这里需要理解一个核心机制Helm chart仓库本质上不是Docker Registry那样的二进制镜像仓库而是一个带索引的静态文件服务器。仓库里除了.tgz格式的chart包本身还需要一个index.yaml文件这个索引记录了所有chart名称、版本、校验值和下载地址。执行helm repo update时客户端拉取的就是这个index.yaml文件之后根据索引里的URL去下载具体的chart包。明白了这个机制自己搭仓库的思路就打开了只要能托管静态文件、能更新索引就可以做chart仓库。4.2 轻量方案对象存储 ChartMuseum如果公司已经有对象存储如某内部S3兼容存储可以采用极简方案。我把整个搭建过程记录一下方便复现ChartMuseum本身是一个开源的chart仓库服务端程序部署方式可以是二进制、Docker镜像或Helm chart。假设我们把它部署在一台内网机器上并配置使用S3兼容存储保存chart包docker run -d \ --name chartmuseum \ -p 8080:8080 \ -e STORAGEamazon \ -e STORAGE_AMAZON_BUCKEThelm-charts \ -e STORAGE_AMAZON_PREFIXchartmuseum \ -e STORAGE_AMAZON_REGIONus-east-1 \ -e STORAGE_AMAZON_ENDPOINThttp://minio.internal.example.com:9000 \ -e STORAGE_AMAZON_S3_FORCE_PATH_STYLEtrue \ chartmuseum/chartmuseum:latest这里细讲一下几个配置的含义。STORAGEamazon表示使用S3兼容协议STORAGE_AMAZON_ENDPOINT指向内网对象存储地址STORAGE_AMAZON_S3_FORCE_PATH_STYLEtrue很关键某些兼容实现如MinIO需要路径风格访问不设置会报签名错误。部署完成后需要启用chartmuseum的上传API。默认API是开启的之后推送chart包可以借助helm cm-push插件helm plugin install https://github.com/chartmuseum/helm-push helm repo add order-repo http://10.24.5.10:8080 helm cm-push order-service/ order-repocm-push命令会把本地chart打包并上传到ChartMuseumChartMuseum会自动更新index.yaml。这样其他同事执行helm repo update后就能搜索到这个新上传的chart并部署了。这个方案的优点是轻、快、成本低部署过程半小时内能完成适合没有现成制品管理平台的小团队。4.3 一体化方案基于Harbor的Chart仓库如果企业内部已部署Harbor作为容器镜像仓库完全不需要额外部署ChartMuseum。Harbor从1.6版本开始原生支持chart仓库功能和镜像仓库在同一个平台统一管理天然支持权限控制、项目隔离和审计。启用Harbor的chart仓库后推送和拉取流程如下。先配置Helm仓库地址。Harbor每个项目下都有独立的chart仓库端点helm repo add order-repo https://harbor.internal.example.com/chartrepo/order-project关于权限要注意Harbor在签发Robot账号时需要勾选对应项目”的Helm Chart读写权限仅仅给镜像权限是不够的。这一步是最容易被忽略的我在落地时没少见到同事卡在这里报401权限错误。上传chart则同样使用helm cm-push插件helm cm-push order-service/ order-repo也可以使用Harbor API直接上传tgz包但我实测下来cm-push更顺手一条命令完成打包上传。使用Harbor方案的好处是明显的chart版本与镜像版本能在同一个平台里关联管理基于项目做访问控制天然适合企业多团队多项目的安全合规诉求。代价是要部署和维护Harbor本身基础服务越多运维负担也越大。4.4 仓库权限与安全设计无论哪种方案仓库的权限控制都必须认真设计。chart文件本质上是一段可执行的部署逻辑任何人都能修改chart内容并部署到集群这是相当危险的一件事。我见过一个真实的例子有同事为了图方便把生产集群的kubeconfig放在共享目录结果任何拿到这份kubeconfig的人都能对生产环境执行任何操作。所以在企业落地时至少要落实三条安全基线chart仓库账号权限最小化发布chart用专门的机器人账号不用个人账号下载权限按项目隔离普通研发人员对生产项目只有拉取权限不能推送chart内容变更要经历审核流程最好是打包上传由CI系统执行不给人肉操作入口。5. 企业项目落地经验版本管理、CI/CD集成与实测踩坑5.1 版本号管理与回滚的实操节奏chart的版本管理是落地的第一道基本功。Chart.yaml中的version字段是chart本身的版本而appVersion则是打包的应用程序版本。两者独立变化。举个例子应用代码从1.2.0升级到1.3.0但同时调整了chart里环境变量的写法那么appVersion和version都应该升级。如果只是改了chart的调度策略应用版本没变那只需递增version。我采用的策略很简单凡是改动chart内容必须递增version。哪怕只是改了一个默认日志级别。否则后果是release历史里两条记录显示相同版本号实际内容却不同回滚时你根本不知道自己在回滚到什么状态。每当更新图表后及时部署并记录helm upgrade order-prod order-service/ --namespace backend查看部署历史helm history order-prod回滚到上一个版本helm rollback order-prod 3配合helm history里展示的REVISION号回滚操作会变得非常可控、准确。5.2 CI/CD集成时最容易出问题的环节真正到了企业级应用chart的发布流程一定会被集成到CI/CD流水线里而不是靠人SSH到服务器手工执行。一个标准的发布流水线可以设计为四个阶段代码构建与镜像推送应用代码编译产物打包成Docker镜像推送到Harbor或内网镜像仓库更新chart包把新的appVersion同步更新到Chart.yaml和values.yaml发布chart到仓库执行helm cm-push把更新后的chart上传触发部署任务在目标集群执行helm upgrade --install。实际执行中容易出问题的环节往往是第2步和第4步的衔接标签与镜像版本不一致。某个服务升级镜像tag已推送但values.yaml里引用的还是旧tag或者CI脚本里用动态生成的tag覆盖了模板里的硬编码值最终部署了错误版本。给一个更稳健的参数传递方式不要在values.yaml里写死镜像tag而是用CI变量动态注入。在CI脚本中显式传入tag参数helm upgrade order-prod order-service/ \ --namespace backend \ --set image.tag${IMAGE_TAG} \ --set replicaCount${REPLICA_COUNT}这样一次发版全链路更新避免多文件维护不同步的问题。5.3 多环境配置隔离的推荐做法企业在测试、预发、生产等多环境之间切换时硬编码环境变量是最危险的。比如测试环境的Redis地址渗透到生产部署上等数据出问题再排查事情的起因就要花很长一段时间。推荐的做法是把环境差异全部收拢到values文件按环境分文件管理。创建三种环境的values文件order-service/ ├── values.yaml # 默认配置 ├── values-test.yaml # 测试环境配置 ├── values-staging.yaml # 预发环境配置 └── values-prod.yaml # 生产环境配置部署测试环境使用helm install order-test order-service/ --values values-test.yaml --namespace test部署生产环境使用helm install order-prod order-service/ --values values-prod.yaml --namespace prod每个values文件定义自己的副本数、资源配置和域名。对于敏感的数据库密码或认证Secret上述方式还有一个补充原则不要写进values.yaml明文中而是通过外部注入的Secret引用。在模板中尽量只支持Secret名称和键名的配置具体内容留到集群的Secret管理器中。5.4 真实踩坑记录与排查链路最后分享一下落地Helm过程中印象比较深刻的两个问题及其排查思路。问题一helm install卡在pending-install状态某次部署一个服务release状态一直是pending-installK8s资源没起来。常规排查流程如下先用helm list -A确认release的存在和状态再通过kubectl describe查看关联的资源对象。发现Pod本身拉取镜像失败——明明私有仓库有该镜像却报ImagePullBackOff。进一步排查发现是chart里的imagePullSecrets没有配置。这个配置在values.yaml里是空数组集群拉取私有镜像时没有凭据。解决方案是先在命名空间创建好docker-registry类型的Secret再在template里引用它。排查这类问题的关键思路是先分清问题是Helm层还是K8s层。Helm层看渲染结果和release状态K8s层看Pod状态和事件。用.命令快速钉死问题所在而不是瞎猜。问题二滚动升级永远不结束升级某服务后新Pod一直处于CrashLoopBackOff状态Deployment的滚动升级因此卡住。这个问题的触发点是liveness探针配置不合理新版本由于环境变量缺少某些值健康检查端口没有正常监听探针永远失败。排查时用kubectl get events看事件流很快发现探针失败的具体时间戳和原因。之后回到values文件补上环境变量参数再执行helm upgrade滚动升级才顺利完成。这个案例提醒了我一件事模板写得好不代表业务配置就正确。Helm只负责把变量渲染成K8s资源至于这些资源运行起来是否符合预期仍然需要强大的K8s排障能力兜底。最后一个经验分享不一定写进文档但很实用。我们内部现在要求所有chart的发布记录都附带一个部署前检查清单渲染结果人工核对、版本号确认、命名空间确认、配置差异对比。这套习惯跑顺之后线上部署出错的概率至少降低了一半。Helm本身只是一个工具真正让部署流程稳定可控的是围绕它建立起来的发布规范和团队协作方式希望这些内容能帮你在自己的企业项目里少踩几个坑。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表