目录
- Docker 网络
- 网络类型
- none 网络
- host 网络
- bridge 网络
- 自定义网络
- 容器间通信
- IP 通信
- Docker DNS Server
- joined 容器
- 容器与外部通信
- 容器访问外部
- 外部访问容器
Docker 网络
- 学习Docker提供的几种原生网络
- 如何创建自定义网络
- 容器间通信,容器于外界交互
Docker 安装时会自动在 host 上创建三个网络,查看:
docker network ls
# bridge host none
网络类型
none 网络
none网络就是什么都没有的网络。挂在这个网络下的容器除了lo,没有其他任何网卡。容器创建时,可以通过 --network=none指定使用none网络
使用场景:封闭隔离,一些对安全性要求高并且不需要联网的应用可以使用,比如某个容器的唯一用途是生成随机密码,就可以放到none网络中避免密码被窃取。
host 网络
连接到 host 网络的容器共享 Docker host 的网络,容器的网络配置与 host 完全一样。通过 --network=host 指定使用 host 网络。
docker run -it --network=host httpdip l # 查看
- 直接使用Docker host的网络最大的好处就是性能,如果容器对网络传输效率有较高要求,则可以选择host网络。
- 这样就牺牲一些灵活性,比如要考虑端口冲突问题,Docker host上已经使用的端口就不能再用了。
- Docker host 的另一个用途是让容器可以直接配置host网路,比如某些跨host的网络解决方案,其本身也是以容器方式运行的,这些方案需要对网络进行配置,比如管理iptables
bridge 网络
Docker安装时会创建一个命名为 docker0 的 Linux bridge。如果不指定 --network,创建的容器默认都会挂到docker0上。
brctl showip a
创建容器后,会有一个新的网络接口(比如veth28)被挂到 docker0 上,这个接口就是新容器的虚拟网卡。
容器本身会有一个网卡(比如eth0@if34)。
实际上 eth0@if34 和 veth28 是一对 veth pair。veth pair是一种成对出现的特殊网络设备,可以把它们想象成由一根虚拟网线连接起来的一对网卡,网卡的一头(eth0@if34)在容器中,另一头(veth28)挂在网桥 docker0 上,其效果就是将 eth0@if34 也挂在了 docker0 上。
自定义网络
除了以上自动创建的网络,用户也可以根据业务需要创建user-defined网络。
Docker 提供三种 user-defined 网络驱动:bridge、overlay 和 macvlan。
通过 bridge 驱动创建类似前面默认的bridge网络:
docker network create --driver bridge my_net
docker network insepect my_net
# 自己指定网段: 在创建网段时指定 --subnet和 --gateway参数
docker network create --driver bridge --subnet 172.22.16.0/24 --gateway 172.22.16.1 my_net2
容器要使用新的网络,需要在启动时通过 --network指定:
docker run -it --network=my_net2 容器ID/Name
容器的IP都是 docker 自动从 subnet 中分配,指定静态 IP: 通过 --ip 指定(只有使用 --subnet 创建的网络才能指定静态 IP)。
docker run -it --network=my_net2 --ip 172.22.16.8 容器ID/Name
如果两个容器都挂在my_net2上,可以互通。所以,同一网络中的容器、网关之间都是可以通信的。
my_net2与默认bridge网络能通信吗?
两个网络属于不同的网桥,是不能通信的。
host上对每个网络都有一条路由,同时操作系统上打开了ip forwarding, host就成了一个路由器,挂接在不同网桥上的网络就能够相互通信。
# 查看 host 上的路由表
ip r
# 查看 ip forwarding:
sysctl net.ipv4.ip_forward # net.ipv4.ip_forward = 1 表示已经启动
# 查看 iptables
iptables-save
# -A DOCKER-ISOLATION -i br-5d863e9f78b6-o docker0-j DROP -A DOCKER-ISOLATION -i docker0-o br-5d863e9f78b6-j DROPiptables
# DROP掉了网桥docker0与br-5d863e9f78b6之间双向的流量# 为容器添加一块net_my2的网卡,就能互通。这个可以通过docker network connect命令实现
docker network connect my_net2 容器ID
容器间通信
IP 通信
从前面的例子可以得出这样一个结论:两个容器要能通信,必须要有属于同一个网络的网卡。
满足这个条件后,容器就可以通过 IP 交互了。具体做法是在容器创建时通过 --network 指定相应的网络,或者通过 docker network connect将现有容器加入到指定网络。
Docker DNS Server
通过 IP 访问容器虽然满足了通信的需求,但还是不够灵活。因为在部署应用之前可能无法确定 IP,部署之后再指定要访问的 IP 会比较麻烦。这个问题,可以通过 docker 自带的 DNS 服务解决。
docker daemon 实现了一个内嵌的 DNS server,使容器可以直接通过“容器名”通信。只要在启动时用 --name 为容器命名就可以了。
使用 docker DNS 有个限制:只能在 user-defined 网络中使用。默认的 bridge 网络是无法使用 DNS 的。
docker run -it --network=my_net2 --name=bbox1 busybox
docker run -it --network=my_net2 --name=bbox2 busyboxping -c 3 bbox1
joined 容器
joined 容器是另一种实现容器间通信的方式。
joined 容器非常特别,它可以使两个或多个容器共享一个网络栈,共享网卡和配置信息,joined 容器之间可以通过 127.0.0.1 直接通信。
使用场景:
- 不同容器中的程序希望通过 loopback 高效快速地通信,比如 Web Server 与 App Server。
- 希望监控其他容器的网络流量,比如运行在独立容器中的网络监控程序。
# 创建web1容器
docker run -d -it --name=web1 httpd
# 创建busybox容器并通过 --network=container:web1指定joined容器为web1
docker run -it --network=container:web1 busybox
busybox 和 web1 的网卡 mac 地址与 IP 完全一样,它们共享了相同的网络栈。busybox 可以直接用 127.0.0.1 访问 web1 的 http 服务。
容器与外部通信
容器访问外部
如果 docker host 可以访问外网,容器默认就能访问外网(这里外网指的是容器网络以外的网络环境,并非特指Internet)。
通过NAT, docker实现了容器对外网的访问。
# host测试
ping -c 3 www.baidu.com
# 登录容器测试
docker run -it busybox
ping -c 3 www.baidu.com
busybox 位于 docker0 这个私有bridge网络中(172.17.0.0/16),当 busybox 从容器向外 ping 时,数据包是怎样到达bing.com的呢?
这里的关键就是NAT。docker host上的iptables规则:
iptables -t nat -S
# -A POSTROUTING -s 172.17.0.0/16 ! -o docker0-j MASQUERADE
# 如果网桥docker0收到来自172.17.0.0/16网段的外出包,把它交给MASQUERADE处理。而MASQUERADE的处理方式是将包的源地址替换成host的地址发送出去,即做了一次网络地址转换(NAT)。
网络地址转换(NAT):是一种在IP网络中用于重用地址空间并隐藏内部网络结构的技术。
解决IPv4地址短缺问题,同时提高内网安全性,通过单一或少量的公共IP地址代表整个内部网络与外部通信。
内到外:当内部设备访问外部网络时,NAT设备(通常是路由器或防火墙)将源IP地址从私有地址转换为公有地址。
外到内:响应数据包到达NAT设备时,根据转换表将目标地址从公有地址转换回相应的私有地址。
端口地址转换(PAT):最常用的NAT形式,多个内部设备可以共享一个公有IP地址,通过不同的端口号区分不同的连接。
类型:
静态NAT:一对一映射,每个内部IP地址永久映射到一个特定的外部IP地址,适用于需要直接从外部访问的服务器。
动态NAT:内部私有地址池与外部公有地址池之间进行一对一临时映射,每次连接时分配,连接结束后释放。
端口地址转换(PAT):一对多映射,多个内部IP地址通过单一或少量公有IP地址的不同端口对外通信,是最节省IP地址的方式。
通过tcpdump查看地址是如何转换的。先查看docker host的路由表:
# 查看 default 默认路由通过enp0s3发出去,所以我们要同时监控enp0s3和docker0上的icmp(ping)数据包
ip rtcpdump -i docker0 -n icmp
docker0收到busybox的ping包,源地址为容器IP 172.17.0.2,这没问题,交给MASQUERADE处理。这时,在enp0s3上我们看到了变化:
# ping包的源地址变成了enp0s3的IP 10.0.2.15
tcpdump -i enp0s3 -n icmp
这就是iptable NAT规则处理的结果,从而保证数据包能够到达外网:
- busybox 发送ping包:172.17.0.2 > www.baidu.com。
- docker0 收到包,发现是发送到外网的,交给NAT处理。
- NAT将源地址换成 enp0s3 的IP:10.0.2.15 > www.baidu.com。
- ping包从 enp0s3 发送出去,到达 www.baidu.com。
外部访问容器
外网访问容器:通过端口映射
docker可将容器对外提供服务的端口映射到host的某个端口,外网通过该端口访问容器。容器启动时通过-p参数映射端口:
docker run -d -p 80 httpd
容器启动后,可通过docker ps或者docker port查看到host映射的端口。在上面的例子中,httpd容器的80端口被映射到host 32773上,这样就可以通过:<32773>访问容器的Web服务了
除了映射动态端口,也可在 -p中指定映射到host某个特定端口,例如可将80端口映射到host的8080端口
docker run -d -p 8080:80 httpd
curl hostId:8080
每一个映射的端口,host都会启动一个docker-proxy进程来处理访问容器的流量,host 查看:
ps -ef | grep docker-proxy
过程是这样的:
- docker-proxy监听host的32773端口
- 当curl访问10.0.2.15:32773时,docker-proxy转发给容器172.17.0.2:80
- httpd容器响应请求并返回结果