用Docker建立WordPress

发布于 2020-02-05  1010 次阅读


运行环境:Ubuntu 18.04 LTS以及Docker。

使用的软件为:

  • MySQL,作为数据库;
  • WordPress,本体;
  • Nginx,网页服务器;
  • Certbot,用于申请Let‘s Encrypt的证书。

以上程序都使用Docker镜像,由于完整的WordPress镜像里内置了Apache,这里使用适用于Nginx的WordPress-fpm镜像

主机使用的是AWS Lightsail 5刀的订阅,最低3.5刀的订阅内存只有512M似乎不够运行这一套东西。

准备

  • 运行Ubuntu 18.04 LTS的服务器,其他系统流程一般应该是大同小异的;
  • 一个域名,如果需要的话在最后有NameSilo的推广链接。此外,谷歌也能找到一些免费的域名提供商;
  • 设置域名的A记录到服务器的IP地址,AWS不提供IPv6地址,所以也没法测试IPv6应该怎么做;
  • 最后,记得打开服务器防火墙上的80和443端口。

安装Docker

这里参照Docker官网的安装说明进行安装,区别是apt-get被我改成了apt,不过没啥区别。如果安装过旧版的Docker要先更新,或者干脆卸载重装。我的机器里没有Docker,所以就直接安装了。

设置Docker仓库

更新软件源列表:

sudo apt update

安装一些依赖项,用于通过HTTPS获取软件仓库:

sudo apt install apt-transport-https ca-certificates curl gnupg-agent software-properties-common

添加Docker的GPG密钥:

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

搜索一下密钥的指纹来,检查一下密钥是否添加成功:

sudo apt-key fingerprint 0EBFCD88

如果添加成功的话会返回如下内容:

pub   rsa4096 2017-02-22 [SCEA]
      9DC8 5822 9FC7 DD38 854A  E2D8 8D81 803C 0EBF CD88
uid           [ unknown] Docker Release (CE deb) <[email protected]>
sub   rsa4096 2017-02-22 [S]

安装Docker EC

更新软件源:

sudo apt update

安装Docker EC和containerd,我这里直接安装最新版的:

sudo apt install docker-ce docker-ce-cli containers.io

测试是否安装成功:

sudo docker run hello-world

如果安装成功的话会输出一些有关Docker的说明之类的东西。

默认情况下,需要使用sudo命令来运行Docker,不过可以将用户添加到docker用户组来设置为Docker管理员,这样就不需要使用sudo了:

 sudo usermod -aG docker your-user

其中 your-user为要使用docker的用户名。

现在Docker已经安装好了,不过为了方便使用这里使用Docker官方提供的容器编排工具Docker Compose来配置容器。

安装Docker Compose

下载Docker Compose:

sudo curl -L "https://github.com/docker/compose/releases/download/1.25.3/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

添加执行权限:

sudo chmod +x /usr/local/bin/docker-compose

测试是否安装成功:

docker-compose --version

如果输出版本号说明安装成功。

编写配置文件

首先建立一个文件夹用来保存所需的配置文件,例如wordpress文件夹:

mkdir wordpress && cd wordpress

编写Nginx配置文件

这里创建的Nginx配置文件用于使Certbot获取证书。如果不需要HTTPS的话也可以直接用,不过HTTPS是现在的趋势,建议还是用上比较好。

创建一个用来保存nginx配置文件的文件夹:

mkdir nginx-conf

编写nginx的配置文件,可以把vim换成顺手的编辑器,例如nano:

vim nginx-conf/nginx.conf

把下面的配置文件粘贴到编辑器里,然后把里面的example.com更换成要使用的域名:

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

        server_name example.com www.example.com;

        index index.php index.html index.htm;

        root /var/www/html;

        location ~ /.well-known/acme-challenge {
                allow all;
                root /var/www/html;
        }

        location / {
                try_files $uri $uri/ /index.php$is_args$args;
        }

        location ~ \.php$ {
                try_files $uri =404;
                fastcgi_split_path_info ^(.+\.php)(/.+)$;
                fastcgi_pass wordpress:9000;
                fastcgi_index index.php;
                include fastcgi_params;
                fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
                fastcgi_param PATH_INFO $fastcgi_path_info;
        }

        location ~ /\.ht {
                deny all;
        }

        location = /favicon.ico { 
                log_not_found off; access_log off; 
        }
        location = /robots.txt { 
                log_not_found off; access_log off; allow all; 
        }
        location ~* \.(css|gif|ico|jpeg|jpg|js|png)$ {
                expires max;
                log_not_found off;
        }
}

