Kubernets(k8s) 网络原理一:Pod与宿主机通信

news2024/11/15 23:50:34

对于刚接触K8S的同学来说,K8S网络显得尤为复杂,例如Pod如何访问主机以及pod间如何进行通信等。本系列文章将站在一个初学者角度,逐层刨析Kubernetes网络实现原理,并利用基本的Linux命令加以实现。

网络虚拟化基石:network namespace

network namespace 在Linux内核2.6版本引入,作用是隔离Linux系统设备,使得它们有独自的协议栈信息,一个直观的例子就是:每个容器都可以有自己的虚拟网络设备,并且容器内进程可以放心的绑定在端口上而不担心冲突。

和其他namespace一样, network namespace也可以通过系统调用来创建,同时也可以助 ip 命令来完成各种操作。ip 命令来自于 iproute2 安装包。

ip命令管理的功能很多,操作network namespce的命令为:ip netns,可以使用ip netns help获取帮助。下面先介绍几条间的的network namespace管理命令。

创建一个名为ns1的network namespace可以使用以下命令:

# ip netns add ns1

当创建出一个network namespace空间后,Linux内核将该namespace挂载至/var/run/netns路径下,此时可以使用ip netns exec 进入该namespae,执行网络查询或者配置工作。

# ip netns exec ns1 ip link
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

可以看到,一个新的network namespace 创建出来之后,其网卡信息中只包含了一块本地回环设备loopback,除此之外,防火墙规则、路由规则等也是一片空白。

想在主机上查看存在的network namespace,可以使用以下命令:

# ip netns list

想删除network namespace 可以通过以下命令实现

# ip netns delete ns1

虚拟设备桥梁:veth pair

veth pair是一种Linux内核技术,可用于连接两个虚拟网络接口,这两个虚拟网络接口总是成对出现,其工作原理就是向veth pair的一端发送的数据,数据经过协议栈后从另外一端出来。

正因为有一个特性,它常常充当着一个桥梁,连接着各种虚拟设备,典型例子就是:使用veth pair连接两个network namespace。

下面命令演示了如何创建一个veth pair:

# ip link add veth0 type veth peer name veth1
46: veth1@veth0: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether 22:18:89:26:fd:63 brd ff:ff:ff:ff:ff:ff
47: veth0@veth1: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether f6:ca:0d:bf:85:34 brd ff:ff:ff:ff:ff:ff

Pod和宿主机通信

接下来,我们使用network namespace和veth pair技术来模拟Pod和主机通信

使用过kubernets的同学应该知道,当我们创建一个Pod之后,CNI Plugin将会这个Pod下的所有containers分配一个network namespace。同时如果使用calico等CNI Plugin时,还能观察到Pod所在宿主机上会多出一些虚拟网卡,这些虚拟网卡一端连接Pod所在network namespace,一端连接在宿主机上。

所谓Pod,从网络的角度来看,就是共享一个ns的多个容器,这些容器在网络上与外界完全隔离,它们既访问不了外面,外面也访问不了它们。要向他们互相通信,就需要kubernetes cni来完成这项工作,下面我们就用Linux命令来完成cni所做的事。

首先,我们创建一个名为pod-1的network namespace

# ip netns add pod-1

我们用这个pod-1代表pod所在namespace,进入pod-1查看网络设备信息:除了loopback设备外一片空白。

# ip netns exec ns1 ip link
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

现在,我们创建一对 veth pair

# ip link add eth0 type veth peer name cali-001

将veth pair中的一块虚拟设备添加进pod-1

# ip link set eth0 netns pod-1

并且为eth0配置ip address,ip地址可以随意分配。

# ip netns exec pod-1 ip addr add 10.1.10.10 dev eth0

启动两块虚拟网络设备

# ip netns exec pod-1 ip link set dev eth0 up
# ip link set dev cali-001 up

进入pod-1,尝试ping一下host,这里我的host ip为:192.168.11.126

# ip netns exec pod-1 ping -c 1 192.168.11.126
connect: Network is unreachable

为什么会出现网络不可达呢,上文不是说veth pair的一端发送数据,会从另外一端出来吗?是上面说错了吗?其实不然,我们回想一下Linux基础网络知识:

