ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

单节点Rancher离线安装的关键一步:镜像仓库证书信任与私有Registry配置

单节点Rancher离线安装的关键一步:镜像仓库证书信任与私有Registry配置

开头先讲一个场景。有次帮一哥们搭单节点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-server

4.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 部署完成后的快速自检

把下面这张表当成部署后的体检清单,每一项都过一遍,再开始创建业务集群。

验证点命令/操作期望结果
仓库HTTPScurl --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容器CAdocker 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 yamlImage前缀包含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启动命令、节点注册命令四样东西单独存一个目录留档。下次遇到相似环境,直接照着自检清单过一遍,比重新翻文档省太多时间。这种离线环境的坑,记录一次,受益很久。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进