数人云工程师手记 | Docker1.12服务发现,负载均衡和Routing Mesh

本文源自9月6日数人云线上分享课,分享嘉宾数人云资深架构师春明。

Docker社区目前最热的话题莫过于Docker1.12的发布,大家都在讨论Docker1.12的新功能,各种关于1.12的使用手册,文章,深度教程层出不穷, 本文从Docker SwarmKit的引入讲起,为大家深入讲解了服务发现,负载均衡和Routing Mesh等功能。

个人理解Docker1.12最大的功能莫过于SwarmKit的引入,这应该算是Docker发展里程碑式的一个Release(上一个里程碑应该是runc和containerd的剥离,标志着docker公司自己孤独地玩到容器标准化概念引入),自此再也不要说Docker是主机容器管理了, Docker已经能够管理一群主机上的容器了。概念上Docker引入了node,service,task,甚至更高于service一层的stack, 自此大家会发现容器和网络已经不是Docker的first-class概念, 取而代之的是service, stack和跨主机网络。Docker SwarmKit的引入给容器编排投入了一颗重磅炸弹, 给我的感觉是原来容器编排还可以这么玩,终于不要某某sos和某8s的全家桶了, 在容器编排的远古时代(其实也不远,就是观念陈旧)不装个五六七八个组件都不好意思跟人说是编排, 真的有这个必要么? 当然了, Docker Swarm主要功能还是是借鉴了“远古时代”的几款产品, 吸收了前人的优秀经验。 让我列几个牛哄哄的功能给大家吧

  • 内置Raft, 妈妈再也不用担心我还要依赖Zookeeper,Etcd, Consul了, 光部署这些东西就浪费我一整天外加三台机器。

  • 不区分管理节点和Worker节点,想让谁上就让谁上, 猜想是参考了人家Nomad吧, 关键还能运行时Promote, Demote, 太方便了。

  • Service,Stack外加DAB, 用过都说好, 方便。

  • Rolling Update, Scale一个Service,灰度发布分分钟搞定(虽然有点鸡肋),这个不用说也是借鉴了K8S。

一张图展示一下Docker1.12的和老版本的功能对比。

当然当前Docker1.12的功能还很初级,还没有听说过有人大规模生产级的应用(据说能管理2000个节点,10w个容器),不过看的出来SwarmKit team 新功能推出的速度还是相当快的。 个人很看好这一趋势。

下面分享一下Docker的一个服务是如何被外部访问的。

在没有Docker的时代我们更喜欢的两种办法来部署一个服务 1, 单个机器上的单个进程,绑定到固定IP上的固定端口, 这种方式的优点是简单明了,程序员同学们需要做的事情最大化的优化单个进程的处理能力, 保障这个进程不会因为各种问题退出, 因为一旦退出就没得玩了。 2, 单个机器上的多个进程绑定到固定端口, 前面放一个代理工具。

容器化后大家发现以前的做法或多或少需要做些调整, 第一,由于主机概念被弱化,集群概念被突出, 应用程序不再依赖某个主机或者某个端口; 第二, 容器生命周期不固定, 容器可以随时被启动被停止, 容器的数量可多可少(这就是我们常说的弹性)。由于上面提到的两个容器化的特性,一些新的问题被引入,聪明的工程师们想到了一些解决办法来应对这些问题, 第一个解决办法就是我们要谈到的服务发现,针对的问题是服务对外地址不定和服务随时被启动停止。 服务被发现以后要解决是如何把任务按照一定规则负载到容器中, 这就是第二个我们要谈的负载均衡。 负载均衡解决了单一入口负载到多个容器上问题, 但是由于容器调度之后可能落到多个机器上, 假如某些主机上面没有工作的容器,而对外服务时候又希望服务可以被访问, Routing Mesh概念引入是解决多个入口点负载到单个容器的问题。

服务发现

单纯的服务发现很好实现, 无非就是监测Container的启动,停止, 根据这些变动记录下服务到底可以通过哪些IP+端口可以被访问, 理论上实现一个初级的可用的服务发现不会超过十行代码。成熟的服务发现还需要额外的工作比如健康检查, 注意到我们之前是假设了容器启动就可以对外服务, 实际中情况可能远非如此, 一个应用启动之后可能要准备环境, 加载数据之后才可以对外服务,健康检查是保证服务可用之后再对外服务。 另外一个成熟的服务发现必备条件是高可用, 明眼人可能已经发现之前的简单版本不具备高可用和状态一致性, 这也是为什么Docker引入了Raft协议。

负载均衡

传统理解的负载均衡根据协议不同可以分为7层的和4层的, 7层负载均衡特点是可以支持很复杂的用户规则,比如HTTP header,Cookie等应用逻辑, 而4层只能支持到IP地址负载不同服务。

