1. Service 定义

在 Kubernetes 中,Service 是一种抽象,用于定义一组 Pod 的访问方式和网络策略。Service 允许将一组具有相同标签的 Pod 组合在一起,并为它们提供一个统一的访问入口,从而实现服务发现和负载均衡。

Service 的 yaml 格式的定义文件的完整内容如下:

apiVersion: v1
kind: Service
metadata:
  name: string
  namespace: string
  labels:
    - name: string 
  annotations:
    - name: string
spec:
  selector: []
  type: string	
  clusterIP: string
  sessionAffinity: string 
  ports:
    - name: string
      protocol: string 
      port: int
      targetPort: int
  nodePort: int 
status:
  loadBalancer: 
    ingress:
      ip: string
      hostname: string
YAML

对 Service 的定义文件模板的各属性的说明如下表所示:

属性名称取值类型是否必选取值说明
apiVersionstring必选v1
kindstring必选Service
metadataobject必选元数据
metadata.name string必选Service 名称,须符合 RFC 1035 规范
metadata.namespacestring必选名称空间,不指定时系统将使用名为“default”的命名空间。
metadata. labels

2. Service 详解

2.1. service 使用示例

创建一个包含多个 nginx pod 副本的集合:

# webapp-deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: webapp
spec:
  replicas: 2
  selector:
    matchLabels:
      app: webapp
  template:
    metadata:
      labels:
        app: webapp
    spec:
      containers:
      - name: webapp
        image: nginx:1.26.2
        ports:
        - containerPort: 80
YAML

创建:

kubectl apply -f webapp-deployment.yaml
ShellScript

查看 pod ip 地址:

kubectl get pod -l app=webapp -o wide

NAME                     READY   STATUS    RESTARTS   AGE   IP            NODE                NOMINATED NODE   READINESS GATES
webapp-78c9968fc-4zhsk   1/1     Running   0          30s   10.244.3.87   k8s-node-04-r-214   <none>           <none>
webapp-78c9968fc-j5vc5   1/1     Running   0          30s   10.244.5.79   k8s-node-08-u-218   <none>           <none>
ShellScript

在集群内访问该 pod:

curl 10.244.3.87:80

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
ShellScript

此时我们通过 pod 的 ip+端口可以访问到 nginx 服务,但 pod 的 ip 是动态变化的(例如发生故障后控制器重启了 pod),同时在运行的过程中 pod 副本的数量是可能变化的,因此对于客户端应用来说,要实现动态感知服务后端实例的变化,以及实现将请求发送到多个后端实例上的负载均衡机制,都会大大增加客户端系统实现的复杂度。

所以,Kubernetes 的 Service 就是用于解决这些问题的核心组件。通过 Service 的定义,可以对客户端应用屏蔽后端实例数量及 PodIP 地址的变化,通过负载均衡策略实现将请求转发到后端实例上,为客户端应用提供一个稳定的服务访问入口地址。Service 实现的是微服务架构中的几个核心功能:全自动的服务注册、服务发现、服务负载均衡等。

为上面创建的 pod 创建 service:

# Kubernetes 提供了一种快速的方法,即通过 kubectl expose 令来创建 Service
kubectl expose deployment webapp service

# 为了更精细化管理,可以通过 yaml 文件来创建 service
# webapp-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: webapp
spec:
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
  selector:
    app: webapp
ShellScript

查看 service 的 ip 地址:

kubectl get svc

NAME         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE
webapp       ClusterIP   10.100.238.224   <none>        80/TCP    23s
ShellScript

访问 service:

curl 10.100.238.224:80

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
ShellScript

同样可以访问到 nginx 服务,请求会被负载均衡的分发给两个 nginx 服务。

一个 Service 对应的“后端”由 Pod 的 IP 地址和容器端口号组成,即一个完整的”IP:Port”
访问地址,它在 Kubernetes 系统中被称作 Endpoint (端点)。通过查看 Service 的详细信息,可以看到其后端 Endpoint 列表:

kubectl describe service webapp

