我们都知道,在K8S集群中,每个Pod都有自己的私有IP地址,并且这些IP地址不是固定的。这意味着其不依赖IP地址而存在。例如,当我们因某种业务需求,需要对容器进行更新操作,则容器很有可能在随后的启动运行过程中被分配到其他IP地址。此外,在K8S集群外部看不到该Pod。因此,Pod若单独运行于K8S体系中,在实际的业务场景中是不现实的,故我们需要通过其他的策略去解决,那么解决方案是什么? 由此,我们引入了Serivce这个概念以解决上述问题。
如下所示,为简单的Pod配置文件定义:
代码语言:javascript复制apiVersion: v1
kind: Pod
metadata:
name: k8s-activemq-pod
labels:
app: k8s-activemq
spec:
containers:
- name: k8s-activemq-prod-container
image: registry.docker.com/baseimg/k8s-active-mq
K8s Service是一种抽象,定义了一组逻辑上可用的pod和策略。服务是一个REST对象,就像一个pod一样,并且具有终结点,因此只要类型支持可见性,就可以从外部访问服务。这到底是什么意思?这意味着即使服务也可以对外界隐藏。
Service 的理念是将一组 Pod 端点划分为单一资源。我们可以配置各种方式来访问该分组。默认情况下,我们会获得稳定的集群 IP 地址,集群内部的客户端可以使用该 IP 地址与 Service 中的 Pod 通信。客户端向稳定 IP 地址发送请求,然后请求会被路由到 Service 的其中一个 Pod。
Service 通过选择器来识别其成员 Pod。为使 Pod 成为 Service 的成员,该 Pod 必须具有选择器中指定的所有标签。标签是附加到对象的任意键值对。
下列 Service 清单所具有的选择器指定了两种标签。selector 字段指示同时具有 app: metrics 标签和 department:engineering 标签的任何 Pod 都是该 Service 的成员。Service配置文件语法定义如下:
代码语言:javascript复制apiVersion: v1
kind: Service
metadata:
name: k8s-activemq-prod-service
spec:
selector:
app: k8s-activemq
ports:
- name: http
port: 8161
nodePort: 32190
- name: tcp
port: 61616
nodePort: 32191
type: NodePort
为何使用 Service?
在 Kubernetes 集群中,每个 Pod 都具有内部 IP 地址。但是,Deployment 中的 Pod 可以随时加入和退出,而且它们的 IP 地址也不是固定的。因此,直接使用 Pod IP 地址毫无意义。通过 Service,您会获得稳定的 IP 地址,该 IP 地址在 Service 的生命周期内有效,即使成员 Pod 的 IP 地址发生变化也仍然有效。
Service 还提供负载平衡。客户端会调用单个稳定的 IP 地址,而且客户端请求在 Service 的成员 Pod 之间保持平衡。
Service 类型
目前,在K8S生态体系中提供了五种类型的 Service:
1、ClusterIP(默认):内部客户端向稳定的内部 IP 地址发送请求。
2、NodePort:客户端向使用 Service 指定的一个或多个 nodePort 值的节点的 IP 地址发送请求。
3、LoadBalancer:客户端向网络负载平衡器的 IP 地址发送请求。
4、ExternalName:内部客户端使用 Service 的 DNS 名称作为外部 DNS 名称的别名。
5、Headless:如果需要对 Pod 进行分组,同时也不需要稳定的 IP 地址,此场景下可以使用 Headless 服务。
ClusterIP
通过集群的内部 IP 暴露服务,选择该值,服务只能够在集群内部可以访问,这也是默认的 ServiceType。简要拓扑如下:
如上图所示,ClusterIP类型的Service就是在K8S节点上创建一个满足Service IP地址的Iptables或Ipvs规则。这种类型的Service的IP地址一定是我们在初始化集群时,指定的Service网络中的地址,这意味着这种类型的Service不能被集群外部客户端所访问,仅能在集群节点上访问。
以下为 ClusterIP 类型的 Service 的实例配置:
代码语言:javascript复制apiVersion: v1
kind: Service
metadata:
name: springboot-demo-service
spec:
selector:
app: metrics
department: sales
type: ClusterIP
ports:
- protocol: TCP
port: 80
targetPort: 8080
NodePort
NodePort 类型是 ClusterIP 类型的扩展。因此,NodePort 类型的 Service 具有集群 IP 地址。通过每个 Node 上的 IP 和静态端口(NodePort)暴露服务。NodePort 服务会路由到 ClusterIP 服务,这个 ClusterIP 服务会自动创建。通过请求 <NodeIP>:<NodePort>,可以从集群的外部访问一个 NodePort 服务。简要拓扑如下:
如上图所示,NodePort类型的Service,是建构在ClusterIP的基础上做的扩展,主要解决了集群外部客户端访问问题。在上图拓扑中,我们可以看到NodePort类型Service在创建时,它会为每个节点上创建一条DNAT规则,外部客户端访问集群任意节点的指定端口,都会被DNAT到对应的Service上,从而实现访问集群内部Pod。对于集群内部客户端的访问它还是通过ClusterIP进行的,NodePort类型Service与ClusterIP类型Service唯一不同的是:NodePort类型Service能够被外部客户端所访问,在集群每个节点上都有对应的DNAT规则。
以下是 NodePort 类型的 Service 的实例配置:
代码语言:javascript复制apiVersion: v1
kind: Service
metadata:
name: springboot-demo-service
spec:
selector:
app: metrics
department: sales
type: NodePort
ports:
- protocol: TCP
port: 80
targetPort: 8080
LoadBalancer
LoadBalancer 类型是 NodePort 类型的扩展。因此,LoadBalancer 类型的 Service 具有集群 IP 地址以及一个或多个 NodePort 值。简要拓扑如下:
如上图所示,LoadBalancer主要基于云提供商的负载均衡器,可以向外部暴露服务。外部的负载均衡器可以路由到 NodePort 服务和 ClusterIP 服务。LoadBalancer这种类型的Service是在NodePort的基础上做的扩展,这种类型Service只能在底层是云环境的K8S上创建,如果底层是非云环境,这种类型无法实现,只能手动搭建反向代理进行对NodePort类型的Service进行反代;它主要解决NodePort类型Service被集群外部访问时的端口映射以及负载。
以下是 LoadBalancer类型的 Service 的实例配置:
代码语言:javascript复制apiVersion: v1
kind: Service
metadata:
name: springboot-demo-service
spec:
selector:
app: metrics
department: engineering
type: LoadBalancer
ports:
- port: 80
targetPort: 8080
ExternalName
ExternalName 类型的 Service 为外部 DNS 名称提供内部别名。内部客户端使用内部 DNS 名称发出请求,然后请求会被重定向到外部名称。简要拓扑如下:
如上图所示,ExternalName这种类型Service主要用来解决对应Service引用集群外部的服务。我们知道对于Service来说,它就是一条Iptables或Ipvs规则,对于后端引用的资源是什么,取决于对应Endpoint关联的是什么资源的Ip地址和端口。如果我们需要在集群中使用集群外部的服务,我们就可以创建ExternalName类型的Service,指定后端关联外部某个服务端ip地址或域名即可。它的工作流程如上图所示,在集群内部客户端访问对应Service时,首先要去Ccore-DNS上查询对应域名的Ip地址,然后再根据Dns返回的Ip地址去连接对应的服务,使用这种类型Service的前提是对应的Coredns能够连接到外部网络解析对应的域名。
以下是 ExternalName类型的 Service 的实例配置:
代码语言:javascript复制apiVersion: v1
kind: Service
metadata:
name: springboot-demo-service
spec:
type: ExternalName
externalName: example.com
ExternalName Service 类型与其他 Service 类型完全不同。事实上,ExternalName 类型的 Service 不符合本主题开头提出的 Service 定义。ExternalName 类型的 Service 不与一组 Pod 相关联,也没有稳定的 IP 地址。相反,ExternalName 类型的 Service 从内部 DNS 名称映射到外部 DNS 名称。
Headless
在某些场景中,我们有时候不需要负载均衡,以及单独的 Service IP,可以通过指定 Cluster IP(spec.clusterIP)的值为 "None" 来创建 Headless Service。我们可以使用 Headless Service 与其他服务发现机制进行接口,而不必与 Kubernetes 的实现捆绑在一起。
对 Headless Service 而言,并不会分配 Cluster IP,Kube-Proxy 不会处理它们,而且平台也不会为它们进行负载均衡和路由。DNS 如何实现自动配置,依赖于 Service 是否定义了 selector。其简要拓扑如下:
以下是 Headless Service类型的 Service 的实例配置:
代码语言:javascript复制apiVersion: v1
kind: Service
metadata:
name: gateway-test
labels:
app: gateway_test
spec:
ports:
- port: 433
name: gateway_api
# clusterIP 设置为 None
clusterIP: None
selector:
app: gateway_test
Headless Services 主要有以下应用场景:
1、自主选择权,有时候 client 想自己来决定使用哪个Real Server,可以通过查询DNS来获取 Real Server 的信息。
2、Headless Service 的对应的每一个 Endpoints,即每一个Pod,都会有对应的DNS域名,这样Pod之间就可以互相访问。
至此,K8S生态体系中的五种类型的 Service简要介绍完成。具体,可以依据当前项目实际场景进行规划与选择。