今天研究了一下k8s的网络模型,该解析基于flannel vxlan+ kubeproxy iptables 模式。

一.Docker

首先分析一下Docker层面的网络模型,我们知道容器是基于内核的namespace机制去实现资源的隔离的。network是众多namespace中的一个,那么如何保证一个节点上容器之间的通信呢?Docker的做法是通过虚拟网桥来桥接虚拟网卡。下面具体解释一下。

首先每一个容器在默认情况下都是在自己的network namespace里面的,也就是说默认情况下它只有一个自己独立的localhost网络(或者是什么网络设备也没有?TBD),无法与外部进行通信。为了解决这个问题,Docker创建了一对veth pair, 这个veth pair总是承兑出现,可以理解为一对端口,所有从一头进去的数据都会从另一头出来。然后docker 会把这对veth pair的一头加入到容器的namespace中,另一头桥接到一个虚拟网桥上, 这个虚拟网桥实际上就是宿主机上的docker0网卡,我们可以通过以下指令来观察:

[wlh@meizu storage]$ brctl show
bridge name bridge id STP enabled interfaces
docker0 .02422551422b no veth43dc241
           veth551eae5
           veth844b02c
vethd06364a
vethe95e44c

上图可以看到docker0上面桥接的各个容器的veth设备,这样容器内的通信就可以沿着vethA-1 -> vethA-2 -> docker0 -> vethB-2 -> vethB-1流动了

2. Flannel

Docker实现了同一节点上容器之间的通信,那么k8s作为一个容器编排平台,如何实现不同节点上容器的通信呢?这需要第三方插件的支持,目前有多种overlay network解决方案,这里介绍其中比较简单的一种, flannel。flannel目前支持三种工作模式:vxlan, udp, host-gw,其中udp和vxlan比较像,udp是flannel程序自己在用户态下将报文封装,而vxlan是内核对报文进行处理,因此udp会比较慢。所以udp不推荐在生产环境下使用,只是用于debug。而host-gw模式需要所有节点与其他任一节点间都有直接路由(具体可以查阅相关文章), 这里我们使用vxlan作为工作模式进行讲解。

在工作的时候,flannel会从k8s的etcd存储中同步数据,包括使用的工作模式和集群中其它节点的子网。例如,在我的机器上,其etcd中存储的数据为:

 [wlh@xiaomi xuexi]$ etcdctl ls /kube-fujitsu/network
/kube-fujitsu/network/config
/kube-fujitsu/network/subnets [wlh@xiaomi xuexi]$ etcdctl get /kube-fujitsu/network/config
{"Network":"172.30.0.0/16","SubnetLen":,"Backend":{"Type":"vxlan"}} [wlh@xiaomi xuexi]$ etcdctl ls /kube-fujitsu/network/subnets
/kube-fujitsu/network/subnets/172.30.20.0-
/kube-fujitsu/network/subnets/172.30.44.0-
/kube-fujitsu/network/subnets/172.30.83.0- [wlh@xiaomi xuexi]$ etcdctl get /kube-fujitsu/network/subnets/172.30.83.0-
{"PublicIP":"10.167.226.38","BackendType":"vxlan","BackendData":{"VtepMAC":"b6:c7:0f:7f:66:a7"}}

这里第6行中的172.30.0.0/16表示的是整个集群的子网段, 而8/9/10三行分别代表了三个节点,每创建一个新的节点,都会从172.30.0.0/16中再分配一个子网给它。各个节点上的flannel进程读取etcd中的这些配置,然后修改自己节点上的docker进程的启动参数,在其中添加一个--bip=172.30.20.1/24,这样该节点上docker启动的所有容器都会在这个子网段里。通过这些设定,保证了集群中所有的容器之间ip地址是不会重复的。

解决了容器ip地址重复的问题后,下面就是实现容器跨节点通信了。在vxlan模式下,flannel会在节点上创建一个虚拟网卡叫flannel.1,它的MAC地址就是上面输出中的VtepMAC。同样的节点的路由表也会被修改,如下图所示:

 [wlh@meizu storage]$ route
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
//....more
172.30.20.0 0.0.0.0 255.255.255.0 U docker0
172.30.44.0 172.30.44.0 255.255.255.0 UG flannel.
172.30.83.0 172.30.83.0 255.255.255.0 UG flannel.

