Docker 作为这几年兴起的虚拟化解决方案,具有便于移植、开销较低的优势。这篇知乎回答很好的概括了 Docker 的特性,感兴趣的话可以看一看。把 WordPress 迁移到 Docker 中后,不仅可以方便的备份和克隆,更可以轻松的在不同的系统和配置之间迁移。一切网站需要的依赖项(LAMP等)都会包含在容器里,所以移植的时候只需要把容器打包带走即可。话不多说,下面教程中我会尽可能详细的阐述迁移的每一步,希望对诸君能有帮助。

致谢:本文的灵感和早期的研究方向主要来自于这篇文章,感谢作者 Stephen AfamO。

开始前的准备

在开始之前,请务必备份 WordPress 工作目录下的文件和相关的数据库。无论是否迁移,经常备份站点有利无害。

我推荐诸君做好以下准备,以确保迁移成功:

  • 目标机器运行 Ubuntu 16.04 LTS。CentOS 甚至其他 Linux 发行版理论上都可以适用这套教程,但我不能保证成功。特别是 Nginx 的配置,会因为系统不同而略有出入。
  • 目标机器在防火墙开通了80(HTTP)和443(HTTPS)端口。一些提供商,如阿里云,需要自行在控制台的安全组中添加相关规则。
  • 已经将你的域名(或者一个子域名)解析到新的系统。下面的示例我们将用 example.com,请根据自身情况修改。
  • 目标机器最好和原始机器是两台不同的实例。同一台机器中的迁移理论上可以成功,但一旦失败极易将已有的系统搞得乱七八糟,甚至导致你的博客不再可访问。迁移到新的机器还能实现平滑过渡,在你验证新的机器正常工作后可以再移除老的机器。
  • 当然,你需要能 SSH 到目标机器和原始机器。本文所有操作均在 root 身份下进行。

第零步:导出原始机器上的 WordPress 存档

WordPress 有很多著名的备份插件,而我要推荐的是这款下载量最多、也是好评最高的这款插件:All-in-One WP Migration。虽然插件没有中文翻译,但过程非常简单。可以导出的内容非常全面,包括文章、媒体、评论、主题、插件、设定等。安装后在仪表盘 All-in-One WP Migration > Export 打开导出界面,然后参考下面的图片开始导出:

按照你的需求选择导出的内容后,按下这个诱人的 EXPORT TO 按钮,然后在弹出的窗口中选择 FILE 下载到本地(你也可以选择其他的保存方式,但我没试过)。根据你网站的配置和文件数量,存档大小从几十M到几百M都有可能。假设我们这次下载的文件叫做 example.com-20180704-012345-678.wpress

第一步:在目标机器安装 Nginx 和 Docker

安装 Nginx

apt update
apt install nginx
apt 还是 apt-get?
apt 为经典的 apt-get 的“升级版”,对用户也更加友善。这篇文章总结了这两者的一些区别。

安装 Docker

在这里我们要用到原文里的一个脚本,只用一键就可完成任务

wget -O - https://bit.ly/docker-install| bash

这个命令会安装(或者更新) Docker 本身并下载 Docker-compose。Docker-compose 可以方便管理多个 Docker 容器,将它们打包成一个整体管理,非常适合我们即将安装的 WordPress 实例。

如果以上脚本报错提示 bash: line 25: git: command not found,说明你的系统没有安装 Git,请自行手动安装(apt install git)。

第二步:创建 docker-compose.yml 文件

虽说有了 Docker-compose 这样的利器,但我们先得创建一个定义文件,其中我们会指定需要的镜像、他们之间的关系、环境变量等等。

请注意
在下面的所有示例中,你应该将 example.comexample 等字样替换成你自己的域名或者你自己的内容。
mkdir -pv /var/www/example.com/
cd /var/www/example.com/

-pv 选项会创建必要的上级目录,如果它们还尚未存在。

touch docker-compose.yml

接下来,用你喜欢的文本编辑器打开 docker-compose.yml,并输入以下内容(推荐复制/粘贴,因为 YAML 文件对空格和缩进有严格的要求,少个空格可能会导致编译失败)。

version: '3.3'

