ZooKeeper是一个流行的分布式协调服务。它提供了两种不同的部署方式:单机模式和分布式模式。其中单机模式的部署非常简单,网上也有很多资料,我们今天会利用Docker搭建分布式Zookeeper集群,并来帮助大家熟悉Docker中容器网络模型的使用。

ZooKeeper集群中所有的节点作为一个整体对分布式应用提供服务。节点中有两个的角色:Leader和Follower。在整个集群运行过程中,只有一个Leader,其他节点的都是Follower,如果ZK集群在运行过程中Leader出了问题,系统会采用选举算法重新在集群节点选出一个Leader。

Zookeeper节点之间是利用点对点的方式互相联结在一起的,这样的点对点部署方式对利用Docker容器搭建ZK集群提出了挑战。这是因为Zookeeper集群中每个节点需要在启动之前获得集群中所有节点的IP信息, 而当使用Docker缺省bridge网络模式启动容器时,Docker Daemon会为容器分配一个新的的IP地址。这样就形成了信息循环依赖。我们需要一些技巧来确保Zookeeper集群配置正确。

利用Host网络模式

自从1.9.1之后,Docker容器支持5种不同的网络模式,分别为bridge、host、container、overlay,none。我们可以在docker run命令中利用“--net”参数来指定容器网络。详解信息请参见 https://docs.docker.com/reference/run/#network-settings。

如果启动容器的时候使用--net host模式,那么这个容器将和宿主机共用一个Network Namespace。这时Docker Engine将不会为容器创建veth pair并配置IP等,而是直接使用宿主机的网络配置,就如直接跑在宿主机中的进程一样。注意,这时容器的其他资源,如文件系统、进程列表等还是和宿主机隔离的。

利用host网络,容器的IP地址和机器节点一致,这样我们在部署容器之前就能确定每个ZK节点的IP地址。我们可以分别在三台的机器上用host模式启动一个ZK容器并配置一个分布式集群。

首先我们需要获得一个ZK的Docker镜像。 本文会直接利用阿里云镜像服务上的镜像registry.aliyuncs.com/acs-sample/zookeeper:3.4.8,
你也可以参照GitHub上代码自己构造
https://github.com/AliyunContainerService/docker-zookeeper

我们假设三个节点的主机名:zookeeper1, zookeeper2, zookeeper3

我们分别在三台不同的主机上依次启动zookeeper容器:利用环境变量SERVER_ID指明节点ID,并在/opt/zookeeper/conf/zoo.cfg中添加ZK集群节点配置信息,具体请详见Docker镜像的启动脚本https://github.com/AliyunContainerService/docker-zookeeper/blob/master/run.sh

登录到zookeeper1上,并执行下列命令启动集群的第一个节点

docker run -d \--name=zk1 \--net=host \-e SERVER_ID=1 \-e ADDITIONAL_ZOOKEEPER_1=server.1=zookeeper1:2888:3888 \-e ADDITIONAL_ZOOKEEPER_2=server.2=zookeeper2:2888:3888 \-e ADDITIONAL_ZOOKEEPER_3=server.3=zookeeper3:2888:3888 \registry.aliyuncs.com/acs-sample/zookeeper:3.4.8

登录到zookeeper2上,并执行下列命令启动集群的第二个节点

docker run -d \--name=zk2 \--net=host \-e SERVER_ID=2 \-e ADDITIONAL_ZOOKEEPER_1=server.1=zookeeper1:2888:3888 \-e ADDITIONAL_ZOOKEEPER_2=server.2=zookeeper2:2888:3888 \-e ADDITIONAL_ZOOKEEPER_3=server.3=zookeeper3:2888:3888 \registry.aliyuncs.com/acs-sample/zookeeper:3.4.8 

登录到在zookeeper3上,再执行下列命令启动集群的第三个节点

docker run -d \--name=zk3 \--net=host \-e SERVER_ID=3 \-e ADDITIONAL_ZOOKEEPER_1=server.1=zookeeper1:2888:3888 \-e ADDITIONAL_ZOOKEEPER_2=server.2=zookeeper2:2888:3888 \-e ADDITIONAL_ZOOKEEPER_3=server.3=zookeeper3:2888:3888 \registry.aliyuncs.com/acs-sample/zookeeper:3.4.8

