服务发现解决了两个问题,一是运行服务的Pod的IP不固定,需要提供固定访问入口;二是提供服务的多个Pod需要负载均衡。Service 是由 kube-proxy 组件,加上 iptables 来共同实现的。
Service类型
ClusterIP
通过集群的内部 IP 暴露服务,服务只能够在集群内部可以访问,这也是默认的 ServiceType。ClusterIP公开的内容:
- spec.clusterIp:spec.ports[*].port
可以从spec.clusterIp端口访问它。如果设置了spec.ports [*].targetPort,它将从端口路由到targetPort。调用kubectl get services时获得的CLUSTER-IP是内部在集群内分配给此服务的IP。
NodePort
通过每个 Node 上的 IP 和静态端口(每个 Node 上的同一端口,由nodePort指定)暴露服务,可以从集群的外部访问。NodePort公开的内容:
:spec.ports[*].nodePort - spec.clusterIp:spec.ports[*].port
如果通过nodePort的方式从节点的外部IP访问此服务,它会将请求路由spec.clusterIp:spec.ports[].port,然后将其路由到spec.ports [].targetPort。Service的ClusterIP 服务会自动创建。kubernetes将为nodePort分配端口,默认:30000-32767,也可以指定。
特别的,在该模式下,在IP包离开宿主机发往目的 Pod 时,Kubernetes会对这个IP包做一次 SNAT 操作,这是因为当一个客户端通过nodeIP+nodePort访问不在被访问宿主机上运行的Pod时,给客户端返回信息的是另一个宿主机上的进程,该信息必须原路返回,而不能由Pod所在节点发送到客户端。
client\ ^\ \v \node 1 <--- node 2| ^ SNAT| | --->v |endpoint
LoadBalancer
使用云提供商的负载均衡器,可以向外部暴露服务。LoadBalancer公开的内容:
- spec.loadBalancerIp:spec.ports[*].port
:spec.ports[*].nodePort - spec.clusterIp:spec.ports[*].port
可以从负载均衡器的IP地址访问此服务,该IP地址将请求路由到nodePort,而nodePort又将请求路由到clusterIP端口,于是可以像访问NodePort或ClusterIP服务一样访问此服务。
iptables,IPVS区别
在iptables模式下,kube-proxy监视API Server写入的service和endpoint的变化情况,在每个node上,对于每个services与对应的每个endpoint,都会生成相应的iptables规则。默认的策略是随机选择一个 pod。
IPVS模式和iptables模式类似,但有以下区别:
- IPVS 不需要在宿主机上为每个 Pod 设置 iptables 规则,而是把对这些“规则”的处理放到了内核态,从而极大地降低了维护这些规则的代价。
- IPVS使用哈希表作为底层数据结构并在内核空间中工作。这意味着IPVS可以更快地重定向流量,并且在同步代理规则时具有更好的性能。iptables模式下连接复杂度为O(n),而IPVS模式下的复杂度为O(1)。
- IPVS提供更多的负载均衡算法,如轮询调度(rr)、最小连接数(lc)、目标哈希(dh)、源哈希(sh)、最短期望延迟(sed)、不排队调度(nq)。
IPVS模式下,IPVS 模块只负责负载均衡和代理功能。而一个完整的 Service 流程正常工作所需要的包过滤、SNAT 等操作,还是要靠 iptables 来实现。在大规模Kubernetes集群里,IPVS模式有明显的性能优势,可通过设置–proxy-mode=ipvs 来开启它。
Port、nodePort、targetPort、containerPort 的区别
- Port是Service暴露出来的端口,
- nodePort是Node 暴露出来的Port
- targetPort是Service的目的端口,这个端口和该服务对应Pod中的containerPort对应,targetPort是对应关系而不是一个实际的端口,Service将原端口映射到Pod中容器的具体端口。
- containerPort是Pod中容器暴露出的端口
例如一个简单的pod和一个Service:
apiVersion: v1
kind: Pod
metadata:
labels:
run: nginx
name: nginx
spec:
containers:
- image: nginx
name: nginx
ports:
- containerPort: 80
创建了一个测试用的Pod, 用来跑nginx,container的端口打开在80口。
apiVersion: v1
kind: Service
metadata:
labels:
run: nginx
spec:
ports:
- nodePort: 31199
port: 8080
protocol: TCP
targetPort: 80
selector:
run: nginx
type: NodePort
创建了一个NodePort型的service, 这个service通过selector来寻找相同label(run: nginx)的Pod。该Service会在各Node上开31199端口,映射到Service的8080端口。并将Service自身8080端口映射到Pod的80端口,即nginx服务的端口。
于是,内部可通过ClusterIP+8080、外部可通过NodeIP+31199访问该服务。
在集群内部,可通过“ClusterIP+Port”来访问一个服务
在集群外部,可通过“任意Node的公网IP+nodePort”来访问一个服务
DNS
DNS 服务器监视着创建新 Service 的 Kubernetes API,从而为每一个 Service 创建一组 DNS 记录。如果整个集群的 DNS 一直被启用,那么所有的 Pod 应该能够自动对 Service 进行名称解析。
对于 ClusterIP 模式的 Service的 DNS A 记录的格式是:..svc.cluster.local。当访问这条 A 记录的时候,它解析到的就是该 Service 的 VIP 地址。对于指定了 clusterIP=None 的 Headless Service 来说,它的 DNS A 记录的格式也是:..svc.cluster.local。但是,当访问这条 A 记录的时候,它返回的是所有被代理的 Pod 的 IP 地址的集合。Service 代理的 Pod 被自动分配的 A 记录的格式是:..pod.cluster.local。这条记录指向 Pod 的 IP 地址。
