踩坑记录: Docker部署WordPress&NGINX并启用SSL - Tinsley's blog

/ 0评 / 2

本站使用的博客程序是WordPress, 目前采取的搭建方式是, 通过脚本LAMP部署环境, 再手动安装WordPress到Apache的/var/www/html目录, 然后配置SSL. 说实话, 搭建的时间精力成本都十分地高, 光是LAMP脚本就要跑一小时左右. 如果哪天换了VPS服务商, 要再重新来一遍, 想想就头疼...

最近学习了一些docker的知识, 十分轻易地在本地跑起来一个WordPress镜像. 在不考虑https的情况下, 部署流程可以简化为:

  1. 安装MySQL/MariaDB, 创建用户和数据库;
  2. 启动WordPress镜像.

大体上两步就完事了... 耗时5分钟, 高下立判!

之前尝试过将docker版的WordPress用NGINX配置SSL证书, 但是失败了. 这两天有一些空闲时间, 终于搞定了这个问题. 所以编辑此文记录一下完整踩坑流程~

环境信息

部署过程

本文的部署流程参照了这篇文章, 并根据实际情况作了一些调整.

安装数据库

由于我使用的是Ubuntu, 所以直接用apt安装mysql-server-5.7, 其他发行版可以用相应的包管理工具自行安装.

sudo apt-get install -y mysql-server-5.7

安装完成后, 使用root用户登录

sudo mysql -uroot -p

然后新建用户和数据库, 此处YOURPASSWORD替换为你自己的密码.

CREATE USER 'wordpress'@'%' IDENTIFIED BY 'YOURPASSWORD';
GRANT ALL PRIVILEGES ON *.* TO 'wordpress'@'%' IDENTIFIED BY 'YOURPASSWORD';
FLUSH PRIVILEGES;
CREATE DATABASE wordpress;

这里我偷懒了, 直接创建了任何host都能登录的用户, 而且无脑给了all privileges... 如果不嫌麻烦, 可以自己优化🤣

经过上面的步骤, 我们创建好了名为wordpress的用户, 名为wordpress的数据库.

MySQL服务默认监听的是127.0.0.1:3306, 而我们的wordpress之后会运行在docker内, 一般来说, ip将会是172.17.0.1, 这样是访问不到宿主机的MySQL服务的. 这里解决办法有几个:

  1. 使用docker版本的MySQL/MariaDB, 通过docker内部ip地址进行通信;
  2. docker run wordpress镜像的时候, 通过--network=host指定网络模式; [参考]
  3. 将宿主机的MySQL配置调整为, 监听0.0.0.0:3306

Emm, 我又选择了偷懒的第三种方式. 直接开放端口是不安全的! 但是配合云服务商的安全组, 或者将3306端口修改为其他非常用端口, 又或是用iptables作限制, 还是可以保证安全性的. 至少对于我的弱鸡VPS加上无人访问的博客, 能挡住端口扫描暴力破解就够了...

开始部署之前, 先准备一个workspace, 假设它的目录是WORKSPACE. 在该目录下新建两个目录wpnginx, 用来存放相关的配置文件.

cd WORKSPACE; mkdir wp; mkdir nginx;

配置WordPress

首先在wp目录下新建一个wp-app文件夹用来存放映射的WordPress程序文件:

cd WORKSPACE/wp; mkdir wp-app;

然后在wp目录新建一个docker-compose.yml配置文件:

vim WORKSPACE/wp/docker-compose.yml

内容如下:

version: '3'

services:
  wp:
    image: wordpress:latest
    ports:
      - 9009:80
    volumes:
      - ./config/php.conf.uploads.ini:/usr/local/etc/php/conf.d/uploads.ini
      - ./wp-app:/var/www/html # Full wordpress project
      #- ./plugin-name/trunk/:/var/www/html/wp-content/plugins/plugin-name # Plugin development
      #- ./theme-name/trunk/:/var/www/html/wp-content/themes/theme-name # Theme development
    environment:
      WORDPRESS_DB_HOST: 172.17.0.1:3306
      WORDPRESS_DB_NAME: wordpress
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: YOURPASSWORD

