尝试理解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 LOCALChain 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 LOCALChain 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:80Chain 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,ESTABLISHED2   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:80Chain 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 1000link/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 1000link/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 1000link/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 1000link/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 1000link/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              vir10vir20
  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 1500inet 10.105.0.1  netmask 255.255.0.0  broadcast 10.105.255.255inet6 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 0TX packets 13  bytes 978 (978.0 B)TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

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

  1. 分析ns1流量走势

iptables处理分组时间轴

timelinetitle iptables时间线PREROUTING : natINPUT : filter: natFORWARD : natOUTPUT : filter: natPOSTROUTING: 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 1500inet 10.2.254.2  netmask 255.255.255.0  broadcast 10.2.254.255ether 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 0TX 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 1500inet 10.105.1.250  netmask 255.255.0.0  broadcast 10.105.255.255ether 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 0TX 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对分组进行拦截过滤和转发。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/45910.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

spring-boot2.x整合Kafka步骤

1.pom依赖添加 <properties><java.version>1.8</java.version><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.source>1.8</maven.compiler.source><maven.compiler.target>1.8</ma…

自学鸿蒙HarmonyOS的ArkTS语言<十二>wrapBuilder:组件工厂类封装

// FactoryComponent.ets Builder function Radio1() {Column() {Text(单选组件&#xff1a;)Row() {Radio({ value: 1, group: radioGroup })Text(选项1)}Row() {Radio({ value: 2, group: radioGroup })Text(选项2)}}.margin(10) }Builder function Checkbox1() {Column() {T…

DP(5) | 完全背包 | Java | 卡码52, LeetCode 518, 377, 70 做题总结

完全背包 感觉越写越糊涂了&#xff0c;初始化怎么做的&#xff1f;递推公式怎么来的&#xff1f; 卡码52. 携带研究材料 https://kamacoder.com/problempage.php?pid1052 import java.util.*;public class Main {public static void main(String[] args) {Scanner sc new …

Java面试八股之Redis集群是怎么选择数据库的

在Redis集群中&#xff0c;数据被水平分割&#xff08;sharding&#xff09;到各个节点上&#xff0c;这意味着所有的键空间被分成16384个哈希槽&#xff08;hash slots&#xff09;&#xff0c;这些槽均匀地分布在集群中的各个节点上。Redis集群并不支持传统的数据库切换&…

xiuno兔兔超级SEO插件(精简版)

xiuno论坛是一个一款轻论坛产品的论坛&#xff0c;但是对于这个论坛基本上都是用插件实现&#xff0c;一个论坛怎么能离开网站seo&#xff0c;本篇分享一个超级seo插件&#xff0c;自动sitemap、主动提交、自动Ping提交。 插件下载:tt_seo.zip

实验11 数据库日志及数据库恢复

一、 实验目的 了解Mysql数据库系统中数据恢复机制和主要方法。 二、 实验环境 操作系统&#xff1a;Microsoft Windows 7旗舰版&#xff08;32&64位&#xff09;/Linux。 硬件&#xff1a;容量足以满足MySQL 5.7&#xff08;8.0&#xff09;安装及后续实验的使用。 软件…

Python | Leetcode Python题解之第232题用栈实现队列

题目&#xff1a; 题解&#xff1a; class MyQueue:def __init__(self):self.A, self.B [], []def push(self, x: int) -> None:self.A.append(x)def pop(self) -> int:peek self.peek()self.B.pop()return peekdef peek(self) -> int:if self.B: return self.B[-1…

什么叫图像的中值滤波,并附利用OpenCV和MATLB实现均值滤波的代码

图像的中值滤波&#xff08;Median Filtering&#xff09;是一种非线性数字滤波技术&#xff0c;常用于图像处理以减少噪声&#xff0c;同时保留图像边缘细节。其基本思想是用图像中某个窗口内像素的中值替代该窗口中心像素的值。具体步骤如下&#xff1a; 选择窗口&#xff1a…

C++树(二)【直径,中心】

