前言
Rancher 作为 Kubernetes 集群的统一管理平面,在多集群环境下能极大降低运维负担。
这次我计划把它部署在家里的一台内网虚拟机上,通过 Caddy 反代暴露到公网以便出门时也能访问,同时在架构上预留好网络通路:后续可以通过 Tailscale 私有网络将异地的跨机房 K3s 集群直接注册进来,实现统一纳管。
部署架构
这次的机器规划是这样的:
| 角色 | 主机名 | 公网 IP | 内网 IP | Tailscale IP / 域名 | 备注 |
|---|---|---|---|---|---|
| 反代入口 | aliyun-server | ✓ | - | 10.64.0.11 / aliyun-server.ts.net | 跑 Caddy,公网域名 rancher.example.com 解析到这里 |
| Rancher 服务端 | rancher | - | 192.168.2.1 | 10.64.0.12 / rancher.ts.net | 仅运行 Rancher 容器,只对外通过 Tailscale 暴露 |
| K3s Server | k3s-server | - | 192.168.2.2 | 10.64.0.13 | 运行 v1.34.6+k3s1 的现有集群 |
| K3s Agent | k3s-agent-01 | - | 192.168.2.3 | 10.64.0.14 | 运行 v1.34.6+k3s1 的现有集群 |
| K3s Agent | k3s-agent-02 | - | 192.168.2.4 | 10.64.0.15 | 运行 v1.34.6+k3s1 的现有集群 |
我的
Tailscale是通过自建的Headscale管理的,部署过程参考:部署 Headscale 和 DERP 以通过 Tailscale 实现无公网 IP 服务器之间的组网。
一共两条流量路径:
- 公网访问链路:浏览器 →
rancher.example.com(公网 DNS 解析到aliyun-server公网 IP) →Caddy→ Tailscale 私网 →rancher:8443→ Rancher。 - Tailscale 私网访问链路:K3s 节点 →
rancher.example.com(Headscale MagicDNS 解析到aliyun-server的 Tailscale IP) →Caddy→ Tailscale 私网 →rancher:8443→ Rancher。
两条路径最终都通过 Caddy 收口,好处是:
- 公网和私网使用同一个域名
rancher.example.com,所以 Rancher 的server-url只有一个值,不会出现登录后被跳转到不可访问地址的问题。 - TLS 证书只在
Caddy上签一次(Let’s Encrypt 自动签rancher.example.com),Rancher 容器只在内部 Tailscale 网络中跑,自签证书即可,无需为它单独管理证书。 rancher完全不暴露公网,安全性更高。
截止 2026/04/23,Rancher 的最新稳定版本为 v2.14.0,它能完整支持 K3s v1.34.x,因此本文以这个版本作为示例。
方案概述
- 使用 Docker 部署
Rancher服务端- 安装 Docker 环境
- 准备私有镜像仓库(用于解决境内拉取
rancher/*镜像困难的问题) - 配置
rancher的 Docker 默认镜像地址 - 启动
Rancher容器并持久化数据
- 在
aliyun-server上配置Caddy反向代理 - 首次登录并进行设置
- 设置管理员密码
- 将
server-url固定为公网域名 - 将
agent-tls-mode调整为System Store
- 通过 Tailscale 私网将外部 K3s 集群注册到 Rancher
- 在 Headscale 中下发自定义 DNS 记录
- 在 K3s 节点上验证 DNS 记录已下发
- 在 Rancher 中创建导入集群
- 在 K3s 集群中执行导入命令
操作步骤
一、使用 Docker 部署 Rancher 服务端
1、安装 Docker 环境
官方的 Docker 安装方式可以参考我的另一篇文章:Ubuntu 20.04 从官方源安装最新的 Docker
或是直接使用下面适配 Ubuntu/Debian 的命令快速安装:
sudo apt-get update -y
sudo apt-get install ca-certificates curl gnupg lsb-release -y
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update -y
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin -y
docker -v
Rancher 官方对于单节点部署推荐使用 Docker 的方式:Installing Rancher on a Single Node Using Docker
但要注意:Docker 单节点部署只适合做测试/家庭实验室,不适合用在生产环境。
2、准备私有镜像仓库
我这台 rancher 是在国内,直连 Docker Hub 拉 rancher/* 镜像非常痛苦。
更重要的是,Rancher 的 Docker 容器启动后*会在内部再起一个 K3s 集群来跑自己的组件(Fleet、rancher-webhook、shell 等等),这些组件的镜像同样来自 Docker Hub,如果一启动就卡在镜像拉取上会非常折腾。
因此我会把所有 rancher/* 系镜像全部从自建的 registry.example.com 拉取。
这个仓库是一个 Docker Registry v3 的 pull-through 缓存,部署方式参考:使用 Docker 启动 Registry 镜像加速服务并使用 Caddy 进行 TLS 证书管理
3、配置 rancher 的 Docker 默认镜像地址
为了让所有 docker pull rancher/xxx 都自动走 registry.example.com 的缓存,我们给本机 Docker daemon 加一个镜像加速地址:
sudo nano /etc/docker/daemon.json
{
"registry-mirrors": [
"https://registry.example.com"
]
}
保存后重启 Docker:
sudo systemctl daemon-reload
sudo systemctl restart docker
设置完之后手动拉一次 Rancher 镜像验证链路是否通了、顺便让第一层镜像提前预热:
docker pull rancher/rancher:v2.14.0
看到输出里出现 Pull complete 和 Status: Downloaded newer image for rancher/rancher:v2.14.0 就说明链路正常。
注意
registry-mirrors的作用仅限于宿主机 Docker。
Rancher 容器内部那一套 K3s(用的是独立的 containerd)不会读取宿主机/etc/docker/daemon.json,所以内嵌 K3s 拉取它自己的组件镜像时还是会去docker.io。
这也是下面docker run里仍然要显式写CATTLE_SYSTEM_DEFAULT_REGISTRY=registry.example.com的原因:把内嵌 K3s 的镜像前缀也一并改写掉。
4、启动 Rancher 容器并持久化数据
创建工作目录和持久化目录,防止之后升级 Rancher 时数据丢失:
mkdir -vp /opt/rancher/data # 持久化 Rancher 的 etcd 数据
mkdir -vp /opt/rancher/auditlog # 持久化 Rancher 的 API 审计日志
mkdir -vp /opt/rancher/etc # 放 containerd 的 registries.yaml
cd /opt/rancher
我们需要先准备好 registries.yaml,这一步非常关键!
Rancher 容器内部跑的 K3s 有它自己的
containerd,在创建每个 Pod 前会去拉取沙箱镜像(rancher/mirrored-pause:3.6),这个拉取发生在 Kubernetes 的抽象之下,不受CATTLE_SYSTEM_DEFAULT_REGISTRY控制,只认/etc/rancher/k3s/registries.yaml。
如果不配置它,Rancher 启动过程中会大量出现下面这种报错:FailedCreatePodSandBox … failed to pull image “rancher/mirrored-pause:3.6”: failed to resolve reference “docker.io/rancher/mirrored-pause:3.6”: failed to do request: … i/o timeout
nano /opt/rancher/etc/registries.yaml
mirrors:
"docker.io":
endpoint:
- "https://registry.example.com"
然后编写 docker-compose.yml:
nano docker-compose.yml
因为 Rancher 的 80/443 端口这里只会被 aliyun-server 上的 Caddy 通过 Tailscale 回源访问,所以端口具体使用什么值都可以。这里为了避免之后在这台机器上部署其他服务时冲突,我把它显式绑定到 8080/8443:
version: "3.8"
services:
rancher:
image: rancher/rancher:v2.14.0
container_name: rancher
restart: unless-stopped
privileged: true
ports:
- "8080:80"
- "8443:443"
volumes:
- /opt/rancher/data:/var/lib/rancher
- /opt/rancher/auditlog:/var/log/auditlog
# 把 registries.yaml 挂到 K3s 默认读取的路径,让内嵌 containerd
# 把 docker.io 的拉取(含 pause 沙箱镜像)转到私有仓库
- /opt/rancher/etc/registries.yaml:/etc/rancher/k3s/registries.yaml:ro
environment:
AUDIT_LEVEL: "1"
# 让 Rancher 所有内嵌 K3s 里跑的业务组件(Fleet、rancher-webhook、shell 等)
# 从私有仓库拉取镜像,不再尝试走 Docker Hub;注意不要带 http:// / https://
CATTLE_SYSTEM_DEFAULT_REGISTRY: registry.example.com
# 使用容器内自带的 Helm 系统 Chart(monitoring / logging 等),
# 不去 GitHub 上拉取——这对国内环境几乎是必须的
CATTLE_SYSTEM_CATALOG: bundled
参数说明:
privileged: true:Rancher 容器内部会起一个本地的 Kubernetes 集群,所以必须以特权模式运行。volumes里的前两条挂载:持久化 Rancher 的核心数据(含内嵌 K3s 的 etcd)和 API 审计日志,AUDIT_LEVEL=1打开审计。volumes的第三条是本节开头提到的registries.yaml,作用于内嵌 K3s 的 containerd,解决 sandbox/pause 这类基础镜像也必须走私有仓库的问题。CATTLE_SYSTEM_DEFAULT_REGISTRY/CATTLE_SYSTEM_CATALOG的作用见代码注释,官方文档:Docker Install Commands - Air Gap。image直接写rancher/rancher:v2.14.0,不用带前缀,因为上一步已经给本机 Docker daemon 配置了registry-mirrors,docker pull会透明命中registry.example.com的缓存。- 不在启动参数里指定域名(例如
--acme-domain),让 Rancher 使用自签名证书,由Caddy统一处理对外的 TLS。
最终的镜像拉取路径如下:
镜像类型 拉取者 被什么改写到私有镜像仓库 Rancher 本体容器 rancher/rancher:v2.14.0宿主机 Docker /etc/docker/daemon.json的registry-mirrorsPod 沙箱 rancher/mirrored-pause:3.6内嵌 K3s 的 containerd /etc/rancher/k3s/registries.yaml(volume 挂载)Rancher 业务组件(rancher-webhook、fleet、shell…) kubelet 按 Pod spec 拉 CATTLE_SYSTEM_DEFAULT_REGISTRY环境变量
启动服务:
docker compose up -d
启动之后可以验证下 containerd 是否真的吃到了 registries.yaml:
docker exec rancher cat /var/lib/rancher/k3s/agent/etc/containerd/config.toml | grep -A 3 'docker.io'
看到 endpoint = ["https://registry.example.com"] 就说明配置到位了。
等几分钟让它完成初始化,可以用下面的命令确认容器跑起来了:
docker compose ps
docker compose logs -f
日志里看到 Rancher startup complete 字样就说明服务端已经准备就绪。
二、在 aliyun-server 上配置 Caddy 反向代理
由于 rancher 本身没有公网 IP,我们将它自己生成的自签名证书直接交给跑在 aliyun-server 上的 Caddy 处理,由 Caddy 统一对外签发 Let’s Encrypt 的真实证书。
确保 aliyun-server 已经通过 Tailscale 加入了同一张私网(10.64.0.11),并且能够解析到 rancher.ts.net:
# 在 aliyun-server 上
tailscale status | grep rancher
ping -c 2 rancher.ts.net
在 aliyun-server 的 /etc/caddy/Caddyfile 中加入以下配置:
sudo nano /etc/caddy/Caddyfile
rancher.example.com {
# 通过 Tailscale 私网回源到 rancher
# 这里一定要使用 HTTPS
reverse_proxy https://rancher.ts.net:8443 {
transport http {
tls
tls_insecure_skip_verify
}
header_up Host {host}
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
}
}
保存后重载 Caddy:
sudo systemctl reload caddy
将 rancher.example.com 的公网 A 记录指向 aliyun-server 的公网 IP,访问 https://rancher.example.com 应该就能看到 Rancher 的欢迎页了。
三、首次登录并进行设置
1、设置管理员密码
首次访问时,Rancher 会要求你输入初始密码,直接去容器内取:
docker compose -f /opt/rancher/docker-compose.yml logs 2>&1 | grep "Bootstrap Password:"
如果看不到密码的话,也可以直接去 Secrets 里找:
docker exec -it rancher kubectl get secret --namespace cattle-system bootstrap-secret -o go-template='{{.data.bootstrapPassword|base64decode}}{{ "\n" }}'
把这个密码填入页面,按指引设置好新的管理员密码,然后登录。
2、将 server-url 固定为公网域名
这一步是整个部署最容易入坑的地方。
Rancher 的 server-url 决定了:
- 下游集群的
cattle-cluster-agent要连回来的地址; - 登录成功后前端会跳转的地址。
如果这个值填错了,你从公网访问登录之后,会被 302 到内网的 Tailscale 域名导致直接空白或超时。
登录后第一次进入界面时,Rancher 会弹窗询问 Server URL。
在这里直接填公网域名:
https://rancher.example.com
如果错过了这个弹窗,可以在
☰>Global Settings>Settings>server-url中修改。
为什么一定要填公网域名,而不是 Tailscale 域名?
因为我只有一个出口,而从公网访问 Rancher 时一定处于无法解析ts.net域名的环境下。
如果把server-url设成rancher.ts.net,公网登录后 Rancher 就会把我重定向到 Tailscale 域名而变成空白页。
反过来,我们完全可以通过在 K3s 节点上配置/etc/hosts或 Tailscale MagicDNS,让 K3s 集群把rancher.example.com解析到 Tailscale IP,这样两边访问同一个域名,但走不同的链路,就不会有跳转问题了。
3、将 agent-tls-mode 调整为 System Store
从 Rancher v2.9 起,agent-tls-mode 的默认值变成了 strict,即 cattle-cluster-agent 只信任 Rancher 内置 CA 签的证书。
因为我们这里的 TLS 是由前面 Caddy 的 Let’s Encrypt 证书托管的(对 Rancher 而言属于未知的公开 CA),所以需要把这个设置改成 system-store,让 agent 信任系统根证书库中的所有 CA:
进入 ☰ > Global Settings > Settings,找到 agent-tls-mode,点击 ⋮ > Edit Setting,将值改为 System Store:
官方说明:TLS Settings
如果不调这个参数,之后你导入的 K3s 集群里cattle-cluster-agentPod 会一直CrashLoopBackOff并抛x509: certificate signed by unknown authority或unable to read CA file。
四、通过 Tailscale 私网将 K3s 集群注册到 Rancher
到这里 Rancher 本身已经部署完成,剩下的就是把那台跑在另一处的 K3s v1.34.6+k3s1 集群注册进来。
1、在 Headscale 中下发自定义 DNS 记录
前面提到过,为了让 server-url 在两种访问路径下都保持一致,需要让 K3s 节点把 rancher.example.com 解析到 aliyun-server 的 Tailscale IP 而不是公网 IP,这样下游流量才会从 Tailscale 私网先到 Caddy,再由 Caddy 回源到 rancher。
你可能会想:那是不是让 DNS 直接解析到
rancher的 Tailscale IP 更简单?
可以但是我不建议,如果跳过Caddy直连 Rancher 容器的8443,K3s 的cattle-cluster-agent会拿到 Rancher 的自签名证书,除非你把Rancher CA手动灌进去,否则 TLS 链路就断了。
统一走Caddy才能继续复用 Let’s Encrypt 的真实证书,整体更干净。
由于我的 Tailscale 是通过自建的 Headscale 管理的,可以直接利用 Headscale 的 dns.extra_records 特性在服务端一次性下发自定义 DNS 记录,所有已经加入 Headscale 网络的客户端都会自动拿到这条记录,完全不需要在每台 K3s 节点上改 /etc/hosts。
先在 aliyun-server 上拿到它自己的 Tailscale IP:
tailscale ip -4
登录到 Headscale 服务端所在的主机,修改配置文件:
sudo nano /etc/headscale/config.yaml
在已有的 dns: 小节里加入 extra_records:
dns:
magic_dns: true
base_domain: ts.net
override_local_dns: true # 必须为 true,否则客户端会优先用本机 DNS 导致下面的记录被覆盖
nameservers:
global:
- 223.5.5.5
- 119.29.29.29
- 1.1.1.1
- 2400:3200::1
# 下面是新增的
extra_records:
# rancher.example.com -> aliyun-server(跑 Caddy 的那台)
- name: "rancher.example.com"
type: "A"
value: "10.64.0.11"
extra_records里的值会被Headscale通过控制面协议推送给所有已连接的客户端,由 Tailscale 客户端内置的 MagicDNS 进行应答,效果等价于给所有节点批量改 /etc/hosts,但由服务端集中管理。
需要注意的是extra_records仅支持A/AAAA,目前还不能直接写成rancher.ts.net这样的 MagicDNS 域名。
相关 Issue 和官方文档:juanfont/headscale#2508 [Feature] Extra DNS Records / API support CNAME、DNS - Headscale。
保存后重启 Headscale:
sudo systemctl restart headscale
sudo systemctl status headscale
2、在 K3s 节点上验证 DNS 记录已下发
登录到任意一台 K3s 节点,确认这条记录已经生效:
# 刷新 Tailscale 客户端的 netmap(一般会自动同步)
sudo tailscale set --accept-dns=true
# 解析测试
getent hosts rancher.example.com
ping -c 2 rancher.example.com
curl -I https://rancher.example.com
只要解析出来的 IP 是 aliyun-server 的 Tailscale IP(10.64.0.11),就说明路径正确。
如果解析不到或仍然是公网 IP,依次检查:
Headscale端override_local_dns: true是否已生效、magic_dns: true是否开启;- 客户端加入网络时是否带了
--accept-dns=true(我在 Headscale 那篇文章里提过这个参数);resolvectl status查看是否把100.100.100.100(Tailscale 的内置 DNS)列为了 Current DNS Server。如果你使用的是官方的 Tailscale,没有
Headscale的控制面,这里就需要改用 Tailscale 管理后台的 Split DNS / Global nameservers 来下发一个 override 记录,或者回退到在每个节点的/etc/hosts里手动写一条10.64.0.11 rancher.example.com。
3、在 Rancher 中创建导入集群
回到 Rancher UI,点击左上角 ☰ > Cluster Management,然后点击右上角的 Import Existing:
选择 Generic,填写集群名字,其他保持默认,点击 Create:
几秒后 Rancher 会生成两条注册命令:
# 标准方式(需要 server 证书是被系统信任的 CA 签的)
kubectl apply -f https://rancher.example.com/v3/import/xxxxxxxxxxxxxxxxxxxx.yaml
# 插入 insecure 的兜底方式
curl --insecure -sfL https://rancher.example.com/v3/import/xxxxxxxxxxxxxxxxxxxx.yaml | kubectl apply -f -
由于我们用的是 Let’s Encrypt 的真实证书,且已经把 agent-tls-mode 改成了 System Store,直接使用第一条 kubectl apply 命令即可。
4、在 K3s 集群中执行导入命令
SSH 到 k3s-server 节点,执行上面那条命令:
kubectl apply -f https://rancher.example.com/v3/import/xxxxxxxxxxxxxxxxxxxx.yaml
执行后过十几秒,检查 cattle-system 命名空间下的 Pod 是否都正常运行:
kubectl get pods -n cattle-system -o wide
正常情况下应该看到 cattle-cluster-agent-xxxxx 处于 Running 状态。
由于我们在第二步已经把
CATTLE_SYSTEM_DEFAULT_REGISTRY设置成了registry.example.com,Rancher 生成的这份导入 YAML 里rancher/rancher-agent的镜像地址就会自动变成registry.example.com/rancher/rancher-agent:...,K3s 节点不需要额外配置镜像加速就能直接拉下来。
想实时看镜像拉取进度(
rancher-agent压缩后约 300 MB,第一次拉通常要几分钟)可以在k3s-server上手动触发一次k3s ctr images pull:sudo k3s ctr images pull registry.example.com/rancher/rancher-agent:v2.14.0它直连 K3s 自带的
containerd,和kubelet共用同一个 socket、自动去重,不会重复下载也不会干扰正在进行的拉取:registry.example.com/rancher/rancher-agent:v2.14.0: resolved |+++++++++++++++++|
manifest-sha256:abcdef… |++++++++++++ | done
config-sha256:123456… |++++++++++++++++ | done
layer-sha256:aaa111… : downloading [====> ] 42.5 MiB/290.1 MiB
layer-sha256:bbb222… : downloading [================> ] 128.0 MiB/180.3 MiB
elapsed: 1m23s total: 170.5 M (2.1 MiB/s)
如果卡在
CrashLoopBackOff,用下面的命令看日志:kubectl logs -n cattle-system deploy/cattle-cluster-agent
回到 Rancher UI,几十秒后集群状态就会从 Pending 变成 Active,这时就能在 Rancher 里直接管理这套远端的 K3s 集群了:
结束。