这里可以看到,目的地址为172.30.20.0/24的都会被发到docker0,这其实就是本主机上的容器。而其他节点上的容器则会被路由到flannel.1网卡上进行处理。flannel将flannel.1网卡上收到的数据进行处理,加上flannel规定好的报文头,然后从绑定的网卡中发出去。这个封装好的报文是udp协议,目标地址是容器所在的节点的物理地址,并且其默认端口是8472(udp模式的默认端口是8285)。也就是说vxlan模式的底层实现也是用udp报文发送的,只是vxlan模式中报文封装是在内核态中完成,而udp模式中报文封装是在用户态完成。目标容器所在的主机上,flannel会监听8472端口,去掉报文的flannel头,然后传送给docker0网卡,docker0网卡收到的就是普通的容器通信的报文,不会感知到底层的这些处理。

3. kube-proxy

我们知道,k8s中有service的概念,它拥有自己的ip地址。那么对service的访问是如何分发给后端的pod呢。这些工作是由kube-proxy完成的,它有三种工作模式,userspace(older), iptables(faster),ipvs(experimental)。其中userspace是早期的模式,它本质上是利用kube-proxy做一个代理,所有对service的访问都会转发给kube-proxy组件,然后由它再分发请求到pod。显然这种模式对于一个大规模集群来说是一个速度瓶颈。iptables模式是通修改iptable来实现请求分发的。ipvs模式不太了解。

下面以一个例子来具体说明iptables模式。首先创建下面列出的deployment和service:

 apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
labels:
name: nginx
spec:
selector:
matchLabels:
name: nginx1
replicas:
template:
metadata:
labels:
name: nginx1
spec:
nodeName: meizu
containers:
- name: nginx
image: nginx
ports:
- containerPort:
---
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
name: nginx1
spec:
ports:
- port:
targetPort:
selector:
name: nginx1
[wlh@xiaomi xuexi]$ kubectl get pod -o wide|grep nginx
nginx-cb648c7f5-c8h26       1/1     Running   0          24m    172.30.20.7   meizu    <none>           <none>
nginx-cb648c7f5-pptl9       1/1     Running   0          40m    172.30.20.6   meizu    <none>           <none>
nginx-cb648c7f5-zbsvz       1/1     Running   0          24m    172.30.20.8   meizu    <none>           <none> [wlh@xiaomi xuexi]$ kubectl get svc -o wide
nginx        ClusterIP   10.254.40.119   <none>        4432/TCP   38m    name=nginx1

这里创建一个service,在4432端口向外提供简单的nginx service。观察到这些资源被创建以后,kube-proxy会在节点上的iptables的NAT表中添加以下规则:

[wlh@meizu storage]$ sudo iptables-save|grep nginx
-A KUBE-SERVICES ! -s 10.254.0.0/ -d 10.254.40.119/ -p tcp -m comment --comment "default/nginx: cluster IP" -m tcp --dport -j KUBE-MARK-MASQ
-A KUBE-SERVICES -d 10.254.40.119/ -p tcp -m comment --comment "default/nginx: cluster IP" -m tcp --dport -j KUBE-SVC-4N57TFCL4MD7ZTDA
[wlh@meizu storage]$ sudo iptables-save|grep 0x4000/0x4000
-A KUBE-MARK-MASQ -j MARK --set-xmark 0x4000/0x4000
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -m mark --mark 0x4000/0x4000 -j MASQUERADE
-A KUBE-FORWARD -m comment --comment "kubernetes forwarding rules" -m mark --mark 0x4000/0x4000 -j ACCEPT

