docker网络

一:网络模式

docker容器中部署应用要与外部通信首先需要网络打通,docker网络有3种工作方式,分别为Host(主机网络),Bridge(桥接网络),Container网络.

网络模式 配置 简介
HOST –net=host 容器将不会虚拟出自己的网卡,配置自己的IP等,而是使用宿主机的IP和端口。
container模式 -net=container:NAME_or_ID 创建的容器不会创建自己的网卡,配置自己的IP,而是和一个指定的容器共享IP、端口范围,共享Network namespace。 kubernetes中的pod就是多个容器共享一个Network namespace。
bridge模式 –net=bridge 此模式会为每一个容器分配、设置IP等,并将容器连接到一个docker0虚拟网桥,通过docker0网桥以及Iptables nat表配置与宿主机通信。(默认网络)

安装docker会自动创建三个网络:使用docker network ls可查看当前docker的网络。

image-20201223115926078

使用bridge网络时会连接到docker0网络

image-20201223125209497

运行容器时使用参数 –network=host 来指定网络模型,否则默认使用bridge模式

  • host模式:使用 –net=host 指定。
  • none模式:使用 –net=none 指定。
  • bridge模式:使用 –net=bridge 指定,默认设置。
  • container模式:使用 –net=container:NAME_or_ID 指定。

HOST网络

Docker使用了Linux的Namespaces技术来进行资源隔离,如PID Namespace隔离进程,Mount Namespace隔离文件系统,Network Namespace隔离网络等。

一个Network Namespace提供了一份独立的网络环境,包括网卡、路由、Iptable规则等都与其他的Network Namespace隔离。一个Docker容器一般会分配一个独立的Network Namespace。但如果启动容器的时候使用host模式,那么这个容器将不会获得一个独立的Network Namespace,而是和宿主机共用一个Network Namespace。容器将不会虚拟出自己的网卡,配置自己的IP等,而是使用宿主机的IP和端口。

使用host模式的容器可以直接使用宿主机的IP地址与外界通信,容器内部的服务端口也可以使用宿主机的端口,不需要进行NAT,host最大的优势就是网络性能比较好,但是docker host上已经使用的端口就不能再用了,网络的隔离性不好。

Bridge网络

当Docker进程启动时,会在主机上创建一个名为docker0的虚拟网桥,此主机上启动的Docker容器会连接到这个虚拟网桥上。虚拟网桥的工作方式和物理交换机类似,这样主机上的所有容器就通过交换机连在了一个二层网络中。

Docker会从RFC1918所定义的私有IP网段中,选择一个和宿主机不同的IP地址和子网分配给docker0,连接到docker0的容器就从这个子网中选择一个未占用的IP给容器使用,并设置docker0的IP地址为容器的默认网关。单机网络下的网络拓扑图如下:

network-docker-bridge-1.jpg

bridge 网络模型主要依赖于大名鼎鼎的 docker0 网桥以及 veth 虚拟网络设备对实现,通过之前笔记对于 Linux 虚拟网络设备的了解,我们知道 veth 设备对中从一端 veth 设备发出的数据包,会直接发送到另一端的 veth 设备上,即使不在一个网络命名空间中,所以 veth 设备对实际上是连接不同网络命名空间的“网线”。docker0 网桥设备充当不同容器网络的网关,事实上,我们一旦以 bridge 网络模式创建容器时,会自动创建相应的 veth 设备对,其中一端连接到 docker0 网桥,另外一端连接到容器网络的 eth0 虚拟网卡。

img

docker0网桥

​ 当在一 台未经特殊网络配置的Ubuntu机器上安装完Docker之后, 在宿主机上通过使用 ifconfig命令可以看到多了一块名为dockero的网卡,假设IP为172.17.0.1/16。有了这样一块网卡,宿主机也会在内核路由表上添加一条到达相应网络的静态路由, 可通过route -n命令查看。

1
2
$ route -n 
172.17.0.0 0.0.0.0 255.255.0.0 U 0 o dockero

​ 此条路由表示所有目的IP地址为172.17.0.0/l6的数据包从dockero网卡发出。然后使用dockerrun命令创建一个执行shell(/bin/bash)的Docker容器,假设容器名称为con1。

