docker受一个github上的issue启发,引入了容器网络模型(container network model,CNM),容器网络模型主要包含了3个概念
network:网络,可以理解为一个Driver,是一个第三方网络栈,包含多种网络模式:单主机网络模式(none、host、bridge,joined container),多主机网络模式(overlay、macvlan、flannel)
sandbox:沙盒,它定义了容器内的虚拟网卡、DNS和路由表,是network namespace的一种实现,是容器的内部网络栈
endpoint:端点,用于连接sandbox和network
我们可以类比传统网络模型,将network比作交换机,sandbox比作网卡,endpoint比作接口和网线,另外,docker在创建容器时,先调用控制器创建sandbox对象,再调用容器运行时为容器创建network namespace
这里我们先讨论docker的单主机网络模式,它包括以下4类:host、bridge、none、joined-containe
docker不会为容器创建独有的network namespace;使用宿主机的默认网络命名空间,共享一个网络栈;表现为容器内和宿主机的IP一致;这种模式用于网络性能较高的场景,但安全隔离性相对差一些。
桥接模式,有点类型VM-NAT,dockerd进程启动时会创建一个docker0网桥,容器内的数据通过这个网卡设备与宿主机进行数据传输。
虚拟网桥的工作方式和物理交换机类似,这样主机上的所有容器就通过交换机连在了一个二层网络中。从docker0
子网中分配一个 IP 给容器使用,并设置 docker0 的 IP 地址为容器的默认网关。在主机上创建一对虚拟网卡veth pair
设备,Docker 将 veth pair 设备的一端放在新创建的容器中,并命名为eth0
(容器的网卡),另一端放在主机中,以vethxxx
这样类似的名字命名,并将这个网络设备加入到 docker0 网桥中。bridge
模式是 docker 的默认网络模式,不写–net
参数,就是bridge
模式。使用docker run -p
时,docker 实际是在iptables
做了DNAT
规则,实现端口转发功能。可以使用iptables -vnL
查看。
docker会为容器创建独有的network namespace,也会为这个命名空间配置好虚拟网卡,路由,DNS,IP地址与iptables规则(也就是sandbox的内容)。
none模式可以说是桥接模式的一种特例,docker会为容器创建独有的network namespace ,但不会为这个命名空间准备虚拟网卡,IP地址,路由等,需要用户自己配置。
容器共享模式,这种模式是host模式的一种延伸,一组容器共享一个network namespace;对外表现为他们有共同的IP地址,共享一个网络栈;kubernetes的pod就是使用的这一模式。
关于跨主机的docker网络通信,包含overlay、macvaln,又包含calico、flannel、weave等方案,不过跨主机的docker网络管理更多的是交给kubernetes或swarm等编排工具去实现了。
回顾docker网络对象和网络模式的关系其实就是下面这一张表格,每个容器在network namespace中的占比决定了采用哪种网络模式
[root@localhost ~]# iptables -h iptables v1.4.21 Usage: iptables -[ACD] chain rule-specification [options] iptables -I chain [rulenum] rule-specification [options] iptables -R chain rulenum rule-specification [options] iptables -D chain rulenum [options] iptables -[LS] [chain [rulenum]] [options] iptables -[FZ] [chain] [options] iptables -[NX] chain iptables -E old-chain-name new-chain-name iptables -P chain target [options] iptables -h (print this help information) Commands: Either long or short options are allowed. --append -A chain Append to chain --check -C chain Check for the existence of a rule --delete -D chain Delete matching rule from chain --delete -D chain rulenum Delete rule rulenum (1 = first) from chain --insert -I chain [rulenum] Insert in chain as rulenum (default 1=first) --replace -R chain rulenum Replace rule rulenum (1 = first) in chain --list -L [chain [rulenum]] List the rules in a chain or all chains --list-rules -S [chain [rulenum]] Print the rules in a chain or all chains --flush -F [chain] Delete all rules in chain or all chains --zero -Z [chain [rulenum]] Zero counters in chain or all chains --new -N chain Create a new user-defined chain --delete-chain -X [chain] Delete a user-defined chain --policy -P chain target Change policy on chain to target --rename-chain -E old-chain new-chain Change chain name, (moving any references) Options: --ipv4 -4 Nothing (line is ignored by ip6tables-restore) --ipv6 -6 Error (line is ignored by iptables-restore) [!] --protocol -p proto protocol: by number or name, eg. `tcp' [!] --source -s address[/mask][...] source specification [!] --destination -d address[/mask][...] destination specification [!] --in-interface -i input name[+] network interface name ([+] for wildcard) --jump -j target target for rule (may load target extension) --goto -g chain jump to chain with no return --match -m match extended match (may load extension) --numeric -n numeric output of addresses and ports [!] --out-interface -o output name[+] network interface name ([+] for wildcard) --table -t table table to manipulate (default: `filter') --verbose -v verbose mode --wait -w [seconds] maximum wait to acquire xtables lock before give up --wait-interval -W [usecs] wait time to try to acquire xtables lock default is 1 second --line-numbers print line numbers when listing --exact -x expand numbers (display exact values) [!] --fragment -f match second or further fragments only --modprobe=<command> try to insert modules using this command --set-counters PKTS BYTES set the counter during insert/append [!] --version -V print package version.
iptables -A INPUT -p icmp -j REJECT
iptables -I INPUT -p tcp --dport 9501 -j ACCEPT iptables -I INPUT -p udp --dport 9501 -j ACCEPT
iptables -A OUTPUT -p tcp --dport 80 -j REJECT
使用 iptables -L
可以查看已设置的规则,iptables -D
可以删除规则,iptables 命令执行完是即时生效的,但是如果主机重启,已设置的规则就会丢失,这里可以使用 iptables-save
和 iptables-restore
。iptables-save 将现有规则保存成文件,iptables-restore 从文件中恢复规则。
docker run -d --name redis01 -p 6380:6379 redis
该命令执行后,docker 会在 iptables 自定义链 DOCKER 中定义转发规则,如果此时系统的 net.ipv4.ip_forward
为0,该命令执行完会提示:WARNING: IPv4 forwarding is disabled. Networking will not work,只需打开该配置就行了,无需重启容器。此时查看 DOCKER 链可以看到添加了一条允许所有来源转发到6379端口的流量,用 redis-cli 也可以顺利连上
开发中,经常会遇到容器里面放问宿主机的情况,除了使用 host.docker.internal
之外,还可以配置 extra_hosts 解决,因为 docker0 与 宿主机是相通的,直接用 ifconfig
查看宿主机 en0
网卡的ip地址,配置到 extra_hosts 即可,如:
version: '3' networks: web-network: driver: bridge services: fpm: build: context: ./fpm ports: - '8080:8080' networks: - web-network extra_hosts: - "test.com:192.168.1.100"
Docker提供了bridge, host, overlay等多种网络,同一个Docker宿主机上同时存在多个不同类型的网络。位于不同网络中的容器,彼此之间是无法通信的。Docker容器的跨网络隔离与通信,是借助了iptables的机制。我们知道,iptables的filter表中默认划分为IPNUT, FORWARD和OUTPUT共3个链,详情请参考 iptables及其过滤规则。Docker在FORWARD链中,还额外提供了自己的链,以实现bridge网络之间的隔离与通信。
在2015.12之前,Docker只额外提供了DOCKER链。在此之后,直到Docker 17.06.0(2017.6)之前的版本中,Docker提供了如下2个链:
DOCKER DOCKER-ISOLATION
在Docker 17.06.0(2017.6)及之后,Docker 18.03.1(2018.4)及之前的版本中,Docker提供了如下3个链:
DOCKER DOCKER-ISOLATION DOCKER-USER
查看Docker的iptables如下:
Chain FORWARD (policy ACCEPT) target prot opt source destination DOCKER-USER all -- 0.0.0.0/0 0.0.0.0/0 DOCKER-ISOLATION-STAGE-1 all -- 0.0.0.0/0 0.0.0.0/0 DOCKER all -- 0.0.0.0/0 0.0.0.0/0
在Docker 18.05.0(2018.5)及之后的版本中,提供如下4个chain:
DOCKER DOCKER-ISOLATION-STAGE-1 DOCKER-ISOLATION-STAGE-2 DOCKER-USER
目前,Docker默认对宿主机的iptables设置规则完整一览:
iptables -N DOCKER iptables -N DOCKER-ISOLATION-STAGE-1 iptables -N DOCKER-ISOLATION-STAGE-2 iptables -N DOCKER-USER iptables -A FORWARD -j DOCKER-USER iptables -A FORWARD -j DOCKER-ISOLATION-STAGE-1 iptables -A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT iptables -A FORWARD -o docker0 -j DOCKER iptables -A FORWARD -i docker0 ! -o docker0 -j ACCEPT iptables -A FORWARD -i docker0 -o docker0 -j ACCEPT iptables -A DOCKER-ISOLATION-STAGE-1 -i docker0 ! -o docker0 -j DOCKER-ISOLATION-STAGE-2 iptables -A DOCKER-ISOLATION-STAGE-1 -j RETURN iptables -A DOCKER-ISOLATION-STAGE-2 -o docker0 -j DROP iptables -A DOCKER-ISOLATION-STAGE-2 -j RETURN iptables -A DOCKER-USER -j RETURN
仅处理从宿主机到docker0的IP数据包。
可以看到,为了隔离在不同的bridge网络之间的容器,Docker提供了两个DOCKER-ISOLATION阶段实现。DOCKER-ISOLATION-STAGE-1链过滤源地址是bridge网络(默认docker0)的IP数据包,匹配的IP数据包再进入DOCKER-ISOLATION-STAGE-2链处理,不匹配就返回到父链FORWARD。在DOCKER-ISOLATION-STAGE-2链中,进一步处理目的地址是bridge网络的IP数据包,匹配的IP数据包表示该IP数据包是从一个bridge网络的网桥发出,到另一个bridge网络的网桥,这样的IP数据包来自其他bridge网络,将被直接DROP;不匹配的IP数据包就返回到父链FORWARD继续进行后续处理。
Docker启动时,会加载DOCKER链和DOCKER-ISOLATION(现在是DOCKER-ISOLATION-STAGE-1)链中的过滤规则,并使之生效。绝对禁止修改这里的过滤规则。
如果用户要补充Docker的过滤规则,强烈建议追加到DOCKER-USER链。DOCKER-USER链中的过滤规则,将先于Docker默认创建的规则被加载,从而能够覆盖Docker在DOCKER链和DOCKER-ISOLATION链中的默认过滤规则。例如,Docker启动后,默认任何外部source IP都被允许转发,从而能够从该source IP连接到宿主机上的任何Docker容器实例。如果只允许一个指定的IP访问容器实例,可以插入路由规则到DOCKER-USER链中,从而能够在DOCKER链之前被加载。示例如下:
只允许192.168.1.1访问容器
iptables -A DOCKER-USER -i docker0 ! -s 192.168.1.1 -j DROP
只允许192.168.1.0/24网段中的IP访问容器
iptables -A DOCKER-USER -i docker0 ! -s 192.168.1.0/24 -j DROP
只允许192.168.1.1-192.168.1.3网段中的IP访问容器(需要借助于iprange模块)
iptables -A DOCKER-USER -m iprange -i docker0 ! --src-range 192.168.1.1-192.168.1.3 -j DROP
为了能够从容器中访问其他Docker宿主机,Docker需要在iptables的nat表中的POSTROUTING链中插入转发规则,示例如下:
iptables -t nat -A POSTROUTING -s 172.18.0.0/16 -j MASQUERADE
上述配置,还进一步限制了容器实例的IP范围,这是为了区分Docker宿主机上有多个bridge网络的情况。
dockerd启动时,参数--iptables默认为true,表示允许修改iptables路由表。要禁用该功能,可以有两个选择:设置启动参数--iptables=false
修改配置文件/etc/docker/daemon.json,设置"iptables": "false";然后执行systemctl reload docker重新加载