zoukankan      html  css  js  c++  java
  • [Kubernetes]Kubernetes的网络模型

    Kubernetes的网络模型从内至外由四个部分组成:

    1. Pod内部容器所在的网络
    2. Pod所在的网络
    3. Pod和Service之间通信的网络
    4. 外界与Service之间通信的网络

    建议在阅读本文之前先了解Docker的网络模型。可以参看作者的前两篇文章[Kubernetes]Docker的网络模型[Kubernetes]Docker的overlay网络模型

    1. Pod内部容器所在的网络和Pod所在的网络

    Kubernetes使用了一种“IP-per-pod”网络模型:为每一个Pod分配了一个IP地址,Pod内部的容器共享Pod的网络空间,即它们共享Pod的网卡和IP。Kubernetes是怎么做的呢?聪明的读者一定想到了[Kubernetes]Docker的网络模型里介绍的Docker的container网络。Kubernetes在创建Pod时,先在Node节点的Docker上创建了一个运行在bridge网络的“pod容器”,为这个pod容器创建虚拟网卡eth0并分配IP地址。而Pod里的容器(称为app容器),在创建时使用--net=container:来共享pod容器的网络空间。

    例如,笔者手里有一个Kubernetes Node主机PT-169124, IP地址为192.168.169.124. 笔者的环境用flannel作为了Docker的bridge网络驱动,用ifconfig命令可以看到flannel网络设备:

    flannel0: flags=4305<UP,POINTOPOINT,RUNNING,NOARP,MULTICAST>  mtu 1472
            inet 172.17.17.0  netmask 255.255.0.0  destination 172.17.17.0
            unspec 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00  txqueuelen 500  (UNSPEC)
            RX packets 7988  bytes 410094 (400.4 KiB)
            RX errors 0  dropped 0  overruns 0  frame 0
            TX packets 12681  bytes 17640033 (16.8 MiB)
            TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

    这个flannel0是Kubelet用flannel创建出来的bridge,并在Docker进程启动时,作为了--bridge参数值:--bridge=flannel0,这样使得Docker里创建的容器默认都使用flannel0网段里的IP地址。查看这个主机上运行的Docker容器(受限于格式,这里只显示容器的ID和名字):

    # docker ps
    CONTAINER ID       NAMES
    7672d97e01d1       k8s_kubernetes-dashboard.979fa630_kubernetes-dashboard-4164430742-lqhcg_kube-system_a08a0d66-57bf-11e6-84b8-5cf3fcba84a8_086f7305
    431338595af6       k8s_POD.42430ae1_kubernetes-dashboard-4164430742-lqhcg_kube-system_a08a0d66-57bf-11e6-84b8-5cf3fcba84a8_e96d8681
    

    这两个容器即是作者在[Kubernetes]Kubernetes集群和Docker私有库搭建(CentOS 7)一文里搭建的kubernetes-dashboard所创建出来的pod容器和app容器。其中pod容器就运行在由flannel驱动的bridge网络里:

    # docker network inspect bridge
    [
        {
            "Name": "bridge",
            "Id": "6031ecf132905a10b6500a6911d914aff2e15ca8894225aa59ca34bf965b902e",
            "Scope": "local",
            "Driver": "bridge",
            "IPAM": {
                "Driver": "default",
                "Options": null,
                "Config": [
                    {
                        "Subnet": "172.17.17.1/24",
                        "Gateway": "172.17.17.1"
                    }
                ]
            },
            "Containers": {
                "431338595af6236a3feb661129e17a4aed4d8331c173903bc2aa7a788d494c6d": {
                    "Name": "k8s_POD.42430ae1_kubernetes-dashboard-4164430742-lqhcg_kube-system_a08a0d66-57bf-11e6-84b8-5cf3fcba84a8_e96d8681",
                    "EndpointID": "6f22b9c24be10bf069973e0ba651efafdc68e13177e9cbbe3f41b6b4e963eff1",
                    "MacAddress": "02:42:ac:11:11:03",
                    "IPv4Address": "172.17.17.3/24",
                    "IPv6Address": ""
                }
            },
            "Options": {
                "com.docker.network.bridge.default_bridge": "true",
                "com.docker.network.bridge.enable_icc": "true",
                "com.docker.network.bridge.enable_ip_masquerade": "true",
                "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
                "com.docker.network.bridge.name": "docker0",
                "com.docker.network.driver.mtu": "1472"
            }
        }
    ]

    再用docker inspect来查看kubernetes-dashboard app容器的详细信息,能看到NetworkMode值为 "container:431338595af6236a3feb661129e17a4aed4d8331c173903bc2aa7a788d494c6d",说明app容器共享pod容器的网络空间,即它们有相同的网卡和IP地址。

    Kubernetes这种“IP-per-pod”网络模型,能让Pod里的容器相互之间通过loopback(127.0.0.1, localhost)网络访问,同时也意味着Pod里的容器在端口分配上不能发生冲突,而Pod里的容器根本不用担心和其他Pod里的容器发生端口冲突。

    更有意思的是,Kubernetes把各Node主机上的Docker的bridge网络“外包”给了flannel,flannel在这些主机上创建的flannel0网络使用的是同一个子网的不同区间。例如本文的环境里,flannel在两台Node主机上创建的flannel0网络分别是:

    主机PT-169121, IP地址为192.168.169.121:

    flannel0: flags=4305<UP,POINTOPOINT,RUNNING,NOARP,MULTICAST>  mtu 1472
            inet 172.17.13.0  netmask 255.255.0.0  destination 172.17.13.0
            unspec 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00  txqueuelen 500  (UNSPEC)
            RX packets 12684  bytes 17640285 (16.8 MiB)
            RX errors 0  dropped 0  overruns 0  frame 0
            TX packets 7991  bytes 410346 (400.7 KiB)
            TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

    主机PT-169124, IP地址为192.168.169.124:

    flannel0: flags=4305<UP,POINTOPOINT,RUNNING,NOARP,MULTICAST>  mtu 1472
            inet 172.17.17.0  netmask 255.255.0.0  destination 172.17.17.0
            unspec 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00  txqueuelen 500  (UNSPEC)
            RX packets 7988  bytes 410094 (400.4 KiB)
            RX errors 0  dropped 0  overruns 0  frame 0
            TX packets 12681  bytes 17640033 (16.8 MiB)
            TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

    flannel通过etcd,将各Node主机上的Docker bridge网络联合了起来,实现了:

    • 所有的容器都能相互通信,不管容器是否运行在同一个Node主机上
    • 所有的容器和Node主机都能相互通信,不管容器是否运行在该Node主机上。

    2. Pod和Service之间通信的网络

    Kubernetes里的Pod不是一个“长生”的家伙,它会由于各种原因被销毁和创造。比如在垂直扩容和滚动更新过程中,旧的Pod会被销毁,被新的Pod代替。这期间,Pod的IP地址甚至会发生变化。所以Kubernetes引进了Service.Service是一个抽象的实体,Kubernetes在创建Service实体时,为其分配了一个虚拟的IP,当我们需要访问Pod里的容器提供的功能时,我们不直接使用Pod的IP地址和端口,而是访问Service的这个虚拟IP和端口,由Service把请求转发给它背后的Pod.

    Kubernetes在创建Service时,根据Service的标签选择器(Label Selector)来查找Pod,据此创建与Service同名的EndPoints对象。当Pod的地址发生变化时,EndPoints也随之变化。Service接受到请求时,就能通过EndPoints找到请求转发的目标地址。

    例如,作者在搭建kubernetes-dashboard是创建出的服务如下图所示,背后有两个Pod在支撑。

    通过kubectl get命令分别查看一下Service和EndPoints的信息:

    # kubectl get services/kubernetes-dashboard --namespace=kube-system -o yaml
    
    apiVersion: v1
    
    kind: Service
    
    metadata:
    
      creationTimestamp: 2016-08-01T08:12:02Z
    
      labels:
    
        app: kubernetes-dashboard
    
      name: kubernetes-dashboard
    
      namespace: kube-system
    
      resourceVersion: "18293"
    
      selfLink: /api/v1/namespaces/kube-system/services/kubernetes-dashboard
    
      uid: a0953fa0-57bf-11e6-84b8-5cf3fcba84a8
    
    spec:
    
      clusterIP: 10.254.213.209
    
      portalIP: 10.254.213.209
    
      ports:
    
      - nodePort: 31482
    
        port: 80
    
        protocol: TCP
    
        targetPort: 9090
    
      selector:
    
        app: kubernetes-dashboard
    
      sessionAffinity: None
    
      type: NodePort
    
    status:
    
      loadBalancer: {}
    
    # kubectl describe endpoints/kubernetes-dashboard --namespace=kube-system
    Name:        kubernetes-dashboard
    Namespace:    kube-system
    Labels:        app=kubernetes-dashboard
    Subsets:
      Addresses:        172.17.13.2,172.17.17.3
      NotReadyAddresses:    <none>
      Ports:
        Name    Port    Protocol
        ----    ----    --------
        <unset>    9090    TCP
    
    No events.
    

    可以看到,Service的targetPort和Pod的IP地址都记录在了与Service同名的EndPoints里。我们再深入看看当通过Service的虚拟IP和端口访问Service时,请求是如何到达Pod的。

    前面说过,Service只是一个虚拟的实体,真正完成请求转发的是运行在Node节点上的kube-proxy.Service的虚拟IP就是由kube-proxy实现的。kube-proxy有两种请求转发模式:userspace模式和iptables模式。在Kubernetes v1.1版本之前默认是userspace模式,v1.2版本后默认是iptables模式。

    userspace模式:当创建Service时,所有Node节点上的kube-proxy都会在其所在的本地节点上随机开放一个端口(称为代理端口),然后建立一个iptables规则(一种linux包处理逻辑),iptables会完成<服务的虚拟IP, 端口>与代理端口的流量转发,再从EndPoints里选择一个Pod,把代理端口的流量转给该Pod. 当EndPoints下有多个Pod时,选择Pod的算法有两种:1 依次循环,如果一个Pod没有响应,就试下一个(service.spec.sessionAffinity值为"None");2 选择与请求来源IP更接近的Pod(service.spec.sessionAffinity值为"ClientIP").

    userspace模式的缺点是:只适用于规模较小的集群;会对Pod屏蔽请求的source IP,使得部分基于source IP的防火墙失效。

    iptables模式:当创建Service时,所有Node节点上的kube-proxy都会建立两级iptables规则,一级为Service创建,目的是将<服务虚拟IP,端口>的流量转给后端,另一级为EndPoints创建,目的是用于选择Pod. 当service.spec.sessionAffinity值为"ClientIP"时,iptables模式选择Pod的算法和userspace模式相同。当service.spec.sessionAffinity值为"None"时,随机选择Pod,所以如果被选择的Pod没有响应,不会尝试选择另一个Pod.

    例如,作者的Kubernetes环境是v1.2版本,用iptables -vL --line-numbers -t nat命令能看到创建的iptables规则:

    kube-proxy为kubernetes-dashboard服务创建的iptables规则:

    kube-proxy为kubernetes-dashboard EndPoints创建的iptables规则:

    iptables模式相比userspace模式更快更稳定,也不存在请求的source IP的问题。

    我们介绍了Kubernetes是如何用Service来“代理”Pod,那么被创建的Service如何被其他Pod感知到呢?比如说,一个前端的Pod如何找到后端的Service?Kubernetes提供了两种方法:环境变量和DNS.这部分的内容,Kubernetes的官网有所介绍。

    3. 外界与Service之间通信的网络

    如果想把服务暴露给外界(为什么不直接暴露Pod呢?相信读者自己有答案。),有三种方式:

    Service的类型

    如果不指定Service的spec.type的值,创建的Service的类型默认为ClusterIP类型。这种类型的Service只会得到虚拟的IP和端口,只能在Kubernetes集群内部被访问。

    如果指定Service的spec.type的值为“NodePort”,创建的Service的类型默认为NodePort类型。这种类型的Service除了会得到虚拟的IP和端口,Kubernetes还会在所有Node节点上为其分配端口。分配的端口的值可以通过spec.ports[*].nodePort指定,或由Knubernetes在配置好的区间里分配(默认为30000-32767)。这种Service即可以从Kubernetes集群通过虚拟IP:端口访问,也可以从集群外部通过Node节点的IP:nodePort访问,例如,作者创建的kubernetes-dashboard服务就是NodePort类型,Kubernetes为其分配的nodePort为31482,通过任意节点的IP(192.168.169.121或192.168.169.124)都能访问。

    如果指定Service的spec.type的值为“LoadBalancer”,创建的Service的类型默认为LoadBalancer类型。这种类型的Service除了会得到虚拟的IP和端口,Kubernetes还会在所有Node节点上为其分配端口,然后为其开通负载均衡。这种Service即可以从Kubernetes集群通过虚拟IP:端口访问,也可以从集群外部通过Node节点的IP:nodePort访问,还可以通过负载均衡的IP访问。

    绑定外部IP

    通过指定Service的spec.externalIPs[*],可以将Service暴露在节点的外部IP上。外界通过这个IP和Service的端口可以访问到该Service.

    使用Ingress

    先参见Kubernetes官方文档。待作者亲手完成环境验证后补上。

  • 相关阅读:
    Hibernate系列教材 (十六)- 各类概念
    Hibernate系列教材 (十五)- 各类概念
    Hibernate系列教材 (十四)- 各类概念
    Hibernate系列教材 (十三)- 各类概念
    Hibernate系列教材 (十二)- 关系
    Hibernate系列教材 (十一)- 关系
    Hibernate系列教材 (十)- 关系
    codeforces1509 C. The Sports Festival (区间DP)
    洛谷P3194 [HNOI2008]水平可见直线(计算几何+单调栈)
    CF1265E Beautiful Mirrors (概率dp)
  • 原文地址:https://www.cnblogs.com/ilinuxer/p/6654070.html
Copyright © 2011-2022 走看看