专栏首页深入理解k8s网络原理深入理解kubernetes(k8s)网络原理之一-pod连接主机
原创

深入理解kubernetes(k8s)网络原理之一-pod连接主机

深入理解kubernetes(k8s)网络原理之一-pod连接主机

对于刚接触k8s的人来说,最令人懵逼的应该就是k8s的网络了,如何访问部署在k8s的应用,service的几种类型有什么区别,各有什么使用场景,服务的负载均衡是如何实现的,与haproxy/nginx转发有什么区别,网络策略为什么不用限制serviceIP等等

本文将站在一个初学者的角度,采用一边讲解一边实践的方式把k8s的网络相关的原理慢慢剖析清楚,并且用普通的linux命令把pod/serviceIP/nodePort等场景都模拟出来;

本文比较适合刚接触k8s,对docker有一些了解,有一定计算机基础的童鞋,在浏览本文时,各位童鞋可以准备个centos7.6的环境,安装docker和iproute2工具包,一边看一边操作,加深理解。

OK,走起!

一些关于linux网络的知识

在开始之前,有一些关于linux网络的知识你需要先知道,就像做数学题之前,先得理解公式一样。

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

  1. 查找到该数据包的目的地的路由信息,如果是直连路由(不知道什么是直连路由?没关系,后面会解释),则在邻居表中查找该目的地的MAC地址
  2. 如果非直连路由,则在邻居表中找下一跳的MAC地址
  3. 如果找不到对应的路由,则报“network is unreachable”
  4. 如果在邻居表中没有查找到相应的MAC信息,则向外发送ARP请求询问
  5. 发送出去的数据帧,源MAC地址为发送网卡的MAC地址,目标MAC则是下一跳的MAC,只要不经过NAT,那么源目地IP是全程不会变化的,而MAC地址则每一跳都会变化

每当linux收到一个数据帧时,总会执行以下步骤:

  1. 如果数据帧目标MAC地址不是收包网卡的MAC,也不是ff:ff:ff:ff:ff:ff(ARP广播),且网卡未开启混杂模式,则拒绝收包;
  2. 如果数据帧目标MAC为ff:ff:ff:ff:ff:ff,则进入arp请求处理流程;
  3. 如果数据帧目标MAC地址是收包网卡的MAC,且是IP包,则:
    • 目标IP地址在本机,则上送到上一层协议继续处理;
    • 目标IP地址不在本机,则看net.ipv4.ip_forward是否为1,如果为1,则查找目标IP的路由信息,进行转发;
    • 目标IP地址不在本机,且net.ipv4.ip_forward为0,则丢弃

一些常用的命令

ip link ##查看网卡信息
ip addr ##查看网卡IP地址
ip route ##查看路由信息
ip neigh ##查看邻居表信息

##上面的命令均可简化,就是第二个单词的首字母,
##例如ip link可以简化为ip l,ip addr可以简化为ip a,以此类推……

iptables-save ##查看所有iptables规则

然后,我们就正式开始了,因为k8s的网络主要都是要解决怎么访问pod和pod怎么访问外面的问题,所以先来了解一下什么是pod

pod是什么

现在的服务器一般配置都比较高,64核256G的配置,如果一台服务器只用来跑一个java程序,显然就太浪费了,如果想资源利用率高一些,可以用qemu-kvm或vmware等软件进行虚拟化,让多个java进程分别运行在虚拟机里,这样可以相互不受影响;

但虚拟化难免会带来一些资源损耗,而且要先拉起一台虚拟机再在里面启动一个java进程也会比直接在裸金属服务上启动一个java进程要耗费更多的时间,只是从运行几个java进程的角度来说,虚拟化并非资源利用的最优解;

如果不用虚拟化,直接同时在裸金属服务上运行多个java进程,就要解决各个进程CPU内存资源占用、端口冲突、文件系统冲突等几个问题,否则就会出现:

  • 一个进程消耗了大量的内存和CPU,而另一个更重要的进程却得不到资源;
  • 80端口只有一个,进程A用了,进程B就用不了
  • 多个进程同时操作相同的目录或读写相同的文件,造成异常

