Docker Embedded DNS

因为工作需要,去学习了下Docker的embedded DNS. 这个功能似乎是1.10才加进来的,用来对Docker自带的overlay网络提供DNS服务(容器)发现。我学习的是Docker1.10.3版本,对应的libnetwork是release/v0.7。这个版本的Embedded DNS仅支持IPv4,后续的1.11版本还会支持IPv6.

Container Network Model(CNM)

Docker的容器网络模型如下图所示:


Docker Embedded DNS_第1张图片
CNM from github.com/docker/libnetwork

其中

  • 图中少了一个关键组件:NetworkController,他是一个Docker Daemon唯一的,用来管理Docker的所有网络环境,跟Docker Daemon打交到;
  • 每个Sandbox对应一个Container;
  • 每个Network对应了一个网络环境(logical connectivity zone),他背后对应一个network driver,一些关键过程是调用其api;
  • 每个Endpoint对应一个Sandbox到Network的逻辑链接(logical connection).

How it works

什么情况下会使用embedded DNS

方法Daemon.connectToNetwork()是被调用来将一个container连接到某个网络的。

func (daemon *Daemon) connectToNetwork(container *container.Container, idOrName string, endpointConfig *networktypes.EndpointSettings, updateSettings bool) (err error) {
    if endpointConfig == nil {
        endpointConfig = &networktypes.EndpointSettings{}
    }
    n, err := daemon.updateNetworkConfig(container, idOrName, endpointConfig, updateSettings)
    if err != nil {
        return err
    }
    if n == nil {
        return nil
    }

    controller := daemon.netController

    sb := daemon.getNetworkSandbox(container)
    createOptions, err := container.BuildCreateEndpointOptions(n, endpointConfig, sb)  // <--------
    if err != nil {
        return err
    }

    endpointName := strings.TrimPrefix(container.Name, "/")
    ep, err := n.CreateEndpoint(endpointName, createOptions...)
    if err != nil {
        return err
    }
  ......

其中BuildCreateEndpointOptions方法返回了创建一个endpoint所需要的option。而此方法中的如下代码直接指定了是否要使用embedded DNS。

  if !containertypes.NetworkMode(n.Name()).IsUserDefined() {  // using embedded DNS if not user defined network mode
    createOptions = append(createOptions, libnetwork.CreateOptionDisableResolution())
  }

在package github.com/docker/engine-api/types/container中定义的方法IsUserDefined()会判断网络名不为default, bridge, host, none, container时,则认为是用户定义网络,进而enable了embedded DNS。

// IsDefault indicates whether container uses the default network stack.
func (n NetworkMode) IsDefault() bool {
    return n == "default"
}

// NetworkName returns the name of the network stack.
func (n NetworkMode) NetworkName() string {
    if n.IsBridge() {
        return "bridge"
    } else if n.IsHost() {
        return "host"
    } else if n.IsContainer() {
        return "container"
    } else if n.IsNone() {
        return "none"
    } else if n.IsDefault() {
        return "default"
    } else if n.IsUserDefined() {
        return n.UserDefined()
    }
    return ""
}

......

// IsUserDefined indicates user-created network
func (n NetworkMode) IsUserDefined() bool {
    return !n.IsDefault() && !n.IsBridge() && !n.IsHost() && !n.IsNone() && !n.IsContainer()
}

而整个embedded DNS的生命周期,其实可以从两个角度来看

从Container的角度

Docker Embedded DNS_第2张图片
Docker Network Embedded DNS
  1. Sandbox每次通过一个Endpoint去Join一个Network都会去发布一把这个ep,sb.populateNetworkResources(ep). 其中回去判断一把这个Network是否需要resolver,即Embedded DNS. 如果是用户定义的Network则会enable embedder DNS功能sb.startResolver()。anyway,Sandbox都会将这个ep的信息保存到NetworkController的一个Map解构中networkController.svcDB。他保存了每个Network中的每个ep的name、alias和分配的IP的对应关系。是跨host的name resolution的根据。(libnetwork/sandbox.go

  2. sb.startResolver( )方法中会做embedded DNS的初始化和启动工作:

  1. sb.rebuildDNS()首先会记录初始时容器内部的/etc/resolv.conf文件的内容,然后更新/etc/resolv.conf文件,将servername指向127.0.0.11。(127.0.0.0/8网段都是loopback地址

    resolv.conf in container

  2. sb.resolver.SetExtServers(sb.extDNS)将之前读取的容器初始/etc/resolv.conf文件的nameserver作为embedded DNS的recursive DNS,当embedded DNS不能resolve name的时候就delegate到extDNS。注意这也是为什么--nameserver参数还能工作的原因,虽然容器内的/etc/resolv.conf文件显示的nameserver还是127.0.0.11

  3. sb.osSbox.InvokeFunc(sb.resolver.SetupFunc())在Container环境中申请tcp和udp两个随机端口,姑且命其为tcp_port和udp_port。

  4. sb.resolver.Start()启动embedded DNS:

    • r.setupIPTable()添加iptables规则将127.0.0.11:53的name resolution query通过DNAT转发到127.0.0.11:tcp_port127.0.0.11:udp_port(DNS query默认是udp,效率高)。将出去的query通过SNAT转换回127.0.0.11:53
    • 调用github.com/miekg/dns包的api启动两个DNS server分别监听tcp_port和udp_port。
      Docker Embedded DNS_第3张图片
      iptables and port info
  1. 当DNS query来的时候,resolver会去handle:r.ServeDNS( )。resolver会在方法r.handleIPQuery( )里去resolve name。他会delegate给Sandboxsb.ResolveName( ),然后是去NetworkController里的SvcDB里查找:。没找到就delegate到ExtDNS。

从Docker Daemon的角度

Docker Embedded DNS_第4张图片
Docker Network Embedded DNS Process (zoom-in-able)

其中紫色部分为上一节关于embedded DNS的启动过程。(图片可通过浏览器放大。。。
这部分是整个环境的搭建流程:

  1. 在Daemo被创建的时候NewDaemon( )会初始化网络环境d.initNetworkController( ),其中会创建NetworkControllerlibnetwork.New( )。此时,会在NetworkController里开启一个loopc.startWatch( ),一直监听watchunwatch两个channel。这两个channel分别接受被创建的Endpoint实例被删除的Endpoint实例然后分别调用c.processEndpointCreate( )c.processEndpointDelete( )分别去维护NetworkController里的SvcDB里的ep name/alias和IP的对应关系。

  2. 当调用n.CreateEndpoint( )ep.Join( )时,会往NetworkController的watchchannel里放入当前的ep,从而完成一个DNS record的注册

  3. 当调用ep.Delete( )时,会往NetworkController的unwatchchannel里放入当前ep,以完成这个DNS record的注销。

整个embedded DNS的环境就是这个样子。

你可能感兴趣的:(Docker Embedded DNS)