kubernetes-flannel-14

容器网络通信

理论来源

docker网络说明

  • bridge: 桥接网络, 自由使用网络名称空间,其宿主机接口也就是docker0桥,每一个容器也会有自己的虚拟网卡,来实现容器内部的虚拟网络通信

  • joined: 联盟式网络, 共享使用另外容器的IPC

  • opened: 开放式网络,容器直接共享使用虚拟机的IPC

  • none: 无网络, 不使用任何网络名称空间

    如果跨节点之间的容器通信时,必须要使用NAT,任何容器在出去之前都是私有的地址,离开本地时必须要做SIP转换,取到物理机地址出去,每个容器要想被别人所访问,也必须做NAT

​ 在docker时代,每一个容器在创建时,容器所默认使用的网络模型,叫做bridge,其宿主机接口也就是docker0桥,每一个容器也会有自己的虚拟网卡,来实现容器内部的虚拟网络通信;

​ 而docker0桥的网络地址是固定的,那么如果某一个Pod控制,编排了两个Pod,这两个Pod各自编排了一个容器,此时我们剥离Pod,单谈容器,刚好这两个Pod或者两个容器本身分别被调度到了两个节点之上,那么这两个Pod中的容器如果关联到我们的docker0桥上,那么很有可能第一个容器获取到的地址是172.16.0.2,第二个容器在第二个节点之上获取的IP地址还是172.16.0.2,那么这两个容器之间使用的地址就相同了,更何况这两个桥上默认与节点外部的其他端点进行通信时,还需要使用SNAT方式,因为每一个节点本地就是一个局域网,

​ 这个局域网在节点外部默认是不能被路由的,因此节点外部流量并没有办法直接访问这个节点上的容器的,除非我们在节点上面做DNAT,因此出去请求别人,我们需要通过SNAT实现,如果要提供服务还需要通过DNAT实现,这样一来就会带来,巨大的问题,跨节点通信,要通过两次NAT才能完成,客户端先做SNAT,被访问的目的端要通过DNAT暴露出来,那么如果我们有五个Pod彼此之间要通信,那得多少个NAT需要维护,且不说容器自身怎么维护,这些NAT规则在我们容器增时就头疼不已,所以这种网络模型显然在这个所谓的容器编排系统当中不是一个可取的网络模型;

在这里插入图片描述

k8s网络通信模型

  1. 对于Kubernetes来讲,无论我们怎么编排Pod,经过充分测试它的地址是不会冲突的
  2. Kubernetes要求所有的Pod应该有自己的网络Pod Network,另外Kubernetes的设计机制当中,Pod和Pod的通信是不会直接进行的,而是通过一个Service中间层进行通信的,Service也有地址,我们把它称为Service Network也称为Cluster Network,在集群内部Pod和Pod虽然,可以直接通信,但是Pod要请求某个服务一般请求的它的Service Network地址,而后由Service Network给他调度并且代理至后端的Pod
  3. 其实对于Kubernetes网络,应该是我们的管理员手动维护的,因为节点地址还没有进行编排范畴,那么集群地址是Kubernetes自己指定的,由Kubernetes内部的Service资源,通过把它定义或者转换成iptables或ipvs规则来实现,它并不配置在任何一个网卡上,所以Kubernetes自行就能管理好Cluster Network
  4. Pod Network: 每创建一个pod, 如果都需要pod 地址,而没有共享宿主机的IPC(网络名称空间), 那么就需要从这个Pod Network分配一个地址,更重要的是Kubernetes还要求,各Pod之间,必须能够使用对方的地址进行通信,不能走NAT
  5. 也就是说,如果客户端的Pod想访问某个服务端的Pod,在不考虑Service的情况下,二者之间不能经过任何NAT要能够直接进行通信,这是Kubernetes的要求,因为在Kubernetes内部它要维持这样几种通信。
    1. 容器间通信: 同一个POD内的多个容器间通信: lo
    2. pod间通信: Pod IP 与其它 Pod IP 能直接进行通信,不经过任何的NAT
    3. pod与Service通信: podIP与ClusterIP通信,其通过Iptables/ipvs转换之后就能直接进行通信了
    4. Service与集群外部Client通信: ingress, nodeport