​ 在con1容器中可以看到它有两块网卡lo和etho。 lo设备不必多说, 是容器的回环网卡;etho
即为容器与外界通信的网卡,etho的IP为172.17.0.2/16,和宿主机上的网桥dockero在同一个网段。
查看con1的路由表, 可以发现con1的默认网关正是宿主机的dockerO网卡, 通过测试, con1可以顺利访问外网和宿主机网络, 因此表明con1的etho网卡与宿主机的dockero网卡是相互连通的。

​ 这时在其他控制台窗口查看宿主机的网络设备, 会发现有一块以 “ veth”开头的网卡, 如vethe043于86, 我们可以大胆猜测这块网卡肯定是veth设备了,而vethpair总是成对出现的。 在3.1 节中介绍过,veth pair:il常用来连接两个network namespace, 那么另一个应该是Docker容器con1中的etho了。之前已经判断con1容器的etho和宿主机的dockero是相连的, 那么vethe043f86也应一个简单的网卡设备了, 而是一个网桥。该是与dockero相连的,不难想到, dockero就不只是一个简单的网卡设备,而是一个网桥。
​ 真实情况正是如此,图3-18即为Docker默认网络模式(bridge模式)下的网络环境拓扑图,
创建了dockero网桥,并以vethpair连接各容器的网络,容器中的数据通过dockero网桥转发到etho
网卡上。

image-20220614095538666

