02月09, 2017

个人博客搭建

当前环境是阿里云ECS,运行系统 CentOS,为方便以后维护和迁移,我利用 Docker 容器来运行各个独立模块。采用开源博客系统 FireKylin,该系统是用 ThinkJS 框架开发的,框架本身则使用的是 Node.js 作为服务端语言,博客系统搭档了 MySQL 数据库,因此我需要 Node.jsMySQLNginx 这三个基本的 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_PASSWORDMYSQL_ALLOW_EMPTY_PASSWORDMYSQL_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;
}

Nginx 配置及优化内容较多,后面补充博文记录...

安装配置 FireKylin

访问 https://domain.com/index/install 安装配置 FireKylin,这样博客就可以跑起来了,然后优化去...

本文链接:https://xlange.com/post/first-post

-- EOF --

Comments

?