当Linux需要向外发送一个数据包时,总是执行以下步骤:

  1. 查找该数据包目的地的路由信息,如果是直连路由,则在邻居表插在该目的地的MAC地址。
  2. 如果非直连路由,则在邻居表查找下一跳的MAC地址。
  3. 如果找不到对应路由信息,就报告”network is unreachable“。
  4. 如果邻居表没有相应MAC信息,则向外发送ARP请求询问。
  5. 找到MAC地址后,数据帧源MAC地址为发送网卡MAC地址,目标MAC则为下一跳MAC地址。

什么是直连路由和非直连路由

而这里,我们ping host,则会发出ICMP报文,因为没有路由,所以返回了network is unreachable

我们查看以下pod-1的路由信息

# ip netns exec pod-1 route
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface

发现路由表为空,上文我们说过,当一个network namespce创建出来之后,其路由表为空。既然这样,我们添加路由信息

# ip netns exec pod-1 ip route add 169.254.1.1 dev eth0
# ip netns exec pod-1 ip route add default via 169.254.1.1 dev eth0

169.254.1.1是CNI插件calico的默认的网关,后面系列文章会讲到

再次查看路由信息

# ip netns exec pod-1 ip route
default via 169.254.1.1 dev eth0 
169.254.1.1 dev eth0 scope link 

此时,我们看到路由表中多了一条非直连路由,意思是默认流量走eth0网卡,下一跳为169.254.1.1

然后我们再次尝试我们ping host

# ip netns exec pod-1 ping -c 1 192.168.11.126
PING 192.168.11.126 (192.168.11.126) 56(84) bytes of data.

--- 192.168.11.126 ping statistics ---
1 packets transmitted, 0 received, 100% packet loss, time 0ms

还是不行,按照刚才所说的Linux网络知识第2点,我们查看一下邻居表

# ip netns exec pod-1 ip neigh
169.254.1.1 dev eth0  FAILED

这里可以看到,eth0为FAILED,意味着获取不到网关的MAC地址,即整个网络中没有一张网卡是这个地址,我们可以抓包证明

# ip netns exec pod-1 tcpdump -n 
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
07:57:00.780329 ARP, Request who-has 169.254.1.1 tell 10.1.10.10, length 28

可以看到,我们在pod-1 ping host的时候,eth0发起ARP广播,请求169.254.1.1的MAC地址,但是没有任何响应。

再次回到Linux网络知识第4点,当前数据帧源地址为eth0的MAC地址,通过邻居表知道,下一跳是169.254.1.1。这时候如果能给169.254.1.1一个MAC地址,则数据帧就满足发送要求了,那把谁的地址给他呢?

答案是:veth pair的另外一端,即主机上的cali-001

那如何将cali-001的MAC地址给到169.254.1.1呢?

答案是:通过ARP欺骗,让cali-001作为代理ARP的角色,收到eth0的ARP请求时,代为应答,告诉eth0自己的MAC地址,而eth0收到响应,则认为是169.254.1.1的MAC地址,然后写入邻居表

可以通过以下命令设置网络设备开启代理APR

 # echo 1 > /proc/sys/net/ipv4/conf/cali-001/proxy_arp

同时打开转发功能

# echo 1 > /proc/sys/net/ipv4/ip_forward

我们再次尝试ping host

# ip netns exec pod-1 ping -c 1 192.168.11.126
PING 192.168.11.126 (192.168.11.126) 56(84) bytes of data.

--- 192.168.11.126 ping statistics ---
1 packets transmitted, 0 received, 100% packet loss, time 0ms

仍然访问不通,如果这时候抓包,你会发现,发送的arp包仍然没有响应。同时邻居表中也没有写入cali-001的MAC地址,实验环境为centos7,不排除其他OS可以。

这时你需要执行以下命令,关闭反向校验

# echo 0  >  /proc/sys/net/ipv4/conf/cali-001/rp_filter

# echo 0  >  /proc/sys/net/ipv4/conf/all/rp_filter

对于某个网卡而言,实际生效的值为 相应网卡的值与 all 值两者中的最大值

这时候,你再次ping host,可以通吗?

结果是仍然不同,但幸运的是,这时候你如果查看pod-1的邻居表,会发现169.254.1.1有MAC地址了。

# ip netns exec pod-1 ip neigh
169.254.1.1 dev eth0 lladdr aa:bc:80:1d:6d:29 REACHABLE


# ip addr
52: cali-001@if53: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether aa:bc:80:1d:6d:29 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet6 fe80::a8bc:80ff:fe1d:6d29/64 scope link 
       valid_lft forever preferred_lft forever

