服务发现解决了两个问题,一是运行服务的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所在节点发送到客户端。

  1. client
  2. \ ^
  3. \ \
  4. v \
  5. node 1 <--- node 2
  6. | ^ SNAT
  7. | | --->
  8. v |
  9. 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模式类似,但有以下区别:

  1. IPVS 不需要在宿主机上为每个 Pod 设置 iptables 规则,而是把对这些“规则”的处理放到了内核态,从而极大地降低了维护这些规则的代价。
  2. IPVS使用哈希表作为底层数据结构并在内核空间中工作。这意味着IPVS可以更快地重定向流量,并且在同步代理规则时具有更好的性能。iptables模式下连接复杂度为O(n),而IPVS模式下的复杂度为O(1)。
  3. IPVS提供更多的负载均衡算法,如轮询调度(rr)、最小连接数(lc)、目标哈希(dh)、源哈希(sh)、最短期望延迟(sed)、不排队调度(nq)。

IPVS模式下,IPVS 模块只负责负载均衡和代理功能。而一个完整的 Service 流程正常工作所需要的包过滤、SNAT 等操作,还是要靠 iptables 来实现。在大规模Kubernetes集群里,IPVS模式有明显的性能优势,可通过设置–proxy-mode=ipvs 来开启它。

Port、nodePort、targetPort、containerPort 的区别

  1. Port是Service暴露出来的端口,
  2. nodePort是Node 暴露出来的Port
  3. targetPort是Service的目的端口,这个端口和该服务对应Pod中的containerPort对应,targetPort是对应关系而不是一个实际的端口,Service将原端口映射到Pod中容器的具体端口。
  4. 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 地址。