转载

如何让Docker容器从外部DHCP服务器获得地址并注册DNS域名

我在家里的电脑上起了几个Docker容器用来运行web服务。服务之一当然是Nginx,此外还有blog、wiki等web应用,甚至还跑了个MLDonkey。我本来的规划是让Nginx单独跑在一个容器中,其他的web应用都跑在各自专属的容器中,这样有利于增强安全性,维护也方便些。很遗憾,有许多困难。要解释这一点,得先解释一下Docker的网络是如何管理的。

Docker默认会创建三个网络,分别叫bridge, host 和none. 本文需要关注的是bridge网络,顾名思义,是一个桥接网络。bridge是docker默认创建的,关联到网桥设备docker0.当然可以通过docker的配置文件中指定网桥,而不是使用默认的名称。这个默认的网桥使用172.17.0.0/16网段,网关是172.17.42.1。

当docker启动一个容器时,首先会创建一对虚拟以太网设备(VETH, Virtual Ethernet Device).这个veth设备总是成对创建的,把它们想象成是用一根网线连接起来的两个网口就好了。创建veth需要指定两个口的名字,docker会随机创建它们。然后docker把其中一个端口关联到docker0网桥上,相当于下面的命令:

brctl  addif docker0 veth-xxxA

想象成是把网线插入到交换机的一个网口中就行了。

那么另一端veth-xxxB呢?不要发挥想象力,认为要插入一台主机。在一定程度上,docker容器可以被看作是虚拟机,只是这种虚拟是通过namespace的隔离来实现的。所以本质上容器内的进程和宿主机上的进程没什么不同,只是各自关联的namespace不同而已。当然,namespace上施加的限制也不同。关于namespace,可以参考 namespaces - overview of Linux PID namespaces 。为了把veth-xxxB个容器关联起来,docker会把veth-xxxB移到容器所在的network namespace中,并改名成eth0.于是,在容器中就能看到eth0设备了,但实际上和宿主机的eth0并没有什么关系。接下来,docker会给veth-xxxB分配ip地址--当然还包括路由设置,ip是从配置好的地址池中选择的。更详细的关于地址分配的工作流程,我还没有在文档中看到。

一般来说,分属不同网络空间的设备是不能直接通信的,但veth设备设备对是个例外,虽然分属不同的网络空间了,但是Linux内核允许两者通信。这样,从容器中经eth0发出的数据包会被传递到veth-xxxA, veth-xxxA再传递给docker0.但是docker0和物理网卡eth0之间并没有连接起来,因此,以太数据包到此就结束了,没法传到物理网络上去的。如果我们希望docker0是一个和外部隔绝的网络,这恰好就是想要的。可是,如果要和外部网络通信怎么办?这时候就需要路由器的帮助了。docker0和eth0此时都是在宿主机的名字空间可见的,通过iptables设置nat实现docker0到eth0的通信。

好了,我遇到的问题来了。Nginx提供外网服务,是要能够从外面访问容器的80端口的,可是容器在Nat后面啊,那就只能在Nat上打洞,开端口映射了。Docker允许开端口映射,80和443嘛,又不麻烦。blog和wiki,是Nginx去访问,因此通信只局限在docker0之内,并不需要在nat上做端口映射。

第一个问题来了。我在Nginx中如何设置upstream到后端的blog和wiki呢?每个容器的ip是动态分配的,我在写Nginx配置的时候并不能确定。常规的Nginx配置并没有这个问题,因为后端要么有域名,要么有静态ip。静态ip不太讨人喜欢,域名最好。但是docker并不自带dhcp和域名服务啊,怎么办?理想的方案是用传统的dhcp和域名系统进行管理。

第二个问题,MLDonkey要开许多监听,于是我不得不做很多的端口映射。这只是一个容器。要是我多开几个呢?光端口映射就得烦死我。另外一个,如果我想尝试起两个Nginx的容器,那我是没办法把一个80端口同时映射到两个容器的。况且,我运行Docker的机器本来就是在内部网络,为什么还需要做一次Nat才能到内网呢?它们应该完全可以工作得就像实际存在的若干台物理主机啊,这只需要把网桥docker0和eth0关联到一起就足以解决了。所以,理想的方案是容器直接从物理网络系统获取ip地址,通过二层交换和外网通信,而不是走三层的Nat。这里所谓的“物理”网络,并不一定要是真实的网线啦,交换机啦,路由啦什么的,也可以是虚拟的,是独立于docker和docker所在宿主机之外的。毕竟,很多docker的宿主机其实也只是个虚拟机而已。

