Docker 网络学习笔记
Docker 网络学习笔记简介Docker在网络方面并没有特别出彩的地方。没有复杂的网络设置,如果需要支持复杂应用的网络需求,光靠Docker的设置参数也是不够的,还需要借助linux本身的一些网络设置工具。本文只总结Docker本身的网络能力,对于更复杂的网络设置,不属于Docker网络详解这个题目的范畴了,文章还是要短小精悍比较好读,当然我也好写。下
Docker 网络学习笔记
简介
Docker在网络方面并没有特别出彩的地方。没有复杂的网络设置,如果需要支持复杂应用的网络需求,光靠Docker的设置参数也是不够的,还需要借助linux本身的一些网络设置工具。
本文只总结Docker本身的网络能力,对于更复杂的网络设置,不属于Docker网络详解这个题目的范畴了,文章还是要短小精悍比较好读,当然我也好写。
下面会按照以下思路来总结下Docker的网络:
1. Docker daemon启动时对网络干了什么?
2. Container默认的启动方法有哪些网络能力?
3. Container如何对外暴露网络服务?
4. Container之间的相互通信。
(注:本文基于Docker v1.0.1 代码分析)
网络初始化
我们先来看看,在不加任何参数的情况下,Docker daemon初始化对网络干了什么。运行 docker -d & 后,会有如下打印(省略了无关联部分):
- [/var/lib/docker|116d5cd4] +job init_networkdriver()
- [/var/lib/docker|116d5cd4.init_networkdriver()] creating new bridge for docker0
- [/var/lib/docker|116d5cd4.init_networkdriver()] getting iface addr
- [/var/lib/docker|116d5cd4] -job init_networkdriver() = OK (0)
看样子只是简单的创建了bridge docker0,分配ip地址。其实内部还是有比较复杂的操作,图示如下:
Init bridge
这里有两个相关的启动参数:
- -b, --bridge="": Attach containers to a pre-existing network bridge; use 'none' to disable container networking
- --bip="": Use this CIDR notation address for the network bridge's IP, not compatible with -b
第一个是指定bridge接口,第二个指定bridge IP,两个参数不能共存。这就是说,指定bridge IP只能针对默认的docker0。第一个参数如果不指定,那么默认是用docker0, 第二个参数不指定,默认用docker内部定好的IP,一般没冲突的话,就会是第一个172.17.42.1 。
这里做了什么?
- 查看指定(或者默认)的bridge口是否存在。
- 如果不存在,则创建默认bridge0口(不指定-b参数)。
逻辑上非常简单,这里就不再深入展开了,细节可以去看代码。插一句嘴,实际创建bridge0口的操作是通过golang的syscall库进行的:
- if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(s), SIOC_BRADDBR, uintptr(unsafe.Pointer(nameBytePtr))); err != 0 {
- return err
- }
Init iptables
这里直接看看启动完后,iptalbes里面有什么变化,就了解Docker干了什么了:
- $ sudo iptables -L --verbose
- Chain INPUT (policy ACCEPT 168 packets, 11814 bytes)
- pkts bytes target prot opt in out source destination
- Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
- pkts bytes target prot opt in out source destination
- 0 0 ACCEPT all -- any docker0 anywhere anywhere ctstate RELATED,ESTABLISHED
- 0 0 ACCEPT all -- docker0 !docker0 anywhere anywhere
- 0 0 ACCEPT all -- docker0 docker0 anywhere anywhere
- Chain OUTPUT (policy ACCEPT 128 packets, 16744 bytes)
- pkts bytes target prot opt in out source destination
- $ sudo iptables -L -t nat --verbose
- Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
- pkts bytes target prot opt in out source destination
- 0 0 DOCKER all -- any any anywhere anywhere ADDRTYPE match dst-type LOCAL
- Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
- pkts bytes target prot opt in out source destination
- Chain OUTPUT (policy ACCEPT 11 packets, 1014 bytes)
- pkts bytes target prot opt in out source destination
- 0 0 DOCKER all -- any any anywhere !127.0.0.0/8 ADDRTYPE match dst-type LOCAL
- Chain POSTROUTING (policy ACCEPT 11 packets, 1014 bytes)
- pkts bytes target prot opt in out source destination
- 0 0 MASQUERADE all -- any any 172.17.0.0/16 !172.17.0.0/16
- Chain DOCKER (2 references)
- pkts bytes target prot opt in out source destination
先来看filter表,docker启动后在FORWARD chain里面增加了三条规则。
1. 第一条规则表示从其他接口发往docker0(就是发往container)的报文,只有RELATED和ESTABLISHED状态能通过。(比如ftp的关联链接,container主动建链的TCP链接。外部是不能主动跟container内部的服务建链的。)
2. 第二条和第三条表示从docker0发往其他端口的报文都能通过。
再来看看nat表,这里比较重要。
1. PREROUTING chain中和OUTPUT chain增加了都一条规则,表示在收发两端,查找route之前,如果报文地址类型是local,就会送到DOCKER chain处理。初始化时DOCKER chain是空的,后面再详细介绍这两条规则的作用。(在route之前怎么知道目的地址是local?这里需要再研究下。)
2. 在POSTROUTING chain中加了一条规则: 表示从container往host外发(不是container之间)的报文,都会将报文原地址改为外发接口地址发出去。这样在container访问host外部网络时,外部看到的其实只是host地址,看不到container的私有地址。
Init IP forward
这个比较简单,就是将proc设置打开:/proc/sys/net/ipv4/ip_forward 。打开了之后,报文就能够在host上的各个接口之间转发。
Register function
Docker内部的各种功能是以Job形式存在,这里注册的就是network相关的几个Job,在后面container的启动中会用到这些Job。
- "allocate_interface": Allocate,
- "release_interface": Release,
- "allocate_port": AllocatePort,
- "link": LinkContainers,
到这里,Docker的网络初始化就介绍完了。
Container默认网络
默认网络,指的是在启动container时不加其他网络相关的参数,比如直接用 $docker run -i -t image command 运行启动的container。这个container默认将使用bridge模式的网络,链接到docker0。
内部流程主要分为两个部分,docker engine部分和execdriver部分,即配置部分和实际执行部分。
这里先介绍bridge模式,其他模式会在后面的参数介绍中简单介绍。
Bridge 网络模式初始化
主要分为两部分:
1. allocate_interface
前面docker daemon初始化最后,register function操作中注册了 “allocate_interface” Job,这里主要就是调用这个方法。
这个方法比较简单,主要就是分配IP(在bridge0的网络地址范围中递增分配,不会重用已释放的IP。)、配置掩码、网关等,保存到container.NetworkSettings结构。这里还没实际的创建接口。
逻辑简单,更细节的东西可以去看看代码。
2. create_interface
这里是真正的创建网络接口,所以与所使用的execdriver强相关(lxc或libcontainer)。 这里先介绍lxc的实现,因为简单。
Lxc的网络怎么配?可以用man查看帮助:$man lxc.conf , 里面专门有一节讲解怎么配container的网络。
- NETWORK
- The network section defines how the network is virtualized in the container. The network virtualization acts
- at layer two. In order to use the network virtualization, parameters must be specified to define the network
- interfaces of the container. Several virtual interfaces can be assigned and used in a container even if the
- system has only one physical network interface.
- lxc.network.type
- specify what kind of network virtualization to be used for the container. Each time a lxc.network.type
- field is found a new round of network configuration begins. In this way, several network virtualization
- types can be specified for the same container, as well as assigning several network interfaces for one
- container. The different virtualization types can be:
- empty: will create only the loopback interface.
Docker在这里也是用lxc的配置,不过配置文件是动态生成的。可以参看 lxc_template.go里面的配置文件:
- const LxcTemplate = `
- {{if .Network.Interface}}
- # network configuration
- lxc.network.type = veth
- lxc.network.link = {{.Network.Interface.Bridge}}
- lxc.network.name = eth0
- lxc.network.mtu = {{.Network.Mtu}}
- {{else if .Network.HostNetworking}}
- lxc.network.type = none
- {{else}}
- # network is disabled (-n=false)
- lxc.network.type = empty
- lxc.network.flags = up
- lxc.network.mtu = {{.Network.Mtu}}
- {{end}}
有了配置文件,通过lxc-start就能启动一个带网络的container了,container里面的接口链接到bridge0。
注:这里涉及到network namespace和bridge的知识,这里不扩展介绍了,不然写不完了。
Container端口映射
如果host外部客户想访问container里面的服务,怎么办? 从前面介绍的docker daemon网络初始化看到,docker添加了一条iptables规则,外部发往container的报文,只有在状态是RELATED和ESTABLISHED链接中,才能通过。那么外部客户主动向container服务发起的建链,是会被拒绝的。
Docker官网推荐的方式是使用端口映射,对外暴露的是host IP:port,由host做NAT转换为container内部的IP和端口。具体是怎么实现的呢?我们来看一下。
如果我们这样创建一个container: $docker run -i -t -p 6000 image command ,然后看看iptables发生了什么变化(省略了一些没影响的Chain):
- <span style="color:#444444;">$ sudo iptables -L --verbose
- Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
- pkts bytes target prot opt in out source destination
- </span><span style="color:#ff0000;">0 0 ACCEPT tcp -- !docker0 docker0 anywhere 172.17.0.2 tcp dpt:x11</span><span style="color:#444444;">
- 0 0 ACCEPT all -- any docker0 anywhere anywhere ctstate RELATED,ESTABLISHED
- 0 0 ACCEPT all -- docker0 !docker0 anywhere anywhere
- 0 0 ACCEPT all -- docker0 docker0 anywhere anywhere
- $ sudo iptables -L -t nat --verbose
- Chain POSTROUTING (policy ACCEPT 18 packets, 1486 bytes)
- pkts bytes target prot opt in out source destination
- 0 0 MASQUERADE all -- any any 172.17.0.0/16 !172.17.0.0/16
- Chain DOCKER (2 references)
- pkts bytes target prot opt in out source destination
- </span><span style="color:#ff0000;">0 0 DNAT tcp -- !docker0 any anywhere anywhere tcp dpt:49153 to:172.17.0.2:6000</span>
这里就很明显了,里面主要添加了两条规则:
1. 在filter表FORWARD链中添加一条规则,让从host外部过来的报文能够进入container(这里172.17.0.2是新建的container的IP,x11其实就是端口6000,这是个知名端口)。
2. 在nat表DOCKER链中添加一条规则,host外部过来的访问49153端口的报文,会做DNAT将目的IP和端口转换为172.17.0.2:6000。
有了这两条规则,外部进来的报文就能正常访问container内部6000端口的服务了。
Container之间通信
同一个host上的container,默认是可以相互通信的,原理就是linux的bridge原理。这里要介绍的其实是禁止container之间相互通信。
- --icc=true Enable inter-container communication
上面这个参数是启动docker daemon时可选的参数,默认是true,如果设置为false,那么这个host上的container就不能相互通信了,这其实也是通过iptables来实现的。
- <span style="color:#444444;">$ sudo iptables -L --verbose
- Chain INPUT (policy ACCEPT 94 packets, 6424 bytes)
- pkts bytes target prot opt in out source destination
- Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
- pkts bytes target prot opt in out source destination
- </span><span style="color:#ff0000;"> 0 0 DROP all -- docker0 docker0 anywhere anywhere</span><span style="color:#444444;">
- 0 0 ACCEPT all -- any docker0 anywhere anywhere ctstate RELATED,ESTABLISHED
- 0 0 ACCEPT all -- docker0 !docker0 anywhere anywhere
- Chain OUTPUT (policy ACCEPT 62 packets, 7000 bytes)
- pkts bytes target prot opt in out source destination</span>
这里在FORWARD chain里面增加了一条规则,如果报文是在docker0这个bridge内部通信,那么就丢弃。这样就禁止了container之间的直接通信。
小结
以上总结了Docker默认网络模式与常用网络配置。但其实从Docker选项和代码中可以看到,还有些网络相关的地方没有介绍到,要么是我没有用过就没分析、要么是我还没看懂的,比如docker run时可以用 -net 参数指定的host网络模式、container网络模式(这两个初步看,一个是直接用host的namespace,一个是用指定container的namespace,似乎很简单),还有内部的proxy(似乎是用于container通过host映射的端口访问另一个container的服务,这个还没怎么看懂。)。 后面分析之后会更新
更多推荐
所有评论(0)