常见网络模型网络解析

桥接式网络

​ ​ ​ ​ ​ ​ ​ ​ Kubernetes要求我们的网络必须是一个平面网络,不能经由NAT转发,但是这种逻辑就必须要实现一种工作逻辑,比如在一个节点之上,我们运行多个容器,这些容器的地址有可能的动态指定的,那么跨两个节点之间的容器间通信,如果不经过NAT直接与对方地址通信,那么只有突破传统的私有网络模型来实现,只要让我们Pod网络使用桥接网络即可,每个网桥可以直接通信,比如A节点占用10.244.1.0/24,B节点占用10.244.2.0/24,地址不产生冲突即可,但是这种桥接式网络模型也有问题,假如我们有一千个节点,每个节点跑了100个Pod,这可有十万个Pod,十万个Pod在同一个物理网络,那很容易形成广播风暴,耗尽带宽,因此就产生VLAN式网络
在这里插入图片描述

VLAN式网络

​ ​ ​ ​ ​ ​ ​ ​ 桥接式网络在节点Pod运行多的情况下就会很容易形成广播风暴,从而耗尽带宽,因此最好的办法就是把每一个节点之上最好能做成vlan,每一个节点做成一个vlan,同一个节点的Pod互相通信就直接通信了,如果跨节点通信,通过Vlan交换机进行交换就行了,这是一种方案;
​ ​ ​ ​ ​ ​ ​ ​ 但是如果网络很大,变动也很频繁的时候,使用vlan也未必是一种好的解决方案,更何况vlan使用的是不同的网段地址,也虽然可以使用相同网段,但是网络管理起来也很麻烦,所以如果必要的时候完全可以使用BGP网络来实现,构建Kubernetes的网络;
在这里插入图片描述

叠加式网络

​ ​ ​ ​ ​ ​ ​ ​ 不管是桥接式网络或者VLAN式网络,Pod都只是连接到节点之上的docker0桥上,而不是物理网络,因此它依旧不能与节点之外的Pod进行通信,但是多个节点的物理接口之间可以打开一个隧道,一旦两个Pod之间通信,所在网段是本地网段就直接本地通信,而如果不是则可以通过物理网卡封装一个隧道协议报文,送到其它节点的物理网卡上了,但网卡将数据包解封装之后会发现里面还有一层报文,而这层报文是由本地转发来的,因此就可以与其它节点的Pod进行通信了,而在虚拟网络协议中有一种技术叫做VXlan,就是实现这么一个功能,我们可以通过某个控制器,让两个局域网之间的报文能够借助于VXLAN的隧道协议,让它们进行通信,所以两个或多个局域网可以理解为一个大网中的几个小子网而已,也就意味着,从大的角度来看他们还是一个网段;
​ ​ ​ ​ ​ ​ ​ ​ 比如我们VXlan所管理的网络是10.244.0.0/16的B类网,我们再把这个B类网切分成256个C类网,A节点地址为0.1/24,B节点为1.1/24,然后其内部Pod地址都由dhcp分配,一旦两个节点内部的Pod需要进行通信时,可以通过VXLAN技术将两个节点的地址它的掩码从24位给他,聚合提升为16位,因此两者要进行通信时,A节点就从0.1/24提升为0.1/16,B节点也从1.1/24提升为1.1/16,因此,他们就是同一个网段了,而VXLAN借助于自己的二层隧道,把报文从一个节点送到另外一个节点,但是VXlan怎么知道哪一个节点上拥有哪一个子网呢,如果VXlan自己不能保存这些信息,那么通常要借助于外部的存储系统来保存;

