最早开始折腾服务器是在 2017 年,实在想不起来是什么原因了,我接触到了 AppNode 这个东西,从这里我走上了折腾的不归路。五年后,我的服务器从「容器暴露端口 + Caddy 反代」的模式全面转向「服务发现」的模式,和最开始的那个 AppNode 已经大大不同了。这过程中的摸索,回想起来还是感触颇深。

1.0:AppNode

最早听说的就是「Linux 服务器流行用 CentOS」,于是在最早买的美国的 Bandwagon VPS 上装了 CentOS(当然主要用途不是搭网站,而是 emmmmm……)。

AppNode 官网截图

想不起来是怎么接触到 AppNode 的。这真的是一个非常优秀的产品。

「LNMP」指的是 Linux + Nginx + MySQL + PHP,据说是当时最为流行的服务器架构。简单来说,大概示意图是这样的(灵魂画师上线了(这个海豚真难画 😵‍💫)):

LNMP 架构示意图

AppNode 所有功能完全免费,之前付费版和免费版唯一的区别是免费版限制 3 个网站,付费版无限制。某次更新之后,免费版的网站数量限制增加到了 10 个,基本上就是无限制了。

AppNode 还集成了一大堆功能,所有功能都有对应的「管理面板」的组件,可视化控制,简直是神仙设计。
(和 AppNode 类似的还有一个「宝塔面板」(有个很 NB 的域名:bt.cn),虽然功能似乎更加强大,不过显然有更多收费功能和其他限制,似乎主打「提供服务」。个人认为对于个人服务器运维而言不如 AppNode)

可惜 AppNode 很早就停更了。也由于很久没更新,时不时就有一些奇怪的 bug。这种过于「黑箱」的封装式设计,虽然利于生产环境的快速部署,但是显然是不利于我们的开发和学习研究的。所以我就去寻找了 AppNode 更「透明化」的替代品。

1.5:LNMP.org

「LNMP 一键安装包」这种东西其实是不少的,但是功能全面、持续更新的似乎不多。LNMP.org 是我找到的比较满意的选择。

LNMP.org 的官网截图

不仅可选安装 LNMPA(Apache)、PureFTP 等等,还有 Let's Encrypt 自动获取证书的脚本(并会自动写入 crontab 自动续签),以及数据库备份、修复权限等等各种脚本。

所有功能都是以 Shell 脚本的形式实现的,足够轻量,不会像 AppNode 那样感觉是一个「黑箱」(虽然使用起来也没有 AppNode 直观),甚至可以非常容易地阅读源码,自己动手对脚本进行一些小修改。

纯命令行操作,不提供图形界面。其实对于各种功能熟悉了之后,会感觉 AppNode 的图形界面挺累赘的。真正专业的运维不可能在一个网页里点来点去吧 =_=

此外,这个安装包的所有程序都是编译安装,全部安装完大概需要一小时以上。这带来的是更强的兼容性,不像 AppNode 那样只兼容 CentOS 6/7,这个脚本几乎兼容各种 Linux 发行版。

从 2009 年发布以来,开发者每年都会更新,最近几年的频率稳定在「一年两更」:每年 1 月 8 日发布新版测试版,6 月 1 日发布正式版。完全免费开源(因为只是一套 Shell 脚本),没有任何功能限制。实属良心!!

LNMP.org 是我使用时间最长的一套运维软件了,陪我度过了大概两年时光。

然而随着折腾越来越深入,我发现 LNMP 已经不是最流行和适用的环境,各种不同的软件,有 Nodejs 的,有 Python 的,有 Go 的……都需要不同的运行环境……于是,轮到 Docker 出场了。

2.0:Docker + Caddy

An Ocean of Docker

第一次「被迫」用 Docker 大概是安装 Plausible 还是 Overleaf,只提供了 Docker-compose 方式的安装。我也不知道之前为什么自己这么抗拒 Docker,可能是不知道这东西有多强大。所以会用了之后简直打开了新世界的大门。

现在看来,更大的背景还是:在探索和部署越来越多的服务之后,发现 LNMP 只能是主流的架构「之一」,有诸多例如 Nodejs、Python 的软件服务涌现出来,由于环境、依赖等等的不同,手动部署根本不现实(特别是这个 Nodejs,就没几次安装啥东西不出错的)。

最开始体验了一会 Portainer,这是一个可视化的 Docker 管理面板。但是最后还是选择了直接用 Docker 命令行。给我的感觉就好像 Portainer 是一个类似 AppNode 的东西,「专业人士」是不需要这种东西的 😎

Caddy instead of Nginx

至于 Caddy,可以说是一个 Nginx 的替代品。她简洁优雅的配置曾经深深震撼了我。特别是反向代理,只需要 Caddyfile 中几行代码:

test.skywt.cn {
    reverse_proxy 127.0.0.1:8080
}

Caddy 会自动申请 Let's Encrypt 证书,自动启用 HTTPS 并且默认能够达到 A 级的安全性,无需任何额外配置。当然如果要当作 Web 服务器来用,Caddy 也完全可以替代 Nginx,可以用更简洁的方式实现 Nginx 的所有功能。据说性能是略微逊色于 Nginx 的,但是与其带来的便利性相比,非常值得。

Build up Firewall

为了方便访问文件和网络,Caddy 我没有用 Docker 部署。这阶段我的部署方案是每个容器开一个端口,Caddy 去反向代理到这个端口。但是这样实际上会有潜在的安全性问题。于是我安装了 FirewallD(RedHat 做的防火墙,其实是一个 iptables 的前端)。不过后来才听说了有个东西叫做 UFW(Uncomplicated Firewall,Ubuntu 的亲儿子),似乎也是个很好的选择。

