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
安装 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.com
和 example
等字样替换成你自己的域名或者你自己的内容。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
[…] 在三个月暑假结束之际,本站迁移到了速度超快的CN2搬瓦工。感谢之前Kazumi提供的服务器和迁移教程。 […]
请问下我用这个方法搭搭建完后在/usr/local/etc 里面找不到php文件是因为?
你好,请问你要的是 Docker 容器里的php配置文件吗,可以通过 docker exec example_site ls /usr/local/etc/php 的命令来查看
[…] php –ini 命令来确认它的具体位置(如果你是通过 Docker 安装的 WordPress 的话,php […]