​ ​ ​ ​ ​ ​ ​ ​ 比如像xvlan控制器可以借助于像etcd这类的kv存储系统,当vxlan中每添加一个节点就分配一个子网,然后记录到etcd当中,比如10.244.0.0/24(NODE1虚拟网卡)需通过192.168.1.2(NODE1物理网卡),要到达10.244.1.0/24(NODE2虚拟网卡)需通过192.168.1.3(NODE2物理网卡), 如果node1的Pod(10.244.0.2)要访问node2之上的Pod(10.244.1.2),它会先查询xvlan,然后它查询的目标主机是10.244.1.0/24网段,随后根据etcd当中的信息发现要达到10.244.1.0/24这个网段那么首先要将报文送到192.168.1.3,送到192.168.1.3之后发现目标mac地址是自己的,然后拆掉目标mac, 看到里面mac,而里面的mac是192.168.1.3内部的虚拟交换机的,虚拟交换机收到之后,里面的目标IP是Pod1的IP,然后Pod1就收到了;

​ ​ ​ ​ ​ ​ ​ ​ 这种网络我们称之为叠加网络,其实就是在一个网络上承载了另外一个网络,而叠加网络也是让我们能在一个较大规模的网络当中,实现网络虚拟化的重要基础技术之一,而能实现叠加网络的众多技术当中VXLAN以性能高协议开放著称,所以用的非常非常广泛,甚至于有很多项目都是借助于VXLAN来完成网络虚拟化的;
在这里插入图片描述

k8s网络解析

​ ​ ​ ​ ​ ​ ​ ​ 虽然说叠加网络看似是k8s的最优选择,但k8s自身却没有实现这样的功能,k8s虽然需要为Pod分配网络,从但是它觉得各家网络服务厂商,很可能都有自己的私有技术,因此k8s将pod网络功能基于插件接口提供功能,将它交给第三方网络插件来实现,而K8s只保留出一个插件接口,只要遵循restful风格的网络插件,都可以作为k8s集群之上的Pod网络组件, 集群网络系统, flannel

CNI插件

k8s网络不是由自己来实现,而是靠CNI (网络插件) 接口,如果想使用CNI插件,我们在每一个节点启动kubelet的时候要给定参数–network-plugin=cni启用这个插件,CNI还需要一个外部的节点级的应用程序来对接,因此还需要使用–cni-conf-dir来指定应用程序地址,默认配置文件 /etc/cni/net.d/ ,程序文件默认在/opt/cni/bin目录,我们在使用kubeadm部署的时候不需要手动管理CNI插件

  • flannel, CoreOS所研发的一种轻量级网络实现方案,使用者人数多,占用面积大,简单易用
  • 实现方式:基于VXLAN技术的,跨级节点通信,借助于叠加网络来实现的
  • 网络:隧道网络,但是它不是二层,而是三层,ipip,用一个ip报文承载另外一个ip报文
  • 缺点: 不支持网络策略,无法定义Pod之间的策略
  • 优点: 简单且快速的网络模型
  • calico, 一般可用于与flannel搭配,只需要其网络策略功能
    • 实现方式:
    • 网络:使用BGP协议的,大二层网络解决方案,利用一个mac报文承载另一个mac报文
    • 缺点: 网络配置复杂
    • 优点: 强悍的网络策略
  • cannel
  • flannel + calico的揉合体, 简单且易配的网络、以及网络策略
  • kube-route
  • k8s自带的网络功能, 一般使用少,
  • 网络: 既能实现网络也能够借助于lvs/ipvs来管理Service的kube-proxy ,还能够借助于内核转发功能来构建Pod和Pod之间的网络解决方案,而且不使用叠加网络,直接利用内核级的某些特性就能完成大二层网络之间的Pod通信,所以kube-proxy从这个角度来讲也是一个后起之秀,但是一直处于bate这个级别,没有stabe;

