单台 VPS 运行 24 个容器:中高级 DevOps 的高密度技术架构
一台服务器,6 GB 内存,24 个容器——稳定运行超过一年。这并非实验,而是支撑七个活跃项目的生产基础设施:一个教育科技机器人、反垃圾邮件服务、AI 后端、元搜索引擎、本地大模型推理以及两个实用工具服务。它们虽未直接产生收入,但对运营活动至关重要。目标并非单纯为了削减成本,而是一种有意识的权衡:以手动管理替代托管服务,以可预测的负载替代云弹性,以深度集成替代抽象层。
架构原则:隔离、可观测性、韧性
核心方法依赖于严格的网络隔离。每个项目都位于独立的 Docker 网络(bridge)中,消除了跨依赖并最小化了攻击面。仅使用共享 shared 网络用于系统服务(如监控或通用 Redis)。内部端口(9200, 8088, 9222)仅通过 127.0.0.1 访问;外部访问在 iptables 和 Nginx 配置层面被阻断。
韧性通过 docker-compose.yml 中的健康检查依赖来确保。例如,EdTech 项目中的 PHP 服务不会启动,直到远程数据库的 SSH 隧道通过 nc -z 127.0.0.1 3306 检查。这保证了应用程序不会尝试连接不可用的数据库,从而避免队列因错误而崩溃。
由于内存限制,可观测性构建时未使用 Prometheus/Grafana。取而代之的是一个每 15 分钟运行的 Bash 脚本:
- 检查公共端点的 HTTP 状态码(
/health,/webhook/advert) - 通过
docker ps --filter "health=unhealthy"识别不健康的容器 - 监控磁盘空间和交换空间使用情况
- 通过 curl 调用 Bot API 向 Telegram 聊天发送警报
这为单个管理员提供了最小但足够的可见性。
关键组件:Elasticsearch、无头 Chrome 和 SSH 隧道
三个组件定义了 VPS 的性能边界:
- Elasticsearch 8.12.2 消耗 1.47 GB 内存。如果在
deploy.resources.limits中没有显式的memory: 2g限制,JVM 开始交换邻近进程,导致不稳定。对于约 5 万文档的索引和日志记录,这在功能和负载之间提供了最佳平衡。像 Meilisearch 这样的替代方案需要迁移现有索引并修改客户端代码,在此阶段被评为高投入低回报的任务。
- Chrome Headless (zenika/alpine-chrome) 占用 636 MB 内存,尽管它每小时仅使用一次进行 JS 渲染抓取。通过
docker run --rm按需运行可以节省内存,但每次调用会增加 5–10 秒延迟。对于具有严格 SLA 的服务来说这是不可接受的;在这里,这是一个可接受的妥协。
- SSH 隧道 在带有
autossh的独立 Alpine 容器中实现。它们提供对共享主机上 MySQL 的安全访问,而无需在外部开放端口。配置包括ServerAliveInterval=30、AUTOSSH_GATETIME=0以及通过nc的健康检查。此解决方案适用于所有支持 SSH 的主机,并完全取代了付费托管数据库。
Nginx 和 PHP 优化:将延迟降低至毫秒级
Nginx 仅配置用于 Webhook 和 API——除 /health 外不处理任何静态文件。关键优化是将 Telegram Webhook 处理卸载到单独的 PHP 文件(webhook-advert.php),该文件绕过 Laravel 引导。这将响应时间从 80–120 毫秒减少到 15–25 毫秒,这对于来自 Telegram API 的大规模 Webhook 调用(最大超时 — 60 秒)至关重要。
配置还包括:
- 强制 HTTP → HTTPS 重定向
- 拒绝处理除
/health、/webhook/和/api/telegram/以外的任何路径 - 增加
fastcgi_read_timeout至 120 秒以处理长时间运行的任务 - 使用
realpath_root正确处理index.php
SSL 与安全:主机上的三种策略
SSL 终止采用混合方式组织:
- getssl + Let's Encrypt —— 针对需要端到端加密且不使用 Cloudflare 的域名
- Cloudflare Proxy —— 针对大多数域名:SSL 在边缘终止,流量以 HTTP 传输至 VPS,但受 IP 过滤保护(仅限 Cloudflare ASN)
- 手动更新 —— AI 后端的临时措施,目前正通过 Caddy 进行自动化
Cloudflare 设置包括强制 Always Use HTTPS、WAF 和 速率限制(每个 IP 每分钟 100 次请求),弥补了 VPS 侧缺乏 WAF 的不足。
核心要点
- 通过独立的 Docker 网络实施严格的网络隔离,防止级联故障并简化安全审计。
- docker-compose 中的健康检查依赖确保服务仅在依赖项就绪后才启动(例如,到数据库的 SSH 隧道)。
- Elasticsearch 需要硬性内存限制(2 GB);否则,JVM 会交换邻近容器,触发 OOM Killer。
- 如果启动延迟对业务逻辑至关重要,则在低频使用时保持 Chrome Headless 常驻模式是合理的。
- 在内存限制下,配合明确定义的 SLO,Bash 监控与 Telegram 警报是 Prometheus/Grafana 的可行替代方案。
— Editorial Team
暂无评论。