配置中开放端口9009映射容器的80端口, 然后映射WordPress的程序文件到WORKSPACEwp-app目录下.

接下来在以下路径创建一个php.conf.uploads.ini文件, 配置php相关的一些参数.

vim WORKSPACE/wp/config/php.conf.uploads.ini

内容如下:

file_uploads = On
max_execution_time = 600
memory_limit = 512M
post_max_size = 32M
upload_max_filesize = 32M

最后用docker-compose启动镜像:

cd WORKSPACE/wp; docker-compose up -d

这一部分就完成了!

可以使用docker ps -a命令查看容器是否处在up状态, 如果是的话, 可以访问you_ip_address:9009看看能否进入WordPress的安装界面. 如果失败, 可以尝试往安全组, 防火墙方向排查.

SSL 证书申请

关于SSL, 我使用了acme.sh来申请Let's Encrypt的SSL证书.

首先确保你有个域名, 且已经添加了A记录指向你VPS的公网IP地址.

安装acme.sh

curl  https://get.acme.sh | sh

我比较偏向于使用standalone模式签发证书, 使用之前需要先安装socat:

sudo apt-get install -y socat

安装完成后, 使用这条命令签发证书. your_domain.com替换为你的A记录域名地址.

alias acme.sh=~/.acme.sh/acme.sh
acme.sh --issue -d your_domain.com --standalone

不出意外的话, 申请成功的证书会放在~/.acme.sh/your_domain.com/目录下.

配置NGINX

首先在nginx目录下创建一个certs文件夹用来存放证书文件. acme.sh官方推荐使用--installcert命令来copy/安装证书, 我为了(再次)偷懒, 直接把~/.acme.sh/your_domain.com/这个文件夹cp到了certs文件夹内, 目前并无大碍, 但是并不推荐这么做.😂

复制完证书后, 开始准备NGINX的docker-compose配置文件

vim WORKSPACE/nginx/docker-compose.yml

内容如下:

version: '3'

services:
  nginx:
    image: nginx:latest
    container_name: nginx
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
      #- ./.htpasswd:/etc/nginx/.htpasswd
      - ./certs:/etc/nginx/certs
    ports:
      - 80:80
      - 443:443

配置里映射了nginx.conf配置文件和certs证书文件夹, 开放了80和443端口, 这些可以根据实际情况进行修改.

接下来准备NGINX本身的配置文件nginx.conf:

worker_processes  2;

pid /var/run/nginx.pid;

worker_rlimit_nofile 65535;

#                          [ debug | info | notice | warn | error | crit ]

error_log  /var/log/nginx.error_log  info;

events {
    worker_connections   2000;
    # use [ kqueue | epoll | /dev/poll | select | poll ];
    # use kqueue;
}

http {

    include       mime.types;
    default_type  application/octet-stream;

    log_format main      '$remote_addr - $remote_user [$time_local] '
                         '"$request" $status $bytes_sent '
                         '"$http_referer" "$http_user_agent" '
                         '"$gzip_ratio"';

    log_format download  '$remote_addr - $remote_user [$time_local] '
                         '"$request" $status $bytes_sent '
                         '"$http_referer" "$http_user_agent" '
                         '"$http_range" "$sent_http_content_range"';

    client_header_timeout  3m;
    client_body_timeout    3m;
    send_timeout           3m;

    client_header_buffer_size    1k;
    large_client_header_buffers  4 4k;

    gzip on;
    gzip_min_length  1100;
    gzip_buffers     4 8k;
    gzip_types       text/plain;

    output_buffers   1 32k;
    postpone_output  1460;

    sendfile         on;
    tcp_nopush       on;
    tcp_nodelay      on;
    send_lowat       12000;

    keepalive_timeout  75 20;

    #lingering_time     30;
    #lingering_timeout  10;
    #reset_timedout_connection  on;

    server {
            listen 443 ssl;
            ssl_certificate     /etc/nginx/certs/your_domain.com/your_domain.com.cer;
            ssl_certificate_key /etc/nginx/certs/your_domain.com/your_domain.com.key;
            server_name your_domain.com;
            client_max_body_size 500M ;

            location / {
                proxy_pass         http://172.17.0.1:9009;
                proxy_redirect     off;
                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-Host $server_name;
                proxy_set_header   X-Forwarded-Proto https;
                #proxy_http_version 1.1;
                #proxy_set_header Upgrade $http_upgrade;
                #proxy_set_header Connection "upgrade";
                #proxy_read_timeout 86400;

            }
    }
    server {
            listen 80;
            server_name your_domain.com;    
            location / {
                return 301 https://$host$request_uri;
            }    
    }
}

