作者:xlanger

原文:https://xlange.com/post/first-post.html

当前环境是阿里云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+.
  1. $ cat /etc/redhat-release
  2. CentOS Linux release 7.2.1511 (Core)

安装 Docker

参考官方文档 Get Docker for CentOS 吧!

构建 Docker 镜像

在构建镜像过程中会遇到很多的问题,需要重复尝试,因为网络问题,每次在构建中下载需要的源码包是非常费时的,所以我将下载源码包的步骤移到了宿主机上执行,然后通过 COPY 命令拷贝至构建镜像的容器中。

MySQL 部分直接套用了官方镜像

从 Dockerfile 构建 Nodejs 镜像

  1. $ mkdir nodejs-dockerfile
  2. $ cd nodejs-dockerfile
  3. $ curl -SLO https://nodejs.org/dist/v6.9.5/node-v6.9.5.tar.gz
  4. $ vim Dockerfile # 下边提供内容
  5. $ docker build --rm -t node:6.9.5 .

以下是构建 Nodejs 镜像的 Dockerfile 内容:

  1. FROM centos:latest
  2. MAINTAINER xlangersir@gmail.com
  3. ENV NODE_VERSION 6.9.5
  4. COPY node-v$NODE_VERSION.tar.gz .
  5. RUN yum install -y gcc gcc-c++ make \
  6. && tar -zxf node-v$NODE_VERSION.tar.gz \
  7. && cd node-v$NODE_VERSION \
  8. && ./configure \
  9. && make -j$(getconf _NPROCESSORS_ONLN) \
  10. && make install \
  11. && npm install -g pm2 \
  12. && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
  13. && echo "Asia/Shanghai" > /etc/timezone \
  14. && cd .. \
  15. && rm -Rf "node-v$NODE_VERSION" \
  16. && rm "node-v$NODE_VERSION.tar.gz" \
  17. && rm -rf /var/cache/yum
  18. 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 镜像

  1. $ mkdir nginx-dockerfile
  2. $ cd nginx-dockerfile
  3. $ curl -SLO http://nginx.org/download/nginx-1.11.10.tar.gz
  4. $ curl -SLO https://www.openssl.org/source/openssl-1.1.0d.tar.gz
  5. $ curl -SLO https://ftp.pcre.org/pub/pcre/pcre-8.40.tar.gz
  6. $ curl -SLO http://www.zlib.net/zlib-1.2.11.tar.gz
  7. $ curl -SLO https://github.com/gperftools/gperftools/releases/download/gperftools-2.5/gperftools-2.5.tar.gz
  8. $ vim Dockerfile # 下边提供内容
  9. $ docker build --rm -t nginx:1.11.10 .

