管理 Docker 容器的网络栈
容器网络基础
Docker 容器之间可以相互 ping 通,并且能够访问外部网络。为了实现外部访问,容器的端口会被映射到主机端口。容器使用网络命名空间,当创建第一个容器时,会为其创建一个新的网络命名空间。
在容器和 Linux 桥之间会创建一个虚拟以太网(vEthernet 或 vEth)链接。从容器的 eth0 端口发送的流量通过 vEth 接口到达桥接器,然后进行交换。可以使用以下命令查看 Linux 桥:
$ sudo brctl show
该命令的输出类似于以下内容,显示桥接器名称以及映射到的容器上的 vEth 接口:
$ bridge name bridge id STP enabled interfaces
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 选项完成。
Docker 桥接器
默认情况下,Docker 服务器会在 Linux 内核中创建一个 docker0 桥接器,它可以在其他物理或虚拟网络接口之间来回传递数据包,使它们表现得像一个单一的以太网网络。可以使用以下命令查看:
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 地址。例如:
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 的步骤如下:
1. 停止 Docker 服务:
# sudo service docker stop
- 关闭 docker0 接口:
# sudo ip link set dev docker0 down
- 删除 docker0 桥接器:
# sudo brctl delbr docker0
- 清空 iptables NAT 表的 POSTROUTING 链:
# sudo iptables -t nat -F POSTROUTING
-
在
/etc/default/docker文件中添加新的桥接器设置:
# echo 'DOCKER_OPTS="-b=br0"' >> /etc/default/docker
- 创建 br0 桥接器:
# sudo brctl addbr br0
- 为 br0 桥接器分配 IP 地址:
# sudo ip addr add 192.168.10.1/24 dev br0
- 启动 br0 桥接器:
# sudo ip link set dev br0 up
- 启动 Docker 服务:
# sudo service docker start
使用以下命令可以显示新的桥接器名称和 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
...
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 文件包括:
-
/etc/hostname
-
/etc/resolv.conf
-
/etc/hosts
添加 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
容器与外部网络通信故障排除
只有当
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 规则,以便它可以连接到其他容器暴露的端口。
默认情况下,Docker 的转发规则允许所有外部 IP 访问。要仅允许特定 IP 或网络访问容器,可以在 Docker 过滤链的顶部插入一个否定规则。例如,使用以下命令限制外部访问,仅允许源 IP 10.10.10.10 访问容器:
#iptables -I DOCKER -i ext_if ! -s 10.10.10.10 -j DROP
限制容器之间的 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
- 使用 ping 工具测试容器之间的连通性:
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
^C
--- 172.17.0.5 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1002ms
rtt min/avg/max/mdev = 0.107/0.188/0.270/0.082 ms
root@e58a9bf7120b:/#
- 在两个容器上安装 openssh-server:
#apt-get install openssh-server
- 在主机上启用 iptables。
-
停止 Docker 服务,并在主机的默认 docker 文件中添加
DOCKER_OPTS="--icc=false --iptables=true":
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 服务:
# 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 连接。
容器链接
可以使用
--link
参数来实现旧版容器之间的通信或连接。步骤如下:
1. 创建第一个容器作为服务器(sshserver):
root@ubuntu:~# docker run -i -t -p 2222:22 --name sshserver ubuntu
bash
root@9770be5acbab:/#
#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:/#
- 检查 iptables 规则:
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 会在容器之间创建一个安全通道,无需在容器外部暴露任何端口。
libnetwork 和容器网络模型
libnetwork 是用 Go 语言实现的,用于连接 Docker 容器。其目标是提供一个容器网络模型(CNM),帮助程序员提供网络库的抽象。libnetwork 的长期目标是遵循 Docker 和 Linux 的哲学,提供独立工作的模块,满足容器网络的可组合需求,并将 Docker 引擎和 libcontainer 中的网络逻辑模块化到一个单一的、可重用的库中,具体做法包括:
- 用 libnetwork 替换 Docker 引擎的网络模块。
- 允许本地和远程驱动为容器提供网络。
- 提供一个 dnet 工具来管理和测试 libnetwork(目前仍在开发中)。
libnetwork 实现了 CNM,它规范了为容器提供网络所需的步骤,同时提供了一个可用于支持多个网络驱动的抽象。其端点 API 主要用于管理相应的对象并进行记录,以提供 CNM 所需的抽象级别。
CNM 对象
CNM 基于三个主要组件构建:
1.
Sandbox
:
- 包含容器网络栈的配置,包括路由表管理、容器接口和 DNS 设置。
- 可以是 Linux 网络命名空间、FreeBSD 监狱或其他类似概念。
- 可能包含来自多个网络的多个端点,代表容器的网络配置,如 IP 地址、MAC 地址和 DNS 条目。
- libnetwork 使用特定于操作系统的参数来填充由沙箱表示的网络配置,并提供一个框架,以便在多个操作系统中实现沙箱。
- 目前有两个沙箱实现(
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 地址空间关联,每个端点与一个端点接口关联。
3. 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 容器网络栈的管理、配置和故障排除,以及对 libnetwork 和 CNM 的了解,可以更好地实现容器之间的通信和网络连接,满足不同场景下的需求。
管理 Docker 容器的网络栈(续)
容器网络管理的关键要点总结
为了更清晰地理解容器网络管理的各个方面,下面通过表格形式对前面提到的关键操作和配置进行总结:
| 操作类型 | 具体操作 | 命令示例 |
| — | — | — |
| 查看 Linux 桥 | 显示桥接器信息 |
sudo brctl show
|
| 连接容器到外部世界 | 查看 iptables NAT 表 |
sudo iptables -t nat -L -n
|
| 修改默认桥接器 | 一系列步骤将默认桥接器从 docker0 改为 br0 | 1.
sudo service docker stop
2.
sudo ip link set dev docker0 down
3.
sudo brctl delbr docker0
4.
sudo iptables -t nat -F POSTROUTING
5.
echo 'DOCKER_OPTS="-b=br0"' >> /etc/default/docker
6.
sudo brctl addbr br0
7.
sudo ip addr add 192.168.10.1/24 dev br0
8.
sudo ip link set dev br0 up
9.
sudo service docker start
|
| DNS 配置 | 添加 DNS 服务器 |
docker run --dns=8.8.8.8 --net="bridge" -t -i ubuntu:latest /bin/bash
|
| 故障排除 | 检查和设置 ip_forward 参数 | 1.
cat /proc/sys/net/ipv4/ip_forward
2.
echo 1 > /proc/sys/net/ipv4/ip_forward
3.
cat /proc/sys/net/ipv4/ip_forward
|
| 限制 SSH 访问 | 一系列步骤限制容器间 SSH 访问 | 1. 创建容器 c1 和 c2
2. 测试连通性(ping)
3. 安装 openssh-server
4. 启用 iptables
5. 停止 Docker 服务并修改配置文件
6. 重启 Docker 服务
7. 检查 iptables |
| 容器链接 | 创建服务器和客户端容器并链接 | 1.
docker run -i -t -p 2222:22 --name sshserver ubuntu bash
2.
docker run -i -t --name sshclient --link sshserver:sshserver ubuntu bash
|
容器网络管理的流程图
下面通过 mermaid 流程图展示容器网络的基本管理流程:
graph LR
classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px
A([开始]):::startend --> B(创建容器):::process
B --> C{检查网络配置}:::decision
C -->|需要修改| D(修改默认设置):::process
C -->|不需要修改| E(配置 DNS):::process
D --> E
E --> F{测试连通性}:::decision
F -->|失败| G(故障排除):::process
F -->|成功| H(进行其他操作):::process
G --> F
H --> I([结束]):::startend
深入理解 CNM 组件之间的关系
前面介绍了 CNM 的三个主要组件(Sandbox、Endpoint、Network)和 Network controller,下面通过流程图进一步展示它们之间的关系:
graph LR
classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
A(Network controller):::process --> B(Network):::process
B --> C(Endpoint):::process
C --> D(Sandbox):::process
D --> E(Docker 容器):::process
B -.-> E(提供网络连接)
C -.-> E(提供服务连接)
从这个流程图可以看出,Network controller 负责管理 Network,Network 包含多个 Endpoint,Endpoint 连接到 Sandbox,而 Sandbox 与 Docker 容器关联。整个过程中,Network 为容器提供网络连接,Endpoint 为容器提供服务连接。
未来容器网络管理的发展趋势
虽然 libnetwork 已经在容器网络管理方面提供了很多便利,但未来容器网络管理仍有一些发展趋势值得关注:
1.
多网络驱动支持的增强
:随着云计算和容器技术的不断发展,可能会出现更多类型的网络驱动。libnetwork 有望进一步增强对多种网络驱动的支持,以满足不同用户的需求。
2.
更智能的网络配置
:未来可能会出现更智能的网络配置工具,能够根据容器的类型、用途和资源需求自动进行网络配置,减少人工干预。
3.
安全性的进一步提升
:容器网络的安全性始终是一个重要问题。未来可能会在 CNM 中集成更多的安全机制,如加密通信、访问控制等,以保护容器网络免受攻击。
总结
容器网络管理是 Docker 应用中的一个重要方面,涉及到网络配置、故障排除、容器链接等多个环节。通过对 Docker 容器网络栈的深入理解,以及对 libnetwork 和 CNM 的运用,可以更好地实现容器之间的通信和网络连接。同时,随着技术的不断发展,未来容器网络管理将朝着更智能、更安全的方向发展。希望本文能够帮助读者更好地掌握容器网络管理的相关知识和技能,在实际应用中更加得心应手。
超级会员免费看
712

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



