深入理解 Kubernetes Service:从入门到实践
在容器化和微服务盛行的今天,Kubernetes 已经成为容器编排的事实标准。它极大地简化了应用的部署、扩展和管理。然而,Kubernetes 中的 Pod 是短暂且动态的,它们的 IP 地址会随着创建、销毁和重新调度而改变。试想一下,如果你的前端应用需要访问后端服务,而后端服务的 Pod IP 地址不断变化,这无疑会带来巨大的挑战。
这时,Kubernetes Service 应运而生。Service 是 Kubernetes 中一个核心概念,它为一组 Pod 提供了一个稳定、持久的网络端点(IP 地址和端口)。无论 Pod 如何变化(创建、销毁、扩缩容),Service 的 IP 地址和端口都不会改变,从而使得其他应用能够轻松地发现和访问这组 Pod。
本文将带你深入理解 Kubernetes Service 的概念、作用、工作原理以及不同类型,并通过实际的 YAML 示例,帮助你快速入门并掌握 Services 的使用。
为什么我们需要 Kubernetes Service?Pod 的挑战
在深入了解 Service 之前,让我们先看看直接访问 Pod 会面临哪些问题:
- Pod IP 的不稳定性: Pod 是 Kubernetes 调度的基本单位,但它们是易失性的。当 Pod 发生故障、被重新调度、或应用进行滚动更新时,旧的 Pod 会被终止,新的 Pod 会被创建,随之而来的是新的 IP 地址。如果客户端直接依赖 Pod 的 IP 地址,那么一旦 Pod 变化,客户端的连接就会中断,需要重新发现新的 IP。
- 多副本的管理: 在生产环境中,为了高可用性和可伸缩性,我们通常会运行同一应用的多个 Pod 副本。这些副本拥有不同的 IP 地址。客户端如何在这多个副本之间进行负载均衡?Service 天然提供了这一能力。
- 服务发现: 应用程序通常由多个微服务组成,一个服务需要调用另一个服务。直接管理和跟踪每个服务的 Pod IP 地址列表是极其困难的。Service 提供了一个抽象层,客户端只需要知道 Service 的名字和端口,Kubernetes 会负责将请求路由到健康的后端 Pod。
正是为了解决这些问题,Kubernetes 引入了 Service 这一概念,它扮演了“服务发现”和“负载均衡”的角色,为动态的 Pod 集合提供了一个静态的访问入口。
什么是 Kubernetes Service?
Kubernetes Service 是一个抽象层,它定义了一组 Pod 的逻辑集合以及访问它们的策略。这个策略通常包括一个稳定的 IP 地址和端口。当 Service 被创建后,Kubernetes 会为它分配一个唯一的 Cluster IP(集群内部 IP 地址),并且这个 IP 地址在 Service 的整个生命周期内都不会改变。
Service 通过 标签选择器 (Label Selector) 来识别它所代理的 Pod 集合。这意味着,只要 Pod 拥有与 Service 定义的标签选择器匹配的标签,它就会被该 Service 纳管,成为 Service 的后端。
例如,你有一个 Deployment 部署了 3 个带有 app: my-backend
标签的 Pod。你可以创建一个 Service,使用 selector: {app: my-backend}
来选择这些 Pod。所有发送到这个 Service 的请求都将被转发到这 3 个 Pod 中的一个。
Service 实际上并不运行任何进程,它只是 Kubernetes API Server 中的一个资源对象。它依赖于 kube-proxy
组件在集群中的每个节点上维护网络规则(如 iptables 或 IPVS),以便将 Service 的请求转发到正确的后端 Pod。
Service 的核心组成部分
一个 Service 定义通常包含以下关键信息:
apiVersion
和kind
: 定义资源类型,对于 Service 来说是apiVersion: v1
和kind: Service
。metadata
: 包含 Service 的名称、命名空间、标签等元信息。spec
: Service 的核心配置部分。selector
: 一个标签选择器,用于匹配作为 Service 后端的 Pod。ports
: 定义 Service 监听的端口以及转发到后端 Pod 的端口映射。port
: Service 监听的端口。targetPort
: Pod 实际监听的端口(Pod 的容器端口)。如果省略,默认为与port
相同。protocol
: 端口使用的协议(TCP, UDP, SCTP),默认为 TCP。nodePort
(仅适用于NodePort
和LoadBalancer
类型): 暴露在节点上的端口。
type
: 定义 Service 的访问类型,这是 Service 最重要的配置之一,决定了如何从集群内部或外部访问 Service。
Service 的工作原理(简述)
- 创建 Service: 你通过 YAML 文件或
kubectl
命令创建一个 Service 对象。Service 定义了其标签选择器和端口映射。 - API Server 协调: Kubernetes API Server 接收 Service 创建请求,将其存储到 etcd 中。
- Endpoints Controller 监控: Endpoints Controller 监控 Service 和 Pod 的变化。它通过 Service 的标签选择器查找所有匹配的 Pod。
- 创建 Endpoints 对象: Endpoints Controller 根据匹配到的 Pod 及其 IP 地址、端口信息,自动创建一个与 Service 同名的
Endpoints
对象。例如,如果 Service 选择器匹配到 IP 为 10.1.1.1:80 和 10.1.1.2:80 的 Pod,Endpoints 对象就会包含这两个地址。注意: Endpoints 对象包含了 Pod 的实际网络地址,Service 本身不包含。 - Kube-proxy 更新规则: 集群中每个节点上的
kube-proxy
组件监控 Service 和 Endpoints 对象的变化。当它们变化时,kube-proxy
会根据 Service 的类型,在节点上配置相应的网络规则(如 iptables 或 IPVS)。这些规则将 Service 的 Cluster IP 和端口映射到 Endpoints 对象中记录的 Pod IP 和端口。 - 请求路由: 当有请求发送到 Service 的 Cluster IP 和端口时,节点上的网络规则会截获这些请求,并根据 Endpoints 对象中的 Pod 地址,通过负载均衡算法(如轮询)将其转发到其中一个健康的后端 Pod。
整个过程对于客户端是透明的,客户端只需要知道 Service 的 Cluster IP 和端口即可。
Service 的不同类型
Service 的 type
字段决定了 Service 如何暴露给外部或集群内部使用。Kubernetes 支持以下几种 Service 类型:
-
ClusterIP (默认类型)
- 描述: 这是默认的 Service 类型。Service 会在集群内部获得一个稳定的 Cluster IP 地址。这个 IP 地址只能在 Kubernetes 集群内部访问。
- 用途: 主要用于集群内部服务之间的通信。例如,前端服务调用后端服务,或者一个微服务调用另一个微服务。
- 访问方式: 通过 Service 的 Cluster IP 和端口在集群内部访问。Kubernetes DNS 会为 Service 创建一个 DNS 记录(
service-name.namespace.svc.cluster.local
),因此通常可以通过服务名称来访问(例如,直接使用service-name
如果在同一个命名空间下,或者使用service-name.namespace
跨命名空间)。 - 外部访问: 不能 直接从集群外部访问 ClusterIP Service。如果需要外部访问,通常需要结合 Ingress 或其他 Service 类型。
YAML 示例 (ClusterIP):
yaml
apiVersion: v1
kind: Service
metadata:
name: my-backend-service
namespace: default # Service 所在的命名空间
spec:
selector:
app: my-backend # 选择带有 app: my-backend 标签的 Pod 作为后端
ports:
- protocol: TCP
port: 80 # Service 监听的端口 (Cluster IP:80)
targetPort: 8080 # 转发到 Pod 的端口 (Pod IP:8080)
type: ClusterIP # 指定服务类型为 ClusterIP (可以省略,因为是默认值) -
NodePort
- 描述: NodePort 类型 Service 会在集群的每个 Node 上都开放一个静态端口(
nodePort
),范围通常是 30000-32767。发送到任意节点的nodePort
端口的请求,都会被路由到该 Service 的 Cluster IP,进而转发到后端 Pod。 - 用途: 将 Service 暴露到集群外部的最简单方式。可以通过
AnyNodeIP:NodePort
的方式从外部访问。 - 访问方式:
http://<任何一个节点的外部IP>:<NodePort>
。 - 优点: 简单易用,不需要额外的云基础设施。
- 缺点:
- 端口范围有限 (30000-32767)。
- 每个节点都会开放该端口,需要知道至少一个节点的 IP 地址。
- 如果节点 IP 发生变化,外部访问地址也会变化。
- 不适合生产环境的大规模流量,缺乏负载均衡能力(除非在 NodePort 前面再加一个外部负载均衡器)。
YAML 示例 (NodePort):
yaml
apiVersion: v1
kind: Service
metadata:
name: my-frontend-nodeport
namespace: default
spec:
selector:
app: my-frontend # 选择带有 app: my-frontend 标签的 Pod 作为后端
ports:
- protocol: TCP
port: 80 # Service 监听的端口 (Cluster IP:80)
targetPort: 80 # 转发到 Pod 的端口 (Pod IP:80)
nodePort: 30080 # 在每个节点的 30080 端口上暴露服务 (可省略,Kubernetes 会自动分配一个端口)
type: NodePort # 指定服务类型为 NodePort
注意: 如果nodePort
字段省略,Kubernetes 会自动从配置的 NodePort 范围内分配一个端口。 - 描述: NodePort 类型 Service 会在集群的每个 Node 上都开放一个静态端口(
-
LoadBalancer
- 描述: 这是在云提供商(如 AWS, GCP, Azure, 阿里云等)环境中使用的 Service 类型。创建 LoadBalancer 类型 Service 会向云提供商请求创建一个外部负载均衡器。这个负载均衡器会获得一个外部 IP 地址,并将流量转发到 Service 的 NodePort(如果指定了
nodePort
)或直接转发到后端 Pod。 - 用途: 将 Service 完全暴露到集群外部,提供高性能、可伸缩的外部访问。
- 访问方式: 通过云提供商分配的外部负载均衡器 IP 地址和 Service 的端口访问。
- 优点: 提供了生产级别的外部访问,自带负载均衡能力,外部 IP 地址稳定。
- 缺点: 需要依赖特定的云提供商环境,可能会产生额外的云服务费用。不是所有 Kubernetes 环境都支持(例如,本地 minikube 可能需要额外配置)。
YAML 示例 (LoadBalancer):
yaml
apiVersion: v1
kind: Service
metadata:
name: my-web-loadbalancer
namespace: default
spec:
selector:
app: my-web # 选择带有 app: my-web 标签的 Pod 作为后端
ports:
- protocol: TCP
port: 80 # Service 监听的端口 (Cluster IP:80)
targetPort: 80 # 转发到 Pod 的端口 (Pod IP:80)
type: LoadBalancer # 指定服务类型为 LoadBalancer
创建此 Service 后,Kubernetes 会与云提供商集成,provision 一个外部负载均衡器,并将其外部 IP 地址更新到 Service 的status.loadBalancer.ingress
字段中。 - 描述: 这是在云提供商(如 AWS, GCP, Azure, 阿里云等)环境中使用的 Service 类型。创建 LoadBalancer 类型 Service 会向云提供商请求创建一个外部负载均衡器。这个负载均衡器会获得一个外部 IP 地址,并将流量转发到 Service 的 NodePort(如果指定了
-
ExternalName
- 描述: ExternalName Service 不会映射到集群内的 Pod 或 IP 地址,而是通过返回一个 CNAME 记录的方式,将 Service 映射到指定的外部 DNS 名称。
- 用途: 用于为外部服务提供一个集群内部的、基于 DNS 的别名。例如,你的应用需要访问一个外部数据库(
my.external.database.com
),你可以创建一个 ExternalName Service,让应用可以通过my-db-service
这样的内部名称来访问它。 - 工作原理: 当访问 ExternalName Service 时,Kubernetes DNS 服务会返回一个 CNAME 记录,指向 Service 定义中指定的外部 DNS 名称。请求实际上是直接发送到那个外部地址,而不是经过
kube-proxy
或集群内部的网络。 - 特点: 没有
selector
、ClusterIP
、port
、targetPort
,因为它不代理集群内部的 Pod。
YAML 示例 (ExternalName):
yaml
apiVersion: v1
kind: Service
metadata:
name: my-external-database
namespace: default
spec:
type: ExternalName # 指定服务类型为 ExternalName
externalName: my.external.database.com # 指定外部 DNS 名称
如何创建和访问 Service
创建 Service
你可以将 Service 定义写入一个 YAML 文件(例如 my-service.yaml
),然后使用 kubectl apply -f my-service.yaml
命令来创建。
一个更完整的示例 (ClusterIP Service for a simple Nginx Deployment):
首先,创建一个 Deployment YAML (nginx-deployment.yaml
):
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 3 # 运行 3 个 Nginx Pod 副本
selector:
matchLabels:
app: nginx # Deployment 选择带有 app: nginx 标签的 Pod
template:
metadata:
labels:
app: nginx # Pod 模板的标签
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80 # Nginx 容器监听 80 端口
应用 Deployment: kubectl apply -f nginx-deployment.yaml
然后,创建 Service YAML (nginx-service.yaml
):
yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
selector:
app: nginx # Service 选择带有 app: nginx 标签的 Pod
ports:
- protocol: TCP
port: 80 # Service 监听 80 端口 (Cluster IP:80)
targetPort: 80 # 转发到 Pod 的 80 端口 (Pod IP:80)
type: ClusterIP # 默认为 ClusterIP,也可不写
应用 Service: kubectl apply -f nginx-service.yaml
查看 Service
使用 kubectl get svc
命令查看集群中的 Service 列表:
bash
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 1d # Kubernetes 自己的 Service
nginx-service ClusterIP 10.100.xxx.yyy <none> 80/TCP 1m
你可以看到 nginx-service
的名称、类型、分配的 CLUSTER-IP
以及端口。
使用 kubectl describe svc <service-name>
查看 Service 的详细信息,包括其选择器、Endpoints(即匹配到的 Pod IP 列表)等:
bash
$ kubectl describe svc nginx-service
Name: nginx-service
Namespace: default
Labels: <none>
Annotations: <none>
Selector: app=nginx # Service 使用的标签选择器
Type: ClusterIP
IP: 10.100.xxx.yyy # Service 的 Cluster IP
Endpoints: 172.17.0.4:80,172.17.0.5:80,172.17.0.6:80 # 匹配到的 Pod IP 和 targetPort
Port: <unset> 80/TCP # Service 监听的端口
TargetPort: 80/TCP # 转发到 Pod 的端口
Session Affinity: None
Events: <none>
这里的 Endpoints
字段非常重要,它显示了 Service 当前代理的 Pod 列表及其 IP:Port。
访问 Service
-
ClusterIP: 只能在集群内部访问。例如,你可以进入一个集群内的 Pod,然后使用
curl
或其他工具访问 Service 的 Cluster IP 和端口,或者直接使用 Service 名称:
bash
# 在集群内任一 Pod 中执行
curl http://10.100.xxx.yyy # 使用 Cluster IP
curl http://nginx-service # 使用 Service 名称 (如果处于同一命名空间)
curl http://nginx-service.default.svc.cluster.local # 使用完整的 DNS 名称 -
NodePort: 可以从集群外部通过任意节点的 IP 地址和 Service 的 NodePort 访问。
bash
curl http://<any_node_ip>:30080 -
LoadBalancer: 从集群外部通过云提供商分配的外部 IP 地址和 Service 的端口访问。
首先,你需要查看 Service 的外部 IP:
bash
$ kubectl get svc my-web-loadbalancer
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-web-loadbalancer LoadBalancer 10.100.zzz.www <pending> 80:3xxxx/TCP 1m # 初始可能是 <pending>
# 等待一段时间,EXTERNAL-IP 会被分配
$ kubectl get svc my-web-loadbalancer
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-web-loadbalancer LoadBalancer 10.100.zzz.www AAA.BBB.CCC.DDD 80:3xxxx/TCP 5m
然后通过 EXTERNAL-IP 访问:
bash
curl http://AAA.BBB.CCC.DDD
Service Discovery (服务发现)
Service 不仅提供了一个稳定的访问入口,还提供了服务发现机制。集群内部的 Pod 可以通过多种方式发现 Service:
-
DNS: 这是最常用和推荐的方式。Kubernetes 集群通常运行着一个 DNS 服务(如 CoreDNS 或 kube-dns)。当你创建 Service 时,DNS 服务会自动为 Service 创建一个 DNS 记录。
- 在同一个命名空间内,可以直接使用 Service 名称:
<service-name>
- 在不同命名空间内,使用:
<service-name>.<namespace>
- 完整的集群内 DNS 名称:
<service-name>.<namespace>.svc.cluster.local
应用可以通过这些 DNS 名称来访问 Service。
- 在同一个命名空间内,可以直接使用 Service 名称:
-
环境变量: 当一个 Pod 在运行时,Kubernetes 会为该 Pod 创建当前命名空间中所有已存在 Service 的环境变量。这些变量包含 Service 的 Cluster IP 和端口。然而,这种方式不如 DNS 灵活(例如,Service 需要在 Pod 创建之前存在)且变量名可能比较复杂,因此更推荐使用 DNS。
Service 与 Ingress 的关系
Service 主要负责处理集群内部 Pod 的访问,以及基本的外部访问(NodePort, LoadBalancer)。它工作在 OSI 模型的第 4 层(传输层),但也部分涉及第 7 层(应用层)的端口映射和简单的请求转发。
然而,对于更复杂的外部访问场景,例如:
- 基于不同的域名或路径路由请求到不同的 Service。
- SSL/TLS 终端( termination)。
- 虚拟主机。
- 更高级的负载均衡策略。
Service 本身无法直接满足这些需求。这时就需要使用 Ingress。Ingress 是 Kubernetes API 对象,它管理集群外部访问集群内 Service 的规则集合。Ingress 依赖于一个 Ingress Controller(如 Nginx Ingress Controller, Traefik 等)来实际实现这些规则。
可以将 Service 理解为 Pod 的稳定入口,而 Ingress 则是集群外部流量进入集群的“网关”,负责根据规则将外部请求路由到相应的 Service。Ingress 更多地工作在 OSI 模型的第 7 层。Service 和 Ingress 通常是协同工作的关系。
总结
Kubernetes Service 是构建高可用、可伸缩的微服务应用的关键组件。它解决了 Pod IP 不稳定、多副本管理和内部服务发现的难题,为动态的 Pod 集合提供了稳定的网络端点和负载均衡能力。
我们了解了 Service 的核心概念、通过标签选择器与 Pod 关联的工作原理,以及四种主要的 Service 类型:
- ClusterIP: 默认类型,用于集群内部服务通信。
- NodePort: 通过节点 IP 和静态端口暴露服务到外部,简单但不适合生产大规模使用。
- LoadBalancer: 在云环境中创建外部负载均衡器暴露服务,提供生产级别的外部访问。
- ExternalName: 通过 DNS 别名映射到外部服务。
掌握 Service 是使用 Kubernetes 的基础,理解不同 Service 类型的用途及其限制,能够帮助你更好地设计和部署你的应用。结合 Service Discovery (DNS) 和 Ingress,你可以构建强大的、易于管理的云原生应用。
希望通过本文的详细介绍和示例,你已经对 Kubernetes Service 有了清晰的认识,并能顺利地在你的 Kubernetes 集群中应用 Service。动手实践是最好的学习方式,尝试创建不同类型的 Service,并从集群内部和外部访问它们,加深你的理解。