以下是构建 Nginx 镜像的 Dockerfile 内容:

  1. FROM centos:latest
  2. MAINTAINER xlangersir@gmail.com
  3. ENV NGINX_VERSION 1.11.10
  4. WORKDIR /tmp/tmpdir
  5. COPY nginx-$NGINX_VERSION.tar.gz .
  6. COPY openssl-1.1.0d.tar.gz .
  7. COPY pcre-8.40.tar.gz .
  8. COPY zlib-1.2.11.tar.gz .
  9. COPY gperftools-2.5.tar.gz .
  10. COPY nginx-ct.tar.gz .
  11. RUN CONFIG="\
  12. --prefix=/usr/local/nginx \
  13. --sbin-path=/usr/sbin/nginx \
  14. --conf-path=/etc/nginx/nginx.conf \
  15. --error-log-path=/var/log/nginx/error.log \
  16. --http-log-path=/var/log/nginx/access.log \
  17. --user=nginx \
  18. --group=nginx \
  19. --with-file-aio \
  20. --with-http_ssl_module \
  21. --with-http_v2_module \
  22. --with-http_realip_module \
  23. --with-http_addition_module \
  24. --with-http_sub_module \
  25. --with-http_dav_module \
  26. --with-http_flv_module \
  27. --with-http_mp4_module \
  28. --with-http_gunzip_module \
  29. --with-http_slice_module \
  30. --with-http_gzip_static_module \
  31. --with-http_random_index_module \
  32. --with-http_secure_link_module \
  33. --with-http_degradation_module \
  34. --with-http_stub_status_module \
  35. --with-http_perl_module=dynamic \
  36. --with-http_xslt_module=dynamic \
  37. --with-http_geoip_module=dynamic \
  38. --with-http_image_filter_module=dynamic \
  39. --with-pcre \
  40. --with-pcre-jit \
  41. --with-mail=dynamic \
  42. --with-mail_ssl_module \
  43. --with-stream=dynamic \
  44. --with-stream_ssl_module \
  45. --with-stream_ssl_preread_module \
  46. --with-stream_realip_module \
  47. --with-stream_geoip_module=dynamic \
  48. --with-google_perftools_module \
  49. --with-openssl=../openssl-1.1.0d \
  50. --with-pcre=../pcre-8.40 \
  51. --with-zlib=../zlib-1.2.11 \
  52. --add-module=../nginx-ct \
  53. "\
  54. && yum update -y \
  55. && yum install -y gcc gcc-c++ make gd-devel libunwind-devel libxslt-devel libxml2-devel perl-devel perl-ExtUtils-Embed GeoIP GeoIP-devel \
  56. && tar zxf nginx-ct.tar.gz \
  57. && tar zxf nginx-$NGINX_VERSION.tar.gz \
  58. && tar zxf openssl-1.1.0d.tar.gz \
  59. && tar zxf pcre-8.40.tar.gz \
  60. && tar zxf zlib-1.2.11.tar.gz \
  61. && tar zxf gperftools-2.5.tar.gz \
  62. && cd ../gperftools-2.5 \
  63. && ./configure --enable-shared \
  64. && make -j$(getconf _NPROCESSORS_ONLN) \
  65. && make install \
  66. && cd ../nginx-$NGINX_VERSION \
  67. && ./configure $CONFIG --with-debug \
  68. && make -j$(getconf _NPROCESSORS_ONLN) \
  69. && make install \
  70. && groupadd -r nginx \
  71. && useradd -s /sbin/nologin -g nginx nginx \
  72. && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
  73. && if [ -f /etc/timezone ]; then echo "Asia/Shanghai" > /etc/timezone; fi \
  74. && echo "/usr/local/lib" > /etc/ld.so.conf.d/libprofiler.so.0.conf \
  75. && ldconfig \
  76. && rm -rf /var/cache/yum /tmp/tmpdir
  77. WORKDIR /var/www
  78. EXPOSE 80 443
  79. CMD ["/usr/sbin/nginx", "-g", "daemon off;"]

Nginx 部分配置比较多,稍后单独写一篇记录下来…

准备启航

应用 Docker 的 create 命令创建几个容器,方便其他容器通过 run 命令中的选项 —volumes-from 直接挂载容器共享宿主机资源。

  1. # 宿主机上的www目录挂在到nginx容器的www目录
  2. $ docker create --name wwwcontainer -v /path/to/www:/var/www centos:latest
  3. # 宿主机上的nginx日志保存目录挂在到nginx容器的目录
  4. $ docker create --name nginxlogcontainer -v /path/to/log/nginx:/var/log/nginx centos:latest
  5. # 宿主机上的ningx用TSL签名证书目录挂在到nginx容器的证书目录,通过修改nginx配置文件调用
  6. $ docker create --name nginxsslcontainer -v /path/to/ssl:/var/ssl centos:latest
  7. # 宿主机上的MySQL数据保存目录挂在到MySQL容器的目录
  8. $ 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

  1. $ docker run \
  2. --name mysql \
  3. --volumes-from=datadircontainer \
  4. -v /etc/localtime:/etc/localtime:ro \
  5. -e MYSQL_ROOT_PASSWORD=$(cat ~/docker/mysql-root-pwd) \
  6. -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 博客系统准备一个数据库用户和一个空的数据库,并给予相应的权限。
  1. $ CREATE USER 'bloguser'@'%' identified BY 'bloguser@pwd';
  2. $ CREATE DATABASE `firekylin` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
  3. $ GRANT ALL ON firekylin.* TO 'bloguser'@'%';
  4. $ FLUSH PRIVILEGES;

