编程 Uncloud v0.22 部署笔记:没有控制面,用 WireGuard mesh 和 CRDT SQLite 管 Docker 集群

2026-11-06 21:32:10

Uncloud v0.22 部署笔记:没有控制面,用 WireGuard mesh 和 CRDT SQLite 管 Docker 集群

Uncloud 是一个轻量集群与容器编排工具,目标是把云 VM、独立服务器、裸金属上的 Docker 主机连成一个集群,部署和管理 Web 应用。它会在主机之间建立 WireGuard mesh 网络,并提供服务发现、负载均衡、HTTPS ingress 和一套类 Docker CLI。

和 Kubernetes 的区别主要在控制面:Uncloud 没有中心控制面,也不需要维护 quorum。每台机器通过 peer-to-peer 通信持有一份同步的集群状态,部分机器离线时,集群操作仍然可用。和 Docker Swarm 相比,它不依赖 manager/worker 角色和 Raft。和 Kamal 相比,Kamal 更偏向通过 SSH 做单机或少量主机的部署,而 Uncloud 把多机网络、服务发现、ingress、状态同步都放进了集群层。

它的编排模型是 imperative,不是 declarative:CLI 直接下发操作,不做状态收敛。状态层用 corrosion(基于 CRDT 的分布式 SQLite)在机器间复制,网络层用 WireGuard mesh,每台机器分到一个 10.210.X.0/24 子网给容器使用。

文档在 uncloud.run/docs。

快速开始

安装 CLI:

brew install psviderski/tap/uncloud
# 或
curl -fsS https://get.uncloud.run/install.sh | sh

初始化第一台机器:

uc machine init root@your-server-ip

部署服务:

uc run -p app.example.com:8000/https image/my-app

-p 的格式是 域名:容器端口/https,Caddy 会为这个域名申请 Let's Encrypt 证书并反向代理到容器端口。

然后创建 DNS A 记录,把 app.example.com 指向入口机器的 IP。查看和删除服务:

uc ls
uc rm my-app-name

卸载:

uncloud-uninstall

uc machine init 会通过 SSH 登录目标机,安装 Docker 和 uncloudd,生成 WireGuard 密钥对,分配 10.210.0.0/24 子网,配置 corrosion,创建连接到 WireGuard 接口的 Docker bridge 网络,并把连接信息写到 ~/.config/uncloud/config.yaml。这台机器成为集群入口。

再加一台机器时,新机器会分到下一个子网(比如 10.210.1.0/24),注册进集群状态,与第一台机器交换 WireGuard 密钥并建立隧道。它只需要和一台现有机器建立 WireGuard 连接,其他机器通过共享状态获知。新机器启动 uncloud-corrosion 容器后,成为备用入口;一台机器离线,仍然可以通过另一台机器执行 uc 命令。

架构

machine init 做了什么

  1. CLI SSH 登录目标机,安装 Docker 和 uncloudd daemon,由 systemd 管理。
  2. 生成 WireGuard 密钥对,为机器及其容器分配专用子网 10.210.0.0/24,配置 uncloudd。后续通信通过 uncloudd 的 gRPC API over SSH 进行。
  3. 启动 corrosion 容器,用 CRDT 分布式 SQLite 共享集群状态。systemd unit 文件位于 /etc/systemd/system/uncloud.socket 和 uncloud.service。v0.21 起 API socket 从 /run/uncloud/uncloud.sock 移到 /run/uncloud/api/uncloud.sock,并启用了 systemd socket activation。
  4. 创建连接到 WireGuard 接口的 Docker bridge 网络。
  5. 机器成为集群入口,连接信息存入 ~/.config/uncloud/config.yaml。

请求怎么落到容器

执行 uc run 时,CLI 选一台机器,uncloudd 用 grpc-proxy 把请求转发到目标机器。容器从 bridge 网络拿到集群内唯一 IP,范围是 10.210.X.2-254。

每台机器上以 global 模式运行 Caddy 反向代理,监听集群状态并更新配置。v0.22 默认启用基于 caddy-uncloud 的集群存储,TLS 证书和 ACME challenge token 在多台机器间共享。

镜像直推

unregistry 集成让镜像可以直接构建并推送到机器,不需要外部 registry,并且只传输缺失的 layer。对没有自建 Harbor 或云 registry 的小集群,这一步省掉了不少中转。