Name:                     webapp
Namespace:                default
Labels:                   <none>
Annotations:              <none>
Selector:                 app=webapp
Type:                     ClusterIP
IP Family Policy:         SingleStack
IP Families:              IPv4
IP:                       10.100.238.224
IPs:                      10.100.238.224
Port:                     <unset>  80/TCP
TargetPort:               80/TCP
Endpoints:                10.244.5.79:80,10.244.3.87:80
Session Affinity:         None
Internal Traffic Policy:  Cluster
Events:                   <none>
ShellScript

2.2. service 负载均衡机制

2.1.1. kube-proxy 的代理模式

  • iptables 模式(仅适用于 Linux 系统)
  • ipvs 模式(仅适用于 Linux 系统)
  • kernelspace 模式(适用于 windows 系统)

2.1.2. 会话保持机制

Service 支持通过设置 sessionAffinity 来实现基于客户端 IP 地址的会话保持机制,即首次将某个客户端来源IP地址发起的请求转发到后端的某个 Pod 上,之后从相同的客户端 IP地址发起的请求都将被转发到相同的后端 Pod上,配置参数为 service.spec.sessionAffinity,示例:

apiVersion: v1
kind: Service
metadata:
  name: webapp
spec:
  sessionAffinity: ClientIP
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
  selector:
    app: webapp
YAML

同时,用户可以设置会话保持的最长时间,在此时间之后重置客户端来源 IP 地址的保持规则,配置参数为 service.spec.sessionAffinityConfig.clientIP.timeoutSeconds。例如下面的服务将会话保持时间设置为 10800s(3h):

apiVersion: v1
kind: Service
metadata:
  name: webapp
spec:
  sessionAffinity: ClientIP
  sessionAffinityConfig:
    clientIP:
      timeoutSeconds: 10800
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
  selector:
    app: webapp
YAML

2.1.3. 流量策略

  • spec.internalTrafficPolicy:内部流量策略,该策略是控制从内部源发起的流量的路由策略,可选配置项包括Cluster和 Local。
    • Cluster:将从内部源发起的流量路由到所有处于 Ready 状态的 Endpoint。
    • Local:将从内部源发起的流量仅路由到本地 Node 上处于 Ready 状态的Endpoint 如果本地 Node 上没有处于 Ready 状态的 Endpoint,kube-proxy 会丢弃该流量。
  • spec.externalTrafficPolicy:外部流量策略,该策略是控制从外部源发起的流量的路由策略,可选配置项包括 Cluster 和 Local。
    • Cluster:将从外部源发起的流量路由到所有处于 Ready 状态的 Endpoint。
    • Local:将从外部源发起的流量仅路由到本地 Node 上处于 Ready 状态的Endpoint,如果本地 Node 上没有处于 Ready 状态的 Endpoint,,kube-proxy会丢弃该流量。
  • ProxyTerminatingEndpoints:发往正在停止的 Endpoint 的流量策略

2.3. service 的多端口设置

service 可以设置多个端口来提供不同的服务,示例:

apiVersion: v1
kind: Service
metadata:
  name: webapp
spec:
  ports:
  - port: 80
    targetPort: 80
    name: web
  - port: 81
    targetPort: 81
    name: management
  selector:
    app: webapp
YAML

同一个端口使用不同的协议示例:

apiVersion: v1
kind: Service
metadata:
  name: kube-dns
  namespace: kube-system
  labels:
    k8s-app: kube-dns
    kubernetes.io/cluster-service: "true"
    kubernetes.io/name: "KubeDNS"
spec:
  selector:
    k8s-app: kube-dns
  clusterIP: 169.169.0.100
  ports:
  - name: dns
    port: 53
    protocol: UDP
  - name: dns-tcp
    port: 53
    protocol: TCP
YAML

2.4. 将外部服务定义为 service

普通的 Service 通过标签选择器对后端 Endpoints 列表进行了一层抽象,如果后端的 Endpoint 不是由 Pod 副本集提供的,则 Service 还可以抽象定义任意其他服务,将一个 Kubernetes 集群外的已知服务定义为 Kubernetes 内的一个 Service,供集群中的其他应用访问,常见的应用场景如下:

  • 已部署的一个集群外服务,例如数据库服务、缓存服务等。
  • 其他 Kubernetes 集群的某个服务。
  • 迁移过程中对某个服务进行 Kubernetes 内服务名访问机制的验证。

