首先,祝各位新年快乐,万事如意,鸡年大吉。javascript
此次要来讲说一个和前端并不太相关的东西——docker compose,一个整合发布应用的利器。css
若是,你对 docker 有一些耳闻,那么,你可能知道它是什么。html
不过,你不了解也没有关系,在做者眼中,docker 就相似于一个沙箱,而你的应用起在这个沙箱里,不受服务器系统环境的影响,同时也不污染服务器,配置完成以后往服务器部署或移除应用都至关方便。前端
而 compose 就如同它的字面意思组合,它就好像是一个大箱子,能够把几个不相关的沙箱给组合起来,变成一个总体,就如同小时候动画片中变形金刚的合体变身。java
理论知识就没有什么比官方文档更好的了,这里就不讲了,主要来看看如何应用。本文主要包含如下几个部分:node
安装nginx
Hello worldgit
经常使用命令github
Real worldweb
若是,你只对前端技术感兴趣,那么,这篇文章可能不适合你。
常言道:一个不懂运维的设计,不是一个好前端。
Windows 和 Mac 装了 Docker 以后已经自带 docker-compose,其余环境根据 Docker 官网介绍,简单几步也能完成安装。
这里要提一下,在亚马逊 aws 上安装 docker-compose,因为没有 root 权限会遇到官网上所提到的 Permission denied
错误,加了 sudo 也是没法直接下载到 /usr/local/bin 目录下的。
硬来不行,还能够曲线救国嘛~
先将文件下载到 aws 服务器上,再将文件移动到 /usr/local/bin
目录就能够了。
curl -L https://github.com/docker/compose/releases/download/1.9.0/docker-compose-`uname -s`-`uname -m` > docker-compose sudo chown root docker-compose sudo mv docker-compose /usr/local/bin sudo chmod +x /usr/local/bin/docker-compose
验证是否安装成功,试试 docker-compose version
。若是有输出版本信息,就说明 docker-compose 已经安装好了。
docker-compose 虽然安装好了,但并不必定能用,由于 docker 和 docker-compose 是分开安装,即便它俩各自运行正常,在一块儿就不必定合拍了。
那怎么知道它俩合不合拍?答案很简单,hello world~
在任意的目录下,建立一个 docker-compose.yml 文件,并添加下面的内容。
version: '2' services: helloworld: image: 'hello-world'
而后,在当前目录下使用 docker-compose up
启动 docker-compose。
启动时,如遇到
client and server don't have same version (client : 1.22, server: 1.18)
相似这样的错误,能够经过设置 docker-compose 的 api 版原本解决。
COMPOSE_API_VERSION=auto
不要尝试经过一次次安装不一样的 docker-compose 版原本解决,你会 ? 的。若是,还遇到
docker.errors.InvalidVersion: inspect_network is not available for version < 1.21
这是 Ubuntu 14.04 LTS 默认的 docker 版本过低引发的,须要升级 docker。然而,在 aws 的服务器上升级 docker 版本时,须要先建立 /etc/apt/sources.list.d/docker.list
文件,并添加
deb https://packages.docker.com/1.12/apt/repo ubuntu-trusty main
再运行
sudo apt-get update && sudo apt-get upgrade docker-engine
就能升级成功。看到?这样的结果,就表示 docker 和 docker-compose 都安装成功,并且它俩很搭。
docker-compose 的命令很简单,它已经将一些 docker 经常使用关于 image, container & volume 的命令都整合在了一块儿,使发布变得极其简单。好比,以前刚刚提到的 docker-compose up
,就相似于 docker build & run,用来建立并启动 container。
其余经常使用的命令有:
build
:构建或从新构建 services
config
:验证 docker-compose 配置文件
create
:建立 services
down
:与 up
相对,中止并删除 container, image, volumn 等
kill
:杀死某个 container
logs
:查看 container 日志
ps
:查看 container 信息
restart
:重启 services
rm
:删除已经中止的 container
start
:启动 services
stop
:中止 service
version
:显示 docker-compose 版本
是否是发现有几个命令和 docker 的命令同样?的确,但就如同以前的安装过程同样,docker-compose 是依赖于 docker 的,docker 命令更底层。好比 docker-compose ps
这个命令,它只会显示由 docker-compose 启动的容器信息,但不包含 docker 启动的容器信息,相反 docker ps
能够查看由 docker-compose 启动的容器信息。
还剩几个命令没有列出来,有兴趣的童鞋能够经过 docker-compose help
命令或上官网查看更多信息。
光说不练假把式。docker-compose 究竟好很差用,只有用了才知道。
以前,我的博客的静态资源一直都是经过 node 提供服务。这的确能够,但这不是 node 的强项。
专业的事交给专业的人去作。 - by S(ome)B(ody)
这个专业的人就是 nginx。
除此以外,2017 年起水果和古哥都强推 https,升级 https 也是箭在弦上(虽然一直有这个打算,也拖到了如今彡(-_-;)彡)。
因而,程序再也不是原先单一的 node 服务,而是,变成了一系列密切相关的服务。若是,经过基础的 docker 命令来一个个启动、中止服务的话,那么,就须要额外添加一个复杂的脚原本控制。
docker-compose 就是用来处理相似的问题。它能够作到经过一条命令来控制一个应用相关的一系列服务的启动、中止等,而且不依赖于机器环境,做到随时能够将应用迁移至其余的机器上发布。
知道了准备作什么,先看看最终设计的应用结构和以前的对比。
直接看这张图可能有点蒙圈,没事,一点点来看。
本文一开始就有提到,docker 能够看作是一个小箱子,而 docker-compose 是一个大箱子用来装这些小箱子。
那么,如何将小箱子放入这个大箱子里哪?
很是简单!只需告诉 docker-compose 如何启动你的应用就能够了,那就先看看原先的启动命令。
docker run -d -p 80:8080 --name blog
启动命令中,主要配置了一个端口的映射 -p
,以及命名了容器名,用于方便地启动、中止应用。清楚了这些,那么改为 docker-compose 的文件也就垂手可得了。
version: '2' services: node: build: . container_name: node ports: - "80:8080"
docker 到 docker-compose 的转换就这样完成了,这些更新都不须要修改任何的业务逻辑或者打包配置。
试着使用 docker-compose up -d
启动服务验证看看。
启动正常以后,仍是一步步来,先引入 nginx。
Nginx 是一个高性能的 Web 服务器,它具备配置简单、运行稳定和负载均衡等特色,常被做为静态资源服务器。(详细的 Nginx 信息,请自行查询资料,这方面本人也不是行家)
Nginx 在 docker hub 上有现成的官方镜像,直接拿来用就能够了。
version: '2' services: # ... nginx: image: nginx:stable container_name: nginx ports: - "80:80" restart: always
此时,启动服务会失败并报错,由于 nginx 和原有的 node 容器都绑定到了 80 端口。docker-comopse 各个容器之间是相互独立的,容器内部的接口相互之间不影响,但对外暴露的接口不能相同,否则就会引发冲突。
从以前的结构图能够看到,请求所有由 nginx 接受并转发到 node 服务,也就是说,node 不直接对外提供服务。那么,docker-compose 中也就能够移除 ports 部分(这里便于测试 node 服务依旧暴露 8080 端口)。
其次,静态文件是由 node 打包后生成的,也就是说须要将 node 服务中的数据共享给 nginx 服务,这就须要用到 volume(数据卷)。数据卷能够将数据在宿主机和容器之间、容器和容器之间共享,即便容器被删除了,数据卷依旧存在。
这里就须要将服务器上的 nginx 配置文件和 node 构建以后的静态文件共享给 nginx。
version: '2' services: node: build: . container_name: node # node service port export for test ports: - "8080:8080" volumes: - ./log/node:/var/log/node nginx: image: nginx:stable container_name: nginx depends_on: - node volumes: - ./config/nginx:/etc/nginx/conf.d:ro - ./log/nginx:/var/log/nginx volumes_from: - node:ro ports: - "80:80" restart: always
volume 是 docker 中至关重要及经常使用的一部分,理解它对使用 docker 解决问题有巨大的帮助。推荐一篇关于 docker volume 的文章,有助于理解 volume。
docker-compose 配置完了,再来看看 nginx 配置。本章一开始有提到 nginx 能够作负载均衡,那该如何配置哪?
在 nginx 中配置负载均衡至关简单,只需在 upstream
里配置一下目标服务器。
然而,这里就会遇到一个问题。因为,容器之间是相互独立的,因而,localhost 便没法在容器之间相互访问。不过,由同一 docker-compose 所起的容器之间能够经过容器名相互访问,这里就是
upstream node_server { server node:8080 max_fails=2 fail_timeout=30s; }
若是要额外再起一个服务,只需在 docker-compose 文件中再启动一个容器(能够依赖同一套代码),并将以前所配的 upstream
中额外多添加一条 server 信息,好比:
upstream node_server { server node:8080 max_fails=2 fail_timeout=30s; server node-backup:8080 max_fails=2 fail_timeout=30s; }
这样即便一个服务挂了,只要另外一个服务还运行正常,nginx 会将请求转发给运行正常的服务。一个最简单的复杂均衡就作好了,全部这些都不须要修改任何功能性的代码。
知道了 nginx 能够提供负载均衡,但也不要忘了老朋友 pm2。
pm2 经过命令行参数 -i,或配置文件经过起多个实例来作负载均衡(本人的小博客也是用的这个方式)。
引入 nginx 以后,将全站升级成 https 就垂手可得了,只需在配置文件中标明证书及秘钥文件的位置就能够了。接下去,就看看如何生成证书和秘钥。
获取 ssl 证书的方式有许多种,有的买域名就送证书,这里介绍一下用 letsencrypt(现已改名为 certbot
)获取免费 ssl 证书。
常言道:前人栽树,后人乘凉。
一样的,letsencrypt 在 docker hub 上也有现成的镜像。镜像有了,剩下的就只需根据不一样的场景来生成证书。
certbot
支持 5 种生成证书的模式,分别是:apache
, nginx
, webroot
, standalone
和 manual
,分别用于不一样的场景。这里 nginx 和 certbot 使用的是不一样的镜像,因此选用的模式是 webroot
。
选定了镜像和模式,那么参照 certbot 的文档就可以简单地生成证书了。
docker run -it --rm --name certbot \ -v /letsencrypt/etc/letsencrypt:/etc/letsencrypt \ -v /letsencrypt/lib/letsencrypt:/var/lib/letsencrypt \ -v /letsencrypt/challenge:/usr/share/nginx/html \ -v /var/log/letsencrypt:/var/log/letsencrypt \ deliverous/certbot \ certonly --webroot -w /usr/share/nginx/html
须要注意的是,在 webroot
模式下申请证书,须要向 certbot 证实服务器能被访问。certbot 验证程序会访问 web root 目录(这里是 /usr/share/nginx/html)来验证。这里又要用到以前提到的 volume 将目录共享给 nginx,让 nginx 可以访问到目录内部的文件。
server { listen 80; listen [::]:80; server_name discipled.me; # ... # letsencrypt challenge file location location /.well-known { root /usr/share/nginx/html; access_log /var/log/nginx/challenge-access.log main; allow all; } ... }
修改 nginx 配置以后,别忘重启 nginx 服务。
docker-compose restart nginx
重启 nginx 以后,而后再运行上面生成证书的命令就能生成证书了。
看到 Congratulations!
,证书就生成成功了。
再一次修改 nginx 配置,添加 ssl 证书信息,并监听 443 端口。
# redirect host http://domain to https://domain server { listen 80; listen [::]:80; server_name discipled.me; # letsencrypt challenge file location location /.well-known { root /usr/share/nginx/html; access_log /var/log/nginx/challenge-access.log main; allow all; } location / { return 301 https://discipled.me$request_uri; } } # https://domain server server { listen 443 ssl; listen [::]:443 ssl; server_name discipled.me; charset utf-8; gzip on; gzip_types text/plain application/javascript application/x-javascript text/javascript text/xml text/css; root /usr/app/build/client/; ssl_certificate /etc/letsencrypt/live/discipled.me/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/discipled.me/privkey.pem; location / { try_files $uri @node; } location @node { proxy_pass http://node_server; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }
重启 nginx 服务后,访问网站就能够看到
小锁加上,大功告成。
七牛的图床用 https 还要实名认证,为了保护(pa)个(cha)人(shui)隐(biao)私,就暂时用 Github 来救一下急。(谁知道有啥好用的图床麻烦推荐一下,像七牛同样支持 qrsync 用脚本批量上传的就最好了~先谢过...)
letsencrypt 生成的证书有效期是 3 个月,因此,至少 3 个月内须要更新一次证书。
certbot 提供了 renew 命令能够方便地更新证书,使用 --dry-run
参数能够验证证书更新命令是否正确。
docker run -it --rm --name certbot \ -v /letsencrypt/etc/letsencrypt:/etc/letsencrypt \ -v /letsencrypt/lib/letsencrypt:/var/lib/letsencrypt \ -v /letsencrypt/challenge:/usr/share/nginx/html \ -v /var/log/letsencrypt:/var/log/letsencrypt \ deliverous/certbot \ renew --dry-run
一样,看到 Congratulations
说明证书更新成功了。
因为,本人每个月都会发布文章并重启服务,就能够把证书更新一块儿交由 docker-compose 管理。(这里偷了个懒,增长了证书同应用之间的耦合关系,仍是建议你们证书是经过系统定时任务来更新,免得哪天忘更新证书,证书就过时了)。
看一下最终的 docker-compose 配置文件和发布脚本。
# docker-compose.yml version: '2' services: node: build: . image: "blog:${TAG_NAME}" container_name: node # node service port export for test ports: - "8080:8080" volumes: - ./log/node:/var/log/node nginx: image: nginx:stable container_name: nginx depends_on: - node - letsencrypt volumes: - ./config/nginx:/etc/nginx/conf.d:ro - ./letsencrypt/etc/letsencrypt:/etc/letsencrypt - ./letsencrypt/lib/letsencrypt:/var/lib/letsencrypt - ./letsencrypt/challenge:/usr/share/nginx/html - ./log/nginx:/var/log/nginx volumes_from: - node:ro ports: - "80:80" - "443:443" restart: always letsencrypt: image: deliverous/certbot container_name: certbot volumes: - ./letsencrypt/etc/letsencrypt:/etc/letsencrypt - ./letsencrypt/lib/letsencrypt:/var/lib/letsencrypt - ./letsencrypt/challenge:/usr/share/nginx/html - ./log/letsencrypt:/var/log/letsencrypt command: renew
发布脚本主要用来更新代码,以及获取应用版本号。
# deploy.sh # git operation git reset HEAD --hard git fetch git pull # TAG_NAME used to set docker image tag export TAG_NAME=`git tag -l | sort -r | head -n 1` # docker operation docker-compose down --volumes docker-compose up --build -d
其余配置能够上 github 查看。
一扯彷佛又扯远了,欢迎提意见和建议,顺便再问一下有啥好的图床推荐。