在docker中,几个容器间的互相通信是通过docker自带的网络就行通讯的。
docker网络类型分为三种,分别是:none(无网络)、bridge(桥接)和host(仅主机)。
通过以下命令可以查看
docker network ls
none网络指无网络
也就是挂载在none下的容器没有任何网卡,可以通过以下命令指定使用none网络
docker run -itd --network=none nginx
none网络的实际应用:
封闭意味着隔离,一些对安全性要求高并且不需要联网的应用可以使用none网络。
比如某个容器的唯一用途是生成随机密码,就可以放到none网络中避免密码被窃取。
host指仅主机,使用host网络的容器会和宿主机共享网络空间
在host网络下,由于docker容器会和宿主机公用网络空间,即公用网卡,因此IP会和宿主机一致。
因此在host网络下会出现端口冲突的问题,需要docker容器向外进行端口映射避开宿主机中已经使用的端口。
可以通过-network-host 指定使用host网络
docker run -itd --network=host nginx
bridge指桥接,在Docker安装时会创建一个命名为docker0的Linux bridge。如果不知道-network,创建的容器都会挂到docker0上。
docker创建容器时如果不指定网络连接方式,默认使用bridge连接。
网桥可以简单理解为两台网络设备相互通讯的桥梁:
而在docker中的网桥便是docker0
通过以下命令查看:
brctl show
此时该网桥下没有挂载任何容器,因此interfaces处为空
现在向docker0网桥下挂载一个容器后,再查看该网桥
[root@localhost ~]# docker run -itd --name=busybox --network=bridge busybox
b9edeff14d7d8531196a2315b62b488544d160b8b57f8a39f8320b4321386730
[root@localhost ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b9edeff14d7d busybox "sh" 5 seconds ago Up 4 seconds busybox
[root@localhost ~]# brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.0242348d6acb no veth3b29978
virbr0 8000.52540050602f yes virbr0-nic
可以看到此时interfaces处出现一个叫veth3b29978
的网卡,该网卡就是新创建容器的虚拟网卡
再查看当前容器的信息
此时Gateway是docker网桥的ip,IPAddress是子容器的ip。在宿主机中向子容器中ping是可用ping得通的。
此时再进子容器中查看子容器中查看网卡
[root@localhost ~]# docker exec -it busybox sh
可以看到子容器中是有分配ip的。
ps:为什么在宿主机中网卡叫veth3b29978
,而在容器内网卡叫eth0
?
实际上eth0@if34 和veth28c57df 是一对veth pair。 veth pair 是一种成对出现的特殊网络设备,可以把它们想象成由一根虚拟网线连接起来的一对网卡, 网卡的一头(eth0@if34) 在容器中,另一头(veth28c57df) 挂在网桥docker0 上,其效果就是将eth0@if34 也挂在了docker0上。
参考:Docker网络(host、bridge、none)详细介绍_docker network none_南宫乘风的博客-CSDN博客
除了none、host、bridge网络外,docker支持用户自定义user-defined网络。
dockers提供了三种user-defined的网络驱动:bridge、overlay、macvian。
查看所有网络:
docker network ls
创建网络:
docker network create [--参数 参数值] 网络名称
--driver
:指定网络驱动:bridge、overlay、macvian(默认 “bridge”)。--subnet
:指定子网网段(如192.168.0.0/16、172.88.0.0/24)--ip-range
:执行容器的IP范围,格式同subnet参数--gateway
:子网的IPv4 或 IPv6网关,如(192.168.0.1)删除网络:
docker network rm 网络名称 [网络名称...]
查看一个或多个网络的详细信息:
docker network inspect [--参数 参数值] 网络名称 [网络名称...]
docker inspect [--参数 参数值] 网络名称 [网络名称...]
-f, --format
:根据format输出结果为启动的容器指定网络模式:
docker run/create --network 网络名称
网络连接或断开:
# 连接网络
docker network connect [--参数 参数值] 网络名称 容器名称
# 断开网络
docker network disconnect [--参数 参数值] 网络名称 容器名称
-f, --force
:强制断开连接(用于disconnect)现在,为了方便管理容器,我们需要自定义一个网络。
首先,我们需要新增一个网桥,其作用与docker0作用类似
[root@localhost ~]# docker network create my_net
9edae79796cfd1277064c62d421f438b3caf935e8d9f1ee4fa6dbbab64df6bcf
默认驱动使用bridge
ps:创建时可能会报错:
[root@localhost ~]# docker network create --driver bridge my_net
Error response from daemon: Failed to Setup IP tables: Unable to enable SKIP DNAT rule: (iptables failed: iptables --wait -t nat -I DOCKER -i br-2913d0b63faa -j RETURN: iptables: No chain/target/match by that name.
(exit status 1))
此错误说明在docker开启时,防火墙状态发生了改变。要么把防火墙状态还原,要么重启docker。
这里我选择重启docker:
[root@localhost ~]# systemctl restart docker.service
此时再查看当前网络可以看到刚刚创建的网络:
[root@localhost ~]# docker network ls
通过brctl show
也可以看到刚刚配置的网桥:
[root@localhost ~]# brctl show
其中的br-9edae79796cf
是my_net网桥的"br-" + 网桥id
容器要使用新的网络,需要在启动时通过–network指定
[root@localhost ~]# docker run -itd --network=my_net --name=busybox1 busybox
b12e22c6f51c4edba9c2defcf07d8afb37971f4199dccde82f0dfaa90425ec73
[root@localhost ~]# docker run -itd --network=my_net --name=busybox2 busybox
2683f81188f497eed40a2388716c9626ebe81dbb41d315b1e60e156186c81814
[root@localhost ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
2683f81188f4 busybox "sh" 3 seconds ago Up 2 seconds busybox2
b12e22c6f51c busybox "sh" 6 seconds ago Up 5 seconds busybox1
此时容器网络的拓扑结构为:
此时busybox1与busybox2之间互ping是可以通的。
此时再创建一个网桥my_net1
和容器busybox3
:(目的:让busybox2可以同时连通my_net下的busybox1和my_net1下的busybox3)
先创建my_net1网桥:
[root@localhost ~]# docker network create --driver bridge --subnet 172.20.0.0/16 my_net1
321dbffb10c2e87935baa158841d07cbd320b4a46da4d046ef707f3738d275f1
--driver bridge
:驱动使用bridge--subnet 172.20.0.0/16
:网段在172.20.0.0/16,除去172.20.0.0(广播地址)和172.20.0.1(路由地址)外,子容器取值在当前网段下2~16之内。此时可以看到多出的网络
再创建容器busybox3,将其挂载在my_net1下:
[root@localhost ~]# docker run -itd --network=my_net1 --name=busybox3 busybox
2b110f97c029d3029a48ab968ac19148c1bd9519dbcc0e2b23aaf73f376c249c
此时可以看到当前容器的IP为:
[root@localhost ~]# docker inspect busybox3
此时的网络拓扑结构为:
此时,如果想要busybox2只能通讯busybox1,因为他俩在同一网段下,但是busybox2不能和busybox3通讯。
如果需要将busybox2连通busybox3,需要将busybox2接入my_net1,指令:docker network connect [--参数 参数值] 网络名称 容器名称
[root@localhost ~]# docker network connect my_net1 busybox2
此时通过docker inspect busybox2
可以查看到当前容器的网卡
进入busybox3容器下验证是否能ping成功:
[root@localhost ~]# docker exec -it busybox2 sh[root@localhost ~]# docker exec -it busybox2 sh
/ # ping 172.18.0.2
PING 172.18.0.2 (172.18.0.2): 56 data bytes
64 bytes from 172.18.0.2: seq=0 ttl=64 time=0.248 ms
64 bytes from 172.18.0.2: seq=1 ttl=64 time=0.102 ms
^C
--- 172.18.0.2 ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 0.102/0.175/0.248 ms
/ # ping 172.20.0.2
PING 172.20.0.2 (172.20.0.2): 56 data bytes
64 bytes from 172.20.0.2: seq=0 ttl=64 time=0.199 ms
64 bytes from 172.20.0.2: seq=1 ttl=64 time=0.083 ms
^C
--- 172.20.0.2 ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
显示能ping成功
此时的拓扑结构为:
docker在默认情况下可以向外部网络环境进行通讯,同时也可以和同属于当前网段的其他容器进行通讯。
Docker DNS Server
通过IP访问容器虽然满足了通信的需求,但还是不够灵活。因为在部署应用之前可能无法确定IP,部署之后再指定要访问的IP会比较麻烦。对于这个问题,可以通过docker自带的DNS服务解决。
从Docker 1.10版本开始,docker daemon实现了一个内嵌的DNS server,使容器可以直接1通过“容器名”通信。方法很简单,只要在启动时用-name为容器命名就可以了。
使用docker DNS有个限制:只能在user-defined网络中使用。也就是说,默认的bridge网络是无法使用DNS的。
docker容器在默认情况下,是可以向外通讯的。
先新建一个容器:
[root@localhost ~]# docker run -itd --name busybox1 busybox
778c05a4574d1e9842346fb246e16e7b0bda8a96b716c3bf3c67050f8f6bbfea
进入容器:
[root@localhost ~]# docker exec -it busybox1 sh
ping百度:
[root@localhost ~]# docker exec -it busybox1 sh
/ # ping www.baidu.com
PING www.baidu.com (14.119.104.254): 56 data bytes
64 bytes from 14.119.104.254: seq=0 ttl=127 time=26.366 ms
但是,通过ifconfig可以看到,当前的容器ip是172.17.0.2。
/ # ifconfig
eth0 Link encap:Ethernet HWaddr 02:42:AC:11:00:02
inet addr:172.17.0.2 Bcast:172.17.255.255 Mask:255.255.0.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:8 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:656 (656.0 B) TX bytes:0 (0.0 B)
该ip是由docker0网桥进行发放的,而docker0并不会直接连接外部网络环境。
解释:
如果内部容器想要与外部网络环境进行交互,光是通过自己的网段是肯定不行的,数据包应该是通过宿主机交上去的,所有从子容器到宿主机时一定做了一次IP转换,即NAT。
nat:是将 IP 数据报报头中的 IP 地址转换为另一个 IP 地址的过程,主要用于实现内部网络(私有 IP 地址)访问外部网络(公有 IP 地址)的功能。
先通过以下指令查看nat:
iptables -t nat -S
其含义是:如果网桥docker0收到来自172.17.0.0/16网段的外出包,把它交给MASQUERADE处理。而且MASQUERADE的处理方式是将包的原地址替换成host的地址发送出去,宿主机做了一次网络地址转换(NAT)
知道了路由转发的规则之后,需要通过以下指令查看路由表,查看当前的默认路由,因为数据包会通过默认路由向外发送。相当于机器的大门。看住大门,就能知道进出的数据包都有哪些。
ip r
路由表:简单点说路由表就是路由器用于指导数据包如何转发的表项,记录了去往目的IP的下一跳去哪里。
目的地 协议 优先级 开销 下一跳 出接口 192.2.0 Static 60 0 10.1.1.2 Ethernet0/0/0 路由表的作用类似于我们生活中的地图,指引我们去往一个目的地该如何走?
推荐视频:路由器是如何路由的?(上集)_哔哩哔哩_bilibili
默认路由通过ens33发出去,所以我们同时要监控ens33和docker0上的icmp(ping)的数据包。
首先先进入busybox1容器后通过以下命令进行抓包:
tcpdump -i 网卡名称 -n icmp
此时是:172.17.0.2 ==> www.baidu.com
。
但再对宿主机的网卡进行抓包时:
发现此时是通过宿主机的ip发送到baidu.com的。即:192.168.137.128 ==> www.baidu.com
。
这里ping出去的包源变成了192.168.137.128。
用图表示以上过程:
容器访问外部网络可以通过NAT做转发,但是外部网络访问docker,需要进行端口映射。
docker可以将对外提供服务的端口映射到宿主机的某个端口,外网通过该端口访问容器。
启动时需要通过-p参数映射端口
[root@localhost ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
busybox latest 8135583d97fe 41 hours ago 4.86MB
nginx latest 448a08f1d2f9 2 weeks ago 142MB
[root@localhost ~]# docker run -itd -p 80 --name web nginx
129690c906b777a120ab148fcfe343c79ea7b3b9010a3114b55a6f2863f9aec9
此时通过docker ps -a
可以查看当前容器所映射的端口
映射到外部的端口是32768,访问时需要访问宿主机ip:32768
,即:192.168.137.128:32768
除了映射动态端口,也可在-p中指定映射到host某个特定端口,例如可将80端口映射到host的8080端口
[root@localhost ~]# docker run -itd --name web1 -p 80:80 nginx
75d46481983abbfe4e8b66b57359daaa8901e6569bfa69dfc54fd07f4d497165
此时直接访问192.168.137.128:80
也可以访问到nginx的页面。
图示:
参考:Docker容器访问外部世界_南宫乘风的博客-CSDN博客