因为需要让多个进程都能高效地相互不受影响地运行,所以容器技术出现了,其中又以docker最为流行,容器解决了多进程间的环境隔离:

  • 资源隔离,使用linux control group(简称:cgroup)解决各进程cpu和内存、io的资源分配问题
  • 网络隔离,使用linux network namespace(下面开始简称:ns)使各个进程运行在独立的网络命名空间,使各类网络资源相互隔离(网卡、端口、防火墙规则、路由规则等)
  • 文件系统隔离,使用union fs,例如:overlay2/aufs等,让各个进程运行在独立的根文件系统中

而所谓的pod,就是共享一个ns的多个容器

但是,什么叫“共享一个ns的多个容器”?

每当我们用docker运行一个容器,默认情况下,会给这个新的容器创建一个独立的ns,多个容器间相互访问只能使用对方IP地址

docker run -itd --name=pause busybox
docker run --name=nginx -d nginx

此时要在pause中访问nginx,先查找一下nginx容器的IP地址:

docker inspect nginx|grep IPAddress

"IPAddress": "172.17.0.8",

然后在pause容器中用刚查到的IP访问:

docker exec -it pause curl 172.17.0.8

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
....
</body>
</html>

但其实是可以让nginx容器加入pause容器的ns,用下面的命令可以模拟:

docker run -itd --name=pause busybox
docker run --name=nginx --network=container:pause -d nginx

此时pause容器和nginx容器在相同的ns中,相互间就可以用localhost访问对方了,可以用下面的命令验证:

docker exec -it pause curl localhost

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
....
</body>
</html>

pause容器和nginx容器就是“共享一个ns的两个容器”,所以,pause容器和nginx容器加起来,就是k8s的pod

在k8s集群的节点中使用docker ps,总是会发现一堆名为pause的容器,就是这个原因,pause是为多个业务容器提供共享的ns的

可以用下面的命令进入之前用docker创建的pause容器的ns

先获取pause容器的pid

docker inspect pause|grep Pid

            "Pid": 3083138,

nsenter命令进入指定pid的ns

nsenter --net=/proc/3083138/ns/net

此时我们已经在pause容器的ns中了,可以查看该ns的网卡,路由表,邻居表等信息

ip addr show 

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
605: eth0@if606: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.17.0.7/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever
ip route

default via 172.17.0.1 dev eth0
172.17.0.0/16 dev eth0 proto kernel scope link src 172.17.0.2

从网络的角度看,一个pod就是一个独立的ns,解决了ns的网络进出问题,就解决了pod的网络问题,所以下面的讨论就都以ns为主,不再频繁创建docker容器,后面的描述不管是pod还是ns,请统一理解为ns就好

认识ns

对于一台linux主机来说,影响网络方面的配置主要有以下几个:

  • 网卡:启动时初始化,后期可以添加虚拟设备;
  • 端口:1到65535,所有进程共用
  • iptables规则:配置进出主机的防火墙策略和NAT规则
  • 路由表:到目标地址的路由信息
  • 邻居表:与主机在同个二层网络(什么是同个二层网络?大概可以理解为在一台交换机上,彼此之间通过ARP找到对方的)的其它主机的MAC地址与IP地址的映射关系

对于每一个ns来说,这几个配置都是独立的,所以从网络的角度来说,当你创建一个新的ns,其实就相当于拥有了一台新的主机

用下面的命令创建新的ns

ip netns add ns1 ##创建ns

然后我们可以用ip netns exec ns1为前缀来执行命令,这样显示的结果就都是ns1的网络相关配置:

ip netns exec ns1 ip link show

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

可以看到,我们用ip命令创建的ns除了一张lo网卡外,其它都是空白,这样一来她里面运行的程序访问不了外面,外面也找不到她,需要我们一点点地去组织,这就是k8s的cni要做的事情

上面我们用docker启动的容器中,网卡,IP,路由都是docker网络管理(cnm)给组织好的,从单节点的网络管理来看,cni和cnm做的事情还是挺像的

下面我们就一步步来完成k8s的cni做的事情,第一步是让主机和pod能相互访问

主机与pod相互访问

首先给ns1增加一张与主机连接的网卡,这里会用到linux虚拟网络设备veth网卡对,对于veth,基本上可以理解为两张网卡中间连着线,一端发送会触发另一端接收,用下面的命令创建:

