了解docker通过桥接模式实现网络通信,并使用shell命令实现模拟docker网络环境的创建过程
一、docker是什么
- Docker本质是一个进程,
- 宿主机通过namespace隔离机制提供进程需要运行基础环境,并且通过Cgroup限制进程调用资源。
- Docker的隔离机制包括
- network隔离,此次主要探讨网络隔离
- mount隔离
- hostname隔离
- user隔离
- pid隔离
- 进程通信隔离
二、docker 网络模式
host
: 主机模式- 不会创建网络隔离机制,直接宿主机的网络。
bridge
: 默认桥接, 新建虚拟桥接网卡用于docker
容器之间的通信。- 桥接网卡通过
iptables
对分组进行转发到主网卡,通常使用主机外网IP与外部网络通信。 - 桥接网卡会关联多张
interface
网络接口。 实现内网通信。 interface
主要是通过veth pair
虚拟设备对,一端用于容器,一端用于绑定网桥。
- 桥接网卡通过
macvlan
: 为容器穿件一张虚拟网卡,该网卡用于独立的MAC地址和相同的网卡操作逻辑。- 相比
bridge
和hosts
模式。 即满足了网络隔离需求,也满足了和宿主机网络相同地位。
- 相比
ipvlan
: 需要要求内核版本 4.2+, 暂不复现。 可以通过ip link vlan
相关命令了解用法overlay
: 未复现,主要用于swarm
集群none
: 容器不具备网络通信能力,更多作为类似于脚本任务。
三、docker 网络通信
docker
主要通过iptables
规则进行报文转和过滤。
目标: 尝试理解docker
创建的iptables
行为。
1. 清空iptables
规则, 重启docker
并启动nginx容器
# 清空iptables规则,重启docker后,docker-proxy会自动更新iptables策略
[root@mking /]# iptables -F && iptables -t nat -F && systemctl restart docker
# 启动docker容器,并暴露网卡
[root@mking /]# docker network create demo
# 查看网卡信息,demo创建桥接的网卡名为: br-a397efbb3fb4
[root@mking /]# docker network ls
NETWORK ID NAME DRIVER SCOPE
be4c6d689139 bridge bridge local
a397efbb3fb4 demo bridge local
d843459f25fd host host local
7a3b4a1a7c00 none null local
# 启动docker容器,
[root@mking /]# docker run -itd --rm --name nginx --network demo -p 80:80 nginx
demo创建桥接的网卡名为br-a397efbb3fb4
, 后续对该网卡规则进行理解。
2. 查看nat
转发表信息
nat主要修改报文目标IP,目标端口和源P地址,源端口
[root@mking /]# iptables -t nat -nvL
Chain PREROUTING (policy ACCEPT 4 packets, 600 bytes)
pkts bytes target prot opt in out source destination
0 0 DOCKER all -- * * 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL
Chain INPUT (policy ACCEPT 4 packets, 600 bytes)
pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
0 0 DOCKER all -- * * 0.0.0.0/0 !127.0.0.0/8 ADDRTYPE match dst-type LOCAL
Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
0 0 MASQUERADE all -- * !docker0 10.0.254.0/24 0.0.0.0/0
0 0 MASQUERADE all -- * !br-a397efbb3fb4 10.2.254.0/24 0.0.0.0/0
0 0 MASQUERADE tcp -- * * 10.2.254.2 10.2.254.2 tcp dpt:80
Chain DOCKER (2 references)
pkts bytes target prot opt in out source destination
0 0 RETURN all -- docker0 * 0.0.0.0/0 0.0.0.0/0
0 0 RETURN all -- br-a397efbb3fb4 * 0.0.0.0/0 0.0.0.0/0
0 0 DNAT tcp -- !br-a397efbb3fb4 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:80 to:10.2.254.2:80
网桥 br-a397efbb3fb4
在nat
中处理逻辑。
- PREROUTING: 外部流量到达主机时处理,该链路主要动作是修改报文目的IP地址,通过DNAT动作实现请求转发
- 链路展示所有报文会通过
DOCKER CHAIN
链, 该链也恰好有处理DNAT
动作
- 链路展示所有报文会通过
- OUTPUT: 流量出去的时候,也会经过
DOCKER CHAIN
。 - DOCKER: 关于网桥
br-a397efbb3fb4
有两处处理。- RETURN处: 流量从网卡
br-a397efbb3fb4
进入的, 就不再继续向下匹配,也就不修改目标IP - DNAT: 流量从其他网卡进入且访问80端口的,通过修改了目的地址指向nginx容器的IP:
10.2.254.2
。
- RETURN处: 流量从网卡
- POSTROUTING: 内部流量从网卡出去时候处理, 该链路主要用于修改源IP地址,用于响应回源。
br-a397efbb3fb4
有2处处理- 第二行
- 命令:
iptables -t nat -A POSTROUTING ! -o br-a397efbb3fb4 -s 10.2.254.0/24 -j MASQUERADE
- 理解: 来自源IP网段
10.2.254.0/24
且流量不经过网卡br-a397efbb3fb4
出去的报文,修改动态源IP地址。MASQUERADE
动态获取IP地址,通常是网卡的IP地址。- 该规则也用于第一行的
docker0
。 说明这是docker创建网卡时的标准规则。
- 命令:
- 第三行:
- 命令:
iptables -t nat -A POSTROUTING -p tcp --dport 80 -s 10.2.254.2 -d 10.2.254.2 -j MASQUERADE
- 理解: 对源IP和目标IP都是
10.2.254.2
且端口是80的TCP发出的报文动态修改源IP地址。
- 命令:
- 第二行
场景1: DNAT转换后报文是如何到达nginx的
- 假设外部通过
eth0
访问80端口,先通过iptable
规则进行报文分析处理- 报文的入口网卡是
eth0
- 报文的入口网卡是
- 在
PREROUTING
进行了目的端口转发,那么报文就要发送到10.2.254.2
, 见docker
链的DNAT
规则- 要求不是入口网卡
br-a397efbb3fb4
的报文进行DNAT转换 - 网桥
br-a397efbb3fb4
作为一个网关角色,是保持内部通信的基础,所以其IP不能进行改动
- 要求不是入口网卡
- 通过路由寻址。
route -n
可以看到目标网段在10.2.254.0
是通过br-a397efbb3fb4
出去。- 此时报文的入口是
eth0
, 报文的出口br-a397efbb3fb4
br-a397efbb3fb4
和容器IP10.2.254.2
在同一个网段下,且满足通信。
- 此时报文的入口是
场景2: 容器是如何通过SNAT实现对外通信的
-
假设容器请求其他主机:
192.168.35.253
,删除a397efbb3fb4
的SNAT规则后。进行ping测试 -
通过简单的分析拿到容器在主机的设备对网卡是
vetha276645
- 在容器查看命令
ip link show
可以看容器网卡的ID@外部的网卡ID。eth0@if125
- 外部查看
ip link show
查到125网卡vetha276645
- 在容器查看命令
-
ping
请求通过vetha276645
到达主机后,通过路由会从网卡eth0
发出,查看eth0
抓包信息。可以看到是有报文出去
抓包容器网卡vetha276645
向192.168.35.253
发起请求
[root@mking net]# tcpdump -vvn -i vetha276645 icmp
tcpdump: listening on vetha276645, link-type EN10MB (Ethernet), capture size 262144 bytes
11:15:39.983214 IP (tos 0x0, ttl 64, id 19344, offset 0, flags [DF], proto ICMP (1), length 84)
10.2.254.3 > 192.168.35.253: ICMP echo request, id 32, seq 782, length 64
11:15:40.983457 IP (tos 0x0, ttl 64, id 19815, offset 0, flags [DF], proto ICMP (1), length 84)
10.2.254.3 > 192.168.35.253: ICMP echo request, id 32, seq 783, length 64
11:15:41.983658 IP (tos 0x0, ttl 64, id 20419, offset 0, flags [DF], proto ICMP (1), length 84)
10.2.254.3 > 192.168.35.253: ICMP echo request, id 32, seq 784, length 64
11:15:42.983858 IP (tos 0x0, ttl 64, id 21070, offset 0, flags [DF], proto ICMP (1), length 84)
10.2.254.3 > 192.168.35.253: ICMP echo request, id 32, seq 785, length 64
抓包192.168.33.253
出口网卡也发出去请求
[root@mking ~]# tcpdump -i enp3s0 -p icmp -vvnn
tcpdump: listening on enp3s0, link-type EN10MB (Ethernet), capture size 262144 bytes
11:04:49.856208 IP (tos 0x0, ttl 63, id 10747, offset 0, flags [DF], proto ICMP (1), length 84)
10.2.254.3 > 192.168.35.253: ICMP echo request, id 32, seq 132, length 64
11:04:50.856421 IP (tos 0x0, ttl 63, id 11569, offset 0, flags [DF], proto ICMP (1), length 84)
10.2.254.3 > 192.168.35.253: ICMP echo request, id 32, seq 133, length 64
11:04:51.856633 IP (tos 0x0, ttl 63, id 11661, offset 0, flags [DF], proto ICMP (1), length 84)
10.2.254.3 > 192.168.35.253: ICMP echo request, id 32, seq 134, length 64
11:04:52.856829 IP (tos 0x0, ttl 63, id 12190, offset 0, flags [DF], proto ICMP (1), length 84)
10.2.254.3 > 192.168.35.253: ICMP echo request, id 32, seq 135, length 64
11:04:53.857026 IP (tos 0x0, ttl 63, id 12795, offset 0, flags [DF], proto ICMP (1), length 84)
10.2.254.3 > 192.168.35.253: ICMP echo request, id 32, seq 136, length 64
抓包192.168.35.253
的确收到来自10.2.254.3
的请求,也尝试直接回复10.2.254.3
它的请求,但失败。
[root@host-253 ~]# tcpdump -i ens192 icmp -vvnn
tcpdump: listening on ens192, link-type EN10MB (Ethernet), capture size 262144 bytes
11:05:53.852475 IP (tos 0x0, ttl 62, id 43080, offset 0, flags [DF], proto ICMP (1), length 84)
10.2.254.3 > 192.168.35.253: ICMP echo request, id 32, seq 196, length 64
11:05:53.852530 IP (tos 0x0, ttl 64, id 13542, offset 0, flags [none], proto ICMP (1), length 84)
192.168.35.253 > 10.2.254.3: ICMP echo reply, id 32, seq 196, length 64
11:05:54.852667 IP (tos 0x0, ttl 62, id 43886, offset 0, flags [DF], proto ICMP (1), length 84)
10.2.254.3 > 192.168.35.253: ICMP echo request, id 32, seq 197, length 64
11:05:54.852719 IP (tos 0x0, ttl 64, id 13570, offset 0, flags [none], proto ICMP (1), length 84)
192.168.35.253 > 10.2.254.3: ICMP echo reply, id 32, seq 197, length 64
11:05:55.312720 IP (tos 0x0, ttl 64, id 39942, offset 0, flags [DF], proto ICMP (1), length 60)
192.168.35.253 > 125.64.129.223: ICMP echo request, id 3328, seq 5836, length 40
11:05:55.319196 IP (tos 0x0, ttl 56, id 39942, offset 0, flags [DF], proto ICMP (1), length 60)
125.64.129.223 > 192.168.35.253: ICMP echo reply, id 3328, seq 5836, length 40
11:05:55.852795 IP (tos 0x0, ttl 62, id 44326, offset 0, flags [DF], proto ICMP (1), length 84)
10.2.254.3 > 192.168.35.253: ICMP echo request, id 32, seq 198, length 64
11:05:55.852817 IP (tos 0x0, ttl 64, id 13723, offset 0, flags [none], proto ICMP (1), length 84)
192.168.35.253 > 10.2.254.3: ICMP echo reply, id 32, seq 198, length 64
11:05:56.853000 IP (tos 0x0, ttl 62, id 45059, offset 0, flags [DF], proto ICMP (1), length 84)
10.2.254.3 > 192.168.35.253: ICMP echo request, id 32, seq 199, length 64
11:05:56.853028 IP (tos 0x0, ttl 64, id 13921, offset 0, flags [none], proto ICMP (1), length 84)
192.168.35.253 > 10.2.254.3: ICMP echo reply, id 32, seq 199, length 64
11:05:57.853198 IP (tos 0x0, ttl 62, id 45512, offset 0, flags [DF], proto ICMP (1), length 84)
10.2.254.3 > 192.168.35.253: ICMP echo request, id 32, seq 200, length 64
-
通过抓包可以简单分
- 容器
10.2.254.3
向192.168.35.253
发起了ICMP
请求。 - 主机
192.168.33.253
通过路由指定网卡进行外部通信 - 目标
192.168.35.253
也收到来自IP10.2.254.3
的请求 - 目标
192.168.35.253
向10.2.254.3
进行回应, - 目标
192.168.35.253
找不到10.2.254.3
, 因为他们本身不具备直接通信的条件。 - 容器
10.2.254.3
不能收到192.168.35.253
响应。 导致网络不能互通
- 容器
-
通过
SNAT
修改源IP地址,让192.168.35.253
可以和主机通信- 对
10.2.254.3
修改源IP,所以docker取网段范围-s 10.2.254.0/24
满足更多容器 - 其中
br-a397efbb3fb4
是一个网桥,是内部通信的基础。所以不需要修改其源IP。假设修改了,那么会造成主机内网不能互通的情况 - 综合规则:
iptables -t nat -A POSTROUTING ! -o br-a397efbb3fb4 -s 10.2.254.0/24 -j MASQUERADE
- 对
主机抓包后可以看到icmp
的报文原始IP地址变更主机网卡IP地址
[root@mking ~]# tcpdump -i enp3s0 -p icmp -vvnn
tcpdump: listening on enp3s0, link-type EN10MB (Ethernet), capture size 262144 bytes
11:23:42.355262 IP (tos 0x0, ttl 63, id 62150, offset 0, flags [DF], proto ICMP (1), length 84)
192.168.33.233 > 192.168.35.253: ICMP echo request, id 33, seq 9, length 64
11:23:42.355422 IP (tos 0x0, ttl 63, id 16529, offset 0, flags [none], proto ICMP (1), length 84)
192.168.35.253 > 192.168.33.233: ICMP echo reply, id 33, seq 9, length 64
11:23:43.355476 IP (tos 0x0, ttl 63, id 62489, offset 0, flags [DF], proto ICMP (1), length 84)
192.168.33.233 > 192.168.35.253: ICMP echo request, id 33, seq 10, length 64
11:23:43.355662 IP (tos 0x0, ttl 63, id 16562, offset 0, flags [none], proto ICMP (1), length 84)
192.168.35.253 > 192.168.33.233: ICMP echo reply, id 33, seq 10, length 64
目标主机抓包信息的请求源地址也是满足可以通信的主机IP地址。并且能正常响应回复
[root@runner253 ~]# tcpdump -i ens192 icmp -vvnn
tcpdump: listening on ens192, link-type EN10MB (Ethernet), capture size 262144 bytes
11:26:11.368853 IP (tos 0x0, ttl 62, id 12798, offset 0, flags [DF], proto ICMP (1), length 84)
192.168.33.233 > 192.168.35.253: ICMP echo request, id 33, seq 158, length 64
11:26:11.368893 IP (tos 0x0, ttl 64, id 26732, offset 0, flags [none], proto ICMP (1), length 84)
192.168.35.253 > 192.168.33.233: ICMP echo reply, id 33, seq 158, length 64
11:26:12.369095 IP (tos 0x0, ttl 62, id 13330, offset 0, flags [DF], proto ICMP (1), length 84)
192.168.33.233 > 192.168.35.253: ICMP echo request, id 33, seq 159, length 64
11:26:12.369136 IP (tos 0x0, ttl 64, id 27102, offset 0, flags [none], proto ICMP (1), length 84)
192.168.35.253 > 192.168.33.233: ICMP echo reply, id 33, seq 159, length 64
目前docker主机
可以收到来自目标主机的回复响应,主机又如何把回复响应转交给docker容器
根据网上查询,自己没有验证。 当创建SNAT后,iptables也会自动在
nat
创建DNAT规则,用于回源到最原始的IP
简单总结nat
规则目的
- 外部对内请求,会在PREROUTING链DNAT转发请求到容器端口。
- 外部对内过程没有修改源IP地址。所以容器是可以拿到客户端IP地址。
- 内部对外访问,会在POSTROUTING链SNAT转发请求到外部网络。
- 内部对外修改了源IP地址。所以目的端收到的IP服务器出网的网卡IP。
3. 查看filter
过滤表信息
filter主要对流量进行拦截, 查看主机规则。 很多都是docker
创建, 还是以br-a397efbb3fb4
为例。
[root@mking /]# iptables -t filter -nvL
Chain INPUT (policy ACCEPT 9502 packets, 752K bytes)
pkts bytes target prot opt in out source destination
Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
34 5744 DOCKER-USER all -- * * 0.0.0.0/0 0.0.0.0/0
34 5744 DOCKER-ISOLATION-STAGE-1 all -- * * 0.0.0.0/0 0.0.0.0/0
16 2984 ACCEPT all -- * br-a397efbb3fb4 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED
2 120 DOCKER all -- * br-a397efbb3fb4 0.0.0.0/0 0.0.0.0/0
16 2640 ACCEPT all -- br-a397efbb3fb4 !br-a397efbb3fb4 0.0.0.0/0 0.0.0.0/0
0 0 ACCEPT all -- br-a397efbb3fb4 br-a397efbb3fb4 0.0.0.0/0 0.0.0.0/0
Chain DOCKER (2 references)
pkts bytes target prot opt in out source destination
2 120 ACCEPT tcp -- !br-a397efbb3fb4 br-a397efbb3fb4 0.0.0.0/0 10.2.254.2 tcp dpt:80
Chain DOCKER-ISOLATION-STAGE-1 (1 references)
pkts bytes target prot opt in out source destination
0 0 DOCKER-ISOLATION-STAGE-2 all -- docker0 !docker0 0.0.0.0/0 0.0.0.0/0
16 2640 DOCKER-ISOLATION-STAGE-2 all -- br-a397efbb3fb4 !br-a397efbb3fb4 0.0.0.0/0 0.0.0.0/0
34 5744 RETURN all -- * * 0.0.0.0/0 0.0.0.0/0
Chain DOCKER-ISOLATION-STAGE-2 (2 references)
pkts bytes target prot opt in out source destination
0 0 DROP all -- * docker0 0.0.0.0/0 0.0.0.0/0
0 0 DROP all -- * br-a397efbb3fb4 0.0.0.0/0 0.0.0.0/0
16 2640 RETURN all -- * * 0.0.0.0/0 0.0.0.0/0
Chain DOCKER-USER (1 references)
pkts bytes target prot opt in out source destination
34 5744 RETURN all -- * * 0.0.0.0/0 0.0.0.0/0
通过链路分析关于网桥 br-a397efbb3fb4
处理拦截。
-
PREROUTING 链路: 进入主机后规则匹配成功,修改目标IP地址。
-
INPUT 链: 对所有目的IP存在本机的流量进行处理
- 因为容器对网络环境进行了隔离,所以当前主机 (
ip address
和ifconfig
) 没有容器IP地址。
- 因为容器对网络环境进行了隔离,所以当前主机 (
-
FORWARD 链:对所有目的IP不在本机的流量进行处理
- 容器IP不在主机网络的,会经过当前链进行拦截处理。
- 网桥
br-a397efbb3fb4
处理动作有3个- 第三行规则
- 命令:
iptables -I FOREWARD -o br-a397efbb3fb4 -m stat RELATED,ESTABLISHED -j ACCEPT
- 理解: 经过
br-a397efbb3fb4
出去的网络,允许回应报文通过。
- 命令:
- 第四行规则
- 命令:
iptables -I FOREWARD -o br-a397efbb3fb4 -j DOCKER
- 理解: 经过
br-a397efbb3fb4
出去的网络,由Docker
进一步筛选控制。
- 命令:
- 第五行规则:
iptables -I FOREWARD -i br-a397efbb3fb4 ! -o br-a397efbb3fb4 -j ACCEPT
- 规则翻译: 允许经过
br-a397efbb3fb4
进的网络, 从其他网卡出去。 - 本质是:允许容器内网与外网通信。
- 规则翻译: 允许经过
- 第六行规则:
iptables -I FOREWARD -i br-a397efbb3fb4 -o br-a397efbb3fb4 -j ACCEPT
- 规则翻译: 允许经过
br-a397efbb3fb4
进的网络并从该网卡出去
- 规则翻译: 允许经过
- 第三行规则
-
OUTPUT 链: 通过网卡出去的网络进行拦截。
- 此处不涉及
-
Docker 链: 对于
br-a397efbb3fb4
有一项处理- 第一行规则:
- 命令:
iptables -I DOCKER -p tcp --dport 80 -d 10.2.254.2 ! -i br-a397efbb3fb4 -o br-a397efbb3fb4 -j ACCEPT
- 理解: 允许非
br-a397efbb3fb4
网络到br-a397efbb3fb4
网络对目标IP10.2.254.2
进行80端口访问
- 命令:
- 第一行规则:
-
DOCKER-ISOLATION-STAGE-1规则上最终交给DOCKER-ISOLATION-STAGE-2: 有一个丢包机制
- 第二行规则:
- 命令:
iptables -I DOCKER-ISOLATION-STAGE-2 -o br-a397efbb3fb4 -j DROP
- 理解: 拒绝从网桥
br-a397efbb3fb4
出去的流量
- 命令:
- 第二行规则:
场景1: 外部请求容器服务需要经历哪些关卡
- 从
192.168.35.253
访问 容器HTTP 服务,请求经过eth0
进入主机。 - 在
PREROUTING
链路修改目的IP地址,通过目的端口转向到10.2.254.2
- 路由选择: 匹配到
10.2.254.2
需要经过过br-a397efbb3fb4
出去- 报文入口网卡是
eth0
- 分组出口网卡是
br-a397efbb3fb4
- 报文入口网卡是
- 目标IP不在主机网络,经过
FORWARD
链处理,这里主要有2个处理- 尝试建立链接: 允许 TCP 3次握手,所以满足
RELATED,ESTABLISHED
进行放行。 行三规则 - 已经建立链接: 允许通过来自外部对
10.2.254.2
的80端口访问。 行四规则
- 尝试建立链接: 允许 TCP 3次握手,所以满足
场景2: 容器对外请求/响应需要经历哪些关卡
- 容器 回复
192.168.35.253
的响应报文,经过br-a397efbb3fb4
到达主机 - 在
POSTROUTING
链路修改了源IP地址。 - 路由选择: 匹配到
192.168.35.253
需要经过eth0
出去- 流量入口网卡是
br-a397efbb3fb4
。 - 流量出口网卡是
eth0
- 流量入口网卡是
- 目标IP不在主机网络,经过
FORWARD
链处理。- 允许从其他网卡出去。行五规则
场景3: 不同网络的容器是如何隔离的
10.1.254.2
尝试建立10.2.254.2
请求10.1.254.2
从docker0
进入主机,- 路由选择: 匹配到
10.2.254.2
需要经过过br-a397efbb3fb4
出去- 流量入口网卡是
docker0
- 流量出口网卡是
br-a397efbb3fb4
- 流量入口网卡是
- 目标IP不在主机网络,经过
FORWARD
,DOCKER-ISOLATION-STAGE-1
,DOCKER-ISOLATION-STAGE-2
链处理。- 拒绝从
br-a397efbb3fb4
出去的网卡 第二行规则 - 拒绝之后,到达
RETURN
规则,不再进行匹配。 - 验证取消容器网络之间的隔离:
iptables -F DOCKER-ISOLATION-STAGE-2
- 拒绝从
四、 通过命令模拟docker网络通信
1. 通过shell
命令实现网络隔离
, 并实现内部通信和外网通信
1. 准备工作
- 会用到centos网络设备管理命令
ip help # 支持子命令。 所有的命令: 增(add)删(delete/rm)改(set/put)查(show)
ip link # 查看网络连接, 网卡,支持虚拟技术的。硬件虚拟,vmware就是硬件虚拟机。
ip link type bridge 虚拟网卡 / veth: 一对虚拟网卡(常见用于docker命名空间隔离)
ip address # 查看IP地址
ip netns # 网络命名空间,简单理解虚拟了一个环境,与外部独立。
ip route # 查看路由。 看IP怎么走的
-
网桥: 网络连接的一种形式,主要连接多个局域网(lan), 进行流量接收,存储和转发。
-
虚拟以太网设备对(veth-pair): 网络连接的另一种形式, 会产生一对网卡。常用于
网络隔离
通信。
2. 设备对通信
- 创建一个网络隔离的namespace: demo
[root@mking /]# ip netns add demo
# 查看网络NS信息
[root@mking /]# ip netns ls
demo
- 创建一组网卡设备对: veth pair。
设备对网卡可以通过关键字@
快速辨别。 51,51 表示网卡的编号ID , peer1
和peer2
表示网卡别名
[root@mking /]# ip link add name peer0 type veth peer name peer1
[root@mking /]# ip link show
51: peer1@peer0: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether 6a:a0:1b:3a:58:e5 brd ff:ff:ff:ff:ff:ff
52: peer0@peer1: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether 6e:90:2d:40:59:1e brd ff:ff:ff:ff:ff:ff
- 把peer0的网卡对设置到另一个命名空间
demo
[root@mking /]# ip link set peer0 netns demo
- 查不到编号52:
peer0@peer1
的网卡,同时编号51网卡显示:peer1@if52
, 说明编号52
网卡存在,但已被设置到其他的ns
, 当前的ns
查看不了
[root@mking /]# ip link show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: enp3s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
link/ether d4:5d:64:7f:92:66 brd ff:ff:ff:ff:ff:ff
51: peer1@if52: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
link/ether 6a:a0:1b:3a:58:e5 brd ff:ff:ff:ff:ff:ff link-netnsid
- 分别为两张网卡设置IP,实现通信。
veth pari
创建的设备对,默认链路是通的。
# 操作宿主机默认的NS。 操作中需要满足IP在一个网段内。
[root@mking /]# ip address add 10.100.0.15/16 dev peer1
[root@mking /]# ip link set dev peer1 up
# 操作隔离的NS: 设置IP地址并启动
[root@mking /]# ip netns exec demo ip address add 10.100.15.34/16 dev peer0
[root@mking /]# ip netns exec demo ip link set dev peer0 up
- 相互ping测
[root@mking /]# ip netns exec demo ping 10.100.0.15
PING 10.100.0.15 (10.100.0.15) 56(84) bytes of data.
64 bytes from 10.100.0.15: icmp_seq=1 ttl=64 time=0.081 ms
64 bytes from 10.100.0.15: icmp_seq=2 ttl=64 time=0.047 ms
64 bytes from 10.100.0.15: icmp_seq=3 ttl=64 time=0.046 ms
# 从宿主机进行探测ping通10.100.15.34
[root@mking /]# ping 10.100.15.34
PING 10.100.15.34 (10.100.15.34) 56(84) bytes of data.
64 bytes from 10.100.15.34: icmp_seq=1 ttl=64 time=0.066 ms
64 bytes from 10.100.15.34: icmp_seq=2 ttl=64 time=0.046 ms
64 bytes from 10.100.15.34: icmp_seq=3 ttl=64 time=0.046 ms
宿主机创建隔离网络空间demo
实现通信, 主要步骤如下
- 创建一个
ns
独立的网络空间 - 创建一组虚拟设备对
veth pair
产生两张网络接口满足通信链路条件 - 其中一张网卡设置在
ns
- 两张网卡IP在同一个网段
- 启动两张网卡
3.多个隔离网络之间的通信
我们实现了一个ns与主机的通信,现在模拟docker多个容器之间的通信: 即可实现多个ns内部相互通信。
- IP段规划
设备 | 命名空间 | IP | 广播地址 | 备注 |
---|---|---|---|---|
br0 | default | 10.105.0.1 | 10.105.255.255 | 拟gateway |
vir10 | default | 10.105.1.110 | 10.105.255.255 | veth-peer中的一个网卡 |
vir11 | ns1 | 10.105.3.50 | 10.105.255.255 | veth-peer中的一个网卡 |
vir20 | default | 10.105.2.110 | 10.105.255.255 | veth-peer中的一个网卡 |
vir21 | ns2 | 10.105.8.110 | 10.105.255.255 | veth-peer中的一个网卡 |
- IP二进制换算: 128 64 32 16 8 4 2 1
- 10.105.0.1 == 00001010.01101001.00000000.00000001
- 子网换算
- 10.105.0.1/24 == 00001010.01101001.00000000.xxxxxxxx == 10.105.0.0-10.105.0.255
- 10.105.0.1/20 == 00001010.01101001.0000xxxx.xxxxxxxx == 10.105.0.0-10.105.31.255
- 10.105.0.1/16 == 00001010.01101001.xxxxxxxx.xxxxxxxx == 10.105.0.0-10.105.255.255
- 需求
- 创建桥接网卡模拟gateway的功能,实现arp
- ns1网卡满足通过gateway找到ns2的网卡实现通信
- 要求
- 隔离在ns1的网卡需要访问到gateway
- 隔离在ns2的网卡也需要访问到gateway
- 已知
- ns1的vir11可以与vir10进行通信
- vir10可以与gateway直接通信
- ns2的vir21可以与vir20进行通信
- vir11可以与gateway直接通信
- 可得
- vir11可以通过vir10把数据请求到gateway进行通信
- vir21可以通过vir20把数据请求到gateway进行通信
- 创建两个ns, 模拟两个docker网络环境
[root@mking /]# ip netns add ns1
[root@mking /]# ip netns add ns2
-
同上两组网卡设备对
vir10(主机)/vir11(ns1)
和vir20(主机)/vir21(ns2)
。同一主机内的
vir10
和vir20
是相互独立的, 需要创建一张网卡
作为其网关
,将流量转发到网关
, 用于arp
IP寻址此处设置默认网关地址
10.105.0.1/16
, 广播地址得出10.105.255.255
。
# 安装相关命令
[root@mking /]# yum install bridge-utils -y
# 创建一张桥接网卡
[root@mking /]# brctl addbr gateway1
# 为gateway1设置网卡IP并启动
[root@mking /]# ip address add 10.105.0.1/16 broadcast 10.105.255.255 dev gateway1
[root@mking /]# ip link set dev gateway1 up
- 同上两组网卡设备对
veth pair
分别用于ns1
和ns2
。 并对两组ns
设置IP,要求在同一个网段内,不需要设置vir10
和vir20
网卡
# 创建vir1x和vir2x的veth peer网卡
[root@mking /]# ip link add name vir10 type veth peer name vir11
[root@mking /]# ip link add name vir20 type veth peer name vir21
# 把vir11和vir21网卡分别放在ns1和ns2中
[root@mking /]# ip link set vir11 netns ns1
[root@mking /]# ip link set vir21 netns ns2
# 分别为vri11和vir21设置IP,并启动环路lo网卡
[root@mking /]# ip netns exec ns1 ip address add 10.105.3.50/16 broadcast 10.105.255.255 dev vir11
[root@mking /]# ip netns exec ns1 ip link set dev vir11 up
[root@mking /]# ip netns exec ns1 ip link set dev lo up
[root@mking /]# ip netns exec ns2 ip address add 10.105.8.110/16 broadcast 10.105.255.255 dev vir21
[root@mking /]# ip netns exec ns2 ip link set dev vir21 up
[root@mking /]# ip netns exec ns2 ip link set dev lo up
- 设置同主机内的
vir10
和vir20
, 我们将网卡指向到gateway1
# 处理vir10和vir20, 把vir10和vir20 指向到gateway1
[root@mking /]# brctl addif gateway1 vir10
[root@mking /]# brctl addif gateway1 vir20
# 启动同一主机下的网卡
[root@mking /]# ip link set dev vir10
[root@mking /]# ip link set dev vir20
# 查看网桥信息
[root@mking /]# brctl show
bridge name bridge id STP enabled interfaces
gateway1 8000.7e6c5709cc5e no vir10
vir20
- 至此
ns1
和ns2
可以相互ping通, 理论上不同的网络隔离空间
下的设备对网卡指向到gateway1
,都可以访问
# 从ns1 访问 ns2
[root@mking /]# ip netns exec ns1 ping 10.105.8.110
PING 10.105.8.110 (10.105.8.110) 56(84) bytes of data.
64 bytes from 10.105.8.110: icmp_seq=1 ttl=64 time=0.043 ms
64 bytes from 10.105.8.110: icmp_seq=2 ttl=64 time=0.042 ms
64 bytes from 10.105.8.110: icmp_seq=3 ttl=64 time=0.122 ms
# 从ns2 访问 ns1
[root@mking /]# ip netns exec ns2 ping 10.105.3.50
PING 10.105.3.50 (10.105.3.50) 56(84) bytes of data.
64 bytes from 10.105.3.50: icmp_seq=1 ttl=64 time=0.068 ms
64 bytes from 10.105.3.50: icmp_seq=2 ttl=64 time=0.038 ms
64 bytes from 10.105.3.50: icmp_seq=3 ttl=64 time=0.105 ms
多个网络空间ns
实现通信, 主要步骤如下
- 创建多个
ns
和多组veth pair
虚拟设备对的网卡 - 设备对的一端网卡分别设置在多个
ns1
- 宿主机创建一张
bridge
网卡 - 设备对的另一端网卡添加到
bridge
网卡。 ns
里面的网卡和bridge
网卡的IP设置在一个网段内
4.多个NS对外通信
模拟了多个docker的网络隔离之间通信,现在模拟docker对外的通信。
- 创建两个ns, 模拟两个docker网络环境。 此处继续使用之间建好的
ns1
和ns2
。
# 从ns1 访问外网,寻址失败
[root@mking /]# ip netns exec ns2 ping 61.139.2.69
connect: Network is unreachable
# 从ns2 访问外网, 寻址失败
[root@mking /]# ip netns exec ns1 ping 61.139.2.69
connect: Network is unreachable
- 创建默认路由,让请求外网的分组请求到
gateway1
# 添加默认路由
[root@mking /]# ip netns exec ns1 route add default gw 10.105.0.1
[root@mking /]# ip netns exec ns2 route add default gw 10.105.0.1
# 访问外网,但无响应。
[root@mking /]# ip netns exec ns1 ping 61.139.2.69
PING 61.139.2.69 (61.139.2.69) 56(84) bytes of data.
2 packets transmitted, 0 received, 100% packet loss, time 1000ms
[root@mking /]# ip netns exec ns2 ping 61.139.2.69
PING 61.139.2.69 (61.139.2.69) 56(84) bytes of data.
2 packets transmitted, 0 received, 100% packet loss, time 1001ms
# 查看gateway1有分组经过。RX packets 接收流量, TX packets 13 传输流量
[root@mking /]# ifconfig gateway1
gateway1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 10.105.0.1 netmask 255.255.0.0 broadcast 10.105.255.255
inet6 fe80::dcb0:d8ff:fe6b:856f prefixlen 64 scopeid 0x20<link>
ether 22:4e:62:c8:f3:80 txqueuelen 1000 (Ethernet)
RX packets 33 bytes 2124 (2.0 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 13 bytes 978 (978.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
此时ns1
和 ns2
可以把流量发送到网卡gateway1
。
- 分析
ns1
流量走势
iptables处理分组时间轴
timeline
title iptables时间线
PREROUTING : nat
INPUT : filter: nat
FORWARD : nat
OUTPUT : filter: nat
POSTROUTING: nat
分组从 ns1
的网卡 vir11
经过vir10
到达网卡gateway
, 会经过如下判断
- PREROUTING
- 事件: 接收分组时候
- 备注: 对分组进行修改, 此处不修改
- FORWARD:
- 事件: 接收分组后
- 执行动作: 放行
- 触发条件: 目标IP不在主机. 所以进行
forward
转发
- POSTROUTING:
- 事件: 通过转发后触发
- 执行动作: 修改报文
source ip
, 本质是将分组交给source ipaddress
发出去
# 放行FORWARD, 流入gateway1网卡分组通过。 主要是通过来自ns1,ns2请求流量
[root@mking /]# iptables -t filter -I FORWARD -i gateway1 -j ACCEPT
# 放行FORWARD, 出口gateway1网卡分组通过。 主要是通过返回ns1,ns2响应流量
[root@mking /]# iptables -t filter -I FORWARD -o gateway1 -j ACCEPT
# 地址转换, 把gateway1网段的分组, 修改Source IP或指定网卡IP。实现外部请求
[root@mking /]# iptables -t nat -A POSTROUTING -s 10.105.0.0/16 -o ens33 -j MASQUERADE
- 自建的
ns1
可以访问外部网络。
[root@mking /]# ip netns exec ns2 ping 61.139.2.69
PING 61.139.2.69 (61.139.2.69) 56(84) bytes of data.
64 bytes from 61.139.2.69: icmp_seq=1 ttl=57 time=3.09 ms
64 bytes from 61.139.2.69: icmp_seq=2 ttl=57 time=3.03 ms
64 bytes from 61.139.2.69: icmp_seq=3 ttl=57 time=3.44 ms
--- 61.139.2.69 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
5.外部访问NS网络
实现ns
可以访问外网,模拟外网如何访问ns
内部服务(访问docker服务)
-
前置条件: 创建一个
ns
, 并要求ns
网络可以对外访问。 此处继续使用之间建好的ns1
和ns2
。 -
终端1在
ns1
开启一个端口
[root@mking /]# ip netns exec ns1 nc -l -p 8000
- 终端2检查
ns1
端口情况
[root@mking /]# ip netns exec ns1 netstat -anpt
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:8000 0.0.0.0:* LISTEN 26130/nc
tcp6 0 0 :::8000 :::* LISTEN 26130/nc
- 通过
iptables
的DNAT转发,对外提供访问
enp3s0
网卡收到外部请求端口: 8000。 可通过iptables如下链路
- PREROUTING
- 事件: 接收分组时候。
- 执行动作: 修改目的IP和端口。
- 备注: 因为当前主机是没有提供端口服务,而主机与
ns1
可以直接通信。 所以在入口时通过修改dst address
和dst port
转发请求到目的网络
- FORWARD:
- 事件: 接收分组后
- 执行动作: 放行
- 触发条件: 目标IP已改,目的不在主机. 所以进行
forward
转发
# 放行FORWARD, 流入gateway1网卡分组通过。 主要是通过来自ns1,ns2请求流量
[root@mking /]# iptables -t filter -I FORWARD -i gateway1 -j ACCEPT
# 放行FORWARD, 出口gateway1网卡分组通过。 主要是通过返回ns1,ns2响应流量
[root@mking /]# iptables -t filter -I FORWARD -o gateway1 -j ACCEPT
# 地址转换, 把gateway1网段的分组, 修改Source IP或指定网卡IP。实现外部请求
[root@mking /]# iptables -t nat -I PREROUTING -p tcp --dport 8000 -j DNAT --to-destination 10.105.3.50
- 验证自建的
ns1
对外提供端口访问。
# 从其他设备访问8000
[root@others /]# telnet 192.168.33.253 8000
# 检查ns1的网络连接情况
[root@mking /]# ip netns exec ns1 netstat -anpt
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 10.105.3.50:8000 192.168.35.253:48246 ESTABLISHED 26332/nc
可以看到8000端口有来自192.168.35.253
的访问,基础目标完成
2. 给docker做适配: 为容器接入自定义ns
集群
目标: 把自建隔离的网络放入到docker中
- 创建好一组虚拟网卡设备对, 规划和
gateway1
相同的IP段.
# 创建ns3
[root@mking /]# ip netns add ns3
# 创建新的虚拟网卡设备对
[root@mking /]# ip link add name vir30 type veth peer name vir31
- 启动一个nginx docker,不暴露端口。
# 启动一个nginx服务,并使用自定义网络, 但不暴露端口。
[root@mking /]# docker run --name nginx --network demo -itd --rm nginx
指定网络是docker
启动会创建新的隔离网络, 不暴露端口是咱不使用docker-proxy
代理。
ns
的信息主要存放在路径:/var/run/netns/
, 通过创建/proc/${pid}/ns/net
软连接便可以管理进程的ns
。
# 查询docker容器ID
[root@mking /]# docker inspect nginx --format="{{ .State.Pid }}"
6331
# 创建PID下的ns软连接到/var/run/netns/ngx, 便可以ngx去管理docker启动nginx容器网络
[root@mking /]# ln -s /proc/6331/ns/net /var/run/netns/ngx
# 可以查看目前ngx有一张网卡
[root@mking net]# ip netns exec ngx ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 10.2.254.2 netmask 255.255.255.0 broadcast 10.2.254.255
ether 02:42:0a:02:fe:02 txqueuelen 0 (Ethernet)
RX packets 8 bytes 656 (656.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
- 将
vir31
网卡设置到ngx
内。把vir30
网卡添加到gateway1
的网桥
# 把vir31网卡设置到ngx内
[root@mking net]# ip link set vir31 netns ngx
# 查看目前ngx新加入的eth1
[root@mking net]# ip netns exec ngx ifconfig vir31
# 设置IP段
[root@mking /]# ip netns exec ngx ip address add 10.105.1.250/16 broadcast 10.105.255.255 dev vir31
# 查看网卡信息
[root@mking net]# ip netns exec ngx ifconfig vir31
vir31: flags=4098<BROADCAST,MULTICAST> mtu 1500
inet 10.105.1.250 netmask 255.255.0.0 broadcast 10.105.255.255
ether 76:f4:11:39:f9:48 txqueuelen 1000 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
# 启动容器网卡
[root@mking net]# ip netns exec ngx ip link set vir31 up
# 启动主机网卡
[root@mking net]# ip link set vir30 up
# 把外部的vir30网卡添加到gateway1.
[root@mking net]# brctl addif gateway1 vir30
- 内部
ns
访问容器网络
# 在ns1访问ngx的IP。
[root@mking net]# ip netns exec ns1 curl http://10.105.1.250
#查看nginx的日志
[root@mking /]# docker logs -f nginx
10.105.3.50 - - [12/Jul/2024:07:24:36 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/8.5.0" "-"
- 通过
iptables
方式暴露对外访问
# 先放行FORWARD(以上已做),
[root@mking net]# iptables -I FORWARD -i gateway1 -j ACCEPT
[root@mking net]# iptables -I FORWARD -o gateway1 -j ACCEPT
#添加 nat 地址映射。更改目标IP
[root@mking net]# iptables -t nat -I PREROUTING -p tcp --dport 8002 -j DNAT --to-destination 10.105.1.250:80
#查看nginx的日志
[root@mking /]# docker logs -f nginx
192.168.35.253 - - [12/Jul/2024:07:55:21 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.29.0" "-"
五、 小结
- docker利用桥接和虚拟设备对实现网络隔离以及通信。
- docker网络通过iptables对分组进行拦截过滤和转发。