你可以通过docker logs ...来查看容器日志,ZK集群是否搭建成功

采用host网络方式的优点是,配置简单、网络性能与原生进程一样,对于关注性能和稳定性的生产环境,host方式是一个较好的选择。但是需要登录到每台虚机、物理机上操作太过繁琐

如果利用阿里云容器服务,我们可以利用下面的docker-compose模板,一键在一组ESC实例上创建基于host网络方式的ZK集群。注意:下面模板部署要求集群上至少包括3个ECS实例。

zookeeper1:image: 'registry.aliyuncs.com/acs-sample/zookeeper:3.4.8'net: hostenvironment:- SERVER_ID=1- ADDITIONAL_ZOOKEEPER_1=server.1=${ZOOKEEPER_1}:2888:3888- ADDITIONAL_ZOOKEEPER_2=server.2=${ZOOKEEPER_2}:2888:3888 - ADDITIONAL_ZOOKEEPER_3=server.3=${ZOOKEEPER_3}:2888:3888- constraint:aliyun.node_index==1
zookeeper2:image: 'registry.aliyuncs.com/acs-sample/zookeeper:3.4.8'net: hostenvironment:- SERVER_ID=2- ADDITIONAL_ZOOKEEPER_1=server.1=${ZOOKEEPER_1}:2888:3888- ADDITIONAL_ZOOKEEPER_2=server.2=${ZOOKEEPER_2}:2888:3888 - ADDITIONAL_ZOOKEEPER_3=server.3=${ZOOKEEPER_3}:2888:3888- constraint:aliyun.node_index==2
zookeeper3:image: 'registry.aliyuncs.com/acs-sample/zookeeper:3.4.8'net: hostenvironment:- SERVER_ID=3- ADDITIONAL_ZOOKEEPER_1=server.1=${ZOOKEEPER_1}:2888:3888- ADDITIONAL_ZOOKEEPER_2=server.2=${ZOOKEEPER_2}:2888:3888 - ADDITIONAL_ZOOKEEPER_3=server.3=${ZOOKEEPER_3}:2888:3888- constraint:aliyun.node_index==3

这里利用Docker Compose支持的变量名替换能力,比如 ${...} 可以在运行时让用户输入下列参数

  • ZOOKEEPER_1:第一个ECS实例的IP地址或域名
  • ZOOKEEPER_2:第二个ECS实例的IP地址或域名
  • ZOOKEEPER_3:第三个ECS实例的IP地址或域名

当部署应用时,会提示用户根据集群实际信息输入下面参数

然而容器服务是如何保证特定的zookeeper容器能够确保调度到指定ECS实例上呢?因为阿里云完全支持Docker Swarm的调度约束,比如对于zookeeper1容器,它会调度到满足下面约束的constraint:aliyun.node_index==1ECS实例上。对于容器服务集群中的每一个ECS实例,在加入集群时都会被自动添加一系列的标签(label),比如节点序号、地域(Region)、可用区(Avaliablity Zone)等信息。通过这些约束我们可以控制容器和ECS实例的亲和性,来控制调度过程。 关于Docker Swarm 调度和容器服务Compose扩展的信息可以通过连接获得。

注:阿里云容器服务在Swarm基本调度策略上提供了很多有针对性的扩展和增强,比如:为节点动态编辑label,支持基于可用区的高可用调度,细粒度CPU share约束等等。我们未来将结合场景介绍。

体验Docker容器网络模型(Container Network Model)的自定义网络

在开发环测试境,为了节省资源,我们经常需要将ZK集群部署在一台主机。这时候我们必须采用手工的方法调整每个Docker容器暴露的服务端口来避免端口冲突,然而这样会导致端口管理的复杂性。那么是否可以有其他方式来解决呢?我们是否可以让每个ZK容器有自己独立的IP,它们之间可以互相发现对方呢?