ip link add ns1-eth0 type veth peer name veth-ns1  ## 增加一对veth网卡,名为ns1-eth0和veth-ns1
ip link set ns1-eth0 netns ns1  ## 其中一端挪到刚才创建的ns1中,另一端留在主机端,这样主机和ns就连接起来了
ip link set veth-ns1 up ##启动主机端的网卡veth-ns1
ip netns exec ns1 ip addr add 172.20.1.10/24 dev ns1-eth0  ##此时ns1-eth0已经在ns1中了,所以要进去ns1中去执行命令设置网卡IP
ip netns exec ns1 ip link set ns1-eth0 up ##启动ns1端的网卡ns1-eth0

笔者使用的主机的IP是192.168.6.160,此时尝试从ns1中ping主机,能通了吗?

ip netns exec ns1 ping 192.168.6.160

connect: Network is unreachable

ping不通主机,此时的情况就是上面提到的linux知识点中关于发送数据包的第3点,因为没有到目的地的路由,ns1不知如何去192.168.6.160,在这里我们要给ns1增加一条默认路由,所有没有显式声明路由的数据包都会走默认网关:

ip netns exec ns1 ip route add default via 172.20.1.1 dev ns1-eth0 

去看一下ns1中的路由表,已经有两条路由信息

ip netns exec ns1 ip route

default via 172.20.1.1 dev ns1-eth0  ##这是一条非直连路由,意思是默认流量走ns1-eth0网卡,下一跳为172.20.1.1
172.20.1.0/24 dev ns1-eth0 proto kernel scope link src 172.20.1.10  ## 这是一条直连路由,没有via的就是直连路由,这是我们给网卡设置IP时系统自动增加的

此时能通了吗?

ip netns exec ns1 ping 192.168.6.160
 
PING 192.168.6.160 (192.168.6.160) 56(84) bytes of data.

还是不行,定在那里半天没反应,又卡在哪了呢?还是linux知识点中关于发送数据包的第2点,如果是非直连路由,会先去拿下一跳的MAC地址,下一跳是172.20.1.1,能获取到它的MAC地址吗?用下面的命令查一下邻居表:

ip netns exec ns1 ip neigh
172.20.1.1 dev ns1-eth0  FAILED

很明显,获取不到,因为网关IP地址确实是个不存在的地址,但其实这个地址不需要是个存在的地址(所以你会看到在calico中,容器的默认网关是个168.254开头的地址),因为这个地址其实是用不到的,网关的IP是不会出现在pod发送的数据包中的,真正需要用到的是网关的mac地址,我们的目的是要得到主机端veth-ns1的mac地址,有两个方法:

  • 设置对端的网卡的arp代答,ns1-eth0的对端是主机上的veth-ns1网卡
echo 1 > /proc/sys/net/ipv4/conf/veth-ns1/proxy_arp 

这样就开启了veth-ns1的arp代答,只要收到arp请求,不管目标IP是什么,veth-ns1网卡都会把自己MAC地址回复回去

  • 把网关地址设置在对端的网卡上

在这里我们用第一种方式,设置后再查一下邻居表:

ip netns exec ns1 ip neigh
 
172.20.1.1 dev ns1-eth0 lladdr b6:58:7b:0e:35:b3 REACHABLE

可以看到,已经拿到网关的MAC地址了,这个地址也确实就是主机端veth-ns1的地址:

 ip link show veth-ns1
607: veth-ns1@if608: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether b6:58:7b:0e:35:b3 brd ff:ff:ff:ff:ff:ff link-netns ns1

这下总该行了吧?兴高采烈地ping一下,发现依旧不行

oh shit!!

什么垃圾!!

入门到放弃!!!

先别急着崩溃,主机委屈说:数据包我收到了,只是我不知道怎么回给你,因为我这里没有到172.20.1.10的路由,所以我就把回包给了我的默认网关……

坚持住,只需最后一步,在主机上添加到pod的直联路由

ip route add 172.20.1.10 dev veth-ns1  ## 这也是一条直连路由,注意是添加主机的路由,所以不用ip netns exec ns1开头了

此时从ns1中ping主机:

