服务
在 Kubernetes 中,服务是一种方法,用于暴露作为集群中一个或多个 Pod 运行的网络应用程序。
Kubernetes 中服务的关键目标是,您无需修改现有应用程序即可使用不熟悉的服务发现机制。您可以在 Pod 中运行代码,无论此代码是为云原生世界设计的代码,还是您已容器化的旧版应用程序。您可以使用服务使该组 Pod 可在网络上使用,以便客户端可以与其交互。
如果您使用 Deployment 运行您的应用程序,则该 Deployment 可以动态创建和销毁 Pod。从一个时刻到另一个时刻,您不知道有多少 Pod 正在运行并且处于健康状态;您甚至可能不知道这些健康 Pod 的名称。Kubernetes Pod 被创建和销毁以匹配集群的所需状态。Pod 是短暂资源(您不应该期望单个 Pod 是可靠和持久的)。
每个 Pod 都有自己的 IP 地址(Kubernetes 期望网络插件确保这一点)。对于集群中的给定 Deployment,在某一时刻运行的 Pod 集可能与稍后运行该应用程序的 Pod 集不同。
这会导致一个问题:如果一些 Pod 集(称为“后端”)为集群内的其他 Pod(称为“前端”)提供功能,那么前端如何找出并跟踪要连接的 IP 地址,以便前端可以使用工作负载的后端部分?
介绍服务。
Kubernetes 中的服务
服务 API(Kubernetes 的一部分)是一种抽象,可以帮助您通过网络公开 Pod 组。每个服务对象定义一组逻辑端点(通常这些端点是 Pod),以及有关如何使这些 Pod 可访问的策略。
例如,考虑一个无状态的图像处理后端,它正在使用 3 个副本运行。这些副本是可互换的——前端不关心使用哪个后端。虽然组成后端集的实际 Pod 可能会发生变化,但前端客户端不应该需要知道这一点,也不应该需要跟踪后端集本身。
服务抽象使这种解耦成为可能。
服务所针对的 Pod 集通常由您定义的 选择器 确定。要了解有关定义服务端点的其他方法的信息,请参阅 没有选择器的服务。
如果您的工作负载使用 HTTP,您可能选择使用 Ingress 来控制网络流量到达该工作负载的方式。Ingress 不是服务类型,但它充当集群的入口点。Ingress 允许您将路由规则合并到单个资源中,以便您可以将工作负载的多个组件(在集群中单独运行)暴露在单个侦听器后面。
用于 Kubernetes 的 网关 API 提供了超出 Ingress 和服务的额外功能。您可以在集群中添加网关——它是一系列扩展 API,使用 CustomResourceDefinitions 实现——然后使用它们来配置对集群中运行的网络服务的访问。
云原生服务发现
如果您能够在应用程序中使用 Kubernetes API 进行服务发现,则可以查询 API 服务器 以查找匹配的 EndpointSlices。Kubernetes 每当服务中的 Pod 集发生变化时,就会更新服务的 EndpointSlices。
对于非原生应用程序,Kubernetes 提供了在应用程序和后端 Pod 之间放置网络端口或负载均衡器的方法。
无论哪种方式,您的工作负载都可以使用这些 服务发现 机制来查找要连接的目标。
定义服务
服务是一个 对象(与 Pod 或 ConfigMap 是对象的方式相同)。您可以使用 Kubernetes API 创建、查看或修改服务定义。通常,您使用 kubectl
之类的工具来为您执行这些 API 调用。
例如,假设您有一组 Pod,每个 Pod 监听 TCP 端口 9376,并被标记为 app.kubernetes.io/name=MyApp
。您可以定义一个服务来发布该 TCP 侦听器
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app.kubernetes.io/name: MyApp
ports:
- protocol: TCP
port: 80
targetPort: 9376
应用此清单将创建一个名为“my-service”的新服务,并使用默认的 ClusterIP 服务类型。该服务针对具有 app.kubernetes.io/name: MyApp
标签的任何 Pod 上的 TCP 端口 9376。
Kubernetes 为此服务分配一个 IP 地址(集群 IP),该地址由虚拟 IP 地址机制使用。有关该机制的更多详细信息,请阅读 虚拟 IP 和服务代理。
该服务的控制器会不断扫描匹配其选择器的 Pod,然后对服务的 EndpointSlices 集进行必要的更新。
服务对象的名称必须是有效的 RFC 1035 标签名称。
注意
服务可以将任何传入的port
映射到 targetPort
。默认情况下,为了方便起见,targetPort
设置为与 port
字段相同的值。端口定义
Pod 中的端口定义具有名称,您可以在服务的 targetPort
属性中引用这些名称。例如,我们可以通过以下方式将服务的 targetPort
绑定到 Pod 端口
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
即使服务中的 Pod 混合使用单个配置的名称,并且相同网络协议通过不同的端口号可用,这也适用。这为部署和发展您的服务提供了很大的灵活性。例如,您可以在后端软件的下一个版本中更改 Pod 公开的端口号,而不会影响客户端。
服务的默认协议是 TCP;您也可以使用任何其他 支持的协议。
由于许多服务需要公开多个端口,因此 Kubernetes 支持为单个服务 定义多个端口。每个端口定义可以具有相同的 protocol
,也可以具有不同的 protocol
。
没有选择器的服务
服务最常通过选择器抽象对 Kubernetes Pod 的访问,但在与一组相应的 EndpointSlices 对象一起使用且没有选择器时,服务可以抽象其他类型的后端,包括在集群外部运行的后端。
例如
- 您希望在生产环境中拥有一个外部数据库集群,但在测试环境中,您使用自己的数据库。
- 您希望将服务指向不同 命名空间 或其他集群中的服务。
- 您正在将工作负载迁移到 Kubernetes。在评估方法时,您仅在 Kubernetes 中运行部分后端。
在任何这些情况下,您都可以定义不指定选择器来匹配 Pod 的服务。例如
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
ports:
- name: http
protocol: TCP
port: 80
targetPort: 9376
由于此服务没有选择器,因此不会自动创建相应的 EndpointSlice(和旧版端点)对象。您可以通过手动添加 EndpointSlice 对象,将服务映射到其运行的网络地址和端口。例如
apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
name: my-service-1 # by convention, use the name of the Service
# as a prefix for the name of the EndpointSlice
labels:
# You should set the "kubernetes.io/service-name" label.
# Set its value to match the name of the Service
kubernetes.io/service-name: my-service
addressType: IPv4
ports:
- name: http # should match with the name of the service port defined above
appProtocol: http
protocol: TCP
port: 9376
endpoints:
- addresses:
- "10.4.5.6"
- addresses:
- "10.1.2.3"
自定义 EndpointSlices
当您为服务创建 EndpointSlice 对象时,您可以为 EndpointSlice 使用任何名称。命名空间中的每个 EndpointSlice 必须具有唯一的名称。您可以通过在该 EndpointSlice 上设置 kubernetes.io/service-name
标签 将 EndpointSlice 链接到服务。
注意
端点 IP 不能是:环回(IPv4 的 127.0.0.0/8,IPv6 的 ::1/128)或链路本地(IPv4 的 169.254.0.0/16 和 224.0.0.0/24,IPv6 的 fe80::/64)。
端点 IP 地址不能是其他 Kubernetes 服务的集群 IP,因为 kube-proxy 不支持虚拟 IP 作为目标。
对于您自己创建的 EndpointSlice 或您自己代码中的 EndpointSlice,您还应该选择一个值用于 endpointslice.kubernetes.io/managed-by
标签。如果您创建自己的控制器代码来管理 EndpointSlices,请考虑使用类似于 "my-domain.example/name-of-controller"
的值。如果您正在使用第三方工具,请使用工具的名称(全部小写),并将空格和其他标点符号更改为连字符(-
)。如果人们直接使用 kubectl
之类的工具来管理 EndpointSlices,请使用描述这种手动管理的名称,例如 "staff"
或 "cluster-admins"
。您应该避免使用保留值 "controller"
,该值标识由 Kubernetes 自己的控制平面管理的 EndpointSlices。
访问没有选择器的服务
访问没有选择器的服务与它有选择器的方式相同。在 示例 中,对于没有选择器的服务,流量将路由到 EndpointSlice 清单中定义的两个端点之一:TCP 连接到 10.1.2.3 或 10.4.5.6,在端口 9376 上。
注意
Kubernetes API 服务器不允许代理到未映射到 Pod 的端点。例如,kubectl port-forward service/<service-name> forwardedPort:servicePort
这样的操作,其中服务没有选择器,将由于此约束而失败。这防止了 Kubernetes API 服务器被用作代理到调用者可能没有授权访问的端点。ExternalName
服务是服务的一种特殊情况,它没有选择器,而是使用 DNS 名称。有关更多信息,请参阅 ExternalName 部分。
EndpointSlices
Kubernetes v1.21 [稳定]
EndpointSlices 是表示服务的支持网络端点子集(一个切片)的对象。
您的 Kubernetes 集群跟踪每个 EndpointSlice 代表的端点数量。如果服务的端点过多,达到某个阈值,Kubernetes 将添加另一个空的 EndpointSlice 并将新的端点信息存储在那里。默认情况下,Kubernetes 一旦现有的 EndpointSlices 都包含至少 100 个端点,就会创建一个新的 EndpointSlice。在需要添加额外的端点之前,Kubernetes 不会创建新的 EndpointSlice。
有关此 API 的更多信息,请参阅 EndpointSlices。
Endpoints
在 Kubernetes API 中,Endpoints(资源种类为复数)定义了一个网络端点列表,通常由服务引用以定义可以将流量发送到哪些 Pod。
EndpointSlice API 是 Endpoints 的推荐替代方案。
容量过高的端点
Kubernetes 限制了单个 Endpoints 对象中可以容纳的端点数。当服务的支持端点超过 1000 个时,Kubernetes 会截断 Endpoints 对象中的数据。由于服务可以与多个 EndpointSlice 链接,因此 1000 个支持端点的限制仅影响旧的 Endpoints API。
在这种情况下,Kubernetes 选择最多 1000 个可能的后台端点存储到 Endpoints 对象中,并在 Endpoints 上设置一个 注释:endpoints.kubernetes.io/over-capacity: truncated
。如果后台 Pod 数量下降到 1000 个以下,控制平面也会删除该注释。
流量仍然会发送到后端,但任何依赖于旧 Endpoints API 的负载均衡机制只会将流量发送到最多 1000 个可用的支持端点。
相同的 API 限制意味着您无法手动更新 Endpoints 以使其包含超过 1000 个端点。
应用程序协议
Kubernetes v1.20 [稳定]
appProtocol
字段提供了一种为每个服务端口指定应用程序协议的方法。这用作实施的提示,以便为它们理解的协议提供更丰富的行为。此字段的值由相应的 Endpoints 和 EndpointSlice 对象镜像。
此字段遵循标准的 Kubernetes 标签语法。有效值之一为
实施定义的前缀名称,例如
mycompany.com/my-custom-protocol
。Kubernetes 定义的前缀名称
协议 | 描述 |
---|---|
kubernetes.io/h2c | HTTP/2 over cleartext,如 RFC 7540 中所述 |
kubernetes.io/ws | WebSocket over cleartext,如 RFC 6455 中所述 |
kubernetes.io/wss | WebSocket over TLS,如 RFC 6455 中所述 |
多端口服务
对于某些服务,您需要公开多个端口。Kubernetes 允许您在服务对象上配置多个端口定义。当为服务使用多个端口时,您必须为所有端口命名,以确保它们不会出现歧义。例如
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app.kubernetes.io/name: MyApp
ports:
- name: http
protocol: TCP
port: 80
targetPort: 9376
- name: https
protocol: TCP
port: 443
targetPort: 9377
注意
与 Kubernetes 名称 一样,端口名称只能包含小写字母数字字符和 -
。端口名称也必须以字母数字字符开头和结尾。
例如,名称 123-abc
和 web
是有效的,但 123_abc
和 -web
无效。
服务类型
对于应用程序的某些部分(例如,前端),您可能希望将服务暴露到外部 IP 地址上,该地址可以从集群外部访问。
Kubernetes 服务类型允许您指定所需的服务类型。
可用的 type
值及其行为如下
ClusterIP
- 在集群内部 IP 上公开服务。选择此值将使服务只能从集群内部访问。如果未显式指定服务的
type
,则这是默认使用的值。您可以使用 Ingress 或 Gateway 将服务公开到公用互联网。 NodePort
- 在每个节点的 IP 上的静态端口(
NodePort
)上公开服务。要使节点端口可用,Kubernetes 会设置一个集群 IP 地址,与您请求type: ClusterIP
的服务相同。 LoadBalancer
- 使用外部负载均衡器在外部公开服务。Kubernetes 不直接提供负载均衡组件;您必须提供一个,或者您可以将您的 Kubernetes 集群与云提供商集成。
ExternalName
- 将服务映射到
externalName
字段的内容(例如,映射到主机名api.foo.bar.example
)。映射配置您的集群的 DNS 服务器以返回具有该外部主机名值的CNAME
记录。不会设置任何类型的代理。
服务 API 中的 type
字段被设计为嵌套功能 - 每个级别都添加到前一个级别。但是,这种嵌套设计有一个例外。您可以通过 禁用负载均衡器 NodePort
分配 来定义一个 LoadBalancer
服务。
type: ClusterIP
这种默认服务类型从您的集群为该目的保留的 IP 地址池中分配一个 IP 地址。
其他几种服务类型以 ClusterIP
类型为基础。
如果定义的服务的 .spec.clusterIP
设置为 "None"
,则 Kubernetes 不会分配 IP 地址。有关更多信息,请参阅 无头服务。
选择您自己的 IP 地址
您可以在 Service
创建请求中指定您自己的集群 IP 地址。为此,请设置 .spec.clusterIP
字段。例如,如果您已经有希望重复使用的现有 DNS 条目,或者您需要配置的遗留系统使用特定的 IP 地址,并且很难重新配置。
您选择的 IP 地址必须是为 API 服务器配置的 service-cluster-ip-range
CIDR 范围内的有效 IPv4 或 IPv6 地址。如果您尝试使用无效的 clusterIP
地址值创建服务,API 服务器将返回 422 HTTP 状态代码,以表明存在问题。
阅读 避免冲突,了解 Kubernetes 如何帮助降低两种不同的服务尝试使用同一个 IP 地址的风险和影响。
type: NodePort
如果将 type
字段设置为 NodePort
,Kubernetes 控制平面将从 --service-node-port-range
标志(默认:30000-32767)指定的范围中分配一个端口。每个节点将该端口(每个节点上的相同端口号)代理到您的服务。您的服务会在其 .spec.ports[*].nodePort
字段中报告分配的端口。
使用 NodePort 使您可以自由地设置自己的负载均衡解决方案,配置 Kubernetes 不完全支持的环境,甚至直接公开一个或多个节点的 IP 地址。
对于节点端口服务,Kubernetes 还会分配一个端口(TCP、UDP 或 SCTP,以匹配服务的协议)。集群中的每个节点都配置自己以侦听该分配的端口,并将流量转发到与该服务关联的某个已准备就绪的端点。您可以从集群外部联系 type: NodePort
服务,方法是使用适当的协议(例如:TCP)和适当的端口(如分配给该服务的端口)连接到任何节点。
选择您自己的端口
如果您希望使用特定端口号,您可以在 nodePort
字段中指定一个值。控制平面将为您分配该端口,或者报告 API 事务失败。这意味着您需要自己注意可能的端口冲突。您还必须使用有效的端口号,即在为 NodePort 使用配置的范围内的一个端口号。
以下是一个 type: NodePort
服务的清单示例,它指定了一个 NodePort 值(本例中为 30007)
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
type: NodePort
selector:
app.kubernetes.io/name: MyApp
ports:
- port: 80
# By default and for convenience, the `targetPort` is set to
# the same value as the `port` field.
targetPort: 80
# Optional field
# By default and for convenience, the Kubernetes control plane
# will allocate a port from a range (default: 30000-32767)
nodePort: 30007
保留 Nodeport 范围以避免冲突
为 NodePort 服务分配端口的策略适用于自动分配和手动分配场景。当用户希望创建使用特定端口的 NodePort 服务时,目标端口可能会与已分配的另一个端口冲突。
为了避免此问题,NodePort 服务的端口范围被划分为两个频段。动态端口分配默认使用上频段,一旦上频段用尽,它可能会使用下频段。然后,用户可以从下频段分配,从而降低端口冲突的风险。
type: NodePort
服务的自定义 IP 地址配置
您可以将集群中的节点设置为使用特定 IP 地址来提供节点端口服务。如果您希望这样做,可能是因为每个节点都连接到多个网络(例如:一个用于应用程序流量的网络,另一个用于节点和控制平面之间的流量)。
如果您希望指定特定 IP 地址来代理端口,您可以为 kube-proxy 设置 --nodeport-addresses
标志或 kube-proxy 配置文件 中的等效 nodePortAddresses
字段设置为特定 IP 块。
此标志接受 IP 块的逗号分隔列表(例如:10.0.0.0/8
、192.0.2.0/25
),以指定 kube-proxy 应视为本地于该节点的 IP 地址范围。
例如,如果您使用 --nodeport-addresses=127.0.0.0/8
标志启动 kube-proxy,kube-proxy 仅为 NodePort 服务选择回环接口。--nodeport-addresses
的默认值为一个空列表。这意味着 kube-proxy 应考虑所有可用的网络接口以供 NodePort 使用。(这也与早期的 Kubernetes 版本兼容。)
注意
此服务可见为<NodeIP>:spec.ports[*].nodePort
和 .spec.clusterIP:spec.ports[*].port
。如果为 kube-proxy 设置了 --nodeport-addresses
标志或 kube-proxy 配置文件中的等效字段,<NodeIP>
将是一个过滤后的节点 IP 地址(或可能是 IP 地址)。type: LoadBalancer
在支持外部负载均衡器的云提供商上,将 type
字段设置为 LoadBalancer
会为您的服务配置一个负载均衡器。负载均衡器的实际创建是异步进行的,有关配置的负载均衡器的信息将在服务的 .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
来自外部负载均衡器的流量将被定向到后台 Pod。云提供商会决定如何负载均衡。
为了实现一个type: LoadBalancer
类型的服务,Kubernetes 通常会先进行与你请求一个type: NodePort
类型的服务等效的更改。然后,cloud-controller-manager 组件会配置外部负载均衡器,将流量转发到该分配的节点端口。
你可以配置负载均衡的服务,不分配节点端口,前提是云提供商实现支持这一点。
一些云提供商允许你指定loadBalancerIP
。在这些情况下,负载均衡器将使用用户指定的loadBalancerIP
创建。如果没有指定loadBalancerIP
字段,则负载均衡器将使用一个临时的 IP 地址进行设置。如果你指定了loadBalancerIP
,但你的云提供商不支持此功能,你设置的loadbalancerIP
字段将被忽略。
注意
服务的.spec.loadBalancerIP
字段在 Kubernetes v1.24 中已弃用。
此字段定义不明确,其含义在不同的实现中有所不同。它也不能支持双栈网络。此字段可能会在将来的 API 版本中被删除。
如果你要与支持通过(提供商特定)注释为服务指定负载均衡器 IP 地址的提供商集成,你应该切换到这样做。
如果你正在编写与 Kubernetes 集成的负载均衡器代码,请避免使用此字段。你可以与 Gateway 集成,而不是与服务集成,或者你可以在服务上定义自己的(提供商特定)注释,以指定等效的详细信息。
节点存活性对负载均衡器流量的影响
负载均衡器健康检查对于现代应用程序至关重要。它们用于确定负载均衡器应该将流量调度到哪个服务器(虚拟机或 IP 地址)。Kubernetes API 没有定义如何为 Kubernetes 管理的负载均衡器实现健康检查,而是由云提供商(以及实现集成代码的人员)来决定行为。负载均衡器健康检查在支持服务的externalTrafficPolicy
字段的上下文中得到了广泛的应用。
具有混合协议类型的负载均衡器
Kubernetes v1.26 [稳定]
默认情况下,对于 LoadBalancer 类型的服务,当定义了多个端口时,所有端口必须具有相同的协议,并且协议必须是云提供商支持的协议。
功能网关MixedProtocolLBService
(从 v1.24 开始,kube-apiserver 默认启用)允许在定义了多个端口时,为 LoadBalancer 类型的服务使用不同的协议。
注意
可以用于负载均衡服务的协议集由你的云提供商定义;它们可能会施加超出 Kubernetes API 强制的限制。禁用负载均衡器节点端口分配
Kubernetes v1.24 [稳定]
你可以选择禁用type: LoadBalancer
的服务的节点端口分配,方法是将spec.allocateLoadBalancerNodePorts
字段设置为false
。这应该只用于将流量直接路由到 Pod 而不是使用节点端口的负载均衡器实现。默认情况下,spec.allocateLoadBalancerNodePorts
为true
,并且 LoadBalancer 类型服务将继续分配节点端口。如果在具有分配的节点端口的现有服务上将spec.allocateLoadBalancerNodePorts
设置为false
,则这些节点端口不会自动取消分配。你必须显式删除每个服务端口中的nodePorts
条目,才能取消分配这些节点端口。
指定负载均衡器实现的类别
Kubernetes v1.24 [稳定]
对于type
设置为LoadBalancer
的服务,.spec.loadBalancerClass
字段使你能够使用除云提供商默认值之外的负载均衡器实现。
默认情况下,.spec.loadBalancerClass
未设置,并且LoadBalancer
类型的服务使用云提供商的默认负载均衡器实现(如果集群使用--cloud-provider
组件标志与云提供商配置)。
如果你指定.spec.loadBalancerClass
,则假定与指定类别匹配的负载均衡器实现正在监视服务。任何默认的负载均衡器实现(例如,由云提供商提供的实现)都将忽略设置了此字段的服务。spec.loadBalancerClass
只能在LoadBalancer
类型的服务上设置。一旦设置,就不能更改。spec.loadBalancerClass
的值必须是标签风格的标识符,可以选择带前缀,例如“internal-vip
”或“example.com/internal-vip
”。无前缀的名称保留给最终用户。
指定负载均衡器状态的 IP 模式
Kubernetes v1.30 [beta]
作为 Kubernetes 1.30 中的一个 Beta 功能,一个名为LoadBalancerIPMode
的功能网关允许你为type
设置为LoadBalancer
的服务设置.status.loadBalancer.ingress.ipMode
。.status.loadBalancer.ingress.ipMode
指定了负载均衡器 IP 的行为方式。只有在也指定了.status.loadBalancer.ingress.ip
字段时才能指定它。
.status.loadBalancer.ingress.ipMode
有两个可能的值:“VIP”和“Proxy”。默认值为“VIP”,这意味着流量将被传递到具有设置为负载均衡器 IP 和端口的目的地节点。在将此设置为“Proxy”时,有两种情况,具体取决于云提供商的负载均衡器如何传递流量
- 如果流量被传递到节点,然后被 DNAT 到 Pod,则目的地将设置为节点的 IP 和节点端口;
- 如果流量被直接传递到 Pod,则目的地将设置为 Pod 的 IP 和端口。
服务实现可以使用此信息来调整流量路由。
内部负载均衡器
在混合环境中,有时需要将来自同一(虚拟)网络地址块内服务的流量路由。
在拆分视野 DNS 环境中,你需要两个服务才能将外部和内部流量都路由到你的端点。
要设置内部负载均衡器,请根据你使用的云服务提供商,向你的服务添加以下注释之一
选择其中一个选项卡。
metadata:
name: my-service
annotations:
networking.gke.io/load-balancer-type: "Internal"
metadata:
name: my-service
annotations:
service.beta.kubernetes.io/aws-load-balancer-internal: "true"
metadata:
name: my-service
annotations:
service.beta.kubernetes.io/azure-load-balancer-internal: "true"
metadata:
name: my-service
annotations:
service.kubernetes.io/ibm-load-balancer-cloud-provider-ip-type: "private"
metadata:
name: my-service
annotations:
service.beta.kubernetes.io/openstack-internal-load-balancer: "true"
metadata:
name: my-service
annotations:
service.beta.kubernetes.io/cce-load-balancer-internal-vpc: "true"
metadata:
annotations:
service.kubernetes.io/qcloud-loadbalancer-internal-subnetid: subnet-xxxxx
metadata:
annotations:
service.beta.kubernetes.io/alibaba-cloud-loadbalancer-address-type: "intranet"
metadata:
name: my-service
annotations:
service.beta.kubernetes.io/oci-load-balancer-internal: true
type: ExternalName
type 为 ExternalName 的服务将服务映射到 DNS 名称,而不是映射到典型的选择器,例如my-service
或cassandra
。你使用spec.externalName
参数指定这些服务。
例如,此服务定义将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
注意
type: ExternalName
类型的服务接受 IPv4 地址字符串,但将其视为由数字组成的 DNS 名称,而不是 IP 地址(然而,互联网不允许 DNS 中存在此类名称)。具有类似于 IPv4 地址的外部名称的服务不会被 DNS 服务器解析。
如果你想将服务直接映射到特定 IP 地址,请考虑使用无头服务.
在查找主机my-service.prod.svc.cluster.local
时,集群 DNS 服务返回一个值为my.database.example.com
的CNAME
记录。访问my-service
的工作方式与其他服务相同,但关键的区别在于重定向发生在 DNS 级别,而不是通过代理或转发。如果你以后决定将数据库移入你的集群,你可以启动它的 Pod,添加适当的选择器或端点,并更改服务的type
。
注意
你可能难以将 ExternalName 用于一些常见的协议,包括 HTTP 和 HTTPS。如果你使用 ExternalName,那么你的集群内部的客户端使用的主机名与 ExternalName 引用的名称不同。
对于使用主机名的协议,这种差异可能会导致错误或意外响应。HTTP 请求将具有源服务器无法识别的Host:
头;TLS 服务器将无法提供与客户端连接到的主机名匹配的证书。
无头服务
有时你不需要负载均衡和单个服务 IP。在这种情况下,你可以创建所谓的无头服务,方法是为集群 IP 地址(.spec.clusterIP
)显式指定"None"
。
你可以使用无头服务与其他服务发现机制交互,而不必绑定到 Kubernetes 的实现。
对于无头服务,不会分配集群 IP,kube-proxy 不会处理这些服务,并且平台不会为它们进行负载均衡或代理。
无头服务允许客户端直接连接到它喜欢的任何 Pod。无头服务不会使用虚拟 IP 地址和代理配置路由和数据包转发;相反,无头服务通过集群的DNS 服务,通过内部 DNS 记录报告各个 Pod 的端点 IP 地址。要定义无头服务,你需要创建一个.spec.type
设置为 ClusterIP(这也是type
的默认值)的服务,并且你需要另外将.spec.clusterIP
设置为 None。
字符串值 None 是一种特殊情况,与不设置.spec.clusterIP
字段不同。
DNS 如何自动配置取决于服务是否定义了选择器
使用选择器
对于定义了选择器的无头服务,端点控制器会在 Kubernetes API 中创建 EndpointSlices,并修改 DNS 配置以返回指向支持服务的 Pod 的 A 或 AAAA 记录(IPv4 或 IPv6 地址)。
没有选择器
对于没有定义选择器的无头服务,控制平面不会创建 EndpointSlice 对象。但是,DNS 系统会查找并配置以下任一项
- 用于
type: ExternalName
服务的 DNS CNAME 记录。 - 用于除
ExternalName
之外的所有服务类型的所有服务就绪端点的所有 IP 地址的 DNS A / AAAA 记录。- 对于 IPv4 端点,DNS 系统会创建 A 记录。
- 对于 IPv6 端点,DNS 系统会创建 AAAA 记录。
当你定义一个没有选择器的无头服务时,port
必须与targetPort
匹配。
发现服务
对于在你的集群中运行的客户端,Kubernetes 支持两种主要的服务查找模式:环境变量和 DNS。
环境变量
当 Pod 在节点上运行时,kubelet 会为每个活动服务添加一组环境变量。它添加了{SVCNAME}_SERVICE_HOST
和{SVCNAME}_SERVICE_PORT
变量,其中服务名称使用大写字母,并且连字符被转换为下划线。
例如,服务redis-primary
,它公开 TCP 端口 6379,并且已分配集群 IP 地址 10.0.0.11,将生成以下环境变量
REDIS_PRIMARY_SERVICE_HOST=10.0.0.11
REDIS_PRIMARY_SERVICE_PORT=6379
REDIS_PRIMARY_PORT=tcp://10.0.0.11:6379
REDIS_PRIMARY_PORT_6379_TCP=tcp://10.0.0.11:6379
REDIS_PRIMARY_PORT_6379_TCP_PROTO=tcp
REDIS_PRIMARY_PORT_6379_TCP_PORT=6379
REDIS_PRIMARY_PORT_6379_TCP_ADDR=10.0.0.11
注意
当您有一个 Pod 需要访问服务,并且您正在使用环境变量方法将端口和集群 IP 发布到客户端 Pod 时,您必须在客户端 Pod 存在之前创建服务。否则,这些客户端 Pod 的环境变量将不会被填充。
如果您只使用 DNS 来发现服务的集群 IP,则无需担心此顺序问题。
Kubernetes 还支持并提供与 Docker Engine 的“传统容器链接”功能兼容的变量。您可以阅读 makeLinkVariables
以了解 Kubernetes 中如何实现此功能。
DNS
您可以(并且几乎总是应该)使用 插件 为您的 Kubernetes 集群设置 DNS 服务。
一个集群感知的 DNS 服务器,例如 CoreDNS,会监控 Kubernetes API 中的新服务并为每个服务创建一个 DNS 记录集。如果 DNS 已在整个集群中启用,那么所有 Pod 应该能够自动通过其 DNS 名称解析服务。
例如,如果您在 Kubernetes 命名空间 my-ns
中有一个名为 my-service
的服务,则控制平面和 DNS 服务协同工作为 my-service.my-ns
创建一个 DNS 记录。my-ns
命名空间中的 Pod 应该能够通过对 my-service
进行名称查找来找到该服务(my-service.my-ns
也将起作用)。
其他命名空间中的 Pod 必须将名称限定为 my-service.my-ns
。这些名称将解析为分配给服务的集群 IP。
Kubernetes 还支持命名端口的 DNS SRV(服务)记录。如果 my-service.my-ns
服务有一个名为 http
且协议设置为 TCP
的端口,则您可以对 _http._tcp.my-service.my-ns
执行 DNS SRV 查询以发现 http
的端口号以及 IP 地址。
Kubernetes DNS 服务器是访问 ExternalName
服务的唯一方法。您可以在 服务和 Pod 的 DNS 中找到有关 ExternalName
解析的更多信息。
虚拟 IP 地址机制
阅读 虚拟 IP 和服务代理 解释了 Kubernetes 提供的用于使用虚拟 IP 地址公开服务的机制。
流量策略
您可以设置 .spec.internalTrafficPolicy
和 .spec.externalTrafficPolicy
字段来控制 Kubernetes 如何将流量路由到健康的(“就绪”)后端。
有关更多详细信息,请参阅 流量策略。
流量分配
Kubernetes v1.31 [beta]
.spec.trafficDistribution
字段提供了一种影响 Kubernetes 服务内流量路由的另一种方法。虽然流量策略侧重于严格的语义保证,但流量分配允许您表达偏好(例如,路由到拓扑上更接近的端点)。这可以帮助优化性能、成本或可靠性。如果您为集群及其所有节点启用了 ServiceTrafficDistribution
功能网关,则可以使用此可选字段。在 Kubernetes 1.31 中,支持以下字段值
PreferClose
- 表示优先将流量路由到拓扑上靠近客户端的端点。对“拓扑上靠近”的解释可能会因实现而异,并且可能包括同一节点、机架、区域甚至区域内的端点。设置此值将允许实现进行不同的权衡,例如优化接近度而不是负载的均衡分配。如果此类权衡不可接受,用户不应设置此值。
如果没有设置该字段,实现将应用其默认路由策略。
有关更多详细信息,请参阅 流量分配
会话粘性
如果您想确保每次来自特定客户端的连接都传递到同一个 Pod,则可以根据客户端的 IP 地址配置会话亲和性。阅读 会话亲和性 以了解更多信息。
外部 IP
如果存在将流量路由到一个或多个集群节点的外部 IP,则 Kubernetes 服务可以在这些 externalIPs
上公开。当网络流量到达集群时,如果外部 IP(作为目标 IP)和端口匹配该服务,则 Kubernetes 配置的规则和路由将确保将流量路由到该服务的端点之一。
定义服务时,您可以为任何 服务类型 指定 externalIPs
。在下面的示例中,名为 "my-service"
的服务可以通过客户端使用 TCP 在 "198.51.100.32:80"
上访问(从 .spec.externalIPs[]
和 .spec.ports[].port
计算得出)。
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app.kubernetes.io/name: MyApp
ports:
- name: http
protocol: TCP
port: 80
targetPort: 49152
externalIPs:
- 198.51.100.32
注意
Kubernetes 不管理externalIPs
的分配;这些由集群管理员负责。API 对象
服务是 Kubernetes REST API 中的顶级资源。您可以在 服务 API 对象 中找到有关该对象的更多详细信息。
下一步
了解有关服务的更多信息以及它们如何在 Kubernetes 中发挥作用
- 按照 使用服务连接应用程序 教程进行操作。
- 阅读有关 入口 的内容,它从集群外部公开到集群内服务的 HTTP 和 HTTPS 路由。
- 阅读有关 网关 的内容,它是 Kubernetes 的扩展,提供了比入口更大的灵活性。
有关更多背景信息,请阅读以下内容