最后记得保存

配置环境变量

这一步主要是为了将数据库的密码与docker compose文件分开保存,如果不做的话需要把密码写在docker compose文件内。

创建一个.env文件:

vim .env

在里面写入数据库密码,其中:

  • your_root_password是root用户密码;
  • your_wordpress_database_user是创建的用户名;
  • your_wordpress_database_password是用户密码。

上面三个内容替换为自己要设置的用户名和密码

MYSQL_ROOT_PASSWORD=your_root_password
MYSQL_USER=your_wordpress_database_user
MYSQL_PASSWORD=your_wordpress_database_password

编辑完成后保存并关闭文件。

为了避免.env文件被存储到Git和Docker中,可以创建两个文件,先创建.dockerignore:

vim .dockerignore

然后输入不想保存的文件名:

.env
.git
docker-compose.yml
.dockerignore

然后保存。

同样,建立.gitignore文件:

vim .gitignore

输入:

.env

并保存。

创建Docker Compose配置文件

创建一个docker-compose.yml文件来为容器进行配置:

vim docker-compose.yml

首先写入Compose文件版本号,版本号适配的版本可以在Docker的官方文档里查询到,写这篇文章时的最新版是3:

version: '3'

接下来在下面配置数据库:

services:
        db:
                image: mysql
                container_name: db
                volumes:
                        - dbdata:/var/lib/mysql
                restart: unless-stopped
                env_file: .env
                environment:
                        - MYSQL_DATABASE=wordpress
                command: '--default-authentication-plugin=mysql_native_password'
                networks:
                        - app-network

其中参数对应如下:

  • image:使用的容器镜像,这里使用mysql镜像,可以在镜像名后面输入“:版本号”来指定使用的版本;
  • container_name:容器的名称;
  • restart:容器的重启策略,默认不会自动重启,这里设置为除非手动关闭,否则自动重启;
  • env_file:将文件加入到环境变量中,这里就是之前创建的.env文件;
  • environment:添加的环境变量,由于数据库名称不涉及到敏感信息,所以可以直接在这里添加;
  • command:替换启动指令,因为使用的WordPress镜像不支持新版MySQL验证,使用这个选项来使MySQL使用旧版的身份验证。

继续在文件中配置wordpress:

        wordpress:
                depends_on:
                        - db
                image: wordpress:fpm
                container_name: wordpress
                restart: unless-stopped
                env_file: .env
                environment:
                        - WORDPRESS_DB_HOST=db:3306
                        - WORDPRESS_DB_NAME=wordpress
                        - WORDPRESS_DB_USER=$MYSQL_USER
                        - WORDPRESS_DB_PASSWORD=$MYSQL_PASSWORD
                volumes:
                        - wordpress:/var/www/html
                networks:
                        - app-network

其中:

  • depends_on:用于设置容器的启动顺序,这里设置为使这个容器在数据库启动之后启动;
  • image:由于使用Nginx作为网页服务器,所以这里使用Wordpress的fpm镜像;
  • env_file:将.env文件作为环境变量;
  • environment:设置数据库的端口,数据库名称,用户名和密码,其中端口号使用默认的3306端口,用户名和密码则通过.env文件来指定;
  • volumes:将wordpress的数据卷挂载到/var/www/html目录下,以便与其他容器共享;
  • networks:将容器添加到app-network网络中。

然后在文件中继续添加Nginx的配置:

        webserver:
                depends_on:
                        - wordpress
                image: nginx
                container_name: webserver
                restart: unless-stopped
                ports:
                        - "80:80"
                        - "443:443"
                volumes:
                        - wordpress:/var/www/html
                        - ./nginx-conf:/etc/nginx/conf.d
                        - certbot-etc:/etc/letsencrypt
                networks:
                        - app-network

其中选项对应为:

  • ports:暴露的端口,这里使用HTTP默认使用的80端口和HTTPS默认使用的443端口;
  • vloumes:这里分别挂载了数据卷和本机的数据文件
    • wordpress:/var/www/html:将wordpress数据卷挂载到/var/www/html目录,这个是Nginx的根目录;
    • ./nginx-conf:/etc/nginx/conf.d:将主机上的配置文件挂载到容器中的相应目录;
    • certbot-etc:/etc/letsencrypt:将HTTPS需要的证书挂载到相应目录中。