ip netns exec ns1 ping -c 5 192.168.6.160

PING 192.168.6.160 (192.168.6.160) 56(84) bytes of data.
64 bytes from 192.168.6.160: icmp_seq=1 ttl=64 time=0.025 ms
64 bytes from 192.168.6.160: icmp_seq=2 ttl=64 time=0.017 ms
64 bytes from 192.168.6.160: icmp_seq=3 ttl=64 time=0.015 ms
64 bytes from 192.168.6.160: icmp_seq=4 ttl=64 time=0.017 ms
64 bytes from 192.168.6.160: icmp_seq=5 ttl=64 time=0.017 ms
--- 192.168.6.160 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 135ms
rtt min/avg/max/mdev = 0.015/0.018/0.025/0.004 ms

从主机ping ns1

ping -c 5 172.20.1.10

PING 172.20.1.10 (172.20.1.10) 56(84) bytes of data.
64 bytes from 172.20.1.10: icmp_seq=1 ttl=64 time=0.026 ms
64 bytes from 172.20.1.10: icmp_seq=2 ttl=64 time=0.015 ms
64 bytes from 172.20.1.10: icmp_seq=3 ttl=64 time=0.016 ms
64 bytes from 172.20.1.10: icmp_seq=4 ttl=64 time=0.015 ms
64 bytes from 172.20.1.10: icmp_seq=5 ttl=64 time=0.016 ms
--- 172.20.1.10 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 138ms
rtt min/avg/max/mdev = 0.015/0.017/0.026/0.006 ms

OK,成功完成第一步,pod与主机互通,此时在pod里能访问百度吗?

答案是不行,172.20.1.10这个地址是我们随便模拟出来的,除了当前这台主机谁也不认识,所以如果他要访问外网,需要做源地址转换,这个场景跟我们办公室的PC上外网原理是一样的,当你用办公室内网的电脑去访问百度时,数据包到达百度的服务器上时源地址肯定不是你电脑的内网网卡地址,而是你的办公网络出外网的出口IP加一个随机端口,这个源地址转换是你的办公室网络出口路由器自动帮你完成的,我们在主机上也要配置针对刚才创建的pod的源地址转换规则

pod访问外网

  • 首先第一步要打开本机的ip转发功能
echo 1 > /proc/sys/net/ipv4/ip_forward
  • 然后是设置snat规则
iptables -A POSTROUTING -t nat -s 172.20.1.10 -j MASQUERADE

此时再试一下已经可以ping通百度

ip netns exec ns1 ping -c 5 www.baidu.com

PING www.a.shifen.com (14.215.177.38) 56(84) bytes of data.
64 bytes from 14.215.177.38 (14.215.177.38): icmp\_seq=1 ttl=54 time=9.47 ms
64 bytes from 14.215.177.38 (14.215.177.38): icmp\_seq=2 ttl=54 time=9.42 ms
64 bytes from 14.215.177.38 (14.215.177.38): icmp\_seq=3 ttl=54 time=9.42 ms
64 bytes from 14.215.177.38 (14.215.177.38): icmp\_seq=4 ttl=54 time=9.33 ms
64 bytes from 14.215.177.38 (14.215.177.38): icmp\_seq=5 ttl=54 time=9.58 ms
--- www.a.shifen.com ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 9ms
rtt min/avg/max/mdev = 9.332/9.443/9.582/0.102 ms

