VPS 架构的自我进化:从宝塔到 Traefik + FrankenPHP

有些服务器,不折腾,却安稳。
有些重构,不惊天动地,却润物无声。

几年前买下这台 VPS 时,第一件事就是装宝塔面板。那时候觉得,管理网站就该有个可视化界面,点一点鼠标就能部署环境、创建站点、申请证书,多省心。

事实证明,宝塔确实省心——直到你开始需要「不省心」的东西。

写在前面

这篇文章记录的是我这台 VPS 从宝塔面板 + Nginx + MySQL 的传统架构,逐步迁移到 Docker + Traefik + FrankenPHP 的全过程。

所有操作均由 Hermes Agent(一个 AI 编程助手)通过 SSH 自动完成,我只负责描述需求,Agent 负责执行。某种意义上,这篇文章也是 AI 辅助运维的一份实践记录。


一、曾经的架构

最初的环境很标准:

宝塔面板(BT Panel)  →  管理界面
Nginx                 →  Web 服务器
MySQL 5.7             →  数据库
PHP 7.x               →  PHP 运行时

站点通过宝塔的「添加站点」功能创建,证书用宝塔的「SSL」面板申请,伪静态规则在宝塔里配置。

这个架构的好处是所见即所得,对于只有一个博客的轻量需求完全够用。但问题也逐渐显现:

  • 宝塔面板本身占用资源(约 200MB 常驻内存)
  • MySQL 对于 Typecho(SQLite 就够用)来说太重了
  • 多站点管理依赖宝塔的 Nginx 配置,修改起来不够灵活
  • 证书续期偶尔出问题,需要手动干预
  • 每次系统更新都要进面板操作,无法脚本化

二、为什么迁移

2.1 资源优化

VPS 只有 1GB 内存,宝塔 + MySQL + Nginx + PHP-FPM 跑下来,可用内存捉襟见肘。换成 Docker 后,每个服务各司其职,不需要的组件一个都不装。

2.2 配置即代码

宝塔的配置存在面板数据库里,导出迁移很麻烦。Docker Compose + Traefik 的配置全是文本文件,可以 Git 管理、可以 AI 修改、可以一键重建。

2.3 自动化运维

既然有了 Hermes Agent 这样的 AI 助手,架构就应该设计成「AI 可运维」的——所有操作通过 SSH 命令行完成,不需要任何图形界面。Traefik 的动态配置、Docker 的容器编排,天然适合这种模式。

三、迁移过程

3.1 第一步:Traefik 就位

Traefik 作为反向代理网关,接管所有 HTTP/HTTPS 流量。

# traefik/docker-compose.yml
services:
  traefik:
    image: traefik:v3.1
    container_name: traefik
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    networks:
      - traefik-net
    environment:
      - CF_DNS_API_TOKEN_FILE=/cloudflare.token
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./traefik.yml:/traefik.yml
      - ./config:/config
      - ./certs:/certs

核心配置亮点:

# 证书:Cloudflare DNS-01 挑战,通配符证书
certificatesResolvers:
  letsencrypt:
    acme:
      dnsChallenge:
        provider: cloudflare

这意味着 *.seekdoor.me 的泛域名证书自动续期,新增子域名无需手动申请证书。

3.2 第二步:博客搬进容器

Typecho 博客迁移到 FrankenPHP 容器。FrankenPHP 是一个基于 Caddy 的现代 PHP 应用服务器,内置 PHP 8.x,性能优于传统 Nginx + PHP-FPM。

# typecho/docker-compose.yml
services:
  app:
    image: typecho-franken:v1.3.0
    container_name: typecho-app
    expose:
      - "80"
    volumes:
      - ./app:/app
      - ./data/typecho:/data
    networks:
      - traefik-net
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.typecho.rule=Host(`blog.seekdoor.me`)"
      - "traefik.http.routers.typecho.entrypoints=websecure"
      - "traefik.http.routers.typecho.tls=true"
      - "traefik.http.routers.typecho.tls.certresolver=letsencrypt"

数据库从 MySQL 迁移到 SQLite——Typecho 原生支持,减少一个服务依赖,备份也变成了拷一个文件的事。

3.3 第三步:周边服务迁移

Tiny Tiny RSS(RSS 阅读器)和 Cockpit(服务器管理面板)也接入 Traefik:

# ttrss 路由配置
http:
  routers:
    ttrss-secure:
      rule: "Host(`ttrss.seekdoor.me`)"
      service: ttrss-service
      tls:
        certResolver: letsencrypt
  services:
    ttrss-service:
      loadBalancer:
        servers:
          - url: "http://ttrss:80"

