2

注:该文由 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 来发现新的或是已经移除的容器。

在下一篇文章中,这个解决方案将被扩展成允许在多主机透明部署。


yexiaobai
4.8k 声望875 粉丝

就是不告诉你 O(∩_∩)O哈哈~。