最后,添加获取HTTPS证书的Certbot的配置:

        certbot:
                depends_on:
                        - webserver
                image: certbot/certbot
                container_name: certbot
                volumes:
                        - certbot-etc:/etc/letsencrypt
                        - wordpress:/var/www/html
                command: certonly --webroot --webroot-path=/var/www/html --email [email protected] --agree-tos --no-eff-email --staging -d example.com

其中,command选项中的参数里的[email protected]为申请证书所使用的邮箱,example.com为要申请的域名。

  • --webroot:使用webroot插件来进行HTTP-01验证;
  • --webroot-path:指定webroot目录的路径;
  • --email:注册和恢复证书使用的电子邮件,在这里使用的例子为[email protected],应换成要使用的电子邮件;
  • --agree-tos:表示同意ACME的使用协议;
  • --no-eff-email:表示不与电子前哨基金会共享电子邮件地址,如果愿意共享的话可以删除;
  • --staging:使用测试模式来获取证书,因为每周申请证书的次数是有限的,可以加上这个参数进行测试,确认运行正常之后再删除该参数来正式申请证书;
  • -d: 指定申请证书的域名,这里的例子是example.com,应换成要使用的域名。

最后,在文件中添加如下内容来定义数据卷和网络:

volumes:
        certbot-etc:
        wordpress:
        dbdata:
networks:
        app-network:
                driver: bridge

其中volumes用于定义数据卷,可用于在容器之间共享文件,networks可以定义容器之间的通信网络,使容器之间可以互相通信。

最后完成的docker-compose.yml文件应如下:

version: '3'

services:
        db:
                image: mysql
                container_name: db
                volumes:
                        - dbdata:/var/lib/mysql
                restart: unless-stopped
                env_file: .env
                environment:
                        - MYSQL_DATABASE=wordpress
                command: '--default-authentication-plugin=mysql_native_password'
                networks:
                        - app-network
        wordpress:
                depends_on:
                        - db
                image: wordpress:fpm
                container_name: wordpress
                restart: unless-stopped
                env_file: .env
                environment:
                        - WORDPRESS_DB_HOST=db:3306
                        - WORDPRESS_DB_NAME=wordpress
                        - WORDPRESS_DB_USER=$MYSQL_USER
                        - WORDPRESS_DB_PASSWORD=$MYSQL_PASSWORD
                volumes:
                        - wordpress:/var/www/html
                networks:
                        - app-network
        webserver:
                depends_on:
                        - wordpress
                image: nginx
                container_name: webserver
                restart: unless-stopped
                ports:
                        - "80:80"
                        - "443:443"
                volumes:
                        - wordpress:/var/www/html
                        - ./nginx-conf:/etc/nginx/conf.d
                        - certbot-etc:/etc/letsencrypt
                networks:
                        - app-network
        certbot:
                depends_on:
                        - webserver
                image: certbot/certbot
                container_name: certbot
                volumes:
                        - certbot-etc:/etc/letsencrypt
                        - wordpress:/var/www/html
                command: certonly --webroot --webroot-path=/var/www/html --email [email protected] --agree-tos --no-eff-email --staging -d example.com
volumes:
        certbot-etc:
        wordpress:
        dbdata:
networks:
        app-network:
                driver: bridge

运行容器

在docker-compose.yml文件所在的文件夹内,执行如下命令来运行容器:

docker-compose up -d

如果配置正确的话,可以看到输出以下信息:

Creating db ... done
Creating wordpress ... done
Creating webserver ... done
Creating certbot   ... done

使用docker-compose ps来查看docker运行状态:

docker-compose ps

如果运行正常的话会返回如下结果:

Name                 Command               State           Ports       
-------------------------------------------------------------------------
certbot     certbot certonly --webroot ...   Exit 0                      
db          docker-entrypoint.sh --def ...   Up       3306/tcp, 33060/tcp
webserver   nginx -g daemon off;             Up       0.0.0.0:80->80/tcp 
wordpress   docker-entrypoint.sh php-fpm     Up       9000/tcp           

如果db wordpress webserver的State列不是中的状态不是Up或者certbot的退出状态不是0的话,可以使用下列命令来查看日志:

docker-compose logs service_name

其中service_name为要查看的容器名称。

接下来,可以使用下列命令来查看证书是否安装成功:

docker-compose exec webserver ls -la /etc/letsencrypt/live

如果证书申请成功的话会看到以下内容:

