本篇的主要内容是关于docker daemon
启动时网络设置的相关部分,在上一篇中已经简要
提到(Docker daemon 启动流程)
。主要内容集中在InitDriver
函数的解析上。
简介
InitDriver
函数位于github.com/docker/docker/daemon/networkdriver/bridge/driver.go
中。
主要包含参数解析,docker0
的创建,iptables
的设置等。
相关网络参数
前面两篇也已经提到了相关的网络参数,主要是以下几个:
-
iptables
是否启用iptables
-
icc
即Inter-Container Communitaion
,是否允许docker container
之间以及与
host
之间的的通信。
-
ip-masq
是否启用IP masquerading
,用于源地址转换。
-
bridge ip
给docker0
指定 IP
-
CIDR
限制给container
分配的 IP 范围.
-
ip
container
映射port
时默认的绑定地址,默认为0.0.0.0
.
-
bridge
使用已经存在的网桥而不是创建docker0
.
这些参数由命令行参数传入,最终存储在Job
的env
中,InitDriver
即通过env
来获取
相关的设置:
1
2
3
4
5
6
7
8
9
|
var (
network *net.IPNet
enableIPTables = job.GetenvBool("EnableIptables")
icc = job.GetenvBool("InterContainerCommunication")
ipMasq = job.GetenvBool("EnableIpMasq")
ipForward = job.GetenvBool("EnableIpForward")
bridgeIP = job.Getenv("BridgeIP")
fixedCIDR = job.Getenv("FixedCIDR")
)
|
之后便是对ip
参数进行设置:
1
2
3
4
5
6
|
defaultBindingIP = net.ParseIP("0.0.0.0")
if defaultIP := job.Getenv("DefaultBindingIP"); defaultIP != "" {
defaultBindingIP = net.ParseIP(defaultIP)
}
|
然后便是判断是否使用默认的网桥docker0
:
1
2
3
4
5
6
|
bridgeIface = job.Getenv("BridgeIface")
usingDefaultBridge := false
if bridgeIface == "" {
usingDefaultBridge = true
bridgeIface = DefaultNetworkBridge
}
|
网桥设置
确定好将要使用的网桥后,便需要对其 IP 地址进行相关设定。首先看能不能获取到其 IP 地址:
1
|
addr, err := networkdriver.GetIfaceAddr(bridgeIface)
|
docker
服务停止后docker0
网桥并不删掉,所以如果不是第一次使用,都能成功获取
到ip
。我们来看下首次创建docker0
的情况:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
if !usingDefaultBridge {
return job.Error(err)
}
if err := configureBridge(bridgeIP); err != nil {
return job.Error(err)
}
addr, err = networkdriver.GetIfaceAddr(bridgeIface)
if err != nil {
return job.Error(err)
}
network = addr.(*net.IPNet)
|
如果要使用自己指定的网桥而且获取不到 IP,那就只能当成错误返回了。如果是使用
docker0
且获取不到 IP,则就创建并且赋给它 IP,configureBridge
参数为bridgeIp
,
即命令行里的-bip
,一般都没有指定,默认值为空。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
addrs = []string{
"172.17.42.1/16",
"10.0.42.1/16",
"10.1.42.1/16",
"10.42.42.1/16",
"172.16.42.1/24",
"172.16.43.1/24",
"172.16.44.1/24",
"10.0.42.1/24",
"10.0.43.1/24",
"192.168.42.1/24",
"192.168.43.1/24",
"192.168.44.1/24",
}
for _, addr := range addrs {
_, dockerNetwork, err := net.ParseCIDR(addr)
if err != nil {
return err
}
if err := networkdriver.CheckNameserverOverlaps(nameservers, dockerNetwork); err == nil {
if err := networkdriver.CheckRouteOverlaps(dockerNetwork); err == nil {
ifaceAddr = addr
break
} else {
log.Debugf("%s %s", addr, err)
}
}
}
|
每次给docker0
分配 IP,都会从addrs
一个一个地尝试,所以我们一般看到的运行中的
docker0
的 IP 都是172.17.42.1/16
。对addrs
中的 IP,要检测其与nameservers
和路由
表是否有重叠。nameservers
即是从系统的/etc/resolv.conf
中读取到的列表,路由表是
类似于如下图的结果中的第一列:

