今天主要学习一下k8s的Service
,之前的文章中有介绍过k8s内服务访问的方式,但是自己也没有特别的去了解具体的实现方式,今天就专门再来学习下Service
这个非常重要的资源。今天主要是学习官方的文档,文档地址:Service。另外我还是觉得中文文档翻译之后不太好理解,所以最好我觉得还是看英文文档。
k8s中的Service
是一个抽象的概念,它定义了一组逻辑的Pod
以及访问这些Pod
的策略(有时这种模式被称作微服务)。直白点讲Service
本身并不提供所谓的服务
,它只是告诉你怎么访问你想要访问的服务
。通常情况下Service
是通过Selector
来定位到目标Pod
的,当然你也可以通过创建相应的Endpoints
定位到具体的服务。
1、虚拟IP和服务代理
在k8s集群中的每个节点上都会运行着一个kube-proxy
,由kube-proxy
负责为非ExternalName
类型的Service
实现一种虚拟IP。
之所以k8s选择代理模式将入站流量转发到后端,而不是通过配置多个A值(ipv6为AAAA)0的DNS
记录,然后依赖轮询名称解析。主要有几点原因:
1、一直以来DNS
都不遵守数据的TTL(Time to live),即使在数据过期后依然会进行缓存;
2、对于某些应用只进行一次DNS
查找,但是结果却会永久缓存;
3、即使应用和库进行了适当的重新解析,DNS 记录上的较低的TTL值低或零值可能会给DNS
带来高负载,从而使管理变得困难。
1、用户空间代理模式
这种模式下kube-proxy
会观察k8s控制节点对Service
对象和 Endpoints
对象的添加和删除操作。对于每一个Service
,kube-proxy
都会在本地的节点上打开一个随机端口,任何连接到这个“代理端口”的请求,都会被转发到该Service
对应的后端Pods
中的某一个上面。具体使用哪一个Pod
,是kube-proxy
基于该Service
的设置项SessionAffinity
来确定的。
最后,kube-proxy
会配置iptables
规则,捕获到达该Service
的clusterIP
(是虚拟 IP)和 port
的请求,并重定向到代理端口,然后由代理端口再代理请求到后端Pod
。
用户空间模式下的kube-proxy
默认通过轮转算法选择后端。
用户空间模式见下图:
2、iptables代理模式
和用户空间代理模式,这种模式下kube-proxy
也会观察k8s控制节点对Service
对象和Endpoints
对象的添加和删除操作。 不同的是对每个Service
,kube-proxy
会通过配置iptables
规则,从而捕获到达该Service
的clusterIP
和port
的请求,然后将请求重定向到该Service
后端集合中的某个上面。对于每个Endpoints
对象,kube-proxy
也会配置 iptables
规则,这个规则会选择一个后端Pod
。
在iptables
模式下kube-proxy
默认会随机选择一个后端。
使用iptables
处理流量具有较低的系统开销,因为流量由Linux netfilter处理,而无需在用户空间和内核空间之间切换,而且这种方法也可能更可靠。
如果kube-proxy
在iptables
模式下运行,kube-proxy
随机选择的Pod
没有响应,那么此次连接失败。这点与用户空间模式不同。在用户空间模式情况下,kube-proxy
如果检测到与第一个Pod
的连接失败,那么它会自动使用其他后端Pod
进行重试。
我们可以使用Pod
readiness probes 验证后端Pod
是否就绪以便iptables
模式下的kube-proxy
仅看到状态正常且就绪的后端。readiness probes
在滚动发布的时候也会用到。
iptables
代理模式如下图:
3、IPVS代理模式
在ipvs
模式下kube-proxy
观察k8s的Services
和Endpoints
,调用netlink
接口相应地创建IPVS
规则,并定期将IPVS
规则与k8s的Services
和Endpoints
同步。该控制循环可确保IPVS
状态与所需状态匹配。访问Service
时,IPVS
将流量定向到后端Pod
之一。
IPVS
代理模式基于类似于iptables
模式的netfilter
钩子函数, 但是使用哈希表作为基础数据结构,并且在内核空间中工作。这意味着与iptables
模式下的kube-proxy
相比,IPVS
模式下的kube-proxy
重定向通信的延迟要短,并且在同步代理规则时具有更好的性能。 与其他代理模式相比IPVS
模式还支持更高的网络流量吞吐量。
IPVS
提供了更多选项来平衡到达后端Pod
的流量。 这些是:
rr:轮替(Round-Robin)
lc:最少链接(Least Connection),即打开链接数量最少者优先
dh:目标地址哈希(Destination Hashing)
sh:源地址哈希(Source Hashing)
sed:最短预期延迟(Shortest Expected Delay)
nq:从不排队(Never Queue)
说明:
要想kube-proxy
使用IPVS
模式,必须在启动kube-proxy
之前保证IPVS
在节点上可用。因为当kube-proxy
以IPVS
代理模式启动时,它将验证IPVS
内核模块是否可用, 如果未检测到IPVS
内核模块,则kube-proxy
将会使用iptables
代理模式运行。
IPVS
代理模式如下图:
在这些代理模式中,在客户端不了解k8s或Service
或Pod
的任何信息的情况下,请求到Service
的ip:port
的流量最终被代理到具体的后端。
如果要确保每次都将来自特定客户端的连接传递到同一Pod
, 则可以通过将service.spec.sessionAffinity
设置为ClientIP
(默认值是None
),来基于客户端的IP地址选择会话关联。 你还可以通过适当设置 service.spec.sessionAffinityConfig.clientIP.timeoutSeconds
来设置最大会话停留时间(默认值为 10800 秒,即 3 小时)。
2、服务发现
k8s支持两种基本的服务发现模式——环境变量和DNS
。
1、环境变量
当Pod
运行在Node
上,kubelet
会为每个存活的Service
添加一组环境变量。 它同时支持Docker links compatible变量(见makeLinkVariables)和简单的 {SVCNAME}_SERVICE_HOST
和 {SVCNAME}_SERVICE_PORT
变量。 这里Service
的名称需大写,横线被转换成下划线。
举个例子,一个名称为 redis-master
的Service
暴露了TCP
端口6379
, 同时给它分配了ClusterIP
地址 10.0.0.11,这个Service
生成了如下环境变量:
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
但是这个环境变量只针对同Namespace
下的Service
,比如我在namespace A
下的Pod
的环境变量中是查看到namespace B
下的Service
的相关信息的。
2、DNS
通常来讲我们的k8s集群都会安装相关的网络插件,比如Calico、Flannel
等来设置DNS服务。
支持集群的DNS
服务器(例如CoreDNS
)会观察k8s API中的新Service
,并为每个服务创建一组DNS
记录。 如果在整个集群中都启用了DNS
,则所有Pod
都应该能够通过其DNS
名称自动解析服务。
例如,如果你在命名空间 my-ns
中有一个名为my-service
的服务,则k8s控制面板和DNS
服务共同为my-service.my-ns
创建DNS
记录。my-ns
命名空间中的Pod
能够通过名称my-service
来找到服务(这里可以直接通过环境变量获取my-service
的clusterIp
和port
等),当然也可以通过my-service.my-ns
来定位到具体的cluserIp
。
其他命名空间中的Pod
必须将名称限定为my-service.my-ns
。这个名称将解析为服务的clusterIP
。
k8s还支持命名端口的DNS SRV
记录。 如果my-service.my-ns
服务具有名为http
的端口,且协议设置为 TCP
,则可以对_http._tcp.my-service.my-ns
执行DNS SRV
查询,以发现该服务http
的端口号以及IP
地址。
k8s DNS
服务器是唯一的一种能够访问ExternalName
类型的Service
的方式。 更多关于ExternalName
信息可以查看DNS Pod 和 Service。