Service是什么?
在说明Service是什么之前先了解下Service的使用场景:
- 当客户端想要访问K8S集群中的pod时,需要知道pod的ip以及端口,那K8S中如何在不知道pod的地址信息的情况下进行pod服务的快速连接?
- 若某一node上的pod发生故障,K8S最大的特点就是能够给感知和重启该pod,但是pod重启后ip会发生变化,那么客户端如何感知并保持对pod的访问?
- 如果多个pod组合在一起形成pod组,如何在被访问时达到负载均衡的效果?
针对上面三种需求,K8S提出了Service的概念,意在解决上述三个问题和场景,下面来看看Service的定义:
一、Service 概念
Service 可以理解为逻辑上的一组Pod,一种可以访问Pod的策略,而且其他Pod可以通过这个Service访问到这个代理的Pod。相对于Pod而言,它会有一个固定的名称,一旦创建就固定不变。
服务之间的调用,要使用Service名称调用,不要使用IP地址调用,因为PodIP是随机的,每次重启会重新生成IP地址,可以新建一个Service,通过Service名称来访问应用。如果跨namespaces,可以使用Service名"."namespaces,来访问
Service的YAML格式的定义文件的完整内容如下:
apiVersion: v1
kind: Service #定义一个Service
metadata:
name: string #Service的名称
namespaces: string #命名空间
labels: #自定义标签属性列表
- name: string
annotations: #自定义注解列表
- name: string
spec: #详细描述
selector: [] #将选择具有指定Label标签的Pod作为管理范围
type: string #类型,指定Service的访问方式,默认是ClusterIP,详细信息如下
clusterIP: string #虚拟服务的IP地址,当type=clusterIP时,如果不指定,系统会自动分配,也可以手工指定,当type=LoadBalancer时,需要指定
sessionAffinity: string #是否支持session,可选值为ClientIP,默认值为None,ClientIP:表示将一个客户端的访问请求都转发到同一个后端服务。
ports: #service端口列表
- name: string #端口名称
protocol: string #端口协议,支持TCP、udp默认是TCP
port: int #服务监听的端口号
targetPort: int #需要转发到后端Pod的端口号
nodePort: int #当type=NodePort时,需要指定端口号,不指定就随机
status: #当type=LoadBalancer时,设置外部负载均衡的地址,用于公有云环境
loadBalancer: #外部负载均衡器
ingress: #外部负载均衡器
ip: string #外部负载均衡器的IP
hostname: string #外部负载均衡器主机名
二、Service工作机制
- master上的kube-apiserver会处理service的创建,以及Service通过label标签与匹配的Pod绑定而产生的endpoints对象。并将这些元数据内容存储到etcd中
- node上的kube-proxy通过实时监控kube-apiserver上的service以及endpoints的变化来感知相关事件并更新本地的service和endpoint的映射关系;同时node上的kubedns、coredns服务也会同时将service域名信息与IP的对应关系存储起来拱dns解析用。
- kube-proxy将从apiserver获取到的service和endpoint的映射关系保存在本地的iptables、ipvs中供后续查询使用
- client发起对服务的访问,首先kubedns、coredns将服务名称解析成一个虚拟IP(ClusterIP)
- 然后使用这个IP去iptables、ipvs中找service和endpoint的映射关系
- 找到映射关系之后,通过iptables/ipvs的负载均衡算法,匹配到一个合适的Pod进行访问即可。
示例:这是一个详细的svc信息
[root@k8s-master-1 test]# kubectl describe svc service-label
Name: service-label
Namespace: default
Labels: <none>
Annotations: <none>
Selector: env=prod
Type: ClusterIP
IP: 10.0.0.254
Port: 80-80 80/TCP
TargetPort: 80/TCP
Endpoints: 10.244.0.170:80,10.244.0.171:80
Session Affinity: None
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedToUpdateEndpointSlices 12d (x2 over 12d) endpoint-slice-controller Error updating Endpoint Slices for Service default/service-label: failed to update service-label-ct7r2 EndpointSlice for Service default/service-label: Operation cannot be fulfilled on endpointslices.discovery.k8s.io "service-label-ct7r2": the object has been modified; please apply your changes to the latest version and try again
[root@k8s-master-1 test]#
对应关系
1、svc和ep的映射关系
service-label==》10.244.0.170:80,10.244.0.171:80。这些数据会存储到iptables和ipvs中
2、service的域名信息与IP的对应关系:service-label => 10.0.0.254,这些数据会存储到kubedns/coredns中
3、负载均衡,反向代理
10.244.0.170:80,10.244.0.171:80 这两个pod就是需要进行负载均衡和反向代理的pod,iptables和ipvs会根据自身的负载均衡算法来完成此过程
4、整体数据访问流程:
service-label => 10.0.0.254 => 10.244.0.170:80
三、Service的负载均衡机制
它是由每个node上的kube-proxy来负责实现负载均衡的,
1、kube-proxy的代理模式
2、会话保持机制
service支持通过设置 sessionAffinity 实现基于客户端IP的会话保持机制,就是首次将某个客户端来源的IP发起的请求装发到后端的某个Pod上,之后相同的客户端IP发起的请求都将被转发到相同的后端Pod上,配置service.spec.sessionAffinity,
[root@k8s-master-1 svc-test]# cat nginx-svc.yaml
apiVersion: v1
kind: Service
metadata:
name: webapp
spec:
sessionAffinity: ClientIP
ports: #定义service的关键字段
- name: http #Service 端口名称
port: 80 #Service自己的端口
protocol: TCP
targetPort: 80 #后端应用的端口,这两个端口可以不同
同时,用户还可以设置会话保持的最长时间,在此时间之后重置客户端来源IP的保持规则,配置参数为service.spec.sessionAffinityConfig.clientip.timeoutSeconds。例如下边:10800为秒
[root@k8s-master-1 svc-test]# cat nginx-svc.yaml
apiVersion: v1
kind: Service
metadata:
name: webapp
spec:
sessionAffinity: ClientIP
sessionAffinityConfig:
clientIP:
timeoutSeconds: 10800
ports: #定义service的关键字段
- name: http #Service 端口名称
port: 80 #Service自己的端口
protocol: TCP
targetPort: 80 #后端应用的端口,这两个端口可以不同
四、Service的多端口设置
一个容器应用可以设置多个端口服务
1、设置两个端口提供不同的服务
ports: #定义service的关键字段
- name: http #Service 端口名称
port: 80 #Service自己的端口
protocol: TCP
targetPort: 80 #后端应用的端口,这两个端口可以不同
- name: http2 #Service 端口名称
port: 81 #Service自己的端口
protocol: TCP
targetPort: 81 #后端应用的端口,这两个端口可以不同
2、设置不同的协议
ports: #定义service的关键字段
- name: http #Service 端口名称
port: 80 #Service自己的端口
protocol: TCP
targetPort: 80 #后端应用的端口,这两个端口可以不同
- name: http2 #Service 端口名称
port: 81 #Service自己的端口
protocol: UDP
targetPort: 81 #后端应用的端口,这两个端口可以不同
五、Service类型 -将Service暴露到集群外部
K8S中service分为4类,分别是ClusterIP、NodePort|、LoadBalancer、以及ExternaName.
其中,绿色代表从外向内的访问模式,蓝色代表从内向外的访问模式,黄色代表集群内部的访问模式。可以看到,除了ExtermaName类型之外,其余三个都是逐层封装而来的。
ClusterIP:
K8S默认的服务类型,只能在集群中进行服务通信,在ClusterIP中,K8S会在Service创建完毕之后提供一个内部的IP作为ClusterIP属性,K8S内部服务可以通过ClusterIP或者ServiceName来访问该服务。
NodePort
NodePort:是在所有安装了kube-proxy的节点上打开一个端口,此端口可以代理至后端Pod,然后集群外部可以使用节点的IP地址和NodePort的端口访问到集群Pod的服务,NodePort端口范围默认是30000-32767 这个值可以修改,在/usr/lib/systemd/system kube-apiserver.service
[root@k8s-master-1 svc-test]# kubectl edit svc nginx-svc
spec:
clusterIP: 10.0.0.60
externalTrafficPolicy: Cluster
ports:
- name: http
nodePort: 30007 #自己设定端口
type: NodePort #类型改为NodePort
#使用浏览器访问
192.168.134.137:30007 如下图
ExternalName 反向代理域名
即设置Service的type为ExternalName。这样做的好处就是内部服务访问外部服务的时候是通过别名来访问的,屏蔽了外部服务的真实信息,外部服务对内部服务透明,外部服务的修改基本上不会影响到内部服务的访问,做到了内部服务和外部服务解耦合。
#使用Service反向代理外部域名
[root@k8s-master-1 svc-test]# cp nginx-svc-external.yaml nginx-externalName.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-externalname
labels:
app: nginx-externalname
spec:
type: ExternalName
externalName: www.baidu.com
[root@k8s-master-1 svc-test]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-externalname ExternalName <none> www.baidu.com <none> 15s
可以看到是没有ClusterIP的,反向代理到www.baidu.com
访问:
[root@k8s-master-1 svc-test]#
[root@k8s-master-1 svc-test]# kubectl exec -it busybox -- sh
/ # wget nginx-externalname
Connecting to nginx-externalname (124.237.176.3:80)
wget: server returned error: HTTP/1.1 403 Forbidden
#返回403报错 我们来解析下
/ # nslookup nginx-externalname
Server: 10.0.0.2
Address 1: 10.0.0.2 kube-dns.kube-system.svc.cluster.local
Name: nginx-externalname
Address 1: 124.237.176.3
Address 2: 124.237.176.4
/ #
直接返回给我们百度的IP地址,可以直接请求IP地址
/ # wget 124.237.176.4
LoadBalancer
在NodePort的基础上,借助cloud provider(云提供商)创建一个外部负载均衡器并将请求转发到NodePort,再次不做演示
六、Headless Service的概念和应用
它的概念就是没有入口访问地址(无ClusterIP 地址),无头Services 不会获得 Cluster IP,kube-proxy 不会处理这类服务, 而且平台也不会为它们提供负载均衡或路由。 DNS 如何实现自动配置,依赖于 Service 是否定义了Label Selector。
1、如果设置了Label Selector, kubernetes则会根据Label Selector查询后端的Pod列表,自动创建 ep ,将服务名的解析设置为:当客户端访问该服务时,得到的是全部ep列表
示例详情请查看:五、K8S-StatefulSet(STS有状态服务)-CSDN博客
[root@k8s-master mainfests]# vim stateful-demo.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx
2、如果没有 设置了Label Selector,控制平面不会创建 EndpointSlice 对象。
七、示例
定义一个service
创建一个deploy
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
selector:
matchLabels:
app: nginx
env: prod
replicas: 2
template:
metadata:
labels:
app: nginx
env: prod
spec:
containers:
- image: nginx:1.15.3
name: nginx
ports:
- containerPort: 80
name: nginx
#定义一个Service
[root@k8s-master-1 svc-test]# kubectl expose deployment nginx
或者yaml文件方式定义
[root@k8s-master-1 svc-test]# cat nginx-svc.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-svc
labels:
app: nginx-svc
spec:
ports: #定义service的关键字段
- name: http #Service 端口名称
port: 80 #Service自己的端口
protocol: TCP
targetPort: 80 #后端应用的端口,这两个端口可以不同
- name: https
port: 443
protocol: TCP
targetPort: 443
selector: #定义service的关键字段
env: prod
#查看svc
[root@k8s-master-1 svc-test]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.0.0.1 <none> 443/TCP 35d
nginx-svc ClusterIP 10.0.0.60 <none> 80/TCP,443/TCP 23s
[root@k8s-master-1 svc-test]#
#创建了Service会创建一个同名的EndPoint,他是通过pod的IP地址来刷新ep的。
[root@k8s-master-1 svc-test]# kubectl get ep
NAME ENDPOINTS AGE
kubernetes 192.168.134.135:6443 35d
nginx-svc 10.244.0.180:443,10.244.1.139:443,10.244.0.180:80 + 1 more... 29s
services的域名表示方法为:<servicename>.<namespaces>.svc.<clusterdomain>
servicename: 服务的名称
namespaces:命名空间
clusterdomain: 为kubernetes集群设置的域名的后缀
[root@k8s-master-1 svc-test]# kubectl exec -it busybox -- sh
/ # wget http://nginx-svc
/ # cat index.html
使用Service反向代理k8s集群之外的应用场景:
- 希望在生产环境中,使用某个固定的名称而非IP地址进行访问外部的中间件服务
- 希望Service指向另一个Namespaces中或其他集群中的服务
- 假如某个项目正在迁移k8s集群,但是一部分仍然在集群外部,此时可以使用service代理至k8s集群外部的服务
示例:
apiVersion: v1
kind: Service
metadata:
name: nginx-svc-external
labels:
app: nginx-svc-external
spec:
ports:
- name: http #Service 端口名称
port: 80 #Service自己的端口
protocol: TCP
targetPort: 80 #后端应用的端口,这两个端口可以不同
# selector:
# env: prod
sessionAffinity: None
type: ClusterIP
#创建Service
[root@k8s-master-1 svc-test]# kubectl apply -f nginx-svc-external.yaml
service/nginx-svc-external created
[root@k8s-master-1 svc-test]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.0.0.1 <none> 443/TCP 35d
nginx-svc ClusterIP 10.0.0.60 <none> 80/TCP,443/TCP 52m
nginx-svc-external ClusterIP 10.0.0.235 <none> 80/TCP 5s
[root@k8s-master-1 svc-test]#
不同之处:
就是没有selector ,不去匹配pod
之前创建完会创建一个同名的EndPoint,但是创建一个没有Selector的Service,就不会创建EP,那怎么连接到外部服务呢?只需要我们自己创建一个EndPoint,
导出之前的ep
[root@k8s-master-1 svc-test]# kubectl get ep nginx-svc -oyaml > nginx-ep-external.yaml
apiVersion: v1
kind: Endpoints
metadata:
labels:
app: nginx-svc-external #名称和协议都要与Servicce的名称一致,才能建立连接
name: nginx-svc-external
namespace: default
subsets:
- addresses:
- ip: 10.244.0.180 #写外部服务的IP地址
ports:
- name: http
port: 80 #外部服务的端口号
protocol: TCP
测试:
把外部的IP地址写成百度的地址:- ip: 39.156.66.10
创建ep,查看ep,
访问百度
[root@k8s-master-1 svc-test]# curl baidu.com -I
HTTP/1.1 200 OK
Date: Wed, 24 May 2023 08:40:34 GMT
Server: Apache
如果外部地址变更,可以使用replace来变更IP地址
[root@k8s-master-1 svc-test]# kubectl replace -f nginx-ep-external.yaml