services:
  db:
    image: mysql:5.7
    container_name: db_example
    volumes:
      - db_data:/var/lib/mysql
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: eXAMpLe     # 随便起,和下面的 phpMyAdmin 中的需要一致
      MYSQL_DATABASE: wp_example       # 这三行设定需要和下面一致
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: wordpress123

  wp:
    depends_on:
      - db
    image: wordpress:latest
    container_name: example_site
    ports:
      - "1234:80"                      # 可以根据需要改成其他端口,但下文的实例将使用1234
    restart: always
    links:
      - db:mysql
    environment:
      WORDPRESS_DB_NAME: wp_example
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: wordpress123
    volumes:
     - ./src:/var/www/html  

  phpmyadmin:                          # 如果不需要 phpMyAdmin,可以删掉33-47行相关部分
    depends_on:
      - db
    image: phpmyadmin/phpmyadmin
    container_name: phpmyadmin
    links:
      - db:mysql
    restart: always
    ports:
      - "8081:80"
    volumes:
      - /sessions
    environment:
      MYSQL_ROOT_PASSWORD: eXAMpLe

volumes:
    db_data:

下面对高亮的部分稍作解释:

8、49行:volumes 指令把容器内的 /var/lib/mysql/ 下的数据库内容映射到当前目录下的文件夹中(默认为 database)文件夹,这样数据库中产生的变化会保存到本地系统。详情可以参考这篇文章

31行:同第8行类似,但把容器内的 /var/www/html/ 下的 WordPress 插件、主题等文件映射到当前目录下的 /src 文件夹下。有兴趣的可以读一读这篇官方文档了解 volume

假如去掉 volumes 指令,那么在你重启 Docker 容器后对 WordPress 所有的更改都会消失!(想象一下你的文章、主题、插件全部消失,取而代之的是“著名的5分钟安装”…不要问我为什么知道…)

25行:link 指令将 mysql 容器和 WordPress 联系起来,这样 WordPress 实例就可以访问数据库了。请注意,虽然我们用 Docker-compose 可以协调多个 Docker 容器之间的合作,但每个容器之间仍然是互相独立的。也就是说,除非是你把 Docker 容器暴露给外部世界(通过 port 或 volumes 等指令映射),它们是完全“绝缘”的。当然我们也可以给 MySQL 实例创建端口映射(一些教程也是这么做的),但在我们这个范例中是完全没必要的。毕竟,端口开得越多一个,安全威胁就多一分。(注:link 指令在最新版 Docker 中被标记为“已过时”,但我懒得去搜新的设定了…)

第三步:初次启动 Docker

激动人心的时候到了,是时候启动我们的 Docker 配置了!

docker-compose up

现在 Docker 会下载并解压 MySQL、WordPress、phpMyAdmin以及它们需要的所有依赖项。接着你眼前就会闪过一大堆log(别急,这只是初次启动时候方便排查问题用的)。假如一切正常,我们就可以按 Ctrl+C 键关闭这套配置。如果在log中注意到问题,也欢迎你在评论中讨论或者求助。在关闭 Docker 容器后,其对应的镜像会被保留,方便日后使用。

第四步:在 Nginx 中创建服务器配置

由于 Docker 中的 WordPress 实例只提供基础的 HTTP 接入,为了方便部署 SSL 证书和创建映射,我们需要用 Nginx 创建一个简单的反向代理(反代)。延伸阅读:什么是正向代理和反向代理

cd /etc/nginx/sites-available
touch example.com.conf

用文本编辑器打开 example.com.conf 文件,并输入以下内容