输出的第一行是做一个标记,意思是所有发往10.254.40.118:4432(nginx服务)的请求(除了source ip 为10.254.0.0/16的报文)都会被打上一个标记,这个报文被打上这个标记后会在filter表中进行后续处理。在filter表中会对打上标记的报文进行MASQUERADE处理,实际上就是SNAT,将报文的source ip地址转化为本地主机物理网卡的地址,然后再发出去,否则如果直接用容器的ip地址的话,物理网络很显然是不会认识这个地址的。

输出的第二行的作用是所有发往10.254.40.119:4432(也就是service) 的地址,全部跳到KUBE-SVC-4N57TFCL4MD7ZTDA进行处理,那么这个KUBE-SVC-4N57TFCL4MD7ZTDA 是啥呢?

 [wlh@xiaomi prensentation]$ sudo iptables-save|grep KUBE-SVC-4N57TFCL4MD7ZTDA
:KUBE-SVC-4N57TFCL4MD7ZTDA - [:]
-A KUBE-SERVICES -d 10.254.40.119/ -p tcp -m comment --comment "default/nginx: cluster IP" -m tcp --dport -j KUBE-SVC-4N57TFCL4MD7ZTDA
-A KUBE-SVC-4N57TFCL4MD7ZTDA -m statistic --mode random --probability 0.33332999982 -j KUBE-SEP-TMIUL2YW4YRKUWF7
-A KUBE-SVC-4N57TFCL4MD7ZTDA -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-H2SQHV5FZD5TQOIZ
-A KUBE-SVC-4N57TFCL4MD7ZTDA -j KUBE-SEP-GK74E3IZTU4ZAMUJ

这里的输出中,第二行是统计流量的,和我们本篇文章关系不大。第三行之前分析过了, 第四五六行实际上就是load balance, 这里集群启动了三个pod作为这个service的后端,对应的有三个服务节点,分别被赋予了访问概率。四五六行中最后的KUBE-SEP自然就是对应了各自的pod了。以第四行为例:

 [wlh@xiaomi prensentation]$ sudo iptables-save|grep KUBE-SEP-TMIUL2YW4YRKUWF7
:KUBE-SEP-TMIUL2YW4YRKUWF7 - [:]
-A KUBE-SEP-TMIUL2YW4YRKUWF7 -s 172.30.20.6/ -j KUBE-MARK-MASQ
-A KUBE-SEP-TMIUL2YW4YRKUWF7 -p tcp -m tcp -j DNAT --to-destination 172.30.20.6:
-A KUBE-SVC-4N57TFCL4MD7ZTDA -m statistic --mode random --probability 0.33332999982 -j KUBE-SEP-TMIUL2YW4YRKUWF7

第四行定义了具体的下一跳,这边的意思是所有跳到这里的请求全部进行DNAT操作,将目标地址改为172.30.20.6:80。这样结合前面的利用KUBE-MARK-MASQ打标记然后转换源地址的操作,整个访问就变成:

source ip            des ip

pod ip          ->      service

host ip         ->      backend pod ip

k8s 网络模型解析之原理的更多相关文章

  1. k8s 网络模型解析之实践

    一. 实践说明 首先我们先创建一组资源,包括一个deployment和一个service apiVersion: apps/v1 kind: Deployment metadata: name: ng ...

  2. k8s 网络模型

    一.前言 k8s对Pods之间如何进行组网通信提出了要求,k8s对集群的网络有以下要求: 所有的Pods之间可以在不使用NAT网络地址转换的情况下相互通信 所有的Nodes之间可以在不使用NAT网络地 ...

  3. [转帖]我花了10个小时,写出了这篇K8S架构解析

    我花了10个小时,写出了这篇K8S架构解析 https://www.toutiao.com/i6759071724785893891/   每个微服务通过 Docker 进行发布,随着业务的发展,系统 ...

  4. k8s网络模型与集群通信

    在k8s中,我们的应用会以pod的形式被调度到各个node节点上,在设计集群如何处理容器之间的网络时是一个不小的挑战,今天我们会从pod(应用)通信来展开关于k8s网络的讨论. 小作文包含如下内容: ...

  5. 浏览器解析JavaScript原理

    1.浏览器解析JavaScript原理特点: 1.跨平台 2.弱类型 javascript 定义的时候不需要定义数据类型,数据类型是根据变量值来确定的.    var a = 10; 数字类型    ...

  6. k8s 理解Service工作原理

    什么是service? Service是将运行在一组 Pods 上的应用程序公开为网络服务的抽象方法. 简单来说K8s提供了service对象来访问pod.我们在<k8s网络模型与集群通信> ...

  7. K8s无状态控制器原理介绍

    Pod控制器: ReplicationController:早期K8s只有这一个控制器,但后来发现让这一个来完成所有任务,太复杂.因此被废弃. ReplicaSet: 它用于帮助用户创建指定数量的Po ...

  8. 【Kubernetes】两篇文章 搞懂 K8s 的 fannel 网络原理

    近期公司的flannel网络很不稳定,花时间研究了下并且保证云端自动部署的网络能够正常work. 1.网络拓扑 拓扑如下:(点开看大图)  容器网卡通过docker0桥接到flannel0网卡,而每个 ...

  9. SpringMVC源码之参数解析绑定原理

    摘要 本文从源码层面简单讲解SpringMVC的参数绑定原理 SpringMVC参数绑定相关组件的初始化过程 在理解初始化之前,先来认识一个接口 HandlerMethodArgumentResolv ...