至于上面的iptables规则和为何要开启转发,下一章解释serviceIP时再详细介绍。

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 深入理解kubernetes(k8s)网络原理之三-跨主机pod连接

    在之前的两篇文章中分别介绍了pod与主机连接并且上外网的原理及service的clusterIP和nodeport的实现原理,对于组织pod的网络这件事来说,还...

    一生无聊
  • 深入理解kubernetes(k8s)网络原理之六-同主机pod连接的几种方式及性能对比

    本来说这一篇是要撸个cni出来的,但感觉这个没什么好说的,cni的源码github一大堆了,大概的套路也就是把前面说的命令用go语言实现一下,于是本着想到啥写啥...

    一生无聊
  • 深入理解kubernetes(k8s)网络原理之四-pod流量控制

    在前面的几篇文章中,我们解决了pod连接主机、pod连接外网、pod与相同节点或不同节点的pod连接、用clusterIP和nodeport的方式访问pod等几...

    一生无聊
  • 深入理解kubernetes(k8s)网络原理之五-flannel原理

    flannel有udp、vxlan和host-gw三种模式,udp模式因为性能较低现在已经比较少用到,host-gw我们在前面简单介绍过,因为使用场景比较受限,...

    一生无聊
  • 深入理解kubernetes(k8s)网络原理之二-service原理

    上一篇文章中我们介绍了pod与主机互通及pod访问外网的原理,在这一章我们将介绍pod与pod的相互访问以及外部服务访问pod的方式,由此引出k8s的servi...

    一生无聊
  • 了解 Kubernetes

    Docker 虽好用,但面对强大的集群,成千上万的容器,突然感觉不香了。这时候就需要我们的主角 Kubernetes 上场了,先来了解一下 Kubernetes...

    耕耘实录
  • 为容器时代设计的高级 eBPF 内核特性(FOSDEM, 2021)

    本文翻译自 2021 年 Daniel Borkmann 在 FOSDEM 的一篇分享:Advanced eBPF kernel features for th...

    米开朗基杨
  • docker bridge 到 k8s pod 跨节点网络通信机制演进

    2020 还没来得及品味就即将过去一个季度,愿剩下的时光不被辜负。进入正题,docker container是单进程模式,能够解决一些单一的问题,在现实中,我们...

    用户5166556
  • k8s网络开发丨k8s与OpenStack网络如何打通?

    点击上方“腾讯云TStack”关注我们 获取最in云端资讯和海量技术干货 ? ? 本文作者 / ice yao 喜欢看动漫的IT男 还是火影迷、海贼迷、死神...

    腾讯云TStack
  • k8s基本原理

    前面hello world程序中,对于如何访问到服务,有必要了解一下k8s的网络模型,在这之前先介绍docker的网络模型

    kinnylee
  • 用Kubernetes部署超级账本Fabric的区块链即服务(1)

    【注:下载本文PDF版本以及本文源代码,可关注本公众号:亨利笔记,后台发送消息“区块链即服务” 或 “baas”即可。】

    Henry Zhang
  • 「走进k8s」Kubernetes基本概念和组件(13)

    k8s为每个pod分配了唯一的IP地址,一个pod里的多个容器共享pod IP。 pod其实有两种类型:普通的pod和静态pod,后者比较特殊,它并不存放在et...

    IT架构圈
  • K8s 系列(一) - 知识图谱

    Kubernetes(K8s) 作为当前最知名的容器编排工具,称得上是云原生(Cloud Native)时代的“操作系统”,熟悉和使用它是研发、运维、产品等的必...

    astraw99
  • kubernetes集群网络

    问题:Pod是K8S最小调度单元,一个Pod由一个容器或多个容器组成,当多个容器时,怎么都用这一个Pod IP?

    yuezhimi
  • 2.2 Kubernetes--网络通讯

      k8s的网络模型假定了所有的Pod都在一个可以直接连通的扁平的网络空间中, 这在GCE(Google Compute Engine)里面是线程的网络模型, ...

    用户7798898
  • Kubernetes架构学习笔记

    Kubernetes是Google开源的容器集群管理系统,其提供应用部署、维护、 扩展机制等功能,利用Kubernetes能方便地管理跨机器运行容器化的应用,是...

    kubernetes中文社区
  • ASP.NET Core on K8S深入学习(11)K8S网络知多少

    本篇已加入《.NET Core on K8S学习实践系列文章索引》,可以点击查看更多容器化技术相关系列文章。

    Edison Zhou
  • 简单5步,轻松debug K8s服务!

    在Kubernetes中,服务是一个核心概念。在本文中,将介绍如何调试K8S服务,这些服务是由多个Pod组成的工作负载的抽象接口(主机+端口)。

    CNCF
  • 开工必备!50+篇超实用云原生技术干货合集

    kai 开 gong 工 da 大 ji 吉 新年新气象,更要1G棒 2020年没写完的代码,现在还有思路吗? 2021年开始使用云原生技术了吗? 一开工就遇...

    腾讯云原生

扫码关注云+社区

领取腾讯云代金券