可以看到169.254.1.1的MAC地址就等于cali-001的MAC地址,我们的代理arp生效了!!!

我们在主机上抓包cali-001看一下

# tcpdump -pne -i cali-001
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on cali-001, link-type EN10MB (Ethernet), capture size 262144 bytes
11:20:40.322404 a2:37:06:54:4f:f6 > aa:bc:80:1d:6d:29, ethertype IPv4 (0x0800), length 98: 10.1.10.10 > 192.168.11.126: ICMP echo request, id 41357, seq 1, length 64

终于不是arp请求了,而是icmp报文,但是这个报文只有请求,没有回复。

是什么原因呢?此时pod-1与host的对话应该是这样

pod-1:我给你发送了icmp报文,你为什么不回复我?

host:我收到了报文,但是我不知道怎么回复你,我这里没有看到10.1.10.10的路由,所以我交给了默认网关

默认网关:这条报文的目标地址我不知道,我把它丢了

pod-1: ......

还是路由问题,我们在主机上添加直连路由

# ip route add  10.1.10.10 dev  cali-001 scope link 

然后,我们再次ping host

# ip netns exec pod-1 ping -c 1  192.168.11.126
PING 192.168.11.126 (192.168.11.126) 56(84) bytes of data.
64 bytes from 192.168.11.126: icmp_seq=1 ttl=64 time=0.024 ms

--- 192.168.11.126 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.024/0.024/0.024/0.000 ms

终于通了!!!!

还没完,这时候我们执行以下命令,开启反向校验

# echo 1  >  /proc/sys/net/ipv4/conf/cali-001/rp_filter

再次尝试ping host,发现仍然可以ping通,为什么会这样呢?

我们先来看内核参数rp_filter 的说明

含义
0关闭反向路由校验
1开启严格的反向路径校验。对每个进来的数据包校验其反向路径是否是最佳路径接收报文的网卡和回数据的网卡是否是同一张网卡。如果反向路径不是最佳路径则直接丢弃该数据包。
2开启松散的反向路径校验。对每个进来的数据包校验其源地址是否可达即反向路径是否能通通过任意网卡如果反向路径不通则直接丢弃

现在我们删除路由信息,并把rp_filter修改为1,看看发生了什么

# ip route del 10.1.10.10 dev cali-001
# echo 1 > /proc/sys/net/ipv4/conf/cali-001/rp_filter

要观察内核发生了什么,我们还需要打印syslog

# sysctl -w net.ipv4.conf.all.log_martians=1

该参数用于打印是否存在火星包(丢包)

一切准备就绪,还是在pod-1中ping host

# ip netns exec pod-1 ping -c 1  -I eth0 192.168.11.126
PING 192.168.11.126 (192.168.11.126) from 10.1.10.10 eth0: 56(84) bytes of data.

--- 192.168.11.126 ping statistics ---
1 packets transmitted, 0 received, 100% packet loss, time 0ms


# dmesg
[85071.926604] IPv4: martian source 169.254.1.1 from 10.1.10.10, on dev cali-001
[85071.926609] ll header: 00000000: ff ff ff ff ff ff a2 37 06 54 4f f6 08 06        .......7.TO...

# netstat -s | grep IPReversePathFilter
IPReversePathFilter: 10

上面展示了三条命令,第一条是pod-1 ping host,第二个是查看syslog信息,第三个是查看反向过滤拦截数。

从syslog日志中可以看到,出现了火星包,这个日志大意是:

cali-001上收到了src=10.0.10.10,dst=192.254.1.1的包,但是按照本机的路由设置对10.0.10.10进行路由计算,得出的out dev不是cali-001,主机路由如下:

# ip route
default via 192.168.11.2 dev ens33 proto static metric 100 
192.168.11.0/24 dev ens33 proto kernel scope link src 192.168.11.126 metric 100 

因为主机没有配置10.0.10.10路由,就会从默认路由,即ens33出去,所以校验失败,内核丢弃该包,所以pod-1内收不到Arp响应

当我们配置路由后,主机路由中就有了10.0.10.10的路由,并且这个路由对应的网卡就是cali-001,满足过滤条件,内核就不会丢弃该包,而是应答Arp请求。

总结