Take control:Restic backup

从这里开始,我的服务器不局限于部署博客、网站,而是利用 Docker 部署了各种各样的服务,从 bitWarden 密码管理到 Outline 在线文档,Overleaf 用来写 LaTeX,Tiny Tiny RSS 用来订阅 RSS,还有 Nextcloud、Code-server……在平常使用各种 SaaS 平台的时候我有时候都会去找找有没有开源的 Self-host 方案,然后尝试部署到自己的服务器上(Docker 的使用大大降低了这一步的难度)。

像这样掌握自己数据的感觉很好。某次看到一个说法:在如今时代,我们都是「数据无产者」。我们以数据的形式生产了多种多样的劳动成果和智慧,最终数据都不在我们手中,而在各种商业公司的服务器里。

那么如何做到「数据在自己的手中」呢?要得到这种踏实的感觉,我第一时间想到的就是本地备份:将服务器上的数据每天定期备份到我家里闲置的 2T 移动硬盘里。于是我用树莓派又搭建了个小服务器,用 frp 做了内网穿透,每天在服务器上用 Restic 自动备份 /data 目录(是 Docker 所有可持久化数据挂载的目录)。这样,自己手中的硬盘的数据和服务器上的数据最多只有 24h 的时间差。数据的确可以是现实生活中我的东西了。

此外我还在树莓派上搭建了 MinIO,用于 Nextcloud 上的文件存储,这样我的 Nextcloud 网盘虽然由服务器提供服务(其实只运行了 Nextcloud-fpm,Web 服务器使用 Caddy,相比带 Apache 的镜像运行速度会更快),但是数据却存在本地的硬盘里。想想半年前还傻傻地尝试在树莓派上搭建整个 Nextcloud(得到奇慢的运行速度),感觉自己还是有进步的呢。

2.5:Docker-compose

容器多了,管理也是难事。在了解 Compose 之前,我的原始做法是在笔记本里开辟一个文档,记录各台服务器运行的各个容器的 Docker 启动命令(也就是 docker run 啥啥啥的)。渐渐就感觉每次手动复制、粘贴、回车的感觉非常不优雅。之后是将启动脚本迁移到服务器上,专门放个文件夹,每个容器都有个 .sh 文件来记录启动命令。但是在管理网络等等方面,还是感觉不优雅。

于是轮到 Docker-compose 出场了。把启动脚本写成 docker-compose.yml,然后在目录下 docker-compose up -d 就会创建网络、创建容器、后台运行,docker-compose down 则会停止、删除容器,删除网络。不仅不需要自己管理网络(对于「启动容器时加入多个网络」,Docker run 命令就是无法做到的了……),更让人眼前一亮的是这种「停用即删除」的逻辑很符合真正的「容器无状态」的理念,感觉干净、优雅。所以我将服务器上的所有容器启动脚本都 Docker-compose 化了。

2.7:Traefik / Caddy-docker-proxy

Traefik as Gateway

随着服务器上运行的服务越来越多,运维的过程中也涌现了一个很大的问题:没有「服务发现」。每次启动一个新的服务都要去修改和 reload Caddyfile 来反代,搞得 Caddyfile 很长很混乱,非常不优雅。

解决方案自然是有的。我发现了这个叫做 Traefik 的东西。

通过在容器的 docker-compose.yml 中指定一些 labels,容器可以被 Traefik 探测到,并可以通过调整 labels 设置反向代理等等。Traefik 本身也作为一个容器(只是挂载了 /var/run/docker.sock 可以探测 Docker 的服务),只暴露 80/443 端口,而其他容器只须加入 Traefik 的网络,无需暴露任何端口。

这 Traefik,相比我之前「暴露端口」的部署方式,有点真正「路由网关」的意思了。

Traefik 架构示意图(来自官方文档)

然而,Traefik 只能作为路由网关,而没有 Web 服务器的功能。如果我要搞一个静态的网站,还需要在它的后端部署上 Caddy/Nginx。

尤其是,这里提到的奇怪的需求,很难用 Traefik 实现。虽然它也提供了自动申请、续签证书,Alidns 泛域名证书插件等等,但是我需要的是 Traefik 做一次路由,如果没有匹配则传给 Caddy 再做一次路由,这种思路非常不优雅。事实上我搜了一通资料也没有配置成功。

然而我发现了另一个东西来替代 Traefik:Caddy-docker-proxy。

Caddy-docker-proxy takes both

这本质上只是一个 Caddy 的插件,为 Caddy 加上了服务探测的功能,使用之后我们既可以像 Traefik 一样通过 labels 设定配置而被服务发现,也可以用 Caddyfile 写自己特殊的配置(作为 Web 服务器使用),插件会自动在内存中生成一个完整的 Caddyfile。换句话说,对于我的需求来说它结合了 Caddy 和 Traefik 的优点。正合我意!

关于这东西的安装,还是挺曲折的。Caddy-docker-proxy 的 Docker 镜像里 Caddy 主程序没有集成 Alidns 的插件,而这是我必需的。所以我用了一个有点骚的方法:在宿主机用 Xcaddy 编译好带 Caddy-docker-proxy 和 Alidns 两个插件的 Caddy 二进制文件,然后挂载到容器内的 /bin/caddy

所以现在是 Caddy 作为网关,也可以进行容器化部署了。这大概是本阶段的终极架构!

最终架构示意图

3.0:Kubernetes

这大概才是生产前沿真正应用的技术。但是我还不会。

未完待续……