Cockpit 不走 Docker,直接通过 Traefik 代理到宿主机端口:

services:
  cockpit-service:
    loadBalancer:
      servers:
        - url: "http://172.25.0.1:9090"

3.4 第四步:域名与流量接入 Cloudflare

所有域名通过 Cloudflare 托管,DNS 解析指向 VPS IP。Traefik 的 Cloudflare DNS-01 挑战自动为每个域名签发 Let's Encrypt 证书。

流量路径:

用户 → Cloudflare CDN → VPS:443 (Traefik) → 容器:80 (FrankenPHP/Nginx)

四、当前架构全景

┌─────────────────────────────────────────┐
│              Cloudflare                 │
│    (DNS + CDN + DDoS 防护)              │
└────────────────┬────────────────────────┘
                 │
                 ▼
┌─────────────────────────────────────────┐
│           Traefik v3.1                  │
│  反向代理 / SSL 终结 / 自动证书          │
│  端口: 80→443, 443→各自容器              │
│  证书: Let's Encrypt (DNS-01)           │
└──────┬──────────┬──────────┬────────────┘
       │          │          │
       ▼          ▼          ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Typecho  │ │  ttrss   │ │ Cockpit  │
│ Franken  │ │  PHP     │ │ (宿主机)  │
│  SQLite  │ │PostgreSQL│ │  9090    │
│ blog     │ │  RSS     │ │ 管理面板  │
└──────────┘ └──────────┘ └──────────┘
       │
       ▼
┌──────────┐
│ Initial-M│
│ 主题 v3.2│
│ +自定义   │
└──────────┘

五、AI 运维的实践

这次架构迁移的整个过程——从分析现状、规划方案、编写配置、部署验证——全部由 Hermes Agent 通过 SSH 自动完成。

几个有意思的细节:

5.1 路径陷阱

第一次部署主题时,Agent 把文件放到了 Nginx 的网站根目录,但博客实际跑在 Docker 容器里,数据卷挂载在另一个路径。首页能正常访问(因为 Traefik 代理到了容器),但后台「外观」页面找不到新主题。

排查过程:

  1. 查看 Nginx 配置发现 root /www/server/stop——非典型路径,怀疑是反向代理
  2. 检查 Docker 运行状态,确认博客在容器中
  3. 查看 docker-compose.yml 找到数据卷映射关系
  4. 重新部署到正确的数据卷路径 → 问题解决

这就是 AI 运维的优势:Agent 不会"觉得丢脸",它会一步步排查,直到找到根因。

5.2 主题定制的迁移

博客原本使用 Initial 主题并做了 7 项个性化定制(Pangu.js 自动间距、波浪动画页脚、归档下拉菜单、Bangumi 挂件等)。升级到 Initial-M 时,Agent 逐项分析了每个定制在新主题中的对应关系,保留了 Initial-M 的新功能,叠加了用户的个性化改动。

5.3 故障恢复

所有配置都有备份:

  • 旧主题备份在 themes/initial-backup-YYYYMMDD/
  • 旧 Nginx 配置保留在宝塔备份中
  • 数据库每日自动备份
  • 即使重建整个 Docker 环境,docker-compose up -d 就能恢复所有服务

六、迁移收益

指标迁移前迁移后
常驻内存~400MB(宝塔+Nginx+MySQL+PHP)~150MB(Traefik+FrankenPHP+ttrss)
证书管理手动续期/宝塔自动全自动(Traefik + DNS-01)
新增站点宝塔界面操作添加一行 docker-compose labels
备份策略宝塔整站备份SQLite 文件 + 配置文件 Git 管理
运维方式图形界面SSH + AI Agent

七、写在最后

这次迁移最大的收获不是技术指标的提升,而是一种运维模式的改变。

当架构变成「配置即代码」,当 AI 助手可以 SSH 进服务器执行任务,运维就不再是「登录面板 → 找按钮 → 点鼠标」的流程,而是「描述需求 → AI 执行 → 验证结果」的对话。

这大概就是未来运维的样子——不需要记住每个配置项的位置,只需要知道自己想要什么。

致谢

  • Traefik — 优秀的反向代理和证书管理器
  • FrankenPHP — 现代化的 PHP 应用服务器
  • Initial-M — Typecho 主题的现代化 fork
  • Hermes Agent — 帮我完成所有操作的 AI 助手

最后,一台 VPS 的生命力,不在于装了多新的软件,而在于你是否愿意持续维护它。而有了 AI 助手的帮忙,维护这件事,变得前所未有的轻松。