了解docker网络通信原理

了解docker通过桥接模式实现网络通信,并使用shell命令实现模拟docker网络环境的创建过程

一、docker是什么

  1. Docker本质是一个进程,
  2. 宿主机通过namespace隔离机制提供进程需要运行基础环境,并且通过Cgroup限制进程调用资源。
  3. Docker的隔离机制包括
    • network隔离,此次主要探讨网络隔离
    • mount隔离
    • hostname隔离
    • user隔离
    • pid隔离
    • 进程通信隔离

二、docker 网络模式

  1. host: 主机模式
    • 不会创建网络隔离机制,直接宿主机的网络。
  2. bridge : 默认桥接, 新建虚拟桥接网卡用于 docker 容器之间的通信。
    • 桥接网卡通过iptables对分组进行转发到主网卡,通常使用主机外网IP与外部网络通信。
    • 桥接网卡会关联多张 interface 网络接口。 实现内网通信。
    • interface 主要是通过 veth pair 虚拟设备对,一端用于容器,一端用于绑定网桥。
  3. macvlan : 为容器穿件一张虚拟网卡,该网卡用于独立的MAC地址和相同的网卡操作逻辑。
    • 相比bridgehosts 模式。 即满足了网络隔离需求,也满足了和宿主机网络相同地位。
  4. ipvlan : 需要要求内核版本 4.2+, 暂不复现。 可以通过 ip link vlan 相关命令了解用法
  5. overlay: 未复现,主要用于swarm 集群
  6. 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-a397efbb3fb4nat 中处理逻辑。

  1. PREROUTING: 外部流量到达主机时处理,该链路主要动作是修改报文目的IP地址,通过DNAT动作实现请求转发
    • 链路展示所有报文会通过DOCKER CHAIN链, 该链也恰好有处理DNAT动作
  2. OUTPUT: 流量出去的时候,也会经过 DOCKER CHAIN
  3. DOCKER: 关于网桥 br-a397efbb3fb4 有两处处理。
    • RETURN处: 流量从网卡 br-a397efbb3fb4 进入的, 就不再继续向下匹配,也就不修改目标IP
    • DNAT: 流量从其他网卡进入且访问80端口的,通过修改了目的地址指向nginx容器的IP: 10.2.254.2
  4. 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的

  1. 假设外部通过eth0访问80端口,先通过iptable规则进行报文分析处理
    • 报文的入口网卡是eth0
  2. PREROUTING进行了目的端口转发,那么报文就要发送到10.2.254.2, 见docker链的DNAT规则
    • 要求不是入口网卡 br-a397efbb3fb4 的报文进行DNAT转换
    • 网桥br-a397efbb3fb4 作为一个网关角色,是保持内部通信的基础,所以其IP不能进行改动
  3. 通过路由寻址。route -n 可以看到目标网段在10.2.254.0是通过br-a397efbb3fb4出去。
    • 此时报文的入口是eth0, 报文的出口br-a397efbb3fb4
    • br-a397efbb3fb4 和容器IP10.2.254.2 在同一个网段下,且满足通信。

场景2: 容器是如何通过SNAT实现对外通信的

  1. 假设容器请求其他主机: 192.168.35.253,删除a397efbb3fb4的SNAT规则后。进行ping测试

  2. 通过简单的分析拿到容器在主机的设备对网卡是vetha276645

    • 在容器查看命令ip link show可以看容器网卡的ID@外部的网卡ID。eth0@if125
    • 外部查看ip link show查到125网卡vetha276645
  3. ping 请求通过vetha276645到达主机后,通过路由会从网卡eth0发出,查看eth0抓包信息。可以看到是有报文出去

