Kubernetes(四):Service
Kubernetes Service是暴露Pod应用程序的统一访问入口,提供负载均衡和稳定IP。主要类型包括:1) ClusterIP(默认集群内访问);2) NodePort(通过节点端口暴露);3) LoadBalancer(集成外部负载均衡器);4) ExternalName(映射外部服务)。特殊类型有无选择器Service(手动指定端点)和Headless Service(直接访问Pod)
目录
Kubernetes 中 Service 是 将运行在一个或一组 Pod 上的应用程序对外暴露出去的方法。这样多个相同功能容器可以对外提供统一的访问入口。Service基于网络第四层(既TCP/UDP层)工作。在《Kubernetes in Action》中介绍Service的一个示例场景如下图:

Service解决了:
- 避免直接访问Pod,因为Pod是临时的,其数量和IP都可能发生变化,客户端很难提前知道有哪些Pod IP。而Service以稳定统一入口对外进行暴露。
- 提供了负载均衡机制(包括后端Pod自动注册和发现)
一、定义Service
定义Service的yaml常见字段含义说明如下:
apiVersion: v1
kind: Service
metadata:
name:
namespace:
labels: []
anotations: []
spec:
selector: [] # 标签选择器,选择满足条件的后端Pod
type: # Service的公开暴露类型,type可取值(首字母大写):ClusterIP默认、NodePort、LoadBalancer、ExternalName
clusterIP: # 分配给该Service的虚IP地址列表。type=ClusterIP或NodePort时,若不手动指定IP则自动分配(在kubeadm init指定的网段范围内);若填值None则为Headless服务
sessionAffinity: # 可取值:ClientIP、None默认。若配置为ClientIP表示对收到同个客户端IP的访问请求每次都转发到同个后端Pod上。
ports: # 端口列表,列表多个须分别指定端口的name
- name: # 端口名。若仅有一个port,可不设置此字段
protocol: # 协议。可取值: TCP默认、UDP、SCTP
port: # Service在clusterIP上的监听端口
nodePort: # 当type=NodePort或LoadBalancer时,在每个节点机器上的分配端口(端口允许范围30000-32767)。
targetPort: # 转发至后端Pod上的端口,若未指定则等于port
externalIPs: [] # 指定暴露的外部IP列表,这些IP不由Kubernetes管理所以称为外部IP。需自行确认些IP能够路由至一个或多个集群工作节点。某个节点收到流量后且流量中携带的目标IP在外边IP列表范围,则Kubernetes自动将该流量转发至后端Pod
status: # 当type=LoadBalancer时设置外部负载均衡器地址,比如公有云环境
loadBalancer: # 外部负载均衡器
ingress: # 外部负载均衡器
ip: # 外部负载均衡器的IP地址
hostname: # 外部负载均衡器的主机名
下面介绍四种type的Service中,其中ClusterIP、NodePort、LoadBalancer三种type大致是“叠加递进”关系。
1.1 type=ClusterIP
在前文【Kubernetes(三):Workload工作负载-CSDN博客】已经创建Deployment的基础上(Pod的标签是app=nginx, Pod内容器端口为80)。然后通过以下nginx-service.yaml文件,创建一个Service:
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
selector: # 标签选择器,选择满足条件的后端Pod
app: nginx
type: ClusterIP
ports:
- port: 55580 # Service在clusterIP上的监听端口
targetPort: 80 # 转发至后端Pod上的端口,若未指定则等于port
kubectl apply -f nginx-service.yaml
# 以下service可简写svc
kubectl get service -o wide
kubectl describe service/nginx-service

另外:kube-apiserver本身也是一个Service,名字为kubernetes。 该service的ClusterIP是地址池中第一个IP,端口是HTTPS的443。如上图。
Service中的Endpoints是后端Pod的地址列表,只有处于Running且readinessProbe检查通过的Pod才会出现在Endpoints地址列表中。如果Pod发生增加/减少等变化时,Kubernetes会自动维护Service的EndPoints地址列表,自动保持最新。
从Kubernetes集群内(包括集群节点机器、Pod容器内)可通过命令访问Service的虚拟VIP和端口来访问后端的Pod:curl http://172.16.242.61:55580
默认情况,访问请求会被Service均衡分发给后端Pod之一。
1.2 type=NodePort
在上文type=ClusterIP的例子中,因为仅在服务虚拟VIP(clusterIP)上监听,所以只能在Kubernetes集群内(包括集群节点机器及容器内)才能访问到该Service。 在《Kubernetes in Action》书中对type=NodePort对外暴露示意图如下:

