目录
一、定义Service
1.1 type=ClusterIP
1.2 type=NodePort
1.3 type=LoadBalancer
1.4 type=ExternalName
1.5 无标签选择器的Service
1.6 Headless Service
二、Kubernetes的服务发现
2.1 环境变量方式
2.2 DNS方式
Kubernetes 中 Service 是 将运行在一个或一组 Pod 上的应用程序对外暴露出去的方法。这样多个相同功能容器可以对外提供统一的访问入口。
Service解决了:
- 避免直接访问Pod,因为Pod的数量和IP都可能发生变化。而通过Service可以提供稳定的统一入口。
- 提供了负载均衡机制(包括后端Pod自动注册和发现)
一、定义Service
定义Service的yaml常见字段含义说明如下:
apiVersion: v1
kind: Service
metadata:name: namespace:labels: []anotations: []
spec:selector: [] # 标签选择器,选择满足条件的后端Podtype: # Service的公开暴露类型,type可取值(首字母大写):ClusterIP默认、NodePort、LoadBalancer、ExternalNameclusterIP: # 分配给该Service的IP地址列表。type=ClusterIP或NodePort时,若不手动指定IP则自动分配(在kubeadm init指定的网段范围内);若填值None则为Headless服务sessionAffinity: # 可取值:ClientIP、None默认。若配置为ClientIP表示对收到同个客户端IP的访问请求每次都转发到同个后端Pod上。ports: # 端口列表,列表多个须分别指定端口的name- name: # 端口名。若仅有一个port,可不设置此字段protocol: # 协议。可取值: TCP默认、UDP、SCTPport: # Service在clusterIP上的监听端口nodePort: # 当type=NodePort或LoadBalancer时,在每个节点机器上的分配端口(端口允许范围30000-32767)。targetPort: # 转发至后端Pod上的端口,若未指定则等于port
status: # 当type=LoadBalancer时设置外部负载均衡器地址,比如公有云环境loadBalancer: # 外部负载均衡器ingress: # 外部负载均衡器ip: # 外部负载均衡器的IP地址hostname: # 外部负载均衡器的主机名
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: # 标签选择器,选择满足条件的后端Podapp: nginxtype: ClusterIPports:- port: 55580 # Service在clusterIP上的监听端口targetPort: 80 # 转发至后端Pod上的端口,若未指定则等于port
kubectl apply -f nginx-service.yaml# 以下service可简写svc
kubectl get service -o widekubectl describe service/nginx-service
注:kube-apiserver本身也是一个Service,名字为kubernetes。 该service的ClusterIP是地址池中第一个IP,端口是HTTPS的443。如上图。
Service中的Endpoints是后端Pod的地址列表。如果Pod发生变化,Kubernetes会自动维护Service的EndPoints地址列表,自动保持最新。
从Kubernetes集群内(包括集群节点机器、Pod容器内)可通过命令访问Service的虚拟IP和端口来访问后端的Pod:curl http://172.16.242.61:55580
默认情况,访问请求会被Service均衡分发给后端Pod之一。
1.2 type=NodePort
在上文type=ClusterIP的例子中,因为值在服务虚拟IP(clusterIP)上监听,所以只能在Kubernetes集群内(包括集群节点机器及容器内)才能访问到该Service。
若希望从Kubernetes集群外也可以访问Service,则需把改type=NodePort。NodePort是建立在type=ClusterIP的基础上,并在每个节点机器上分配一个nodePort端口。通过该nodePort端口访问的请求,Service也可以分发到与后端Pod的Endpoints。
apiVersion: v1
kind: Service
metadata:name: nginx-service
spec:selector: # 必选。标签选择器,选择满足条件的后端Podapp: nginxtype: NodePortports:- 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 widekubectl describe service/nginx-service
不仅可以从Kubernetes集群内(包括集群节点机器、Pod容器内)可通过命令访问Service的虚拟IP和端口来访问后端的Pod:curl http://172.16.167.197:55580
并且还可以从Kubernetes集群外访问,访问地址为:任意一个节点机器IP:nodePort 如下图:
虽然可以通过节点机器IP访问,但在节点机器操作系统上通过netstat命令是无法看到nodePort侦听端口。这是因为没有采用通常的直接监听方式,而是kube-proxy利用操作系统的代理模式来转发。
kube-proxy的--proxy-mode选项指定转发的代理模式,当节点机器是Linux则可为ipvs或iptables模式(不手工指定时,若Linux操作系统加载启用了IPVS内核,kube-proxy会自动优先选择IPVS模式,否则切换iptables模式);当节点机器是Windows则为kernelspace模式。
1.3 type=LoadBalancer
type=LoadBalancer是基于NodePort的基础上构建并创建一个外部负载均衡器,通过该外部负载均衡器路由到与 clusterIP 相同的 Endpoints。
type=LoadBalancer主要用于云平台环境。Service使用云平台负载均衡器,Kubernetes不直接提供负载均衡。这要求云平台能够监控Kubernetes的Service资源对象创建过程,云平台负载均衡器中upstream值自动配置为Service的Endpoints列表,同时云平台还能对Kubernetes的Service的Status信息补充配置好的负载均衡器的IP地址。这样外部客户端就可以通过云平台负载均衡器IP及Service端口进行访问后端Pod服务了。
默认情况:对于type=LoadBalancer的Service会默认开启节点机器NodePort,这是为了让负载均衡器能够通过Node节点IP来访问内部Pod。 从v1.24版开始,Service配置中提供了一个布尔类型字段allocateLoadBalancerNodePorts来指定是否分配NodePort(默认为true)。仅当云平台负载均衡器能够直接将流量路由到 Pod 而无需使用节点机器端口的情况才设置为false。
1.4 type=ExternalName
type=ExternalName是将 Service 映射到指定的目标域名或IP。相当于对目标域名或IP设置别名。
下面externalname-service.yaml
apiVersion: v1
kind: Service
metadata:name: externalname-service
spec:type: ExternalNameexternalName: www.baidu.com
kubectl apply -f externalname-service.yaml# 以下service可简写svc
kubectl get service -o widekubectl 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" 标签,以选择匹配Servicekubernetes.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 widekubectl describe service/outside-servicekubectl get endpointslices
可以在Kubernetes集群内(包括集群节点机器、Pod容器内)可通过命令访问Service的虚拟IP和端口来访问外部服务。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的服务发现
对于在集群内运行的客户端,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>