注:该文由 adetante 编写,原文地址为 Service discovery with Docker
这篇博客的第一篇文章,我将写一篇基于 Docker 容器构建一个不可变架构的解决方案的文章。
这个主题将通过系列文章来描述,从最简单的案例到更复杂的架构。
总体的思想是设计一个“基于概念验证”的解决方案,它允许:
- 启动和停止一个新容器,如果系统需要扩展或是下线
- 当一个新版本的应用准备推送到生产,这时使用新容器替换老的容器
- 使用服务注册和发现来自动把新的容器推送到生产架构
概述
这个逻辑架构是非常简单的,一个无状态的应用通过负载均衡器访问。
每个应用的实例运行在它自己的 docker 容器中。
为了动态配置管理,当我们启动和停止一个新容器的时候,我们想后端能自动注册进负载均衡器。这是基本需求,叫做**服务发现***:我们想负载均衡器能自动发现提供服务的容器。
在这篇文章中,所有的节点将运行在相同的 docker 主机上。这是非常简单的,但是这是实现基础概念的第一个方法。然后我们将通过允许在不同主机上透明的部署来是架构复杂化。
工具集
第一个示例将使用以下工具实现:
- Docker。一个运行应用容器的开源平台
- Synapse。一个 Airbnb 团队开发的简单的服务发现的工具
- Haproxy。一个负载一个后端节点列表的 TCP 流量代理,它打开一个本地的端口,然后把流量传递进这个后端节点的端口。
服务发现
目标是减少或消除组件之间的“手动”的连接。当你把你的应用程序推送进生产的时候,所有的这些事情都可以配置:数据库服务器的主机和端口,REST 服务的 URL 等等,在一个高可扩展的架构中,这些连接可以动态改变。一个新的后端可以被添加,一个数据库节点可以被停止。你的应用需要适应这种动态环境。
这里有一些工具可以管理这些需求(Apache Zookeeper, etcd, ...)。这些工具的普遍原则是:当启动的时候,一个服务的实例必须注册进配置服务器。当停止的时候(完美停止或是 Crash 了),节点必须从配置服务中移除掉。注册后,其他服务可以在配置服务器中搜索到提供制度服务的实例列表(主机和端口)。
Synapse
Synapse 是一个简单的服务发现的工具。Synapse 与以下俩个组件一起使用:
- Watcher:它们经常检查一组服务器提供的服务。这可以通过连接 Zookeeper,etcd 或是通过使用 Docker API 来检查 Docker 容器来实现。
- Haproxy:Synapse 根据 watcher 的结果来自动改变 HAproxy 的配置。这个意味着当一个新的实例被 watcher 发现,一个后端会被添加进 HAproxy 并且可以通过代理的本地端口访问的。同样地,当实例停止的时候,Synapse 移除后端节点。
第一个解决方案
第一个解决方案将使用 Synapse 和 检查 Docker 容器实现。
Synapse 管理一个运行在安装了 Docker 的相同的主机上的以 8080 端口运行着的 HAproxy 实例。
Synapse 检查 Docker 来发现容器是否运行着一个指定镜像并且暴露一个指定端口。为每一个匹配的容器,Synapse 把其添加进 HAproxy 的配置。
对于这个示例,我们从一个干净的 *Ubuntu 14.04 amd64 安装开始。
安装 Docker
安装步骤已经在 Docker 的文档中描述了:
$ sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 36A1D7869245C8950F966E92D8576A8BA88D21E9
$ sudo sh -c "echo deb https://get.docker.io/ubuntu docker main > /etc/apt/sources.list.d/docker.list"
$ sudo apt-get update
$ sudo apt-get install lxc-docker
把以下行添加进 /etc/default/docker
,使得 Docker API 在 tcp 上可用:
DOCKER_OPTS="-H 127.0.0.1:4243"
重起 docker:
$ sudo service docker restart
最后,定义以下环境变量来以便 docker 客户端使用 tcp API:
$ export DOCKER_HOST=tcp://127.0.0.1:4243
为 web(nodejs)应用程序创建镜像
从 Docker 仓库获取最新的 Ubuntu 镜像
$ sudo -E docker pull ubuntu:latest
启动一个新的容器
$ sudo -E docker run -ti ubuntu bash
在这个容器中,安装 nodejs
$ apt-get update && apt-get install -y nodejs
在这个容器中,使用以下内容创建一个简单的 nodejs 脚本 /server.js
var http = require('http');
var os = require('os');
var server = http.createServer(function (request, response) {
response.writeHead(200, {"Content-Type": "text/plain"});
response.end("Hello from " + os.hostname() + "\n");
});
server.listen(8000);
console.log("Server running at http://127.0.0.1:8000/");
在这个容器中,使用以下内容编写启动脚本 /run.sh
#! /bin/sh
/usr/bin/nodejs /server.js
$ chmod a+x /run.sh
停止容器并且创建一个新的镜像:
$ exit
# Get the ID of the container
$ sudo -E docker ps -a
# Change 3796ab3f5b76 in the following command with the ID listed above
$ sudo -E docker commit 3796ab3f5b76 local/nodeapp
# Remove the old container
$ sudo -E docker rm 3796ab3f5b76
在主机上安装 synapse
$ sudo apt-get install build-essential ruby ruby-dev haproxy
$ sudo gem install synapse
编辑 /etc/default/haproxy
把 ENABLED 设置成 1
。
启动一个后端实例
启动一个 webapp 容器的实例:
$ sudo -E docker run -d -p 8000 local/nodeapp /run.sh
通过直接在这个容器中调用 nodejs 来测试。我们必须首先获取暴露的公共端口。
# Get the public port (mapped to 8000 in the container, here 49153)
$ sudo docker ps
$ curl http://127.0.0.1:49153
# Responds with "Hello from {container_id}"
使用 Synapse 自动配置 HAproxy
使用以下内容创建一个 /etc/synapse.json.conf
配置文件:
{
"services": {
"nodesrv": {
"discovery": {
"method": "docker",
"servers": [
{
"name": "localhost",
"host": "localhost"
}
],
"container_port": 8000,
"image_name": "local/nodeapp"
},
"haproxy": {
"port": 8080,
"listen": [
"mode http",
"option httpchk /",
"http-check expect string Hello"
]
}
}
},
"haproxy": {
"reload_command": "service haproxy reload",
"config_file_path": "/etc/haproxy/haproxy.cfg",
"do_writes": true,
"do_reloads": true,
"global": [
"chroot /var/lib/haproxy",
"user haproxy",
"group haproxy",
"daemon"
],
"defaults": [
"contimeout 5000",
"clitimeout 50000",
"srvtimeout 50000"
]
}
}
我们可以在这个文件中看到:
-
services.nodesrv.discovery
: 配置的观察者。这里我们使用 Docker API 来发现容器运行的名为 local/nodeapp 的镜像以及它暴露的 8080 端口 -
services.nodesrv.haproxy
:配置与 nodesrv service 有关的相关的 HAproxy 端口 -
haproxy
:被 Synapse 管理的全局配置实例
启动 HAproxy 和 Synapse
$ sudo service haproxy start
$ sudo synapse -c /etc/synapse.json.conf
通过直接调用 HAproxy(监听 8080 端口)来测试
$ curl http://localhost:8080
# Responds Hello from {container_id}
用 nodeapp 启动第二个容器:
$ sudo -E docker run -d -p 8000 local/nodeapp /run.sh
通过 HAproxy 测试一些请求。几秒后,每个节点都将响应。
在一个新的 shell 中,运行一下循环,每两秒调用一次 HAproxy:
while :
do
curl http://localhost:8080
sleep 2
done
HAproxy 不是通过 container1 就是通过 container2 响应。
停止其中一个容器:
$ sudo -E docker stop {container_id}
几秒后,仅仅剩下的容器响应。
但是在之前我们可以看到一些 503 Service Unavailable
错误。这是由于 Synapse 发现停止的容器并且从代理移除它的时候。
总结
在第一篇文章中,我配置 HAprxoy 从 Docker 容器发现后端节点。Synapse 对使这个进程自动化给予了很多帮助。尽管如此,这个解决方案还有一些缺点:
- 因为 Synapse 使用 Docker API 发现后端服务,所有的被组织在一个 HAprxoxy 前端的服务必须是在同一个 Docker 主机上。
- 正如我们所看到的,当停止容器的时候,会有一些中断。这是由于 Synapse 定期的调用 Docker API 来发现新的或是已经移除的容器。
在下一篇文章中,这个解决方案将被扩展成允许在多主机透明部署。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。