对于这些应用场景,用户在创建 Service 资源对象时不设置标签选择器(后端 Pod 也不存在),同时再定义一个与 Service 关联的 Endpoints 资源对象,在 Endpoints 对象中设置外部服务的 IP 地址和端口号,示例:

kind: Service
apiVersion: v1
metadata:
  name: my-service
spec:
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80

---
kind: Endpoints
apiVersion: v1
metadata:
  name: my-service
subsets:
  - addresses:
      - IP: 192.168.2.201
    ports:
      - port: 80
YAML

将 ClusterIP 设置为 None 表示该 Service 不会分配一个固定的 ClusterIP 地址。这意味着该 Service 不会有一个固定的虚拟 IP 地址,而是通过 Endpoints 对象中定义的具体 IP 地址和端口来实现服务发现和负载均衡。

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  type: ClusterIP
  clusterIP: None  # clusterIP 设置为 None
  ports:
    - name: port
      port: 80

---
apiVersion: v1
kind: Endpoints
metadata:
  name: my-service # 名称必须和 Service 一致
subsets:
  - addresses:
      - IP: 192.168.2.201 # Service 将连接重定向到 endpoint
    ports:
      - name: port
        port: 80
YAML

2.5. service 的类型

service 的类型如下:

  • ClusterIP:Kubernetes 默认会自动设置 Service 的虚拟 IP 地址,仅可被集群中的客户端应用访问。
  • NodePort:将 Service 的端口号映射到每个 Node 的一个端口号上,这样集群中的任意 Node 都可以作为 Service 的访问入口地址,即 NodeIP:NodePort。
  • LoadBalancer:将 Service 映射到一个已存在的负载均衡器的 IP 地址上,通常在公有云环境下使用。
  • ExternalName:将 Service 映射为一个外部域名地址,通过 externalName 字段进行设置。

2.5.1. ClusterIP 类型

这是 Kubernetes 为 Service 设置 IP 地址的默认类型,kube-apiserver 服务的启动参数 –service-cluster-ip-range 设置了 Service 的 IP 地址范围,系统将自动从这个IP地址池中为
Service 分配一个可用的 IP 地址。
用户也可以手动指定一个 ClusterIP 地址,可以通过 spec.clusterlP 字段进行设置,需要确保该 IP 地址在可用的 ClusterIP 地址池内,并且没有被其他 Service 使用。示例如下:

apiVersion: v1
kind: Service
metadata:
  name: webapp
spec:
  type: ClusterIP
  clusterIP: 10.100.238.224
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
  selector:
    app: webapp
YAML

2.5.2. NodePort 类型

NodePort 类型用于将 Service 暴露到 Node 上,这样集群外的客户端可以通过 Node 的
IP地址访问集群中的 Service。使用 NodePort 类型时,系统会在 Node 上开启一个端口号,考虑到可能会有其他应用程序已经占用了一些端口号(例如操作系统的系统服务占用的端口号),要求在部署 Kubernetes 集群时,通过 kube-apiserver 的启动参数 –service-node-port-range 指定可以分配的 NodePort 端口号范围,默认值为[30000~32767]。
在Service的定义中,可以在每个竭口的配置中,通过字段 nodePort 指定一个值,如果不指定,Kubernetes 会基于端口号范围给每个端口号自动分配一个端口号。示例如下:

apiVersion: v1
kind: Service
metadata:
  name: webapp
spec:
  type: NodePort
  ports:
  - port: 80
    targetPort: 80
    nodePort: 30000
  selector:
    app: webapp
YAML

2.5.3. LoadBalancer 类型

2.5.4. ExternalName 类型

ExternalName 类型的服务用于将集群外的服务定义为 Kubernetes 的集群的 Service,并且通过 externalName 字段指定外部服务的地址,可以使用域名或 IP 格式。集群中的客户端应用通过访问这个 Service 就能访问外部服务了。

