Kubernetes里的Service是如何工作的
Service是Kubernetes接入层的一种抽象资源,它为我们提供了一种固定的、统一的访问接口地址和负载均衡能力,这时可能会想到,当时使用docker-compose的时候,不存在Service概念,不也运行起来了吗?是的,在Kubernetes集群内部Pod ip也是互通的,但是Pod的ip会经常因为扩容、重建而导致客户端访问错误,pod访问无法提供负载均衡的能力,而Service通过选择一组Pod的label就直接可以访问到Pod,而且可以使用万年不变的域名,所以就选择Service了。
1、Service是怎么产生的,在集群内部是如何存在的呢?
在kubernetes当中所谓的Service是kube-proxy生成iptables或ipvs规则,它会产生一组虚拟地址,在集群环境下有效。Service不能直接到达Pod内部,中间会间隔EndPoints,这是一组ip和port的组合。默认类型是ClusterIP它仅能接收集群中pod客户端程序的访问请求。这也是最常用的一种类型,另外还有NodePort、LoadBalancer、ExternalName。
2、iptables或ipvs,到底是iptables还是ipvs呢?
一个Service对象就是工作节点上的一些iptables或ipvs规则,用于将到达Service对象IP地址的流量调度转发至相应的Endpoints对象指向的IP地址和端口之上。
这句话我们经常看到,如何理解呢?
Kubernetes1.1之前是基于userspace实现,这种模型之下,每次请求流量要先到达内核空间,经有套接字转发到kube-proxy,然后再由它送回到内核空间,之后调度到后端pod之上,可以看出请求在用户空间和内核空间来回转发,效率必然不高。但是当pod无响应时,能够自动重定向到其它pod。
在Kubernetes1.1版本开始引入iptables规则,Kubernetes1.2开始成为默认类型。即在创建Service资源时,集群上每个节点的kube-proxy都会收到通知,并且创建iptables规则,用于转发到此Service ClusterIP的流量。工作TCP/IP的传输层,高效稳定。
但是这种方式有如下缺点:
1、iptables代理模型挑中的pod无响应时,不能自动重定向到集群内部其它pod资源对象之上。
2、kube-proxy通过iptables处理Service和pod的交互,每产生一个计算节点或者产生大量的pod就需要产生相应大量的iptables规则,不难想象,这些iptables规则每次需要刷新匹配保证正确性,就需要占用大量的CPU资源,所以基于iptables的Service实现就成了制约Kubernetes承载更多pod的瓶颈。
在Kubernetes1.11开始默认使用ipvs模型,在这种模型下kube-proxy会跟踪APIServer上Service和endpoints对象变化,调用netlink创建ipvs规则,请求调度流量功能由ipvs实现,运行于netfilter之上的钩子函数,具有流量转发速度快,规则性能同步好的特点,支持众多调度算法,剩下仍然由iptables完成。说到这里我们就大概明白了iptables和ipvs的作用和关系了。
3、Kubernetes的服务发现是通过dns实现,那么为什么会出现四种类型的服务暴露方式呢?
说到Service不得不介绍kubernetes网络模型和通信方式
一个完整的Kubernetes集群应该包含三层网络,首先第一层是mater和node节点之间的网络,这个网络需要在部署kubernetes集群之前配置完成;第二层网络是pod的网络通过kubelet或者cni插件实现,用于pod之间或者内部的通信,集群中的所有pod均处在同一个网络平面空间内,可以直接通信;第三层网络是Service资源的网络,是一个虚拟网络,用于为Kubernetes集群配置IP地址,但此地址并不配置于任何主机或者容器的网络接口之上,而是通过kubeproxy配置为iptables规则,将发往该地址的所有流量调度至后端的pod之上。
通信方式分为以下四种:
- 同一个pod的内部通信;
- 各个pod彼此通信;
- pod和service的通信;
- 集群外部流向service的通信。
所以Service为了满足这些通信方式就出现了如下类型:
ClusterIP:为集群内部ip地址暴露服务,仅在集群内可达,外部ip无法访问,默认Service类型;
NodePort:这种类型建立在clusterIp之上,为节点的IP地址暴NodePort服务,外部节点可以通过NodeIP:NodePort直接访问;
LoadBalancer:这种类型构建在NodePort之上,它可以关联到集群外部的某个负载均衡设备。Kubernetes没有为私有化集群提供LoadBalancer的支持。如果在私有化集群使用需要自建负载均衡器;
ExternalName:其通过将Service映射至由externalName字段的内容指定的主机名来暴露服务,此主机名需要被DNS服务解析至CNAME类型的记录。这句话怎么理解呢?
举个例子,你所有的服务都在集群内部,但是你有个数据库是mongodb,没有实现容器化,更没有部署在Kubernetes内部,当然你可以通过在ConfigMap中添加配置访问这个外部服务,但是当你的环境发生变化,比如从开发环境和生产环境的数据地址不同,这个时候你需要修改和重建ConfigMap。这个时候可以使用Kubernetes ExternalName内置服务发现机制运用于集群外部运行的服务,像使用集群内的服务一样使用外部服务!通过这种方式,您可以在开发环境和生产环境中实现相同的功能,如果您最终将服务移入集群内,则不需要更改任何代码和配置。
kind: Service apiVersion: v1 metadata: name: mongo spec: type: ExternalName externalName: mango123456.com
你只需要访问:mongodb://<dbuser>:<dbpassword>@mongo:<port>
就可以自动访问外部服务。
4、Service本身有端口、Pod也有端口、容器也有端口,之间有什么关系呢?
containerPort:一个信息性数据,为集群提供一个可以快速了解相关pod可以访问端口的途径,而且显式指定容器端口,无论你是否指定都不影响其他节点上的客户端pod对其进行访问;
port:服务提供端口,用于kubernetes集群内部服务访问;
targetPort:pod目标端口,如果不设置使用默认port端口,port和nodePort的数据通过这个端口进入到Pod内部,Pod里面的containers的端口映射到这个端口,提供服务;
nodePort:Kubernetes集群外部用户访问端口;