kubernetes控制器-Service
# kubernetes控制器-Service
Service 是 将运行在一个或一组 Pod (opens new window) 上的网络应用程序公开为网络服务的方法。
使用Deployment部署Pod时,Pod大部分都是随机地部署到节点上,并且经常被删除重建,这就导致了容器间访问需要一种固定不变的资源类型来访问。
而kubernetes定义了一种对象类型,通过Service来访问Pod,而Service充当负载均衡器来把连接转发到其中任意一个Pod当中。
# 1. 创建Service
# 1.1. 带有标签选择器的Service
与Deployment相同,Service也通过标签选择器进行匹配Pod,新建 Service.yml
:
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app.kubernetes.io/name: MyApp
ports:
- protocol: TCP
port: 80
targetPort: 9376
2
3
4
5
6
7
8
9
10
11
这个yml文件创建了一个名称为 my-service
的Service,并匹配所有标签为 app.kubernetes.io/name: MyApp
的Pod,并把Pod的9376端口(协议类型为TCP)反向代理到80端口。
创建这个Service,并查看Service的状态:
kubectl apply -f Service.yml
kubectl get services
2
- TYPE:Service 类型,默认为ClusterIP,即只能在集群内部进行访问,通过
CLUSTER-IP:PORT
进行访问,或者通过NAME:PORT
进行访问。
也可以在Pod中 .spec.containers[].ports[].name
自定义端口的名称,之后在Service中根据名称进行调用,通过自定义端口的名称可以极大提高Service配置的灵活性,例如我更新了Pod的端口,就不再需要再次更新Service的targetPort,减少了配置的工作量:
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 1.2. 不带标签选择器的Service
通过不设置标签选择器可以自定义匹配Service的后端服务,相当于自定义了一个负载均衡器。
而这个负载均衡器又通过定义 EndpointSlice
对象和 Endpoints
对象来把连接转发到其中任意一个IP地址当中。
可以应用在以下场景:
希望在生产环境中使用某个固定的名称而非IP地址访问外部的中间件服务;
使用其他命名空间或者其他集群的服务;
从基础环境迁移到kubernetes时,需要将一部分流量转发到外部服务当中;
# 1.2.1. Endpoints
Endpoints (opens new window) (该资源类别为复数)定义了网络端点的列表,通常由 Service 引用,以定义可以将流量发送到哪些 Pod。
简单来说,Endpoints对象包含了需要进行反向代理的IP和端口。而针对Endpoints对象,又根据Service的情况分为两种Endpoints:
- Service带有标签选择器:由kubernetes管理,有与Service匹配的Pod新建或者删除时,kubernetes都会自行更新Endpoints对象中的信息,来做到动态的负载均衡。
- Service不带标签选择器:如果创建了不包含标签选择器的Service,kubernetes将不会自动创建Endpoints对象,而这种方式可以通过手动创建Endpoints对象来实现Service与Endpoints的对象相匹配。
下面我们主要看一下不带标签选择器的Service是如何与Endpoints建立连接的,新建 ServiceWithoutSelector_Endpoints.yml
:
apiVersion: v1
kind: Service
metadata:
name: my-service # Service的名称必须与Endpoints的名称相匹配
spec:
ports:
- protocol: TCP
port: 80 # Service对外暴露的端口
---
apiVersion: v1
kind: Endpoints
metadata:
name: my-service # Endpoints的名称必须与Service的名称相匹配
subsets:
- addresses:
- ip: 10.1.1.1 # 被反向代理的ip地址
- ip: 10.1.1.2 # 被反向代理的ip地址
ports:
- port: 80 # 被反向代理的端口
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
创建这个Service,并查看Service和Endpoints的状态:
kubectl apply -f ServiceWithoutSelector_Endpoints.yml
kubectl get services
kubectl get endpoints
2
3
通过访问 CLUSTER-IP:PORT
即可正常访问对应的Service。
# 1.2.2. EndpointSlice
EndpointSlice (opens new window) 对象表示某个服务的后端网络端点的子集(切片)。
例如电商行业在进行促销活动或秒杀抢购活动时,业务流量相对较大。为了应对这种场景,通常会设置弹性扩容。在活动进行时,服务会进行弹性伸缩直到能够承载流量,这时会基于弹性扩容的策略,为业务增加副本数,也就是 Pod 会变多。
每个 Pod 都有各自唯一的 IP ,但同时 Pod 的 IP 也不是固定的。为了及时追踪 Pod IP 的变化,从而进行负载均衡,Endpoints API 提供了在 Kubernetes 中跟踪网络端点的一种简单而直接的方法。
但随着 Kubernetes 集群和服务逐渐开始为更多的后端 Pod 进行处理和发送请求,比如上文提到大流量场景下,Pod 数量会被不断扩容,Endpoints API 也将变得越大。这种情况下,Endpoints API 局限性变得越来越明显,甚至成为性能瓶颈。
为了解决这个局限性问题,在 Kubernetes v1.21 的版本中引入了对 Endpointslice API 的支持,解决了 Endpoints API 处理大量网络端点带来的性能问题,同时提供了可扩展和可伸缩的能力。
Endpoints 在流量高峰时的变化:
Endpointslices 在流量高峰时的变化:
下面我们主要看一下不带标签选择器的Service是如何与EndpointSlice建立连接的,新建 ServiceWithoutSelector_EndpointSlice.yml
:
apiVersion: v1
kind: Service
metadata:
name: my-service # Service的名称必须与Endpoints的标签kubernetes.io/service-name相匹配
spec:
ports:
- protocol: TCP
port: 80
targetPort: 8443
---
apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
name: my-service-1 # 按惯例将Service的名称用作EndpointSlice名称的前缀
labels:
# Endpoints的标签kubernetes.io/service-name必须与Service的名称相匹配
kubernetes.io/service-name: my-service
addressType: IPv4 # 地址类型,可以配置IPv4,IPv6,FQDN三种参数
ports:
- appProtocol: http # 关联的服务的协议
protocol: TCP # 协议类型
port: 8443
endpoints:
- addresses:
- "10.1.1.1" # 被反向代理的ip地址
- "10.1.1.2" # 被反向代理的ip地址
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
需要注意的是,Service的名称与Endpoints的标签 kubernetes.io/service-name
相匹配,与Endpoints匹配方式略有不同。
同样创建这个Service,并查看Service和EndpointSlice的状态:
kubectl get services
kubectl get endpointslices
2
Service与EndpointSlice都成功被创建并且可以正常访问。
# 2. Service的类型
Service Type(服务类型)主要包括以下几种:
- ClusterIP:默认值,只能在集群内部进行访问。通过
CLUSTER-IP:PORT
进行访问,或者通过NAME:PORT
进行访问。 - NodePort:在所有安装了 kube-proxy 的节点上打开一个端口,此端口可以代理至后端Pod,可以通过NodePort从集群外部访问集群内的服务,格式为
NodeIP:NodePort
。 - LoadBalancer:使用云提供商的负载均衡器公开Service。
- ExternalName:通过返回定义的CNAME别名,将Service映射到可被DNS解析的其他域名。
ClusterIP这里省略,直接分析下Service对外暴露的三种类型,NodePort,LoadBalancer和ExternalName。
# 2.1. NodePort
通过创建Service,并将Type设置为NodePort来实现通过NodePort从集群外部访问集群内的服务。
通过图片我们可以看出来,将Type设置为NodePort可以实现在每一个集群节点上都会打开一个NodePort,而通过访问任意一个集群节点的IP+NodePort就可以从外部访问到后端的Pod。
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
type: NodePort
selector:
app.kubernetes.io/name: MyApp
ports:
# 为了方便起见,targetPort被设置为与port字段相同的值
- port: 80
targetPort: 80
# 可选字段,不指定此字段的情况下,kubernetes会从某个范围内分配一个端口号(默认:30000-32767)
nodePort: 30007
2
3
4
5
6
7
8
9
10
11
12
13
14
可以通过如下方式访问此Sercice:
- 集群内部:通过
CLUSTER-IP:PORT
进行访问,或者通过NAME:PORT
进行访问。 - 集群外部:任意一个集群节点的宿主机IP+NodePort进行访问。
# 2.2. LoadBalancer
LoadBalancer底层依赖于NodePort。
使用NodePort就又涉及到负载均衡的问题,而LoadBalancer就是为了解决K8s将内部服务对外暴露时如何进行负载均衡的问题而出现的。
假设我们有一套阿里云K8s环境,要将K8s内部的一个服务通过LoadBalancer方式暴露出去,可以将Service type设定为LoadBalancer。服务发布后,阿里云K8s不仅会自动创建服务的NodePort端口转发,同时会自动帮我们申请一个SLB,有独立公网IP,并且阿里云K8s会帮我们自动把SLB映射到后台K8s集群的对应NodePort上。这样,通过SLB的公网IP,我们就可以访问到K8s内部服务,阿里云SLB负载均衡器会在背后做负载均衡。
值得一提的是,如果是在本地开发测试环境里头搭建的K8s,一般不支持LoadBalancer,因为通过NodePort做测试访问就够了。但是在生产环境或者公有云上的K8s,例如GCP或者阿里云K8s,基本都支持自动创建LoadBalancer。
如果是自建集群,可以通过安装 MetalLB (opens new window) 来使用 LoadBalancer。这里暂时不跟进讲解自建集群的LoadBalancer相关配置。
apiVersion: v1
kind: Service
metadata:
labels:
app: nginx
name: my-nginx-svc
namespace: default
spec:
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
app: nginx
type: LoadBalancer
2
3
4
5
6
7
8
9
10
11
12
13
14
15
公有云具体配置可以参考阿里云官方提供的 通过使用自动创建SLB的服务公开应用 (opens new window) 这篇文章进行配置。
# 2.3. ExternalName
通过返回定义的CNAME别名,将Service映射到可被DNS解析的其他域名。
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
type: ExternalName
externalName: my.database.example.com
2
3
4
5
6
7
这个yml把名称为my-service的Service映射到了my.database.example.com这个域名。
# 2.4. externalIPs
定义 Service 时,你可以为任何服务类型 (opens new window)指定 externalIPs。
externalIPs可以说是Service类型的扩充。
如果有外部可达的 IP(如网络通过BGP,OSPF等路由协议进行宣告路由可达集群节点) ,即集群外能通过这个IP访问到集群内的Node(至少其中一台节点),那我们就可以通过这些Node将流量转发到Service,并提供负载均衡。
🔔
external IP
一般由数据中心或者网络管理员管理,独立于kubernetes之外。
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app.kubernetes.io/name: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
externalIPs:
- 192.168.7.30
2
3
4
5
6
7
8
9
10
11
12
13
这个叫 my-service
的服务可以在 192.168.7.30:80
上被正常访问。
# 3. 服务发现
kubernetes支持两种类型的服务发现:环境变量和DNS。
# 3.1. 环境变量
当Pod部署到一个Node节点后,kubelet会在其中为每个活跃的Service添加一组环境变量,并以 {SERVICE_NAME}_SERVICE_HOST
和 {SERVICE_NAME}_SERVICE_PORT
格式命名的变量,其中服务名都转换为大写字母,.
和 -
转换为 _
。
# 3.2. DNS
参考文章及书籍: