[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官方文档。待作者亲手完成环境验证后补上。

原文地址:https://www.cnblogs.com/ilinuxer/p/6654070.html