若希望从Kubernetes集群外也可以访问Service,则可把改type=NodePort。NodePort是建立在type=ClusterIP的基础上,并在每个工作节点机器上分配一个nodePort端口。通过该nodePort端口访问的请求,Service也可以分发到与后端Pod的Endpoints。
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
selector: # 必选。标签选择器,选择满足条件的后端Pod
app: nginx
type: NodePort
ports:
- port: 55580 # Service在clusterIP上的监听端口
nodePort: 32000 # 当type=NodePort或LoadBalancer时,在每个节点机器上的分配端口(端口允许范围默认30000-32767)。
targetPort: 80 # 转发至后端Pod上的端口,若未指定则等于port
kubectl apply -f nginx-service.yaml
# 以下service可简写svc
kubectl get service -o wide
kubectl describe service/nginx-service

不仅可以从Kubernetes集群内(包括集群节点机器、Pod容器内)可通过命令访问Service的虚拟VIP和端口来访问后端的Pod:curl http://172.16.167.197:55580
并且还可以从Kubernetes集群外访问,访问地址为:任意节点机器IP:nodePort 如下图:

注意1、通过任意一个节点机器IP访问,例如通过工作节点机器1的IP进行访问,既可访问到节点1上的后端Pod,也能访问其他节点上的后端Pod。kube-proxy会自动进行转发。
注意2、在节点机器操作系统上通过netstat命令是无法看到nodePort侦听端口。这是因为没有采用通常的直接监听方式,而是kube-proxy利用操作系统的代理模式来转发。具体看实现原理。
1.3 type=LoadBalancer
以上的type=NodePort解决了从Kubernetes外部访问问题,虽然从任意一个工作节点机器IP均可以访问,但对外不是暴露的统一的地址,不便于外部客户端使用。
type=LoadBalancer是在NodePort基础上,使用在Kubernetes集群外部已存在的负载均衡器,外部负载均衡器先完成工作节点机器选择。type=LoadBalancer除多了一个外部负载均衡器以外,其他方面与type=NodePort类似。
这里的外部负载均衡器是指:不属于Kubernetes、在Kubernete外部存在的担任负载均衡功能的硬件/应用/组件等。该外部负载均衡器负责按分发规则对收到流量转发至Kubernetes的Node或Pod。
通常使用场景:由云厂商托管的Kubernetes(如Amazon的EKS、阿里云的ACK)会自动检测到type=LoadBalancer的Service,并通过云控制器管理器调用云厂商的 API(如 AWS EC2 API、阿里云 SLB API)自动创建一个云负载均衡器(如 ELB、SLB)。云负载均衡器会被分配一个外部 IP 地址(公网 IP),用于外部客户端访问。因此LoadBalancer 类型的 Service 是云环境中暴露服务的首选方案,其核心价值在于:自动集成云厂商负载均衡器,提供稳定的外部访问入口,实现请求负载均衡,并动态适配后端 Pod 变化,大幅简化了云上服务对外暴露的复杂度。
当然除了云厂商托管自动创建情况外,也可以手工搭建外部负载均衡器。
在《Kubernetes in Action》书中对type=LoadBalancer图如下:

默认情况:对于type=LoadBalancer的Service会默认开启节点机器NodePort,这是为了让负载均衡器能够通过Node节点IP来访问内部Pod。 从v1.24版开始,Service配置中提供了一个布尔类型字段allocateLoadBalancerNodePorts来指定是否分配NodePort(默认为true)。仅当云平台负载均衡器能够直接将流量路由到 Pod 而无需使用节点机器端口的情况才设置为false。
注意1:外部负载均衡器选择的节点机器,并不一定就是真正运行Pod所在的节点机器。即图中期望访问的目标Pod在可能请求的节点A上,但负载均衡器选择并分发的是节点B。因此可能还需要额外的网络跳转才能到达Pod。对此,在Service中.spec.externalTrafficPolicy设置为Local(可取
Cluster与Local,默认Cluster)来确定外部均衡器仅分发到Pod真正所在的节点机器,以避免额外跳数。注意2:当负载均衡器采用均匀分发情况下,若.spec.externalTrafficPolicy=Local则会出现Pod数量的节点上每个Pod收到的请求流量反而少。原理对比如下图