Docker1.12的服务发现和负载均衡是结合到一起的, 实现办法有两种,DNS轮询和IPVS。 理解这两种策略之前需要知道的一个前提是Docker为每个服务都分配了一个虚拟IP, 这个IP对应的不是具体的一个Task或者一个具体的Container,而是专门为一个服务保留。

下面我们举例子分别说明一下两种负载均衡实现:

首先准备一下环境。

第一步如下图所示用Go写一段小代码, 编译成二进制放在一个Ubuntu基础镜像里, 同时我的Ubuntu里面安装了一些基础的网络工具, 比如dig, curl, ping等, 保存成镜像ubuntu-base。

第二步, 根据Docker官方安装文档 我们在虚拟机环境里启动两个虚拟机, 构造一个Docker Swarm集群。 第三步, 创建一个overlay网络, 取名overlay-test。 下面我们试一下DNS轮训模式的服务发现和负载均衡, 设置一个服务使用DNS轮询还是vip, 只要设置好endpoint-mode这个参数就好, 如下图所示:

使用下面命令启动我们的服务:

docker service create --network overlay-test --name demo --replicas=4 --endpoint-mode=dnsrr ubuntu-base

—network overlay-test是指定使用刚刚创建的overlay网络, --replicas=4 指定task数量, --endpoint-mode=dnsrr使用dnsrr方式做负载均衡。

启动成功后如下图所示 : 当前机器上运行 docker ps会发现有只有两个容器生成, 原因是另外两个task被调度到另外一台主机上。 下一步我们考虑进入容器中看一下虚拟出来的网络情况 。 我们之前明明指定了使用overlay-test网络, 按理应该只有两块网卡, 实际上Docker却为我们生成了4块, 多出来的两块是Docker为容器做的手脚, 让我来一一讲解为什么会有4块网卡的出现。

  1. lo网卡不用多说, 是本地网卡, 也叫回环网卡;
  2. eth2属于之前创建的overlay-test网络;
  3. eth1和docker_gwbridge网桥构成的网络可以使容器内的服务可以在主机上访问, 主机上telnet 172.18.0.4可以访问到我们的服务,如下图
    eth0是ingress网络中的一块网卡, 是为了Routing Mesh而设,稍后我们会详细说明。

继续回到容器里面看我们查看一下服务情况, 执行命令: 大家会发现通过服务名或者容器ID都能找到ip地址, 区别是服务名发现了本机上所有提供服务的容器IP, 至此同时实现了服务发现和内部负载均衡。

以上就是通过DNS轮询实现的服务发现和负载均衡, 这样的负载均衡是有一些缺点的, 比如: 一些应用可能缓存DNS请求, 导致容器变化之后DNS不能实时更新; DNS生效时间也导致不能实时反映服务变化情况;
由于以上原因导致的负载均衡不够准确。

下面我来演示一下VIP和IPVS模式的服务发现和负载均衡。 VIP和IPVS原理上比较容易理解, 就是Docker为每个服务分配了一个VIP, DNS解析服务名称或者自定义的别名到这个VIP上。

由于VIP本身没有容器提供服务,Docker把到VIP的请求通过IPVS技术负载到后面的容器上。

Docker发布一个服务时候默认会选择VIP模式, 下面我们用同样的镜像演示一下。

docker service create --network overlay-test --name demo --replicas=4 --endpoint-mode=vip —publish 8080:8080 ubuntu-base

启动服务之后我们inspect一下, 会发现Virtual IPs有两个入口, 期中10.0.0.2就是overlay-test网络下面的一个Endpoint, 这个Endpoint是服务而非容器的。 我们进入容器中执行dig命令会发现: 此时DNS解析出来的是一个虚拟地址, 虚拟地址通过IPVS和一系列的iptables规则动态转发到容器中。 这样的负载均衡屏蔽了DNS轮询的时效性问题,同时可以支持UDP等协议, 性能也非常好。 缺点是不能做比如回话保持, 基于URL等应用层协议的规则的转发。

下面聊聊Routing Mesh, Routing Mesh的目的是每个主机上都为服务预留端口, 保证每台机器上都可以访问到服务。实现的办法就是Ingress网络, 之前我们提到容器中会多出一块网络,我们Inspect ingress网络,同时会发现网络对应的容器上多出一个容器 ingress-sbox。 一个请求到主机端口8080之后, 数据包的流向如下所示:

主机端口8080 => Ingress-sbox-VIP:8080 => 容器Ingress-sbox => IPVS分发到containers。

大家可以看到访问主机之后数据包流到了一个特殊的Sandbox容器里, 这个容器和我们的容器共享一个Ingress网络,通过Iptables和IPVS等重定向到了最终容器之上。 达到了服务在任何一台主机的8080端口都可达的目的。

以上就是我理解的Docker SwarmKit的几种功能, 一些新的网络技术大家也在讨论,比如Macvlan和Ipvlan等, 通过这些技术Docker的应用场景越来越丰富, 越来越成熟, 让我们拭目以待。