在Docker 1.9以前,Docker的网络模型有很多限制,比如:不支持跨节点的容器网络,服务发现能力较弱等等。而且不同应用对网络的需求不同,不同的底层网络技术也各有特点。所以Docker在2015年中发布了一个可扩展的容器网络管理项目libnetwork,并引入了新的容器网络模型(Container Network Model CNM)。

CNM在Docker 1.9版本中第一次正式发布,并持续增强。从此network成为了Docker的第一类资源。用户可以创建容器网络,并将容器关联到网络之上。在相同network上的任何容器都可以利用容器名称来解析服务的访问地址。这样不但解决了容器之间网络互联的问题,还简化了容器之间的服务端点发现。

我们下面利用CNM的自定义特性来部署ZK集群

下面我们首先创建一个名为“foo”的网络

$ docker network ls
NETWORK ID          NAME                TYPE
c642305f9430        none                null
7cbaa2884a5e        host                host
22078b0862fc        bridge              bridge             $ docker network create foo
9b2d3faa64bdcecc1fdd2eb649e57719a9f1b7e4ac5d93b9c11430703908dd2f$ docker network ls
NETWORK ID          NAME                TYPE
c642305f9430        none                null
7cbaa2884a5e        host                host
22078b0862fc        bridge              bridge
9b2d3faa64bd        foo                 bridge    

然后,我们会创建三个ZK节点容器并分别在“foo”的网络将它们命名为"zk1","zk2",和"zk3"。利用CNM的特性,我们可以使用容器名称来访问网络中其他容器。执行命令如下

docker run -d \--name zk1 \--net foo \-e SERVER_ID=1 \-e ADDITIONAL_ZOOKEEPER_1=server.1=0.0.0.0:2888:3888 \-e ADDITIONAL_ZOOKEEPER_2=server.2=zk2:2888:3888 \-e ADDITIONAL_ZOOKEEPER_3=server.3=zk3:2888:3888 \registry.aliyuncs.com/acs-sample/zookeeper:3.4.8 docker run -d \--name zk2 \--net foo \-e SERVER_ID=2 \-e ADDITIONAL_ZOOKEEPER_1=server.1=zk1:2888:3888 \-e ADDITIONAL_ZOOKEEPER_2=server.2=0.0.0.0:2888:3888 \-e ADDITIONAL_ZOOKEEPER_3=server.3=zk3:2888:3888 \registry.aliyuncs.com/acs-sample/zookeeper:3.4.8docker run -d \--name zk3 \--net foo \-e SERVER_ID=3 \-e ADDITIONAL_ZOOKEEPER_1=server.1=zk1:2888:3888 \-e ADDITIONAL_ZOOKEEPER_2=server.2=zk2:2888:3888 \-e ADDITIONAL_ZOOKEEPER_3=server.3=0.0.0.0:2888:3888 \registry.aliyuncs.com/acs-sample/zookeeper:3.4.8

在这里我们利用容器名作为DNS中容器的域名来配置ZK节点。由于3个ZK容器都被挂载到相同的"foo"网络,他们之间可以通过容器名相互访问。这就优雅地解决了ZK配置和动态容器IP地址之间的循环依赖问题

当然,我们还可以在本地使用下面的docker-compose.yml文件,执行docker-compose up -d来一键部署一个ZK集群。这将大大简化搭建测试环境的工作。

zookeeper1:image: 'registry.aliyuncs.com/acs-sample/zookeeper:3.4.8'container_name: zookeeper1net: fooenvironment:- SERVER_ID=1- ADDITIONAL_ZOOKEEPER_1=server.1=0.0.0.0:2888:3888- ADDITIONAL_ZOOKEEPER_2=server.2=zookeeper2:2888:3888 - ADDITIONAL_ZOOKEEPER_3=server.3=zookeeper3:2888:3888
zookeeper2:image: 'registry.aliyuncs.com/acs-sample/zookeeper:3.4.8'container_name: zookeeper2net: fooenvironment:- SERVER_ID=2- ADDITIONAL_ZOOKEEPER_1=server.1=zookeeper1:2888:3888- ADDITIONAL_ZOOKEEPER_2=server.2=0.0.0.0:2888:3888 - ADDITIONAL_ZOOKEEPER_3=server.3=zookeeper3:2888:3888
zookeeper3:image: 'registry.aliyuncs.com/acs-sample/zookeeper:3.4.8'container_name: zookeeper3net: fooenvironment:- SERVER_ID=3- ADDITIONAL_ZOOKEEPER_1=server.1=zookeeper1:2888:3888- ADDITIONAL_ZOOKEEPER_2=server.2=zookeeper2:2888:3888 - ADDITIONAL_ZOOKEEPER_3=server.3=0.0.0.0:2888:3888