我不太认同Docker以及一些容器管理软件做的网络管理功能,为什么不利用已有的网络管理技术呢?还是说你发现了它们都有某种缺陷?还是你做得比它们好?现状是Docker做的不好,许多管理软件做得也不好,太复杂不说还有种种局限。对于网络,Docker只要做好容器的必要支持,使得传统的网络技术得以延伸到容器上,就好了。如果容器有什么网络需求是传统网络管理处理不了的,Docker你去为这些传统网络提供管理工具就好。

对于上面两个问题,我很长时间以来都没什么进展。最近新装了台机器,决定死磕,终于找到了解决方案。

首先,docker0是Docker管理的,我不可能真的把eth0绑定到它上面去。我需要大致考虑一下网络拓扑,具体如下:

如何让Docker容器从外部DHCP服务器获得地址并注册DNS域名

中间这个网桥不使用docker0似乎更合理,只是我后来发现使用docker0也并没有什么问题,简单起见,就这样。注意,红色的eth0设备是连接到网桥br0上。用下面的命令来创建br0, 注意,过程中网络会中断,别远程操作。

# brctl addbr br0
# brctl addbr docker0 
# ip link add veth-br0 type veth peer name veth-docker0
# brctl addif br0 veth-br0
# brctl addif br0 eth0
# brctl addif docker0 veth-docker0
# ip link set br0 up
# ip link set eth0 up
# dhclient -v br0
# ip link set veth-br0 up
# ip link set veth-docker0 up

也可以修改/etc/network/interfaces:

auto lo br0

    iface veth-br0 inet manual
        up ip link add veth-br0 type veth peer name veth-docker0
        down ip link delete veth-br0

    iface br0 inet dhcp
        pre-up ifup eth0
        post-down idown eth0
        bridge_ports eth0 veth-br0
        bridge_stp off

    iface docker0 inet auto
        pre-up ifup veth-br0
        post-down ifdown veth-br0
        bridge_ports veth-docker0

注意,上面的配置并没有给docker0分配地址,但是docker并不会搭理你,在启动后任然会给docker0分配一个ip。这个时候,假设你起了个容器,注意,hostname设为test,且不要和host机器的相同:

docker run -ti --rm --hostname test --name test debian:latest /bin/bash

在容器里查看地址:

ip addr show eth0

你会看到docker还是给这个容器分配了一个172开头的IP地址,没有走dhcp啊!别急,先切回host,在root下运行下面的命令:

# hostname
    # pid=$(docker inspect -f '{{.State.Pid}}' test)
    # nsenter -t $pid -u -n
    # hostname

对比前后两个hostname的输出,第二个输出应该是test。nsenter会启动一个shell,并且关联了test容器的uts和network名字空间。下面的命令都是在nsenter的shell里运行的,先检查一下:

# ip addr show             # 此时只会列出lo和eth0
    # ip route list                # 默认网关时指向172开头的docker0的ip

好,开始dhcp请求ip地址:

# ip addr flush dev eth0     # 删除docker分配的IP地址.
    # dhclient -v eth0

如果前面的配置没有错误的话,这时就能看到dhcp成功分配的ip地址了。在宿主机上运行nslookup test就可以验证是否成功注册了DNS域名。对于Debian系统,可以检查一下/etc/dhcp/dhclient.conf,是否写了 send host-name = gethostname(); 。脚本的 github地址 。

遗留问题:

dhclient被调用后会一直在后台运行,即使容器退出了,dhclient进程也不会结束。这对有系统洁癖的来说可能有点难受。

好了,说说的我的感受。

Docker至今还是不能通过命令行修改容器配置。网上有些人说可以直接改配置文件的,太naive了。docker服务在停止时,会保存所有容器配置。也就是说,如果你不先停下docker,是没发更新配置的。而要停下docker,那所有的容器都会终止运行。这个对于经常要在docker里面搞开发,装这个装那个的人来说,太麻烦了。如果配置不许在线改,那也最多是对应容器不能在线也就够了啊。再说了,docker network咋就允许在线修改了呢?

我的工作和容器其实没什么关系,就是自己折腾,因此k8s啊,swarm啊我都没摸过。但是我实在不觉得一个基本网络管理这样的小事非要劳驾一些重量级软件,而且这些软件的解决手法也是让人看得皱眉,除了pipework大概算是个例外。我对Docker下一个期待的特性是热迁移,工具要足够简单。

参考:

  1. https://github.com/jpetazzo/pipework

  2. https://docs.docker.com/v1.5/articles/networking/#bridge-building

原文  http://icerote.net/blog/post/128
正文到此结束
Loading...