开头先讲一个场景。有次帮一哥们搭单节点Rancher环境,镜像按文档同步进内网仓库了,docker run也把Rancher容器拉起来了,浏览器访问HTTPS页面都正常,结果一创建下游Kubernetes集群,节点状态就一直卡在Provisioning,最后直接Failed。翻日志全是x509和超时。查了一下午,问题根本不在Rancher本身,而是整个离线链路里那条最容易被忽略的信任链——节点上的容器运行时根本不认内网Registry的自签证书,同时Rancher也没被告知去内网仓库拉系统组件。这篇就围绕单节点Rancher离线安装这条链路,重点把那个“关键一步”讲透。
不管你是刚接触Kubernetes,还是已经在生产环境用过Rancher,只要内网环境准备离线部署,这篇都能帮你少踩很多坑。
1. 离线链路里的关键角色:镜像仓库与信任关系
1.1 单节点Rancher,为什么也要单独搭一个仓库
很多人以为“单节点Rancher”就是在一台机器上跑一个rancher/rancher容器,装好就能用。这句话对一半。Rancher Server本身确实只是个容器,但它要做的事情远不止“启动一个Web界面”。你后面要创建下游集群,下游节点的kubelet、pause、coredns、ingress-controller这些镜像,Rancher默认都会从docker.io等公共仓库拉取。
离线环境下,没有公网拉取通道,就必须有一个内网镜像仓库承担“中转站”的角色。即使你只管理一个下游集群、只跑几个应用,这个仓库也绕不开。它不是可选项,而是离线部署的地基。
而且这里有个容易误判的点:很多教程会把重点放在“怎么把镜像包导进去”,但实际项目里,真正决定成败的往往是“节点拉镜像时信不信任这个仓库”。后面会专门讲。
1.2 离线部署的三段式链路
离线安装Rancher,整个过程可以拆成三段:
- 第一段:在一台有外网连通的环境上,把Rancher相关镜像拉下来,打包成tar。
- 第二段:把镜像包拷贝到内网,导入到私有镜像仓库,并重新打上内网仓库的tag。
- 第三段:内网节点从私有镜像仓库拉取镜像,启动Rancher容器并完成Kubernetes集群初始化。
三段各有关键点。第一段容易漏镜像,尤其只拉了主镜像、没拉系统组件镜像;第二段容易漏改tag,镜像push进仓库后tag还是原来的docker.io/rancher/...,拉到节点上照样找不到;第三段是重灾区,错误大多发生在运行时与仓库的TLS握手阶段。
单节点部署看似简单,但它把三个角色压缩到了一台机器上:Rancher Server、内嵌K3s/containerd、还可能同时要跑下游节点。角色越多,镜像来源链路越复杂,任何一个环节断了,表象都是一样的——集群创建失败。
1.3 大多数部署“卡壳”的位置
从实际经验看,离线安装Rancher最容易卡住的,不是镜像打包失败,也不是Registry起不来,而是“所有东西都看起来正常,但就是拉不动镜像”。典型表现:
- Rancher容器起来了,但页面加载很慢,或者部分功能报错。
- 创建下游K3s集群时,节点注册成功,但Pod一直Pending。
- 节点日志里有
x509: certificate signed by unknown authority,或者failed to resolve reference。
这些问题背后,基本都是同一个根因:内网镜像仓库用的是自签HTTPS证书,而各个容器运行时的CA信任链里没有这把证书。再加上Rancher的全局镜像仓库地址没有设置,系统组件还在尝试去公网拉,当然拉不到。
所以标题里的“关键一步”,用一句话概括就是:让内网仓库成为Rancher唯一可信的镜像来源,并让所有涉及拉镜像的组件都信任这个仓库。
2. 用registry:2快速搭一个支持HTTPS的内网仓库
2.1 仓库选型与命名规划
实现内网镜像仓库,方案很多。生产环境我会优先考虑带Web界面、带权限管理的企业级仓库,比如Harbor这一类。但如果你只是单节点Rancher离线安装,部署一个轻量的registry:2完全够用,它不依赖外部数据库,占资源少,也方便排查。
我建议的示例环境规划:
| 角色 | 配置 |
|---|---|
| Rancher节点 | IP192.168.10.10,域名rancher.internal |
| 镜像仓库 | 与Rancher同机,域名registry.internal,端口5000 |
| 操作系统 | Ubuntu 20.04 / CentOS 7.9均可 |
| 软件 | Docker、k3s、registry:2 |
这里有个细节值得说一下:仓库地址建议用域名,而不是IP。虽然用192.168.10.10:5000也能跑,但后面证书SAN、镜像tag、节点配置都要跟着地址走。用域名统一管理,迁移和排障都轻松很多。离线环境没有DNS不要紧,在各节点上写/etc/hosts就行。
2.2 签发自签证书并写入Registry配置
registry:2要想启用HTTPS,需要提供证书和私钥。这里我不用自签的单张证书,而是先创建一个私有CA,再给registry发一张带SAN的服务器证书。这样做的好处是:后续所有节点只需要信任同一个CA,换仓库域名、加新仓库时,不需要在每台机器上重新信任证书。
先创建证书目录:
mkdir -p /opt/registry/certs cd /opt/registry/certs生成CA私钥和CA证书:
openssl genrsa -out ca.key 2048 openssl req -new -x509 -days 3650 -key ca.key -out ca.crt -subj "/CN=Offline Registry CA"生成仓库服务器的私钥和证书签名请求:
openssl genrsa -out server.key 2048 openssl req -new -key server.key -out server.csr -subj "/CN=registry.internal"用CA给服务器证书签名,加上SAN字段,这样IP访问和域名访问都能通过证书校验:
printf "subjectAltName=DNS:registry.internal,IP:192.168.10.10\n" > san.conf openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial \ -out server.crt -days 1825 -extfile san.conf然后写registry配置文件。registry:2默认读取容器内的/etc/docker/registry/config.yml,我们把TLS证书路径和存储路径都配置好:
version: 0.1 storage: filesystem: rootdirectory: /var/lib/registry http: addr: :5000 tls: certfile: /certs/server.crt keyfile: /certs/server.key启动registry容器:
docker run -d --name registry --restart=always \ -p 5000:5000 \ -v /opt/registry/config.yml:/etc/docker/registry/config.yml \ -v /opt/registry/certs:/certs \ -v /opt/registry/data:/var/lib/registry \ registry:2.8.3到这里,一个支持HTTPS的私有镜像仓库就起来了。如果你的仓库需要账号密码,还可以用htpasswd生成认证文件,在config.yml里加一段auth配置。不过为了控制篇幅,示例先不开启认证,后面会单独提醒认证对信任链的影响。
2.3 启动Registry并完成推送自测
仓库起来后,先用curl验证HTTPS是否正常。在仓库本机执行:
curl --cacert /opt/registry/certs/ca.crt https://registry.internal:5000/v2/返回{}就说明HTTPS接口正常。这里注意,如果不加--cacert,curl大概率会报证书错误,这是正常的,说明自签证书已经在工作。
接着做一次真实的推拉测试。从我自己的经验看,这一步建议认真做,比后面反复排查节省时间。
# 拉一个测试镜像 docker pull alpine:3.19 # 打上内网仓库tag docker tag alpine:3.19 registry.internal:5000/test/alpine:3.19 # 推送 docker push registry.internal:5000/test/alpine:3.19 # 清理本地tag,验证能从仓库拉回 docker rmi registry.internal:5000/test/alpine:3.19 docker pull registry.internal:5000/test/alpine:3.19如果push和pull都成功,说明仓库本身没问题,可以进入下一阶段。
3. 关键一步:让每一层容器运行时都信任内网仓库
3.1 信任链断在哪:镜像已入库,节点仍拒连
这是整篇文章最想让你记住的部分。很多部署失败,不是说镜像没进去,而是“镜像进去了,但节点不敢连”。
打个比方:内网仓库是一个需要验身份证的门口保安,你把行李(镜像)搬进了仓库,不代表每个搬运工(节点)都认识保安。所有来拉镜像的客户端,都要在TLS握手阶段先核验仓库的证书。仓库的证书是私有CA签发的,客户端没有这把CA,连接就会在握手中被拒。
具体到Rancher环境里,至少有三个“客户端”要认识这把CA:
- 宿主机上的Docker守护进程
- 节点上的K3s/containerd运行时
- Rancher容器内部的内嵌K3s/containerd
只配其中一两个,都会留下死角。
3.2 宿主机Docker/runtime的信任配置
如果Rancher节点用的是Docker来管理容器,信任配置很简单。在宿主机上创建Docker单仓库证书目录,把CA文件放进去:
sudo mkdir -p /etc/docker/certs.d/registry.internal:5000 sudo cp /opt/registry/certs/ca.crt /etc/docker/certs.d/registry.internal:5000/ca.crt sudo systemctl restart docker这个目录名必须带端口号,因为registry监听的是5000,不是默认的443。Docker守护进程在连接registry.internal:5000时,会优先从这个目录读取ca.crt作为信任锚点。
验证方式:
sudo docker login registry.internal:5000 sudo docker pull registry.internal:5000/test/alpine:3.19如果之前本地已经有同名镜像tag,pull不会真正走仓库,可能“假成功”。建议先docker rmi删掉本地tag再pull。
如果节点上运行的是K3s或RKE2,Docker的配置不生效,因为K3s自带containerd。这时候要写registries.yaml:
sudo mkdir -p /etc/rancher/k3s sudo tee /etc/rancher/k3s/registries.yaml > /dev/null <<'EOF' mirrors: "registry.internal:5000": endpoint: - "https://registry.internal:5000" configs: "registry.internal:5000": tls: ca_file: /opt/registry/certs/ca.crt EOF然后重启K3s服务:
sudo systemctl restart k3s如果是RKE2节点,路径换成/etc/rancher/rke2/registries.yaml,重启服务名改成rke2-server或rke2-agent。
3.3 Rancher容器内部也需要信任CA
这一条容易漏。很多人把宿主机Docker和K3s的信任都配好了,结果Rancher容器自己初始化时仍然拉不到系统组件镜像,日志报x509。原因很简单:Rancher Server容器内部也跑了一套K3s/containerd,它自己在初始化cattle-system时,也要去访问内网仓库。容器内部的系统CA链和宿主机是隔离的,宿主机信任,不代表容器内信任。
解决思路是:把CA文件挂载进容器,并让容器更新CA证书列表。
启动Rancher容器时,先挂载CA文件:
docker run -d --name rancher-server --restart=unless-stopped --privileged \ -p 80:80 -p 443:443 \ -v /opt/registry/certs/ca.crt:/usr/local/share/ca-certificates/offline-ca.crt \ rancher/rancher:v2.7.9容器起来后,执行一次CA证书更新,再重启Rancher:
sudo docker exec rancher-server update-ca-certificates sudo docker restart rancher-server这一步看起来不起眼,但如果你跳过,后面大概率会在“系统组件镜像拉取”这个环节卡很久。Rancher容器内部的子系统仓库信任,和外部节点的仓库信任,是两个不同的信任域,都要配。
3.4 常见错配现象与定位方法
信任链配错的表现各不相同,这里列一份错误信息对照表,方便你排查时直接对照:
| 报错关键词 | 问题方向 |
|---|---|
x509: certificate signed by unknown authority | 某层运行时没有信任私有CA |
server gave HTTP response to HTTPS client | 节点用HTTPS访问,但仓库监听的是HTTP |
connection refused | 仓库服务没起来或防火墙没放行端口 |
manifest unknown | 镜像没有推全,或tag前缀写错 |
401 Unauthorized | 仓库启用了认证,但运行时没有登录凭证 |
有一个快速定位思路:从“离仓库最近”的客户端开始验。先在仓库本机用curl验HTTPS,再用Docker从本机pull一次,再切到Rancher节点上用节点运行时pull一次,最后才看Rancher容器内部。按这个顺序排查,能快速把范围缩小到某一层。
4. 启动Rancher时指定SystemDefaultRegistry
4.1 docker run参数逐项拆解
把信任链配好之后,再启动Rancher,命令就比之前完整得多:
docker run -d --name rancher-server --restart=unless-stopped --privileged \ -p 80:80 -p 443:443 \ -e CATTLE_SYSTEM_DEFAULT_REGISTRY=registry.internal:5000 \ -v /opt/registry/certs/ca.crt:/usr/local/share/ca-certificates/offline-ca.crt \ -v /opt/rancher:/var/lib/rancher \ rancher/rancher:v2.7.9逐个参数说清楚:
-p 80:80 -p 443:443:Rancher Web界面的HTTP/HTTPS入口。--privileged:Rancher容器需要修改iptables、挂载文件系统等,特权模式是官方标准做法。-e CATTLE_SYSTEM_DEFAULT_REGISTRY=registry.internal:5000:这是“告诉Rancher去哪里拉系统镜像”的核心参数。-v /opt/registry/certs/ca.crt:/usr/local/share/ca-certificates/offline-ca.crt:把CA文件放进容器内,供容器内部运行时信任。-v /opt/rancher:/var/lib/rancher:Rancher的数据持久化目录,容器升级后数据不丢。
容器起来后,记得执行一次:
sudo docker exec rancher-server update-ca-certificates sudo docker restart rancher-server4.2 CATTLE_SYSTEM_DEFAULT_REGISTRY的作用边界
这个环境变量的含义,很多人理解得不够透。它指定的是Rancher系统组件的镜像仓库前缀,也就是Rancher自身要部署的cattle-system、监控、认证、UI组件等镜像,不再从默认公网仓库拉,而是统一从你填的地址拉。
但它不负责业务应用的镜像。你在业务命名空间里手动创建Deployment时,镜像地址该写什么还是写什么。比如你部署一个业务服务,镜像得已经推到了内网仓库,并且运行时能信任这个仓库,业务Pod才能正常拉起。
还有一个容易踩的点:如果你启动时忘了设置这个环境变量,也不要慌,可以到Rancher UI的Settings里找到system-default-registry,把它改成内网仓库地址。但注意,改完之后已经部署过的组件不会自动更新镜像源,有些组件要重启甚至重建才会生效。所以更推荐在第一次启动时就把环境变量带上,后面省事。
4.3 下游集群的私有仓库设置与节点注册
Rancher Server起来了,下游集群的配置也有对应设置。创建K3s或RKE2集群时,集群的高级选项里通常有一个类似“Private Repository”或“Registry”的字段,填registry.internal:5000。填完后,Rancher生成的节点注册命令会自动带上--system-default-registry registry.internal:5000参数。
节点注册命令长这样,示意一下:
curl -fL https://192.168.10.10/system-agent-install.sh | sudo sh -s - --server \ https://192.168.10.10 --token xxxxx --ca-checksum xxxxxxxxxx \ --etcd --controlplane --worker --system-default-registry registry.internal:5000注意,这里的--system-default-registry是给下游节点用的,它只能保证“下游k3s组件从内网仓库拉”,并不能替代节点本身的信任配置。节点上如果没有写registries.yaml,注册后照样会因为证书问题拉不到pause镜像。
所以顺序应该是:先给所有节点配好信任链,再在Rancher里创建集群并注册节点。
5. 一份可复现的验证清单与踩坑复盘
5.1 部署完成后的快速自检
把下面这张表当成部署后的体检清单,每一项都过一遍,再开始创建业务集群。
| 验证点 | 命令/操作 | 期望结果 |
|---|---|---|
| 仓库HTTPS | curl --cacert /opt/registry/certs/ca.crt https://registry.internal:5000/v2/ | 返回{} |
| Docker信任 | 先删本地tag,再docker pull registry.internal:5000/test/alpine:3.19 | 拉取成功 |
| hosts解析 | getent hosts registry.internal | 输出仓库IP |
| Rancher容器CA | docker exec rancher-server update-ca-certificates | 无报错 |
| Rancher容器运行 | docker logs rancher-server --tail 50 | 无x509/unauthorized报错 |
| UI登录 | 浏览器访问https://192.168.10.10 | 能设置管理员密码并登录 |
| 下游集群Pod | 创建集群后,kubectl get pods -A | 所有Pod处于Running |
| 镜像来源 | 查看一个Pod的yaml,kubectl get pod -n cattle-system -o yaml | Image前缀包含registry.internal:5000 |
这张表看着简单,但每次部署时我基本都会跑一遍。离线的坑就是这样,前期省一步,后面可能要多花一小时排查。
5.2 两个我实际遇到的典型故障
第一个故障:证书信任断裂,节点注册后马上Failed。现象是节点K3s日志里持续滚动x509: certificate signed by unknown authority。当时宿主机Docker的信任已经配好,但忽略了下游节点走的是K3s内嵌containerd,它读的是registries.yaml而不是Docker配置。把CA路径写进/etc/rancher/k3s/registries.yaml并重启K3s后,问题立即消失。
第二个故障:Rancher Server起来了,但创建集群时一直等镜像。排查发现集群模板里没有设置私有仓库,节点注册命令没有带--system-default-registry,于是节点还在尝试从公网仓库拉pause镜像。补上集群级别的Registry配置,重新生成带参数的注册命令,问题解决。
这两个故障说明一件事:离线部署里,“成功”和“真正走通了”之间有好几层落差。镜像load成功不代表能从仓库拉;页面能打开不代表所有组件都在从这个仓库拉。每一层都要单独验证。
5.3 后续升级与维护时要留意的细节
离线环境是一次性的,但之后升级维护会反复遇到同类问题。有几个细节建议提前做:
第一,所有节点上把CA文件放到统一路径,比如/opt/certs/offline-ca.crt,这样以后批量脚本管理方便。不要各自的路径都不一样,排障时非常痛苦。
第二,Rancher升级新版本前,必须先把新版本的镜像清单同步到内网仓库,再升级Server。不要在镜像不足的情况下直接拉新版本,否则升级过程中会卡在镜像拉取。
第三,自签CA有有效期,建议在日历里设置过期提醒。CA过期后所有节点都要重新信任,影响面比想象中大。
第四,离线环境的NTP时钟同步别忽略,证书校验强依赖系统时间。有一台节点时钟偏差大,就可能出现“证书未生效”或“证书过期”的怪错。
最后说点实在的
如果让我总结单节点Rancher离线安装最核心的经验,就两句话:先把内网仓库跑起来并配合好证书,然后让Rancher和所有下游节点都明确用这个仓库。信任链对了,镜像地址对了,剩下的都是一步步走流程。
最后分享一个我自己的小习惯:每次部署,我会把证书生成命令、镜像清单、Rancher启动命令、节点注册命令四样东西单独存一个目录留档。下次遇到相似环境,直接照着自检清单过一遍,比重新翻文档省太多时间。这种离线环境的坑,记录一次,受益很久。