Docker容器网络栈管理全解析
1. Docker容器网络基础
Docker容器之间可以相互ping通,也能访问外部网络。为实现外部访问,容器的端口会映射到主机端口。容器使用网络命名空间,创建第一个容器时,会为其创建新的网络命名空间。
在容器和Linux桥接器之间会创建虚拟以太网(vEthernet或vEth)链接。容器eth0端口发出的流量通过vEth接口到达桥接器,随后进行交换。可以使用以下命令查看Linux桥接器:
$ sudo brctl show
输出示例如下:
| 桥接器名称 | 桥接器ID | STP是否启用 | 接口 |
| — | — | — | — |
| docker0 | 8000.56847afe9799 | no | veth44cb727 veth98c3700 |
为实现容器与外部网络的连接,主机上的iptables NAT表用于伪装所有外部连接,命令如下:
$ sudo iptables -t nat -L -n
输出片段示例:
Chain POSTROUTING (policy ACCEPT) target prot opt
source destination MASQUERADE all -- 172.17.0.0/16
!172.17.0.0/16
从外部访问容器同样使用主机上的iptables NAT选项进行端口映射。
2. Docker桥接器与容器连接
默认情况下,Docker服务器会在Linux内核中创建docker0桥接器,它能在其他物理或虚拟网络接口之间来回传递数据包,使其表现得像一个单一的以太网网络。可以使用
ifconfig
命令查看:
root@ubuntu:~# ifconfig
docker0 Link encap:Ethernet HWaddr 56:84:7a:fe:97:99
inet addr:172.17.42.1 Bcast:0.0.0.0 Mask:255.255.0.0
inet6 addr: fe80::5484:7aff:fefe:9799/64 Scope:Link
inet6 addr: fe80::1/64 Scope:Link
...
collisions:0 txqueuelen:0
RX bytes:516868 (516.8 KB) TX bytes:46460483 (46.4 MB)
eth0 Link encap:Ethernet HWaddr 00:0c:29:0d:f4:2c
inet addr:192.168.186.129 Bcast:192.168.186.255
Mask:255.255.255.0
当有一个或多个容器运行时,可以在主机上运行
brctl
命令,查看输出的接口列,以确认Docker是否已将容器正确连接到docker0桥接器。首先使用以下命令安装桥接工具:
$ apt-get install bridge-utils
示例主机连接两个不同容器的输出:
root@ubuntu:~# brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.56847afe9799 no veth21b2e16
veth7092a45
Docker在创建容器时使用docker0桥接器设置,并为新容器从桥接器可用的IP地址范围中分配一个新的IP地址。例如:
root@ubuntu:~# docker run -t -i --name container1 ubuntu:latest /bin/bash
root@e54e9312dc04:/# ifconfig
eth0 Link encap:Ethernet HWaddr 02:42:ac:11:00:07
inet addr:172.17.0.7 Bcast:0.0.0.0 Mask:255.255.0.0
inet6 addr: 2001:db8:1::242:ac11:7/64 Scope:Global
inet6 addr: fe80::42:acff:fe11:7/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
...
root@e54e9312dc04:/# ip route
default via 172.17.42.1 dev eth0
172.17.0.0/16 dev eth0 proto kernel scope link src 172.17.0.7
默认情况下,Docker提供的vnet docker0的IP地址是172.17.42.1,Docker容器的IP地址范围是172.17.0.0/16。若要更改Docker的默认设置,可修改
/etc/default/docker
文件。例如,将默认桥接器从docker0更改为br0的步骤如下:
# sudo service docker stop
# sudo ip link set dev docker0 down
# sudo brctl delbr docker0
# sudo iptables -t nat -F POSTROUTING
# echo 'DOCKER_OPTS="-b=br0"' >> /etc/default/docker
# sudo brctl addbr br0
# sudo ip addr add 192.168.10.1/24 dev br0
# sudo ip link set dev br0 up
# sudo service docker start
使用
ifconfig
命令可查看新的桥接器名称和Docker服务的IP地址范围:
root@ubuntu:~# ifconfig
br0 Link encap:Ethernet HWaddr ae:b2:dc:ed:e6:af
inet addr:192.168.10.1 Bcast:0.0.0.0 Mask:255.255.255.0
inet6 addr: fe80::acb2:dcff:feed:e6af/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:7 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:0 (0.0 B) TX bytes:738 (738.0 B)
eth0 Link encap:Ethernet HWaddr 00:0c:29:0d:f4:2c
inet addr:192.168.186.129 Bcast:192.168.186.255
Mask:255.255.255.0
inet6 addr: fe80::20c:29ff:fe0d:f42c/64 Scope:Link
...
3. DNS配置
Docker无需构建自定义镜像,就能为每个容器提供主机名和DNS配置。它会用虚拟文件覆盖容器内的
/etc
文件,以便写入新信息。可以在容器内运行
mount
命令查看。容器创建时,会接收与主机相同的
/resolv.conf
文件。若主机的
/resolv.conf
文件被修改,只有在容器重启后,更改才会反映在容器的
/resolv.conf
文件中。
在Docker中,有两种设置DNS选项的方法:
- 使用
docker run --dns=<ip-address>
- 在Docker守护进程文件中添加
DOCKER_OPTS="--dns ip-address"
还可以使用
--dns-search=<DOMAIN>
指定搜索域。例如,添加DNS服务器的命令:
# docker run --dns=8.8.8.8 --net="bridge" -t -i ubuntu:latest /bin/bash
添加主机名的命令:
#docker run --dns=8.8.8.8 --hostname=docker-vm1 -t -i ubuntu:latest /bin/bash
4. 容器与外部网络通信故障排除
只有当
ip_forward
参数设置为1时,数据包才能在容器之间传递。通常,只需将Docker服务器保持默认设置
--ip-forward=true
,服务器启动时,Docker会将
ip_forward
设置为1。可以使用以下命令检查设置:
# cat /proc/sys/net/ipv4/ip_forward
0
# echo 1 > /proc/sys/net/ipv4/ip_forward
# cat /proc/sys/net/ipv4/ip_forward
1
启用
ip-forward
可使容器与外部世界进行通信,在多桥接器设置中,容器间通信也需要此设置。
Docker不会删除或修改Docker过滤链中已有的规则,这允许用户创建规则来限制对容器的访问。Docker使用docker0桥接器在单个主机上的所有容器之间进行数据包流动,并在iptables的FORWARD链中添加一条规则(空白接受策略),使数据包在两个容器之间流动。
--icc=false
选项会丢弃所有数据包。
当Docker守护进程配置为
--icc=false
和
--iptables=true
,并且使用
--link=
选项运行
docker run
时,Docker服务器会为新容器插入一对iptables ACCEPT规则,使其能够连接到其他容器在Dockerfile的EXPOSE行中指定的端口。
默认情况下,Docker的转发规则允许所有外部IP访问。若要只允许特定IP或网络访问容器,可在Docker过滤链顶部插入否定规则。例如,只允许源IP 10.10.10.10访问容器的命令:
#iptables -I DOCKER -i ext_if ! -s 10.10.10.10 -j DROP
5. 限制容器间的SSH访问
限制容器间的SSH访问可按以下步骤操作:
1. 创建两个容器c1和c2:
# docker run -i -t --name c1 ubuntu:latest /bin/bash
root@7bc2b6cb1025:/# ifconfig
eth0 Link encap:Ethernet HWaddr 02:42:ac:11:00:05
inet addr:172.17.0.5 Bcast:0.0.0.0 Mask:255.255.0.0
inet6 addr: 2001:db8:1::242:ac11:5/64 Scope:Global
inet6 addr: fe80::42:acff:fe11:5/64 Scope:Link
...
# docker run -i -t --name c2 ubuntu:latest /bin/bash
root@e58a9bf7120b:/# ifconfig
eth0 Link encap:Ethernet HWaddr 02:42:ac:11:00:06
inet addr:172.17.0.6 Bcast:0.0.0.0 Mask:255.255.0.0
inet6 addr: 2001:db8:1::242:ac11:6/64 Scope:Global
inet6 addr: fe80::42:acff:fe11:6/64 Scope:Link
- 使用发现的IP地址测试容器间的连通性,例如在c1中ping c2:
root@7bc2b6cb1025:/# ping 172.17.0.6
PING 172.17.0.6 (172.17.0.6) 56(84) bytes of data.
64 bytes from 172.17.0.6: icmp_seq=1 ttl=64 time=0.139 ms
64 bytes from 172.17.0.6: icmp_seq=2 ttl=64 time=0.110 ms
^C
--- 172.17.0.6 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 999ms
rtt min/avg/max/mdev = 0.110/0.124/0.139/0.018 ms
root@7bc2b6cb1025:/#
root@e58a9bf7120b:/# ping 172.17.0.5
PING 172.17.0.5 (172.17.0.5) 56(84) bytes of data.
64 bytes from 172.17.0.5: icmp_seq=1 ttl=64 time=0.270 ms
64 bytes from 172.17.0.5: icmp_seq=2 ttl=64 time=0.107 ms
- 在两个容器上安装openssh-server:
#apt-get install openssh-server
- 启用主机上的iptables,初始时可以在容器间进行SSH连接。
-
停止Docker服务,并在主机的默认docker文件中添加
DOCKER_OPTS="--icc=false --iptables=true",此选项将启用iptables防火墙并丢弃容器间的所有端口。默认情况下,主机上的iptables未启用:
root@ubuntu:~# iptables -L -n
Chain INPUT (policy ACCEPT)
target prot opt source destination
Chain FORWARD (policy ACCEPT)
target prot opt source destination
DOCKER all -- 0.0.0.0/0 0.0.0.0/0
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0
DOCKER all -- 0.0.0.0/0 0.0.0.0/0
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0
#service docker stop
#vi /etc/default/docker
- 可在Docker Upstart和SysVinit配置文件中自定义Docker二进制文件的位置(特别是用于开发测试):
#DOCKER="/usr/local/bin/docker"
-
使用
DOCKER_OPTS修改守护进程启动选项:
#DOCKER_OPTS="--dns 8.8.8.8 --dns 8.8.4.4"
#DOCKER_OPTS="--icc=false --iptables=true"
- 重启Docker服务:
# service docker start
- 检查iptables:
root@ubuntu:~# iptables -L -n
Chain INPUT (policy ACCEPT)
target prot opt source destination
Chain FORWARD (policy ACCEPT)
target prot opt source destination
DOCKER all -- 0.0.0.0/0 0.0.0.0/0
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 ctstate RELATED, ESTABLISHED
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0
DOCKER all -- 0.0.0.0/0 0.0.0.0/0
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 ctstate RELATED, ESTABLISHED
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0
DROP all -- 0.0.0.0/0 0.0.0.0/0
主机的iptables中添加了DROP规则,会丢弃容器间的连接,此时无法在容器间进行SSH连接。
5. 容器链接
可以使用
--link
参数实现旧版容器之间的通信或连接。步骤如下:
1. 创建作为服务器的第一个容器
sshserver
:
root@ubuntu:~# docker run -i -t -p 2222:22 --name sshserver ubuntu bash
root@9770be5acbab:/#
执行iptables命令,可发现添加了一个Docker链规则。
#root@ubuntu:~# iptables -L -n
Chain INPUT (policy ACCEPT)
target prot opt source destination
Chain FORWARD (policy ACCEPT)
target prot opt source destination
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
Chain DOCKER (0 references)
target prot opt source destination
ACCEPT tcp -- 0.0.0.0/0 172.17.0.3 tcp dpt:22
- 创建作为SSH客户端的第二个容器:
root@ubuntu:~# docker run -i -t --name sshclient --link
sshserver:sshserver
ubuntu bash
root@979d46c5c6a5:/#
- 可以看到Docker链规则中添加了更多规则:
root@ubuntu:~# iptables -L -n
Chain INPUT (policy ACCEPT)
target prot opt source destination
Chain FORWARD (policy ACCEPT)
target prot opt source destination
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
Chain DOCKER (0 references)
target prot opt source destination
ACCEPT tcp -- 0.0.0.0/0 172.17.0.3 tcp dpt:22
ACCEPT tcp -- 172.17.0.4 172.17.0.3 tcp dpt:22
ACCEPT tcp -- 172.17.0.3 172.17.0.4 tcp spt:22
root@ubuntu:~#
-
可以使用
docker inspect检查链接的容器:
root@ubuntu:~# docker inspect -f "{{ .HostConfig.Links }}" sshclient
[/sshserver:/sshclient/sshserver]
- 现在可以使用IP成功SSH到SSH服务器:
#ssh root@172.17.0.3 -p 22
使用
--link
参数,Docker会在容器之间创建一个安全通道,无需在容器外部暴露任何端口。
6. libnetwork与容器网络模型
libnetwork用Go语言实现,用于连接Docker容器。其目标是提供容器网络模型(CNM),帮助程序员提供网络库的抽象。libnetwork的长期目标是遵循Docker和Linux的哲学,提供独立工作的模块,满足容器网络的可组合需求,还旨在将Docker引擎和libcontainer中的网络逻辑模块化到一个单一的、可重用的库中,具体做法如下:
- 用libnetwork替换Docker引擎的网络模块
- 允许本地和远程驱动为容器提供网络
- 提供一个dnet工具用于管理和测试libnetwork,但这仍在开发中
libnetwork实现了CNM,它规范了为容器提供网络所需的步骤,同时提供了可用于支持多个网络驱动的抽象。其端点API主要用于管理相应对象并进行记录,以提供CNM所需的抽象级别。
7. CNM对象
CNM基于三个主要组件构建:
-
沙箱(Sandbox)
:包含容器网络栈的配置,包括路由表管理、容器接口和DNS设置。沙箱的实现可以是Linux网络命名空间、FreeBSD监狱或其他类似概念。一个沙箱可以包含来自多个网络的多个端点,它代表容器的网络配置,如IP地址、MAC地址和DNS条目。libnetwork利用特定于操作系统的参数来填充沙箱所代表的网络配置,为在多个操作系统中实现沙箱提供了框架。Netlink用于管理命名空间中的路由表,目前存在两种沙箱实现:
namespace_linux.go
和
configure_linux.go
,用于唯一标识主机文件系统上的路径。一个沙箱与单个Docker容器关联。沙箱的运行时元素数据结构如下:
type sandbox struct {
id string
containerID string
config containerConfig
osSbox osl.Sandbox
controller *controller
refCnt int
endpoints epHeap
epPriority map[string]int
joinLeaveDone chan struct{}
dbIndex uint64
dbExists bool
isStub bool
inDelete bool
sync.Mutex
}
新的沙箱从网络控制器实例化:
func (c *controller) NewSandbox(containerID string, options ...SandboxOption) (Sandbox, error) {
.....
}
- 端点(Endpoint) :将沙箱连接到网络,为容器暴露的服务提供与同一网络中其他容器的连接。它可以是Open vSwitch的内部端口或类似的vEth对。一个端点只能属于一个网络和一个沙箱,它代表一个服务,提供各种API来创建和管理端点,具有全局范围,但只连接到一个网络。端点由以下结构体指定:
type endpoint struct {
name string
id string
network *network
iface *endpointInterface
joinInfo *endpointJoinInfo
sandboxID string
exposedPorts []types.TransportPort
anonymous bool
generic map[string]interface{}
joinLeaveDone chan struct{}
prefAddress net.IP
prefAddressV6 net.IP
ipamOptions map[string]string
dbIndex uint64
dbExists bool
sync.Mutex
}
端点与唯一的ID和名称关联,连接到网络和沙箱ID,还与IPv4和IPv6地址空间关联,每个端点都与一个端点接口关联。
-
网络(Network)
:一组能够直接相互通信的端点称为网络。它提供了在同一主机或多个主机内所需的连接,每当创建或更新网络时,会通知相应的驱动。例如,VLAN或Linux桥接器在集群内具有全局范围。网络由网络控制器控制,每个网络都有名称、地址空间、ID和网络类型:
type network struct {
ctrlr *controller
name string
networkType string
id string
ipamType string
addrSpace string
ipamV4Config []*IpamConf
ipamV6Config []*IpamConf
ipamV4Info []*IpamInfo
ipamV6Info []*IpamInfo
enableIPv6 bool
postIPv6 bool
epCnt *endpointCnt
generic options.Generic
dbIndex uint64
svcRecords svcMap
dbExists bool
persist bool
stopWatchCh chan struct{}
drvOnce *sync.Once
internal bool
sync.Mutex
}
- 网络控制器(Network controller) :网络控制器对象提供创建和管理网络对象的API,它是libnetwork的入口点,将特定驱动绑定到给定网络,支持多个活动驱动,包括内置和远程驱动。网络控制器允许用户将特定驱动绑定到给定网络:
type controller struct {
id string
drivers driverTable
ipamDrivers ipamTable
sandboxes sandboxTable
cfg *config.Config
stores []datastore.DataStore
discovery hostdiscovery.HostDiscovery
extKeyListener net.Listener
watchCh chan *endpoint
unWatchCh chan *endpoint
svcDb map[string]svcMap
nmap map[string]*netWatch
defOsSbox osl.Sandbox
sboxOnce sync.Once
sync.Mutex
}
通过以上内容,我们全面了解了Docker容器网络栈的管理,包括网络基础、桥接器配置、DNS设置、故障排除、容器间访问限制、容器链接以及libnetwork和CNM等重要概念和操作。这些知识对于有效管理和使用Docker容器网络至关重要。
Docker容器网络栈管理全解析
8. 网络控制器的作用与操作流程
网络控制器在整个 Docker 容器网络管理中扮演着核心角色。它作为 libnetwork 的入口点,负责协调和管理各种网络资源。下面我们详细分析其操作流程:
- 驱动绑定 :网络控制器允许将特定的驱动绑定到给定的网络。这意味着可以根据不同的网络需求,选择合适的本地或远程驱动为容器提供网络服务。例如,在一个复杂的混合云环境中,可以使用远程驱动来连接容器到外部网络。
- 网络对象管理 :通过网络控制器提供的 API,可以创建、更新和删除网络对象。在创建网络时,需要指定网络的名称、地址空间、ID 和网络类型等信息。例如,创建一个基于 VLAN 的网络,就需要在创建网络对象时明确这些参数。
- 端点管理 :网络控制器还负责管理端点。端点是连接沙箱和网络的桥梁,网络控制器确保端点正确地连接到相应的网络和沙箱,并为其分配合适的 IP 地址。
以下是一个简单的 mermaid 流程图,展示了网络控制器的基本操作流程:
graph LR
A[开始] --> B[选择驱动]
B --> C[创建网络对象]
C --> D[创建端点]
D --> E[连接端点到网络和沙箱]
E --> F[分配 IP 地址]
F --> G[完成网络配置]
9. 容器网络配置的最佳实践
在实际使用 Docker 容器网络时,遵循一些最佳实践可以提高网络的性能和安全性:
-
合理配置桥接器
:根据实际需求选择合适的桥接器,并合理分配 IP 地址范围。避免 IP 地址冲突,确保容器之间的通信顺畅。
-
优化 DNS 设置
:使用可靠的 DNS 服务器,避免使用公共 DNS 服务器带来的安全风险。可以在 Docker 守护进程文件中设置多个 DNS 服务器,提高 DNS 解析的可靠性。
-
限制容器访问
:使用 iptables 规则限制容器的访问权限,只允许必要的流量通过。例如,只允许特定 IP 地址的主机访问容器,或者只允许容器访问特定的外部网络。
-
使用容器链接
:对于需要相互通信的容器,使用
--link
参数创建安全通道,避免在容器外部暴露不必要的端口。
以下是一个表格,总结了这些最佳实践及其好处:
| 最佳实践 | 好处 |
| — | — |
| 合理配置桥接器 | 避免 IP 冲突,提高通信效率 |
| 优化 DNS 设置 | 提高 DNS 解析可靠性,增强安全性 |
| 限制容器访问 | 减少安全风险,保护容器数据 |
| 使用容器链接 | 创建安全通道,减少端口暴露 |
10. 未来发展趋势
随着云计算和容器技术的不断发展,Docker 容器网络也在不断演进。以下是一些可能的未来发展趋势:
-
更强大的网络隔离
:未来的 Docker 容器网络可能会提供更强大的网络隔离功能,确保不同容器之间的网络流量完全隔离,提高安全性。
-
支持更多的网络协议
:随着新技术的出现,Docker 可能会支持更多的网络协议,如 IPv6、SDN 等,以满足不同用户的需求。
-
智能化的网络管理
:借助人工智能和机器学习技术,实现智能化的网络管理。例如,自动检测网络故障并进行修复,根据网络流量自动调整网络配置等。
-
与云原生技术的深度融合
:Docker 容器网络将与云原生技术如 Kubernetes 等进行更深度的融合,提供更强大的容器编排和网络管理能力。
11. 总结与展望
通过对 Docker 容器网络栈管理的全面分析,我们了解了从基础的网络连接、桥接器配置、DNS 设置,到复杂的容器间访问限制、容器链接以及 libnetwork 和 CNM 等重要概念和操作。这些知识为我们有效管理和使用 Docker 容器网络提供了坚实的基础。
在实际应用中,我们需要根据具体的业务需求和环境,灵活运用这些技术和方法。同时,关注未来的发展趋势,不断学习和掌握新的技术,以适应不断变化的云计算和容器技术环境。
希望本文能够帮助读者更好地理解和管理 Docker 容器网络,在实际工作中发挥更大的作用。在未来的学习和实践中,我们可以进一步探索 Docker 容器网络的更多可能性,为企业的数字化转型和创新发展提供有力支持。
超级会员免费看
687

被折叠的 条评论
为什么被折叠?