抓包容器网卡vetha276645192.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
  1. 通过抓包可以简单分

    • 容器10.2.254.3192.168.35.253 发起了ICMP请求。
    • 主机192.168.33.253通过路由指定网卡进行外部通信
    • 目标192.168.35.253也收到来自IP10.2.254.3的请求
    • 目标192.168.35.25310.2.254.3进行回应,
    • 目标192.168.35.253找不到10.2.254.3, 因为他们本身不具备直接通信的条件。
    • 容器10.2.254.3 不能收到 192.168.35.253 响应。 导致网络不能互通
  2. 通过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规则目的

  1. 外部对内请求,会在PREROUTING链DNAT转发请求到容器端口。
  2. 外部对内过程没有修改源IP地址。所以容器是可以拿到客户端IP地址。
  3. 内部对外访问,会在POSTROUTING链SNAT转发请求到外部网络。
  4. 内部对外修改了源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 处理拦截。

  1. PREROUTING 链路: 进入主机后规则匹配成功,修改目标IP地址。

  2. INPUT 链: 对所有目的IP存在本机的流量进行处理

    • 因为容器对网络环境进行了隔离,所以当前主机 ( ip addressifconfig ) 没有容器IP地址。
  3. 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进的网络并从该网卡出去
  4. OUTPUT 链: 通过网卡出去的网络进行拦截。

    • 此处不涉及
  5. 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网络对目标IP 10.2.254.2 进行80端口访问
  6. DOCKER-ISOLATION-STAGE-1规则上最终交给DOCKER-ISOLATION-STAGE-2: 有一个丢包机制

    • 第二行规则:
      • 命令:iptables -I DOCKER-ISOLATION-STAGE-2 -o br-a397efbb3fb4 -j DROP
      • 理解: 拒绝从网桥br-a397efbb3fb4出去的流量

场景1: 外部请求容器服务需要经历哪些关卡

  1. 192.168.35.253访问 容器HTTP 服务,请求经过eth0进入主机。
  2. PREROUTING链路修改目的IP地址,通过目的端口转向到10.2.254.2
  3. 路由选择: 匹配到10.2.254.2需要经过过br-a397efbb3fb4出去
    • 报文入口网卡是eth0
    • 分组出口网卡是br-a397efbb3fb4
  4. 目标IP不在主机网络,经过FORWARD链处理,这里主要有2个处理
    • 尝试建立链接: 允许 TCP 3次握手,所以满足RELATED,ESTABLISHED进行放行。 行三规则
    • 已经建立链接: 允许通过来自外部对10.2.254.2的80端口访问。 行四规则

场景2: 容器对外请求/响应需要经历哪些关卡

  1. 容器 回复 192.168.35.253的响应报文,经过br-a397efbb3fb4到达主机
  2. POSTROUTING链路修改了源IP地址。
  3. 路由选择: 匹配到192.168.35.253需要经过eth0出去
    • 流量入口网卡是br-a397efbb3fb4
    • 流量出口网卡是eth0
  4. 目标IP不在主机网络,经过FORWARD链处理。
    • 允许从其他网卡出去。行五规则

场景3: 不同网络的容器是如何隔离的

  1. 10.1.254.2 尝试建立 10.2.254.2请求
  2. 10.1.254.2docker0进入主机,
  3. 路由选择: 匹配到10.2.254.2需要经过过br-a397efbb3fb4出去
    • 流量入口网卡是docker0
    • 流量出口网卡是br-a397efbb3fb4
  4. 目标IP不在主机网络,经过FORWARD, DOCKER-ISOLATION-STAGE-1, DOCKER-ISOLATION-STAGE-2 链处理。
    • 拒绝从br-a397efbb3fb4出去的网卡 第二行规则
    • 拒绝之后,到达RETURN规则,不再进行匹配。
    • 验证取消容器网络之间的隔离: iptables -F DOCKER-ISOLATION-STAGE-2

四、 通过命令模拟docker网络通信

1. 通过shell命令实现网络隔离, 并实现内部通信和外网通信

1. 准备工作
  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怎么走的
  1. 网桥: 网络连接的一种形式,主要连接多个局域网(lan), 进行流量接收,存储和转发。

  2. 虚拟以太网设备对(veth-pair): 网络连接的另一种形式, 会产生一对网卡。常用于网络隔离通信。

2. 设备对通信
  1. 创建一个网络隔离的namespace: demo
[root@mking /]# ip netns add demo
# 查看网络NS信息
[root@mking /]# ip netns ls
demo
  1. 创建一组网卡设备对: veth pair。

设备对网卡可以通过关键字@快速辨别。 51,51 表示网卡的编号ID , peer1peer2表示网卡别名