total 16
drwx------ 3 root root 4096 May 10 15:45 .
drwxr-xr-x 9 root root 4096 May 10 15:45 ..
-rw-r--r-- 1 root root  740 May 10 15:45 README
drwxr-xr-x 2 root root 4096 May 10 15:45 example.com

这说明证书申请成功,现在应该编辑docker-compose.yml文件,将certbot的command选项里面的--staging替换为--force-renewal,替换之后应如下所示:

        certbot:
                depends_on:
                        - webserver
                image: certbot/certbot
                container_name: certbot
                volumes:
                        - certbot-etc:/etc/letsencrypt
                        - wordpress:/var/www/html
                command: certonly --webroot --webroot-path=/var/www/html --email [email protected] --agree-tos --no-eff-email --force-renewal -d example.com

接下来重新运行certbot:

docker-compose up --force-recreate --no-deps certbot

如果输出如下内容,说明证书申请成功:

Recreating certbot ... done
Attaching to certbot
certbot      | Saving debug log to /var/log/letsencrypt/letsencrypt.log
certbot      | Plugins selected: Authenticator webroot, Installer None
certbot      | Renewing an existing certificate
certbot      | Performing the following challenges:
certbot      | http-01 challenge for example.com
certbot      | http-01 challenge for www.example.com
certbot      | Using the webroot path /var/www/html for all unmatched domains.
certbot      | Waiting for verification...
certbot      | Cleaning up challenges
certbot      | IMPORTANT NOTES:
certbot      |  - Congratulations! Your certificate and chain have been saved at:
certbot      |    /etc/letsencrypt/live/example.com/fullchain.pem
certbot      |    Your key file has been saved at:
certbot      |    /etc/letsencrypt/live/example.com/privkey.pem
certbot      |    Your cert will expire on 2019-08-08. To obtain a new or tweaked
certbot      |    version of this certificate in the future, simply run certbot
certbot      |    again. To non-interactively renew *all* of your certificates, run
certbot      |    "certbot renew"
certbot      |  - Your account credentials have been saved in your Certbot
certbot      |    configuration directory at /etc/letsencrypt. You should make a
certbot      |    secure backup of this folder now. This configuration directory will
certbot      |    also contain certificates and private keys obtained by Certbot so
certbot      |    making regular backups of this folder is ideal.
certbot      |  - If you like Certbot, please consider supporting our work by:
certbot      | 
certbot      |    Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
certbot      |    Donating to EFF:                    https://eff.org/donate-le
certbot      | 
certbot exited with code 0

修改Web服务器来开启HTTPS

首先应先停止Nginx服务:

docker-compose stop webserver

使用curl来下载Certbot推荐的Nginx参数:

curl -sSLo nginx-conf/options-ssl-nginx.conf https://raw.githubusercontent.com/certbot/certbot/master/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf

以上命令会在nginx-conf文件夹中创建一个文件名为options-ssl-nginx.conf文件。

接下来编辑nginx.conf文件:

vim nginx.conf

将文件内容替换为如下内容:

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

        server_name example.com www.example.com;

        location ~ /.well-known/acme-challenge {
                allow all;
                root /var/www/html;
        }

        location / {
                rewrite ^ https://$host$request_uri? permanent;
        }
}

server {
        listen 443 ssl http2;
        listen [::]:443 ssl http2;
        server_name example.com www.example.com;

        index index.php index.html index.htm;

        root /var/www/html;

        server_tokens off;

        ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

        include /etc/nginx/conf.d/options-ssl-nginx.conf;

        add_header X-Frame-Options "SAMEORIGIN" always;
        add_header X-XSS-Protection "1; mode=block" always;
        add_header X-Content-Type-Options "nosniff" always;
        add_header Referrer-Policy "no-referrer-when-downgrade" always;
        add_header Content-Security-Policy "default-src * data: 'unsafe-eval' 'unsafe-inline'" always;
        # add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
        # enable strict transport security only if you understand the implications

        location / {
                try_files $uri $uri/ /index.php$is_args$args;
        }

        location ~ \.php$ {
                try_files $uri =404;
                fastcgi_split_path_info ^(.+\.php)(/.+)$;
                fastcgi_pass wordpress:9000;
                fastcgi_index index.php;
                include fastcgi_params;
                fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
                fastcgi_param PATH_INFO $fastcgi_path_info;
        }

        location ~ /\.ht {
                deny all;
        }

        location = /favicon.ico { 
                log_not_found off; access_log off; 
        }
        location = /robots.txt { 
                log_not_found off; access_log off; allow all; 
        }
        location ~* \.(css|gif|ico|jpeg|jpg|js|png)$ {
                expires max;
                log_not_found off;
        }
}