总结以下,上文我们创建了一对虚拟网卡cali-001/eth0,并将eth0分配给pod-1这个network namespace,然后在容器中发送了一个ICMP报文,数据流转如下:

  1. 在用户态中执行ping命令,通过socket调用给到pod-1协议栈
  2. pod-1协议栈准备发起ICMP报文,查找路由表,获知从eth0出去,下一跳是169.254.1.1
  3. pod-1协议栈查找Arp表,没有169.254.1.1的MAC地址,于是先发起Arp请求
  4. cali-001收到pod-1中eth0发来的Arp请求,因为配置了proxy_arp,于是响应自己的MAC地址
  5. pod-1协议栈收到响应,组装ICMP报文,发送给cali-001
  6. cali-001收到ICMP报文,交给自己的协议栈,即host协议栈
  7. host协议栈处理完ICMP报文,准备回复
  8. host协议栈先查自己的路由表,获知应该从cali-001出去
  9. host将响应报文从cali-001发出
  10. cali-001和eth0是一对veth pair,所以eth0收到响应报文
  11. eth0将报文交给pod-1的协议栈

后续

读完本文,你可能还有疑问,同宿主机上不同network namespace应该如何通信,以及跨主机不同network namespace应该如何通信,关于这些问题,在后续文章中将会展开介绍

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

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

相关文章

【Qt】QDial和QSlider

QDial QDial类用于创建一个旋转式的圆形控件&#xff0c;通过鼠标点击旋转、方向键或者pageUp和pageDown调整一个值。常用在需要进行连续调整的场景&#xff0c;比如音量控制、亮度控制、透明度调节等 常见属性 属性说明value持有的值minimum持有值所能到达的最小值maximum持有…

Fiddler抓包及设置

1、打开Fiddler.exe 2、设置过滤&#xff0c;只抓取具体网页或APP 3、勾选 Request Headers 中的 Hide if url contains 过滤项&#xff0c;贴入下方正则表达式&#xff1a;REGEX:(?index)/[^\?/]*\.(css|ico|jpg|png|gif|bmp|wav|js|jpeg|webp)(\?.*)?$&#xff0c;表示过…

云计算 docker 管理镜像和容器

docker的概述 命令说明docker version查看服务器与客户端版本docker info查看 docker 服务配置信息 容器安装部署dnf install -y docker-ce systemctl enable --now docker 配置镜像仓库 镜像概述 镜像管理命令 镜像管理命令说明docker images查看本机镜像docker pull 镜像名…

HDU1100——Trees Made to Order以及卡特兰数

HDU1100——Trees Made to Order 题目描述 Problem - 1100 运行代码 #include <iostream> #include <vector> using namespace std; vector<long long> C(21, 1); // 第21个卡特兰数达到65亿 // 预处理卡特兰数 void Catalan() {for (int i 1; i < 2…

网络学习:应用层DNS域名解析协议

目录 一、简介 二、工作流程 一、简介 DNS( Domain Name System)是“域名系统”的英文缩写&#xff0c;是一种组织成域层次结构的计算机和网络服务命名系统&#xff0c;它用于TCP/IP网络&#xff0c;它所提供的服务是用来将主机名和域名转换为IP地址的工作。 同时,DNS…

[Leetcode 875][Medium]-爱吃香蕉的珂珂-二分搜索

目录 一、题目描述 二 、整体思路 三 、代码 一、题目描述 原题地址 二 、整体思路 题目要求在时间h内(含h)&#xff0c;求解最小速度k。那么首先要知道速度与吃香蕉所用时间的关系。 假设速度为k&#xff0c;那么吃香蕉所用时间t就等于每堆香蕉piles[i]除以速度k所得的向…

电子元器件—电容和电感(一篇文章搞懂电路中的电容和电感)(笔记)(面试考试必备知识点)电容和电感作用、用途、使用、注意事项、特点等(面试必备)-笔记(详解)

作者&#xff1a;Whappy 座右铭&#xff1a;不曾拥有&#xff0c;何来失去&#xff01; 时间&#xff1a;2024年8月2日08:40:04 一、电容的作用 储能&#xff1a; 电容器通过充电储存电荷在电容板上&#xff0c;形成电场储存电能。当需要释放储存的电能时&#xff0c;电荷…

django集成pytest进行自动化单元测试实战

文章目录 一、引入pytest相关的包二、配置pytest1、将django的配置区分测试环境、开发环境和生产环境2、配置pytest 三、编写测试用例1、业务测试2、接口测试 四、进行测试 在Django项目中集成Pytest进行单元测试可以提高测试的灵活性和效率&#xff0c;相比于Django自带的测试…