在云端,阿里容器服务为集群缺省创建了一个全局跨节点的“multi-host-network”网络,这样在集群内部容器之间的网络是相互连通的。无需指明网络模式,容器之间就可以直接通过容器名进行访问,相应的docker-compose模板被简化如下

zookeeper1:image: 'registry.aliyuncs.com/acs-sample/zookeeper:3.4.8'container_name: zookeeper1environment:- SERVER_ID=1- ADDITIONAL_ZOOKEEPER_1=server.1=0.0.0.0:2888:3888- ADDITIONAL_ZOOKEEPER_2=server.2=zookeeper2:2888:3888 - ADDITIONAL_ZOOKEEPER_3=server.3=zookeeper3:2888:3888
zookeeper2:image: 'registry.aliyuncs.com/acs-sample/zookeeper:3.4.8'container_name: zookeeper2environment:- SERVER_ID=2- ADDITIONAL_ZOOKEEPER_1=server.1=zookeeper1:2888:3888- ADDITIONAL_ZOOKEEPER_2=server.2=0.0.0.0:2888:3888 - ADDITIONAL_ZOOKEEPER_3=server.3=zookeeper3:2888:3888
zookeeper3:image: 'registry.aliyuncs.com/acs-sample/zookeeper:3.4.8'container_name: zookeeper3environment:- SERVER_ID=3- ADDITIONAL_ZOOKEEPER_1=server.1=zookeeper1:2888:3888- ADDITIONAL_ZOOKEEPER_2=server.2=zookeeper2:2888:3888 - ADDITIONAL_ZOOKEEPER_3=server.3=0.0.0.0:2888:3888

利用容器网络模型提供的容器连接

除了上面的方法,我们还可以在自定义网络中利用容器连接。

自从Docker 1.10版本之后,Docker内嵌了DNS服务,还支持在用户定义的网络上使用容器链接别名来访问引用的容器。 比如我们在容器zk1中,定义了连接 --link zk2:zknode2 那么意味着,在容器中可以利用zknode2作为容器zk2的别名来访问,而Docker容器内部的DNS将其动态解析正确的IP地址。注意与经典的容器链接不同,在自定义网络中容器链接并不需要被连接的容器已经启动,所以我们可以方便地描述双向连接或者P2P方式部署的应用。

docker rm -f zk1
docker rm -f zk2
docker rm -f zk3docker run -d \--name zk1 \--link zk2:zknode2 \--link zk3:zknode3 \--net foo \-e SERVER_ID=1 \-e ADDITIONAL_ZOOKEEPER_1=server.1=0.0.0.0:2888:3888 \-e ADDITIONAL_ZOOKEEPER_2=server.2=zknode2:2888:3888 \-e ADDITIONAL_ZOOKEEPER_3=server.3=zknode3:2888:3888 \registry.aliyuncs.com/acs-sample/zookeeper:3.4.8 docker run -d \--name zk2 \--link zk1:zknode1 \--link zk3:zknode3 \--net foo \-e SERVER_ID=2 \-e ADDITIONAL_ZOOKEEPER_1=server.1=zknode1:2888:3888 \-e ADDITIONAL_ZOOKEEPER_2=server.2=0.0.0.0:2888:3888 \-e ADDITIONAL_ZOOKEEPER_3=server.3=zknode3:2888:3888 \registry.aliyuncs.com/acs-sample/zookeeper:3.4.8docker run -d \--name zk3 \--link zk1:zknode1 \--link zk2:zknode2 \--net foo \-e SERVER_ID=3 \-e ADDITIONAL_ZOOKEEPER_1=server.1=zknode1:2888:3888 \-e ADDITIONAL_ZOOKEEPER_2=server.2=zknode2:2888:3888 \-e ADDITIONAL_ZOOKEEPER_3=server.3=0.0.0.0:2888:3888 \registry.aliyuncs.com/acs-sample/zookeeper:3.4.8

