使用Docker、Registrator、Consul、Consul Template和Nginx实现高可扩展的Web框架


【编者的话】Consul是一个支持多数据中心分布式高可用的服务发现和配置共享的服务软件,由 HashiCorp 公司用Go语言开发,基于 Mozilla Public License 2.0 的协议开源。本文介绍了如何使用Consul将多个Docker容器组合起来,以提供一个高可扩展的Web服务。
docker_whale.png_1411805342_.jpg_1421807584_.jpg

当你开始将容器拼装起来构建你的系统的时候,你会发现Docker非常有趣。最近,我在玩Consul,并尝试用它来构建一个高可扩展的Web应用框架。Consul是HashiCorpVagrant的创建者)开发的一个服务发现与配置项目。

我之前尝试借助SRV的记录使用Consul来创建一个可扩展的框架(详情请见:here),但我发现这种方式有点复杂。而我追求简单,所以我又发现了Consul Template,当添加或者移除服务时,Consul Template可以通过连接Consul更新配置并重启应用。

在这篇文章中,我将会讲述如何使用Docker将Consul、Consul Template、Registrator和Nginx组装成一个值得信任且可扩展的框架——DR CoN。一旦组装完成,DR CoN就可以让你在这个框架中添加和移除服务,关键是你不需要重写任何配置,也不需要重启任何服务,一切都能正常运行!

Docker

Docker在LXC的基础上(Linux Containers)上包装了一些API,所以它只能运行在Linux上(注[1])。由于我使用的是OS X操作系统(读者中可能大部分人都是),为此我写了 “通过Boot2Docker在OSX上运行Docker”这篇文章,以下是简要介绍:
brew install boot2docker
boot2docker init  
boot2docker up

执行之后,将在Ubuntu上启一个虚拟机来运行Docker daemon。为了连上daemon,执行以下命令:
export DOCKER_IP=`boot2docker ip`  
export DOCKER_HOST=`boot2docker socket`

你可以通过执行下述命令来验证Docker是否成功安装:
docker ps

通过Docker构建一个简单的Web服务

我们需要一个服务来测试Dr Con这个框架。为此,让我们开始构建一个我了解的简单服务(详情请见此文)。创建一个Dockerfile文件,将以下内容写入其中:
FROM  python:3  
EXPOSE  80  
CMD ["python", "-m", "http.server"]

在Dockerfile文件所在目录下执行以下命令:
docker build -t python/server .

执行后,将创建一个名为 python/server 的镜像,通过执行以下命令运行一个容器:
docker run -it \
-p 8000:80 python/server

我们可以通过curl命令调用这个简单服务来测试它是否能够正常运行:
curl $DOCKER_IP:8000

Consul

Consul是一个拥有DNS和HTTP API的服务。它还有很多其它功能,例如服务健康检测、跨主机集群构建和“键-值”对存储库。执行以下命令在Docker container中运行Consul:
docker run -it -h node \
 -p 8500:8500 \
 -p 8600:53/udp \
 progrium/consul \
 -server \
 -bootstrap \
 -advertise $DOCKER_IP \
 -log-level debug

如果你通过浏览器能够访问$DOCKER_IP:8500,你将在控制面板上看到Consul中已经注册的所有服务。

我们能够通过curl向Consul的Web API注册一个服务:
curl -XPUT \
$DOCKER_IP:8500/v1/agent/service/register \
-d '{
 "ID": "simple_instance_1",
 "Name":"simple",
 "Port": 8000, 
 "tags": ["tag"]
}'

然后,我们可以通过 dig 命令查询Consul为这个提供的DNS API:
dig @$DOCKER_IP -p 8600 simple.service.consul

执行后,结果如下:
; <<>> DiG 9.8.3-P1 <<>> simple.service.consul
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 39614
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;simple.service.consul.        IN    A

;; ANSWER SECTION:
simple.service.consul.    0    IN    A    192.168.59.103

;; Query time: 1 msec
;; SERVER: 192.168.59.103#53(192.168.59.103)
;; WHEN: Mon Jan 12 15:35:01 2015
;; MSG SIZE  rcvd: 76

等等,这有个问题,服务的端口号在哪?很遗憾,DNS的记录中并没有返回服务的端口号。为了获取端口号,我们必须检查SRV记录:
dig @$DOCKER_IP -p 8600 SRV simple.service.consul

执行后,结果如下:
; <<>> DiG 9.8.3-P1 <<>> SRV simple.service.consul
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 3613
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; QUESTION SECTION:
;simple.service.consul.        IN    SRV

;; ANSWER SECTION:
simple.service.consul.    0    IN    SRV    1 1 8000 node.node.dc1.consul.

;; ADDITIONAL SECTION:
node.node.dc1.consul.    0    IN    A    192.168.59.103

;; Query time: 1 msec
;; SERVER: 192.168.59.103#53(192.168.59.103)
;; WHEN: Mon Jan 12 15:36:54 2015
;; MSG SIZE  rcvd: 136

SRV记录很难用,因为很多技术不支持它们。

srv-router这个容器被Consul和Nginx用来路由访问请求到相应服务(详情请见:here)。但是,下面我们将介绍一种更简单的方式让Nginx路由这些请求到相应服务。

Registrator

在Docker容器启动后,Registrator配置好相应的环境变量并将这个容器注册到Consul上,示例如下:
docker run -it \
-v /var/run/docker.sock:/tmp/docker.sock \
-h $DOCKER_IP progrium/registrator \
consul://$DOCKER_IP:8500

