Docker(七)—docker网络(原生&自定义网络、容器通信)
文章目录
1.Docker原生网络
安装Docker时,它会自动创建三个网络,bridge(创建容器默认连接到此网络)、 none 和host。
docker network ls
docker网络模式 | 功能 |
---|---|
host | 容器将不会虚拟出自己的网卡,配置自己的IP等,而是使用宿主机的IP和端口。 |
Container(Joined) | 创建的容器不会创建自己的网卡,配置自己的IP,而是和一个指定的容器共享IP、端口范围。 |
None | 该模式关闭了容器的网络功能。 |
Bridge | 此模式会为每一个容器分配、设置IP等,并将容器连接到一个docker0虚拟网桥,通过docker0网桥以及Iptables nat表配置与宿主机通信。 |
运行容器的时候指定网络模式:
模式 | 参数 |
---|---|
host模式 | 使用 --net=host(–network host) 指定。 |
none模式 | 使用 --net=none 指定。 |
bridge模式 | 使用 --net=bridge 指定,默认设置。 |
container模式 | 使用 --net=container:NAME_or_ID 指定。 |
bridge网桥模式
bridge模式下容器没有一个公有ip,只有宿主机可以直接访问,外部主机是不可见的。
容器通过宿主机的NAT规则后可以访问外网。
docker安装时会创建一个名为docker0的linux bridge,新建的容器会自动桥接到这个接口
docker run -d --name nginx nginx
brctl show
ip addr show #添加了veth638a7a8@if41
可以发现,当挂起一个容器时,就会出现相对应的网桥接口:
这时,我们的容器就通过docker0网桥与宿主机建立通信了:
docker inspect nginx
ping 172.17.0.2
curl 172.17.0.2
再运行一个容器:
docker run -d --name nginx2 nginx
brctl show
可以看到docker0上又接了一个接口:
ip addr show
又出现了vethad38eee@if43:
不仅如此,只要宿主机做了NAT,我们同时在容器中做好dns解析,就能与外网通信:
docker run -it --rm busybox
ip addr show
vi /etc/resolv.conf
ping baidu.com
sysctl -a | grep ip_forward
host模式
相当于使用了Vmware中的桥接模式,与宿主机在同一个网络中,但没有独立IP地址。
一个Network Namespace提供了一份独立的网络环境,包括网卡、路由、Iptable规则等都与其他的Network Namespace隔离。一个Docker容器一般会分配一个独立的Network Namespace。但如果启动容器的时候使用host模式,那么这个容器将不会获得一个独立的Network Namespace,而是和宿主机共用一个Network Namespace。容器将不会虚拟出自己的网卡,配置自己的IP等,而是使用宿主机的IP和端口。
host模式可以让容器共享宿主机网络栈,这样的好处是外部主机与容器直接通信,但是容器的网络缺少隔离性。
做此过程之前删除上一步骤中运行的所有容器
netstat -antlp #先查看宿主机的端口情况
docker run -d --name nginx --network host nginx
netstat -antlp #再次查看宿主机的端口情况(80端口打开了)
brctl show
我们可以看到它并不像bridge模式那样,没有在docker0下桥接接口,而是与宿主机共用一个网络:
用外部主机访问,可以看到nginx默认发布页面
curl 172.25.254.1
docker run -it --rm --network host busybox
ip addr
可以看到与宿主机的网络一模一样:
如果此时我们再开一个nginx:
dockedocker run -d --name nginx2 --network host nginx
docker ps #开启成功
docker ps #其中一个nginx消失
docker ps -a #可以看到被停掉的nginx
这是因为两个nginx的端口和ip冲突了
none模式
none模式是指禁用网络功能,只有lo接口,在容器创建时使用–network none
none模式关闭了容器的网络功能,但是在以下两种情况下是有用的:
- 容器并不需要网络(例如只需要写磁盘卷的批处理任务)。
- overlay:在docker1.7代码进行了重构,单独把网络部分独立出来编写,所以在docker1.8新加入的一个overlay网络模式。
docker run -it --rm --network none busybox
ip addr
可以看到只有lo接口:
docker run -d --name nginx nginx
docker ps
docker inspect nginx | grep Pid
cd /proc/9206
cd ns
ll
查看到网络是不可以使用的:
2.Docker自定义网络
原生网络是docker安装后自动创建的,真正需要配置的是自定义网络。
Docker 提供三种 user-defined 网络驱动:bridge, overlay 和 macvlan。
- bridge驱动类似默认的bridge网络模式,但增加了一些新的功能
- overlay 和 macvlan 用于创建跨主机的网络
创建自定义网络mynet1:
docker network ls
docker network create -d bridge mynet1
docker network ls
docker network inspect mynet1
可以看到Docker自动分配IP网段是172.18.0.0/16:
使用自定义的网络mynet1运行容器:
docker run -it --name demo --network mynet1 busybox
ping demo #可以看到自动分配的ip为172.18.0.2
查看当前 host 的网络结构变化:
ip addr
创建自定义网络mynet2:
docker network create -d bridge mynet2
docker network ls
查看当前 host 的网络结构变化:
ip addr
可以看到mynet2的网段为172.19.0.1/16
brctl show
可以看到mynet1上有demo这个接口,而mynet2是一个没有接口的bridge:
使用自定义的网络mynet2运行容器:
docker run -it --name test --network mynet2 busybox #按ctrl+pq退出
可以看到给他分配的ip为172.19.0.2:
使用自定义的网络mynet2运行容器test2:
docker run -it --name test2 --network mynet2 busybox
可以看到给他分配的ip为172.19.0.3,它可以和test通信,但不能和demo通信,因为他们不在同一网段,使用的是不同的自定义网络
那么你可能会有疑问,在容器中通信时为什么要用容器名,而不用ip地址呢?
我们通过一个实验来理解这个问题
docker run -it --name vm1 --network mynet1 busybox
ip addr
按ctrl+d退出
我们可以看到它自动分配的ip为172.18.0.2/16
此时再运行一个容器:
docker run -it --name vm2 --network mynet1 busybox
ip addr
按ctrl+pq退出
我们可以看到它自动分配的ip为172.18.0.2/16,是刚才vm1的ip,所以说当一个容器停掉后,它的ip会被释放,如果有别的容器运行,就会分配给其他容器。
此时再启动vm1:
docker start vm1
docker inspect vm1 | grep IPAddress
我们可以看到它自动分配的ip为172.18.0.3。说明如果再次启动被停掉的容器,docker会给他自动分配一个其他的ip。如果我们用ip来进行容器之间的通信,显然是不合理的,因为它的ip会随着容器的启动和停止重新分配
连接到vm1:
docker attach vm1
我们可以看到,使用容器名通信时,它会自动帮我们连接到对应的容器,它内部的dns可以自动更新容器的ip
除此之外,我们也可以自己指定 IP 网段:
需要在创建网络时指定 --subnet 和 --gateway 参数
我们先将之前的网络和容器删除:
创建自定义网络mynet1:
docker network create --subnet 192.168.1.0/24 --gateway 192.168.1.1 mynet1
ip addr
可以看到它创建的网络网段为192.168.1.1/24:
运行容器使用自定义网络:
docker run -it --name vm1 --network mynet1 busybox
容器分配到的IP为192.168.1.2:
容器的IP都是docker自动从subnet中分配,我们能否指定一个静态IP呢?
当然可以,通过–ip指定,你必须指定该网络模式的网段ip,否则会报错:
docker run -it --name vm2 --network mynet1 --ip 192.168.1.100 busybox
注意:只有使用 --subnet 创建的网络才能指定静态IP
3.Docker容器间通信
通过上面的实验我们可以知道,使用同一自定义网络(即同一网段)运行的容器是可以相互通信的,那么如果两个容器不在同一网段,但我们也想让它通信,怎么办?
首先我们删除之前创建的网络和容器并且新建两个自定义网络:
docker network create -d bridge mynet1
docker network create -d bridge mynet2
ip addr
可以看到他们的网段分别为172.20.0.1/16和172.21.0.1/16
开启两个容器基于不同的docker网络:
docker run -it --name vm1 --network mynet1 busybox
docker run -it --name vm2 --network mynet2 busybox
按ctrl+pq退出!
如何让他们之间通信呢?
方法1:使用connect方法给vm2添加一块mynet1网卡
docker network connect mynet1 vm2
docker attach vm2
ip addr #我们可以看到这里多出了一个网卡eth1@if81,ip为172.20.0.3
ping vm1 #可以通信
方法2:joined
在容器创建时使用–network container:vm1指定(vm1指在运行的容器)
container模式指定新创建的容器和已经存在的一个容器共享一个Network Namespace,而不是和宿主机共享。新创建的容器不会创建自己的网卡,配置自己的IP,而是和一个指定的容器共享IP、端口范围等。同样,两个容器除了网络方面,其他的如文件系统、进程列表等还是隔离的。两个容器的进程可以通过lo网卡设备通信。
docker run -it --name vm3 --network container:vm1 busybox
ip addr
可以发现,和vm1网络完全一致,使用相同的网络name space:
方法3:使用–link连接容器
格式:–link :alias
name和id是源容器的,alias是源容器在link下的别名
首先删除之前使用的容器
运行容器vm1:
docker run -d --name vm1 nginx
使用link连接vm1,别名为nginx:
docker run -it --link vm1:nginx busybox
env #可以看到ip为172.17.0.2
ping vm1
ping nginx
此时我们再开一个shell:
docker inspect vm1 | grep IPAddress
查看到vm1的ip为172.17.0.2,说明nginx连接vm1时使用的是vm1的网络资源
docker stop vm1
停掉以后我们在link连接的nginx中ping不通nginx和vm1了:
docker start vm1
启动vm1后又可以ping通了,说明网络又可以用了:
停掉vm1并且创建容器vm2:
docker stop vm1
docker run -d --name vm2 nginx
docker inspect vm2 | grep IPAddress
可以看到vm2占用了vm1之前使用的ip地址:
此时再启动vm1:
docker start vm1
可以看到nginx中的ip地址也随着vm1的改变而改变:
但是env查看变量,这里面的ip地址没有改变
4.容器是如何访问外网的?
容器与外网通信是通过iptables的SNAT实现的:
容器发送数据包到外网时,通过地址伪装
iptables -t nat -nL
在postrouting中进行伪装
5.外网是如何访问容器的?
是通过端口映射访问的-p host端口:容器内端口
docker run -d --name web -p 80:80 nginx
iptables -t nat -nL
外网访问容器使用到了docker-proxy和iptables的DNAT:
- 宿主机host访问本机容器使用的是iptables DNAT
- 外部主机访问容器内部或者容器之间的访问是docker-proxy实现的
netstat -antlp | grep 80
ps ax | grep docker-proxy
6.Docker跨主机的容器网络
docker跨主机的网络方式:
docker原生的overlay和macvlan
第三方的flannel、weave、calico
如何与docker集成?
使用libnetwork:docker网络库
CNM(container network model)模型:对容器网络进行抽象
CNM(container network model)模型
三类组件 | 作用 |
---|---|
Sandbox | 容器网络栈(namespace) |
Endpoint | 将sandbox接入network(veth) |
Network | 包含一组endpoint,同一network的endpoint可以通信 |
使用macvlan实现Docker容器跨主机网络
macvlan特点:
- 使用linux内核提供的一种网卡虚拟化技术
- 性能好:因为无需使用桥接,直接使用宿主机物理网口。
具体步骤:
step1 为各自docker宿主机各添加一块网卡,打开网卡混杂模式:
scp /etc/sysconfig/network-scripts/ifcfg-eth0 /etc/sysconfig/network-scripts/ifcfg-eth1
vim /etc/sysconfig/network-scripts/ifcfg-eth1
BOOTPROTO=none
DEVICE=eth1
ONBOOT=yes
scp /etc/sysconfig/network-scripts/ifcfg-eth1 server2:/etc/sysconfig/network-scripts/ifcfg-eth1
在server1和server2中:
ifup eth1 #激活网卡:
ip link set eth1 promisc on #在eth1网卡上打开混杂模式(开启子接口,会有多个ip地址)
step2 在两台docker宿主机各自创建macvlan网络:
在宿主机server1上:
建立macvlan的docker网络:
docker network create -d macvlan --subnet 172.20.0.0/24 --gateway 172.20.0.1 -o parent=eth1 mynet1
docker network ls
运行一个基于该网络模式的容器:
docker run -it --rm --name vm1 --network mynet1 busybox
ip addr
在宿主机server2上:
建立macvlan的docker网络:
docker network create -d macvlan --subnet 172.20.0.0/24 --gateway 172.20.0.1 -o parent=eth1 mynet1
运行一个基于该网络模式的容器:
docker run -it --name vm2 --rm --network mynet1 --ip 172.20.0.100 busybox
ip addr
测试:
ping 172.20.0.2
分析macvlan
- 网络结构:没有新建桥接,容器的接口直接与宿主机网卡连接,不需要NAT或者端口映射
brctl show
macvlan会独占网卡,但是可以使用vlan的子接口实现多macvlan网络,最多可以将物理二层网络划分成4094各逻辑网络,且彼此隔离。
解决macvlan独占宿主机网卡:
docker network create -d macvlan --subnet 172.21.0.0/24 --gateway 172.21.0.1 -o parent=eth1.1 mynet2
macvlan网络在物理网络二层是隔离的,无法直接通信,但是可以在三层上(即容器内部)通过网关将macvlan网络连通起来。