注:在Docker 1.11版本中对容器链接方式进一步增强,支持一对多这种方式的容器链接。并支持DNS轮询来实现简单的负载均衡。

静态分配容器IP

自从Docker 1.10开始,已经允许用户直接指明容器的IP地址。我们也可以利用这种方法来配置ZK集群。

我们首先创建一个网络“bar”,并设置它的子网为“172.19.0.0/16”动态IP分配区域为“172.19.0.0/17”

docker network create --subnet 172.19.0.0/16 --gateway 172.19.0.1 --ip-range 172.19.0.0/17 bar

这样,我们就保留了172.19.128.0/17这个子网可以用于静态IP地址分配了,下面我们可以自己选择几个不冲突的IP地址来配置ZK集群。比如:节点1至3的IP地址为,172.19.200.1,172.19.200.2和172.19.200.3。

docker run -d \--name zk1 \--net bar \--ip 172.19.200.1 \-e SERVER_ID=1 \-e ADDITIONAL_ZOOKEEPER_1=server.1=172.19.200.1:2888:3888 \-e ADDITIONAL_ZOOKEEPER_2=server.2=172.19.200.2:2888:3888 \-e ADDITIONAL_ZOOKEEPER_3=server.3=172.19.200.3:2888:3888 \registry.aliyuncs.com/acs-sample/zookeeper:3.4.8 docker run -d \--name zk2 \--net bar \--ip 172.19.200.2 \-e SERVER_ID=2 \-e ADDITIONAL_ZOOKEEPER_1=server.1=172.19.200.1:2888:3888 \-e ADDITIONAL_ZOOKEEPER_2=server.2=172.19.200.2:2888:3888 \-e ADDITIONAL_ZOOKEEPER_3=server.3=172.19.200.3:2888:3888 \registry.aliyuncs.com/acs-sample/zookeeper:3.4.8docker run -d \--name zk3 \--net bar \--ip 172.19.200.3 \-e SERVER_ID=3 \-e ADDITIONAL_ZOOKEEPER_1=server.1=172.19.200.1:2888:3888 \-e ADDITIONAL_ZOOKEEPER_2=server.2=172.19.200.2:2888:3888 \-e ADDITIONAL_ZOOKEEPER_3=server.3=172.19.200.3:2888:3888 \registry.aliyuncs.com/acs-sample/zookeeper:3.4.8

总结

Docker的容器网络模型的出现大大推动了容器网络技术的发展。善用容器网络模型可以解决不同应用的互联互通问题。

阿里云容器服务完全支持Docker的容器网络模型,并简化和优化了跨节点容器网络的配置。结合Docker Swarm, Docker Compose的能力可以方便地部署各种类型的应用。

本文部分改写自我去年介绍单机环境搭建Zookeeper的文章,补充了很多Docker 1.9版本以来网络模型的进展。也参考了朋友车漾的相关项目。