CNI基本解决方案

  • 虚拟网桥 (bridge):纯软件方式实现一个虚拟网卡,如docker的桥接,容器、主机各一半接口;
  • 多路复用(MacVlan):基于mac的方式创建vlan,为每个虚拟接口创建一个独有的MAC地址,使得一个物理网卡能承载多个容器去使用, 基于物理网卡中的VLAN机制进行跨节点之间通信;
  • 硬件交换:SR-IOV (单根IO虚拟化), 一个网卡支持能直接在物理机虚拟出多个接口;
~]# cat /etc/cni/net.d/10-flannel.conflist
{
    
    
  "name": "cbr0",            # 网卡名称
  "cniVersion": "0.3.1",     # 插件版本
  "plugins": [
    {
    
    
      "type": "flannel",     # 插件类型
      "delegate": {
    
    
        "hairpinMode": true,
        "isDefaultGateway": true   # 默认网关
      }
    },
    {
    
    
      "type": "portmap",
      "capabilities": {
    
    
        "portMappings": true
      }
    }
  ]
}

CNI配置与使用

​​ ​ ​ ​ ​ ​ ​ ​ 我们可以看到每个节点上都可以看到cni0的网络接口,同时还有一个接口flannel.1,这是flannel自己的隧道接口桥(隧道网络有自己专用的封装隧道协议的接口在这里叫做flannel.1),任何Pod在创建以后会自动关联到cni0这个网段上来,因此关联的地址和cni0的地址,同时也是一个24为的掩码 ,如果目标主机IP和cni0地址不在同一个网段,那就不是本地cni0的子网,那么就会通过内核级的路由转发到flannel.1,而后flannel.1给它打上隧道报文,送给另外一个节点,从而完成跨节点的隧道间协议转发,因为flannel默认情况下使用的是vxlan的方式,来做为后端网络传输机制的,

​​ ​ ​ ​ ​ ​ ​ ​ 两个节点之间的Pod通信:组成一个叠加网络,两个主机之上应该应该有一个专门用于叠加报文封装的隧道flannel-0, flannel-1这样的接口, 如: 两个节点pod网络之间是无法通信的 需要借助于flannel接口的报文封装通道,
在这里插入图片描述
flannel支持多种后端:

  1. VxLAN: 扩展的虚拟局域网

  2. vxlan

    • vxlan在实现报文通信时,它是一种类型与四层隧道的协议,当帧经由flannel隧道时其头部会封装一个vxlan首部在到其前面加一个udp首部外层会在加一层IP首部,
    • 以太网首部 POD2 <-- [以太网首部|IP首部|udp首部|vxlan] <-- flannel隧道 <-- POD1
  3. DirectRouting

    • 直接路由,源和目标节点在同一网络, 使用路由转发,不用隧道叠加,如果大家在同一个网络中就使用Host-gw类型,如果不在同一个网络中就工作为VXLAN模型
  4. host-gw: Host Gateway (主机网关)

    • 如:2个节点,节点之上各自有一个专用网段, 主机自己所在的网络接口当做网关使用,从而给pod接口各自配置地址,网关设置为主机地址

    • 通过物理网桥直接接入物理网桥,网桥不是路由,所有Pod直接通信,但是这种方式虽然性能很好,但是有局限性,既然大家都是直接通过mac地址通信的,那也就意味着所有节点必须在同一个物理网络中不能跨路由器

    • 不支持跨节点
      在这里插入图片描述

  5. UDP

两台主机在同一个网段\同一个2层交换机下使用host-gw, 如果跨网段\跨路由就自动降级为Vxlan

flannel说明

为了让k8s运行, flannel事先应该是先存在的,也就是说在运行之前就应该先部署flannel。

​ 部署k8s有两种方式: k8s直接部署在节点上,使用kubeadm将服务都运行在pod之上, 但凡有kubenet的节点都需要安装flannel插件,Kubenet要借助flannel为pod设置网络接口,添加网络以及激活网络等功能;
​ 而flannel也同于k8s安装方式, 部署于节点之上的守护进程 或 运行于pod之中,运行在pod之中就必须把flannel配置为共享它所运行的节点网络名称空间的pod, 一旦做为Pod运行那它一定是一个daemonSet, 在每一个节点之上只运行一个副本,而这个副本是直接共享宿主机的网络名称空间,这样pod才能设置宿主机的网络名称空间,

