03/24/2017

Docker 学习笔记(一) - 搭建 Firekylin 博客

一直想玩 Docker 但是都没有机会。这次有点空就搭个博客玩玩顺便学习学习 Docker。这篇文章讲述了用多个容器协作的方式来搭建与管理现今流行的 Flrekylin 博客系统。使用到了 Docker Hub 下的各大容器和 Docker Compose 工具来定义及运行相互协作的容器。

0x00 - 设置本地 Docker 开发环境

Docker 官网提供了各大平台的安装教程。我使用了 Docker for Mac Community Edition 安装。还有一个叫 Docker Toolbox 的东西不要搞混了,简单来说 Docker for Mac 是原生的而 Docker Toolbox 是建立在一个 VM 之上的「详情」

安装完成在状态栏会有 docker icon >< 这个小图标,点开来有 Docker for Mac 的菜单。

docker dropdown menu

同时也安装了 Docker Compose。它是用于定义和运行复杂Docker应用的工具。你可以在一个文件中定义一个多容器的应用,然后使用一条命令来启动你的应用,然后所有相关的操作都会被自动完成。

0x01 - 使用 Docker Hub 仓库发现镜像

根据安装文档Firekylin 需要 Node.js 和 MySQL 的支持。同时推荐在服务器上使用流行的 pm2 和 nginx 服务器来管理和反向代理。

以上几个都是现今较流行的服务,Docker Hub 里都有官方和其他开发者发布的 Images:nodepm2mysqlmariadbnginx

查看 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 。

这些都是官方版本,如果去 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

  1. 如果宿主有装 npm,可以直接运行
$ cd firekylin && npm install
  1. 如果你不想在宿主机中安装 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 将无法被此镜像使用。 在这种情况下,我们可以:

  1. 找别的开发者发布的支持 JSON 格式的 Process File 的 pm2 镜像,
  2. 自己建一个 pm2 镜像,此镜像的 Dockerfile 言简意赅,可以直接替换 process.ymlprocess.json
  3. Overwrite CMD
  4. 把我们的 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 时会更改文件。比如安装时会创建 .installedconfig/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:

注释:

  1. 使用了数据卷,见下方 Volumes
  2. mysql 的 Docfile 里有 EXPOSE 3306,所以其他同一网络的容器可以访问,但是宿主不行。添加这条可以让宿主通过 3306 端口访问
  3. 死了自动复活
  4. 也可配置非 root 用户,详讯 Docker Hub 文档
  5. 自动新建数据库
  6. pm2 需要 db,所以 db 会先启动。depends_on 会自动 link
  7. 因为 link 过,db 可以自动解析到 db 容器的 ip
  8. 以下三条与上面的 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 提几个小建议:

  1. PORT 用一个文件来控制太 Hacky 了,希望可以使用环境变量 process.env.PORT #264
  2. 希望可以通过环境变量 prepopulate 安装界面上的数据库信息

Happy blogging!

本文链接:https://blog.whe.me/post/docker-series-1-firekylin-installation.html

-- EOF --

Comments

评论加载中...

注:如果长时间无法加载,请针对 disq.us | disquscdn.com | disqus.com 启用代理。