迁移 WordPress 站点并安排容器化与 CDN

由于之前使用的主机节点抽风比较严重,周末狠了狠心决定切换至新的供应商上,并且套它一层 CDN。最早的部署方案是在 16 年左右设计的,那时候还比较年轻,docker 之类的技术也没那么流行,就直接照着百度出来的过时 Blog 拉了一套 LAMP 起来;后来大概在 17 年时需要迁移站点,捯饬了半天搞不定 Apache 的多站点 SSL 配置,干脆把它换成了 Nginx + php-fpm 。像 CDN 啥的也没整,直接源站就在这之后又经过了数次迁移,节点也搞挂过许多次,博客也一度停更了许久,不过数据倒是奇迹般的一点都没丢。

然而现在已经是 2022 年了,是时候整一点新鲜的花活了!所以在部署新的站点时,我采用了容器化和 Cloudflare CDN。这篇文章主要介绍迁移时遇到的坑,包括:

  1. 迁移 DB 与部署 phpMyAdmin;
  2. 整合旧 WordPress 站点与官方的 Docker Images;
  3. 配置 Cloudflare CDN 并验证回源请求。

其实在确定这个方案之前,我也搜过了一些文章,不过许多都是教我用一个啥啥插件把数据导出出来,然后在新站点安装完毕后再导入进去。这方法倒也不是不行,但是要先手动配置好插件啥的,静态文件还要单独处理,搞不好还有兼容性问题,其实挺费劲的。这就好比我只是想换一套电脑硬件,非要重新安装一遍操作系统并使用一个 XX 管家备份还原数据一样;其实这种情况直接把硬盘插过去就好了嘛,或者 dd 把磁盘里的内容完整搞过去就是。所以我的迁移计划也是一样的,先试试能不能直接把数据完完整整搞过去,然后用容器拉起来算完事。

迁移 DB 与部署 phpMyAdmin

想要迁移 WordPress,第一件事是要把 DB 给搞过去。说来惭愧,虽然已经工作了一段时间了,但说实话由于实际业务使用 DB 的场景并不多,我对数据库的权限、认证、DDL、DML 这些可谓是一窍不通…… 依稀记得在上次迁移,我还是用一个网上抄来的命令把用户信息和 WordPress 的那些表导出,然后在新节点上安装好 MariaDB 并导入的。

严格一点来说,之前的处理方式好像跟用 XX 管家备份还原的区别也不大了。其实对于 MariaDB (或者 MySQL) 来说,所有数据都是保存在 /var/lib/mysql 中,由于现代软件大多是能够做到版本兼容的(事实上我的 MariaDB 也跟着大蜥蜴升级了好几个版本),完全可以直接停掉 DB 后把这个目录拷走(记得用 tarrsync -av ,否则新库启动时可能有权限问题)。MariaDB 的 Docker Image 使用起来非常简单,直接 -v 选项把目录映射进去就行了;端口可以 -p 映射到 localhost ,也可以直接使用 Docker Compose 默认的网桥。

phpMyAdmin 的安装也非常简单,映射一下端口,然后再设置 PMA_ARBITRARY=1 环境变量就成。不过这里建议映射端口时只监听 localhost,不要暴露给公网:一方面存在被攻击的风险;另一方面直接暴露出去也没有 SSL,存在安全隐患。需要访问 phpMyAdmin 时,使用 ssh 的 -L 转发功能,即可在本机打开管理页面。

不过需要注意的是,塞进了容器里面意味着 Network Namespace 与 Host 不一样,如果 DB 中原本配置的账户信息用了 localhost ,可能会造成账户都无法登陆。这个问题可以通过进入容器后直接用内置的 mysql 命令来处理,也可以把容器内的 /var/run/mysqld/mysql.sock 映射到容器外面来,或者干脆直接用 unix socket 来进行业务访问。至于本蒟蒻,则直接在旧节点的安全设置上屏蔽了外部访问,修改好账户之后重新同步了一遍数据到新节点……

容器化部署 WordPress

一般来说服务总是可以分割为程序 + 数据两部分,或者说是不可变 + 可变两部分。对于一般的服务来说,不可变的就是程序、依赖库等部分,这些一般会打进 Docker Image 中,可变的则是配置文件和本地数据,一般通过 Volume、环境变量等方式传递。