- 访问方发出请求,流量首先到达外部负载均衡器
- 外部负载均衡器根据预设规则(如轮询、权重),将请求转发到Kubernetes集群内的某个 Node 机器对应端口,既NodePort(通常是 30000-32767 范围内的端口)。
- Node 上的 kube-proxy 组件接收到请求后,根据 Service 与 Pod 的关联关系,将请求转发到后端的某个 Pod(通过nftables或iptables转发,在Kubernetes 1.35已废弃ipvs)。
- Pod 处理请求后,将响应按原路返回给访问方。
1.4 type=ExternalName
type=ExternalName是将 Service 映射到指定的目标域名或IP。相当于对目标域名或IP设置别名。下面externalname-service.yaml
apiVersion: v1
kind: Service
metadata:
name: externalname-service
spec:
type: ExternalName
externalName: www.baidu.com
kubectl apply -f externalname-service.yaml
# 以下service可简写svc
kubectl get service -o wide
kubectl describe service/externalname-service

注:type=ExternalName的Service没有Endpoints,也没有为服务分配ClusterIP。
Kubernetes对服务自动生成的域名格式为:<ServiceName>.<namespace>.svc.<clusterDomain>
从Kubernetes集群内(包括集群节点机器、Pod容器内)可通过该服务域名访问,既自动访问externalName指定的目标域名。本例中访问 curl http://externalname-service.default.svc.cluster.local 相当于访问www.baidu.com
1.5 无标签选择器的Service
上文type=ExternalName的Service没有Endpoints,也没有为服务分配ClusterIP。 除上文外,还可以通过基于type=ClusterIP类型来将外部服务定义为Service,这种将外部服务定义为Service也称为没有标签选择器的Service (Services without selectors)。
普通type=ClusterIP的Service通过selector标签选择器对后端Pod的Endpoints列表进行了抽象封装,如果后端的Endpoints不是由Pod提供,而是在Kubernetes之外的其他服务提供。在Kubernetes里的一些应用也需要访问这些外部服务,典型场景为:
- 非Kubernetes管理的服务。例如单独部署的数据库、Redis、或者其他服务。
- 从一个Kubernetes集群访问另一个Kubernetes集群。
本场景中,在创建Service时不设置selector标签选择器(也无后端Pod可选)。由于此 Service 没有选择器,因此不会自动创建对应的 EndpointSlice 对象。需要手工在EndpointSlice定义外部服务的IP地址和端口号。
注意:从v1.33版开始,Endpoints已被标记为deprecated。所以低版本Kubernetes可使用Endpoints,高版本Kubernetes应当使用EndpointSlice。
为演示,先通过python启动简易Web,模拟已存在的外部服务
# 若python版本为2.x
python -m SimpleHTTPServer 9999
# 若python版本为3.x
python -m http.server 9999
下面outside-service.yaml
---
apiVersion: v1
kind: Service
metadata:
name: outside-service
spec:
ports:
- port: 21212 # Service在clusterIP上的监听端口
targetPort: 1111 # 此情况因为不使用targetPort,所以配成什么端口都无所谓
---
apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
name: outside-service # 习惯上,将Service名称作为EndpointSlice名称前缀
labels:
# 须设置 "kubernetes.io/service-name" 标签,以选择匹配Service
kubernetes.io/service-name: outside-service
addressType: IPv4 # 取值范围:IPv4、IPv6、FQDN
ports: # 若列表多个须分别指定端口name(且name值须与Service中spec.ports.name字段值匹配一致)
- port: 9999
endpoints: # 此列表中的 IP 地址可以按任何顺序显示
- addresses:
- "192.168.43.49"
kubectl apply -f outside-service.yaml
# 以下service可简写svc
kubectl get service -o wide
kubectl describe service/outside-service
kubectl get endpointslices

