前言
在这里,你将了解 Kubernetes 集群如何实现通过服务名,进行服务发现,负载均衡,调用后端服务。
这里,我们以服务名为ticknet为例,假设我们要访问内部服务ticknet的某个http接口,则,我们的请求链接格式可以是:http://ticknet/get/user/10000
1. kube-dns服务发现原理
DNS:我们非常熟悉且最简单的一种方式,跟域名和IP映射的原理类似,我们可以将服务域名名称和一到多个机器IP进行关联或者是一个负载均衡器(指向服负载均衡好处是可以避免失效DNS条目问题)。DNS的服务发现方式最大的优点就是它是一种大家熟知的标准形式,技术支持性好。缺点就是当服务节点的启动和销毁变得更加动态时DNS更新条目很难做到高可用和实时性。
K8S选择的是DNS作服务发现,除此之外,感兴趣还可以了解Zookeeper、Consoul、Doozerd、Eureka、Etcd
注意:Etcd在k8s集群中的作用是用于保存集群所有的网络配置和对象的状态信息。如果etcd需要实现负载均衡,需要与第三方工具结合,常与Registrator(通过检查容器在线或停止来完成相关服务数据的注册和更新)和Confd(为轻量级的配置管理工具通过储存在Etcd中的数据来保持配置文件的最新状态)结合。
a. kube-dns原理
kube-dns组成
etcd存储SkyDNS需要的各种数据;kube2sky是数据写入方;skydns是数据读入方;execheathz是辅助容器,有健康检查作用。
域名格式
kube-dns支持的域名格式,具体为:
b. K8S自动为容器配置域名解析
进入K8S集群某个Pod容器,我们通过命令行cat /etc/resolv.conf,得到:
nameserver 172.22.0.10
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
感兴趣的可以在网上查找一些resolv.conf的资料来了解具体的含义。之所以能够通过Service名称和Namespace就能访问Service,就是因为search配置的规则。
举个栗子:我们有服务名ticknet,则其服务名(Service)对应的完整域名就是ticknet.default.svc.cluster.local。看到这里,相信很多人会有疑问,既然完整域名是这样的,那为什么在Pod中只通过Service名称和Namespace就能访问Service呢?下面kube-proxy会告诉你。
c. 通过域名配置解析到该服务的IP地址
在解析域名(此时域名我叫“短域名”)时会自动拼接成完整域名去查询DNS。查询到DNS后,我们查找到该DNS的A记录IP地址,然后转发请求该IP地址。
该IP地址是我们服务名的IP地址,也可以说是该服务的集群IP地址,也可以说是该服务的网关IP。
但是我们是如何从服务IP请求转发到其“内部”的Pod呢?这就是接下来要讲的负载均衡了。
2. kube-proxy实现负载均衡
service是一组pod的服务抽象,相当于一组pod的LB,负责将请求分发给对应的pod。service会为这个LB提供一个IP,一般称为cluster IP。
kube-proxy的作用主要是负责service的实现,具体来说,就是实现了内部从pod到service和外部的从node port向service的访问。
举个栗子:我们有服务名ticknet,其下,我们运行了两个实例(pod)。那么kube-proxy的作用,就是可以将对服务 ticknet的请求转发到其下任一个实例容器上。
当然,我们可以通过kubectl logs
a.kube-proxy负载均衡的方式
Iptables模式(目前的默认模式),它支持相当复杂的基于规则的IP管理。在内核中通过iptables的NAT实现本地转发和负载均衡。默认负载均衡方案是随机转发。在大规模试用下存在性能问题。。
Userspace模式。这种模式时最早的,不过已经不推荐使用了,它使用循环的负载分配,在IP列表上来分配下一个可以使用的pod,然后更换(或置换)该列表。效率低,因为需要在内核空间和用户空间转换。
Ipvs模式。可选方案,如果内核支持且用户指定那么kube-proxy将使用这种模式。在早期版本的K8S中不支持。
b. iptables实现负载均衡
Kubernetes通过在目标node的iptables中的nat表的PREROUTING和POSTROUTING链中创建一系列的自定义链 (这些自定义链主要是“KUBE-SERVICES”链、“KUBE-POSTROUTING”链、每个服务所对应的“KUBE-SVC-XXXXXXXXXXXXXXXX”链和“KUBE-SEP-XXXXXXXXXXXXXXXX”链),然后通过这些自定义链对流经到该node的数据包做DNAT和SNAT操作以实现路由、负载均衡和地址转换。
先来看一下iptables。通过命令过滤iptables逐条分析请求如何通过iptables实现负载均衡
首先通过通过ticknet端口1081访问,则会进入到以下链: (命令iptables -S -t nat | grep ticknet)
-A KUBE-SERVICES ! -s 172.21.0.0/16 -d 172.22.205.23/32 -p tcp -m comment --comment "default/ticknet:1081tcp10812 cluster IP" -m tcp --dport 1081 -j KUBE-MARK-MASQ
-A KUBE-SERVICES -d 172.22.205.23/32 -p tcp -m comment --comment "default/ticknet:1081tcp10812 cluster IP" -m tcp --dport 1081 -j KUBE-SVC-YEIPYIOWWP7C4WPS
然后进一步跳转到KUBE-SEP-MTUPTVFIZED52LUP的链:(命令iptables -S -t nat | grep KUBE-SEP-MTUPTVFIZED52LUP)
-A KUBE-SVC-YEIPYIOWWP7C4WPS -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-MTUPTVFIZED52LUP
-A KUBE-SVC-YEIPYIOWWP7C4WPS -j KUBE-SEP-UEPUCSEE5ENLOTNT
这里利用了iptables的–probability的特性,使连接有50%的概率分别进入两个链 KUBE-SEP-MTUPTVFIZED52LUP 和 KUBE-SEP-UEPUCSEE5ENLOTNT 。
以 KUBE-SEP-MTUPTVFIZED52LUP 为例,发现就是将请求通过DNAT发送到172.21.192.6的1081端口:(命令iptables -S -t nat | grep KUBE-SEP-MTUPTVFIZED52LUP)
-A KUBE-SEP-MTUPTVFIZED52LUP -s 172.21.192.6/32 -j KUBE-MARK-MASQ
-A KUBE-SEP-MTUPTVFIZED52LUP -p tcp -m tcp -j DNAT --to-destination 172.21.192.6:1081
分析完nodePort的工作方式,接下里说一下clusterIP的访问方式。
对于直接访问cluster IP(10.254.162.44)的3306端口会直接跳转到KUBE-SVC-YEIPYIOWWP7C4WPS。
-A KUBE-SERVICES -d 172.22.205.23/32 -p tcp -m comment --comment "tce-sandbox/tdea-test:1081tcp10812 cluster IP" -m tcp --dport 1081 -j KUBE-SVC-YEIPYIOWWP7C4WPS
接下来的跳转方式同上文,这里就不再赘述了。
所以我们通过iptables的负载均衡转发,默认是随机的方式
3. 总结