不过对于 WordPress 来说,这个划分可能会变得更加复杂。在 Docker 官方打包的 WordPress 镜像说明中,推荐我们把 /var/www/html 作为 Volume 挂载,并传递一系列 WORDPRESS_DB_* 环境变量进去。那么 Volume 中的那些 HTML PHP 文件是哪来的呢?在镜像的 docker-entrypoint.sh#L66 位置我们可以看到在容器首次运行时,启动脚本会将镜像内预置在 /usr/src/wordpress 下的整个 WordPress 目录拷贝到 /var/www/html 中,并将配置文件中的值按环境变量进行替换。至于后续的启动,由于配置已经写进 wp-config.php 了,只要 Volume 还在,也就不需要再指定环境变量了。

显然,官方推荐的这种方式是把 Apache + PHP 环境和初始化脚本当作了不可变部分,把整个 WordPress 当作了可变部分了。也就是说,WordPress 和相关插件还是像以前一样,在管理后台该升级升级,该安装安装,只有大版本升级发生依赖变更时才需要更新 docker images。

除此之外,WordPress 本身的目录结构设计还允许我们将其 wp-content 目录作为可变部分保存在 Volume 中,让 WordPress 主题随着镜像一起升级。其实在 Docker Image 的 README 中也提到了这种做法,这时虽然可以在管理后台升级 WordPress 版本,但是容器重新创建等之后会立即丢失,另外如果某次升级导致主版本和一些主题、插件等不兼容,也会相对比较难处理一些,所以并不是很推荐萌新这么干。

综上所述,既然官方推荐我们把整个 /var/www/html 目录作为可变部分处理了,正好只要把源 nginx 下面的目录塞过来就好了。不过由于我的站点是放在 /wp/ 子目录下而不是根目录下,在 docker-compose.yaml 中还需要指定一下 WORKDIR ,否则每次初始化时启动脚本还会在根目录下给再创建一份……

配置 Cloudflare CDN

至此,WordPress 服务和数据库都拉起来了,不过还需要解决一下域名和 TLS 的问题。由于提供 HTTP 服务的 Apache 打在了容器内部,不太方便直接修改配置文件和进行多站点部署,因此需要在 Docker 之前套上一层 Nginx 代理,由 Nginx 接管 TLS、处理其他目录的静态请求并提供反向代理。

Nginx 的配置属于老生常谈的问题了,这里就不多做展开,不过在反代部分还是有一些点需要注意。WordPress 默认登陆和后台管理页面是需要走 TLS 的,因此会尝试将收到的 HTTP 请求重定向到 HTTPS 地址上去,这显然会在反向代理的场景下存在问题,因为 Nginx 与容器之间的通信一定是明文传输,因此在登陆时会出现无限循环。好在官方考虑到了这个场景,给出了一种通过 X-Forwarded-Proto 扩展头的解决方案,因此一定记得在 Nginx 中正确设置这个扩展头。

另外,由于我的 WordPress 是由一个非 Docker 版本迁移过来的,而不是由官方镜像初始化的。由于检测 X-Forwarded-Proto 扩展头的逻辑是特殊定制在 Docker Image 的 wp-config-docker.php#L116 中的,所以还需要手动把这一小段逻辑复制到 wp-config.php 中才能使扩展头生效。

到这里,站点基本上就迁移结束了,不过要想闷声发大财,最好还是把站点藏在 CDN 后面。对于我等穷鬼来说,Cloudflare 就是一个不错的选择,虽说测起速来全国飘红,但至少是免费的。唯一的问题是按正常接入流程需要把整个域名解析服务器换到 Cloudflare 上,或者通过第三方 API 进行 CNAME 接入。

Cloudflare 的配置其实也没什么复杂的,随便一搜全都有。不过需要注意 Cloudflare 本身相当于另一层反向代理,因此源站的 Nginx 传递给 WordPress 的协议类型实际上是 Cloudflare 到 Nginx 使用的协议,因此出于安全角度考虑最好直接开启 “强制 HTTPS” 开关,以保证 Browser – Cloudflare 这段总是经由 HTTPS 传输。另外,Cloudflare 这段最好配置成 TLS 传输,证书可以用 Cloudflare 签发的 15 年私有证书,最好再开启 Authenticated Origin Pulls 功能,保证只有 Cloudflare 能够访问你的源站且数据是加密的。最后,由于 Cloudflare 的回源是支持双栈的,源站完全可以只监听 IPv6 的 443 端口,减少暴露面。

额外一提,Cloudflare 还提供了 Email Routing 功能,可以用自己的域名创建一些地址,然后把邮件转发至私有信箱。这个功能非常不错,由于暂时没有发信需求,这里就只把 mail server 的数据传输了过来,没有再拉起 poste.io 套件。

发表评论