学习Docker容器网络模型 - 搭建分布式Zookeeper集群相关推荐

  1. 使用Docker Swarm搭建分布式爬虫集群

    转载自  使用Docker Swarm搭建分布式爬虫集群 在爬虫开发过程中,你肯定遇到过需要把爬虫部署在多个服务器上面的情况.此时你是怎么操作的呢?逐一SSH登录每个服务器,使用git拉下代码,然后运 ...

  2. [416]使用Docker Swarm搭建分布式爬虫集群

    在爬虫开发过程中,你肯定遇到过需要把爬虫部署在多个服务器上面的情况.此时你是怎么操作的呢?逐一SSH登录每个服务器,使用git拉下代码,然后运行?代码修改了,于是又要一个服务器一个服务器登录上去依次更 ...

  3. Solr集群搭建,zookeeper集群搭建,Solr分片管理,Solr集群下的DataImport,分词配置。...

    1   什么是SolrCloud SolrCloud(solr 云)是Solr提供的分布式搜索方案,当你需要大规模,容错,分布式索引和检索能力时使用 SolrCloud.当一个系统的索引数据量少的时候 ...

  4. Solr集群搭建,zookeeper集群搭建,Solr分片管理,Solr集群下的DataImport,分词配置。

    1   什么是SolrCloud SolrCloud(solr 云)是Solr提供的分布式搜索方案,当你需要大规模,容错,分布式索引和检索能力时使用 SolrCloud.当一个系统的索引数据量少的时候 ...

  5. Spark基础学习笔记07:搭建Spark HA集群

    文章目录 零.本讲学习目标 一.Spark HA集群概述 二.Spark HA集群架构 三.集群角色分配表 四.Spark HA集群搭建步骤 (一)安装配置ZooKeeper 1.在虚拟机master ...

  6. 淘淘商城23_Linux上的操作_solr集群的搭建、zookeeper集群的搭建

    安装包链接: 百度网盘: 链接:https://pan.baidu.com/s/13m_kjoZiN2cVH_nIGs0a9w  提取码:be17 一.概念理解 1. 什么是SolrCloud Sol ...

  7. Hadoop(六)搭建分布式HBase集群

    接上一章节,在单机版的HBase中,HMaster,HRegionServer,Zookeeper都在一个JVM进程中运行,通过两个阶段来搭建.学习分布式的HBase,伪分布式和分布式. 一,伪分布式 ...

  8. Spark基础学习笔记05:搭建Spark Standalone集群

    文章目录 零.本讲学习目标 一.Spark Standalone架构 (一)client提交方式 (二)cluster提交方式 二.Spark集群拓扑 (一)集群拓扑 (二)集群角色分配 三.搭建三节 ...

  9. Apache Zookeeper 集群环境搭建

    Zookeeper 是 一个分布式.开放源码的分布式应用程序协调服务,是Google Chubby的一个开源实现,大多数的分布式应用都需要Zookeeper的支持,这篇文章先简单的和大家分享如何搭建一 ...

最新文章

  1. 用chrome的snippets片段功能创建页面js外挂程序,从控制台创建js小脚本
  2. SAP WM初阶之TO报表LX12 - TOs Resident Documents(Detailed Overview)
  3. Android饼图Pie Chart
  4. MDK编译后生成bin文件占用FLASH大小说明
  5. autocad完全应用指南_建筑绘图慢?580页的AUTOCAD完全自学必备指南,高效绘图不求人...
  6. K折交叉验证和pipeline
  7. 软件配置管理(六)常用配置软件配置工具指令
  8. JQGrid 参数、属性API
  9. WordPress美化_节日灯笼插件
  10. python 开发工具_一个极具意义的 Python 前端开发工具
  11. 在Node.js中,如何从其他文件中“包含”函数?
  12. tkmybatis开启二级缓存
  13. java多线程访问beans对象_java-多线程同时操作同一个对象之解决方法:读写锁ReadWriteLock的使用...
  14. Shell账户管理,用户和组管理
  15. TF,数据转换和softmax()
  16. 如何写Java单元测试
  17. Android CheckBoxPreference设置默认值会触发持久化以及其内部实现逻辑
  18. lumion拍摄视频基础
  19. java的duplicate用法_Java IntBuffer duplicate()用法及代码示例
  20. Source Insight 4.0.0084 Patched

热门文章

  1. GTK+, Qt, wxWidgets compare
  2. 转:Silverlight样式写法
  3. Linux上Core Dump文件的形成和分析
  4. Linux运维系统工程师系列---25
  5. 说说设计模式~大话目录(Design Pattern)
  6. oracle dataguard
  7. 蛋花花:互联网正在告别青春期
  8. nGrinder中快速编写groovy脚本03-在GET请求中发送参数
  9. springboot~Compiler时开启插件的注解功能
  10. opencv图像特征检测之斑点检测