​ 这里网桥的概念等同于交换机,为连在其上的设备转发数据帧。 网桥上的veth网卡设备相当 于交换机上的端口,可以将多个容器或虚拟机连接在其上,这些端口工作在二层,所以是不需要配置IP信息的。图中dockero网桥就为连在其上的容器转发数据帧,使得同一台宿主机上的Docker 容器之间可以相互通信。 读者应该注意到dockero既然是二层设备, 其上怎么也配置了IP呢? dockero是普通的Linux网桥, 它是可以在上面配置IP的,可以认为其内部有一个可以用于配置IP信息的网卡接口(如同每一个Open vSwitch网桥都有一个同名的内部接口一样儿在Docker的桥接 网络模式中, dockero的IP地址作为连千之上的容器的默认网关地址存在。

​ 在Linux中,可以使用brctl命令查看和管理网桥 (需要安装bridge-utils软件包)。

​ dockero网桥是在Docker daemon启动时自动创建的,其IP默认为172.17.0.1/16, 之后创建的Docker容器都会在dockero子网的范围内选取一个未占用的IP使用, 并连接到dockero网桥上。Docker提供了如下参数可以帮助用户自定义dockero的设置。

  • –bip=CIDR:设置dockero的IP地址和子网范围, 使用CIDR格式,如192.168.100.1/24。注意这个参数仅仅是配置docker0的,对其他自定义的网桥无效。并且在指定这个参数的时候,宿主机是不存在docker0的或者docker0已存在且docker0的IP和参数指定的IP一致才行。
  • –fixed-cidr=CIDR:限制Docker容器获取IP的范围。Docker容器默认获取的IP范围为Docker网桥(docker0网桥或者–bridge指定的网桥) 的整个子网范围, 此参数可将其缩小到某个子网范围内,所以这个参数必须在Docker网桥的子网范围内。如dockero的IP为172.17.0.1/16 , 可将--fixed-cidr 设为172.17.1.1/24, 那么Docker 容器的IP范围将为172.17.1.1 -172.17.1.254。
  • –mtu=BYTES:指定dockero的最大传输单元(MTU)。

docker配置bridge流程

1、在主机创建一对虚拟网卡 veth pair设备,veth设备总是成对出现,它们组成一个数据通道,数据从一个设备进入,然后会从另一个设备出去。因此常用于连接两个网络设备。

2、Docker将veth pair设备的一端放在新创建的容器中,并命名为eth0(容器的网卡)。另一端放在主机中,以vethxxx这样类似的名字命名,并将这个网络设备加入到docker0网桥中,可以通过brctl show命令查看。3、从docker0子网中分配一个IP给容器使用,并设置docker0的IP地址为容器的默认网关。

image-20201223170337708

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[root@oceanxie ~]# docker inspect 2766089fcb73
"Networks": {
"bridge": {
"IPAMConfig": null,
"Links": null,
"Aliases": null,
"NetworkID": "657c8b93445642b253c461a820b1b221f17769140dad74ea84f1ca30a646617c",
"EndpointID": "2e045aa2019cfd08034bbcc31e906bce62f1325aa4ee2b16952eba41d49488ba",
"Gateway": "172.17.0.1",
"IPAddress": "172.17.0.4",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"MacAddress": "02:42:ac:11:00:04",
"DriverOpts": null
}
}

image-20201223170905777

进入docker容器内执行ip addr可看到容器内有一网卡

image-20201223180233426

那么主机上同样有对应一张网卡,可在容器内执行命令cat /sys/class/net/eth0/iflink或者ip link show可查看到对应的网卡

image-20201224093504129

在宿主机上可查看到该网卡信息:

image-20201223180445468

由此可见容器内eth0@if2489与宿主机veth73c1092@if2488成对。在书主机上可查看到该veth绑定在docker0网络上。

image-20201224092508246

该容器两端各有一张veth pair(eth0@if2489和veth73c1092@if2488),一个(eth0@if2489)挂在容器上,另一个(veth73c1092@if2488)挂在docker0上

image-20201224095326068

3、bridge模式下容器的通信:

在bridge模式下,连在同一网桥上的容器可以相互通信(若出于安全考虑,也可以禁止它们之间通信,方法是在DOCKER_OPTS变量中设置–icc=false,这样只有使用–link才能使两个容器通信)。

Docker可以开启容器间通信(意味着默认配置–icc=true),也就是说,宿主机上的所有容器可以不受任何限制地相互通信,这可能导致拒绝服务攻击。进一步地,Docker可以通过–ip_forward和–iptables两个选项控制容器间、容器和外部世界的通信。

容器也可以与外部通信,我们看一下主机上的Iptable规则,可以看到这么一条

1
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE

这条规则会将源地址为172.17.0.0/16的包(也就是从Docker容器产生的包),并且不是从docker0网卡发出的,进行源地址转换,转换成主机网卡的地址。这么说可能不太好理解,举一个例子说明一下。假设主机有一块网卡为eth0,IP地址为10.10.101.105/24,网关为10.10.101.254。从主机上一个IP为172.17.0.1/16的容器中ping百度(180.76.3.151)。IP包首先从容器发往自己的默认网关docker0,包到达docker0后,也就到达了主机上。然后会查询主机的路由表,发现包应该从主机的eth0发往主机的网关10.10.105.254/24。接着包会转发给eth0,并从eth0发出去(主机的ip_forward转发应该已经打开)。这时候,上面的Iptable规则就会起作用,对包做SNAT转换,将源地址换为eth0的地址。这样,在外界看来,这个包就是从10.10.101.105上发出来的,Docker容器对外是不可见的。

那么,外面的机器是如何访问Docker容器的服务呢?我们首先用下面命令创建一个含有web应用的容器,将容器的80端口映射到主机的80端口。

1
docker run --name=nginx_bridge --net=bridge -p 80:80 -d nginx

然后查看Iptable规则的变化,发现多了这样一条规则:

1
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 80 -j DNAT --to-destination 172.17.0.2:80

此条规则就是对主机eth0收到的目的端口为80的tcp流量进行DNAT转换,将流量发往172.17.0.2:80,也就是我们上面创建的Docker容器。所以,外界只需访问10.10.101.105:80就可以访问到容器中的服务。

bridge模式是docker的默认网络模式,不写–net参数,就是bridge模式。使用docker run -p时,docker实际是在iptables做了DNAT规则,实现端口转发功能。可以使用iptables -t nat -vnL查看

当在容器内ping外网时,我们依次抓包查看

在容器内ping外网:

image-20201229180215454

抓包容器内etho网卡:

image-20201229180247665

抓包宿主机docker0网络

image-20201229180324653

抓包宿主机eth0网络:

image-20201229180355683

本文参考:

https://blog.csdn.net/meltsnow/article/details/94490994
https://juejin.cn/post/6904201044390051848