一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第18天,点击查看活动详情。
一、前言
Service 其实是一个抽象的概念,通过虚拟 IP(VIP) 映射出指定的端口,将客户端发来的请求进行代理并转发到后端具有相同 Label 的 Pods 中。
它是 Kubernetes 常用的向外部暴露内部服务的方式。
Tips: 想要集群外也可以访问:
LoadBalancer:提供一个公网的 IPNodePort:使用NAT将Service的端口暴露出去Ingress:Kubernetes提供的一种路由转发机制。
(1)Service的作用
Service通过标签选择器Label可以选择具有相同标签的Pod形成一个集合。
三大作用:
- 对外暴露流量
- 做负载均衡(
load balancing) - 服务发现(
service-discovery)
(2)Service的工作原理
Service根据标签选择器查找Pod- 创建和
Service同名的Endpoints对象(Endpoints是组成Service的一组IP地址和端口Node资源) - 当
Pod地址发生变化时,Endpoints也会随之发生变化,Service接收请求后通过Endpoints查找请求转发的目标地址。
如下是 Service 工作原理图:
Endpoints Controller 的主要用处是负责生成和维护所有 Endpoints 对象,通过监听 Service 和对应 Pods 的变化,及时更新 Service 的 Endpoints 对象。
当 Service 被创建后 Endpoints Controller 会监听符合标签选择器对应的 Pod 的状态,当 Pod 处于 Running 并准备就绪时,Endpoints Controller 就会将 Pod ip 记录到 Endpoints 对象中,所以 Service 发现就是通过 Endpoints 实现的。
具体到每个 Node 上会使用 kube-proxy 实现负载均衡,kube-proxy 会根据 Service 和 Endpoints 的变化更新每个 Node 节点上 Iptables 或 Ipvs 中保存的路由转发规则。
二、Service 的类型
根据 type 类型分为 4 种模式:
-
ClusterIP:这是Kubernetes的默认方式。这其中根据是否会生成ClusterIP又分为普通Service和Headless Service。普通
Service:创建的Service会分配一个集群内部可访问的固定虚拟IP,这是最常使用的方式。Headless Service:创建的 Service 不会分配固定的虚拟IP,同样也不会通过kube-proxy做反向代理和负载均衡,主要通过DNS提供稳定的网络ID进行访问,通常用于StatefulSet中。 -
NodePort:使用ClusterIP,并且将Service的port端口映射到集群中每个Node节点的相同端口port,这样在集群外部访问服务可以直接使用nodeIP:nodePort进行访问。 -
LoadBalancer:在NodePort的基础上(也就是拥有ClusterIP和nodePort),还会向所处的公有云申请负载均衡器LB(负载均衡器的后端直接映射到各Node节点的nodePort上),这样就实现了通过外部的负载均衡器访问服务。 -
ExternalName:这是 Service 的一种特例形式。主要用于解决运行在集群外部的服务问题,这种方式下会返回外部服务的别名来为集群内部提供服务。上述提到的 3 种模式主要依赖于kube-proxy,而这种模式依赖于kube-dns的层级。
举个栗子:
kind: Service
apiVersion: v1
metadata:
name: my-service
namespace: test
spec:
type: ExternalName
externalName: www.donald.com
当使用上面的 YAML 文件创建服务以后,DNS 服务会给 <service-name>.<namespace>.svc.cluster.local(在这里就为 my-service.test.svc.cluster.local)创建一个 CNAME 记录,并向其中写入 www.donald.com。
当查询服务 my-service.test.svc.cluster.local 时,集群中的 DNS 服务就会返回映射的 www.donald.com。
Service 涉及到的 3 种 Port
port:指Service暴露在ClusterIP上的端口port,在Kubernetes集群内部就是通过ClusterIP:port访问服务的。nodePort:指节点Node开放的端口,从Kubernetes集群外部可以通过nodeIP:nodePort访问服务。nodePort的范围是 30000-32767,设置端口时不能够与已经在使用的有冲突。targetPort:指 Pod 开放的端口,外部访问的请求经过 port 端口和nodePort端口,以及kube-proxy服务最终转发到后端 Pod 的targetPort端口,然后进入到 Pod 中的容器内进行处理。
4 种 IP
ClusterIP:这是一个虚拟地址(VIP),没有实际的网络设备承载这个地址,它的底层实现原理是依靠 kube-proxy 通过 iptables 规则重定向到 Node 节点的端口上,然后再负载均衡到后端 Pod。具体过程为:kube-proxy 发现新 Service,在 Node 节点上打开一个任意的 nodePort 端口,创建对应的 iptables 规则或是 IPVS 规则,重定向 Service 的ClusterIP:port到新建的 nodePort 端口上,然后开始处理关于新 Service 的连接访问。NodeIP:上面提到的ClusterIP由于是虚拟的,只能用于 Kubernetes 集群内部的访问。当集群需要提供外部服务,这时就需要为服务提供公共 IP,通过定义 YAML 文件指定 Service 类型spec.type=NodePort,然后集群会在所有 Node 节点上开放一个指定的 nodePort,集群外部通过nodeIP:nodePort就可以访问服务了。PodIP:当每个新 Pod 创建时,集群都会先使用gcr.io/google_containers/pause镜像创建一个容器,然后再创建其它要求的容器。在 Pod 内部其它容器都使用container网络模式,并指定其值为 pause 容器的 ID(即:network_mode: "container: pause 容器 ID"),这样 Pod 内所有容器将共享 pause 容器的网络,所有其他容器在 Pod 内部都处于同一个网络模式下,可以彼此间直接通信,而与外部通信都会经过 pause 容器进行代理,因此 pause 容器 IP 才是真正实际意义上的 PodIP。ExternalIP:外部 IP,通过负载均衡(LB)方式发布服务的话,也就是 LoadBalancer Service 类型,公有云提供的负载均衡器的访问地址。
Service 服务发现
Service服务发现目前有两种类型:环境变量和DNS。通常而言更加推荐使用后一种,也就是DNS。
- 环境变量
当新创建 Pod 时,kubelet 会向该 Pod 中注册所有已经创建的与 Service 相关的环境变量。注意这里就存在一个问题,当某个特定的 Service 晚于 Pod 的创建,那么先创建的 Pod 就不会注册该 Service 的环境变量。因此我们更加推荐使用 DNS 来进行服务发现。
DNS
通过在集群中部署 CoreDNS 服务(在老版本的 Kubernetes 集群中使用的是 KubeDNS)来实现集群内部 Pod 通过 DNS 方式进行通讯。
CoreDNS 是以插件方式集成到 Kubernetes 集群中作为默认的 DNS 服务,这样可以更加灵活方便的进行拓展。