随机推荐

  1. 浅谈矩阵变换——Matrix

    矩阵变换在图形学上经常用到.基本的常用矩阵变换操作包括平移.缩放.旋转.斜切. 每种变换都对应一个变换矩阵,通过矩阵乘法,可以把多个变换矩阵相乘得到复合变换矩阵. 矩阵乘法不支持交换律,因此不同的变换 ...

  2. Caused by: com.mchange.v2.resourcepool.TimeoutException: A client timed out while waiting to acquire a resource from com.mchange.v2.resourcepool.BasicResourcePool@1483de4 -- timeout at awaitAvailable(

    Caused by: com.mchange.v2.resourcepool.TimeoutException: A client timed out while waiting to acquire ...

  3. php操作kafka

    php操作kafka----可以参照网上的安装步骤,先安装ldkafka rdkafka,然乎启动zookeeper和kafka服务器 <?php //$conf = new Rdkafka\P ...

  4. BZOJ 3514: Codechef MARCH14 GERALD07加强版 (LCT维护最大生成树+主席树)

    题意 给出nnn个点,mmm条边.多次询问,求编号在[l,r][l,r][l,r]内的边形成的联通块的数量,强制在线. 分析 LCTLCTLCT维护动态最大生成树,先将每条边依次加进去,若形成环就断掉 ...

  5. linux 安装go环境

    https://golang.google.cn/dl/ 进入这个地址选择一个版本下载 wget https://dl.google.com/go/go1.13.4.linux-amd64.tar.g ...

  6. ila核数据输出

    在Tcl Console中输入以下命令(其中dataxxxx表示文件名,hw_ila_2则为ila窗口名): write_hw_ila_data -csv_file -force dataxxxx [ ...

  7. Linux之GDB命令(二)

    gdb命令: 前提条件:可执行文件必须包含调试信息 gcc -g gdb 文件名 –启动gdb调试 查看代码命令   当前文件:     list 行号(函数名)   指定文件:     list 文 ...

  8. Leetcode题目96.不同的二叉搜索树(动态规划-中等)

    题目描述: 给定一个整数 n,求以 1 ... n 为节点组成的二叉搜索树有多少种? 示例: 输入: 3 输出: 5 解释: 给定 n = 3, 一共有 5 种不同结构的二叉搜索树: 1 3 3 2 ...

  9. 安装docker以及常规操作

    一.安装 docker对内核版本是有要求的,反正建议用7以上的版本,少坑 如果需要卸载旧版本(凡是卸载删除操作都要谨慎!): yum remove docker \ docker-client \ d ...

  10. python格式化输出(% format用法)

    %基本用法: 十进制输出:print('%d' % 6)    6也可以换成其它的数字变量 八进制输出:print('%o' % 6)  6也可以换成其它的数字变量 字符串输出:print('%s' ...