Registrator启动后,我们运行一个服务:
docker run -it \
-e "SERVICE_NAME=simple" \
-p 8000:80 python/server

命令执行后,这个服务被自动添加到Consul,同理,如果我们关闭这个服务,它在Consul上会被自动移除。由于Registrator不需要我们手动将服务注册到Consul,因此它是DR CoN需要组装的第一部分。

Consul Template

Consul Template的作用是,当它发现Consul上的服务有变化时,它会利用Consul更新文件并执行相应的命令。

比如说,它能够重写nginx.conf这个文件,将所有服务的路由信息列入其中,然后重新加载Nginx的配置,使得多个相似服务达到负载均衡,或者给多种服务提供一个单一的终端。

我对这个Docker容器做了如下修改(详情请见这里):
FROM nginx:1.7

#Install Curl
RUN apt-get update -qq && apt-get -y install curl

#Download and Install Consul Template
ENV CT_URL http://bit.ly/15uhv24
RUN curl -L $CT_URL | \
tar -C /usr/local/bin --strip-components 1 -zxf -

#Setup Consul Template Files
RUN mkdir /etc/consul-templates
ENV CT_FILE /etc/consul-templates/nginx.conf

#Setup Nginx File
ENV NX_FILE /etc/nginx/conf.d/app.conf

#Default Variables
ENV CONSUL consul:8500
ENV SERVICE consul-8500

# Command will
# 1. Write Consul Template File
# 2. Start Nginx
# 3. Start Consul Template

CMD echo "upstream app {                 \n\
  least_conn;                            \n\
  {{range service \"$SERVICE\"}}         \n\
  server  {{.Address}}:{{.Port}};        \n\
  {{else}}server 127.0.0.1:65535;{{end}} \n\
}                                        \n\
server {                                 \n\
  listen 80 default_server;              \n\
  location / {                           \n\
    proxy_pass http://app;               \n\
  }                                      \n\
}" > $CT_FILE; \
/usr/sbin/nginx -c /etc/nginx/nginx.conf \
& CONSUL_TEMPLATE_LOG=debug consul-template \
  -consul=$CONSUL \
  -template "$CT_FILE:$NX_FILE:/usr/sbin/nginx -s reload";

下载这个Dockerfile,请点击这里

注意: 上面的 \n\ 表示另起一行,并且为Docker的多行命令去除换行。

这个Docker容器将会运行Consul Template和Nginx,当服务有变化时,它将重写Nginx的app.conf文件并且重新加载Nginx。

通过以下命令构建这个容器:
docker build -t drcon .

然后,启动容器:
docker run -it \
-e "CONSUL=$DOCKER_IP:8500" \
-e "SERVICE=simple" \
-p 80:80 drcon

SERVICE 选项用来指定Consul上的服务。因此,上面这个DR CoN 容器将会通过所有命名为 simple 的服务来达到负载均衡。

组装到一起

现在让我们把一切都组装起来。

运行Consul:
docker run -it -h node \
 -p 8500:8500 \
 -p 53:53/udp \
 progrium/consul \
 -server \
 -bootstrap \
 -advertise $DOCKER_IP

运行Registrator:
docker run -it \
-v /var/run/docker.sock:/tmp/docker.sock \
-h $DOCKER_IP progrium/registrator \
consul://$DOCKER_IP:8500

运行DR CoN:
docker run -it \
-e "CONSUL=$DOCKER_IP:8500" \
-e "SERVICE=simple" \
-p 80:80 drcon

执行 curl $DOCKER_IP:80 命令,结果如下:
curl: (52) Empty reply from server

现在开启 simple 的服务:
docker run -it \
-e "SERVICE_NAME=simple" \
-p 8000:80 python/server

执行后,将发生:
  1. Registrator将这个服务注册到Consul;
  2. Consul Template重写nginx.conf,然后重新加载配置。


现在执行curl $DOCKER_IP:80将成功路由到这个服务。

如果我们在其他端口上启动另一个simple服务:
docker run -it \
-e "SERVICE_NAME=simple" \
-p 8001:80 python/server

请求将通过这两个simple服务进行负载均衡。

有趣的是,我们可以执行while true; do curl $DOCKER_IP:80; sleep 1; done这个脚本,当关闭或者启动simple服务时,脚本仍在快速运行且所有请求都能正常进行。

总结

借助Docker,描述、分布和实现DR CoN这类框架变得更加容易,当然也少不了Consul这么好用的工具。使用Docker更加强大的工具来组装服务是非常有趣和有用的,现在我就能创建一种横向可扩展的框架,并且一切都能正常运行。

注[1]: Docker在1.2版本之后就默认使用libcontainer,而不是LXC,虽然Docker支持LXC。由于不论libcontainer和LXC都是基于Linux Kernel中的namespace和Cgroup,所以现在只能运行在Linux上。

原文链接:Scalable Architecture DR CoN: Docker, Registrator, Consul, Consul Template and Nginx (翻译: 肖远昊 校对:李颖杰)

===========================

译者介绍

肖远昊,硕士研究生,就读于北京航空航天大学计算机学院ACT实验室,目前从事虚拟化、云计算方向的研究。希望通过DockerOne与大家一起交流和学习Docker。

4 个评论

good job
请假下, Consul Template可以实时/定时监控模板文件的变化然后触发动作吗
mark
"这个Docker容器将会运行Consul Template和Nginx,当服务有变化时,它将重写Nginx的app.conf文件并且重新加载Nginx"中说的“当服务有变化时”,请问这个变化指的是什么?重写的作用是什么?

要回复文章请先登录注册