和之前一样,将example.com替换为要使用的域名。

编辑完成之后保存文件,并重新运行webserver容器:

docker-compose up -d --force-recreate --no-deps webserver

再次使用docker-compose ps命令来检查容器运行状态:

docker-compose ps

正常情况下会返回如下内容:

  Name                 Command               State            Ports                  
-----------------------------------------------------------------------------------
certbot     certbot certonly --webroot ...   Exit 0                                           
db          docker-entrypoint.sh --def ...   Up       3306/tcp, 33060/tcp                     
webserver   nginx -g daemon off;             Up       0.0.0.0:443->443/tcp, 0.0.0.0:80->80/tcp
wordpress   docker-entrypoint.sh php-fpm     Up       9000/tcp    

安装wordPress

现在,使用浏览器访问设置好的域名,就可以看到WordPress的安装界面。

首先在安装界面内选择要使用的语言,然后点击继续。

接下来需要输入网站的名称,用户名和密码以及电子邮件,也可以使用WordPress自动生成的强密码。最后还可以选择是否让搜索引擎来搜索这个网站。都设置完成后点击安装,安装完成之后会提示进行登录。

登录完成之后,WordPress就安装完毕。

设置证书自动更新

因为之前使用的Let‘s Encrypt证书的有效期只有90天,所以需要为设置自动更新来确保证书不会失效。

用cron可以定期运行脚本来使Certbot定期更新Let‘s Encrypt证书。

创建一个脚本:

vim ssl_renew.sh

在里面写入如下内容:

#!/bin/bash

COMPOSE="/usr/local/bin/docker-compose --no-ansi"
DOCKER="/usr/bin/docker"

cd /home/user/wordpress/
$COMPOSE run certbot renew --dry-run && $COMPOSE kill -s SIGHUP webserver
$DOCKER system prune -af

这个脚本可以通过docker-compose来使certbot更新证书并重新加载nginx的配置。其中/home/user/wordpress/是存放配置文件的文件夹,应更换为实际存放的位置。--dry-run参数则是用于测试更新过程。

编辑完成后保存,然后为脚本增加执行权限:

chmod +x ssl_renew.sh

完成之后使用crontab来使脚本重复运行:

sudo crontab -e

如果第一次使用的话会出现选择编辑的界面,选择最顺手的就可以:

no crontab for root - using an empty one

Select an editor.  To change later, run 'select-editor'.
  1. /bin/nano        <---- easiest
  2. /usr/bin/vim.basic
  3. /usr/bin/vim.tiny
  4. /bin/ed

Choose 1-4 [1]:

进入编辑器后,在文件最后添加以下内容:

*/5 * * * * /home/sammy/wordpress/ssl_renew.sh >> /var/log/cron.log 2>&1

其中最开始的*/5 * * * *为执行时间的设置,从左到右分别为分钟,小时,日期,月份,星期。其中符号含义如下

  • *:表示所有可能的值;
  • , :用逗号隔开的值表示列表范围,如“1,3,5,7”;
  • -:横线表示范围,如2-6;
  • /:表示间隔频率,可以和*同时使用如2-8/2或*/2

后面为要执行的命令,这里表示为每5分钟执行一次命令。

等待5分钟后,检查一下/var/log/cron.log文件来验证更新是否成功:

tail -f /var/log/cron.log

如果输出如下内容,说明更新成功:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
** DRY RUN: simulating 'certbot renew' close to cert expiry
**          (The test certificates below have not been saved.)

Congratulations, all renewals succeeded. The following certs have been renewed:
  /etc/letsencrypt/live/example.com/fullchain.pem (success)
** DRY RUN: simulating 'certbot renew' close to cert expiry
**          (The test certificates above have not been saved.)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

成功之后重新设置自动更新的间隔,如每月1日自动检查更新:

0 0 1 * * /home/sammy/wordpress/ssl_renew.sh >> /var/log/cron.log 2>&1

之后编辑自动更新脚本,删除其中的测试选项--dry-run:

#!/bin/bash

COMPOSE="/usr/local/bin/docker-compose --no-ansi"
DOCKER="/usr/bin/docker"

cd /home/sammy/wordpress/
$COMPOSE run certbot renew && $COMPOSE kill -s SIGHUP webserver
$DOCKER system prune -af

到这里,WordPress就正式安装完成了。

参考

How To Install WordPress With Docker Compose

NameSilo推广链接: