关于Service需要了解的知识:
-
Kubernetes将为Service分配一个IP地址,供Service Proxy使用(这个我不了解)
-
Kubernetes将不断扫描符合该Selector的Pod,并将最新的结果更新到与Service同名
my-servie
的Endpoint对象中(对Endpoint还不是太熟悉) -
Pod的另一种,Port可能被赋予了一个名字,您可以在Service的targetPort 字段引用这些名字,而不是直接写端口号。这种做法可以使得您在将来修改后端程序监听的端口号,而无需影响到前端程序(因为不需要修改Service,而拥有不同name的Pod应该都可以正确接受转发的流量)。
-
Service的默认传输协议是TCP,您也可以使用其他支持的传输协议。
-
Kubernetes Service中,可以定义多个端口,不同的端口可以使用相同或不同的传输协议。
其他知识:
Service从自己的IP地址和port端口接受请求,并将请求映射到符合条件的Pod的targetPort。为了方便,默认targetPort的取值与port字段相同。(实际上就是一条lvs转发记录)
高级的应用
Service通常用于提供对Kubernetes Pod的访问,但是您也可以将其用于任何其他形式的后端。例如:
- 想要在生产环境中使用一个Kubernetes外部的数据库集群,在测试环境中使用Kubernetes内部的数据库
- 想要将Service指向另一个名称空间中的Service,或者另一个Kubernetes集群中的Service
- 正在将程序迁移到Kubernetes,但是根据迁移路径,只将一部分后端程序运行在Kubernetes中
在上述这些情况下,可以定义一个没有Pod Selector的Service:
|
|
因为该Service没有selector,相应的Endpoint对象就无法自动创建。可以手动创建一个Endpoint,以便该Service映射到后端服务真实的IP地址和端口:
|
|
对于Service的访问者来说,Service是否有label selector都是一样的。
注意事项:
-
Endpoint中的IP地址不可以是loopback(127.0.0.0/8 IPv4 或 ::1/128 IPv6),或link-local(169.254.0.0/16 IPv4、224.0.0.0/24 IPv4 或 fe80::/64 IPv6)
-
Endpoint中的IP地址不可以是集群中其他Service的ClusterIP
Service原理
Kubernetes集群中每个节点都运行了一个kube-proxy,负责为Service提供虚拟IP访问。
Iptables代理模式:
-
kube-proxy监听kubernetes master以获得添加和移除Service/Endpoint的事件
-
kube-proxy在其所在的节点上为每一个Service安装iptable规则
-
iptables将发送到Service的ClusterIP/Port的请求重定向到Service的后端Pod上
- 对于Service中的每一个Endpoint,kube-proxy安装一个iptable规则
- 默认情况下,kube-proxy随机选择一个Service的后端Pod
IPVS代码模式:
-
kube-proxy监听kubernetes master以获得添加和移除Service/Endpoint的事件
-
kube-proxy根据监听到的事件,调用netlink接口,创建LPVS规则,并且将Service/Endpoint的变化同步到IPVS规则中
-
当访问一个Service时,IPVS将请求重定向到后端POD
多端口的Service
Service对象中定义多个端口时,必须为每个端口定义一个名字:
|
|
使用自定义的IP地址
创建Service时,如果指定spec.cluserIp
字段,可以使用自定义的ClusterIP地址。使用到自定义IP地址的场景:
- 想要重用某个已经存在的DNS条目(不理解)
- 遗留系统是通过IP地址寻址,且很难改造(不存在说K8S中的项目还有写死IP地址的情况吧)
服务发现
K8S支持两种主要的服务发现模式:
- 环境变量
- DNS
环境变量
kubelet查找有效的Service,并针对每一个Service,向其所在节点的Pod注入一组环境变量。支持的环境变量有:
- Docker links兼容的环境变量(不理解这个)
- {SERVICE}_SERVICE_HOST和{SVCNAME}_SERVICE_PROT
- ServiceName被转换成大写
- 小数点被转换成下滑线
案例:
REDIS_MASTER_SERVICE_HOST=10.0.0.11
REDIS_MASTER_SERVICE_PORT=6379
REDIS_MASTER_PORT=tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP=tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP_PROTO=tcp
REDIS_MASTER_PORT_6379_TCP_PORT=6379
REDIS_MASTER_PORT_6379_TCP_ADDR=10.0.0.11
我无法理解这种基于环境变量的服务发现方式,这样不会导致后加入的服务不会被发现么?
DNS
CoreDNS监听Kubernetes API上创建和删除Service的事件,并为每一个Service创建一条DNS记录。
需要结合CoreDNS、LVPS理解一波了,不然容易混乱:CoreDNS完成的是ServiceName到ServiceIp的转换,待完成了这个转换过程后,Pod就知道往哪个IP发送消息了。LVPS完成的是ServiceIp到PodId的转换,本质上,这个请求从宿主机发出时,就被路由到了目标机器上。
Kubernetes同样支持DNS SRV记录,用于查找一个命名的端口。假如my-service.my-ns有一个TCP名为http的端口,则可以使用nslookup _http._tcp.my-service.my-ns
发现该Service的IP地址及端口。(有点意思,第一次听说,哈哈。)
对于ExternalName类型的Service,只能通过DNS的方式进行服务发现(我对ExternalName类型的Service的理解为:就是手动指定Endpoint的Service)。
Headless Services
HeadlessService不提供负载均衡的特性,也没有自己的IP地址,创建HeadlessService时,只需要指定spec.clusterIP
为None。
HeadlessService可以用于对接其他形式的服务发现机制,无需与Kubernetes的实现绑定。对于HeadlessService而言:
- 没有ClusterIP
- kube-proxy不处理这类Service
- kubernetes不提供负载均衡或代理支持
DNS的配置方式取决于该Service是否配置了Selector:
-
配置了Selector
EndpointController创建Endpoints记录,并修改DNS配置,使其直接返回指定Selector选取的Pod的IP地址(为什么要这样设计了,有什么目的么)。
-
没有配置Selector
Endpoints Controller不创建Endpoints记录。DNS服务器返回如下结果中得一种:
- 对ExternalName类型的Service,返回CNAMNE记录
- 对于其他类型的Service,返回与Service同名的EndPoints的A记录
我好想理解错ExternalName类型,我以为不设置Selector,然后手动创建一个Endpoints就是ExternalName,看样子不是这样的。
虚拟IP的实现
Iptables方案
假设Service的ClusterIp为10.0.0.1,port为1234,targetPort为4567
Service创建后,kube-proxy设定了一系列的iptables规则,这些规则是可以将虚拟IP映射到per-Service的规则。per-Service规则进一步链接到per-Endpoint规则,并最终将网络请求重定向到后端Pod(使用destination-NAT)。
当一个客户端连接到该Service的虚拟Ip地址时,iptables的规则被触发。一个后端Pod将被选中(基于session affinity或者随机选择),且网络报文被重定向到该后端Pod。
使用node-port或load-balancer类型的Service时,以上的代理处理过程是相同的。
IPVS
IPVS的设计是基于in-kernel hash table执行负载均衡,因此,使用LPVS的kube-proxy在Service数量较多的情况仍然能保持好的性能。同时,基于LPVS的kube-proxy可以使用更复杂的负载均衡算法(最少连接数、基于地址的,基于权重的等)