有了 IP 之后,再次调用GetIfaceAddr
,将获取到的地址赋给addr
,后面的IP Tables
的
设置要用到这个地址。
IPTables 设置
1
2
3
4
5
|
if enableIPTables {
if err := setupIPTables(addr, icc, ipMasq); err != nil {
return job.Error(err)
}
}
|
下面的重点部分就是介绍setupIPTables
函数的流程。因为所涉及到IPTables
部分较多,
单看代码难以理顺,所以将主要结合程序运行时的日志信息来进行介绍。
IP Masquerade
关于IP Masquerade
的介绍,因为个人英语水平有限,怕翻译的不准确,感兴趣可以先看下这个
简单的介绍What is IP Masquerade?。
简单来说就是用来做对包的源地址做地址转换(NAT)
如果ipmasq
参数为真,则插入一条相应的 rule(先检查是否存在,不存在则插入)
1
2
3
4
5
6
7
8
9
10
11
|
natArgs := []string{"POSTROUTING", "-t", "nat", "-s", addr.String(), "!", "-o", bridgeIface, "-j", "MASQUERADE"}
if !iptables.Exists(natArgs...) {
if output, err := iptables.Raw(append([]string{"-I"}, natArgs...)...); err != nil {
return fmt.Errorf("Unable to enable network bridge NAT: %s", err)
} else if len(output) != 0 {
return &iptables.ChainError{Chain: "POSTROUTING", Output: output}
}
}
|
从日志中我们可以看到实际运行的命令是:

这条 rules 的作用是对于 container 向外发出的包做源地址转换操作.
Package Forward
- Inter-Container Communitaion
如果启用 Inter-Container Communitaion (icc
为真),则实际执行的结果如下:

确保container
之间包的转发的target
为ACCEPT
。
1
2
3
4
5
6
7
8
9
|
iptables.Raw(append([]string{"-D"}, dropArgs...)...)
if !iptables.Exists(acceptArgs...) {
log.Debugf("Enable inter-container communication")
if output, err := iptables.Raw(append([]string{"-I"}, acceptArgs...)...); err != nil {
return fmt.Errorf("Unable to allow intercontainer communication: %s", err)
} else if len(output) != 0 {
return fmt.Errorf("Error enabling intercontainer communication: %s", output)
}
}
|
- Non-Intercontainer Outgoing Packets
对于non-intercontainer outgoing packets
,也将其target
设为ACCEPT
:
1
2
3
4
5
6
7
8
|
outgoingArgs := []string{"FORWARD", "-i", bridgeIface, "!", "-o", bridgeIface, "-j", "ACCEPT"}
if !iptables.Exists(outgoingArgs...) {
if output, err := iptables.Raw(append([]string{"-I"}, outgoingArgs...)...); err != nil {
return fmt.Errorf("Unable to allow outgoing packets: %s", err)
} else if len(output) != 0 {
return &iptables.ChainError{Chain: "FORWARD outgoing", Output: output}
}
}
|
实际执行的命令为:
/sbin/iptables --wait -C FORWARD -i docker0 ! -o docker0 -j ACCEPT
iptables
的参数中,-C
代表检查,-D
删除,-I
插入。一般设置都是先检查,没有的话
再插入,所以一般日志中见到的都只有-C
- Incoming Packets for Existing Connecting
Accept incoming packets for existing connections
1
2
3
4
5
6
7
8
9
|
existingArgs := []string{"FORWARD", "-o", bridgeIface, "-m", "conntrack", "--ctstate", "RELATED,ESTABLISHED", "-j", "ACCEPT"}
if !iptables.Exists(existingArgs...) {
if output, err := iptables.Raw(append([]string{"-I"}, existingArgs...)...); err != nil {
return fmt.Errorf("Unable to allow incoming packets: %s", err)
} else if len(output) != 0 {
return &iptables.ChainError{Chain: "FORWARD incoming", Output: output}
}
}
|
实际执行的命令为:
/sbin/iptables --wait -C FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEP
Docker Chain
以上两部分就是setIPTables
的流程,主要是一些基本的网络设置。除此之外,还有一些针
对NAT
表中docker chain
的设置。
先从日志中看看实际执行的命令是什么:

(-F
删除一条chain
中的所有规则,-X
删除一条用户自定义的chain
,-N
创建一条用
户自定义的chain
,-A
在一条chain
后面添加一条规则.)
首先是尝试删除docker chain
中的规则及其本身:
1
2
3
|
if err := iptables.RemoveExistingChain("DOCKER"); err != nil {
return job.Error(err)
}
|
然后添加新的空的docker chain
:
1
2
3
4
5
6
7
|
if enableIPTables {
chain, err := iptables.NewChain("DOCKER", bridgeIface)
if err != nil {
return job.Error(err)
}
portmapper.SetIptablesChain(chain)
}
|
最终的nat
的表的结果如下图所示:

当有container
的端口暴露时,我们就可以看到其中会有新的rules
添加进来:

Handlers
网络设置的最后一部分是一些handler
的注册,具体见下面列表:
Name |
Funciton |
Description |
allocate_interface |
Allocate |
Allocate a network interface |
release_interface |
Release |
Release an interface for a select ip |
allocate_port |
AllocatePort |
Allocate an external port and map it to the interface |
link |
LinkContainers |
|