运行 Nodejs 容器

  1. $ docker run \
  2. --name nodejs \
  3. --restart=always \
  4. --link mysql:mysql \
  5. --volumes-from=wwwcontainer \
  6. -itd node:6.9.5 \
  7. /bin/bash -c "pm2 start /var/www/xlange.com/pm2.json && while true; do ping 127.0.0.1; done"
  8. $ docker exec nodejs cat /etc/hosts | grep mysql
  9. 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 也可避免了这个错误。

  1. $ docker run --rm -it nginx:1.11.10 /usr/sbin/nginx -g 'daemon off;'
  2. /usr/sbin/nginx: error while loading shared libraries: libprofiler.so.0: cannot open shared object file: No such file or directory
  1. $ docker run \
  2. --name nginx \
  3. --restart=always \
  4. --link nodejs:nodejs \
  5. --volumes-from=wwwcontainer \
  6. --volumes-from=nginxsslcontainer \
  7. --volumes-from=nginxlogcontainer \
  8. -v /path/to/nginx.conf:/etc/nginx/nginx.conf \
  9. -v /path/to/xlange.com.conf:/etc/nginx/conf.d/xlange.com.conf \
  10. -itd -p 80:80 -p 443:443 nginx:1.11.10 \
  11. /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 配置参考:
  1. server {
  2. listen 443 ssl http2 default_server;
  3. listen [::]:443 ssl http2 default_server;
  4. server_name xlange.com www.xlange.com;
  5. root /var/www/xlange.com/www;
  6. index index.js index.html index.htm;
  7. ssl_session_cache shared:SSL:10m;
  8. ssl_session_timeout 10m;
  9. ssl_session_tickets off; # Requires nginx >= 1.5.9
  10. ssl_ecdh_curve secp384r1; # Requires nginx >= 1.1.0
  11. resolver 233.5.5.5 119.29.29.29 valid=300s;
  12. resolver_timeout 5s;
  13. proxy_hide_header Vary;
  14. proxy_hide_header X-Runtime;
  15. proxy_hide_header X-Version;
  16. proxy_hide_header X-Powered-By;
  17. proxy_ignore_headers Set-Cookie;
  18. add_header Cache-Control no-cache;
  19. add_header X-Frame-Options SAMEORIGIN;
  20. add_header X-Content-Type-Options nosniff;
  21. add_header X-XSS-Protection "1; mode=block";
  22. 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;";
  23. # Strict Transport Security (HSTS)
  24. add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload" always;
  25. # Public Key Pinning (HPKP)
  26. add_header Public-Key-Pins 'pin-sha256="NO1aizeokVCU2u4ZQob2YJI7OAieh8lcfzcYkZCVUs8="; pin-sha256="JMg0zx3zzkB61hGibJBlmv//NPcPH8lqIFlGFV5I2Ts="; max-age=2592000; includeSubDomains' always;
  27. ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
  28. 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;
  29. ssl_prefer_server_ciphers on;
  30. # Forward Secrecy (DHE and ECDHE)
  31. ssl_dhparam /var/ssl/dhparams.pem; # >2048-bits recommended
  32. # CT
  33. ssl_ct on;
  34. # RSA
  35. ssl_certificate /var/ssl/xlange.com/fullchain.pem;
  36. ssl_certificate_key /var/ssl/xlange.com/privkey.pem;
  37. ssl_ct_static_scts /var/ssl/xlange.com;
  38. # ECC
  39. ssl_certificate /var/ssl/xlange.com_ecc/fullchain.cer;
  40. ssl_certificate_key /var/ssl/xlange.com_ecc/xlange.com.key;
  41. ssl_ct_static_scts /var/ssl/xlange.com_ecc;
  42. # Offensive Security Certified Professional (OCSP) stapling
  43. ssl_stapling on; # Requires nginx >= 1.3.7
  44. ssl_stapling_verify on; # Requires nginx >= 1.3.7
  45. ssl_stapling_file /var/ssl/xlange.com_ecc/stapling.ocsp;
  46. ssl_trusted_certificate /var/ssl/xlange.com_ecc/fullchain.cer;
  47. # Load configuration files for the default server block.
  48. include /etc/nginx/default.d/*.conf;
  49. if ($http_host != xlange.com) {
  50. rewrite (.*) https://xlange.com$1;
  51. }
  52. location ~* \.html$ {
  53. rewrite ^/(.*)\.html$ /$1 last;
  54. }
  55. location / {
  56. proxy_http_version 1.1;
  57. proxy_set_header X-Real-IP $remote_addr;
  58. proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  59. proxy_set_header Host $http_host;
  60. proxy_set_header X-NginX-Proxy true;
  61. proxy_set_header Upgrade $http_upgrade;
  62. proxy_set_header Connection "upgrade";
  63. proxy_pass http://node-web;
  64. proxy_redirect off;
  65. }
  66. location = /development.js {
  67. deny all;
  68. }
  69. location = /testing.js {
  70. deny all;
  71. }
  72. location = /production.js {
  73. deny all;
  74. }
  75. location ~ /static/ {
  76. etag on;
  77. expires max;
  78. }
  79. }
  80. server {
  81. listen 80 default_server;
  82. listen [::]:80 default_server;
  83. server_name xlange.com www.xlange.com;
  84. return 301 https://xlange.com$request_uri;
  85. }
  86. upstream node-web {
  87. server nodejs:8080 weight=1 max_fails=3 fail_timeout=30s;
  88. }

安装配置 FireKylin

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