docker consul
在过去的一年中,我非常喜欢使用Consul处理与服务发现有关的所有事情。 如果您正在执行微服务,那么您可能会遇到这样的问题:当您创建的服务数量增加时,管理所有这些服务之间的通信变得越来越困难。 领事非常适合此问题。 它为服务发现提供了一种易于使用的,基于开放标准的(有针对性的)方法(此外,它还提供了大量其他功能)。 我最近做了一个关于如何使用Consul在微服务体系结构中进行服务发现的演示,并收到了一些请求以对它进行更多的解释。 因此,在这篇文章中,以及一些后续操作中,我将进一步解释如何使用Consul。 我不仅会只关注Consul提供的服务发现部分,而且还会向您展示Consul或围绕它的工具之一提供的其他一些功能。 请注意,可以在以下存储库中找到所有示例,docker文件等: https : //github.com/josdirksen/next-build-consul 。 因此,无需克隆本文中的“复制和粘贴”,只需克隆存储库即可。
入门
在第一篇文章中,我们将创建一个基于docker的简单体系结构,其中包含许多服务,这些服务将使用简单的HTTP调用相互通信,并使用Consul相互发现。 我们最初的目标架构看起来像这样:
为了完成所有这些工作,我们首先需要采取以下步骤来设置环境,我们可以在其中运行服务:
注意:我正在Mac上使用docker-machine完成所有操作。 如果运行Windows或Linux,则命令可能会略有不同。 我们希望适用于Mac(和Windows)的DockerSwift脱离beta( https://blog.docker.com/2016/03/docker-for-mac-windows-beta/) ,因此我们不再需要它了…
- 创建四个docker-machine:一台将运行Consul服务器,三台将运行我们的服务和Consul代理。
- 启动主领事服务器:我们将使用一台领事服务器(和多个领事代理,稍后再介绍)来跟踪正在运行的服务和一些与docker相关的东西。
- 设置docker swarm:为避免必须单独部署我们的服务,我们将使用Docker Swarm来管理我们将在其上运行服务的三个节点。 在本文的其余部分中,我们将使用docker-compose来启动和停止单个服务。
- 设置docker覆盖网络:如果我们希望我们的服务以简单的方式相互通信,我们将创建一个覆盖网络。 这将使我们部署到docker的组件能够轻松地彼此通信(因为它们将共享同一子网)
- 启动Consul代理:每个节点将拥有自己的Consul代理,该代理将监视该节点上服务的运行状况并与Consul服务器通信。
创建docker-machines
因此,我们要做的第一件事是创建一些docker-machines。 首先,我们将创建用于存放领事服务器的docker-machine。 我们之所以首先运行它,是因为我们可以将其他docker-machine指向在该容器内运行的领事,并使用它来管理docker-swarm和我们要使用的覆盖网络。
docker-machine create nb-consul --driver virtualbox
在启动Consul服务器之前,让我们快速查看Consul背后的体系结构。
在此图中,您可以看到Consul可以在两种模式下运行。它可以在Server模式或Agent模式下运行。 所有服务器互相交谈,并确定谁是领导者。 代理仅与其中一台服务器对话,通常在也运行服务的节点上运行。 请注意,群集中所有服务器和代理之间的状态是共享的。 因此,当一项服务向其中一个代理注册时,该信息可用于彼此连接的所有服务器和代理。
对于这组文章,我们将不会设置服务器集群,而只使用其中一个。 现在我们已经运行了docker-machine,我们可以启动领事服务器。 在开始之前,让我首先向您展示一个简单的脚本,该脚本使在不同的docker-machines之间切换以及我们用来避免键入“ docker-machine”的别名更加容易。
# quickly switch environments e.g: . dm-env nb-consul
$ cat ~/bin/dm-env
eval `docker-machine env $2 $1`
# avoid typing too much
$ alias dm
dm=docker-machine
因此,有了这些别名,首先我们执行“ dm-env nb-consul”以选择正确的docker-machine。
启动主领事机
接下来,我们获得该服务器的IP地址,然后我们可以像这样启动Consul服务器。
# get the ip address
$
192.168.99.106
# use this ip address in the advertise
docker run -d --restart always -p 8300:8300 -p 8301:8301 -p 8301:8301/udp -p 8302:8302/udp \
-p 8302:8302 -p 8400:8400 -p 8500:8500 -p 53:53/udp -h server1 progrium/consul \
-server -bootstrap -ui-dir /ui -advertise $(dm ip nb-consul)
至此,我们的docker consul服务器正在运行。 现在,让我们创建其他三台将在其上运行服务的服务器。
设置docker swarm
如您在以下命令中看到的,我们还同时创建了一个docker swarm集群,并且“ nb1”节点是swarm主机。
docker-machine create -d virtualbox --swarm --swarm-master \
--swarm-discovery="consul://$(docker-machine ip nb-consul):8500" \
--engine-opt="cluster-store=consul://$(docker-machine ip nb-consul):8500" \
--engine-opt="cluster-advertise=eth1:2376" nb1
docker-machine create -d virtualbox --swarm \
--swarm-discovery="consul://$(docker-machine ip nb-consul):8500" \
--engine-opt="cluster-store=consul://$(docker-machine ip nb-consul):8500" \
--engine-opt="cluster-advertise=eth1:2376" nb2
docker-machine create -d virtualbox --swarm \
--swarm-discovery="consul://$(docker-machine ip nb-consul):8500" \
--engine-opt="cluster-store=consul://$(docker-machine ip nb-consul):8500" \
--engine-opt="cluster-advertise=eth1:2376" nb3
至此,我们已经启动并运行了四台docker机器。 一个正在运行领事主,而其他人还没有做太多。
$ dm ls
NAME ACTIVE DRIVER STATE URL SWARM
nb1 - virtualbox Running tcp://192.168.99.110:2376 nb1 (master)
nb2 - virtualbox Running tcp://192.168.99.111:2376 nb1
nb3 - virtualbox Running tcp://192.168.99.112:2376 nb1
nb-consul * virtualbox Running tcp://192.168.99.106:2376
在继续配置从属服务器之前,可能需要使用另外一个实用程序脚本:
$ cat addToHost
#!/usr/bin/env bash
$ cat addToHost
#!/usr/bin/env bash
update-docker-host(){
# clear existing docker.local entry from /etc/hosts
sudo sed -i "/"${1}"\.local$/d" /etc/hosts
# get ip of running machine
export DOCKER_IP="$(docker-machine ip $1)"
# update /etc/hosts with docker machine ip
&& sudo /bin/bash -c "echo \"${DOCKER_IP} $1.local\" >> /etc/hosts"
}
update-docker-host nb1
update-docker-host nb2
update-docker-host nb3
update-docker-host nb-consul
该脚本将docker-machines的ip地址添加到本地“主机”文件中。 这意味着我们只需访问“ http://nb-consul.local:8500 ”即可简单地访问Docker主机。
设置Docker网络
在我们的方案中,我们希望我们所有的服务都能够相互通信。 我们有多个Docker主机,因此我们需要找到一种简单的方法来使服务在节点“ nb1”中运行,以便能够与“ nb2”进行通信。 实现此目的的最简单方法是创建一个由Docker容器中运行的所有服务使用的单一网络。 为此,我们创建一个简单的“覆盖”网络,如下所示:
# select the swarm master
$dm-env nb1 --swarm
# create an overlay network the the name my-net
并且由于我们是在群集主服务器上创建的,因此该网络将在群集的所有成员中可用。 稍后创建服务时,我们会将其连接到该网络,以便它们都共享相同的子网。
启动领事代理
要启动领事代理,我们将使用docker-compose。 docker-compose文件非常简单,并且是避免键入所有启动命令的简单方法(尤其是在进行实时演示时)
version: '2'
services:
agent-1:
image: progrium/consul
container_name: consul_agent_1
ports:
- 8300:8300
- 8301:8301
- 8301:8301/udp
- 8302:8302
- 8302:8302/udp
- 8400:8400
- 8500:8500
- 53:53/udp
environment:
- "constraint:node==nb1"
command: -ui-dir /ui -join 192.168.99.106 -advertise 192.168.99.110
networks:
default:
aliases:
- agent-1
agent-2:
image: progrium/consul
container_name: consul_agent_2
ports:
- 8300:8300
- 8301:8301
- 8301:8301/udp
- 8302:8302
- 8302:8302/udp
- 8400:8400
- 8500:8500
- 53:53/udp
environment:
- "constraint:node==nb2"
command: -ui-dir /ui -join 192.168.99.106 -advertise 192.168.99.111
networks:
default:
aliases:
- agent-2
agent-3:
image: progrium/consul
container_name: consul_agent_3
ports:
- 8300:8300
- 8301:8301
- 8301:8301/udp
- 8302:8302
- 8302:8302/udp
- 8400:8400
- 8500:8500
- 53:53/udp
environment:
- "constraint:node==nb3"
command: -ui-dir /ui -join 192.168.99.106 -advertise 192.168.99.112
networks:
default:
aliases:
- agent-3
networks:
default:
external:
name: my-net
此文件没有什么特别的。 您可能会注意到的唯一一件事是,我们在命令中使用显式IP地址来启动Consul代理。 我们可以轻松地为此使用一个环境变量,它是通过一个简单的bash脚本设置的。 但是对于本文,我们仅指定相关码头工人机器的IP地址。 确保您的“ DOCKER_HOST”指向docker swarm master并启动代理,如下所示:
# start the agents
$ docker-compose -f docker-compose-agents.yml up -d
Creating consul_agent_3
Creating consul_agent_2
Creating consul_agent_1
# check what is running
$ docker ps --format '{{ .ID }}\t{{ .Image }}\t{{ .Command }}\t{{ .Names}}'
bf2000882dcc progrium/consul "/bin/start -ui-dir /" nb1/consul_agent_1
a1bc26eef516 progrium/consul "/bin/start -ui-dir /" nb2/consul_agent_2
eb0d1c0cc075 progrium/consul "/bin/start -ui-dir /" nb3/consul_agent_3
此时,我们有一个在Consul机器“ nb-consul”中运行的Consul服务器,并且在节点上运行了三个代理。 为了验证我们的设置,我们打开Consul服务器的界面: http://nb-consul.local:8500
而且,正如您所看到的,我们有1台服务器(我们的Consul Server)正在运行,并且有3个代理。 因此,在这一点上,我们可以开始添加我们的服务,以使用此架构:
添加服务
在这种情况下,服务只是简单的golang应用程序。 我创建了一个可以在前端或后端模式下运行的简单应用程序。 在前端模式下,它提供带有按钮的最小UI,以调用后端服务;在后端模式下,它提供一个简单的API,该API将一些信息返回给主叫方,并且提供一个简单的UI,显示一些统计信息。 为了方便起见,我已将此映像推送到Docker中心( https://hub.docker.com/r/josdirksen/demo-service/ ),因此您可以轻松使用它,而无需从源github存储库中构建。
如您在以前的体系结构概述中所看到的,我们希望在每个节点上启动一个前端和一个后端服务。 我们可以手动执行此操作,但是由于有了docker-swarm,我们可以轻松地通过单个docker-compose文件执行此操作。 如果要查看此文件的外观,可以在此处查看源代码( https://github.com/josdirksen/next-build-consul) 。
首先启动服务,然后看一下它们如何在Consul中注册自己:
# make sure you select the swarm master
$ . dm-env nb1 --swarm
# now use docker-compose to run the backend services
$ docker-compose -f docker-compose-backend.yml up -d
Creating Backend2
Creating Backend3
Creating Backend1
# and use docker-compose to run the frontend services
$ docker-compose -f docker-compose-frontend.yml up -d
Creating Frontend1
Creating Frontend3
Creating Frontend2
# check in docker if everything is running
$ docker ps --format '{{ .ID }}\t{{ .Image }}\t{{ .Command }}\t{{ .Names}}'
65846be2e367 josdirksen/demo-service "/entrypoint.sh --typ" nb2/Frontend2
aedd80ab0889 josdirksen/demo-service "/entrypoint.sh --typ" nb3/Frontend3
d9c3b1d83b5e josdirksen/demo-service "/entrypoint.sh --typ" nb1/Frontend1
7c860403b257 josdirksen/demo-service "/entrypoint.sh --typ" nb1/Backend1
80632e910d33 josdirksen/demo-service "/entrypoint.sh --typ" nb3/Backend3
534da0670e13 josdirksen/demo-service "/entrypoint.sh --typ" nb2/Backend2
bf2000882dcc progrium/consul "/bin/start -ui-dir /" nb1/consul_agent_1
a1bc26eef516 progrium/consul "/bin/start -ui-dir /" nb2/consul_agent_2
eb0d1c0cc075 progrium/consul "/bin/start -ui-dir /" nb3/consul_agent_3
如您在“ docker ps”的最后输出中所见,我们有三个前端,三个后端和三个领事代理正在运行。 这几乎就是我们的目标。 当我们打开领事时,我们也可以看到以下内容:
如您所见,我们在Consul中注册了三个前端服务和三个后端服务。 如果我们打开一个后端,我们将看到一些常规信息:
我们可以使用前端UI来调用我们的后端之一:
但是,我们需要回答几个问题:
- 服务注册 :启动后端或前端服务时,我们会在Consul中看到它。 我们如何做到这一点?
- 服务发现 :当我们单击前端服务上的按钮时,就会调用其中一个后端服务。 前端如何知道要调用哪个服务?
在接下来的部分中,我们将进一步探讨这些问题。
服务注册
首先,服务注册。 要向Consul注册服务,我们必须对本地consul-agent进行非常简单的REST调用,如下所示:
{
"Name": "service1",
"address": "10.0.0.12",
"port": 8080,
"Check": {
"http": "http://10.0.0.12:8080/health",
"interval": "5s"
}
}
如您所见,我们指定可以找到服务的名称,地址和端口,并添加其他运行状况检查。 当healtcheck返回200范围内的值时,该服务将标记为运行状况良好,并且可以被其他服务发现。 那么我们如何为我们的服务做到这一点。 如果查看此示例的源代码,则可以找到“ script / entrypoint.sh”文件,如下所示:
#!/usr/bin/env bash
IP=`ip addr | grep -E 'eth0.*state UP' -A2 | tail -n 1 | awk '{print $2}' | cut -f1 -d '/'`
NAME="$2-service"
read -r -d '' MSG << EOM
{
"Name": "$NAME",
"address": "$IP",
"port": $PORT,
"Check": {
"http": "http://$IP:$PORT",
"interval": "5s"
}
}
EOM
curl -v -XPUT -d "$MSG" http://consul_agent_$SERVER_ID:8500/v1/agent/service/register && /app/main "$@"
该脚本的作用是,它创建要发送到领事代理的JSON,并在启动主应用程序之前使用“ curl”发送它。 因此,当服务启动时,它会自动将其注册到本地领事代理(请注意,例如,您也可以使用Consul Registrator来更自动地进行此操作。之所以可行,是因为我们可以通过其名称引用本地代理,因为它位于如果您仔细观察,可能会发现我们在这里使用了两个环境变量,这些变量是通过docker-compose文件传入的:
...
frontend-1:
image: josdirksen/demo-service
container_name: Frontend1
ports:
- 8090:8090
environment:
- "constraint:node==nb1"
- SERVER_ID=1
- SERVERNAME=Server1
- PORT=8090
command: /entrypoint.sh --type frontend
dns: 192.168.99.106
dns_search: service.consul
...
有趣的部分是DNS条目。 您可能还记得192.168.99.106是我们的领事服务器的地址。 这意味着我们针对Consul进行DNS查找(我们也可能指向Consul代理)。
服务发现
通过此设置,我们可以仅按名称引用服务,然后使用DNS来解析它。 下面显示了它是如何工作的。
# check which IPs are registered for the backend-service
# called from outside the container
$ dig @nb-consul.local backend-service.service.consul +short
10.0.9.7
10.0.9.8
10.0.9.6
# If we do this from a container, we can do just this
docker exec -ti nb2/Frontend2 ping backend-service
PING backend-service.service.consul (10.0.9.8): 56 data bytes
64 bytes from 10.0.9.8: icmp_seq=0 ttl=64 time=0.809 ms
64 bytes from 10.0.9.8: icmp_seq=1 ttl=64 time=0.636 ms
酷吧? 我们只需使用DNS即可发现服务。 这也意味着将其集成到我们现有的应用程序中非常容易,因为我们只能依靠基本的DNS解析。 例如,在前端服务中,我们使用以下代码调用后端:
resp, err := http.Get("http://backend-service:8081")
if err != nil {
// handle error
fmt.Println(err)
} else {
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
w.Header().Set("Content-Type",resp.Header.Get("Content-Type"))
w.Write(body)
}
这将使用DNS调用后端服务之一。 由于Consul的DNS timetolive设置为0,我们现在也有了一些简单的故障转移。应用程序可能仍会进行一些缓存,但这意味着我们已经有了一些基本的故障转移:
$ curl -s backend-service:8081
{"result" : {
"servername" : "Server1",
"querycount" : 778
}
}
# shutdown server 1 and do again, curl has a DNS cache of
# 1 minute, so you might need to wait a bit
$ curl -s backend-service:8081
{"result" : {
"servername" : "Server2",
"querycount" : 770
}
}
$ curl -s backend-service:8081
{"result" : {
"servername" : "Server2",
"querycount" : 771
}
}
当然,哪种方法也适用于我们的前端/ golang应用程序:
在本文的后续文章中,我们还将通过引入HAProxy作为更高级的故障转移技术的中介,来展示一些更高级的故障转移。
结论
这几乎涵盖了第一篇文章。 总而言之,我们做了什么:
- 我们使用4个docker节点设置了一个简单的架构。 1个用于领事服务器,三个用于我们的服务。
- 服务在服务启动时向Consul注册。
- 我们无需显式执行某些操作即可启用服务发现。 我们可以使用标准DNS查找服务。
- Consul对DNS使用TTL为0,并使用轮询机制返回可用的服务。 如您所见,当DNS查找失败时,您已经可以将其用于基本故障转移。
在接下来的几周内,请继续关注后续文章。
翻译自: https://www.javacodegeeks.com/2016/04/service-discovery-docker-consul-part-1.html
docker consul