作者:xlanger
当前环境是阿里云ECS,运行系统 CentOS
,为方便以后维护和迁移,我利用 Docker
容器来运行各个独立模块。采用开源博客系统 FireKylin
,该系统是用 ThinkJS
框架开发的,框架本身则使用的是 Node.js
作为服务端语言,博客系统搭档了 MySQL
数据库,因此我需要 Node.js
、 MySQL
和 Nginx
这三个基本的 Docker
镜像,相关链接:
- Docker Docker enables developers and IT admins to build, ship and run any application, anywhere.
- ThinkJS The Web framework beyond your dreams, use the full ES6/7 features to develop Node.js applications.
- FireKylin A Simple & Fast Node Blogging Platform Base On ThinkJS 2.0 & ReactJS & ES2015+.
$ cat /etc/redhat-release
CentOS Linux release 7.2.1511 (Core)
安装 Docker
参考官方文档 Get Docker for CentOS 吧!
构建 Docker 镜像
在构建镜像过程中会遇到很多的问题,需要重复尝试,因为网络问题,每次在构建中下载需要的源码包是非常费时的,所以我将下载源码包的步骤移到了宿主机上执行,然后通过 COPY
命令拷贝至构建镜像的容器中。
MySQL
部分直接套用了官方镜像
从 Dockerfile 构建 Nodejs 镜像
$ mkdir nodejs-dockerfile
$ cd nodejs-dockerfile
$ curl -SLO https://nodejs.org/dist/v6.9.5/node-v6.9.5.tar.gz
$ vim Dockerfile # 下边提供内容
$ docker build --rm -t node:6.9.5 .
以下是构建 Nodejs
镜像的 Dockerfile
内容:
FROM centos:latest
MAINTAINER xlangersir@gmail.com
ENV NODE_VERSION 6.9.5
COPY node-v$NODE_VERSION.tar.gz .
RUN yum install -y gcc gcc-c++ make \
&& tar -zxf node-v$NODE_VERSION.tar.gz \
&& cd node-v$NODE_VERSION \
&& ./configure \
&& make -j$(getconf _NPROCESSORS_ONLN) \
&& make install \
&& npm install -g pm2 \
&& cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& echo "Asia/Shanghai" > /etc/timezone \
&& cd .. \
&& rm -Rf "node-v$NODE_VERSION" \
&& rm "node-v$NODE_VERSION.tar.gz" \
&& rm -rf /var/cache/yum
CMD ["node"]
在 Dockerfile 中 npm install -g pm2
用来管理 Node.js 服务,cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
&& echo "Asia/Shanghai" > /etc/timezone
用来修改时区。
当前node环境已基于镜像
node:8.1.0-alpine
创建,Github地址:https://github.com/xlanger/docker/tree/master/node
从 Dockerfile 构建 Nginx 镜像
$ mkdir nginx-dockerfile
$ cd nginx-dockerfile
$ curl -SLO http://nginx.org/download/nginx-1.11.10.tar.gz
$ curl -SLO https://www.openssl.org/source/openssl-1.1.0d.tar.gz
$ curl -SLO https://ftp.pcre.org/pub/pcre/pcre-8.40.tar.gz
$ curl -SLO http://www.zlib.net/zlib-1.2.11.tar.gz
$ curl -SLO https://github.com/gperftools/gperftools/releases/download/gperftools-2.5/gperftools-2.5.tar.gz
$ vim Dockerfile # 下边提供内容
$ docker build --rm -t nginx:1.11.10 .
以下是构建 Nginx
镜像的 Dockerfile
内容:
FROM centos:latest
MAINTAINER xlangersir@gmail.com
ENV NGINX_VERSION 1.11.10
WORKDIR /tmp/tmpdir
COPY nginx-$NGINX_VERSION.tar.gz .
COPY openssl-1.1.0d.tar.gz .
COPY pcre-8.40.tar.gz .
COPY zlib-1.2.11.tar.gz .
COPY gperftools-2.5.tar.gz .
COPY nginx-ct.tar.gz .
RUN CONFIG="\
--prefix=/usr/local/nginx \
--sbin-path=/usr/sbin/nginx \
--conf-path=/etc/nginx/nginx.conf \
--error-log-path=/var/log/nginx/error.log \
--http-log-path=/var/log/nginx/access.log \
--user=nginx \
--group=nginx \
--with-file-aio \
--with-http_ssl_module \
--with-http_v2_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_slice_module \
--with-http_gzip_static_module \
--with-http_random_index_module \
--with-http_secure_link_module \
--with-http_degradation_module \
--with-http_stub_status_module \
--with-http_perl_module=dynamic \
--with-http_xslt_module=dynamic \
--with-http_geoip_module=dynamic \
--with-http_image_filter_module=dynamic \
--with-pcre \
--with-pcre-jit \
--with-mail=dynamic \
--with-mail_ssl_module \
--with-stream=dynamic \
--with-stream_ssl_module \
--with-stream_ssl_preread_module \
--with-stream_realip_module \
--with-stream_geoip_module=dynamic \
--with-google_perftools_module \
--with-openssl=../openssl-1.1.0d \
--with-pcre=../pcre-8.40 \
--with-zlib=../zlib-1.2.11 \
--add-module=../nginx-ct \
"\
&& yum update -y \
&& yum install -y gcc gcc-c++ make gd-devel libunwind-devel libxslt-devel libxml2-devel perl-devel perl-ExtUtils-Embed GeoIP GeoIP-devel \
&& tar zxf nginx-ct.tar.gz \
&& tar zxf nginx-$NGINX_VERSION.tar.gz \
&& tar zxf openssl-1.1.0d.tar.gz \
&& tar zxf pcre-8.40.tar.gz \
&& tar zxf zlib-1.2.11.tar.gz \
&& tar zxf gperftools-2.5.tar.gz \
&& cd ../gperftools-2.5 \
&& ./configure --enable-shared \
&& make -j$(getconf _NPROCESSORS_ONLN) \
&& make install \
&& cd ../nginx-$NGINX_VERSION \
&& ./configure $CONFIG --with-debug \
&& make -j$(getconf _NPROCESSORS_ONLN) \
&& make install \
&& groupadd -r nginx \
&& useradd -s /sbin/nologin -g nginx nginx \
&& ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& if [ -f /etc/timezone ]; then echo "Asia/Shanghai" > /etc/timezone; fi \
&& echo "/usr/local/lib" > /etc/ld.so.conf.d/libprofiler.so.0.conf \
&& ldconfig \
&& rm -rf /var/cache/yum /tmp/tmpdir
WORKDIR /var/www
EXPOSE 80 443
CMD ["/usr/sbin/nginx", "-g", "daemon off;"]
Nginx
部分配置比较多,稍后单独写一篇记录下来…
准备启航
应用 Docker 的 create
命令创建几个容器,方便其他容器通过 run
命令中的选项 —volumes-from
直接挂载容器共享宿主机资源。
# 宿主机上的www目录挂在到nginx容器的www目录
$ docker create --name wwwcontainer -v /path/to/www:/var/www centos:latest
# 宿主机上的nginx日志保存目录挂在到nginx容器的目录
$ docker create --name nginxlogcontainer -v /path/to/log/nginx:/var/log/nginx centos:latest
# 宿主机上的ningx用TSL签名证书目录挂在到nginx容器的证书目录,通过修改nginx配置文件调用
$ docker create --name nginxsslcontainer -v /path/to/ssl:/var/ssl centos:latest
# 宿主机上的MySQL数据保存目录挂在到MySQL容器的目录
$ docker create --name datadircontainer -v /path/to/mysql/datadir:/var/lib/mysql centos:latest
运行 MySQL 容器
通过官方 MySQL
镜像创建容器,第一次启动会拉取 MySQL
官方镜像,要成功启动还需要通过 run
命令的 -e
选项为其指定必要参数,是否需要为 MySQL
的 root 用户设置密码,有以下三个选项【参考这里】: MYSQL_ROOT_PASSWORD
、 MYSQL_ALLOW_EMPTY_PASSWORD
、 MYSQL_RANDOM_ROOT_PASSWORD
。
$ docker run \
--name mysql \
--volumes-from=datadircontainer \
-v /etc/localtime:/etc/localtime:ro \
-e MYSQL_ROOT_PASSWORD=$(cat ~/docker/mysql-root-pwd) \
-itd mysql:5.7.17
其他 Run
选项说明如下:
—volumes-from=datadircontainer
挂载前面创建的容器与宿主机共享MySQL
数据目录-v /etc/localtime:/etc/localtime:ro
让容器运行时日期时间与宿主机同步-e MYSQL_ROOT_PASSWORD=$(cat ~/docker/mysql-root-pwd)
从文件读取预先准备的root用户密码。 接下来要为FireKylin
博客系统准备一个数据库用户和一个空的数据库,并给予相应的权限。
$ CREATE USER 'bloguser'@'%' identified BY 'bloguser@pwd';
$ CREATE DATABASE `firekylin` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
$ GRANT ALL ON firekylin.* TO 'bloguser'@'%';
$ FLUSH PRIVILEGES;
运行 Nodejs 容器
$ docker run \
--name nodejs \
--restart=always \
--link mysql:mysql \
--volumes-from=wwwcontainer \
-itd node:6.9.5 \
/bin/bash -c "pm2 start /var/www/xlange.com/pm2.json && while true; do ping 127.0.0.1; done"
$ docker exec nodejs cat /etc/hosts | grep mysql
192.168.0.2 mysql e12af043f8c9
Run
命令选项中 —link mysql:mysql
使得 Nodejs
容器运行起来后,可以通过别名与 MySQL
容器通讯(要链接容器的名称或者ID号:
当前容器中使用的别名) ,前者容器的IP、当前容器中使用的别名以及前者容器的ID号对将写入当前容器中的 /etc/hosts
文件中,可以通过 Docker
命令 exec
查看。其他选项说明如下:
—volumes-from=wwwcontainer
挂载前面创建的容器与宿主机共享www
目录pm2 start /var/www/xlange.com/pm2.json
使用pm2
管理Nodejs
服务while true; do ping 127.0.0.1; done
使pm2
转入后台之后,保持容器为up状态
运行 Nginx 容器
之前遇到启动 Nginx 时,找不到类库文件
libprofiler.so.0
的错误,采用export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH && /usr/sbin/nginx -g 'daemon off;'
方式将/usr/local/lib
目录追加到运行时共享类库查找目录的环境变量LD_LIBRARY_PATH
中解决该问题。现在修改 Dockerfile,其中echo "/usr/local/lib" > /etc/ld.so.conf.d/libprofiler.so.0.conf && ldconfig
也可避免了这个错误。
$ docker run --rm -it nginx:1.11.10 /usr/sbin/nginx -g 'daemon off;'
/usr/sbin/nginx: error while loading shared libraries: libprofiler.so.0: cannot open shared object file: No such file or directory
$ docker run \
--name nginx \
--restart=always \
--link nodejs:nodejs \
--volumes-from=wwwcontainer \
--volumes-from=nginxsslcontainer \
--volumes-from=nginxlogcontainer \
-v /path/to/nginx.conf:/etc/nginx/nginx.conf \
-v /path/to/xlange.com.conf:/etc/nginx/conf.d/xlange.com.conf \
-itd -p 80:80 -p 443:443 nginx:1.11.10 \
/bin/bash -c "/usr/sbin/nginx -g 'daemon off;'"
Run
选项说明如下:
—link nodejs:nodejs
—volumes-from=wwwcontainer
共享宿主机www
目录—volumes-from=nginxsslcontainer
共享宿主机TSL签名证书文件目录—volumes-from=nginxlogcontainer
共享宿主机Nginx日志文件目录/usr/sbin/nginx -g 'daemon off;'
使Nginx
作为后台驻留程序运行,保持容器为up状态Nginx
配置参考:
server {
listen 443 ssl http2 default_server;
listen [::]:443 ssl http2 default_server;
server_name xlange.com www.xlange.com;
root /var/www/xlange.com/www;
index index.js index.html index.htm;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_session_tickets off; # Requires nginx >= 1.5.9
ssl_ecdh_curve secp384r1; # Requires nginx >= 1.1.0
resolver 233.5.5.5 119.29.29.29 valid=300s;
resolver_timeout 5s;
proxy_hide_header Vary;
proxy_hide_header X-Runtime;
proxy_hide_header X-Version;
proxy_hide_header X-Powered-By;
proxy_ignore_headers Set-Cookie;
add_header Cache-Control no-cache;
add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https://hm.baidu.com h ttps://www.google-analytics.com ; style-src 'self' 'unsafe-inline'; img-src 'self' https://hm.baidu.com https://www.google-analytics.com;";
# Strict Transport Security (HSTS)
add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload" always;
# Public Key Pinning (HPKP)
add_header Public-Key-Pins 'pin-sha256="NO1aizeokVCU2u4ZQob2YJI7OAieh8lcfzcYkZCVUs8="; pin-sha256="JMg0zx3zzkB61hGibJBlmv//NPcPH8lqIFlGFV5I2Ts="; max-age=2592000; includeSubDomains' always;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+ECDSA+AES128:EECDH+aRSA+AES128:RSA+AES128:EECDH+ECDSA+AES256:EECDH+aRSA+AES256:RSA+AES256:EECDH+ECDSA+3DES:EECDH+aRSA+3DES:RSA+3DES:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4:!DSS;
ssl_prefer_server_ciphers on;
# Forward Secrecy (DHE and ECDHE)
ssl_dhparam /var/ssl/dhparams.pem; # >2048-bits recommended
# CT
ssl_ct on;
# RSA
ssl_certificate /var/ssl/xlange.com/fullchain.pem;
ssl_certificate_key /var/ssl/xlange.com/privkey.pem;
ssl_ct_static_scts /var/ssl/xlange.com;
# ECC
ssl_certificate /var/ssl/xlange.com_ecc/fullchain.cer;
ssl_certificate_key /var/ssl/xlange.com_ecc/xlange.com.key;
ssl_ct_static_scts /var/ssl/xlange.com_ecc;
# Offensive Security Certified Professional (OCSP) stapling
ssl_stapling on; # Requires nginx >= 1.3.7
ssl_stapling_verify on; # Requires nginx >= 1.3.7
ssl_stapling_file /var/ssl/xlange.com_ecc/stapling.ocsp;
ssl_trusted_certificate /var/ssl/xlange.com_ecc/fullchain.cer;
# Load configuration files for the default server block.
include /etc/nginx/default.d/*.conf;
if ($http_host != xlange.com) {
rewrite (.*) https://xlange.com$1;
}
location ~* \.html$ {
rewrite ^/(.*)\.html$ /$1 last;
}
location / {
proxy_http_version 1.1;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-NginX-Proxy true;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_pass http://node-web;
proxy_redirect off;
}
location = /development.js {
deny all;
}
location = /testing.js {
deny all;
}
location = /production.js {
deny all;
}
location ~ /static/ {
etag on;
expires max;
}
}
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name xlange.com www.xlange.com;
return 301 https://xlange.com$request_uri;
}
upstream node-web {
server nodejs:8080 weight=1 max_fails=3 fail_timeout=30s;
}
安装配置 FireKylin
访问 https://domain.com/index/install
安装配置 FireKylin
,这样博客就可以跑起来了。