1. 项目概述从“无云天空”到分布式应用部署的实践最近在折腾一个挺有意思的开源项目叫cloudless-sky直译过来就是“无云天空”。这个名字乍一听有点哲学意味但它的核心目标非常务实让应用的部署和运行像在晴朗无云的天空下一样清晰、简单、可控摆脱对特定云厂商的深度绑定和复杂编排的依赖。作为一个在运维和开发领域摸爬滚打了十多年的老手我见过太多项目从“上云真香”到“云锁头疼”的转变。cloudless-sky的出现恰好切中了当下很多技术团队的一个痛点如何在享受云原生技术红利如容器化、微服务的同时保持架构的简洁性和对基础设施的掌控力简单来说cloudless-sky不是一个全新的容器编排平台它更像是一个轻量级的粘合剂和最佳实践集合。它基于成熟的开源组件如 Docker, Traefik, PostgreSQL 等通过一套声明式的配置和自动化脚本帮助你在自己的虚拟机、物理机甚至多个云厂商的实例上快速搭建起一个功能完备、易于管理的应用部署环境。它不试图替代 Kubernetes而是为那些觉得 K8s 过于重型、学习曲线陡峭但又需要跨节点服务发现、负载均衡、集中日志等能力的中小型项目或团队提供了一个优雅的折中方案。这个项目适合谁呢我认为有几类朋友会特别感兴趣一是初创团队或独立开发者资源有限但需要快速构建稳定、可扩展的后端服务二是正在做技术选型对全托管云服务成本敏感或对数据主权有要求的团队三是像我一样喜欢“知其然也知其所以然”希望通过亲手搭建来深入理解现代应用部署每一环的工程师。接下来我就结合自己实际的搭建和踩坑经历把这个项目的核心设计、实操细节和那些文档里不会写的经验掰开揉碎了和大家聊聊。2. 核心架构与设计哲学解析2.1 为何选择“无云”而非“多云”在深入细节之前有必要先厘清cloudless-sky的设计哲学。“无云”不是反对云计算而是反对“盲目的云原生”和“供应商锁定”。它的核心思想是“基础设施即代码”和“显式优于隐式”。很多云平台提供了高度抽象的黑盒服务虽然开箱即用但一旦出现问题排查就像在迷宫里摸索。cloudless-sky则把整个栈的组件都明明白白地摆出来反向代理用 Traefik数据库用 PostgreSQL服务发现和配置可能集成 Consul 或 Etcd一切都通过清晰的配置文件进行声明。这样做的好处显而易见。首先可移植性极强。你的应用部署描述不依赖于任何云厂商的特定 API 或服务只要目标机器能跑 Docker就能一键部署。今天你在 A 云明天可以无缝迁移到 B 云甚至自己的机房。其次学习成本可控。你学习和调试的都是标准的、有广泛社区支持的开源软件技能不会因为某个云服务下架而贬值。最后也是最重要的深度可控。从网络路由规则到数据库连接池参数你都能根据实际需要进行精细调优没有黑魔法。2.2 核心组件与职责划分cloudless-sky的架构通常围绕几个核心组件展开我们可以把它想象成一个微缩版的、手动管理的“平台即服务”。容器运行时与环境毫无疑问Docker是基石。cloudless-sky假设所有应用和服务都被容器化。它通过 Docker Compose 或类似的编排工具来定义和管理多容器应用的生命周期。选择 Docker 而非更底层的 containerd是因为 Compose 的声明式语法对于描述服务关系非常友好生态也成熟。流量入口与路由这是对外暴露服务的关键。Traefik是这里的主角。它是一个现代化的反向代理和负载均衡器最大的特点是能自动发现 Docker 容器并生成路由规则。当你在 Compose 文件中为服务添加特定的标签后Traefik 就会自动将service.yourdomain.com的流量路由到对应的容器并自动管理 SSL 证书通常与 Let‘s Encrypt 集成。这省去了手动配置 Nginx 的繁琐。状态管理与数据持久化对于有状态服务如数据库、缓存cloudless-sky强调通过Docker 卷或绑定挂载将数据持久化在宿主机上。它通常会提供 PostgreSQL、Redis 等服务的 Compose 配置示例。关键在于这些数据卷的路径和备份策略是明确在配置中定义的你可以轻松地将其纳入自己的备份体系。服务发现与配置在跨多台主机部署时服务间如何相互发现简单的方案是使用 Docker 的 overlay 网络配合 Compose 的 service name 作为主机名。更复杂的项目可能会集成Consul或Etcd作为服务注册中心并配合Consul Template或类似工具动态更新配置。cloudless-sky的价值在于它提供了这套集成的范例。监控与日志一个可运维的系统离不开可观测性。方案通常包括使用Prometheus收集 Docker 引擎和自定义应用的指标用Grafana进行可视化用Loki或ELK Stack的轻量版集中收集和管理容器日志。cloudless-sky的配置会帮你把这些组件串联起来形成基本的监控面板。注意cloudless-sky本身不是一个安装包它更像一个“配方”或“样板间”。你拿到的是一个包含docker-compose.yml、环境变量文件、配置模板和部署脚本的代码库。真正的价值在于这个预先配置好的、相互协作的组件集合。3. 从零开始环境准备与初始化部署3.1 基础设施准备与关键决策假设我们有两台 Ubuntu 22.04 LTS 的虚拟机IP 分别为192.168.1.10和192.168.1.20。我们将192.168.1.10作为管理节点运行 Traefik、监控栈等基础设施组件192.168.1.20作为工作节点运行业务应用。这是经典的主从架构清晰简单。首先在所有节点上安装 Docker 和 Docker Compose。这里我推荐使用官方脚本安装 Docker用 pip 安装 Compose以确保版本较新。# 在每台机器上执行 # 安装 Docker curl -fsSL https://get.docker.com -o get-docker.sh sudo sh get-docker.sh sudo usermod -aG docker $USER # 将当前用户加入docker组需重新登录生效 # 安装 Docker Compose (以v2为例) sudo apt-get update sudo apt-get install -y python3-pip sudo pip3 install docker-compose # 验证安装 docker --version docker-compose --version接下来是一个关键决策网络规划。我们需要创建一个跨主机的 Docker overlay 网络让不同机器上的容器能直接通过容器名通信。这需要在 Docker 引擎中启用 Swarm 模式即使我们不使用 Swarm 的调度功能。# 在管理节点 (192.168.1.10) 上初始化 Swarm sudo docker swarm init --advertise-addr 192.168.1.10 # 命令会输出一个类似 docker swarm join --token SWMTKN-1-xxx 192.168.1.10:2377 的令牌然后在工作节点上执行上述输出的 join 命令将其加入 Swarm 集群。完成后我们就可以在管理节点上创建 overlay 网络了。# 在管理节点创建 overlay 网络命名为 sky-net sudo docker network create --driver overlay --attachable sky-net--attachable参数至关重要它允许我们后续使用docker-compose启动的独立容器也能连接到这个 Swarm 网络这是混合使用docker run/compose和 Swarm 网络的关键。3.2 核心基础设施部署Traefik 与监控栈现在我们在管理节点上部署基础设施。首先部署 Traefik它是我们所有流量的总入口。创建一个目录如~/cloudless-sky/infra在其中创建traefik/docker-compose.ymlversion: 3.8 services: traefik: image: traefik:v3.0 container_name: traefik restart: unless-stopped networks: - sky-net ports: - 80:80 # HTTP - 443:443 # HTTPS - 8080:8080 # Traefik Dashboard (建议仅内网访问) volumes: - /var/run/docker.sock:/var/run/docker.sock:ro # 关键让Traefik监听Docker事件 - ./traefik.yml:/etc/traefik/traefik.yml:ro - ./acme.json:/acme.json # 存储SSL证书 - ./configs/:/etc/traefik/configs/:ro # 静态配置文件目录 command: - --api.dashboardtrue - --providers.dockertrue - --providers.docker.exposedbydefaultfalse # 安全只有显式标签的服务才暴露 - --providers.docker.networksky-net # 指定监听网络 - --providers.file.directory/etc/traefik/configs - --providers.file.watchtrue - --entrypoints.web.address:80 - --entrypoints.websecure.address:443 labels: - traefik.enabletrue # 保护Dashboard只允许特定IP访问按需配置 - traefik.http.routers.dashboard.ruleHost(traefik.local) - traefik.http.routers.dashboard.serviceapiinternal - traefik.http.routers.dashboard.middlewaresauth - traefik.http.middlewares.auth.ipwhitelist.sourcerange192.168.1.0/24 networks: sky-net: external: true同时创建traefik.yml静态配置文件用于设置 SSL 证书等全局配置。这里以 Let‘s Encrypt 为例配置 HTTP-01 挑战方式。你需要有一个真实的域名并确保其 DNS 解析到了你的公网 IP或通过端口转发。# traefik.yml api: dashboard: true entryPoints: web: address: :80 http: redirections: entryPoint: to: websecure scheme: https permanent: true websecure: address: :443 certificatesResolvers: myresolver: acme: email: your-emailexample.com # 替换为你的邮箱 storage: /acme.json httpChallenge: entryPoint: web然后启动 Traefiksudo docker-compose up -d。现在任何接入sky-net网络、并带有正确标签的容器都会被 Traefik 自动发现并配置路由。接下来在同一个infra目录下我们可以用类似的 Compose 文件部署 Prometheus、Grafana 和 Loki。关键点在于所有这些服务的 Compose 文件都需要声明使用外部网络sky-net并且为 Traefik 添加路由标签。例如给 Grafana 服务加上标签labels: - traefik.enabletrue - traefik.http.routers.grafana.ruleHost(grafana.yourdomain.com) - traefik.http.routers.grafana.tlstrue - traefik.http.routers.grafana.tls.certresolvermyresolver - traefik.http.services.grafana.loadbalancer.server.port3000这样通过https://grafana.yourdomain.com就能安全访问 Grafana 面板了。4. 业务应用部署实战以Web API为例4.1 编写应用 Dockerfile 与 Compose 文件假设我们有一个简单的 Python Flask API 应用代码结构如下myapp/ ├── app.py ├── requirements.txt └── DockerfileDockerfile内容示例FROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . CMD [gunicorn, -w, 4, -b, 0.0.0.0:8000, app:app]docker-compose.yml是cloudless-sky理念的核心体现。我们将它放在工作节点 (192.168.1.20) 上。version: 3.8 services: myapp-api: build: ./myapp container_name: myapp-api restart: unless-stopped networks: - sky-net # 连接到同一个overlay网络 environment: - DATABASE_URLpostgresql://user:passpostgres-sky-net:5432/mydb - REDIS_URLredis://redis-sky-net:6379/0 depends_on: - postgres - redis labels: # 关键Traefik路由规则 - traefik.enabletrue - traefik.http.routers.myapp-api.ruleHost(api.yourdomain.com) - traefik.http.routers.myapp-api.tlstrue - traefik.http.routers.myapp-api.tls.certresolvermyresolver - traefik.http.services.myapp-api.loadbalancer.server.port8000 - traefik.docker.networksky-net # 指定Traefik使用哪个网络与后端通信 postgres: image: postgres:15-alpine container_name: postgres-db restart: unless-stopped networks: - sky-net environment: POSTGRES_DB: mydb POSTGRES_USER: user POSTGRES_PASSWORD: strongpassword volumes: - postgres_data:/var/lib/postgresql/data redis: image: redis:7-alpine container_name: redis-cache restart: unless-stopped networks: - sky-net command: redis-server --appendonly yes volumes: - redis_data:/data networks: sky-net: external: true # 使用已存在的overlay网络 volumes: postgres_data: redis_data:这个 Compose 文件清晰地定义了一个三层应用Web API、数据库、缓存。它们通过sky-net网络互联API 容器通过服务名postgres和redis访问依赖服务。Traefik 的标签使得外部可以通过https://api.yourdomain.com访问到 Flask 应用。4.2 跨节点部署与网络连通性验证在工作节点上我们只需要确保 Docker 守护进程已加入 Swarm并且能连接到sky-net网络。然后直接在该节点运行docker-compose up -d。这里有一个非常重要的点docker-compose默认会为项目创建一个独立的桥接网络。因为我们声明了使用外部网络sky-net并指定了external: true所以 Compose 不会创建新网络而是让所有服务都接入这个已有的 overlay 网络。这样位于管理节点的 Traefik 才能发现并路由到工作节点上的myapp-api容器。如何验证连通性在管理节点运行sudo docker network inspect sky-net查看Containers部分应该能看到来自两个节点的容器。在工作节点的myapp-api容器内尝试ping postgres或curl redis:6379应该能通。最后从外部浏览器或使用curl访问https://api.yourdomain.com应该能得到 Flask 应用的响应。实操心得在跨主机部署时防火墙是头号杀手。务必确保所有节点之间在 overlay 网络所需的端口默认为 TCP/UDP 7946 用于成员发现TCP 4789 用于 VXLAN 数据平面TCP 2377 用于 Swarm 管理上是互通的。在云主机上安全组规则需要放行这些端口以及你的应用端口804435432等。5. 进阶配置服务发现、配置管理与CI/CD集成5.1 集成Consul实现动态服务发现与配置当应用规模增长服务实例动态伸缩时硬编码的服务名如postgres可能不够用。我们可以集成Consul来实现更灵活的服务发现和配置管理。我们可以在管理节点额外部署一个 Consul 集群3节点以保障高可用。Consul 的 Compose 配置略复杂核心是让每个需要注册的服务运行一个Consul Agent Sidecar或者使用registrator这样的工具自动注册。更现代的做法是让应用本身通过 Consul 客户端 API 进行注册。以 Flask 应用为例可以在启动时通过环境变量CONSUL_HTTP_ADDR指向 Consul 服务器调用其 API 注册自身信息服务名、IP、端口、健康检查端点。然后Traefik 可以配置 Consul 作为后端服务发现的提供者。在 Traefik 的静态配置 (traefik.yml) 中添加providers: consul: endpoints: - http://consul-server1:8500 - http://consul-server2:8500 prefix: traefik/config # Consul KV存储中的路径这样服务的路由规则不仅可以定义在 Docker 标签中还可以动态地从 Consul KV 存储中读取和更新实现了配置与代码的进一步分离。5.2 构建自动化部署流水线cloudless-sky环境搭建好后我们需要一个自动化的流程来部署应用。这里结合 GitLab CI 给出一个简单范例。在项目根目录创建.gitlab-ci.ymlstages: - build - deploy variables: DOCKER_HOST: ssh://user192.168.1.20 # 指向工作节点 COMPOSE_PROJECT_NAME: myapp build-image: stage: build image: docker:latest services: - docker:dind script: - docker build -t myapp-api:${CI_COMMIT_SHA} ./myapp - docker tag myapp-api:${CI_COMMIT_SHA} myapp-api:latest only: - main deploy-to-worker: stage: deploy image: alpine:latest before_script: - apk add --no-cache openssh-client docker-compose - eval $(ssh-agent -s) - echo $SSH_PRIVATE_KEY | ssh-add - - mkdir -p ~/.ssh echo $SSH_KNOWN_HOSTS ~/.ssh/known_hosts script: # 将最新的docker-compose.yml和配置传到工作节点 - scp docker-compose.yml user192.168.1.20:/opt/myapp/ - ssh user192.168.1.20 cd /opt/myapp docker-compose pull - ssh user192.168.1.20 cd /opt/myapp docker-compose up -d --build myapp-api # 清理旧镜像 - ssh user192.168.1.20 docker image prune -f only: - main dependencies: - build-image这个流水线做了几件事在主分支提交时构建新的 Docker 镜像通过 SSH 连接到工作节点更新 Compose 文件并重新部署 API 服务。这里使用了DOCKER_HOST环境变量让docker-compose命令在本地执行但实际作用于远程工作节点的 Docker 守护进程。SSH_PRIVATE_KEY和SSH_KNOWN_HOSTS需要配置为 GitLab CI 的 Secret Variables。6. 运维、监控与故障排查实录6.1 基础监控与告警设置部署好 Prometheus 和 Grafana 后首要任务是导入一些有用的 Dashboard。对于 Docker 和主机监控推荐使用docker-compose部署cadvisor来收集容器指标并在每个节点部署node_exporter收集主机指标。在 Prometheus 的prometheus.yml配置文件中添加抓取目标scrape_configs: - job_name: node static_configs: - targets: [192.168.1.10:9100, 192.168.1.20:9100] - job_name: cadvisor static_configs: - targets: [192.168.1.10:8080, 192.168.1.20:8080] - job_name: traefik static_configs: - targets: [traefik:8080] # 通过服务名访问需在同一网络在 Grafana 中可以导入 ID 为193Docker 监控和8919Node Exporter Full的社区仪表盘快速获得可视化视图。对于告警可以在 Prometheus 中配置 Alertmanager对容器内存使用率持续超过90%、服务健康检查连续失败等关键指标设置规则并通过 Webhook 通知到钉钉、Slack 或邮件。6.2 常见问题与排查技巧在实际运行中肯定会遇到各种问题。下面是我踩过的一些坑和解决方法问题1Traefik 仪表盘或服务无法访问但容器运行正常。排查思路检查网络首先确认 Traefik 容器和应用容器是否在同一个 Docker 网络 (sky-net) 中。运行docker network inspect sky-net查看已连接的容器列表。检查标签确认应用容器的docker-compose.yml中 Traefik 相关标签书写正确特别是traefik.http.services.xxx.loadbalancer.server.port必须与容器内应用监听的端口一致。检查路由规则访问 Traefik 的 API 端点默认http://traefik-ip:8080/api/http/routers查看生成的路由规则。如果看不到你的服务说明标签未被正确识别。查看 Traefik 日志docker logs traefik查看有无错误信息常见的有网络权限问题、证书申请失败等。解决技巧养成先看 Traefik 日志和 API 的习惯。对于复杂的路由规则可以先用HostRegexp或PathPrefix等简单规则测试逐步增加复杂度。问题2跨主机容器间网络不通无法通过服务名解析。排查思路确认 Swarm 集群状态在管理节点运行docker node ls确认所有节点状态都是Ready。检查 overlay 网络docker network inspect sky-net查看Peers信息确认所有节点都已成功加入网络对等体。检查防火墙这是最常见的原因。确保所有节点的防火墙如 UFW, firewalld或云平台安全组放行了 Swarm 和 overlay 网络所需的端口2377/tcp, 7946/tcpudp, 4789/udp。测试 DNS 解析在一个容器内运行nslookup tasks.service_name。在 Swarm 模式下service_name会解析到所有运行该服务任务的容器的 VIP。如果解析失败可能是 Swarm 内置的 DNS 有问题。解决技巧使用docker exec进入容器内部进行网络测试 (ping,curl,nslookup) 是最直接的。如果怀疑是防火墙问题可以临时关闭防火墙测试但生产环境务必配置精确的规则。问题3Docker Compose 部署时依赖服务如数据库未就绪主应用启动失败。排查思路depends_on只控制容器的启动和停止顺序并不等待服务“健康”。如果应用在数据库初始化完成前就启动并尝试连接就会失败。解决方案应用层重试在应用代码中添加数据库连接重试逻辑。使用健康检查在docker-compose.yml中为数据库服务定义healthcheck然后主应用使用condition: service_healthy。services: postgres: image: postgres healthcheck: test: [CMD-SHELL, pg_isready -U user] interval: 10s timeout: 5s retries: 5 myapp-api: depends_on: postgres: condition: service_healthy使用启动脚本在应用容器的启动命令前加一个等待脚本例如使用wait-for-it或dockerize工具等待依赖服务的端口可连接。问题4磁盘空间被 Docker 镜像、容器和卷占满。预防与清理定期清理可以设置一个定时任务Cron Job运行清理命令。# 删除所有已停止的容器 docker container prune -f # 删除所有未被使用的镜像谨慎会删除悬空镜像 docker image prune -f # 删除所有未被使用的卷非常谨慎确保数据已备份 docker volume prune -f # 删除构建缓存 docker builder prune -f日志轮转Docker 容器的日志默认是 json-file 驱动不设限制。在/etc/docker/daemon.json中配置全局日志驱动和大小限制。{ log-driver: json-file, log-opts: { max-size: 10m, max-file: 3 } }监控磁盘在 Grafana 中监控节点的磁盘使用率并设置告警。问题5如何优雅地更新应用而不中断服务蓝绿部署策略利用 Traefik 的加权轮询功能。部署一套新的应用栈v2将其接入 Traefik但初始权重设为0。通过健康检查确认 v2 运行正常后逐步将流量权重从 v1 切换到 v2例如 90/10 - 50/50 - 0/100。这可以通过更新 Traefik 的服务配置存储在 Consul KV 或文件动态实现。滚动更新如果使用docker serviceSwarm 模式而不是docker-compose可以直接使用docker service update --image myapp:new-version进行滚动更新。对于docker-compose可以编写脚本先启动新版本容器等待健康检查通过后再停止旧版本容器。核心是确保始终有可用的容器处理请求Traefik 会自动将失败的容器从负载均衡池中剔除。经过这样一套从架构设计到具体部署再到运维监控的完整流程走下来一个清晰、可控、易于扩展的“无云”应用环境就搭建起来了。它没有云平台控制台那么花哨但每一个组件、每一条配置都在你的掌控之中。这种“亲手搭建”带来的理解深度和灵活性是单纯使用托管服务无法比拟的。当然这也意味着你需要承担更多的运维责任从安全补丁到备份恢复都需要自己规划。这其中的权衡正是技术选型的艺术所在。