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 | #firewall-cmd --permanent --zone=public --add-forward-port=port=8080:proto=tcp:toport=80:toaddr=192.168.1.123 |
- 将访问 192.168.1.123(本机)主机 8080 端口的请求转发至 192.168.1.111 的 80 端口
1 | #firewall-cmd --permanent --zone=public --add-forward-port=port=8080:proto=tcp:toaddr=192.168.1.111:toport=80 |
- 将本机的 80 端口转发至 192.168.1.109 8080 端口
1 | #firewall-cmd --permanent --zone=public --add-forward-port=port=80:proto=tcp:toport=8080:toaddr=192.168.1.109 |
- 允许 IP 192.168.1.142 访问本机器的 6379 端口
1 | #firewall-cmd --permanent --add-rich-rule="rule family="ipv4" source address="192.168.1.142" port protocol="tcp" port="6379" accept" |
- 拒绝 IP 192.168.1.142 访问本机的 22 号端口
1 | #firewall-cmd --permanent --add-rech-rule="rule family="ipv4" source address="192.168.1.142" port protocol="tcp" port="80" drop" |
注:转发时,要开启伪装,伪装就是 SNAT
允许 IP 伪装
1 | [root@localhost ~]# firewall-cmd --permanent --zone=public --add-masquerade |