Docker之网络通信
一、引言
Docker 安装完成之后会使用 Linux桥接 在宿主机虚拟一个Docker容器网桥( docker0
),该网桥是每个容器的默认网关,容器会从此网段获得IP地址,称为 container-ip
,容器之间能够通过container-ip
直接通信
由于 docker0
网卡是虚拟出来的,所以外部网络无法直接进行通讯,只能通过端口映射来进行访问容器,即通过 -p, -P
参数来启用
二、正文
有四种网络模式
(一)网络模式
网络模式 | 配置 | 说明 |
---|---|---|
Bridge | --net=bridge |
桥接模式,默认 |
Host | --net=host |
主机模式,容器和宿主机共享网络 |
Container | --net=容器:NAME/ID |
和指定容器共享网络 |
None | --net=none |
无网络模式 |
查看网络
1 | $ docker network ls |
Bridge 模式
该模式是 Docker 的 默认网络 设置,当 Docker 服务启动时,会在主机上创建一个名为 docker0
的虚拟网桥,并分配 172.17.0.0/16
网段分配给 docker0
网桥,默认创建的容器也是连接到这个虚拟网桥上
特点
- 默认为每个容器分配单独的网络空间,彼此相互隔离
- 每个容器都单独的网卡、路由、IP等一些列基本的网络设施
- 每个容器启动后,都会被分配一个独立的虚拟IP
- 该模式会自动,将宿主机上的所有容器,都链接到
ip a
看到的docker0
的虚拟网卡上 - 外界主机不能直接访问宿主机内的容器服务,需要宿主机通过
-p
做端口映射后访问宿主的映射端口
查看端口
1 | $ ip add | grep -A 10 docker0 |
拓扑图
演示
指定网络启动
1 | docker run -itd --net=bridge --name docker1 centos |
查看容器网络
1 | docker exec -it docker1 ip a |
Host 模式
该模式下容器与宿主机在同一个 Network Namespace
网络空间,使用宿主机的IP,容器内部的服务端口也可以使用宿主机的端口,不需要进行 NAT
,不会虚拟出自己的网卡,配置自己的IP等
这种模式的好处就是网络性能比桥接模式的好,点就是会占用宿主机的端口,网络的隔离性不好
特点
- 没有独立的网络空间
- 完全和宿主机共用一个网络空间(端口、IP等),所以该模式下的容器不会虚拟出容器自身的虚拟网卡,也不会配置自己的虚拟 IP
- 除了网络和宿主机共享,其他的资源,如文件系统、进程列表等,容器之间依然是相互隔离的
缺点
同一个端口,比如宿主机的 8080
端口,只能被一个服务占用,如果被某个容器占用了,宿主机就不能用,后续的容器也不能用,直到优先抢到 80
端口的服务,停止提供服务(放弃80端口)
拓扑图
演示
启动容器
1 | docker run -it -d --net=host --name=Nginx1 nginx |
测试
1 | $ curl --head 192.168.6.128 |
Container 模式
这个模式指定新创建的容器和已经存在的一个容器共享同个 Network Namespace
网络空间,但不会自动创建网卡、配置IP,而是和指定的容器共享 IP、端口范围等,容器之间可以通过 lo
网卡设备通信
特点
- 新创建的容器,仅同前面已存在的容器,共享网络空间,不与宿主机共享网络
- 新创建的容器,不会有自己的虚拟网卡和IP,后面新创建容器的网络资源用的是上一个容器的
- 新创建的容器,仅仅是网络和第一个容器共享,其他资源彼此还都是相互隔离的
缺点
第一个指定容器服务停止,则后续的容器也没有办法继续运行
拓扑图
None 模式
该模式关闭了容器的网络功能,容器会有自己的 Network Namespace
网络空间,但是容器不会配置网络,如IP、路由等信息,需要手动添加
等于断网的状态,经常用于测试,生产环境一般不会用到这种
特点
- 容器拥有自己的网络空间,但不会进行任何网络配置
- 容器没有网卡、IP、路由等信息,需要手动添加网卡、配置IP等
拓扑图
演示
启动容器
1 | docker run -it -d --net none --name Docker1 centos |
查看网络配置
1 | docker exec -it Docker1 ip a |
可见没有网络配置
1 | $ docker exec -it Docker1 ip a |
(二)自定义网络
自定义网络是可以用 ip 或者容器名相互 ping
通的,相当于修复了 docker0
的容器之间无法用容器名相互 ping
通的问题,只能使用 ip 才能 ping
通
创建网络
创建网络
1 | docker network create --driver bridge --subnet 192.168.0.0/16 --gateway 192.168.0.1 mynet |
选项 | 注释 |
---|---|
–bridge | 网络模式桥连接 |
–subnet | 子网络网段 |
–gateway | 路由 |
–mynet | 网络名 |
查看
1 | $ docker network ls |
查看网络信息
1 | $ docker network inspect mynet |
在自己的网段里创建两个容器
创建两个容器
1 | docker run -it -d --name centos01 --net mynet centos |
查看自定义网络信息
1 | docker network inspect mynet |
可见我们的容器在自己创建的网络当中
1 | [ |
相互使用容器名ping测试
互ping
1 | docker exec -it centos01 ping centos02 |
发现是可以 ping
通的
1 | $ docker exec -it centos01 ping centos02 |
发现是可以 ping
通的
1 | $ docker exec -it centos02 ping centos01 |
跨网段
那如果在创建一个容器不在该网段内如何能和该网段内的容器进行ping吗?
没有加 -net
默认是走 docker0
的网段
1 | docker run -it -d --name centos03 centos |
互ping
1 | docker exec -it centos03 ping centos04 |
互ping不通
1 | $ docker exec -it centos03 ping centos04 |
跨网络也是ping不通
1 | $ docker exec -it centos03 ping centos01 |
结论
- 在 Docker 中直接
run
创建容器时不加自定义的网络,是 无法使用容器名
相互ping通的,只能通过IP(通过docker0
的桥连接相互通信) - 我们自己定义好的网络在创建容器时,是可以直接通过容器名相互ping通的
让容器和网络链接
1 | docker network connect mynet centos03 |
链接完成之后再去ping
1 | docker exec -it centos03 ping centos01 |
发现可以ping通了,这是为什么呢?
1 | $ docker exec -it centos03 ping centos01 |
原因:一个容器两个ip
1 | docker network inspect mynet |
这里可以看到 centos03
加入到了 mynet
的网络中了
查看 centos03 容器详细信息
1 | docker inspect centos03 |
自定义网桥与默认网桥的区别
用户自定义桥可以在容器之间提供自动DNS解析【DNS resolution 】能力。
默认网桥网络上的容器只能通过IP地址相互访问,除非您使用
--link
选项,但不推荐。在自定义网桥网络中,容器之间可以通过名称或别名进行解析用户自定义桥提供了更好的隔离
- 所有没有指定
--network
的容器都附加到默认网桥网络。这可能是一个风险,因为不相关的堆栈/服务/容器可以进行通信。 - 使用用户定义的网桥提供了一个有作用域的网络,其中只有连接到该网络的容器才能进行通信
- 所有没有指定
容器可以动态地连接或断开与用户定义的网桥的连接
- 在容器的生命周期内,可以动态地连接或断开它与用户定义的网桥的连接。
- 从默认网桥网络中删除容器,需要停止容器并使用不同的网络选项重新创建它。
每个用户定义的网桥都会创建一个可配置的网桥,默认网桥的配置是全局的
- 如果容器使用默认网桥网络,则可以配置它,但所有容器都使用相同的设置,例如MTU和iptables规则。此外,配置默认网桥网络发生在Docker本身之外,并且需要重新启动Docker。
- 用户自定义网桥网络使用
docker network create
创建和配置。如果不同的应用程序组有不同的网络需求,可以在创建桥接时分别配置每个用户定义的桥接。
连接到默认网桥上的所有容器共享环境变量
最初在两个容器之间共享环境变量的唯一方法是使用
1
--link
标志将它们链接起来。这种类型的变量共享在用户定义的网络中是不可能的。但是,有更好的方法来共享环境变量,如下:
- 多个容器可以挂载包含共享信息的文件或目录
- 多个容器可以使用docker-compose一起启动,并且compose文件可以定义共享变量
host
对于独立容器,移除容器和Docker主机之间的网络隔离,直接使用主机的网络。
- 如果容器使用主机网络模式,该容器的网络堆栈
不会与Docker宿主机隔离
(容器共享主机的网络命名空间),并且容器不会分配自己的ip地址
。例如,如果您运行一个绑定到端口80的容器,并且使用主机网络,则容器的应用程序在主机IP地址的端口80上可用。 port-mapping
不生效,-p
、--publish
、-P
、--publish-all
选项被忽略,并产生一个告警WARNING: Published ports are discarded when using host network mode
- 主机模式网络对于
优化性能
非常有用, 在容器需要处理大量端口
的情况下,因为它不需要网络地址转换(NAT),并且没有为每个端口创建“用户空间-代理”。 - 该主机网络驱动程序
仅适用于Linux主机
,不支持Mac、Windows和Windows Server的Docker Desktop
使用命令如下:docker run --rm -d --network host --name my_nginx nginx
overlay
在多个Docker守护进程主机之间创建分布式网络。
该网络位于特定主机网络之上(overlays),允许连接到它的容器在启用加密时安全通信。Docker透明地
处理每个数据包往返于正确的Docker守护进程主机和正确的目标容器间的路由
。
overlay
网络可以实现如下容器间的通信,这种策略消除了在这些容器之间进行操作系统级路由的需要。
- 将多个Docker守护进程连接在一起,使
swarm services
能够相互通信 - 在
swarm services
和独立容器之间通信 - 在不同Docker守护进程上的两个独立容器之间通信
组网方式
当初始化一个swarm或将一个Docker主机加入到一个现有的swarm时,在该Docker主机上创建了两个新的网络:
ingress
: 处理与swarm service 相关的控制和数据流量。创建swarm service 时,如果没有连接到自定义的overlay网络,默认连接到这个网络docker_gwbridge
- 将单个Docker守护进程连接到参与swarm的其他守护进程.
- docker_gwbridge是一个虚拟网桥,连接overlay网络(包括ingress网络)到单个Docker守护进程的物理网络。
- 它不是Docker设备。它存在于Docker主机的内核中。
- 如果需要对docker_gwbridge进行自定义设置,则必须在将Docker主机加入swarm之前或暂时从swarm中移除后进行。
创建overlay的必要条件
- 需要为参与overlay网络的每个Docker主机开放以下端口:
- TCP端口2377 用于集群管理通信
- TCP和UDP端口 7946 用于节点间通信
- UDP端口4789 用于覆盖网络流量
- 在创建overlay网络之前,需要使用
docker swarm init
将Docker守护进程初始化为swarm manager ,或者使用docker swarm join
将其加入现有的swarm 。其中任何一个都会创建默认的ingress
overlay网络,默认情况下由swarm service使用。即使您从未计划使用swarm services,也需要这样做。之后,可以创建额外的用户定义overlay网络。
常用操作命令
自定义ingress网络
1 | # 1.停止所有连接ingress的容器服务, |
- 自定义docker_gwbridge网络
1 | # 1. Stop Docker. |
【ipvlan】
1 | IPvlan`网络让用户完全控制`IPv4`和`IPv6`寻址【`addressing`】,`VLAN`驱动程序建立在此基础上,为用户提供对二层VLAN标记【`layer 2 VLAN tagging` 】的完全控制,甚至对底层网络集成感兴趣的用户提供 `IPvlan L3 routing |
IPvlan是经过验证的、真实的网络虚拟化技术的一个新转变。Linux实现非常轻量级,因为它们不是使用传统的Linux网桥进行隔离,而是与Linux以太网接口或子接口相关联,以加强网络之间的分离和到物理网络的连接。
IPvlan提供了许多独特的功能,并为各种模式的进一步创新提供了大量的空间,2个突出优点如下:
- 移除通常存在于Docker主机NIC【network interface controller】和容器接口之间的桥接,
- 留下一个由容器接口【container interfaces】组成的简单设置,直接连接到Docker主机接口。
1 | docker network create -d ipvlan \ |
【macvlan】
Macvlan网络允许为容器分配MAC地址,使其显示为网络上的物理设备。 Docker守护进程通过容器的MAC地址将流量路由到容器。
在处理希望直接
连接到物理网络
的遗留应用程序(而不是通过Docker主机的网络堆栈路由)时,使用macvlan驱动程序
有时是最佳选择
一些应用程序,特别是遗留应用程序或监视网络流量的应用程序,希望直接连接到物理网络, 可以使用macvlan
网络驱动程序为每个容器的虚拟网络接口分配MAC地址
,使其看起来像一个直接连接到物理网络的物理网络接口
。
使用macvlan
需要注意如下几点:
- 由于IP地址耗尽或“VLAN扩散”,很容易无意中损坏您的网络,在这种情况下,您的网络中有不适当的大量唯一MAC地址
- 网络设备需要能够处理“混杂模式”,即一个物理接口可以分配多个MAC地址
- 应用程序可以使用桥接(在单个Docker主机上)或overlay(在多个Docker主机上通信)工作,从长远来看,这些解决方案可能会更好。
1 | docker network create -d macvlan \ |
自定义网络
1 | version: "3" |
配置默认网络
1 | version: "3" |
指定一个已经存在的网络
1 | networks: |
解决macvlan网络容器与宿主机通讯
默认情况下各个macvlan之间可以通讯,但是不能与宿主机进行通讯!! 出现这种情况的原因是为了安全而禁止互通,如宿主机ping容器的ip,尽管他们属于同一网段,但是也是ping不通的,反过来也是。
【解决方案】:
可以在宿主机上建立一个macvlan链接,这样就可以通过宿主机上的macvlan与容器内部的macvlan进行连接,从而解决了宿主机与macvlan容器之间不能通讯的问题。
创建macvlan网络
1 | docker network create -d macvlan \ |
运行docker容器,macvlan网络模式
1 | docker run -d \ |
配置宿主机和容器通讯
- 宿主机创建一个macvlan网络,名称:
hostvlan
1 | ip link add hostvlan(macvlan名称) link ens33(物理网卡) type macvlan(macvlan驱动) mode bridge(桥接模式) |
- 设置
macvlan IP
并启用
1 | ip addr add 192.168.199.252/32(hostvlan IP) dev hostvlan |
- 增加路由表
1 | ip route add 192.168.199.120(需要建立通讯的macvlan容器IP) dev hostvlan(刚宿主机新建macvlan) |
到此宿主机和容器可以正常通信
三、Docker防火墙
(一)Docker容器之端口转发
docker容器firewalld端口转发规则
1、配置firewalld端口转发,要先打开端口转发,则需要先
1 | firewall-cmd --zone=public --add-masquerade --permanent |
2、然后转发22端口至3753
1 | firewalld-cmd --zone=public --add-forward-port=port22:proto=tcp:toport=3753 |
3、转发22端口数据至另一个ip的 相同端口上
1 | firewalld-cmd --zone=public --add-forward-port=port22:proto=tcp:toport=192.168.199.120 |
4、转发22端口数据至另一个ip的3753端口上
1 | firewalld-cmd --zone=public --add-forward-port=port22:proto=tcp:toport=3753:toaddr=192.168.199.120 |
(二)Docker与Firewalld冲突怎么办
第一种方式:按顺序重启firewalld与docker
仅限于利用firewalld限制docker映射之外的端口,如果是docker使用了该端口,则firewalld配置无效,如果想要对docker使用的端口进行限制,这种方式是不合适的。当启动firewalld出现冲突的时候,首先重启firewalld,然后重启docker,注意顺序不可以反过来。这是最垃圾的一种方式,当然如果它能解决燃眉之急的话也不错。
第二种方式:在DOCKER-USERchain中添加规则
在DOCKER-USER
链中添加的规则会先于DOCKER
链生效,因此在这个链中手动添加规则将会生效。这是最推荐的方式,没有之一。
1、不要尝试去手动修改
DOCKER
链,这样可能会使docker网络出现问题。
2、注意添加的规则要插在DOCKER-USER
链的顶部,默认情况下所有到达docker主机的连接都会被允许。
3、限制条件就是,如果端口不是在Docker中开放的,比如22端口,那么在DOCKER
链中插入规则不会生效,需要在INPUT
链中设置规则。
几个示例:
1 | ## 限制 192.168.3.0/24 网络防止3306端口 |
第三种方式:最新版本的Docker支持与firewalld共存
前提:Docker版本大于等于 20.10.0,
Docker在最新的版本里自动创建了一个名为docker
的 firewalld zone,并把它的所有网络接口(包括docker0)加入到了这个区域里面,执行下面的命令将你的docker0接口移到docker
区域。
1 | # Please substitute the appropriate zone and docker interface |
(三)常用规则
- 将访问192.168.1.123(本机)主机8080端口的请求转发至80端口
1 |
- 将访问192.168.1.123(本机)主机8080端口的请求转发至192.168.1.111的80端口
1 |
- 将本机的80端口转发至192.168.1.109 8080端口
1 |
- 允许IP 192.168.1.142访问本机器的6379端口
1 |
- 拒绝IP 192.168.1.142访问本机的22号端口
1 |
注:转发时,要开启伪装,伪装就是SNAT
允许IP伪装
1 | [root@localhost ~] |