Service API 是 Kubernetes 的组成部分,它是一种抽象用于通过网络暴露 Pod 组合。 每个 Service 对象定义一个逻辑组的端点(通常这些端点是 Pod)以及如何才能访问这些 Pod 的策略。下面会以几个角度来阐述一下 Kubernetes 的服务。
有选择算符服务 vs. 无选择算符服务
有选择符的服务通过选择符选择指定的 Pod,这个服务就是用于通过网路暴露这组 Pod 。服务最常见的用法也是通过有选择符的服务为 Kubernetes Pod 的访问提供抽象。 Pod 和 Service 的定义如下所示:
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
app.kubernetes.io/name: proxy
spec:
containers:
- name: nginx
image: nginx:stable
ports:
- containerPort: 80
name: http-web-svc
---
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
selector:
app.kubernetes.io/name: proxy
ports:
- name: name-of-service-port
protocol: TCP
port: 80
targetPort: http-web-svc
使用无选择算符的服务配合对应的 EndpointSlice ,服务也可以为其他类型的后端提供抽象,包括在集群外运行的后端。无选择符的使用场景有:
- 希望在生产环境中使用外部的数据库集群,但测试环境使用自己的数据库。
- 希望服务指向另一个 名字空间(Namespace) 中或其它集群中的服务。
- 将工作负载迁移到 Kubernetes 时,在 Kubernetes 中运行一部分后端另一部分运行在其他基础设施中。
下面的示例是创建一个名为 my-service
的无选择算符的服务,然后创建一个 my-servive-1
的 EndpointSlice ,将 10.4.5.6
和 10.1.2.3
的 9376
端口提供的服务映射为一个 Kubernetes 服务:
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
ports:
- protocol: TCP
port: 80
targetPort: 9376
---
apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
name: my-service-1 # 按惯例将服务的名称用作 EndpointSlice 名称的前缀
labels:
# 你应设置 "kubernetes.io/service-name" 标签。
# 设置其值以匹配服务的名称
kubernetes.io/service-name: my-service
addressType: IPv4
ports:
- name: '' # 留空,因为 port 9376 未被 IANA 分配为已注册端口
appProtocol: http
protocol: TCP
port: 9376
endpoints:
- addresses:
- "10.4.5.6" # 此列表中的 IP 地址可以按任何顺序显示
- "10.1.2.3"
有头服务 vs. 无头服务
无头服务(Headless 服务)就是不适用 Kubernetes 自带的负载均衡和 Service IP 服务,不通过 kube-proxy 处理的服务。可以通过指定 Cluster IP(spec.clusterIP
)的值为 "None"
来创建 Headless Service。
有头服务就是通过 kube-proxy 处理的由 Kubernetes 提供负载均衡和提供 Service IP 的服务。
无头服务也分带选择算符的服务和无选择算符的服务。
有选择算符的无头服务
对有选择算符的无头服务,Kubernetes 控制平面在 Kubernetes API 中创建 EndpointSlice 对象, 并且修改 DNS 配置返回 A 或 AAA 条记录(IPv4 或 IPv6 地址),通过这个地址直接到达 Service 的后端 Pod 上。
无选择算符的无头服务
对没有定义选择算符的无头服务,控制平面不会创建 EndpointSlice 对象。 然而 DNS 系统会查找和配置以下之一:
-
对于 type: ExternalName 服务,查找和配置其 CNAME 记录
-
对所有其他类型的服务,针对 Service 的就绪端点的所有 IP 地址,查找和配置 DNS A / AAAA 条记录
-
对于 IPv4 端点,DNS 系统创建 A 条记录。
-
对于 IPv6 端点,DNS 系统创建 AAAA 条记录。
-
服务发布类型
Kubernetes ServiceTypes 允许指定你所需要的 Service 类型,具体的类型以及行为如下:
-
ClusterIP:通过集群的内部 IP 暴露服务,选择该值时服务只能够在集群内部访问。 这也是你没有为服务显式指定 type 时使用的默认值。 你可以使用 Ingress 或者 Gateway API 向公众暴露服务。
-
NodePort:通过每个节点上的 IP 和静态端口(NodePort)暴露服务。 为了让节点端口可用,Kubernetes 设置了集群 IP 地址,这等同于你请求
type: ClusterIP
的服务。 -
LoadBalancer:使用云提供商的负载均衡器向外部暴露服务。 外部负载均衡器可以将流量路由到自动创建的 NodePort 服务和 ClusterIP 服务上。
-
ExternalName:通过返回 CNAME 记录和对应值,可以将服务映射到 externalName 字段的内容(例如,foo.bar.example.com)。 无需创建任何类型代理。
ClusterIP 类型
type: ClusterIP
的 Service 在集群作用域范围内有自己的虚拟 IP 地址。 集群内的客户端可以使用该虚拟 IP 地址进行连接,Kubernetes 通过不同的后台 Pod 对该 Service 的流量进行负载均衡。
当 Kubernetes 需要为 Service 分配虚拟 IP 地址时,该分配会通过以下两种方式之一进行:
-
动态分配: 集群的控制面自动从所配置的 IP 范围内为
type: ClusterIP
选择一个空闲 IP 地址。 -
静态分配: 根据为 Service 所配置的 IP 范围,选定并设置你的 IP 地址。
下面是为一个集群的 DNS Service 配置 Cluster IP 的示例。
作为一种非强制性的约定,一些 Kubernetes 安装程序将 Service IP 范围中的第 10 个 IP 地址分配给 DNS 服务。假设将集群的 Service IP 范围配置为 10.96.0.0/16,并且希望 DNS Service IP 为 10.96.0.10
,则必须创建如下 Service:
apiVersion: v1
kind: Service
metadata:
labels:
k8s-app: kube-dns
kubernetes.io/cluster-service: "true"
kubernetes.io/name: CoreDNS
name: kube-dns
namespace: kube-system
spec:
clusterIP: 10.96.0.10
ports:
- name: dns
port: 53
protocol: UDP
targetPort: 53
- name: dns-tcp
port: 53
protocol: TCP
targetPort: 53
selector:
k8s-app: kube-dns
type: ClusterIP
NodePod 类型
如果你将 type
字段设置为 NodePort
,则 Kubernetes 控制平面将在 --service-node-port-range
标志指定的范围内分配端口(默认值:30000-32767)。 每个节点将那个端口(每个节点上的相同端口号)代理到你的服务中。 你的服务在其 .spec.ports[*].nodePort
字段中报告已分配的端口。
使用 NodePort 可以让你自由设置自己的负载均衡解决方案, 配置 Kubernetes 不完全支持的环境, 甚至直接暴露一个或多个节点的 IP 地址。
对于 NodePort 服务,Kubernetes 额外分配一个端口(TCP、UDP 或 SCTP 以匹配服务的协议)。 集群中的每个节点都将自己配置为监听分配的端口并将流量转发到与该服务关联的某个就绪端点。 通过使用适当的协议(例如 TCP)和适当的端口(分配给该服务)连接到所有节点, 你将能够从集群外部使用 type: NodePort
服务。
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
type: NodePort
selector:
app.kubernetes.io/name: MyApp
ports:
# 默认情况下,为了方便起见,`targetPort` 被设置为与 `port` 字段相同的值。
- port: 80
targetPort: 80
# 可选字段
# 默认情况下,为了方便起见,Kubernetes 控制平面会从某个范围内分配一个端口号(默认:30000-32767)
nodePort: 30007
LoadBalancer 类型
在使用支持外部负载均衡器的云提供商的服务时,设置 type 的值为 “LoadBalancer”, 将为 Service 提供负载均衡器。 负载均衡器是异步创建的,关于被提供的负载均衡器的信息将会通过 Service 的 status.loadBalancer 字段发布出去。
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app.kubernetes.io/name: MyApp
ports:
- protocol: TCP
port: 80
targetPort: 9376
clusterIP: 10.0.171.239
type: LoadBalancer
status:
loadBalancer:
ingress:
- ip: 192.0.2.127
ExternalName 类型
类型为 ExternalName 的服务将服务映射到 DNS 名称,而不是典型的选择算符,例如 my-service 或者 cassandra。 你可以使用 spec.externalName
参数指定这些服务。
例如,以下 Service 定义将 prod
名称空间中的 my-service
服务映射到 my.database.example.com:
apiVersion: v1
kind: Service
metadata:
name: my-service
namespace: prod
spec:
type: ExternalName
externalName: my.database.example.com
当查找主机 my-service.prod.svc.cluster.local 时,集群 DNS 服务返回 CNAME
记录, 其值为 my.database.example.com。 访问 my-service 的方式与其他服务的方式相同,但主要区别在于重定向发生在 DNS 级别,而不是通过代理或转发。 如果以后你决定将数据库移到集群中,则可以启动其 Pod,添加适当的选择算符或端点以及更改服务的 type 。