状态与 DNS

corrosion 负责在机器间复制集群状态。每台机器维护一份同步副本,通过 P2P 通信,不依赖中心节点。内置 DNS 服务把服务名解析到容器 IP。公共服务的 *.xxxxxx.uncld.dev 记录可以由 Uncloud DNS 自动管理。

v0.22.0 的三个实用变更

1. uc service update 原地改 CPU/内存

以前改资源限制通常要重建容器。v0.22 起,Uncloud 会直接更新运行中容器的 CPU/memory limits,uc deploy 和 uc service update 都支持:

uc service update web --cpu 0.5 --memory 512m
uc service update web --label-add env=prod --label-rm owner

新增 --memory-reservation 参数。

label 分两类:labels 设置到容器上的 Docker label,deploy.labels 是存在服务上的 label,不应用到容器。服务 label 原地更新,不重启;容器 label 需要重建容器才生效。

2. uc image rm

从所有或选定机器删除镜像,会先给出计划并要求确认:

uc image rm myapp:old
uc image rm myapp
uc image rm 'myapp:1.*' 'otherapp:*' -m machine1,machine2 --yes

被运行中或已停止容器使用的镜像不能删除。uc image ls 可以列出所有 tag 和 digest。

3. 机器的 m.internal DNS 名

容器可以通过机器名或 ID 在内部 m.internal zone 访问机器:

dig +short machine-1.m.internal
# 10.210.0.1

查询 m.internal 会返回所有机器的 IP。

这一版还有一些偏向稳定性的改进:daemon 对容器变更通知做 debounce,避免繁忙集群在每次健康检查时让每台机器重算 Caddy 和 DNS 配置,之前在小机器上可能卡死(#438);stop-first 部署改为先创建新容器并拉取镜像,再停止旧容器(#420);uc logs 的 --since/--until 支持天数,例如 uc logs web --since 2d;uc context show 会遵循 UNCLOUD_CONTEXT。Bug 修复包括 pre-deploy hook 环境里的 secret:// 解析、--since 时显示全部匹配日志、日志过滤使用本地时区,以及 NO_COLOR 支持。

升级时的两个注意事项

uc caddy deploy 换镜像会重签证书

v0.22 默认使用带集群存储的 Uncloud Caddy 镜像 ghcr.io/unlabs-dev/caddy-uncloud,包含 caddy-l4 和 cloudflare DNS 模块,用于 wildcard 证书。

升级 Uncloud 本身不会动正在运行的 Caddy。但下一次执行 uc caddy deploy 时,会切换到新镜像和新存储。Caddy 不会在存储后端之间复制证书,所以除非先迁移证书,否则会重新签发。

另外,uc caddy deploy 要求所有机器都运行 0.22.0 的 daemon,先升级 daemon 再执行。该命令会记住 --image、--caddyfile、--machine 的值;传空值可以重置。

机器名必须是合法 DNS label

机器名只能使用小写字母、数字、连字符,最长 63 个字符。rr 和 nearest 是保留名,名字也不能看起来像机器 ID。

升级顺序:先升级本地 CLI,再升级每台机器上的 daemon。如果从 v0.20.x 或更早版本升级,需要先按 v0.21.0 的升级说明处理 systemd unit 变更。

取舍

Uncloud 还在 pre-1.0,偶尔会有破坏性变更。核心的 build/push/deploy 流程相对稳定,运行时依赖 Docker、Caddy、WireGuard 和 Corrosion。

没有自动回滚。README 写的是失败自动回滚“coming soon”,目前有滚动更新和零停机部署,但部署失败后的回滚要手动处理。

imperative 模型意味着没有状态收敛和自愈。CLI 下发什么就执行什么,没有一个控制器持续把实际状态拉回期望状态。好处是心智模型简单、排障直接;代价是不能指望它像 Kubernetes 那样自动纠偏。

无控制面也是双刃剑:没有 quorum 和单点故障,但状态依赖 CRDT 的最终一致性。单机场景用 Docker Compose 或 Kamal 更直接;强一致、复杂调度、强合规审计的场景不一定合适。网络层面还要求机器之间 WireGuard 可达,入口机器要有可写 DNS 的公网 IP。

项目文档:https://uncloud.run/docs

复制全文 生成海报 docker 部署 运维 Go

推荐文章

程序员茄子在线接单