和参考的那篇原文不同的是, 为了防止NGINX的循环重定向, 我没有选择修改wp-config.php文件, 而是取消注释了下面这一行. 测试证明是有效的.

proxy_set_header   X-Forwarded-Proto https;

另外这里还加入了强制https访问的配置, 80端口收到的请求统统重定向到https.

这样, NGINX这边的配置也都完成了.

在启动nginx镜像前, 请先确保80和443端口没有被占用, 再确保WordPress在http协议下的ip:port的访问是否正常, 再再确保你的WordPress是全新的或做好了资料备份的. 一切OK的话, 前往WordPress的设置->常规中把站点地址和WordPress地址都改成https://your_domain.com. 这一步有风险, 请不要在WordPress有资料且未备份的情况下执行操作!

然后启动nginx镜像:

cd WORKSPACE/nginx; docker-compose up -d

就大功告成了!

正常情况下, 一切顺利的话, 到这一步就可以通过https://your_domain.com来访问你的站点了.

但是如果, 我是说如果... 按照我的叙述进行操作, 很不幸地... 没有成功, 上述修改WordPress站点地址的操作应当会导致整个WordPress崩掉, 且无法登陆进去再把url修改回来🤪(危险操作+1)

当然假设这里部署的是一个船新的WordPress镜像, 里面没有任何资料, 那直接删掉镜像再重来一遍就好. 不过我这里还是给一个官方补救措施供参考.

已知的问题

众所周知, Let's Encrypt的证书签发方便快捷, 但是有效期很短, 需要定期去更新. acme.sh很贴心地帮我们考虑到了这一点, 签发证书成功后, 会自动添加一条crontab, 每天检查证书是否需要更新, 如果有需要更新的证书还会自动帮你renew, 棒呆!

但是, 我上面是通过standalone模式申请的SSL证书, 它要求更新证书操作时, 主机的80端口不能被占用, 因为它的工作原理就是利用socat监听80端口来验证域名的, 而Let's Encrypt并不信任除80和443之外的端口, 我们也就无法指定其他端口来完成验证(ref).

也就是说, 如果网站一直跑着, 监听着80和443端口, 就无法使用acme.sh的自动证书更新功能. 针对这个问题我也想了几个办法:

  1. 弃用证书自动更新, 等发现网站提示证书失效时手动更新(误);
  2. 不使用standalone模式申请SSL证书, 考虑其他高级模式来申请;
  3. 写一个强制更新脚本, 每月执行一次强制更新, 更新前stop掉nginx容器, 更新完成后再start.

希望日后能看到这篇文章的朋友也帮忙想想办法~


时隔大半年诈尸更新了一篇, 往后要加油哇!


2020-07-01更新

上述80端口占用的问题可以通过cloudflare的page rules来解决, 大概的解决方式是: 放弃监听80端口, 通过page rules实现https跳转, 这样做需要删除掉nginx的docker-compose文件中80:80的部分, 然后在nginx的配置文件中把监听80端口的server部分也删掉. 实测可以正常跳转https, 证书更新也一切正常~

发表评论

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