目录&#xff1a; 树的直径&#xff1a; 树的直径的性质&#xff1a; 性质1&#xff1a;直径的端点一定是叶子节点 性质2&#xff1a;任意点的最长链端点一定是直径端点。 性质3&#xff1a;如果一棵树有多条直径,那么它们必然相交&#xff0c;且有极长连…

STM32中PC13引脚可以当做普通引脚使用吗?如何配置STM32的TAMPER?

1.STM32中PC13引脚可以当做普通引脚使用吗&#xff1f; 在STM32单片机中&#xff0c;PC13引脚可以作为普通IO使用&#xff0c;但需要进行一定的配置。PC13通常与RTC侵入检测功能&#xff08;TAMPER&#xff09;复用&#xff0c;因此需要关闭TAMPER功能才能将其作为普通IO使用。…

服务端渲染框架:Nuxt.js 与 Next.js 的区别和对比

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…

2024国家护网面试小结

24年国护马上就要开始&#xff0c;基本上大部分蓝队红队都已经准备入场了 今年护网第一年变成常态化护网&#xff0c;由十五天突然变成了两个月常态化&#xff0c;导致今年护网有很多项目整的七零八落 博主今年参加了三家厂商蓝队护网面试&#xff0c;在这边分享一下护网面试…

掌握这些技巧,让你成为画册制作高手

在数字化的时代背景下&#xff0c;电子画册以其便捷的传播方式、丰富的视觉表现形式&#xff0c;赢得了大众的喜爱。它不仅能够在个人电脑上展现&#xff0c;还能通过智能手机、平板电脑等多种移动设备随时随地被访问和浏览。这种跨平台的支持&#xff0c;使得无论你身处何地&a…

Html_Css问答集(12)

99、将上例的0%改为30%&#xff0c;会如何变化&#xff1f; none:延迟2秒间无色&#xff0c;3.8秒&#xff08;0%-30%占1.8秒&#xff09;前无色&#xff0c;之后变红到5秒绿最后蓝&#xff0c;动画结束时恢复初始&#xff08;无色&#xff09;。 forward:延迟2秒间无色&am…

leetcode刷题总结——字符串匹配

KMP&#xff08;字符串匹配算法&#xff09; 主串或目标串&#xff1a;比较长的&#xff0c;我们就是在它里面寻找子串是否存在&#xff1b; 子串或模式串&#xff1a;比较短的。 前缀&#xff1a;字符串A和B&#xff0c;A BS&#xff0c;S非空&#xff0c;则B为A的前缀。 …

婚礼成本与筹备策略:一场梦幻婚礼的理性规划

婚礼成本与筹备策略&#xff1a;一场梦幻婚礼的理性规划 摘要 婚礼&#xff0c;作为人生中的重要仪式&#xff0c;承载着新人的爱情与梦想&#xff0c;同时也伴随着不菲的经济投入。本文旨在探讨婚礼所需的大致成本、影响成本的主要因素以及婚礼筹备过程中的关键注意事项&…

【Java--数据结构】二叉树

欢迎关注个人主页&#xff1a;逸狼 创造不易&#xff0c;可以点点赞吗~ 如有错误&#xff0c;欢迎指出~ 树结构 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限结点组成一个具有层次关系的集合 注意&#xff1a;树形结构中&#xff0c;子…

Transformer模型在多任务学习中的革新应用

在深度学习领域&#xff0c;多任务学习&#xff08;Multi-task Learning, MTL&#xff09;是一种训练模型以同时执行多个任务的方法。这种方法可以提高模型的泛化能力&#xff0c;因为它允许模型在不同任务之间共享知识。近年来&#xff0c;Transformer模型因其在自然语言处理&…

【linux高级IO(三)】初识epoll

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:Linux从入门到精通⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学更多操作系统知识   &#x1f51d;&#x1f51d; Linux高级IO 1. 前言2. 初识e…

STM32 HRTIM生成PWM时遇到无法输出PWM脉冲波形问题

在使用HRTIM生成PWM时&#xff0c;当把周期寄存器更新的设置放到while循环中时&#xff0c;无法输出PWM脉冲波形&#xff0c;即使增加计数延时也无法输出&#xff0c;最终只能放到中断函数中执行后期寄存器值更新才能够生成PWM脉冲波形。