1. 项目概述与背景
如果你和我一样,在Kubernetes生态里摸爬滚打了几年,那你一定对Helm这个“包管理器”又爱又恨。爱的是它用声明式的Chart把复杂的应用部署变得像helm install一样简单;恨的是版本升级带来的“阵痛”,尤其是从Helm 2到Helm 3的跨越。这不仅仅是客户端版本号的跳动,更是一次底层架构的“心脏移植手术”——Helm 3彻底移除了服务端的Tiller组件,将发布状态直接存储在了Kubernetes集群内(默认是Secrets),这意味着所有在Helm 2时代管理的成百上千个Release,其元数据和历史记录都面临一次“大迁徙”。
helm-2to3插件就是在这个背景下诞生的官方迁移工具。它的核心任务非常明确:安全、准确地将Helm 2的配置、插件、仓库以及最重要的——所有已部署的Release——原封不动地“搬运”到Helm 3的管理体系下。想象一下,你有一个运行了两年、承载了数十个微服务的生产集群,每个服务都有多个版本和回滚历史。手动迁移?那无异于一场灾难。2to3插件就是那个帮你自动化完成这场数据迁移的“摆渡人”。
虽然项目现已归档并不再维护(因为Helm 2早在2020年11月就已停止支持),但理解它的工作原理和最佳实践,对于任何仍在处理遗留系统迁移,或希望深入理解Helm内部数据结构的运维和开发人员来说,依然是一笔宝贵的财富。这篇文章,我将结合自己多次执行大规模迁移的经验,为你彻底拆解helm-2to3,不仅告诉你每个命令怎么用,更会深入解释它背后的逻辑、可能遇到的坑以及如何设计一个万无一失的迁移方案。
2. Helm 2与Helm 3的核心架构差异解析
在动手迁移之前,我们必须先搞清楚我们在迁移什么,以及为什么要迁移。这不仅仅是版本号的改变,而是整个设计哲学的演进。
2.1 Helm 2的“客户端-服务器”架构
Helm 2采用经典的C/S架构:
- 客户端 (
helm): 用户直接交互的命令行工具,负责解析Chart、生成Kubernetes清单。 - 服务器端 (Tiller): 部署在Kubernetes集群
kube-system命名空间(默认)中的一个Pod。它接收来自客户端的指令,并负责:- 与Kubernetes API Server交互,执行实际的资源创建、更新、删除操作。
- 存储Release的元数据和历史版本。默认情况下,这些数据以ConfigMap的形式存储在Tiller所在的命名空间中。每个Release的每次操作(安装、升级、回滚)都会生成一个新的ConfigMap,命名格式类似
<release_name>.<version_number>。
这种架构带来了几个显著问题:
- 安全模型复杂:Tiller在集群内拥有很高的权限(通常是
cluster-admin),才能部署应用到任何命名空间。这导致授权变得困难,需要精细的RBAC配置。 - 状态存储耦合:Release状态与一个特定的Pod(Tiller)强关联。Tiller的故障或数据丢失会直接影响Helm对已部署应用的管理能力。
- 多租户隔离差:所有用户共享同一个Tiller实例,通过Tiller的权限来隔离,这在实践中很难管理。
2.2 Helm 3的“无服务器”架构
Helm 3做出了一个大胆而优雅的简化:彻底移除了Tiller。
- 客户端 (
helm): 现在,helm命令行工具直接与Kubernetes API Server通信,利用用户自身的kubeconfig凭证和RBAC权限来执行操作。这完美契合了Kubernetes的安全模型。 - 状态存储:Release的元数据和历史版本现在直接作为Kubernetes资源对象存储在与Release相同的命名空间里。默认存储后端是Secrets(资源类型为
helm.sh/release.v1),这使得Release状态成为了集群原生的一部分,具有了高可用和易备份的特性。
这种转变带来了根本性的优势:
- 简化的安全模型:权限控制完全依赖Kubernetes RBAC,与
kubectl无异。 - 更强的隔离性:Release状态按命名空间存储,天然支持多租户。
- 更少的运维负担:无需再部署、维护、升级和故障排查Tiller服务。
2.3 迁移的本质:数据格式转换与存储位置转移
理解了架构差异,迁移的本质就清晰了。helm-2to3插件需要完成两件核心工作:
- 数据格式转换:将Helm 2 Tiller存储的Release数据(特定格式的ConfigMap/Secret)解析出来,并重新打包成Helm 3能够识别的、存储在Secrets中的新格式。
- 存储位置转移:将转换后的Release数据,从Tiller所在的命名空间(如
kube-system),写入到该Release实际部署的应用命名空间中。
这个过程必须保证信息的无损,包括:Release名称、命名空间、Chart信息、Chart值(values)、版本号、状态(deployed, failed等)、描述、注释以及完整的版本历史。2to3插件正是为精确完成这个复杂过程而设计的。
3. 迁移前的关键准备与风险评估
迁移绝不是简单地运行几条命令。在按下回车键之前,周密的准备是成功的一半,也是避免生产事故的底线。
3.1 环境与版本检查清单
首先,确保你的环境处于一个已知的、稳定的状态:
- 备份,备份,还是备份!这是铁律。
- Helm v2 Home目录:通常位于
~/.helm或由$HELM_HOME指定。完整备份这个目录。 - 集群内的Release数据:执行
kubectl get configmaps -n <tiller-namespace> -l OWNER=TILLER -o yaml > helm2-releases-backup.yaml(如果使用Secret存储,则将configmaps替换为secrets)。这备份了所有Release的原始数据。 - 集群资源快照:对关键业务应用所在的命名空间,使用
kubectl get all -n <namespace> -o yaml进行备份。
- Helm v2 Home目录:通常位于
- 升级到最终版本:
- 将Helm 2客户端升级到2.17.0(最后一个2.x版本)。这个版本与Helm 3的兼容性和稳定性最好。
- 安装一个较新且稳定的Helm 3版本,例如v3.12.0或更高。避免使用早期有已知问题的v3.0.x版本。
- 检查Kubernetes API兼容性:Helm 2中使用的某些Kubernetes资源API版本可能在新的集群中已被废弃或移除。使用
helm mapkubeapis插件(一个社区插件)可以自动检测并修复这些问题。在迁移前运行它,能避免迁移后应用无法部署的尴尬。 - 验证集群访问权限:确保用于执行迁移的kubeconfig上下文(Context)对Tiller命名空间(如
kube-system)和目标应用命名空间拥有足够的读写权限。插件需要能读取ConfigMaps/Secrets并创建新的Secrets。
3.2 设计迁移策略:分批、灰度与回滚方案
对于拥有大量Release的生产环境,全量一次性迁移风险极高。我推荐采用分批次、灰度的策略:
- 按业务重要性分级:将Release分为三批:
- P0(低风险/测试环境):首先迁移开发、测试环境的Release。这些环境可以承受故障,用于验证迁移流程和脚本。
- P1(非核心生产服务):其次迁移那些对业务连续性影响较小的生产服务,例如内部的监控Agent、日志收集器。
- P2(核心生产服务):最后,在积累了足够信心后,在业务低峰期迁移核心数据库、前端应用等。
- 制定清晰的回滚流程:
- 回滚到Helm 2:如果迁移后发现问题,立即停止向Helm 3操作。由于
2to3默认不会删除Helm 2的数据(除非使用--delete-v2-releases),你可以直接使用Helm 2客户端进行回滚或升级操作。这是最重要的安全网。 - 在Helm 3中回滚:迁移成功后,在Helm 3中测试
helm rollback命令,确保版本历史可用。
- 回滚到Helm 2:如果迁移后发现问题,立即停止向Helm 3操作。由于
- 沟通与时间窗口:通知相关团队(开发、测试、SRE)迁移计划,明确时间窗口和预期影响(通常是无感知的)。
3.3 安装与验证2to3插件
安装过程很简单,但要注意细节:
# 使用Helm 3的插件管理器安装 helm plugin install https://github.com/helm/helm-2to3.git安装后,验证插件是否可用:
helm 2to3 --help你应该能看到move config,convert,cleanup等子命令的帮助信息。
重要提示:插件会从GitHub Releases下载二进制文件。如果你的机器无法直接访问GitHub,需要预先下载对应版本的tar包,然后通过
helm plugin install /path/to/local/helm-2to3.tar.gz方式安装。
4. 分步迁移实战详解
准备工作就绪,现在我们开始实战。请严格按照以下顺序操作。
4.1 第一步:迁移Helm v2配置(move config)
这个步骤迁移的是客户端本地的配置,不涉及集群。
helm 2to3 move config这条命令会做以下几件事:
- 复制仓库配置:将Helm 2的
~/.helm/repository/repositories.yaml复制到Helm 3的~/.config/helm/repositories.yaml。这样你在Helm 2中添加的稳定仓库(stable)、比特纳米(bitnami)等远程仓库地址就会出现在Helm 3中。 - 复制插件:将Helm 2的
~/.helm/plugins目录内容复制到Helm 3的~/.local/share/helm/plugins。注意:不是所有Helm 2插件都能在Helm 3上工作,因为插件API可能发生了变化。 - 复制Starters:如果你用过
helm create --starter,相关的starter模板也会被复制。
执行后的关键检查与操作:
- 运行
helm repo list:确认仓库列表已迁移。你会发现所有本地仓库(如local)虽然出现在列表里,但指向的仍然是Helm 2的本地路径(如http://127.0.0.1:8879),而这个服务通常由helm serve命令启动,在Helm 3中可能未运行。# 移除无效的本地仓库引用 helm repo remove local # 如果需要,用Helm 3的方式重新添加本地仓库(如果需要的话,但Helm 3更推荐使用OCI仓库或直接指定chart路径) # helm repo add my-local http://localhost:8879 (前提是你要启动一个本地chart服务器) - 更新仓库索引:执行
helm repo update。这个命令会连接所有远程仓库,下载最新的index.yaml文件到Helm 3的缓存目录,替换掉从Helm 2带来的可能过时的缓存。 - 测试插件:逐个测试迁移过来的插件是否工作。例如,如果你有
helm-diff插件,运行helm diff upgrade ...看看是否报错。如果插件失效,需要手动卸载并重新安装其Helm 3兼容版本。helm plugin uninstall <plugin-name> # 查找该插件支持Helm 3的安装方式重新安装 helm plugin install <new-plugin-url>
4.2 第二步:迁移Helm v2 Release(convert)
这是迁移的核心和最具风险的部分。强烈建议对每一个Release先使用--dry-run进行模拟。
4.2.1 单Release迁移
基本命令格式如下:
helm 2to3 convert <RELEASE_NAME> --tiller-ns kube-system --release-storage configmaps<RELEASE_NAME>: Helm 2中部署的Release名称。--tiller-ns: Tiller所在的命名空间,默认为kube-system。如果你的环境不同,必须指定。--release-storage: Helm 2存储Release数据的资源类型。这是最容易出错的地方!Helm 2默认使用ConfigMaps,但可以通过--override 'spec.tiller.storage=secret'或在helm init时指定--tiller-storage=secret来改为Secrets。你必须知道你的环境用的是哪一种。可以通过以下命令检查:
哪个命令有输出,就说明用的是哪种存储。# 检查kube-system命名空间下是否有OWNER=TILLER标签的ConfigMaps kubectl get configmaps -n kube-system -l OWNER=TILLER # 检查kube-system命名空间下是否有OWNER=TILLER标签的Secrets kubectl get secrets -n kube-system -l OWNER=TILLER
一个完整的、带有安全检查的实操流程:
# 1. 首先,列出所有待迁移的Release(通过Helm 2) helm list --all --tiller-namespace kube-system # 2. 选择一个非关键的Release进行试迁移,使用--dry-run预览 helm 2to3 convert my-test-app --tiller-ns kube-system --release-storage configmaps --dry-run # 仔细阅读输出,确认它计划读取哪个ConfigMap,并转换到哪个命名空间的哪个Secret。 # 3. 实际执行迁移 helm 2to3 convert my-test-app --tiller-ns kube-system --release-storage configmaps # 4. 迁移后,立即用Helm 3验证 helm list -n <namespace-of-my-test-app> # 确认Release出现 helm history my-test-app -n <namespace-of-my-test-app> # 确认版本历史完整 helm get values my-test-app -n <namespace-of-my-test-app> # 确认配置值正确4.2.2 批量迁移与高级参数
对于成百上千的Release,手动一个个转换是不现实的。我们可以结合kubectl和xargs进行批量操作,但务必谨慎。
示例:批量迁移所有使用ConfigMap存储的Release
# 获取所有唯一的Release名称,然后逐个转换 kubectl get configmap -n kube-system -l "OWNER=TILLER" \ | awk 'NR>1 {print $1}' | cut -d '.' -f1 | sort -u \ | xargs -I {} -n1 -P1 helm 2to3 convert {} --tiller-ns kube-system --release-storage configmaps命令拆解与风险控制:
kubectl get ...: 获取所有Tiller的ConfigMap。awk 'NR>1 {print $1}': 跳过表头,打印第一列(ConfigMap名称)。cut -d '.' -f1: 按.分割,取第一部分,即Release名称(因为CM名格式是<release_name>.<version>)。sort -u: 排序并去重,得到唯一的Release名称列表。xargs -I {} -n1 -P1 helm 2to3 convert {} ...:-I {}: 用{}代替传入的参数。-n1: 每次只传递一个Release名给helm 2to3命令。-P1:至关重要!设置进程数为1,即串行执行。迁移是I/O和网络密集型操作,并行执行可能导致集群API Server过载或意外错误。务必串行。- 在命令末尾可以加上
--dry-run先进行整体预览,确认无误后再移除。
关键参数解析:
--release-versions-max: 默认只迁移每个Release最新的10个版本。如果你的Release有超过10个历史版本,且你需要更早的历史记录,可以增大这个值。设为0表示迁移所有版本。迁移大量历史版本会显著增加时间和资源消耗。--delete-v2-releases:危险参数!如果加上,迁移成功后会自动删除Helm 2对应的ConfigMap/Secret。在最终确认所有迁移完全成功并稳定运行前,绝对不要使用这个参数!保留Helm 2的数据是你的终极回滚保障。--ignore-already-migrated: 如果一个Release已经部分迁移过(比如之前运行过convert),再次运行时会报错。此参数可忽略已迁移的版本,继续迁移新版本。
4.3 第三步:清理Helm v2数据(cleanup)
警告:这是不可逆的操作!只有在所有集群、所有Tiller实例下的Release都已成功迁移到Helm 3,并且经过充分验证(至少一个完整的发布周期)后,才能执行清理。
清理分为三个部分,可以单独执行:
--config-cleanup: 删除本地的Helm 2配置目录(~/.helm)。--release-cleanup: 删除集群中Tiller存储的Release数据(ConfigMaps/Secrets)。--tiller-cleanup: 删除Tiller的Deployment、Service Account等集群资源。
最安全的清理方式是分步、带确认地进行:
# 1. 首先,清理集群中的Tiller部署(这不会影响已迁移的Release,因为Helm 3不依赖它) helm 2to3 cleanup --tiller-cleanup --tiller-ns kube-system # 命令会交互式询问你是否确认,仔细阅读提示。 # 2. 然后,清理集群中的Helm 2 Release数据。执行前,请再次用Helm 3核对所有Release。 helm list --all-namespaces # Helm 3视图 helm list --all --tiller-namespace kube-system # Helm 2视图,应该为空或只剩你确定要废弃的Release # 确认无误后清理 helm 2to3 cleanup --release-cleanup --tiller-ns kube-system --release-storage configmaps # 3. 最后,清理本地配置。这步风险最低,可以在任何时候做。 helm 2to3 cleanup --config-cleanup关于--name参数:它用于清理单个指定Release的v2数据。例如,你迁移了app-A和app-B,但只想清理app-A在Helm 2中的数据,可以:
helm 2to3 cleanup --name app-A --tiller-ns kube-system --release-storage configmaps这个操作是独立的,不能与其他--*-cleanup标志同时使用。
5. 疑难杂症与深度排错指南
即使准备再充分,在生产环境中也难免遇到问题。下面是我在多次迁移中总结的常见“坑”及其解决方案。
5.1 错误:“release not found” 或 “configmaps “xxx” not found”
问题现象:运行convert时,提示找不到Release或对应的ConfigMap/Secret。排查思路:
- 确认Tiller命名空间:你是否使用了正确的
--tiller-ns?有些环境可能将Tiller部署在tiller-system或其他自定义命名空间。用kubectl get pods -A | grep tiller查找。 - 确认存储类型:再次用
kubectl get configmaps/secrets -n <tiller-namespace> -l OWNER=TILLER确认存储类型。确保--release-storage参数与之匹配。 - 检查RBAC权限:执行迁移的用户是否对Tiller命名空间有
get和listConfigMaps/Secrets的权限?以及对目标命名空间有createSecrets的权限?可以临时绑定一个cluster-admin角色测试,或参考前文提供的ClusterRole片段。 - Release名称是否包含特殊字符或版本号?
helm list显示的名称才是真正的Release名。确保在convert命令中使用的是纯名称,不包含版本后缀。
5.2 错误:“failed to convert release: existing release conflict”
问题现象:迁移时提示在Helm 3中已存在同名的Release。原因分析:这通常是因为目标命名空间中已经存在一个由Helm 3直接创建的、同名的Secret(存储了Helm 3的Release)。或者,你已经对这个Release执行过部分迁移。解决方案:
- 检查冲突:
kubectl get secrets -n <target-namespace> -l “owner=helm”查看是否已存在。 - 决策:
- 如果这个Helm 3的Release是旧的、可丢弃的,可以先
helm uninstall它,然后再执行迁移。 - 如果这个Helm 3的Release就是由之前失败的迁移产生的半成品,并且你需要保留Helm 2的数据,可以手动删除这个冲突的Secret:
kubectl delete secret -n <namespace> sh.helm.release.v1.<release_name>.v<version>。操作前务必确认该Secret的内容! - 使用
--ignore-already-migrated标志跳过已迁移的版本(如果错误信息是关于版本冲突)。
- 如果这个Helm 3的Release是旧的、可丢弃的,可以先
5.3 迁移后Helm 3命令失败(如helm upgrade,helm repo update)
问题现象:配置迁移后,执行helm repo update或helm upgrade时出现连接错误或缓存错误。根因分析:这几乎总是因为本地仓库缓存冲突。move config复制了Helm 2的repositories.yaml,其中可能包含指向Helm 2本地缓存文件(如~/.helm/cache下)的索引引用,而这些路径对Helm 3无效。标准解决流程:
# 1. 查看当前仓库列表,找出所有‘local’或路径看起来异常的仓库 helm repo list # 2. 移除所有本地仓库或无效仓库 helm repo remove local # 移除其他可能出问题的仓库 # 3. 重新添加必要的远程仓库(使用Helm 3的格式) helm repo add bitnami https://charts.bitnami.com/bitnami helm repo add stable https://charts.helm.sh/stable # 注意:稳定仓库已归档,可能需要添加其他替代源 # 4. 强制更新仓库索引,重建缓存 helm repo update完成以上步骤后,Chart依赖更新和升级命令应该可以正常工作。
5.4 Tillerless Helm 2环境的迁移
有些环境出于安全考虑,使用“Tillerless”模式运行Helm 2(即通过helm tiller run或类似插件在本地运行Tiller进程,而非部署在集群中)。这种情况下,Release数据可能存储在本地文件或通过不同方式存在于集群。
关键点:对于Tillerless环境,2to3插件可能无法直接定位到Release数据。你需要:
- 明确你的Tillerless方案是如何存储Release状态的(是本地文件,还是通过特定服务账户写入集群的Secrets?)。
- 如果数据在集群中,尝试使用
--tiller-out-cluster标志,并结合--release-storage指定存储类型。你可能需要为插件配置一个能访问这些数据的Kubernetes服务账户。 - 如果数据在本地文件,迁移可能更复杂,需要手动解析文件并尝试通过Helm 3的API重新创建Release。这种情况建议查阅特定Tillerless插件的文档或寻求社区帮助。
6. 迁移后的验证与最佳实践
迁移完成并清理旧数据后,工作并未结束。你需要建立对新体系的信心。
6.1 系统性验证清单
- 清单完整性检查:
- 在Helm 3中运行
helm list --all-namespaces,对比迁移前的Helm 2列表,确保数量、名称、命名空间一致。 - 随机抽样多个关键应用,使用
helm get manifest <release> -n <namespace>,与Helm 2时代helm get manifest <release> --tiller-namespace kube-system的输出进行对比(如果你有备份),确保生成的Kubernetes资源模板一致。
- 在Helm 3中运行
- 版本历史与回滚测试:
- 对抽样应用执行
helm history <release> -n <namespace>,确认所有历史版本(包括FAILED状态的)都已迁移。 - 执行一次真实的回滚测试:选择一个有多个历史版本的非核心应用,记录当前版本号,执行
helm rollback <release> <previous_version> -n <namespace>,观察应用是否按预期回滚。成功后,再将其升级回原版本。
- 对抽样应用执行
- 应用功能冒烟测试:
- 访问应用的端点(Endpoint),执行核心业务流程,确保服务功能完全正常。
- 检查相关的监控图表、日志流,确认没有因迁移引入的异常错误或性能抖动。
- Helm 3新功能验证:
- 测试
helm diff upgrade(需安装插件)查看升级差异。 - 测试
helm uninstall --keep-history和helm rollback到已卸载的版本。 - 验证依赖库(如Bitnami)的Chart是否能在Helm 3下正常安装升级。
- 测试
6.2 长期维护最佳实践
- 统一客户端版本:在所有CI/CD流水线、开发者机器上,强制使用Helm 3,并淘汰Helm 2二进制文件。
- 更新文档与脚本:将内部Wiki、部署手册、Shell脚本、Dockerfile中所有
helm init、--tiller-namespace等Helm 2特有的命令或参数更新为Helm 3的等效形式。 - 利用Helm 3新特性:
- 依赖管理:Helm 3使用
Chart.lock文件锁定依赖版本,使部署更一致。 - Library Charts:创建可重用的通用组件Chart。
- OCI注册中心支持:考虑将Chart推送至OCI兼容的注册中心(如Harbor、ACR、ECR),实现更标准化的分发。
- 依赖管理:Helm 3使用
- 制定新的备份策略:Helm 3的Release状态存储在Secrets中。确保你的集群备份方案(如Velero)覆盖了所有业务命名空间,以便在灾难恢复时能连同Release历史一起还原。
迁移到Helm 3不是一个简单的工具升级,而是一次基础设施管理的现代化。虽然helm-2to3插件已经完成了它的历史使命,但通过这次迁移所获得的关于Helm内部状态管理的深刻理解,会让你在未来的云原生旅程中更加从容。记住,谨慎的计划、充分的备份和循序渐进的执行,是应对任何复杂系统变更的不二法门。