server {
    listen 80;
    listen [::]:80;

    server_name example.com www.example.com;
    location / {
        proxy_pass http://0.0.0.0:1234;          # 将请求发到本机的1234端口
        proxy_set_header Accept-Encoding "";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

有兴趣的可以看这篇官方文档,详细了解 Nginx 反代。注意第7行的端口号需要和先前 docker-compose.yml 中定义的一致。

接下来,把配置文件链接到 site-enabled/ 文件夹下,再重载 Nginx 就可以载入这个配置。

ln -s /etc/nginx/sites-available/example.com.conf /etc/nginx/sites-enabled/example.com.conf
nginx -s reload

第五步:初次测试

重启 Docker 容器。

cd /var/www/example.com/
docker-compose up -d

注意命令中的 -d,将 Docker-compose 放在后台运行 [daemon]。现在你应该看到几条简单的状态消息,但没有瀑布般的输出。

在浏览器里访问你的域名。如果一切正常,你应该能看到 WordPress “著名的5分钟安装”。

第六步:向 HTTPS 进发

虽然这样的网站已经能用,但显然在 8102 年不加密的网站漏洞多多(非加密的网站在搜索引擎中的排名也更低)。首先我们生成一个 Diffe-Hellman 密钥(注:这一步应该是可以跳过的,但有助于提高安全性)。

openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048

光有 DH 密钥还不够,我们还需要一个合格的 SSL 证书。除了这些动辄几十刀的解决方案外,我相信大家对免费好用的 Let’s Encrypt 也有所耳闻。通过 LE 官方推荐的 Certbot 程序,我们可以简化申请和更新证书的流程,只需一键就可完成。下面安装步骤摘自 Certbot 官方教程,你也可以在这个网站找到其他 Linux 发行版的教程。

apt update
apt install software-properties-common
add-apt-repository ppa:certbot/certbot
apt update
apt install python-certbot-nginx

接下来,申请一个新的证书:

sudo certbot --nginx certonly

certonly 即只申请证书但不修改 Nginx 配置。生成的证书和私钥默认放置在 /etc/letsencrypt/live/example.com/ 下。

最后,我们编辑 Nginx 的配置,启用 HTTPS 连接并强化安全。修改后的 example.com.conf 类似这样:

server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;

    location "/.well-known/acme-challenge" {
       default_type "text/plain";
       root /var/www/example.com;
       allow all;
    }

    return 301 https://example.com$request_uri;         # 将 HTTP 请求重定向到 HTTPS
}

server {
 listen 443 ssl;
 server_name example.com www.example.com;
 
 ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
 ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
 ssl_session_cache shared:SSL:10m;
 ssl_session_timeout 5m;
 ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
 ssl_prefer_server_ciphers on;
 ssl_dhparam /etc/ssl/certs/dhparam.pem;
 ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';
 ssl_stapling on;
 ssl_stapling_verify on;
 add_header Strict-Transport-Security max-age=15768000; # 启用 HSTS
 
 location "/.well-known/acme-challenge" {               # 下次证书更新时使用
   default_type "text/plain";
   root /var/www/example.com;
   allow all;
 }

 location / {
   proxy_pass http://0.0.0.0:1234;
   proxy_set_header Host $host;
   proxy_set_header X-Real-IP $remote_addr;
   proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
   proxy_set_header X-Forwarded-Proto $scheme;
 }

 location /phpmyadmin/ {                                # 将 phpMyAdmin 端口映射到 /phpmyadmin/ 下
   proxy_pass http://0.0.0.0:8081/;
   proxy_set_header Host $host;
   proxy_set_header X-Real-IP $remote_addr;
   proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
   proxy_set_header X-Forwarded-Proto $scheme;
 }
}

注意在29行我们启用了 HSTS ,简单来说客户端访问我们的网站时会强制采用 HTTPS 连接,大大的提升了安全性。一个相对应的弊端是,假如你的 SSL 证书以外失效了,所有人(在你修复证书之前)都无法访问你的网站。我推荐在你确认 Nginx 配置稳定后再启用这个设定。

重载 Nginx 配置:

nginx -s reload

再次在浏览器中打开你的域名,你应该能看到网站已经自动跳转到 HTTPS 了。访问 /phpmyadmin (如 example.com/phpmyadmin),你可以看到熟悉的 phpMyAdmin 登录界面(可以用 root 账号和你之前定义的 root 密码登录)。至此,现在你已经有一个完整的 WordPress 实例了!

第七步:导入 WordPress 存档

还记得我们在一开始创建的存档吗?我们现在要把它导入新的网站。完成“5分钟安装”后,在新站中安装 All-in-One WP Migration,并进入 Import 选单。将存档文件(例如 example.com-20180704-012345-678.wpress)拖入框中,或者点开 IMPORT FROM 并选择 FILE(注意,免费版只支持至多 512M 的文件,超出需要付费升级)。接下来文件会上传并覆盖现有的 WordPress 结构。升级完成后,可能会弹出提示要你点开“固定链接”菜单并按两次“保存更改”。现在打开你的网站,一切应该都和原来一样。

如果你选择不导出插件,你可能需要重新安装这些插件。恭喜,你的博客已经完全了迁移到 Docker!

附:常见问题

下面是一些常见问题的解决方案,也欢迎大家提出新的问题,我会尽快解答。

  • 提示”Can’t find a suitable configuration file…”,请确认 docker-compose.yml 文件在你当前的工作目录下(用 pwd 指令查看),如是 /var/www/example.com

 

One Reply to “将 WordPress 整体迁移到 Docker 容器

发表评论

电子邮件地址不会被公开。 必填项已用*标注

This site uses Akismet to reduce spam. Learn how your comment data is processed.