使用域名示例:

apiVersion: v1
kind: Service
metadata:
  name: my-service
  namespace: prod
spec:
  type: ExternalName
  externalName: www.baidu.com
YAML

使用 ip+port 示例:见 2.4 小节。

2.6. Headless Service

Headless Service 是指服务没有入口访问地址无(ClusterIP 地址),kube-proxy 不会为其创建负载转发规则,而服务名(DNS域名)的解析机制根据该 Headless Service 是否设置了标签选择器而有所不同。下面通过几个例子来说明 Headless Service 的概念和用法。

2.6.1. Headless Service 设置了标签选择器

如果 Headless Service 设置了标签选择器,Kubernetes 将根据标签选择器查询后端 Pod列表,自动创建 Endpoints 列表,将服务名(DNS城名)的解析机制设置为:当客户端访问该服务名时,得到的是全部 Endpoints 列表,而不是一个单独的IP地址。示例如下:

# headless-service-test.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.26.2
        ports:
        - containerPort: 80

---
apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
  - port: 80
  clusterIP: None
  selector:
    app: nginx
YAML

创建示例:

kubectl apply -f headless-service-test.yaml
ShellScript

查看创建的 pod ip:

kubectl get pods -l app=nginx -o wide

NAME                     READY   STATUS    RESTARTS   AGE     IP            NODE                NOMINATED NODE   READINESS GATES
nginx-567cbc4d7c-5bs62   1/1     Running   0          2m25s   10.244.5.80   k8s-node-08-u-218   <none>           <none>
nginx-567cbc4d7c-dwff7   1/1     Running   0          2m25s   10.244.2.83   k8s-node-01-c-211   <none>           <none>
nginx-567cbc4d7c-sfnrd   1/1     Running   0          2m25s   10.244.3.89   k8s-node-04-r-214   <none>           <none>
ShellScript

查看 Headless Service 详细信息:

kubectl describe svc nginx

Name:                     nginx
Namespace:                default
Labels:                   app=nginx
Annotations:              <none>
Selector:                 app=nginx
Type:                     ClusterIP
IP Family Policy:         SingleStack
IP Families:              IPv4
IP:                       None
IPs:                      None
Port:                     <unset>  80/TCP
TargetPort:               80/TCP
Endpoints:                10.244.5.80:80,10.244.2.83:80,10.244.3.89:80
Session Affinity:         None
Internal Traffic Policy:  Cluster
Events:                   <none>
ShellScript

FQDN 域名格式:(podname).(headless server name).namespace.svc.cluster.local

可以使用 nslookup 工具解析 ip 地址:

# 创建一个 ubuntu 系统 pod,并进入 pod
kubectl exec -it ubuntu-pod -- bash

# 解析 headless server 将会返回全部的 endpoint 的 ip 地址
nslookup nginx.default.svc.cluster.local

Server:		10.96.0.10
Address:	10.96.0.10#53

Name:	nginx.default.svc.cluster.local
Address: 10.244.5.80
Name:	nginx.default.svc.cluster.local
Address: 10.244.2.83
Name:	nginx.default.svc.cluster.local
Address: 10.244.3.89
ShellScript

statefulSet 创建的 Pod 有 DNS 地址,通过解析 Pod 的 DNS 可以返回 Pod 的 IP 地址而 deployment 创建的 Pod 没有 DNS,则无法解析。我们可以通过设置 hostname 和 subdomain 字段来自定义一个域名。示例如下:

# headless-service-test.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      hostname: nginx-01
      subdomain: nginx
      containers:
      - name: nginx
        image: nginx:1.26.2
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
  - port: 80
  clusterIP: None
  selector:
    app: nginx
YAML
nslookup nginx-01.nginx.default.svc.cluster.local

Server:		10.96.0.10
Address:	10.96.0.10#53

Name:	nginx-01.nginx.default.svc.cluster.local
Address: 10.244.5.84
ShellScript

官方参考文档:https://kubernetes.io/zh-cn/docs/concepts/services-networking/dns-pod-service