我们分享的主题是,Service和DNS和服务发现的关系
我们使用Service来进行绑定IP,这样就解决了Pod的IP不固定的问题,也解决了Pod实例之间总有负载均衡的需求
一个典型的Service定义如下
apiVersion: v1
kind: Service metadata: name: hostnames spec: selector: app: hostnames ports: – name: default protocol: TCP port: 80 targetPort: 9376 |
我们使用了selector来声明代理所有携带app=hostnames标签的Pod,并且这个Service的80,是为了代理目标Pod的9376端口
被代理的Pod声明如下
apiVersion: apps/v1
kind: Deployment metadata: name: hostnames spec: selector: matchLabels: app: hostnames replicas: 3 template: metadata: labels: app: hostnames spec: containers: – name: hostnames image: k8s.gcr.io/serve_hostname ports: – containerPort: 9376 protocol: TCP |
这个Pod的作用,就是每次访问9376端口,都返回自己的hostname
而被selector选中的pod,就被称为service的EndPoints,可以通过kubectl get ep命令去看到这些Pod,而且只有处于Running状态,且readinessProbe检查通过的Pod,才会出现在Service的Endpoints列表,当某一个Pod出现问题的时候,Kubernetes会自动将其从Service中摘除
通过Service的VIP地址去访问到对应代理的Pod
对于VIP,这是Kubernetes为Service自动分配的,我们直接连续的访问Service的VIP和代理端口,就会返回了不同的Pod的hostname,这就是Serivce提供的Round Robin方法的负载均衡
那么Service如何实现的,
其实是靠着kube-proxy组件,加上iptables共同实现的
我们创建了对应的hostnames的Service,一旦被提交给了Kubernetes,那么kube-proxy就可以通过Service的Informer感知到这个Service对象的添加,就可以在宿主机上创建一个iptables规则
-A KUBE-SERVICES -d 10.0.1.175/32 -p tcp -m comment –comment “default/hostnames: cluster IP” -m tcp –dport 80 -j KUBE-SVC-NWV5X2332I4OT4T3
这个iptables的规则说明了,凡是目的地址10.0.1.175,目的端口是80的包,都需要跳转到另一条名为KUBE-SVC-NWV6X2332140T4T3的链进行处理
对于上面的10.0.1.175正是Service的VIP,这个规则,为Service设置了一个固定的入口\
那么这个iptables的具体细节是什么呢?理论如下
-A KUBE-SVC-NWV5X2332I4OT4T3 -m comment –comment “default/hostnames:” -m statistic –mode random –probability 0.33332999982 -j KUBE-SEP-WNBA2IHDGP2BOBGZ
-A KUBE-SVC-NWV5X2332I4OT4T3 -m comment –comment “default/hostnames:” -m statistic –mode random –probability 0.50000000000 -j KUBE-SEP-X3P2623AGDH6CDF3
-A KUBE-SVC-NWV5X2332I4OT4T3 -m comment –comment “default/hostnames:” -j KUBE-SEP-57KPRZ3JQVENLNBR
上面规定的返回的目的IP,以及返回的概率
iptables的匹配规则是从上到下逐条进行的,为了保证选中的概率相同,那么probability字段的值分别为1/3 1/2 1三个
这样,第一条规则被选中的概率为1/3,第一条没有被选中,只有两条了,第二条的概率是1/2,以及最后的一条1
这三条对应的三条规则就是DNAT规则,我们设置对应的DNAT,就是在PREROUTING检查点之前,将流入的IP包的目的地址和端口,改为指定的新的目的地址和端口
这样,就将访问VIP的包,改为了访问具体一个后端Pod的IP包了,这些iptables的规则,正是kube-proxy通过监听Pod的变化事件,在宿主机上生成并维护的
这就是Service的基本工作原理,
Kubernetes的kube-proxy还支持一种叫做IPVS的模式
因为直接配置iptables的话,需要在宿主机上配置相当多的iptables规则,而且kube-proxy还需要在控制循环中不断的刷新这些规则来始终保持是正确的
当宿主机上有大量Pod的时候,成百上千条的iptables规则在不断的刷新,可能会占用大量的宿主机资源,于是有了IPVS模式的Service去解决这个问题
IPVS的工作模式和iptables模式类似,创建了前面的Service之后,kube-proxy会首先在宿主机上创建一个虚拟网卡,并分配Service VIP作为IP地址
# ip addr
… 73:kube-ipvs0:<BROADCAST,NOARP> mtu 1500 qdisc noop state DOWN qlen 1000 link/ether 1a:ce:f5:5f:c1:4d brd ff:ff:ff:ff:ff:ff inet 10.0.1.175/32 scope global kube-ipvs0 valid_lft forever preferred_lft forever |
kube-proxy就会通过Linux的IPVS模块,为这个IP地址设置三个IPVS虚拟主机,并设置三个虚拟主机之间使用轮询模式来作为负载均衡策略,我们可以看到对应额设置
# ipvsadm -ln
IP Virtual Server version 1.2.1 (size=4096) Prot LocalAddress:Port Scheduler Flags -> RemoteAddress:Port Forward Weight ActiveConn InActConn TCP 10.102.128.4:80 rr -> 10.244.3.6:9376 Masq 1 0 0 -> 10.244.1.7:9376 Masq 1 0 0 -> 10.244.2.3:9376 Masq 1 0 0 |
三个虚拟主机的IP地址和端口,就是三个被代理的Pod
任何发往10.102.128.4:80的请求,就会被IPVS模块转发到某个后端Pod上了
相比iptables,IPVS在内核中的实现是基于Netfilter的NAT模式,转发这一层上,理论上IPVS并没有显著的性能提升,IPVS也不需要去维护iptables的规则,而是将规则的处理放在了内核态上,极大的降低了维护这些规则的代价
所以在大集群规模的Kubernetes中,为kube-proxy设置-proxy-mode=ipvs来开启这个功能,进行提升性能
而且,Service和pod都会被分配对应的DNS A记录,去解析IP的记录,对于Cluster IP模式的Service来说,A记录的格式是 ..svc.cluster.lcoal,访问这个A记录的时候,解析到的就是Service 的 VIP
如果指定了Headless Service,并且Pod本身声明了hostname和subdomain字段,这时候Pod的A记录就会变成 <Pod的hostName> ..svc.cluster,local
apiVersion: v1
kind: Service metadata: name: default-subdomain spec: selector: name: busybox clusterIP: None ports: – name: foo port: 1234 targetPort: 1234 — apiVersion: v1 kind: Pod metadata: name: busybox1 labels: name: busybox spec: hostname: busybox-1 subdomain: default-subdomain containers: – image: busybox command: – sleep – “3600” name: busybox |
上面的Pod就指定了直接地址,可以直接通过busybox-1.default-subdomain.default.svc.cluster.local解析到这个Pod的IP地址了
这些ip的挂载的变化,是因为在Kubernetes中 /etc/hosts文件都是单独挂载的,这是为何kubelet可以对hostname进行修改并且pod重建的时候进行重新挂载
总结
我们讲解了Service的工作原理,实际上,Service机制,以及Kubernets中的DNS插件,都是在解决一个问题,如何找一个容器
在平台级的项目,就是服务发现,当一个服务的IP地址是不固定的,并且都没法提前获知的,如何通过一个固定的方式来访问到这个Pod呢?
Service就是提供了一个稳定的访问地址,这个Pod和Service的关系,是可以通过Label进行确定的
Headless Service可以为其提供一个稳定的DNS名字,对应的名字可以通过Pod名字和Service名字进行拼接
Kubernets的Service的负载均衡策略,在iptables和ipvs模式下,有哪几种?具体的工作模式呢?
必然会存在着轮询去进行负载均衡的策略以及利用会话保持的进行保证客户端分配固定的pod Ip的模式