flannel状态信息

  • 如运行于pod之上的flannel, 集群有多少个 flannel 就会安装多少
~]# kubectl get daemonsets.apps -n kube-system 
NAME                      DESIRED   CURRENT   READY   UP-TO-DATE  
kube-flannel-ds-amd64     3         3         3       3     
  • 节点数

    ~]# kubectl get pods -n kube-system -o wide
    NAME                             READY   STATUS   IP              NODE  
    kube-flannel-ds-amd64-cb9h5      1/1     Running  192.168.2.222   slave2
    kube-flannel-ds-amd64-wd7tv      1/1     Running  192.168.2.220   master
    kube-flannel-ds-amd64-z96rw      1/1     Running  192.168.2.221   slave1
    
  • configmap用来配置这三个pod是如何运行的

    ~]# kubectl get configmaps -n kube-system 
    NAME                                 DATA   AGE
    kube-flannel-cfg                     2      8d
    
    ~]# kubectl get configmaps -n kube-system kube-flannel-cfg -o yaml
    apiVersion: v1
    data:
      cni-conf.json: |
        {
          
          
          "name": "cbr0",
          "cniVersion": "0.3.1",
          "plugins": [
            {
          
          
              "type": "flannel",
              "delegate": {
          
          
                "hairpinMode": true,
                "isDefaultGateway": true
              }
            },
            {
          
          
              "type": "portmap",
              "capabilities": {
          
          
                "portMappings": true
              }
            }
          ]
        }
      net-conf.json: |
        {
          
          
          "Network": "10.244.0.0/16",
          "Backend": {
          
          
            "Type": "vxlan"
          }
        }
    kind: ConfigMap
    metadata:
      name: kube-flannel-cfg
      namespace: kube-system
    

flannel配置参数

  • Network: flannel使用的CIDR格式的网络地址,用于为Pod配置网络功能
  • 如大网段是:10.244.0.0/16 node1:10.244.1.0/24 node2: 10.244.2.0/24 …
  • SubnetLen: 把Network切分子网借节点使用时,使用多长的掩码划分,默认: 24;
  • SubnetMin: 指明网段从多少开始划分 比如: 10.244.20.0/24开始
  • SubnetMax: 指明网段最大是多少 比如: 10.244.25.0/24
  • Backend: pod与pod之间的通信协议, vxlan, host-gw, udp
  • flannel配置说明

测试隧道协议

​ 主机网关模式: 假设有两个节点,分别是node1和node2,node1节点Pod地址为10.244.1.1/24, node2节点Pod地址为10.244.2.1/24,此时我们分别将node1与node2网卡地址做为node1\node2的网关, 这时当Pod与外部通信时就可以直接使用自己宿主机网关IP地址进行通信了;

​ 比如我们在物理机上创建一个虚拟逻辑接口lo1,用于连接Pod网络并将其做为Pod网络的网关,此时Pod在传输报文的时候不是通过隧道承载传递的,此时这个Pod的ip地址为10.244.1.1/24,传递报文目标地址为10.244.2.1/24,此时应该将它的报文传递比网关lo1, 地址为10.244.1.254/24,当网关收到该报文时,先查询本地的路由表,发现要到达指定网络需要经过本地的物理网卡 eth0(地址为172.16.1.3/24),此时物理网卡会再查询本地路由表,发现rs表中记录要到达指定的10.244.2.0/24网络要送给对端的物理网卡地址为172.16.1.4/24,对端地址收到报文之后发现目标地址是10.244.2.0/24,那么就会发给10.244.2.254/24(lo1),最终在通过查询本地路由表最终到达10.244.2.1;

​ 两种方式:通过直接将主机节点当成网关性能会比逻辑接口更高;

VxLAN- xvlan

  • 部署两个测试pod
]# cat myapp.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-deploy
  namespace: default
  labels:
    app: myapp
