本文的目的是探索1.9版中引入的新Docker网络功能。 我们将它们应用于Docker Swarm集群。 出于实践目的,我们将容器部署到使用Vagrant在本地创建的Swarm集群中,并将Consul作为服务注册表运行,将Registrator作为工具运行,以监视Docker守护进程并注册/注销我们运行/停止的容器。 合并后,Consul和Registrator将充当我们集群中的服务发现。 我不会详细介绍Docker Swarm或服务发现的工作原理。 您可以在以下文章中找到有关这些主题的更多信息。
- 服务发现:Zookeeper vs etcd vs Consul
- Docker集群工具比较:Kubernetes与Docker Swarm
- 使用Docker Swarm,Docker Compose和Consul扩展到无限
我们将直接跳过Swarm集群中使用的Docker网络功能。
设置集群
首先是第一件事。 让我们设置三个VM,将它们用作练习基础。 swarm-master节点将充当主节点,而其他两个VM将代表由两个节点组成的集群。 所有三个VM都将运行Ubuntu,并使用VirtualBox和Vagrant创建。 请确保两者均已安装。 您还需要Git来克隆将在本文中使用的代码。 如果您是Windows用户,请先按照在Windows上运行Linux VM中介绍的说明进行操作,然后再介绍以下内容。
让我们从创建将模拟Swarm集群的VM开始。
git clone https://github.com/vfarcic/docker-swarm-networking.git
cd docker-swarm-networking
vagrant up swarm-master swarm-node-1 swarm-node-2
vagrant ssh swarm-master
现在已经创建了VM,并且我们在swarm-master内部,让我们为VM提供Docker,Docker Compose,Consul,Registrator和Swarm。 我们将通过Ansible做到这一点 。 如果您不熟悉Ansible,则可以在此博客中找到很多示例。
ansible-playbook /vagrant/ansible/swarm.yml \
-i /vagrant/ansible/hosts/prod
swarm.yml剧本确保Docker正在运行并配置为支持Swarm。 它还为服务器配备了Docker Compose,Consul,Swarm和Registrator。
让我们仔细检查一切是否按预期进行。
export DOCKER_HOST=tcp://localhost:2375
docker info
curl localhost:8500/v1/catalog/nodes | jq '.'
DOCKER_HOST变量告诉Docker将命令发送到在端口2375上运行的Swarm主服务器 。 随后是docker info
,该docker info
显示Swarm集群中有两个节点。 最后,最后一条命令请求在Consul中注册的所有节点的列表,并获得所有三个VM(一个Swarm主节点和两个Swarm节点)作为响应。
目前,我们已经启动并运行了Swarm集群,可以开始使用Docker网络了。 但是,在继续介绍实际示例之前,让我们快速了解其背后的想法。
使用Docker Swarm和Docker Networking进行部署
不久前,Docker推出了新版本1.9。 毫无疑问,它是自1.0版以来最重要的发行版。 它为我们提供了两个期待已久的功能; 多主机网络和持久卷。 网络使不赞成使用链接,这是我们需要在多个主机之间连接容器的功能。 不再需要代理来链接构成服务的多个容器。 这并不是说代理没有用,而是我们应该将其用作服务和网络的公共接口,以连接形成逻辑组的容器。 新的Docker网络和代理服务具有不同的优势,应用于不同的用例。 代理服务尤其提供负载平衡,并可以控制对我们服务的访问。 Docker网络是连接形成单个服务并驻留在同一网络上的各个容器的便捷方法。 Docker网络的一个常见用例是需要连接到数据库的服务。 我们可以通过网络连接这两个。 此外,服务本身可能需要扩展并运行多个实例。 具有负载均衡器的代理服务应满足该要求。 最后,其他服务可能需要访问此服务。 由于我们想利用负载平衡,因此访问也应该通过代理进行。
该图代表一个常见的用例。 我们有一个扩展的服务,在节点1和3上运行着两个实例。与这些服务的所有通信都是通过代理服务执行的,该代理服务负责负载平衡和安全性。 想要访问我们服务的任何其他服务(无论是外部还是内部)都需要通过代理。 在内部,服务使用数据库。 服务和数据库之间的通信是内部的,并通过多主机网络执行。 此设置使我们可以轻松地在集群内扩展,同时保持组成单个服务的容器之间的所有通信内部。 换句话说,组成服务的容器之间的所有通信都是通过网络完成的,而不同服务之间的通信是通过代理完成的。
有多种创建多主机网络的方法。 我们可以手动设置网络。
docker network create my-network
docker network ls
network ls
命令的输出如下。
NETWORK ID NAME DRIVER
f8a50a3c9c13 swarm-node-1/host host
8ae6cefc3957 swarm-node-2/host host
5f68a88668f6 swarm-node-2/bridge bridge
397107ba0daf swarm-node-2/none null
b655577f0030 swarm-node-1/bridge bridge
efb02b0fa9b9 swarm-node-1/docker_gwbridge bridge
eb5ff0f0136a swarm-node-1/none null
71b80ae02620 my-network overlay
ac4261d5e27a swarm-node-2/docker_gwbridge bridge
您可以看到其中一个网络是我们之前创建的my-network 。 它涵盖了整个Swarm集群。 我们可以将此网络与–net参数一起使用。
docker run -d --name books-ms-db \
--net my-network \
mongo
docker run -d --name books-ms \
--net my-network \
-e DB_HOST=books-ms-db \
-p 8080 \
vfarcic/books-ms
在继续之前,让我们确认Swarm已在集群中分发了容器。
docker ps --filter name=books --format "table {{.Names}}"
就我而言,输出如下。
NAMES
swarm-node-2/books-ms
swarm-node-1/books-ms-db
您可以看到每个容器都已部署到不同的节点。 这是Docker Swarm的主要目的; 在整个集群中分布容器。 问题是,如果这些容器位于单独的节点上,它们如何相互通信?
我们开始了两个组成一个服务的容器。 books-ms是与充当数据库的books-ms-db通信的API。 由于两个容器都具有–net my-network参数,因此它们都属于my-network网络。 结果,Docker更新了主机文件,为每个容器提供了一个可用于内部通信的别名。
让我们进入books-ms容器,并查看hosts文件。
docker exec -it books-ms cat /etc/hosts
exec命令的输出如下。
10.0.0.2 3166318f0f9c
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
10.0.0.2 books-ms-db
10.0.0.2 books-ms-db.my-network
主机文件中有趣的部分是最后两个条目。 泊坞窗检测到的书籍,MS-DB容器使用相同的网络,并通过添加书籍-MS-DB(数据库容器的名称)和书籍-MS-db.my网络数据库容器(名称更新hosts文件加网络的名称)别名。 如果使用某种形式的约定,那么对服务进行编码的方式很简单,即它们使用诸如别名之类的别名与位于单独容器中的资源(在本例中为数据库)进行通信。
我们还将环境变量DB_HOST传递给book-ms 。 这向我们的服务指示了用于连接数据库的主机。 我们可以通过输出容器的环境看到这一点。
docker exec -it books-ms env
命令的输出如下。
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=eb3443a66355
DB_HOST=books-ms-db
DB_DBNAME=books
DB_COLLECTION=books
HOME=/root
如您所见,环境变量之一是DB_HOST ,其值是books-ms-db 。
我们现在拥有的是Docker网络,该网络创建了主机别名books-ms-db ,该主机别名指向Docker创建的网络的IP。 我们还有一个环境变量DB_HOST ,其值是books-ms-db 。 服务的代码使用该变量连接到数据库。 您可能使用其他逻辑。 重要的是,Docker使用别名来更新主机文件,该别名可用于访问属于同一覆盖网络的任何其他容器。
与运行create network命令相比,还有一种更好的方法来创建网络 。 在尝试之前,让我们停止这两个容器并删除网络。
docker rm -f books-ms books-ms-db
docker network rm my-network
这次,我们将通过Docker Compose运行容器。 尽管我们可以在docker-compose.yml中使用net参数,从而执行与之前完全相同的过程,但是最好使用新的Docker Compose参数–x-networking 。
cd /vagrant/booksms
docker-compose --x-networking up -d db app
我们刚刚运行的命令的输出如下。
Creating network "booksms" with driver "None"
Creating booksms_app_1
Creating books-ms-db
在创建服务应用程序和数据库之前 ,Docker创建了一个名为booksms的新网络。 网络的名称与项目的名称相同(默认为目录名称)。
我们可以通过运行docker network ls
命令来确认该网络已创建。
docker network ls
输出如下。
NETWORK ID NAME DRIVER
6e5f816d4800 swarm-node-1/host host
aa1ccdaefd70 swarm-node-2/docker_gwbridge bridge
cd8b1c3d9be5 swarm-node-2/none null
ebcc040e5c0c swarm-node-1/bridge bridge
6768bad8b390 swarm-node-1/docker_gwbridge bridge
8ebdbd3de5a6 swarm-node-1/none null
58a585d09bbc booksms overlay
de4925ea50d1 swarm-node-2/bridge bridge
2b003ff6e5da swarm-node-2/host host
如您所见, 覆盖网络bookms已创建。
我们还可以再次检查容器内的主机文件是否已更新。
docker exec -it booksms_app_1 cat /etc/hosts
输出如下。
10.0.0.2 3166318f0f9c
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
10.0.0.3 books-ms-db
10.0.0.3 books-ms-db.my-network
最后,让我们看看Swarm如何分发我们的容器。
docker ps --filter name=books --format "table {{.Names}}"
输出如下。
NAMES
swarm-node-2/books-ms-db
swarm-node-1/booksms_app_1
Swarm将应用程序容器部署到swarm-node-1 ,将db容器部署到swarm-node-2 。
最后,让我们测试book-ms服务是否正常运行。 我们不知道Swarm将哪个容器部署到了该容器,也不知道哪个端口被暴露。 由于我们还没有代理,因此我们将从Consul检索IP和服务的端口,发送PUT请求以将数据存储到位于不同容器中的数据库,最后发送GET请求检查我们是否可以检索记录。 由于我们没有能够确保将请求重定向到正确的服务器和端口的代理服务,因此我们必须从Consul检索地址和端口。 有关如何设置代理服务的更多信息,请查阅《 使用Docker Swarm扩展到无限》,《 Docker Compose和Consul》一文。
ADDRESS=`curl \
localhost:8500/v1/catalog/service/books-ms \
| jq -r '.[0].ServiceAddress + ":" + (.[0].ServicePort | tostring)'`
curl -H 'Content-Type: application/json' -X PUT -d \
'{"_id": 2,
"title": "My Second Book",
"author": "John Doe",
"description": "A bit better book"}' \
$ADDRESS/api/v1/books | jq '.'
curl $ADDRESS/api/v1/books | jq '.'
最后的命令输出如下。
[
{
"author": "John Doe",
"title": "My Second Book",
"_id": 2
}
]
如果该服务无法与位于其他节点的数据库进行通信,则我们将无法放置或获取数据。 部署到单独服务器的容器之间的网络正常工作! 我们要做的就是在Docker Compose( –x-networking )中使用一个附加参数,并确保服务代码利用了hosts文件中的信息。
Docker网络的另一个优点是,如果一个容器停止工作,我们可以将其重新部署(可能重新部署到单独的服务器上),并且假设使用它的服务可以处理临时连接丢失,则可以继续使用它,就像什么也没发生一样。
Docker网络是人们期待已久的功能,它使我们可以分发容器而不必担心它们是否能够相互通信。 最后,我们可以分发容器而没有引入链接的限制(链接的容器必须在同一服务器上运行)。 不再需要我们某些人过去必须采用的解决方法。 这是一项令人兴奋的功能,可以肯定地使Docker Swarm迈上一个新台阶。
自己试试吧。 研究Docker网络引入的其他选项。 完成后,您可能需要停止我们创建的VM,以便为其他任务释放资源。
exit
vagrant halt
翻译自: https://www.javacodegeeks.com/2015/11/deploying-containers-docker-swarm-docker-networking.html