一直想玩 Docker 但是都没有机会。这次有点空就搭个博客玩玩顺便学习学习 Docker。这篇文章讲述了用多个容器协作的方式来搭建与管理现今流行的 Flrekylin 博客系统。使用到了 Docker Hub 下的各大容器和 Docker Compose 工具来定义及运行相互协作的容器。
0x00 - 设置本地 Docker 开发环境
Docker 官网提供了各大平台的安装教程。我使用了 Docker for Mac Community Edition 安装。还有一个叫 Docker Toolbox 的东西不要搞混了,简单来说 Docker for Mac 是原生的而 Docker Toolbox 是建立在一个 VM 之上的「详情」。
安装完成在状态栏会有 这个小图标,点开来有 Docker for Mac 的菜单。
同时也安装了 Docker Compose。它是用于定义和运行复杂Docker应用的工具。你可以在一个文件中定义一个多容器的应用,然后使用一条命令来启动你的应用,然后所有相关的操作都会被自动完成。
0x01 - 使用 Docker Hub 仓库发现镜像
根据安装文档,Firekylin 需要 Node.js 和 MySQL 的支持。同时推荐在服务器上使用流行的 pm2 和 nginx 服务器来管理和反向代理。
以上几个都是现今较流行的服务,Docker Hub 里都有官方和其他开发者发布的 Images:node,pm2,mysql,mariadb,nginx。
查看 pm2 的 Dockerfile 第一行:
FROM node:alpine
我们得知它是建立在 node:alpine Image 之上的。所以我们只需要 pm2, mysql, nginx 就可以把 Firekylin 架设起来了。
题外话:很多 Docker Hub 上发布的 Image 都会有 alpine 版本。普通的基于 Ubuntu 或者 CentOS 的 Image 动不动就几百兆的而 alpine 版本就非常小,一般都在几兆到几十兆以内。感受一下:
$ docker images --format '{{.Repository}}\t{{.Tag}}\t{{.Size}}'
REPOSITORY TAG SIZE
node alpine 59.2 MB
node latest 666 MB
注:docker images
的输出太长了,所以用到了 --format
。
优点:
- 更小的文件下载起来更快
- 更安全:攻击面更小
- 节省 VPS 空间
缺点:
- 自己建 Image 时候各种包都要自己安装(优点?)
详见:「Docker 搬去 Alpine 了」
综上所述,我们将使用到如下几个 Docker Images 。
pm2
- keymetrics/pm2-docker-alpinemariadb
- official/mysql 或mysql
- official/mysqlnginx
- official/nginx
这些都是官方版本,如果去 Docker Hub 搜索这些名字,你会发现有一堆堆不同开发者的版本。开发者水平参差不齐,一般我 sort by downloads 就会发现使用者较多、文档较好、接口设计更为合理的镜像。
0x02 准备工作:添加及设置 Firekylin 文件
根据 Firekylin wiki 安装页面步骤:
1. 在服务器上下载安装包
请访问 https://firekylin.org/release/latest.tar.gz 或者 Github Releases 下载最新的安装包。
$ curl -O https://firekylin.org/release/latest.tar.gz
0% .....
...省略...
100% ...
$ ls -1
docker-compose.yml
latest.tar.gz
至此你应该能看到成功下载的 Firekylin 压缩包 latest.tar.gz
。
2. 解压缩安装包
$ tar -xzf latest.tar.gz
$ ls -1
docker-compose.yml
firekylin
latest.tar.gz
如果你看到的输出是这三个文件,那么恭喜你,Firekylin 下载成功了。
$ rm latest.tar.gz
3. 安装程序依赖
在程序目录中执行 npm install
安装对应的依赖。
pm2
只负责运行不负责安装依赖,所以要在用其他方法运行 npm install
。
- 如果宿主有装
npm
,可以直接运行
$ cd firekylin && npm install
- 如果你不想在宿主机中安装
npm
,可以用这个monostream/nodejs-gulp-bower
Docker 容器完成这一步骤哦。
$ docker run --rm -v $(pwd):/workspace monostream/nodejs-gulp-bower npm install
注意:
执行之前请确认已有 Node.js 4.0+ 环境。如果国内 npm install
太慢可选择添加淘宝源代理 npm install --registry=https://registry.npm.taobao.org
。
4. 配置 pm2
引用将项目下的 pm2_default.json
文件改为 pm2.json
,修改文件中的 cwd
配置值为项目的当前路径。
根据 keymetrics/pm2-docker-alpine
镜像的文档和 Dockerfile
的最后两行:
# Start process.yml
CMD ["pm2-docker", "start", "--auto-exit", "--env", "production", "process.yml"]
此容器只支持 process.yml
的 config 文件,Firekylin 项目下的 pm2_default.json
将无法被此镜像使用。
在这种情况下,我们可以:
- 找别的开发者发布的支持 JSON 格式的 Process File 的 pm2 镜像,
- 自己建一个 pm2 镜像,此镜像的 Dockerfile 言简意赅,可以直接替换
process.yml
为process.json
, - Overwrite CMD,
- 把我们的
pm2_default.json
翻译成 yaml 格式。
为了节约时间我们就选择第四种方法,请在 site/firekylin/ 中运行:
$ touch process.yml
复制以下到 process.yml
,如果你打开对比就会发现这个文件是 pm2_default.json
的直译。
apps:
- name: "firekylin"
script: "www/production.js"
cwd: "/app"
exec_mode: "fork"
max_memory_restart: "1G"
autorestart: true
题外话:我之前还尝试添加了 watch 功能,即有文件变化自动 reload。但是发现安装 Firekylin 时会更改文件。比如安装时会创建 .installed
和 config/db.js
. 这就导致安装时候 POST
请求被中断,因为 pm2
检测到了文件变化并重启了服务。
5. 配置 Nginx
将项目下的 nginx_default.conf
复制到 site/nginx/nginx.conf
并创建 ssl
文件夹方便 Let's Encrypt
等证书 ACME
认证及存储。
$ mkdir ../nginx && cd $_
$ mkdir ssl/ && mkdir ssl/challenges
$ cp ../firekylin/nginx_default.conf nginx.conf
接着对 nginx.conf
进行编辑。前面几行改成这样,HTTPS 和 www 转向区域自行脑补哈:
listen 80;
server_name blog.whe.me; # 域名
root /usr/share/nginx/html; # 对应 docker-compose.yml 文件
set $node_port 8360;
include /etc/nginx/mime.types; # 不添加的话反向代理会丢失正确的 Content-Type
index index.js index.html index.htm;
location ^~ /.well-known/acme-challenge/ {
alias /usr/share/nginx/ssl/challenges/; # 见 docker-compose.yml 文件
try_files $uri = 404;
}
location / {
resolver 127.0.0.11; # 添加本地 dns 解析下面的 pm2 容器 ip
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://pm2:$node_port$request_uri; # firekylin 在 pm2 容器里运行
proxy_redirect off;
}
注意
需要编辑 nginx.conf
把它放在 http { }
里面。所以第一行要加 http {
, 最后一行要加 }
完成之后再在第一行加上 events {}
编辑后前三行为:
events{}
http {
server {
至此,我们已经完成了所有准备工作。快了快了马上就好了。
0x03 - 编写 Compose file docker-compose.yml
a. 介绍 Docker Compose
刚才已经稍稍介绍过了一下 Docker Compose 「Overview」:你可以在一个文件中定义一个多容器的应用,然后使用一条命令来启动你的应用。
截至目前 Compose file 已经在 version 3 了。
b. 创建 docker-compose.yml
$ mkdir site && cd $_
$ touch docker-compose.yml
新建好 docker-compose.yml
后就请启动你最喜爱的编辑器编辑她
我们将要填写的主要部分如下:
version: '3' # Compose 文件版本
services: # 定义不同的容器及其配置属性
db:
pm2:
nginx:
volumes: # 定义数据卷
networks: # 定义网络
数据卷 是可供一个或多个容器使用的特殊目录,数据卷默认会一直存在,即使容器被删除。如果不在这里定义数据卷,容器每次会默认生成一个新的。以前的就变成孤儿 ( dangling )。
使用 docker volume rm $(docker volume ls -qf dangling=true)
可以快速清除这些孤儿 TAT 。「文档」
网络 可以设置私有网络,不同容器只能和同一个网络里的容器连接。案例:db 容器只需和 pm2 容器交流,nginx 容器只需和 pm2 容器交流,通过网络就可以轻松控制。「文档」
c. 填写 docker-compose.yml
version: '3'
services:
db:
image: mariadb:latest # or mysql:latest
volumes:
- db_data:/var/lib/mysql # 1
#ports: # 添加后宿主就可以通过 localhost:3306 直接连接 MySQL
# - 3306:3306 # 2
networks:
- back
restart: always # 3
environment:
MYSQL_ROOT_PASSWORD: firekylin # 4
MYSQL_DATABASE: firekylin # 5
pm2:
depends_on: # 6
- db
image: keymetrics/pm2-docker-alpine
volumes:
- ./firekylin:/app
networks:
- back
- front
#ports: # 添加后宿主就可以通过 localhost:8360 直接访问 firekylin
# - 8360:8360
restart: always
environment:
DB_HOST: db:3306 # 7
DB_NAME: firekylin # 8
DB_USER: root
DB_PASSWORD: firekylin
nginx:
depends_on:
- pm2
image: nginx:alpine
volumes:
- ./firekylin/www:/usr/share/nginx/html:ro
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/ssl:/usr/share/nginx/ssl:ro
networks:
- front
ports:
- 8080:80 # 如果你在**生产环境**中,请使用 80:80
- 443:443
restart: always
volumes:
db_data:
networks:
back:
front:
注释:
- 使用了数据卷,见下方 Volumes
- 在
mysql
的 Docfile 里有EXPOSE 3306
,所以其他同一网络的容器可以访问,但是宿主不行。添加这条可以让宿主通过 3306 端口访问 - 死了自动复活
- 也可配置非 root 用户,详讯 Docker Hub 文档
- 自动新建数据库
pm2
需要db
,所以db
会先启动。depends_on 会自动 link- 因为 link 过,
db
可以自动解析到db
容器的 ip - 以下三条与上面的
db
容器里的环境变量保持一致
db
容器只需和 pm2
容器交流,nginx
容器只需和 pm2
容器交流,通过网络功能就可以轻松控制。
0x04 - 运行 Docker Compose
大功告成!见证奇迹的时刻来了!
注意:服务器端安装的不要忘了添加 DNS 记录,因为本地调试的原因,我把这个域名加入了 /etc/hosts
$ cat "127.0.0.1 blog.whe.me" >> /etc/hosts
CLI 进入 site/
运行
$ docker-compose up
Creating network "site_default" with the default driver
Creating site_db_1
Creating site_pm2_1
Creating site_nginx_1
Attaching to site_db_1, site_pm2_1, site_nginx_1
如果没有问题的话就可以进入 http://blog.whe.me:8080
了!。
0x05 - Firekylin, 初次见面、请多关照
系统安装 - 数据库信息
请参照 pm2
容器的环境变量
0x06 - Tips
停止,删除容器们(数据卷不会被删除,不过会变成孤儿 dangling)
$ docker-compose down
Stopping site_nginx_1 ... done
Stopping site_pm2_1 ... done
Stopping site_db_1 ... done
Removing site_nginx_1 ... done
Removing site_pm2_1 ... done
Removing site_db_1 ... done
Removing network site_front
Removing network site_back
注:Ctrl-C 不会删除容器,仍要运行 docker-compose down
。
启动容器们(后台)
$ docker-compose run -d
浏览容器日志
$ docker ps --format '{{.Names}}\t{{.Image}}'
site_nginx_1 nginx:alpine
site_pm2_1 keymetrics/pm2-docker-alpine
site_db_1 mysql:5.7
$ docker logs site_nginx_1
172.18.0.1 - - [27/Mar/2017:04:53:45 +0000] "GET / HTTP/1.1" 200 3587 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36"
172.18.0.1 - - [27/Mar/2017:04:53:45 +0000] "GET /static/img/firekylin.jpg HTTP/1.1" 200 39160 "http://blog.whe.me:8080/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36"
登陆到 Container
$ docker ps --format '{{.Names}}\t{{.Image}}'
site_nginx_1 nginx:alpine
site_pm2_1 keymetrics/pm2-docker-alpine
site_db_1 mariadb:latest
$ docker exec -it site_nginx_1 bash
WordPress 每个版本都有官方镜像,这样就省了 0x03 这个准备步骤。
给 Firekylin 提几个小建议:
PORT 用一个文件来控制太 Hacky 了,希望可以使用环境变量 process.env.PORT#264- 希望可以通过环境变量 prepopulate 安装界面上的数据库信息
Comments
注:如果长时间无法加载,请针对 disq.us | disquscdn.com | disqus.com 启用代理。