可以在Kubernetes集群内(包括集群节点机器、Pod容器内)可通过命令访问Service的虚拟VIP和端口来访问外部服务。curl http://172.16.155.209:21212
1.6 Headless Service
在某些场景场景中,不需要Service提供负载均衡功能,也不需要Service的ClusterIP。而是直接对后端Pod进行指定访问,因此只需要Service为后端每个Pod生成一个子域名。这是可以创建一个与普通Service不同的Headless Service。Headless Service既是没有入口访问地址(服务无ClusterIP),kube-proxy不会为其创建负载转发规则。
有标签选择器的Headless Service
如果Headless Service配置了标签选择器,则Kubernetes根据匹配选择的后端Pod,自动创建Endpoints列表。此Headless Service会将DNS解析机制设为:①服务域名可得到后端所有Pod的IP列表(而不是单独的一个ClusterIP);②还会为每个Pod都创建一个子域名,便于对特定某个Pod进行指定访问或识别。示例可以参考:https://blog.csdn.net/zyplanke/article/details/150985065 中StatefulSet章节。
无标签选择器的Headless Service
如果Headless Service没有配置标签选择器,则Kubernetes不会自动创建对应的Endpoints列表。Kubernetes的DNS会根据如下条件尝试对该Service设置DNS解析:
- 对于 type=ExternalName,将配置的externalName尝试作为对应的 CNAME 记录。
- 对所有其他类型的 Service,对Service后端处于Ready状态的Endpoints既Endpointslices创建DNS记录。对于 IPv4 端点,DNS 系统创建 A 记录; 对于 IPv6 端点,DNS 系统创建 AAAA 记录。
二、服务发现
对于在集群内运行的客户端,Kubernetes 支持两种Service发现模式:环境变量和DNS。
2.1 环境变量方式
当 Pod 运行时,kubelet 会自动为其注入以“KUBERNETES_”开头的环境变量。
除此环境变量外,根据当前已经存在且活跃 Service,kubelet还会对新创建的Pod自动为已存在的Service生成一组环境变量添加到Pod容器中。 kubelet添加的环境变量包括:
- {SVCNAME}_SERVICE_HOST
- {SVCNAME}_SERVICE_PORT
- {SVCNAME}_PORT
这里 Service 名称被转为大写字母,横线被转换成下划线。

在新创建Pod容器中,可以从环境变量获取需要访问的目的Service的地址了。
2.2 DNS方式
对于环境变量方式,要求Service必须先于Pod创建好。这实际上对部署顺序提出了较为苛刻的要求,而使用DNS方式就不存在此问题。 对于客户端而言,DNS域名方式提供了一种稳定的、不变的访问方式,可以简化客户端配置,是Kubernetes推荐的方式。
Service遵从DNS命名规范:
Service的DNS域名命名规则:<ServiceName>.<namespace>.svc.<clusterDomain>
如果Service中的port端口号指定了名称name,则该端口号也拥有DNS域名:
Service端口的DNS域名命名规则:_<PortName>._<Protocal>.<ServiceName>.<namespace>.svc.<clusterDomain>
Kubernetes自带的CoreDNS服务,在Kubernetes集群内部(例如Pod的容器内)可使用该DNS解析。但节点机器Node无法使用该DNS解析,因为节点机器Node上未配置该CoreDNS服务地址,无法查询该CoreDNS来查询和解析。
解决办法可参考:https://blog.csdn.net/zyplanke/article/details/120062644
三、Service的实现原理
在Kubernetes中的Service是依赖底层操作系统的网络转发来完成的。对于节点是Linux是nftables或iptables模式(若Linux操作系统加载启用了nftables内核,kube-proxy默认会自动优先选择nftables模式,否则切换iptables模式。也可通过kube-proxy的--proxy-mode选项指定转发的代理模式)对于节点是Windows则为kernelspace模式。
在本环境中是通过Linux的iptables转发来实现Service的,可命令“iptables -t nat -L -n -v”查看其中NAT表链规则,或者通过“iptables-save > 文件名”导出至文件以更方便规则规则细节。可以看到需要以KUBE开头的链规则,常见的有:
- KUBE-SERVICES或KUBE-NODEPORTS 为对应Service的入口链规则,这些规则应与服务虚拟IP(ClusterIP)和Service端口对应。
- KUBE-SEP-(hash) SEP是 Kubernetes Service Endpoint 的缩写,为DNAT链规则,这些规则应与Endpoints对应。
- KUBE-SVC-(hash) 为负载均衡链规则,其中--mode random为随机模式,--probability为概率。
- 如果是type=NodePort,还涉及POSTROUTING的SNAT链规则等。
- ……
更多详细知识,可参考《深入理解Kubernetes网络系统原理》书籍。
更多推荐

所有评论(0)