spec:
  replicas: 2
  selector: 
    matchLabels:
      app: myapp
  template:
    metadata:
      name: myapp
      namespace: default
      labels: 
        app: myapp
    spec:
      containers:
      - name: myapp
        image: ikubernetes/myapp:v1
        imagePullPolicy: IfNotPresent
        ports:
        - name: http
          containerPort: 80

~]# kubectl get pods -o wide
NAME                         READY   STATUS    RESTARTS   AGE   IP            NODE  
my-deploy-7b59496b7d-9qpjx   1/1     Running   0          11m   10.244.1.22   slave1
my-deploy-7b59496b7d-v6fmj   1/1     Running   0          11m   10.244.2.26   slave2

# 分别连入
]# kubectl exec -it my-deploy-7b59496b7d-9qpjx /bin/sh ping
  • 查看路由
]#  ip route
default via 192.168.2.1 dev ens33 proto static metric 100 
10.244.0.0/24 dev cni0 proto kernel scope link src 10.244.0.1 
10.244.1.0/24 via 10.244.1.0 dev flannel.1 onlink 
10.244.2.0/24 via 10.244.2.0 dev flannel.1 onlink 


# 10.244.1.22 -->  10.244.1.0 (flannel1.1接口) --> cni0 --> 物理网卡节点1  --> 节点2 --> cni0 --> flannel1.1 --> 10.244.2.26
  • 测试icmp
# xvlan数据流  cni0进, flannel1 出, 最后借助于ens网口到其它网口, 1.1的时候会封装成xvaln报文

 ~]# tcpdump -i cni0 -nn icmp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on cni0, link-type EN10MB (Ethernet), capture size 262144 bytes
11:00:40.294152 IP 10.244.2.26 > 10.244.1.22: ICMP echo request, id 3072, seq 298, length 64
11:00:40.294224 IP 10.244.1.22 > 10.244.2.26: ICMP echo reply, id 3072, seq 298, length 64

 ~]# tcpdump -i flannel.1 -nn icmp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on flannel.1, link-type EN10MB (Ethernet), capture size 262144 bytes
11:01:58.651415 IP 10.244.1.22 > 10.244.2.26: ICMP echo request, id 4352, seq 421, length 64
11:01:58.652113 IP 10.244.2.26 > 10.244.1.22: ICMP echo reply, id 4352, seq 421, length 64

# xvlan是从虚拟接口出的, 从ens接口是直接看不到icmp协议的, 被xvlan封装了

VxLAN-DirectRouting

前期需要分析,如果不存在跨网关可以直接使用,网关模式 减少数据包封装提高性能,如果跨网段是 默认降级为vxlan模式

]# wget https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml

# 需要net-conf.json段, 
net-conf.json: | {
    
    
  "Network": "10.244.0.0/16",
  "Backend": {
    
    
    "Type": "vxlan", 
    "DirectRouting": true   # 增加 Directouting 使flannel直接通过物理网卡连接通信
  }
}

]# kubectl get configmaps -n kube-system kube-flannel-cfg -o json
# 添加 DirectRouting 改为直连后 查看路由连接

# 需要先删除 pods(deployment), flannel,然后在创建, 直接修改json段可能不会生效


 ~]# ip r s
10.244.0.0/24 via 192.168.2.220 dev ens33 
10.244.1.0/24 dev cni0 proto kernel scope link src 10.244.1.1 
10.244.2.0/24 via 192.168.2.222 dev ens33 

# 查看 icmp协议
 ~]# tcpdump -i ens33 -nn icmp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on ens33, link-type EN10MB (Ethernet), capture size 262144 bytes
11:34:59.889258 IP 10.244.2.27 > 10.244.1.23: ICMP echo request, id 4352, seq 46, length 64
11:34:59.889374 IP 10.244.1.23 > 10.244.2.27: ICMP echo reply, id 4352, seq 46, length 64

猜你喜欢

转载自blog.csdn.net/u010304195/article/details/107229054