1. 项目概述一个轻量级的容器化应用部署与管理工具最近在折腾容器化部署时发现了一个挺有意思的开源项目叫fleet。它不是那个已经停止维护的 CoreOS Fleet而是由开发者oguzhnatly在 GitHub 上维护的一个同名项目。简单来说你可以把它理解为一个“轻量级的 Docker Compose 简易编排器”专门为中小规模、追求简洁高效的容器化场景设计。如果你和我一样手头有几个到几十个容器应用需要管理比如个人博客、自建网盘、家庭媒体服务器或者小团队的内部工具链但又觉得上 Kubernetes 太重用 Docker Compose 在多台机器上同步和管理又有点麻烦那么这个fleet项目就值得你花时间了解一下。它的核心目标很明确用尽可能简单的配置和命令帮你把一组相关的 Docker 容器也就是一个“舰队”部署到一台或多台主机上并保持它们的状态和配置同步。它不追求大而全的功能而是聚焦在“够用、好用、易维护”这几个点上对于开发者个人或者小团队来说这种工具往往能极大地提升效率减少运维负担。2. 核心设计思路与架构拆解2.1 为什么需要另一个容器编排工具在 Docker 生态里我们已经有了docker run、docker-compose和 Kubernetes 这一系列从简到繁的工具链。docker run适合单容器快速启动docker-compose完美解决了单机多容器应用的编排问题而 K8s 则是大规模、生产级容器编排的事实标准。那么fleet的生存空间在哪里我理解它的定位恰恰是填补了docker-compose和 K8s 之间的一个空白地带。docker-compose很好但它本质上是一个“单机”工具。它的配置文件docker-compose.yml和命令docker-compose up都是针对当前这台机器的。当你有多台机器想部署同一个应用栈或者想实现简单的服务发现和负载均衡时docker-compose就力不从心了。你需要手动同步配置文件到每台机器分别执行命令管理起来非常分散。而 K8s 呢它功能强大能解决所有问题但学习曲线陡峭组件繁多对资源也有一定要求。对于只有几台服务器、运行十几个容器的小型场景来说部署和维护一个 K8s 集群哪怕是轻量级的如 K3s的复杂度可能已经超过了业务应用本身的复杂度。这就好比用高射炮打蚊子威力过剩操作还麻烦。fleet的设计哲学就是“简单至上”。它不引入 Pod、Service、Deployment 这些复杂概念而是基于开发者已经熟悉的docker-compose.yml格式进行扩展。你可以认为它是在docker-compose的语法基础上增加了“多主机部署”和“配置中心化”的能力。你只需要写一份熟悉的 Compose 文件然后通过fleet的命令就能把整个应用栈推送到一个由多台机器组成的“舰队”中。fleet负责在后台帮你把该启动的容器在指定的机器上启动起来并监控它们的状态。2.2 Fleet 的核心组件与工作流程fleet的架构非常清晰主要包含两个部分控制平面和代理节点。控制平面通常运行在一台你指定的“管理机”上。它包含一个 API 服务器和一个存储后端默认使用 SQLite也支持 PostgreSQL。你的所有操作比如fleet apply部署应用都是通过命令行工具与这个 API 服务器交互。控制平面是整个舰队的大脑它存储了所有应用的定义、配置以及每个应用应该部署到哪些节点的策略。代理节点则是运行在你每一台工作服务器上的守护进程。这个代理会定期向控制平面“心跳”汇报自己的状态比如主机名、IP、资源情况并接收来自控制平面的指令“请在你的机器上运行这个容器”。代理节点负责与本地的 Docker Daemon 通信执行具体的docker run或docker-compose up等操作。整个工作流程可以概括为你在本地编写一个docker-compose.yml文件可能还会附带一个fleet.yml来定义部署策略比如哪个服务跑在哪类机器上。通过fleetCLI 工具连接到控制平面执行fleet apply my-app。控制平面接收你的应用定义并根据策略计算出每个服务应该部署在哪些节点上。控制平面将具体的任务指令下发给对应的代理节点。代理节点接收到指令拉取镜像创建容器网络和卷最终启动容器。代理节点持续向控制平面报告容器状态运行中、停止、异常。这个模型和 K8s 的 Master-Node 架构在思想上类似但实现上要轻量得多。它没有 etcd 这样复杂的分布式存储调度策略也相对简单直接。对于大多数中小型应用这种简单性反而是优势。注意fleet项目目前处于活跃开发阶段并非一个成熟度极高的生产级工具。这意味着它可能更适合用于开发、测试环境或个人项目。用于核心生产业务前需要做好充分的测试和评估。3. 从零开始搭建与配置 Fleet 环境3.1 基础环境准备假设我们有三台 Ubuntu 22.04 的服务器IP 分别为manager.example.com(192.168.1.10)我们将在此运行控制平面。worker-01.example.com(192.168.1.11)工作节点1。worker-02.example.com(192.168.1.12)工作节点2。首先在所有三台机器上我们需要安装 Docker 和 Docker Compose。这是fleet运行的基础。# 更新包索引并安装依赖 sudo apt-get update sudo apt-get install -y ca-certificates curl gnupg lsb-release # 添加 Docker 官方 GPG 密钥 sudo mkdir -p /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg # 设置 Docker 仓库 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 # 安装 Docker Engine sudo apt-get update sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin # 验证安装 sudo docker run hello-world安装 Docker Compose 插件后可以直接使用docker compose命令注意是空格不是横杠。3.2 部署 Fleet 控制平面在管理机 (manager.example.com) 上我们部署控制平面。fleet官方推荐使用 Docker 来运行控制平面这很方便。# 在管理机上创建一个工作目录 mkdir -p ~/fleet-control-plane cd ~/fleet-control-plane # 创建一个 docker-compose.yml 文件来定义控制平面服务 cat docker-compose.yml EOF version: 3.8 services: fleet-server: image: ghcr.io/oguzhnatly/fleet:latest command: server ports: - 8080:8080 # API 服务器端口 - 9090:9090 # 可选指标端点端口 environment: - DATABASE_URLsqlite:///data/fleet.db?moderwc - FLEET_SERVER_ADDR:8080 - FLEET_AGENT_ADDR:9090 volumes: - ./data:/data # 持久化存储数据库和配置 - /var/run/docker.sock:/var/run/docker.sock # 挂载 Docker 套接字允许 fleet 控制本机 Docker可选用于在管理机上也运行容器 restart: unless-stopped EOF # 启动控制平面 docker compose up -d执行后控制平面的 API 服务器就在本机的 8080 端口运行了。你可以通过curl http://localhost:8080/health来检查它是否健康。数据SQLite 数据库会持久化在./data目录下。关键配置解析DATABASE_URL这里使用了 SQLite简单。对于更可靠的需求可以换成postgresql://user:passwordhost:port/dbname。FLEET_SERVER_ADDRAPI 服务器监听地址。FLEET_AGENT_ADDR代理与控制平面通信的地址用于内部状态同步和指令下发。挂载 Docker 套接字这赋予了fleet控制本机 Docker 的权限。如果你不希望控制平面节点也运行工作负载可以移除这个挂载。3.3 在工作节点上部署 Fleet 代理接下来在每台工作节点 (worker-01,worker-02) 上我们需要部署fleet代理。首先我们需要从管理机获取一个“加入令牌”。这个令牌用于代理向控制平面认证。我们需要在管理机上生成它假设控制平面已运行。# 在管理机上使用 fleet CLI 生成令牌。 # 我们需要先下载 fleet 的 CLI 工具。 # 去 GitHub Release 页面找到适合你系统的版本例如 wget https://github.com/oguzhnatly/fleet/releases/download/v0.1.0/fleet_0.1.0_linux_amd64.tar.gz tar -xzf fleet_0.1.0_linux_amd64.tar.gz sudo mv fleet /usr/local/bin/ # 配置 CLI 指向我们的控制平面 export FLEET_SERVERhttp://192.168.1.10:8080 # 生成一个代理加入令牌有效期为 1 小时 fleet token create --role agent --ttl 1h命令会输出一个长长的 JWT 令牌字符串复制下来。然后在工作节点上我们同样创建 Compose 文件来运行代理# 在工作节点上 mkdir -p ~/fleet-agent cd ~/fleet-agent # 将上面生成的令牌填入 YOUR_TOKEN_HERE cat docker-compose.yml EOF version: 3.8 services: fleet-agent: image: ghcr.io/oguzhnatly/fleet:latest command: agent --server-urlhttp://192.168.1.10:8080 --tokenYOUR_TOKEN_HERE environment: - FLEET_AGENT_NAMEworker-01 # 这里修改为当前节点的主机名如 worker-01, worker-02 volumes: - /var/run/docker.sock:/var/run/docker.sock # 必须挂载代理需要操作本地 Docker restart: unless-stopped EOF # 启动代理 docker compose up -d注意事项FLEET_AGENT_NAME建议设置为节点的主机名或一个唯一标识符这样在控制平面里便于区分。--server-url必须指向控制平面的地址确保网络可达。令牌安全生成的令牌具有时效性且应妥善保管。在生产环境中可以考虑使用更长的有效期并通过安全的通道分发给工作节点。防火墙确保管理机的 8080 端口对所有工作节点开放并且工作节点能访问到。3.4 验证集群状态代理启动后等待几十秒让它们完成注册和心跳。然后在管理机上使用 CLI 查看节点状态# 在管理机上 export FLEET_SERVERhttp://localhost:8080 fleet node list如果一切正常你应该能看到类似下面的输出列出了所有已注册的工作节点及其状态应该是ONLINEID NAME STATUS AGE abc... worker-01 ONLINE 1m def... worker-02 ONLINE 1m至此一个最小化的fleet集群就搭建完成了。控制平面在管理机两个代理节点在工作机它们已经建立了连接并准备接收部署任务。4. 编写与部署第一个 Fleet 应用4.1 理解 Fleet 应用定义文件fleet的核心应用定义基于 Docker Compose 格式。这意味着你几乎可以复用现有的docker-compose.yml文件。但为了支持多机部署fleet引入了一个可选的fleet.yml文件或者在你的 Compose 文件中使用x-fleet扩展字段来定义部署策略。让我们部署一个经典的 Web 应用栈一个 Nginx 前端和一个 Redis 缓存。假设我们想让 Nginx 运行在两个工作节点上实现简单的高可用而 Redis 只运行在一个节点上作为单点缓存。首先创建项目目录和文件mkdir -p ~/my-web-app cd ~/my-web-app编写docker-compose.yml:version: 3.8 services: nginx: image: nginx:alpine ports: - 80:80 volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro depends_on: - redis healthcheck: test: [CMD, curl, -f, http://localhost] interval: 30s timeout: 10s retries: 3 redis: image: redis:alpine command: redis-server --appendonly yes volumes: - redis_data:/data healthcheck: test: [CMD, redis-cli, ping] interval: 30s timeout: 10s retries: 3 volumes: redis_data:这个文件定义了服务本身和标准的 Docker Compose 没有区别。编写fleet.yml:# fleet.yml namespace: production # 可选用于隔离不同环境的应用 targets: nginx-target: # 这个部署策略名为 nginx-target对应 docker-compose.yml 里的 nginx 服务 service: nginx # 选择器匹配所有带有标签 roleweb 的节点 selectors: - label: roleweb # 部署配置在两个符合条件的节点上各运行一个实例 deployment: replicas: 2 strategy: spread # 尽可能分散到不同节点 redis-target: service: redis selectors: - label: roledb # 匹配带有标签 roledb 的节点 deployment: replicas: 1 # 只运行一个实例 strategy: randomfleet.yml是关键。它告诉fleetnginx服务应该部署到所有标有roleweb的节点上并且要运行 2 个副本尽量分散开。redis服务部署到标有roledb的节点上只运行 1 个副本。4.2 给节点打标签我们的工作节点目前还没有标签。我们需要给它们打上对应的标签这样部署策略才能生效。标签可以在代理启动时通过环境变量设置也可以通过 CLI 后期添加。这里我们用 CLI 动态添加# 在管理机上为 worker-01 添加 roleweb 标签 fleet node label add worker-01 roleweb # 为 worker-02 添加 roleweb 和 roledb 标签 fleet node label add worker-02 roleweb fleet node label add worker-02 roledb现在worker-01只有web角色worker-02同时有web和db角色。根据策略nginx需要 2 个副本在roleweb的节点上分散部署。符合条件的节点有worker-01和worker-02正好各部署一个。redis需要 1 个副本在roledb的节点上。符合条件的只有worker-02所以它将部署在worker-02上。4.3 部署应用一切就绪开始部署。在管理机的项目目录 (~/my-web-app) 下执行fleet apply my-web-appfleet apply命令会做以下几件事读取当前目录下的docker-compose.yml和fleet.yml如果存在。将应用定义包括镜像、配置、部署策略打包并发送到控制平面。控制平面根据策略和节点标签计算出调度计划。控制平面将任务下发给对应的代理节点。代理节点执行docker pull拉取镜像然后根据 Compose 定义启动容器。你可以通过以下命令查看部署状态# 查看应用列表 fleet app list # 查看特定应用的详细状态 fleet app status my-web-app # 查看应用下所有服务的实例容器状态 fleet service instances my-web-app在fleet service instances的输出中你应该能看到类似信息清晰地显示每个服务实例运行在哪个节点上状态如何。这比手动到每台机器上执行docker ps要直观得多。4.4 更新与回滚应用假设我们要将 Nginx 的镜像从alpine版本升级到stable版本。只需修改docker-compose.yml中nginx服务的镜像标签然后再次运行fleet apply# docker-compose.yml 中修改 services: nginx: image: nginx:stable # 从 alpine 改为 stable # ... 其他配置不变fleet apply my-web-appfleet会识别出配置的差异并执行滚动更新。它会先在新副本上启动新版本的容器健康检查通过后再停止旧版本的容器。这个过程对于前端无状态服务来说非常平滑。如果需要回滚你可以使用fleet app history my-web-app查看之前的版本然后使用fleet apply --version 旧版本号 my-web-app来回滚到指定版本。这得益于控制平面存储了每次应用变更的历史记录。5. 深入核心功能与高级配置5.1 配置管理与敏感信息处理在实际项目中我们不可能把数据库密码、API密钥等敏感信息硬编码在docker-compose.yml里。fleet支持通过“配置”对象来管理这些信息。你可以在fleet.yml中定义配置然后在 Compose 文件中通过环境变量引用。配置值在控制平面加密存储下发到节点时才解密。定义配置(fleet.yml):namespace: production configs: db-password: value: MySuperSecretPassword123! # 实际使用时建议通过 fleet config create 命令传入避免明文写在文件中。 secret: true # 标记为敏感信息 targets: redis-target: service: redis selectors: - label: roledb deployment: replicas: 1 configs: - name: db-password # 将此配置绑定到该部署目标在 Compose 文件中引用(docker-compose.yml):services: redis: image: redis:alpine command: redis-server --requirepass $${REDIS_PASSWORD} # 使用双美元符号引用 fleet 配置变量 environment: - REDIS_PASSWORD${db-password} # 环境变量映射更安全的做法是使用 CLI 创建配置echo -n MySuperSecretPassword123! | fleet config create db-password --stdin --secret这样密码就不会出现在任何版本控制的文件中。然后在fleet.yml的configs部分只需声明db-password这个配置项的存在而不需要写value。5.2 健康检查与自愈能力我们在之前的 Compose 文件中已经定义了healthcheck。fleet会利用 Docker 的健康检查机制。当代理节点检测到某个容器健康检查失败时它会将状态报告给控制平面。控制平面可以根据策略决定是否要重新调度该实例。例如对于replicas: 2的无状态服务如果一个实例失败fleet可以尝试在另一个符合条件的节点上重新启动一个实例以维持所需的副本数。这提供了基本的自愈能力。你可以在fleet.yml的deployment部分配置更详细的策略比如restart_policy重启策略和update更新策略等不过当前版本的fleet对这些高级策略的支持还在演进中。5.3 网络与存储的考量网络默认情况下fleet部署的容器其网络范围是单个主机。也就是说通过 Compose 文件定义的默认网络只在同一台主机上的容器之间共享。如果nginx和redis被调度到不同主机那么nginx容器内通过服务名redis将无法解析到另一个主机上的redis容器。为了解决跨主机容器通信通常有几种方案使用主机网络模式(network_mode: host)让容器直接使用宿主机的网络栈。简单但牺牲了隔离性端口容易冲突。使用覆盖网络如 Docker Swarm 的 overlay 网络。fleet目前没有内置的覆盖网络驱动需要你预先在集群所有节点上创建好相同的 overlay 网络然后在 Compose 文件中指定使用这个外部网络。通过主机IP和暴露的端口通信这是最直接的方式。在 Compose 中为需要跨主机访问的服务固定主机端口然后在其他服务的环境变量中配置对方的主机IP和端口。这种方式需要你提前知道或能发现节点的IP。对于简单的应用第三种方式往往更可控。你可以在fleet.yml中通过节点标签选择器结合一些脚本动态地将服务部署的节点IP注入到其他服务的环境变量中但这需要一些额外的编排逻辑。存储对于有状态服务如 Redis、数据库数据持久化是关键。在fleet多机环境中你需要确保容器无论被调度到哪个节点都能访问到其持久化数据。这通常意味着要使用网络存储如 NFS、Ceph、云提供商提供的块存储等并将这些远程卷挂载到每个可能运行该容器的节点上。在 Compose 文件中你需要定义volumes并指定正确的驱动和配置。例如使用一个简单的 NFS 卷volumes: redis_data: driver: local driver_opts: type: nfs o: addrnfs-server.example.com,rw,nolock,soft,timeo600 device: :/path/to/nfs/share/redis这要求所有工作节点都安装了 NFS 客户端。fleet本身不提供分布式存储解决方案你需要根据基础设施自行搭建。6. 实战经验、常见问题与排查技巧6.1 实操心得与注意事项标签策略是核心fleet的调度完全依赖于节点标签。设计一套清晰、一致的标签体系至关重要。例如除了roleweb、roledb还可以有envprod、zoneus-east、storagessd等。这能让你精细地控制每个服务的部署位置。先规划后部署在fleet apply之前先用fleet plan命令。这个命令会模拟部署过程告诉你应用将会被如何调度哪些配置会生效而不会真正执行。这是一个非常好的安全网能避免许多因配置错误导致的意外。利用命名空间隔离环境namespace在fleet.yml中是个非常有用的概念。你可以为开发、测试、生产环境设置不同的命名空间。这样同一个控制平面可以管理多套环境而应用之间互不干扰。CLI 命令可以通过--namespace参数指定操作哪个环境。镜像拉取策略在 Compose 文件中可以考虑设置image: myapp:latest的拉取策略为always特别是你在使用 CI/CD 自动构建并推送新镜像时。这能确保节点总是拉取最新的镜像。但要注意对于稳定版本最好使用具体的版本标签如myapp:v1.2.3以避免不可预期的更新。日志集中管理fleet本身不提供集群级别的日志聚合。当容器运行在多台主机上时查看日志变得困难。务必在项目初期就考虑好日志方案。可以将所有容器的日志驱动配置为json-file或journald然后使用Fluentd、Loki或ELK等工具进行收集和集中展示。6.2 常见问题与解决方案速查表问题现象可能原因排查步骤与解决方案fleet apply失败提示连接控制平面错误1. 控制平面服务未运行。2. 网络防火墙阻止访问。3.FLEET_SERVER环境变量或--server参数未设置或错误。1. 在管理机执行docker compose logs fleet-server查看控制平面日志。2. 从工作机使用curl http://manager-ip:8080/health测试连通性。3. 检查 CLI 命令是否显式指定了--server或正确设置了环境变量。代理节点状态为OFFLINE1. 代理进程未运行或崩溃。2. 代理无法连接到控制平面网络/防火墙。3. 加入令牌过期或无效。1. 在工作节点检查fleet-agent容器状态docker ps | grep fleet-agent。2. 查看代理日志docker compose logs fleet-agent。3. 在管理机使用fleet token list查看令牌状态必要时创建新令牌并更新代理配置。应用部署后服务实例状态一直为PENDING或FAILED1. 没有节点满足部署策略的标签选择器。2. 节点资源CPU/内存不足。3. Docker 镜像拉取失败网络问题或镜像不存在。4. 容器启动命令本身有错误。1. 执行fleet node list确认节点标签是否正确。2. 执行fleet service instances app-name查看具体失败原因。3. 登录到被调度的节点直接运行docker logs container-id查看容器启动日志。4. 检查 Compose 文件中的配置特别是卷挂载路径、环境变量等。跨主机容器无法通过服务名通信默认的 Docker Compose 网络是单主机范围的。1.推荐改为通过主机IP和固定端口通信。在服务配置中固定主机端口ports: - 主机端口:容器端口并将主机IP通过环境变量或配置传递给依赖的服务。2.高级在所有节点上创建相同的 Docker overlay 网络并在 Compose 中指定networks使用该外部网络。配置Config中的变量在容器中未生效1. 配置未正确定义或未绑定到对应的部署目标。2. Compose 文件中引用变量的语法错误。3. 配置值包含特殊字符未正确处理。1. 使用fleet config list和fleet config inspect config-name确认配置存在且内容正确。2. 确认 Compose 文件中引用的是$${VAR_NAME}双美元符号用于fleet配置变量替换。3. 对于敏感配置确保在创建时使用了--secret标志并在 Compose 中正确映射为环境变量。更新应用后旧版本容器未停止更新策略或健康检查配置可能导致新旧容器短暂共存。1. 这是预期行为的一部分为了实现零停机更新。新容器通过健康检查后旧容器会被停止。2. 如果旧容器长时间未停止检查新容器的健康检查是否一直未通过。3. 可以手动清理在对应节点上执行docker ps -a找到旧容器并用docker rm -f删除。6.3 性能调优与监控建议控制平面数据库对于稍大规模的使用节点数 10 应用数 50强烈建议将默认的 SQLite 数据库切换到 PostgreSQL。SQLite 在并发写入时可能成为瓶颈。修改控制平面 Compose 文件中的DATABASE_URL环境变量即可。代理资源占用fleet代理本身非常轻量通常只占用少量内存和 CPU。但在节点非常多或应用变更极其频繁时可以关注代理的日志输出看是否有异常。确保工作节点有足够的资源运行业务容器。集成监控fleet控制平面暴露了 Prometheus 格式的指标端点默认在:9090/metrics。你可以配置 Prometheus 来抓取这些指标监控集群中应用和节点的数量、状态变化频率等。结合 Grafana 可以绘制出有用的仪表盘。备份定期备份控制平面的数据目录如果使用 SQLite就是./data目录如果使用 PostgreSQL则备份数据库。这是恢复集群状态的关键。经过一段时间的实践我认为fleet非常适合作为从 Docker Compose 单机编排迈向多机、轻量级集群管理的“下一站”。它用最小的概念增量解决了多机部署的核心痛点。当然它还在快速发展中一些高级特性如复杂的滚动更新策略、基于资源的调度、服务网格集成等可能还不完善或缺失。但对于追求简洁、可控又需要超越单机 Compose 能力的中小团队和个人项目来说它是一个非常值得尝试和贡献的优秀工具。它的设计清晰地反映了维护者“解决实际问题而非创造新概念”的务实态度这也是开源软件最吸引人的地方之一。