1. 为什么在企业离线环境里我会选 Sealos
1.1 先盘清楚:离线私有化到底难在哪
我这两年一直在做企业级大模型私有化部署,说白了就是帮客户把大模型能力搬进内网。在外网环境里装一个 K8s 集群已经不算什么新鲜事,真正的麻烦全在离线环境:没有外网源、没有公共镜像仓库、连系统基础依赖都不能在线安装。很多客户的需求又特别一致,模型和数据不能出内网、推理链路必须完整、应用要能长期稳定跑,于是所有交付动作都得压缩在一台能联网的"制作机"和一摞拷贝进内网的离线包里完成。
以前用 kubeadm 做离线集群时,光准备物料就够头疼:kubelet、kubeadm、kubectl 这三个二进制要对应版本,pause、coredns、etcd、calico 这些镜像要从不同渠道下载,还得手工生成证书、配置 containerd、设置 kubelet 启动参数。任何一个组件的版本跟 K8s 主版本不匹配,排错可能就要花掉一整天。我第一套离线环境从清点依赖到真正跑通,前后磨了一个多月,后来切换到 Sealos 重新做,两天就能拉起一个新集群。这个对比让我决定把 Sealos 私有化这条链路彻底摸透。
1.2 Sealos 集群镜像机制是如何把依赖链收进一个包的
Sealos 的核心思路是把整个 K8s 集群当成一个镜像来交付。这里说的集群镜像不是普通容器镜像,而是一个包含 Kubernetes 二进制、etcd、containerd、kubelet、kubeadm、网络插件等全套组件的"集群运行时镜像"。它跟 Docker 镜像类似,有仓库、有 tag、有分层复用,但加载之后输出的不是一个容器,而是一个完整可用的集群。
这个机制对离线环境的意义在于依赖收敛。有网环境里执行一条sealos pull就能把集群镜像拉到本地,再通过sealos save打成 tar 包,拷贝到内网后sealos load导入,然后用sealos run或sealos apply直接拉起集群。整个过程不需要访问任何外部 yum 源、apt 源或镜像仓库。我实测下来,一个 K8s 1.27 集群的离线包体积大约在 2~3GB,比之前手工准备一堆散装二进制和镜像的方式清爽太多,而且 tar 包可以反复拷贝,交付多少个环境都只需同一份物料。
1.3 企业大模型私有化部署场景里 Sealos 能做什么
企业大模型私有化部署是一个比单纯装 K8s 复杂得多的场景。常见组合是 FastGPT 这类知识库问答系统做应用入口,OneAPI 做模型网关转发,Ollama 或者 vLLM 在 GPU 节点上提供推理能力,再加上 Milvus、Qdrant 这类向量库做知识检索。这套链路里每个组件都有各自的镜像、依赖和持久化要求,离线部署时如果逐个处理,工程量非常大。
Sealos 能把这些组件以集群镜像或应用镜像的形式统一管理。应用镜像可以先在有网机器上拉取再离线导入,集群内部的镜像仓库会自动承担镜像分发,应用编排则交给 K8s 原生能力完成。对交付团队来说,最大的改变是操作面收窄了:不需要在每个节点上手工装容器、配仓库、改证书,而是把整集群当成一个镜像来装载和部署。下面我就按实际执行顺序,把从物料准备到上线运维的完整过程写出来。
2. 动手之前:离线部署的物料清点与版本选型
2.1 版本选型:锁定 tag,拒绝 latest
离线环境部署有一个天然约束:一旦进场,就没有后悔药。在有网机器上打包时,如果顺手写了latest,很可能打包当天拉到的镜像跟客户现场验收时不一致,后续升级排查都不知道基线是什么。所以我从一开始就强制自己锁死版本。
我这边用的组合是 Sealos v4.3.x,配套集群镜像labring/kubernetes:v1.27.7和网络插件labring/calico:v3.26.4。选这套组合的原因有三:一是 K8s 1.27 在企业生产环境里已经验证了相当长时间,稳定性有保障;二是这套镜像组合我踩过所有版本兼容性问题,问题都在可控范围;三是 v1.27 对内核版本的要求相对宽松,很多客户现网还是 CentOS 7.9 或者麒麟系统,内核升级的冲击面小。如果是全新的 Ubuntu 22.04 环境,我会考虑labring/kubernetes:v1.28.4,总之原则是明确记录每个镜像的完整 tag,避免任何隐式依赖。
2.2 在有网环境制作离线包的三道工序
制作离线包最好找一台跟目标环境架构一致的有网机器,否则会踩架构不匹配的坑,后面我会单独讲。我的标准流程分三步走。
第一步,拉取集群和附加组件镜像。执行以下命令时建议开启代理或使用加速镜像源,不过我这里只写正常拉取方式,大家根据自己的网络条件调整:
sealos pull labring/kubernetes:v1.27.7 sealos pull labring/calico:v3.26.4第二步,把镜像保存成 tar 包。这一步相当于把集群镜像落地为可拷贝的离线文件:
sealos save -o kubernetes-v1.27.7.tar labring/kubernetes:v1.27.7 sealos save -o calico-v3.26.4.tar labring/calico:v3.26.4第三步,把大模型应用相关镜像也一并保存。企业大模型私有化部署中常用的 FastGPT、Ollama、Milvus 等组件,同样用sealos pull配合sealos save处理。这里给一个我实际用过的组合:
sealos pull labring/fastgpt:v4.7.5 sealos pull labring/ollama:latest sealos pull labring/milvus:v2.3.10 sealos save -o fastgpt-v4.7.5.tar labring/fastgpt:v4.7.5 sealos save -o ollama.tar labring/ollama:latest sealos save -o milvus-v2.3.10.tar labring/milvus:v2.3.10保存出来的 tar 包建议统一放到一个目录里,并在旁边写一个manifest.txt,记录每个 tar 包对应的镜像名、tag、大小。到了客户现场,很多时候不是技术问题先崩,而是混乱的物料让人先崩溃,一份清单能救命的。
2.3 目标环境预检:硬件、系统、网络一个不能少
在拿着 U 盘进机房之前,我强烈建议先做一次目标环境预检。很多离线部署现场的问题,其实在硬件和系统层面就已经注定了。我把预检项整理成下面的清单,每项都标注了踩坑轻重度,省得大家重复交学费:
| 检查项 | 检查要求 | 踩坑影响 |
|---|---|---|
| CPU 架构 | 统一为 amd64 或 arm64,不能混用 | 镜像架构不匹配,拉取后无法运行 |
| 内核版本 | K8s 1.27 建议内核 5.4 及以上 | kubelet 启动失败、网络组件异常 |
| 内存与磁盘 | 每 master 建议 16G 内存,根分区预留 100G+ | 集群组件 OOM、镜像解压空间不足 |
| 网卡与 IP | 确认默认路由网卡,多网卡必须指定接口名 | 集群内部通讯走错网卡,节点反复 NotReady |
| 防火墙 | 关闭或放行 6443、10250、2379 等端口 | 节点心跳、apiserver 访问失败 |
| swap | 必须关闭 | kubelet 直接报错退出 |
| 主机名 | 各节点 hostname 不能重复且要规范 | 集群节点注册冲突 |
预检时我习惯用一条脚本批量收集节点信息:uname -r看内核,cat /etc/os-release看系统版本,ip addr看网卡和 IP,free -g看内存,df -h看磁盘。把结果统一汇总到一张表里发到工作群,比到时候逐个节点排查要高效得多。另外别忘了确认目标机器的时间是否同步,NTP 没有配好的离线环境,证书校验会以很诡异的方式失败,这个坑我踩过一次,后面会细讲。
3. 离线部署实操:从导入镜像到集群拉起
3.1 镜像导入与 Clusterfile 配置
到了离线环境,第一件事不是急着执行部署,而是先把离线包导入 Sealos 本地镜像库。我在客户现场的标准动作是:
sealos load -i kubernetes-v1.27.7.tar sealos load -i calico-v3.26.4.tar sealos load -i fastgpt-v4.7.5.tar sealos load -i ollama.tar sealos load -i milvus-v2.3.10.tarsealos load执行完后,可以用sealos images确认导入结果。接下来准备 Clusterfile,这是 Sealos 声明式部署集群的入口。我常写的模板如下:
apiVersion: apps.sealos.io/v1beta1 kind: Cluster metadata: name: prod-cluster spec: hosts: - roles: [ master ] ips: [ 192.168.10.11, 192.168.10.12, 192.168.10.13 ] - roles: [ node ] ips: [ 192.168.10.21, 192.168.10.22 ] image: - labring/kubernetes:v1.27.7 - labring/calico:v3.26.4 ssh: passwd: "" pkFile: "" registry: domain: sealos.hub port: 5000需要注意 Clusterfile 里的 SSH 认证方式。企业内网环境通常不支持直接用密码登录,我一般用pkFile指向管理员下发的那把部署专用公钥。如果只能用密码,务必确认密码里没有$、#这类容易被终端转义的字符,否则 SSH 连接会以非常隐蔽的方式失败。registry段配置的是 Sealos 内置镜像仓库的域名和端口,这个仓库在集群部署完成后会自动运行在第一个 master 节点上,承担后续应用镜像的分发任务。
3.2 sealos apply 拉起集群的过程与验证
Clusterfile 准备好后,一条命令就能开始部署:
sealos apply -f Clusterfile执行过程中 Sealos 会依次完成节点 SSH 探测、环境检查、组件镜像同步、K8s 集群初始化和网络插件部署。这个过程中终端会输出大量日志,我的建议是不要只盯着最终结果,要留意几个关键节点:SSH 连接是否全部成功、镜像是否在节点间正常分发、apiserver 是否健康、calico 是否处于 running 状态。
部署完成后,登录任意 master 节点执行:
kubectl get nodes kubectl get pods -A正常状态是所有节点显示 Ready,系统组件 pod 没有 CrashLoopBackOff。这里补充一个验证小技巧:kubectl get nodes -o wide可以看到每个节点的内部 IP,你要确认这个 IP 是内网业务网段的 IP,而不是某个管理网段的 IP,避免后续业务流量走错链路。
3.3 大模型应用的离线编排:FastGPT 与 Ollama 的落地
K8s 集群就绪后,大模型应用的编排是我最关注的部分。以企业大模型私有化部署最常见的 FastGPT + OneAPI + Ollama + Milvus 组合为例,FastGPT 提供知识库问答界面,OneAPI 做模型 API 的统一封装,Ollama 在 GPU 节点上跑本地模型,Milvus 负责向量检索。
首先要解决模型文件的问题。Ollama 的模型默认存放在~/.ollama/models目录下,离线环境的模型文件需要在有网机器上提前拉取,然后把整个目录拷到 GPU 节点。我通常这样做:
# 有网机器上拉取模型并打包 ollama pull qwen2.5:14b tar czf ollama-models.tar.gz -C /root/.ollama models到目标 GPU 节点解压并放到相同路径:tar xzf ollama-models.tar.gz -C /root/.ollama。这里最关键的是目录结构要保持一致,否则 Ollama 服务识别不到模型。
接着是应用容器的编排。FastGPT、Milvus 这些应用镜像已经通过sealos load导入,我习惯直接在 K8s 上写 Deployment 和 Service 清单,用本地镜像部署。关键的持久化配置要做到位:Milvus 的 etcd、MinIO 数据目录,FastGPT 的 PostgreSQL 和向量库都要挂载到宿主机目录或已接入的存储上,否则容器一重建,客户的知识库和问答历史就全没了。Ollama 所在的节点还要打上专门标签,用 nodeSelector 让推理 pod 固定调度到 GPU 节点上。
4. 踩坑实录:五个让我加班到深夜的问题
4.1 先看速查表
这部分是我最想分享的内容。离线环境排错非常熬人,往往一个看似不起眼的系统差异就能让整个集群起不来。下面是我按真实发生频率整理的问题速查表,后面的小节再挑典型案例展开说:
| 问题现象 | 根因方向 | 快速解法 |
|---|---|---|
| 集群内部 IP 混乱,节点 NotReady | 多网卡默认路由指向错误网卡 | 部署时用--interface或 Clusterfile 指定内网网卡 |
| kubelet 启动失败,报 cgroup 错误 | 内核版本过低或 swap 未关闭 | 升级内核到 5.4+,并swapoff -a |
| Pod 申请 GPU 失败,调度到 GPU 节点仍报错 | containerd 未配置 nvidia runtime | 安装 nvidia-container-toolkit,配置 RuntimeClass |
| 应用镜像拉不下来,提示 401 或 tls 错误 | 内网仓库认证或 insecure registry 未配置 | 在 containerd 配置中添加内置仓库为 insecure |
| Ollama 模型容器重建后丢失 | 模型目录未挂持久化卷 | 将/root/.ollama挂到宿主机目录 |
| 部署过程 SSH 连接失败 | 密码含特殊字符或目标机禁止密码登录 | 改用密钥,避免特殊字符密码 |
| 证书验证失败,节点注册不了 | NTP 未配置,节点间时间偏差大 | 搭建内网 NTP 服务或手动校准时间 |
4.2 多网卡导致集群通讯异常
这是我踩得最深的坑。某个客户现场每台机器都有两个网卡,一个连业务内网,一个连存储管理网。Sealos 默认会选第一条路由所在网卡,结果集群的节点间通讯全走了存储管理网,K8s 组件心跳时通时断,节点反复跳动在 Ready 和 NotReady 之间,apiserver 日志里全是 TLS 握手超时。
排查到最后才发现是网卡选择问题。解决办法是在 Clusterfile 里给 Sealos 传递指定网卡参数,或者在执行sealos apply时通过环境变量明确默认接口名。实际操作中我简化处理:先把存储管理网卡临时禁用,让默认路由落在业务内网网卡上,部署完成后再恢复。这个办法虽然粗暴,但效果立竿见影,后来我干脆在预检阶段就把目标机器的网卡名全部标注在部署文档里,再也没被这个问题绊倒过。
4.3 内核版本过低导致 kubelet 无法启动
有次交付时客户提供的是 CentOS 7.9 机器,预检时只看系统版本没看内核,结果kubectl get nodes怎么等都等不齐,打开 kubelet 日志发现大量 cgroup v2 相关报错。原因很清楚:K8s 1.27 在 CentOS 7.9 默认的 3.10 内核上跑不稳,即使勉强启动,网络组件和内存管理也会出各种怪问题。
解决路径是升级内核。我在客户机器上把内核升到 5.4.x 版本,重启后 kubelet 才正常注册节点。这里要特别提醒:内核升级涉及机器重启,一定要提前跟客户确认变更窗口,千万别在业务时段直接执行,否则影响面很难收场。另外升级完内核之后,swapoff -a和/etc/fstab里 swap 条目注释这两步也别忘了,kubelet 对 swap 是零容忍的,不关掉照样起不来。
4.4 GPU 节点容器无法调用显卡
企业大模型私有化部署里 GPU 是刚需,但这个环节的坑一个接一个。我把容器调度到 GPU 节点后发现 Pod 能启动,日志却提示找不到 CUDA 设备。用nvidia-smi在宿主机上看驱动一切正常,问题显然出在容器运行时没有把宿主机的 GPU 设备透传进去。
根因是 containerd 里没有配置 NVIDIA 的 runtime。我按官方流程安装了 nvidia-container-toolkit,然后修改/etc/containerd/config.toml,为 containerd 增加 nvidia runtime handler,再创建对应的 RuntimeClass,Pod 里声明runtimeClassName: nvidia后显卡才能正常使用。这一套配置下来,GPU 节点才算真正跑起来。顺带说一句,如果客户的驱动版本低于 CUDA 要求,模型推理时会报 ban 那么一堆兼容性错误,这时候最快的方法不是调驱动,而是换一个跟驱动匹配的 CUDA 基础镜像。
4.5 内置仓库认证与镜像拉取失败
集群是起来了,应用镜像也导入了,但部署 FastGPT 时节点却拉不到镜像,报错要么是unauthorized,要么是 TLS 连接失败。原因是 Sealos 内置镜像仓库默认走 HTTP,而节点上的 containerd 默认认为所有远程仓库都走 HTTPS,不额外配置的话根本无法从内置仓库拉取镜像。
解决办法是在每个节点的/etc/containerd/config.toml里,把内置仓库地址配置到registry.mirrors和registry.configs中,明确使用 HTTP 并开启 skip verify。这里我建议写个一次性脚本在所有节点批量执行,比手动一台台改效率高得多。配置完 containerd 要重启节点上的 containerd 服务,Pod 重新调度后镜像拉取就正常了。这个坑在离线环境尤其隐蔽,因为看起来是认证问题,实际是协议和信任策略不匹配。
4.6 模型文件没做持久化,容器一重建就丢
最后这个坑是应用层面的,但杀伤力很大。给客户做完演示之后,我出于更新配置的目的重建了一次 Ollama 相关 Pod,结果重启后模型全部消失,服务直接报模型不存在。排查才发现 Ollama 容器把模型文件写在了容器可写层,而我没有为/root/.ollama做持久化挂载,容器一重建,可写层直接被回收。
这个问题的教训比技术本身更重要:只要是大模型私有化部署,凡是模型文件、知识库索引、向量数据这类有状态数据,一律要挂到宿主机目录、块存储或网络存储上。应用可以随时重建,但数据和模型不能有任何例外。后来我写部署清单时,把所有需要持久化的路径列成了一张表,逐一确认挂载,再也没在这个问题上翻过车。
5. 私有化上线后的运维三板斧
5.1 离线环境的版本升级路径
在线环境的升级可以依赖镜像仓库自动拉取,离线环境不行,所有新版本镜像都必须通过离线包导入。我的做法是建立一个固定的升级流程:先在有网机器上拉取新版本集群镜像或应用镜像,执行sealos save打成 tar 包,再拷贝到内网执行sealos load,然后用sealos upgrade或更新 Deployment 镜像版本的方式滚动升级。
这里面有一个经验值得分享:升级之前一定要先在测试集群完整走一遍,记录每一步耗时和匹配的镜像 tag。离线环境的客户通常不允许频繁变更,一次升级窗口可能隔几个月才有一次,走完测试流程能让正式操作变得非常快。另外,所有升级操作前后都要对 etcd 和应用数据做快照,一旦升级失败,第一时间回滚才是止损的正确姿势。
5.2 备份、恢复与灾难演练
私有化交付不只是把系统跑起来,更重要的是给客户一个可恢复的兜底方案。我在离线环境里至少做三层备份:etcd 快照、应用数据库备份、模型文件归档。etcd 是整个集群状态的核心,我会在 master 节点上使用 etcdctl 定期执行快照,同时把快照文件同步到独立存储节点或者备份服务器:
ETCDCTL_API=3 etcdctl --cacert=/etc/kubernetes/pki/etcd/ca.crt \ --cert=/etc/kubernetes/pki/etcd/server.crt \ --key=/etc/kubernetes/pki/etcd/server.key \ snapshot save /backup/etcd-$(date +%F).db应用数据库方面,FastGPT 底层是 PostgreSQL 或 MongoDB,Milvus 的元数据和数据存储在 etcd 和 MinIO 中,这些都要配置各自的定时备份。我还会每季度做一次完整的恢复演练,把备份数据恢复到一台全新的测试集群上,确认业务能启动、模型能加载、历史问答记录能查到。备份没有经过恢复验证等于没备份,这句话是我踩过几次坑之后最深的体会。
5.3 监控告警的离线化落地
离线环境的监控不能靠外部 SaaS,只能自己把它收敛到离线包里。我在有网机器上提前拉取 Prometheus、Grafana、node-exporter 等组件的镜像,用 docker save 或 sealos save 打成 tar 包,进内网之后再 load。监控组件和 K8s 集群跑在同一套资源池里即可,不需要额外机器。
部署时我会重点监控四类指标:节点 CPU、内存、磁盘使用率,GPU 的利用率与显存占用,K8s 核心组件的 pod 状态,以及应用层的推理耗时。Grafana 面板里的图表可以事后慢慢调,但告警规则一定要在上线前配好。离线环境出了故障,可定位的手段本身就少,如果监控能第一时间告诉你节点状态变化和容器重启次数,排查效率会完全不同。我在实际配置中会把节点磁盘使用率超过 85% 和 GPU 异常两条告警优先级调最高,因为这两个出问题的频率最高,影响也最直接。
我从第一次做 Sealos 私有化到现在,前后交付了十几个离线环境,最大的体会是这套工具把"部署 K8s 集群"的复杂度和心智负担降了一个数量级,但工具代替不了对系统底层的判断。离线排错时,耐心对照预检清单、逐层排查系统差异、保持"每一步操作都有据可查"的习惯,比任何工具都重要。如果你正准备在自己的环境里做企业大模型私有化部署,我的建议是:先从一套干净的测试环境开始,把离线包制作、集群拉起、应用编排、备份恢复完整走两遍再碰客户现场,那样你会从容得多。最后再分享一个小技巧:给每台目标机器都建一个 deployment 专用账号,权限收敛到最小,并提前把 SSH 密钥和 sudo 规则配好,线下交付时你会感谢自己当初多花的这十分钟。