回测本身就是一种过度拟合?

这也许是一个絮絮叨叨的专题&#xff0c;跟大伙儿唠一唠量化相关的小问题&#xff0c;有感而发写到哪算哪&#xff0c;这是第一期&#xff0c;先唠个10块钱的~ 前段时间在某乎上看到这样一个问题『您怎么理解回测本身就是一种过度拟合&#xff1f;』 个人看来&#xff0c;回测本…

JavaWeb学习——mybatis

目录 一、入门学习 1、什么是mybatis&#xff1f; 2、入门使用 3、配置SQL提示 4、数据库连接池 5、lombok 二、基础操作学习 1、删除 2、新增 3、更新 4、查询 三、XML配置文件 1、映射规范 2、示例代码展示 四、动态SQL 1、学习 2、学习 3、学习 4、学习 一…

TCP/IP协议:互联网通信的基础

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

【深度学习】【框架】【基本结构】激活函数

1. relu 2. softmax 3. sigmoid 4. silu 函数&#xff1a;f(x) x * sigmoid(x) 优点&#xff1a; 它既有 ReLU&#xff08;Rectified Linear Unit&#xff09;激活函数的一些优点&#xff08;例如&#xff0c;能够缓解梯度消失问题&#xff09;&#xff0c;又能解决 ReLU …

JavaEE---Spring MVC(2)

5.传递数组 当请求中参数是多个的时候,浏览器就会封装成一个数组 下面是在postman中返回的值 6.传递集合 运行的时候报错了,状态码是500,表示此时是服务器的错误,我们去查看后端源码发现 默认封装的是数组而不是List接口 修改方式: 此时我们就拿到了列表的值 状态码是HT…

文案生成器有哪些?4款为你一键生成原创文案

大家好&#xff01;今天来分享一波超级实用的干货——文案生成器&#xff01;在今天这个信息爆炸的时代&#xff0c;咱们无论是打理社交媒体&#xff0c;还是搞广告宣传等等&#xff0c;对优质文案的需求那是与日俱增。可有时候&#xff0c;灵感枯竭、时间紧迫&#xff0c;怎么…

测试类型分类

前言&#x1f440;~ 上一章我们介绍了如何设计一个测试用例&#xff0c;接下来我们对测试类型进行分类以便更好的了解和分清不同测试测试的内容、对象、时间点等 按照测试对象划分 界面测试&#xff08;也称UI测试&#xff09; 可靠性测试 容错性测试 文档测试 兼容性测…

vue2怎么上传文件夹,并展示文件夹内的图片?

我使用的是element-ui组件库,发现el-upload组件并不能满足需求,于是用原生实现一下,这里贴一下关键代码,如果大家有更好的实现方法,欢迎分享!! 实现效果:

Jangow-1.0.1靶机

一、安装Jangow: 1.0.1靶机 下载靶机&#xff0c;导入到virtualBox里面 开机可以看到&#xff0c;他已经给出了靶机的IP地址&#xff0c;就不用我们自己去探测了 二、信息收集 扫描靶机的端口 首先访问80端口 扫描目录也就是一个site 点击site&#xff0c;来到以下界面 发现…

进阶SpringBoot之自动装配原理

pom.xml&#xff1a; spring-boot-dependencies -> spring-boot-starter-parent spring-boot-dependencies&#xff1a;核心依赖在父工程&#xff0c;引入依赖时不需要指定版本 启动器&#xff1a;SpringBoot 的启动场景 <dependency><groupId>org.springfra…

机器学习练手(二):基于KMeans的股票分类

总结&#xff1a;本文为和鲸python 可视化探索训练营资料整理而来&#xff0c;加入了自己的理解&#xff08;by GPT4o&#xff09; 原活动链接 在前一关我们学习了逻辑回归&#xff0c;学会如何训练模型、数据基础性分析、如何处理空值等操作&#xff0c;下面我们开始新的一关…

AI+生命科学第二课:入门RNA和特征学习 【Datawhale AI夏令营】

教程链接&#xff1a;Task2&#xff1a;深入理解赛题&#xff0c;入门RNN和特征工程 打卡;https://linklearner.com/activity/12/4/4 在大佬讲解的基础上&#xff0c;带上一些我自己的理解 分析训练流程 从原始特征到输入模型 初始数据转换为tensor后&#xff0c;将x通过fo…