之前最流行的架构是 LNMP(Linux+Nginx+MySQL+PHP) ,随着容器技术的进步和火热,现在对于中小型网站可以使用 DNMP 架构,就是将 Linux 换为了 Docker 。
总览
使用官方镜像很多时候无法满足使用的需求,比如 NGINX 官方镜像并不支持比如 Brotli 等模块,在 NGINX v1.9.11 引入了 Dynamic Module ,一开可以动态加载模块,节约内存,用不到的模块可以在启动程序时不加载。
且 Docker 官方的容器中不提供基于 Alpine 的 MySQL / MariaDB 的镜像,因此还需要自行构造。
本文所需文件全部在 PlanD - Github 中提供。
过程
NGINX
先构建支持 Brotli 的 NGINX 官方容器,在此使用 Multi-Stage 技术基于官方镜像进行构建。
FROM nginx:alpine-perl AS builder
LABEL maintainer="VVavE Docker Maintainers <waveworkshop@outlook.com>"
ENV NGX_BROTLI_COMMIT e505dce68acc190cc5a1e780a3b0275e39f160ca
# For latest build deps, see https://github.com/nginxinc/docker-nginx/blob/master/mainline/alpine/Dockerfile
RUN apk add --no-cache --virtual .build-deps \
gcc \
libc-dev \
make \
openssl-dev \
pcre-dev \
zlib-dev \
linux-headers \
libxslt-dev \
gd-dev \
geoip-dev \
perl-dev \
libedit-dev \
mercurial \
bash \
alpine-sdk \
findutils \
git \
curl \
&& mkdir -p /usr/src \
&& cd /usr/src \
&& git clone --recursive https://github.com/google/ngx_brotli.git \
&& curl -fSL https://nginx.org/download/nginx-$NGINX_VERSION.tar.gz -o nginx.tar.gz \
&& tar -zxC /usr/src -f nginx.tar.gz \
&& NGX_BROTLI_DIR="$(pwd)/ngx_brotli" \
&& cd /usr/src/nginx-$NGINX_VERSION \
&& ./configure --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 \
--with-perl_modules_path=/usr/lib/perl5/vendor_perl \
--user=nginx \
--group=nginx \
--with-compat \
--with-file-aio \
--with-threads \
--with-http_addition_module \
--with-http_auth_request_module \
--with-http_dav_module \
--with-http_flv_module \
--with-http_gunzip_module \
--with-http_gzip_static_module \
--with-http_mp4_module \
--with-http_random_index_module \
--with-http_realip_module \
--with-http_secure_link_module \
--with-http_slice_module \
--with-http_ssl_module \
--with-http_stub_status_module \
--with-http_sub_module \
--with-http_v2_module --with-mail \
--with-mail_ssl_module --with-stream \
--with-stream_realip_module \
--with-stream_ssl_module \
--with-stream_ssl_preread_module \
--add-dynamic-module=$NGX_BROTLI_DIR \
--with-cc-opt='-Os -fomit-frame-pointer' \
--with-ld-opt=-Wl,--as-needed \
&& make -j$(getconf _NPROCESSORS_ONLN) \
&& make install \
&& strip /usr/lib/nginx/modules/*.so \
&& rm -rf /usr/src/
FROM nginx:alpine-perl
# Extract the dynamic module from the builder image
COPY --from=builder /usr/lib/nginx/modules/ngx_http_brotli_filter_module.so /usr/lib/nginx/modules/ngx_http_brotli_filter_module.so
COPY --from=builder /usr/lib/nginx/modules/ngx_http_brotli_static_module.so /usr/lib/nginx/modules/ngx_http_brotli_static_module.so
RUN apk add --no-cache iproute2
EXPOSE 80
STOPSIGNAL SIGTERM
CMD ["nginx", "-g", "daemon off;"]
小贴士:本 Dockerfile 加入了三个额外组件 Nchan Brotli Header ,具体功能和用途请自行查询,本文不再赘述。
配置
使用上述配置构建的容器需要修改默认 NGINX 配置文件以便支持 Brotli 等额外插件
load_module modules/ngx_http_brotli_filter_module.so;
load_module modules/ngx_http_brotli_static_module.so;
...
http {
...
brotli on;
brotli_comp_level 6;
brotli_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss application/javascript image/svg+xml;
...
}
MariaDB
FROM alpine:3.10
LABEL maintainer="VVavE Docker Maintainers <waveworkshop@outlook.com>"
ENV LC_ALL=en_GB.UTF-8
RUN mkdir /docker-entrypoint-initdb.d && \
apk -U upgrade && \
apk add --no-cache mariadb mariadb-client && \
apk add --no-cache tzdata && \
# clean up
rm -rf /var/cache/apk/*
# comment out a few problematic configuration values
RUN sed -Ei 's/^(bind-address|log)/#&/' /etc/my.cnf && \
sed -i 's/^skip-networking/#&/' /etc/my.cnf.d/mariadb-server.cnf && \
# don't reverse lookup hostnames, they are usually another container
sed -i '/^\[mysqld]$/a skip-host-cache\nskip-name-resolve' /etc/my.cnf && \
# always run as user mysql
sed -i '/^\[mysqld]$/a user=mysql' /etc/my.cnf && \
# allow custom configurations
echo -e '\n!includedir /etc/mysql/conf.d/' >> /etc/my.cnf && \
mkdir -p /etc/mysql/conf.d/
VOLUME /var/lib/mysql
COPY docker-entrypoint.sh /usr/local/bin/
RUN ln -s usr/local/bin/docker-entrypoint.sh / # backwards compat
ENTRYPOINT ["docker-entrypoint.sh"]
EXPOSE 3306
# Default arguments passed to ENTRYPOINT if no arguments are passed when starting container
CMD ["mysqld"]
还需要额外的启动脚本,可在仓库中进行下载,也可从下面粘贴。
#!/bin/sh
set -eo pipefail
# set -x
# if command starts with an option, prepend mysqld
if [ "${1:0:1}" = '-' ]; then
set -- mysqld_safe "$@"
fi
# usage: file_env VAR [DEFAULT]
# ie: file_env 'XYZ_DB_PASSWORD' 'example'
# (will allow for "$XYZ_DB_PASSWORD_FILE" to fill in the value of
# "$XYZ_DB_PASSWORD" from a file, especially for Docker's secrets feature)
file_env() {
var=$1
file_var="${var}_FILE"
var_value=$(printenv $var || true)
file_var_value=$(printenv $file_var || true)
default_value=$2
if [ -n "$var_value" -a -n "$file_var_value" ]; then
echo >&2 "error: both $var and $file_var are set (but are exclusive)"
exit 1
fi
if [ -z "${var_value}" ]; then
if [ -z "${file_var_value}" ]; then
export "${var}"="${default_value}"
else
export "${var}"="${file_var_value}"
fi
fi
unset "$file_var"
}
# Fetch value from server config
# We use mysqld --verbose --help instead of my_print_defaults because the
# latter only show values present in config files, and not server defaults
_get_config() {
conf="$1"
mysqld --verbose --help --log-bin-index="$(mktemp -u)" 2>/dev/null | awk '$1 == "'"$conf"'" { print $2; exit }'
}
DATA_DIR="$(_get_config 'datadir')"
# Initialize database if necessary
if [ ! -d "$DATA_DIR/mysql" ]; then
file_env 'MYSQL_ROOT_PASSWORD'
if [ -z "$MYSQL_ROOT_PASSWORD" -a -z "$MYSQL_ALLOW_EMPTY_PASSWORD" -a -z "$MYSQL_RANDOM_ROOT_PASSWORD" ]; then
echo >&2 'error: database is uninitialized and password option is not specified '
echo >&2 ' You need to specify one of MYSQL_ROOT_PASSWORD, MYSQL_ALLOW_EMPTY_PASSWORD and MYSQL_RANDOM_ROOT_PASSWORD'
exit 1
fi
mkdir -p "$DATA_DIR"
chown mysql: "$DATA_DIR"
echo 'Initializing database'
mysql_install_db --user=mysql --datadir="$DATA_DIR" --rpm
chown -R mysql: "$DATA_DIR"
echo 'Database initialized'
# Start mysqld to config it
mysqld_safe --skip-networking --nowatch
mysql_options='--protocol=socket -uroot'
# Execute mysql statement
# statement can be passed directly or by HEREDOC
execute() {
statement="$1"
if [ -n "$statement" ]; then
mysql -ss $mysql_options -e "$statement"
else
cat /dev/stdin | mysql -ss $mysql_options
fi
}
for i in `seq 30 -1 0`; do
if execute 'SELECT 1' &> /dev/null; then
break
fi
echo 'MySQL init process in progress...'
sleep 1
done
if [ "$i" = 0 ]; then
echo >&2 'MySQL init process failed.'
exit 1
fi
if [ -z "$MYSQL_INITDB_SKIP_TZINFO" ]; then
# sed is for https://bugs.mysql.com/bug.php?id=20545
mysql_tzinfo_to_sql /usr/share/zoneinfo | \
sed 's/Local time zone must be set--see zic manual page/FCTY/' | \
mysql $mysql_options mysql
fi
if [ -n "$MYSQL_RANDOM_ROOT_PASSWORD" ]; then
export MYSQL_ROOT_PASSWORD="$(tr -dc _A-Z-a-z-0-9 < /dev/urandom | head -c10)"
echo "GENERATED ROOT PASSWORD: $MYSQL_ROOT_PASSWORD"
fi
# Create root user, set root password, drop useless table
# Delete root user except for
execute <<SQL
-- What's done in this file shouldn't be replicated
-- or products like mysql-fabric won't work
SET @@SESSION.SQL_LOG_BIN=0;
DELETE FROM mysql.user WHERE user NOT IN ('mysql.sys', 'mysqlxsys', 'root') OR host NOT IN ('localhost') ;
SET PASSWORD FOR 'root'@'localhost'=PASSWORD('${MYSQL_ROOT_PASSWORD}') ;
GRANT ALL ON *.* TO 'root'@'localhost' WITH GRANT OPTION ;
DROP DATABASE IF EXISTS test ;
FLUSH PRIVILEGES ;
SQL
# https://mariadb.com/kb/en/library/mariadb-environment-variables/
export MYSQL_PWD="$MYSQL_ROOT_PASSWORD"
# Create root user for $MYSQL_ROOT_HOST
file_env 'MYSQL_ROOT_HOST' '%'
if [ "$MYSQL_ROOT_HOST" != 'localhost' ]; then
execute <<SQL
CREATE USER 'root'@'${MYSQL_ROOT_HOST}' IDENTIFIED BY '${MYSQL_ROOT_PASSWORD}' ;
GRANT ALL ON *.* TO 'root'@'${MYSQL_ROOT_HOST}' WITH GRANT OPTION ;
FLUSH PRIVILEGES ;
SQL
fi
file_env 'MYSQL_DATABASE'
if [ "$MYSQL_DATABASE" ]; then
execute "CREATE DATABASE IF NOT EXISTS \`$MYSQL_DATABASE\` ;"
fi
file_env 'MYSQL_USER'
file_env 'MYSQL_PASSWORD'
if [ "$MYSQL_USER" -a "$MYSQL_PASSWORD" ]; then
execute "CREATE USER '$MYSQL_USER'@'%' IDENTIFIED BY '$MYSQL_PASSWORD' ;"
if [ "$MYSQL_DATABASE" ]; then
execute "GRANT ALL ON \`$MYSQL_DATABASE\`.* TO '$MYSQL_USER'@'%' ;"
fi
execute 'FLUSH PRIVILEGES ;'
fi
# Database cannot be specified when creating user,
# otherwise it will fail with "Unknown database"
if [ "$MYSQL_DATABASE" ]; then
mysql_options="$mysql_options \"$MYSQL_DATABASE\""
fi
echo
for f in /docker-entrypoint-initdb.d/*; do
case "$f" in
*.sh) echo "$0: running $f"; . "$f" ;;
*.sql) echo "$0: running $f"; execute < "$f"; echo ;;
*.sql.gz) echo "$0: running $f"; gunzip -c "$f" | execute; echo ;;
*) echo "$0: ignoring $f" ;;
esac
echo
done
if ! mysqladmin -uroot --password="$MYSQL_PWD" shutdown; then
echo >&2 'Shutdown failed'
exit 1
fi
echo
echo 'MySQL init process done. Ready for start up.'
echo
fi
chown -R mysql: "$DATA_DIR"
exec "$@"
PHP
FROM php:fpm-alpine
LABEL maintainer="VVavE Docker Maintainers <waveworkshop@outlook.com>"
RUN docker-php-ext-install pdo_mysql pdo opcache mysqli\
&& apk add --no-cache libpng-dev libxml2-dev \
&& docker-php-ext-install gd xml xmlrpc \
&& apk add --no-cache autoconf build-base \
&& pecl install igbinary \
&& docker-php-ext-enable igbinary \
&& pecl install msgpack \
&& docker-php-ext-enable msgpack \
&& pecl install redis \
&& docker-php-ext-enable redis \
&& apk add --no-cache libmemcached-libs \
libmemcached-dev \
libevent-dev \
zlib-dev \
&& pecl install memcached \
&& docker-php-ext-enable memcached \
&& apk del --no-cache autoconf \
build-base \
libmemcached-dev \
libevent-dev \
zlib-dev
小贴士:从官方镜像为基础进行构建,增加了例如 mysqli pdo opcache 等常用插件,可根据需求进行增加或者删除。
额外
根据仓库中的说明准备好项目文件和所需配置文件即可配置启动
Compose
version: '3.7'
services:
nginx:
image: nginx:alpine-brotli
build:
context: ./nginx/
depends_on:
- php
ports:
- 80:80
- 443:443
networks:
- internal
restart: on-failure
volumes:
- /data/mystack/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- /data/mystack/nginx/conf.d:/etc/nginx/conf.d
- /data/mystack/nginx/logs.d:/etc/nginx/logs.d
- /data/mystack/nginx/cert.d:/etc/nginx/cert.d
- /data/mystack/www:/var/www
php:
image: php-fpm:alpine
build:
context: ./php/
depends_on:
- mariadb
networks:
- internal
restart: on-failure
volumes:
- /data/mystack/php/php.ini:/usr/local/etc/php/php.ini:ro
- /data/mystack/www:/var/www
mariadb:
image: mariadb:alpine
build:
context: ./mariadb/
depends_on:
- memcached
ports:
- 127.0.0.1:3306:3306
networks:
- internal
restart: on-failure
volumes:
- myRdsVol:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: 'cz72sLJtfWYa983nEUIYA' # 示例密码,使用前请替换
command: ['mysqld', '--character-set-server=utf8mb4', '--collation-server=utf8mb4_unicode_ci']
memcached:
image: memcached:alpine
depends_on:
- redis
ports:
- 127.0.0.1:11211:11211
networks:
- internal
restart: on-failure
redis:
image: redis:alpine
ports:
- 127.0.0.1:6379:6379
networks:
- internal
restart: on-failure
volumes:
- myKvsVol:/data
networks:
internal:
volumes:
myRdsVol:
myKvsVol:
附录
参考链接
本文由 柒 创作,采用 知识共享署名4.0
国际许可协议进行许可。
转载本站文章前请注明出处,文章作者保留所有权限。
最后编辑时间: 2019-11-14 23:59 PM
容器化之后怎么方便地实现数据库备份呢?
容器化的实例和原生部署的性能差距大不大?
应该不会影响的,或者说差距不大。
博主能指导一下容器的问题么?有偿!
有用,mark一下