前几篇文章写的都是一些 Docker 相关的内容,这一篇也不例外。其实在第一篇有关博客搭建的博文写完之后,就看了一些 Docker 相关的内容,体会到了 Docker 的强大和易用,因此萌生了将博客搭建在 Docker 上的想法。折腾完之后又模拟了更换服务器的过程,博客在 Docker 化之后,迁移起来确实非常方便。
从搭建完成到现在已经一个多月了,运行得还算稳定(虽然并没有更新几篇博客,233)。整理一下搭建的全部过程,希望能帮助到有同样需求的人。
本人目前使用的 Linux 发行版是 Debian 9,其他发行版可能需要对相关命令进行调整。
Docker 相关知识的入门,可以参考 yeasy 大大的 Docker — 从入门到实践。
预备工作
本文让 Nginx、PHP、MySQL 各自运行在不同的容器中,并使用 Docker Compose 对各个容器进行管理,因此需要先安装 Docker 和 Docker Compose。
安装 Docker
推荐使用 Docker 官方的脚本进行安装,一条命令即可完成安装。如需了解官方一键安装脚本对发行版的支持情况,可以在脚本中的 SUPPORT_MAP
变量中找到脚本目前支持的发行版。
可通过如下命令安装 Docker:
curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh
如果是国内的服务器,可以添加 --mirror Aliyun
加速下载。
等待安装完成后,运行如下命令,验证安装是否成功。
sudo docker version
可将当前用户加入 docker
组,这样以后执行 Docker 相关命令时就不需要加 sudo
了。
sudo usermod -aG docker anguiao
安装 Docker Compose
参照官方文档进行安装:
sudo curl -L "https://github.com/docker/compose/releases/download/1.23.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
发文时的稳定版本为 1.23.2, 请使用当前的最新稳定版本。
验证是否安装成功:
docker-compose version
构建镜像
官方的 Docker Hub 中已经提供了大量现成的镜像,可以直接使用。但是现成的镜像有时候并不能满足我们的需求,所以需要自行构建镜像。为了缩减镜像体积,这里都会使用 Alpine Linux 作为基础镜像。
下文中的 Dockerfile 可能不能及时更新,最新的更新会放在 GitHub 上。
构建 Nginx 镜像
发文时 Alpine Linux 已升级至 3.9,可以直接安装 OpenSSL 1.1.1 以上版本,所以除非需要启用 3DES 等不够安全的加密算法(为了兼容 IE8),已经不需要自行编译 OpenSSL,即可启用 TLS 1.3。目前 Nginx 的官方 Docker 镜像已经基于 Alpine Linux 3.9,所以如果只需要启用 TLS 1.3,可以直接使用官方镜像,不需要自行构建镜像了。
由于需要启用 Brotli,这里还是选择自行构建镜像。Nginx 官方提供了 Dockerfile,在其之上稍作修改,即可为 Nginx 启用 Brotli 压缩算法。
FROM alpine:3.9
ENV TZ=Asia/Shanghai
ENV NGINX_VERSION 1.15.8
RUN NGINX_GPG_KEYS=B0F4253373F8F6F510D42178520A9993A1C052F8 \
&& CONFIG="\
--prefix=/etc/nginx \
--sbin-path=/usr/sbin/nginx \
--modules-path=/usr/lib/nginx/modules \
--conf-path=/etc/nginx/nginx.conf \
--error-log-path=/var/log/nginx/error.log \
--http-log-path=/var/log/nginx/access.log \
--pid-path=/var/run/nginx.pid \
--lock-path=/var/run/nginx.lock \
--http-client-body-temp-path=/var/cache/nginx/client_temp \
--http-proxy-temp-path=/var/cache/nginx/proxy_temp \
--http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp \
--http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp \
--http-scgi-temp-path=/var/cache/nginx/scgi_temp \
--user=www-data \
--group=www-data \
--with-http_ssl_module \
--with-http_realip_module \
--with-http_addition_module \
--with-http_sub_module \
--with-http_dav_module \
--with-http_flv_module \
--with-http_mp4_module \
--with-http_gunzip_module \
--with-http_gzip_static_module \
--with-http_random_index_module \
--with-http_secure_link_module \
--with-http_stub_status_module \
--with-http_auth_request_module \
--with-http_xslt_module=dynamic \
--with-http_image_filter_module=dynamic \
--with-http_geoip_module=dynamic \
--with-threads \
--with-stream \
--with-stream_ssl_module \
--with-stream_ssl_preread_module \
--with-stream_realip_module \
--with-stream_geoip_module=dynamic \
--with-http_slice_module \
--with-mail \
--with-mail_ssl_module \
--with-compat \
--with-file-aio \
--with-http_v2_module \
--add-module=/usr/src/ngx_brotli \
" \
&& addgroup -g 82 -S www-data \
&& adduser -u 82 -D -S -h /var/cache/nginx -s /sbin/nologin -G www-data www-data \
\
&& apk update \
\
&& apk add --virtual .build-deps \
gcc \
libc-dev \
make \
openssl-dev \
pcre-dev \
zlib-dev \
linux-headers \
curl \
gnupg1 \
libxslt-dev \
gd-dev \
geoip-dev \
git \
&& curl -fSL https://nginx.org/download/nginx-$NGINX_VERSION.tar.gz -o nginx.tar.gz \
&& curl -fSL https://nginx.org/download/nginx-$NGINX_VERSION.tar.gz.asc -o nginx.tar.gz.asc \
&& export GNUPGHOME="$(mktemp -d)" \
&& gpg --keyserver ha.pool.sks-keyservers.net --recv-keys "$NGINX_GPG_KEYS" \
&& gpg --batch --verify nginx.tar.gz.asc nginx.tar.gz \
&& rm -rf "$GNUPGHOME" nginx.tar.gz.asc \
&& mkdir -p /usr/src \
&& tar -zxC /usr/src -f nginx.tar.gz \
&& rm nginx.tar.gz \
&& git clone https://github.com/google/ngx_brotli.git /usr/src/ngx_brotli \
&& cd /usr/src/ngx_brotli \
&& git submodule update --init \
&& cd /usr/src/nginx-$NGINX_VERSION \
&& ./configure $CONFIG --with-debug \
&& make -j$(getconf _NPROCESSORS_ONLN) \
&& mv objs/nginx objs/nginx-debug \
&& mv objs/ngx_http_xslt_filter_module.so objs/ngx_http_xslt_filter_module-debug.so \
&& mv objs/ngx_http_image_filter_module.so objs/ngx_http_image_filter_module-debug.so \
&& mv objs/ngx_http_geoip_module.so objs/ngx_http_geoip_module-debug.so \
&& mv objs/ngx_stream_geoip_module.so objs/ngx_stream_geoip_module-debug.so \
&& ./configure $CONFIG \
&& make -j$(getconf _NPROCESSORS_ONLN) \
&& make install \
&& rm -rf /etc/nginx/html/ \
&& mkdir /etc/nginx/conf.d/ \
&& mkdir -p /usr/share/nginx/html/ \
&& install -m644 html/index.html /usr/share/nginx/html/ \
&& install -m644 html/50x.html /usr/share/nginx/html/ \
&& install -m755 objs/nginx-debug /usr/sbin/nginx-debug \
&& install -m755 objs/ngx_http_xslt_filter_module-debug.so /usr/lib/nginx/modules/ngx_http_xslt_filter_module-debug.so \
&& install -m755 objs/ngx_http_image_filter_module-debug.so /usr/lib/nginx/modules/ngx_http_image_filter_module-debug.so \
&& install -m755 objs/ngx_http_geoip_module-debug.so /usr/lib/nginx/modules/ngx_http_geoip_module-debug.so \
&& install -m755 objs/ngx_stream_geoip_module-debug.so /usr/lib/nginx/modules/ngx_stream_geoip_module-debug.so \
&& ln -s ../../usr/lib/nginx/modules /etc/nginx/modules \
&& strip /usr/sbin/nginx* \
&& strip /usr/lib/nginx/modules/*.so \
&& rm -rf /usr/src/nginx-$NGINX_VERSION /usr/src/ngx_brotli \
\
# Bring in gettext so we can get `envsubst`, then throw
# the rest away. To do this, we need to install `gettext`
# then move `envsubst` out of the way so `gettext` can
# be deleted completely, then move `envsubst` back.
&& apk add --virtual .gettext gettext \
&& mv /usr/bin/envsubst /tmp/ \
\
&& runDeps="$( \
scanelf --needed --nobanner --format '%n#p' /usr/sbin/nginx /usr/lib/nginx/modules/*.so /tmp/envsubst \
| tr ',' '\n' \
| sort -u \
| awk 'system("[ -e /usr/local/lib/" $1 " ]") == 0 { next } { print "so:" $1 }' \
)" \
&& apk add --virtual .nginx-rundeps $runDeps \
&& apk del .build-deps \
&& apk del .gettext \
&& mv /tmp/envsubst /usr/local/bin/ \
\
# Bring in tzdata so users could set the timezones through the environment
# variables
&& apk add tzdata \
&& echo "${TZ}" > /etc/timezone \
&& ln -sf /usr/share/zoneinfo/${TZ} /etc/localtime \
\
# forward request and error logs to docker log collector
&& ln -sf /dev/stdout /var/log/nginx/access.log \
&& ln -sf /dev/stderr /var/log/nginx/error.log \
\
&& rm /var/cache/apk/*
COPY nginx.conf /etc/nginx/nginx.conf
COPY nginx.vh.default.conf /etc/nginx/conf.d/default.conf
EXPOSE 80 443
STOPSIGNAL SIGTERM
CMD ["nginx", "-g", "daemon off;"]
Dockerfile 末尾复制进镜像的配置文件,也都来自 Nginx 的官方镜像。
准备好 Dockerfile 和配置文件之后,就可以进行构建了:
docker build -t anguiao/nginx-brotli .
如果不想自行构建,也可以使用我已经构建好的镜像 anguiao/nginx-brotli。
构建 PHP 镜像
由于官方提供的 PHP 镜像中缺少一些扩展,因此同样需要自行构建镜像。与 Nginx 不同的是,这里只需将官方镜像作为基础镜像,然后安装所需扩展即可,不需要从头构建镜像。
这里添加了 PDO_MYSQL(如果使用 MySQL 作为 Typecho 的数据库,则需安装此扩展)和 GD(部分主题或插件需要,比如我使用的 handsome 主题)两个扩展:
FROM php:7.3.2-fpm-alpine
ENV TZ=Asia/Shanghai
RUN apk update \
&& apk add tzdata \
&& echo "${TZ}" > /etc/timezone \
&& ln -sf /usr/share/zoneinfo/${TZ} /etc/localtime \
\
&& docker-php-ext-install -j$(nproc) pdo_mysql \
\
&& apk add \
freetype \
freetype-dev \
libpng \
libpng-dev \
libjpeg-turbo \
libjpeg-turbo-dev \
&& docker-php-ext-configure gd \
--with-freetype-dir=/usr/include/ \
--with-jpeg-dir=/usr/include/ \
&& docker-php-ext-install -j$(nproc) gd \
&& apk del \
freetype-dev \
libpng-dev \
libjpeg-turbo-dev \
\
&& rm /var/cache/apk/*
COPY ./php.ini /usr/local/etc/php/
COPY ./www.conf /usr/local/etc/php-fpm.d/
同样也可从官方镜像获取配置文件,然后进行构建:
docker build -t anguiao/php-fpm .
和 Nginx 一样,我也将自己构建的镜像发布到了 Docker Hub,可以直接使用我构建的镜像 anguiao/php-fpm。
配置与安装
目录结构
大致的目录结构如下:
.
├── conf 配置文件
├── db 数据库持久化目录
├── docker-compose.yml Docker Compose 配置
├── log 日志
├── ssl SSL 证书
└── www 站点根目录
Nginx 相关配置
Nginx 全局配置
这里根据我自己的需要,对 Nginx 的全局配置文件进行了修改,主要是为了启用 Brotli 压缩算法。
user www-data www-data;
worker_processes 1;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
charset UTF-8;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 60;
gzip on;
gzip_vary on;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_min_length 1000;
gzip_proxied any;
gzip_disable "msie6";
gzip_http_version 1.0;
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript image/svg+xml;
brotli on;
brotli_comp_level 6;
brotli_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript image/svg+xml;
include /etc/nginx/conf.d/*.conf;
}
站点配置
在 Nginx 全局配置文件的最后,包含了 conf.d
目录下的配置文件,这使得我们可以将站点的相关配置写在一个独立的配置文件当中。
server {
listen 80;
listen [::]:80;
server_name test.anguiao.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2 fastopen=3 reuseport;
listen [::]:443 ssl http2 fastopen=3 reuseport;
server_name test.anguiao.com;
index index.php index.html index.htm index.nginx-debian.html;
root /var/www/html;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log warn;
ssl_certificate /var/www/ssl/fullchain.cer;
ssl_certificate_key /var/www/ssl/anguiao.com.key;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;
ssl_dhparam /var/www/ssl/dhparam.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256';
ssl_prefer_server_ciphers on;
add_header Strict-Transport-Security max-age=15768000;
ssl_stapling on;
ssl_early_data on;
resolver 114.114.114.114 valid=300s;
resolver_timeout 10s;
if (!-e $request_filename) {
rewrite ^(.*)$ /index.php$1 last;
}
location ~ .*\.php(\/.*)*$ {
fastcgi_pass fpm:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
这里主要做了 HTTPS 和 Typecho 伪静态化相关的配置。
编写 docker-compose.yml
Docker Compose 默认使用当前目录下的 docker-compose.yml
来编排容器。
version: '3'
services:
nginx:
image: anguiao/nginx-brotli
container_name: blog_nginx
ports:
- 80:80
- 443:443
volumes:
- ./www/:/var/www/html/:rw
- ./ssl/:/var/www/ssl/:ro
- ./conf/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./conf/nginx/test.anguiao.com.conf:/etc/nginx/conf.d/test.anguiao.com.conf:ro
restart: always
depends_on:
- fpm
- mysql
fpm:
image: anguiao/php-fpm
container_name: blog_fpm
expose:
- 9000
volumes:
- ./www/:/var/www/html/:rw
- ./log/php-fpm/:/var/log/php-fpm/:rw
restart: always
mysql:
image: mysql:5.7
container_name: blog_db
expose:
- 3306
volumes:
- ./db:/var/lib/mysql/:rw
restart: always
environment:
- MYSQL_ROOT_PASSWORD=123456
- MYSQL_DATABASE=typecho
- MYSQL_USER=typecho
- MYSQL_PASSWORD=123456
MySQL 相关的环境变量可以自行设置。
安装 Typecho
下载 Typecho 最新版本,并将文件移至站点根目录:
wget http://typecho.org/downloads/1.1-17.10.30-release.tar.gz
tar xvf 1.1-17.10.30-release.tar.gz
mv build www
rm 1.1-17.10.30-release.tar.gz
为了让容器获得站点相关文件的写入权限,需要更改相关文件的所有者。通过查看 PHP 镜像的 Dockerfile,得知其 www-data
用户的 UID 和 GID 均为 82,因此可以这样设置:
sudo chown -R 82:82 www
至此配置完成,可以启动了:
docker-compose up -d
在浏览器中输入 URL,完成最后的安装步骤即可。
需要注意的是,数据库配置中的“数据库地址”应填写“mysql”(与 docker-compose.yml 相对应),其他配置与环境变量保持一致即可。
其他配置可照常设置,整个安装过程就完成了。
博客迁移
在将博客 Docker 化之后,博客的迁移就变得十分方便了,只需将整个目录打包传输至新服务器,然后运行 docker-compose up -d
即可。Docker 会自动拉取镜像并启动容器,博客的迁移就完成了。
5 条评论
old wang, I am coming. |´・ω・)ノ
欸 好欸 這評論自帶表情!! |´・ω・)ノ
CQHTTP观光团~
写的还行OωO
CQHTTP观光团+1 ∠( ᐛ 」∠)_
CQHTTP观光团+2 ( ๑´•ω•) "(ㆆᴗㆆ)