1. 迁移前的盘点:先把家底摸清楚
1.1 为什么我决定从 Rancher 换到 Sealos
先亮结论:Rancher 本身不是不好,而是对“私有化交付”这个场景来说,它太重了。我手上有几十个集群要维护,Rancher 的多集群管理面板确实方便,但代价也实实在在:升级依赖组件多、UI 频繁改版、默认装一堆你用不上的功能,再加上它自己的资源占用,一台 4C8G 的机器光跑 Rancher 就快喘不过气了。而 Sealos 吸引我的点很直接:它把 Kubernetes 集群的安装、维护、组件管理都做成了一条命令的事,天然适合私有化环境,没有多余的“管理中心”,应用要什么组件就装什么组件。
这个决策不是拍脑袋。我在测试环境对比过两者的资源开销:一个全新搭建的 Rancher 集群,系统组件常驻内存大概 2~3GB;Sealos 部署的高可用集群,基础组件加起来不到 1GB。对私有化交付来说,客户不会给你多好的机器,省下来的资源就是实打实的可部署空间。另一个考量是运维复杂度,Rancher 的架构里多了一个“管理面”,你得额外维护证书、认证、agent 通信;Sealos 的管理逻辑嵌在集群本身,没有单独的 control plane 依赖,排障链路短很多。
1.2 盘点清单:别漏掉这些“隐形资产”
迁移前最忌讳的就是对着kubectl get all就以为看全了。get all这玩意儿会漏掉 namespace 级别的一堆资源,而且它显示的字段并不完整。我做了这么多年迁移,踩过的最大一个坑就是漏资产:应用迁过去了,结果日志采集的服务发现工作负载用的是 EndpointSlice 而不是 Service,指标监控依赖的是 ServiceMonitor CRD,这些一旦漏了,新集群里应用表面正常,但监控和日志全断。
我建议把盘点分成五类来做:
- 工作负载类:Deployment、StatefulSet、DaemonSet、Job、CronJob,这些是业务的主体,数量最多,迁移优先级也最高。
- 配置类:ConfigMap、Secret,注意 Secret 不光要盘点名字,还要确认哪些被工作负载挂载、以什么方式挂载,因为迁移后 key 改没改会直接影响启动。
- 网络类:Service、Ingress、NetworkPolicy、EndpointSlice,Rancher 默认的 ingress-controller 跟 Sealos 默认的可能不是同一个,这里最容易出兼容问题。
- 存储类:PersistentVolumeClaim、StorageClass、PV 本身,PV 是没法直接“跨集群搬”的,这就是后面有状态服务难迁的根源。
- 自定义资源类:ServiceMonitor、PrometheusRule、cert-manager 的 Certificate、HPA 扩展等等,这些 CRD 在新集群有没有装,决定了迁移后功能是否完整。
我的习惯是把盘点结果输出成一张表,每行一个资源,标注“名称、命名空间、类型、所属应用、是否有持久化数据、是否可以停机迁移”。这张表后面就是迁移计划的执行清单,每迁完一个打一个勾,心里有数。
1.3 导出资源的通用姿势与注意事项
盘点确认之后就开始导出。大多数人的第一反应是直接kubectl get deploy -n xxx -o yaml,这个姿势没问题,但不建议直接拿来就用。Rancher 管理的集群里,几乎所有资源都会被塞进它的系统标签和注解,像cattle.io/creator、cluster.cattle.io/...这一类,这些字段在 Sealos 的新集群里没有任何意义,直接 apply 过去轻则报警告,重则因为 finalizer 卡在 Terminating。
我常用的清洗方式是导出后用 yq 或 sed 把 metadata 里的uid、resourceVersion、creationTimestamp、generation、managedFields、ownerReferences、status以及所有 cattle 相关字段全部删掉。这里给一个能跑的最小脚本片段:
# 导出并清洗单个 Deployment kubectl get deploy nginx -n web -o yaml \ | yq 'del(.metadata.uid, .metadata.resourceVersion, .metadata.creationTimestamp, .metadata.generation, .metadata.managedFields, .metadata.annotations["field.cattle.io/*"], .status)' \ > nginx-clean.yaml如果你是手工迁移少量应用,这个脚本够用。如果规模大,我更推荐直接把资源先落成目录结构,比如manifests/<namespace>/<kind>/<name>.yaml,然后统一清洗。重点是:selector 和 pod template 里的 labels 必须保留原样,这是 Deployment 跟 Pod 关联的纽带,一旦改了,Rollout 不会更新任何 Pod。
2. 私有化底座搭建:Sealos 环境的存储、镜像与网络准备
2.1 部署规划与离线安装要点
搭建 Sealos 私有化环境之前,先想清楚一个原则:迁移目标集群不是让你“先跑起来就行”,而是要承担生产流量的,所以高可用和可维护性必须一开始就建好。Sealos 支持一条命令拉起高可用 Kubernetes 集群,但如果节点规划不合理,后面扩容、维护都会很被动。
我的推荐规划是:至少 3 个 master 节点组成 etcd 高可用,2~4 个 node 节点承载业务,有状态应用单独打到带存储盘的节点上。不要为了省机器只搞单 master,私有化交付里硬件一挂就是事故,单 master 的集群一旦出问题,恢复成本远高于多一台机器的成本。
Sealos 在离线环境的安装要提前准备安装包,包含 Sealos 二进制、集群镜像和需要用到的组件镜像。这一步建议在能联网的机器上先sealos pull拉好所需镜像,再sealos save导出成 tar 包,带到内网后sealos load导入。实际操作时最容易出错的是版本匹配:镜像包和 Sealos 二进制版本不一致,会出现莫名的加载失败。我的经验是每个环境打一套固定的版本组合,记录下来写进交付文档,不要图省事混用版本。
2.2 存储与数据面准备:PV、StorageClass、共享存储
存储是迁移里最容易被低估的一环。Rancher 自带或使用环境里的 StorageClass,到 Sealos 这边可能根本没有对得上的类名,更要命的是不同的存储驱动对底层实现要求不一样,导致同样一份 YAML 在新集群里完全跑不动。
先理清一个概念:PVC 是集群内的资源,但它背后依赖的 PV 数据分布式在节点或存储设备上,跨集群迁移不可能直接把 PV 搬过去。所以我的做法是:
- 在新集群准备好存储驱动和 StorageClass。如果是本地磁盘方案,我会用 OpenEBS 或者 Sealos 自带的存储组件,给每个节点加一块独立数据盘,避免把业务数据跟系统盘混在一起。
- 把“存储类名”统一规划好。原来 Rancher 用的
local-path、nfs-client之类的名字,在新集群里尽量保持一致,这样工作负载 YAML 里指定的 storageClassName 不用改,省去大量改动。 - 对已经有数据的应用,单独做数据拷贝,这个后面在讲有状态服务时会展开。
这里有个细节:如果新集群的 StorageClass 名字跟旧集群不一样,所有有状态工作负载的 YAML 都要改一遍,包含volumeClaimTemplates里的类名。用 sed 批量替换要小心,有时你只想改存储类,结果把 ConfigMap 里的配置也误改了。建议改动前先把所有出现了storageClassName的文件列出来人工过一遍。
2.3 镜像仓库与私有化拉取的姿势
私有化环境最大的问题是镜像拉取。源集群里用的镜像都写在 docker hub 或者私有仓库里,到了客户内网,如果镜像仓库没同步好,Deployment 起来就是 ImagePullBackOff,而且这个报错会伴随整个迁移过程反复出现。
我这里的标准动作是:先在新环境搭建一个 Harbor 或 Registry,然后把源环境所有工作负载用到的镜像全部同步过去。同步方式有两种,取决于源环境能否联网:
- 能联网的:用 skopeo copy 或 docker pull + tag + push,写一个循环脚本批量处理。
- 完全离线的:在能联网的机器上
docker pull后docker save成 tar,再到内网docker load,最后推送到内网仓库。
批量替换镜像地址也有坑。我习惯先在 YAML 里用image: registry.old.com/xxx:v1.0这种旧地址格式查一遍,然后统一替换成新仓库地址。只改镜像地址还不够,还要检查imagePullSecrets:如果新仓库是需要认证的私有仓库,Pod 里没有对应的 Secret,一样拉不下来。
还有个隐性坑是镜像 tag 以latest结尾的情况。在内网环境,如果所有节点都设置了imagePullPolicy: Always,每次 Pod 调度都会去仓库重新拉取,仓库里没有对应 tag 就会失败。我的做法是迁移前把所有latest全部重新打成一个带具体版本号的 tag,避免后续不可控。
3. 工作负载与数据迁移:从 YAML 到业务的完整路径
3.1 无状态服务:最快搬迁,但也有隐藏依赖
无状态服务理论上是最简单的迁移对象:导出一份 YAML、清洗、apply,就完成了大半。但实际操作中无状态服务也会翻车,最常见的翻车原因有三个。
第一个是环境变量和配置的隐式依赖。很多应用读不到配置不会报“配置缺失”这种直白错误,而是启动成功后连不上数据库、Redis 报错,这类问题排查起来相当折磨人。我的建议是迁移前先把应用依赖的外部资源全部列出来:数据库地址、Redis 地址、消息队列、内部 API 网关,然后逐一核对新集群里这些地址是否通、域名是否解析正确。
第二个是 hostAliases。很多企业内部服务不走 DNS,直接在 Pod 里写 hostAliases 把某个域名映射到内网 IP。旧集群导出的 Deployment YAML 一般会带 hostAliases 字段,但如果你的节点网络或者 Service 网段跟旧环境不一样,这个 IP 很可能在新集群是错的,出现服务间互相访问超时。
第三个是健康检查的探针参数。旧集群节点资源紧张时,很多应用的 readinessProbe 和 livenessProbe 参数其实已经踩在超时边缘了,迁移到新集群后如果探针 interval 和 initialDelay 没有适配新环境的资源规格,会出现 Pod 反复重启,或者出现“启动成功了但永远不 Ready”的状态。
无状态服务的迁移节奏是:先迁一批非核心应用,确认稳定后再迁核心应用。不要在迁移当天一股脑全上,出了问题连回滚范围都判断不了。
3.2 有状态服务:数据库、中间件与持久化数据迁移
有状态服务是整个迁移工程里难度最大的部分。我核心的原则只有一句话:尽量走逻辑导入导出,避免直接文件级拷贝。
以数据库为例,MySQL 用mysqldump导出 SQL,PostgreSQL 用pg_dump,到了新环境再导入。逻辑导出有几个好处:版本跨度的兼容性更好、数据表结构可以趁机整理、导入过程能清晰看到报错。SQL 文件特别大的时候,导入中断是常态,几百 MB 的 SQL 导入到一半网络闪断,整个库回滚是最让人头疼的情况。我的做法是分库分表导出,每个表独立成一个.sql文件,导入时逐个执行,哪个失败就重试哪个。像mysql < xxx.sql这种一把梭的姿势,在动辄几个 GB 的数据量下基本都会出问题。
文件级拷贝主要用在这些场景:对象存储里的大量文件、本地盘上的索引数据、某些不接受停机导出的业务。如果一定要拷贝文件,推荐用rsync先在旧集群跑一轮全量同步,业务切到只读模式后再跑一轮增量同步,然后切换。千万不要复制文件的同时业务还在写,拷贝出来的数据全是裂脑状态,对账对到崩溃。
StatefulSet 迁移时有个关键点:PVC 的名字必须跟volumeClaimTemplates生成的一致,否则新 Pod 挂载旧数据时找不到卷。先手动建 PVC,再让 StatefulSet 的副本按命名规则自动绑定,这是标准姿势。
对于现在很常见的大模型或 Dify 这类 AI 应用迁移,还要特别留意向量库和模型文件的存储位置。向量库的数据如果混在普通 PVC 里,文件量巨大优先走对象存储或块存储的快照方案;模型文件体积也不小,最好是放到独立的数据目录,用 rsync 增量同步,能省下不少时间。
3.3 配置与密钥迁移:Secret、ConfigMap、hostAliases、时区
这一类资源单个都很小,但是数量庞大,而且它们的问题不在“迁移”本身,而在“迁移后的一致性”。Secret 导出时默认是 base64 编码的,直接导没问题,但要注意新集群里 Secret 的 type 是否还是Opaque。还有一个容易被忽略的是 tls 类型的 Secret,在 Rancher 环境里可能由 cert-manager 自动管理,迁移到 Sealos 后如果 cert-manager 没有部署,证书不会自动续期,到期后整站 HTTPS 直接挂掉。
时区问题也值得一提。很多容器镜像默认是 UTC 时区,旧集群里可能已经通过环境变量TZ=Asia/Shanghai做了调整,但这份配置不一定写在工作负载的 YAML 里,而是写在 Rancher 的全局配置里。导出的 YAML 里看不到,新集群就默认 UTC,结果 CronJob 定时任务全部对不上点。迁移后第一时间检查所有 CronJob 的调度时间和应用日志时间戳,这个坑十次里有八次会出现。
4. 流量切换与安全收尾:Ingress、证书和回滚预案
4.1 Ingress 与网关的适配
Rancher 环境最常见的 ingress-controller 是 nginx-ingress,Sealos 环境默认可能装的是 Traefik,或者你在初始化时选择了 nginx。两个控制器在处理同一份 Ingress YAML 时,注解体系完全不同,比如白名单nginx.ingress.kubernetes.io/whitelist-source-range这种注解,在 Traefik 里根本不生效,而且是静默不生效,接口照样被外部访问,没有任何报错提示。
我的做法是先把旧集群里的 Ingress 列表全部导出,逐个确认这些注解在目标 ingress-controller 下的等价写法,然后做一份“注解映射表”:
nginx.ingress.kubernetes.io/rewrite-target对应 Traefik 的traefik.ingress.kubernetes.io/rewrite-target(不同版本写法有差异,以实际为准)- 涉及到 SSL 透传的,确认目标控制器是否支持对应字段。
- 涉及全局访问控制的,必须验证白名单、Basic Auth 这类安全注解是否真的在新环境生效。
还有一个小坑是ingressClassName。旧集群里可能默认用注解方式指定 ingress class,新集群如果不设置这个字段,控制器可能直接不理这条 Ingress,出现 404 或者 503。这个我在测试环境就踩过,因为 YAML 里什么都没写错,就是访问不通。
4.2 证书、内部域名和网络安全策略
证书迁移要说清楚一件事:内部系统用的自签证书,不能只把 Secret 搬到新集群就算完。因为签发证书的 CA 根证书在旧环境的各台服务器上通常是被信任的,新集群的节点能不能信任这张证书,取决于根证书是否也一并安装。
我的标准步骤是:把 Secret 里的tls.crt和tls.key导出,在新集群用kubectl create secret tls重新创建,确保 Secret 名称跟 Ingress 引用的一致。然后在所有需要访问该服务的节点和客户机侧,把签发该证书的根 CA 导入系统信任库。这一步如果漏做,会出现 HTTPS 客户端报证书无效,排查半天发现证书“明明迁过去了”但就是不被信任。
内部域名也需要重新梳理。旧集群里各服务之间经常通过 Service 域名通信,比如service.namespace.svc.cluster.local,这类域名在新集群只要 Service 名字和 namespace 保持一致就能自动解析,问题往往出在那些用了自定义域名访问内部服务的应用上,这些域名在旧环境可能靠额外 DNS 配置或 hosts 文件解决,新集群里没有相应配置就会连不上。
4.3 蓝绿切换与快速回滚
迁移风险最高的时刻是流量切换,我强烈不建议直接改 DNS 让所有流量一瞬间切到新集群。实战中更稳妥的方案是蓝绿切换:
- 先在旧集群旁边建好新集群,两边并行跑一段时间,验证数据同步和功能一致性。
- 用仅改动一台机器 hosts 的方式做“迷你验证”,确认新集群上这个应用的核心链路可用。
- 在流量入口层把少量比例(比如 5%~10%)的请求切到新集群,观察错误率和响应时间。
- 稳定运行一个业务周期后,再把全部流量切过去。
这套流程的意义在于,每一步都有回滚点。某一步出了问题,只需要把流量切回旧集群,不需要重新执行迁移和启动流程。回滚预案要提前写成脚本,不能说“到时候再想办法”,真到出问题的时候,人的判断力会直线下降,手写命令错误率高得离谱。
另外新集群上线后,旧集群至少保留到“新集群完整运行一个自然周”再关停。有些数据问题一周才暴露一次,比如周报任务、月底定时任务,太早释放旧集群,等于把后悔药扔了。
5. 验证、监控与排雷实录
5.1 迁移后的验收清单
迁移后验收不能只看“Pod Running 就完事”,这个状态只能证明容器拉起来了,离业务可用还差得远。我有一套从浅到深的验收顺序:
- 基础设施层:所有节点 Ready、CoreDNS 解析正常、StorageClass 可正常创建 PVC、Ingress 控制器能响应请求。
- 应用层:Deployment 副本数符合预期、日志无 ERROR 级报错、Pod 能被正确调度到有存储的节点。
- 业务层:登录、查询、关键业务链路逐一走一遍,不放过写操作。
- 数据层:源库和目标库的行数对比、最大主键值对比、增量同步的延迟归零。
针对数据层我想多说一句:别只对行数,一定要对“关键业务表的备注字段”之类的细节,有时候行数一致但具体字段值不一致,这类问题在逻辑导出导入的场景下很容易被忽略。
5.2 日志、监控与告警重建
Rancher 环境自带一套监控栈,这套监控进不了新集群。迁移后如果不在 Sealos 环境重新部署监控和日志组件,你的集群会变成“睁眼瞎”:应用出问题全靠用户反馈才知道。
日志这块我的标准动作是:在 Sealos 新集群部署 Loki 或 ElasticSearch,接入 node 节点的容器日志目录,再按 namespace 和应用维度配置日志采集。这一步建议在业务迁移的同时就做,不要等上完线再做,因为上线当天问题最多,恰恰是最需要日志帮助判断的时刻。
监控告警主要覆盖三块:节点层(CPU、内存、磁盘)、容器层(Pod 重启次数、镜像拉取失败)、业务层(HTTP 5xx 响应、队列积压、注册中心实例数)。前两层靠 Prometheus 加 exporter 就能搞定,第三层通常需要应用暴露指标接口。Sealos 本身带有监控组件,直接启用即可,告警规则需要你根据旧集群的经验重新配置一遍,尤其是磁盘空间和内存使用率这两条,私有化环境最容易挂在这上面。
5.3 高频问题速查表
| 问题现象 | 常见原因 | 解决方案 |
|---|---|---|
| Namespace 一直 Terminating | Rancher 的 finalizer 残留在 namespace 上 | 清洗 YAML 时删掉 finalizer,或在新集群手动 patch 移除 |
| PVC 创建成功但 Pod 挂载失败 | StorageClass 的驱动依赖未安装 | 确认新集群对应存储驱动组件已部署,必要时手动安装 |
| 镜像拉取报 ImagePullBackOff | 镜像地址未替换或 imagePullSecret 缺失 | 批量替换镜像地址,重建 PullSecret 并绑定到 ServiceAccount |
| 服务间域名访问不通 | hostAliases 里的 IP 还是旧集群地址 | 重新梳理内网映射关系,更新 hostAliases |
| Ingress 访问 404 | ingressClassName 未匹配 | 检查 Ingress 的 class 字段和实际控制器是否一致 |
| CronJob 时间不对 | 容器默认 UTC 时区 | 为 CronJob 补充 TZ 环境变量,或调整 Cron 表达式 |
| 应用启动成功但永不 Ready | 探针参数与资源规格不匹配 | 调整 initialDelaySeconds 和 timeoutSeconds |
| Secret 导入后应用读不到 | Secret 类型不一致或 namespace 不对 | 确认 type、namespace、mount 路径一致 |
5.4 迁移“隐形坑”名单
最后把上面所有经验浓缩成一份避坑名单,这些都是常规操作发现不了、出问题才追悔莫及的点:
- 导出 YAML 时没清理 Rancher 的
finalizers,导致 namespace 删除不了。 - 迁移了 PVC 但没迁移 StorageClass,新集群没有任何类能绑定。
- 改了镜像仓库地址却忘了
imagePullSecrets,Pod 例常拉起失败。 - 只迁了 Ingress 没迁 tls Secret,页面证书报错。
- 只迁了应用没迁 ServiceMonitor,指标全丢。
- 源库数据迁完没做行级校验,上线后发现单表数据缺失。
- 时区没统一,定时任务全部乱套。
- 假设旧集群的默认 ingress-controller 和新集群一样,没有验证注解兼容性。
这套迁移方案做完之后,我的整体感受是:Sealos 在私有化场景的交付体验确实比 Rancher 顺滑很多,尤其在集群初始化和组件管理这块省了不少时间。但迁移本身是个系统工程,工具只是其中一环,真正决定成败的,是对旧环境资产的完整盘点、对新环境的充分准备,以及留好每一步的回滚退路。最后再分享一个小技巧:在测试环境先完整走三遍迁移流程,把每个步骤的耗时、报错、回滚点都记录成一张执行单,正式迁移那天按执行单走,你会发现“突发状况”比想象中少得多。