名词解释1、网络命名空间:Linux在网络栈中引入了网络命名空间,将独立的网络协议栈隔离到不同的命名空间中,相互之间不能通信;Docker利用这个特性来实现它们之间的无容器网络隔离。2.Veth设备对:也叫虚拟网络接口对。Veth设备对的引入是为了实现不同网络命名空间之间的通信。3、iptables/Netfilter:Netfilter负责在内核中执行各种hooking规则(过滤、修改、丢弃等),运行在内核态;iptables模式是运行在用户态的进程,负责在内核中协助维护Netfilter的各种规则表;通过两者的配合,实现了整个Linux网络协议栈中灵活的数据包处理机制。4.网桥:网桥是二层网络设备,通过它可以连接linux支持的不同端口,可以像交换机一样实现多对多的通信。5、路由:Linux系统包含了完整的路由功能。IP层在处理数据传输或转发时,会使用路由表来决定将数据发送到哪里。令人望而生畏的网络模型Kubernetes将集群内部的网络重新抽象,将整个集群网络扁平化。当我们能够理解网络模型的时候,我们完全可以从物理节点中抽离出来理解它。我们用图来说话,先有个基本印象。其中,主要对以下关键抽象概念进行说明。ServiceService是Kubernetes引入的一种资源对象,用来屏蔽这些后端实例(Pod)的动态变化,对多个实例进行负载均衡。一个Service通常与一个Deployment绑定,并定义了该服务的访问入口地址。应用程序(Pod)可以通过这个入口地址访问其背后由Pod副本组成的一组集群实例。Service与其后端Pod副本集群的映射是通过LabelSelector实现的。Service的类型(Type)决定了Service如何对外提供服务。根据类型的不同,服务只能在Kubernetes集群中可见,也可以暴露在集群外。Service分为三种,ClusterIP、NodePort和LoadBalancer。下面将介绍具体的使用场景。在测试环境中查看:$kubectlgetsvc--selectorapp=nginxNAMETYPECLUSTER-IPEXTERNAL-IPPORT(S)AGEnginexClusterIP172.19.0.16680/TCP1m$kubectldescribesvcnginxName:nginxNamespace:defaultLabels:app=sv后端代理2个Pod实例:172.16.2.125:80和172.16.2.229:80。两个IPKubernetes是描述其网络模型的IP对象,抽象出ClusterIP和PodIP的概念。PodIP是Kubernetes集群中每个Pod的IP地址。由DockerEngine根据docker0网桥的IP地址段分配,是一个虚拟的二层网络。Kubernetes中的Pod之间可以直接通信,一个Pod中的容器通过PodIP地址访问另一个Pod中的容器。ClusterIP只用于Service,没有对应的实体对象,所以无法ping通ClusterIP。它的作用是为Service后端的实例提供一个统一的访问入口。访问ClusterIP时,请求会转发给后端实例,默认是轮询。集群IP和Service一样由kube-proxy组件维护,主要有两种实现方式,iptables和IPVS。在1.8版本之后,kubeproxy开始支持IPVS模式。在上面的示例中,SVC信息包括ClusterIP。这里不列出节点ip的概念,因为是物理机的网卡IP。因此可以理解nodeip就是物理机的IP。在Kubernetes中,这三个端口涉及到容器、Pod、服务、集群等多层次对象之间的通信。为了区分网络模型中各个层级的通信端口,这里对端口进行了抽象。端口这个端口不是一般意义上的TCP/IP中的端口概念。特指Kubernetes中Service的端口,是Service之间的访问端口。例如MysqlService默认端口是3306,它只提供集群内容器的访问,集群外无法通过该端口访问服务。nodePortnodePort为外部机器提供了一种访问集群内服务的方式。比如一个web应用需要其他用户访问,那么就需要配置type=NodePort,配置nodePort=30001,那么其他机器就可以通过浏览器访问scheme://node:30001来访问该服务,比如http://节点:30001。targetPorttargetPort是容器的端口(最基本的端口入口),与制作容器时暴露的端口一致(DockerFile中的EXPOSE)。比如http://docker.io官方的nginx暴露了80端口,举个例子看看Service的端口是如何配置的:kind:ServiceapiVersion:v1metadata:name:mallh5-servicenamespace:abcdockerspec:selector:app:mallh5webtype:NodePortports:-protocol:TCPport:3017targetPort:5003nodePort:31122这里提到了一个服务的yaml,部署在abcdocker的namespace下。这里配置的是NodePort,所以它的Type是NodePort,注意大小写。如果没有配置nodePort,那么这里需要填写ClusterIP,即只支持集群内的服务访问。集群内通信单节点通信集群中单个节点内的通信主要包括两种情况,同一个pod内多个容器之间的通信和同一个节点上不同pod之间的通信。由于不涉及跨节点访问,因此流量不会通过物理网卡转发。通过查看路由表,还可以一窥:root@node-1:/opt/bin#route-nKernelIProutingtableDestinationGatewayGenmaskFlagsMetricRefUseIface0.0.0.0172.23.100.10.0.0.0UG000eth010.1.0.00.0.0.0255.255.0.0U000flannel.1#flannel网络跨节点通讯会交给flannel.1处理10.1.1.00.0.0.0255.255.255.0U000docker0#flannelnetwork节点内的通信会通过docker01Pod内的通信如下图所示:这种情况下,同一个pod内共享networknamespace,容器可以访问127.0.0.1:(端口).图中的veth*指的是vethpair的一端(另一端没有标注,但实际上是成对出现的),vethpair被DockerDaemon挂载到docker0桥上,另一端添加到容器所属的网络名称如图所示的空间是容器中的eth0。该图演示了桥接模式下的容器间通信。docker1向docker2发送请求,docker1和docker2都与docker0建立vethpair进行通信。当请求经过docker0时,由于容器和docker0属于同一个子网,所以请求通过docker2和docker0之间的veth*对转发给docker2。这个进程不跨节点,所以不经过eth0。2Pod与同一节点上的Pod通信相互通信本质上是同一节点上容器之间的通信,因为网络名称空间(由暂停容器创建)在Pod内共享。同时,同一个Node中的Pod的默认路由是docker0的地址。由于他们关联的是同一个docker0网桥,地址网段相同,所以应该可以直接通信。让我们看看这个过程在实践中是如何工作的。如上图所示,Pod1中的容器1和容器2共享网络命名空间,所以pod外的请求是通过pod1和Docker0网桥的veth对实现的(图中挂在eth0和ethx上)。当访问另一个pod中的容器时,请求的地址是PodIP而不是容器的IP。其实也是同一个子网之间的通信,可以直接通过vethpair进行转发。跨节点通信CNI:ContainerNetworkInterfaceCNI是一个旨在为容器平台提供网络标准化的标准。不同的容器平台(比如现在的kubernetes、mesos和rkt)可以通过同一个接口调用不同的网络组件。目前kubernetes支持多种类型的CNI组件,例如:bridgecalicocalico-ipamdhcpflannelhost-localipvlanloopbackmacvlanportmapptpsampletuningvlan。在docker中,有几种主流的跨主机通信方案:1)基于隧道的覆盖网络:根据隧道的类型,不同的公司或组织有不同的实现方案。Docker原生的overlay网络是基于vxlan隧道实现的。ovn需要通过geneve或者stttunnel来实现。最新版的flannel也开始默认实现基于vxlan的overlay网络。2)基于包封装的Overlay网络:基于UDP封装等数据包封装方式,在docker集群上实现跨主机网络。典型的实现包括早期版本的weave和flannel。3)基于三层实现SDN网络:基于三层协议和路由,直接在三层上实现跨主机网络,通过iptables实现网络的安全隔离。一个典型的程序是ProjectCalico。同时,对于不支持三层路由的环境,ProjectCalico也提供了基于IPIP封装的跨主机网络的通信方式。集群中节点间通信涉及到不同子网之间的通信,仅靠docker0是无法实现的。这里需要CNI网络。插件实现。图中展示了如何使用flannel实现跨节点通信。简单的说,flannel的用户态进程flanneld会为每个node节点创建一个flannel.1网桥,根据etcd或者apiserver全局统一的集群信息,为每个节点分配一个全局唯一的网段,避免地址冲突。同时会为docker0和flannel.1创建一个vethpair,docker0将消息抛给flannel.1。Flanneld维护着一张全局节点网络表。通过flannel.1接收到请求后,根据节点表,将请求重新封装成一个UDP包,丢给eth0,通过eth0出口进入物理网络发送到目的节点。另一端为逆流。Flanneld解包并发送到docker0,然后发送到目标Pod中的容器。外部访问集群从集群外部访问集群的方式有很多种。比如loadbalancer、Ingress、nodeport,nodeport和loadbalancer是两种基本的服务类型,是直接对外暴露服务的方式。Ingress提供七层负载均衡。基本原则是将外部流量转发到内部服务,然后再转发到后端端点。在平时的使用中,我们可以根据具体的业务需求选择不同的方式。这里主要介绍nodeport和ingress方法。Nodeport通过设置Service类型为NodePort,可以通过Cluster中宿主机的指定端口暴露服务。注意服务可以通过Cluster中每台主机上的指定端口访问,发送到主机端口的请求会被Kubernetes路由到提供服务的Pod。使用此类服务??,可以通过主机IP:端口在Kubernetes集群网络外部访问该服务。这里有一个influxdb的例子,我们也可以把这个模板修改成其他类型:kind:ServiceapiVersion:v1metadata:name:influxdbspec:type:NodePortports:-port:8086nodePort:31112selector:name:influxdbIngressIngress在生产环境中推荐使用,它充当第7层负载均衡器和Http代理,可以根据不同的url将入口流量分配到不同的后端服务。外部客户端只能看到http://foo.bar.com服务器,这会阻止多个内部服务的实现。这样简化了客户端的访问,增加了后端实现部署的灵活性,可以在不影响客户端的情况下调整后端的服务部署。部署的yaml可以参考如下模板:apiVersion:extensions/v1beta1kind:Ingressmetadata:name:testannotations:ingress.kubernetes.io/rewrite-target:/spec:rules:-host:test.name.comhttp:paths:-path:/testbackend:serviceName:service-1servicePort:8118-path:/namebackend:serviceName:service-2servicePort:8228这里我们定义一个ingress模板,定义为通过http://test.name.com访问Service,在虚拟主机http://test.name.com下定义了两个Path,其中/test分发给后端服务s1,/name分发给后端服务s2。集群中可以定义多个Ingress,完成不同业务的转发。需要入口控制器来管理集群中的入口规则。IngressContronler与KubernetesAPI交互,动态感知集群中Ingress规则的变化,然后读取。根据自定义规则,规则指定哪个域名对应哪个服务,生成一段Nginx配置,然后写到Nginx-ingress-control的pod中,Ingress的pod中运行着一个nginx服务控制器。controller会将生成的nginx配置写入/etc/nginx.conf文件,然后reload配置生效。Kubernetes提供的IngressController模板如下:apiVersion:extensions/v1beta1kind:Ingressmetadata:name:testannotations:ingress.kubernetes.io/rewrite-target:/spec:rules:-host:foo.bar.comhttp:paths:-path:/foobackend:serviceName:s1servicePort:80-path:/barbackend:serviceName:s2servicePort:80总结与展望本文针对Kubernetes的网络模型,从一个服务开始,两个IP,三个端口来说明。详细解释Kubernetes集群内和集群外的访问方式。后续也将对各个网络的细节进行深入分析,敬请期待。