[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
  1. 把peer0的网卡对设置到另一个命名空间 demo
[root@mking /]# ip link set peer0 netns demo
  1. 查不到编号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 
  1. 分别为两张网卡设置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
  1. 相互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实现通信, 主要步骤如下

  1. 创建一个ns独立的网络空间
  2. 创建一组虚拟设备对 veth pair 产生两张网络接口满足通信链路条件
  3. 其中一张网卡设置在ns
  4. 两张网卡IP在同一个网段
  5. 启动两张网卡
3.多个隔离网络之间的通信

我们实现了一个ns与主机的通信,现在模拟docker多个容器之间的通信: 即可实现多个ns内部相互通信。

  1. IP段规划
设备命名空间IP广播地址备注
br0default10.105.0.110.105.255.255拟gateway
vir10default10.105.1.11010.105.255.255veth-peer中的一个网卡
vir11ns110.105.3.5010.105.255.255veth-peer中的一个网卡
vir20default10.105.2.11010.105.255.255veth-peer中的一个网卡
vir21ns210.105.8.11010.105.255.255veth-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进行通信
  1. 创建两个ns, 模拟两个docker网络环境
[root@mking /]# ip netns add ns1
[root@mking /]# ip netns add ns2
  1. 同上两组网卡设备对vir10(主机)/vir11(ns1)vir20(主机)/vir21(ns2)

    同一主机内的vir10vir20是相互独立的, 需要创建一张网卡作为其网关,将流量转发到网关, 用于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
  1. 同上两组网卡设备对veth pair分别用于ns1ns2。 并对两组ns设置IP,要求在同一个网段内,不需要设置vir10vir20网卡
# 创建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
  1. 设置同主机内的 vir10vir20 , 我们将网卡指向到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
  1. 至此ns1ns2可以相互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实现通信, 主要步骤如下

  1. 创建多个ns 和多组 veth pair 虚拟设备对的网卡
  2. 设备对的一端网卡分别设置在多个ns1
  3. 宿主机创建一张bridge网卡
  4. 设备对的另一端网卡添加到bridge网卡。
  5. ns里面的网卡和bridge网卡的IP设置在一个网段内
4.多个NS对外通信

模拟了多个docker的网络隔离之间通信,现在模拟docker对外的通信。

  1. 创建两个ns, 模拟两个docker网络环境。 此处继续使用之间建好的ns1ns2
# 从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
  1. 创建默认路由,让请求外网的分组请求到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

此时ns1ns2 可以把流量发送到网卡gateway1

  1. 分析ns1流量走势

iptables处理分组时间轴

timeline
    title iptables时间线
    PREROUTING : nat
    INPUT : filter: nat
    FORWARD : nat
    OUTPUT : filter: nat
    POSTROUTING: nat

分组从 ns1 的网卡 vir11 经过vir10 到达网卡gateway, 会经过如下判断

  1. PREROUTING
    • 事件: 接收分组时候
    • 备注: 对分组进行修改, 此处不修改
  2. FORWARD:
    • 事件: 接收分组后
    • 执行动作: 放行
    • 触发条件: 目标IP不在主机. 所以进行forward转发
  3. 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
  1. 自建的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服务)

  1. 前置条件: 创建一个ns, 并要求ns网络可以对外访问。 此处继续使用之间建好的ns1ns2

  2. 终端1在ns1开启一个端口

[root@mking /]# ip netns exec ns1 nc -l -p 8000
  1. 终端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            
  1. 通过iptables的DNAT转发,对外提供访问

enp3s0 网卡收到外部请求端口: 8000。 可通过iptables如下链路

  1. PREROUTING
    • 事件: 接收分组时候。
    • 执行动作: 修改目的IP和端口。
    • 备注: 因为当前主机是没有提供端口服务,而主机与ns1可以直接通信。 所以在入口时通过修改dst addressdst port转发请求到目的网络
  2. 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
  1. 验证自建的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中

  1. 创建好一组虚拟网卡设备对, 规划和gateway1相同的IP段.
# 创建ns3
[root@mking /]# ip netns add ns3

# 创建新的虚拟网卡设备对
[root@mking /]# ip link add name vir30 type veth peer name vir31
  1. 启动一个nginx docker,不暴露端口。
# 启动一个nginx服务,并使用自定义网络, 但不暴露端口。
[root@mking /]# docker run --name nginx --network demo -itd --rm nginx 

指定网络是docker启动会创建新的隔离网络, 不暴露端口是咱不使用docker-proxy代理。

  1. 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
  1. 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

  1. 内部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" "-"
  1. 通过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" "-"

五、 小结

  1. docker利用桥接和虚拟设备对实现网络隔离以及通信。
  2. docker网络通过iptables对分组进行拦截过滤和转发。
  • 18
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值