【3】K8S核心对象之Service
Service介绍
Service为Pod提供了网络访问、负载均衡以及服务发现等功能。
Service 位于 pod 的前面, 负责接收请求并将它们传递给它后面的所有pod。一旦服务中的 Pod 集合发生更改,Endpoints 就会被更新,请求的重定向自然也会导向最新的 pod。
不同类型的Service
- ClusterIP
- 原理:使用这种方式发 布时,会为Service提供一个固定的集群内部虚拟IP,供集群内(包含节点)访问。
- 场景:内部数据库服务、内部API服务等。
- NodePort
- 原理:通过每个节点上的 IP 和静态端口发布服务。 这是一种基于ClusterIP的发布方式,因为它应用后首先会生成一个集群内部IP, 然后再将其绑定到节点的IP和端口,这样就可以在集群外通过
nodeIp:port
的方式访问服务。 - 场景:Web应用程序、REST API等。
- 原理:通过每个节点上的 IP 和静态端口发布服务。 这是一种基于ClusterIP的发布方式,因为它应用后首先会生成一个集群内部IP, 然后再将其绑定到节点的IP和端口,这样就可以在集群外通过
- LoadBalancer
- 原理:这种方式又基于 NodePort,另外还会使用到外部由云厂商提供的负载均衡器。由后者向外发布 Service。 一般在使用云平台提供的Kubernetes集群时,会用到这种方式。
- 场景:Web应用程序、公开的API服务等。
- Headless
- 原理:这种方式不会分配任何集群IP,也不会通过Kube-proxy进行反向代理和负载均衡,而是通过DNS提供稳定的网络ID来访问, 并且DNS会将无头Service的后端解析为Pod的后端IP列表,以供集群内访问(不含节点),属于向内发布。
- 场景:一般提供给StatefulSet使用。
- ExternalName
- 原理:与上面提到的发布方式不太相同,这种方式是通过CNAME机制将外部服务引入集群内部,为集群内提供服务,属于向内发布。
- 场景:连接到外部数据库服务、外部认证服务等。
ClusterIP
ClusterIP在集群内部分配一个固定的、具有负载均衡的ip指向Pod服务,集群内部可以通过这个固定的ip地址访问pod服务。
ClusterIP通过分配集群内部IP来在集群内(包含节点)暴露服务,可以在集群内通过clusterIP:port
访问到pod服务,集群外则无法访问。
这种方式适用于那些不需要对外暴露的服务,如节点守护agent等。
# 部署deployment
kubectl apply -f deployment-go-http.yaml
# 修改镜像
kubectl set image deployment/hellok8s-go-http hellok8s=leigg/hellok8s:v3_hostname
# 等待pod更新
kubectl get pods --watch
更新成功后,编写Service配置文件service-clusterip.yaml
# service-clusterip.yaml
apiVersion: v1
kind: Service
metadata:
name: service-hellok8s-clusterip
spec:
type: ClusterIP # 这行是默认的,可省略
# sessionAffinity: ClientIP # or None, 设置会话亲和性(ClientIP表示同一客户端ip的请求会路由到同个Pod)
# sessionAffinityConfig:
# clientIP:
# timeoutSeconds: 3600 # 范围 0~86400,默认10800(3h)
selector:
app: hellok8s # 通过selector关联pod组
ports:
- port: 3000 # service端口
targetPort: 3000 # 后端pod端口
Session affinity
的效果仅会在一定时间期限内生效,默认值为10800秒,超出此时长之后,客户端的再次访问会被调度算法重新调度。另外,Service资源的Session affinity
机制仅能基于客户端IP地址识别客户端身份,它会把经由同一个NAT服务器进行源地址转换的所有客户端识别为同一个客户端,调度粒度粗糙且效果不佳,因此,实践中并不推荐使用此种方法实现会话粘性。
# 应用配置文件
kubectl apply -f service-clusterip.yaml
# 查看服务
> kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.43.0.1 <none> 443/TCP 5d23h
service-hellok8s-clusterip ClusterIP 10.43.25.35 <none> 3000/TCP 30m
# 查看Service后端的逻辑Pod组的信息
> kubectl get endpoints
NAME ENDPOINTS AGE
kubernetes 10.20.30.5:6443 5d23h
service-hellok8s-clusterip 10.42.0.33:3000,10.42.1.54:3000,10.42.1.55:3000 31m
# 创建出来的ClusterIP 10.43.25.35,在master,nodes节点上都能访问,集群外访问不到
多次访问http://10.43.25.35:3000
会发现hostname变化,说明service进行了负载均衡
> curl http://10.43.25.35:3000
[v3] Hello, Kubernetes!, From host: hellok8s-go-http-7c57cd6487-rkpxp
> curl http://10.43.25.35:3000
[v3] Hello, Kubernetes!, From host: hellok8s-go-http-7c57cd6487-sw6sk
NodePort
NodePort在ClusterIP上进行扩展,在集群中的每个节点上都开放了供集群外部访问的端口nodePort, 集群外部可以通过nodePort访问集群内部的pod服务。
ClusterIP只能在集群内访问Pod服务,而NodePort则进一步将服务暴露到集群节点的静态端口上。
比如k8s集群有2个节点:node1和node2,暴露后就可以通过 node1-ip:port
或 node2-ip:port
的方式来稳定访问Pod服务。
# service-nodeport.yaml
apiVersion: v1
kind: Service
metadata:
name: service-hellok8s-nodeport
spec:
type: NodePort
selector:
app: hellok8s
ports:
- port: 3000 # Service端口
protocol: TCP # 协议
targetPort: 3000 # 容器端口
nodePort: 30000 # 节点固定端口。在NodePort类型中,k8s要求在 30000-32767 范围内,否则apply报错
# 若需要暴露多个端口,则按下面形式
# - name: http
# protocol: TCP
# port: 80
# targetPort: 9376
# - name: https
# protocol: TCP
# port: 443
# targetPort: 9377
# 应用service
kubectl apply -f service-nodeport.yaml
# 查看service
> kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.43.0.1 <none> 443/TCP 37m
service-hellok8s-nodeport NodePort 10.43.128.116 <none> 3000:30000/TCP 4s
- 在master、node节点可以通过
curl http://10.43.128.116:3000
访问服务。 - 在集群内外使用curl或在局域网使用浏览器都可以通过
http://10.20.30.8:30000/
或http://10.20.30.5:30000/
访问服务
LoadBalancer
LoadBalancer在NodePort上进行扩展,支持使用云提供商的负载均衡器向外暴露服务,在不支持LoadBalance Service的环境中运行类似NodePort Service。
LoadBalancer 是通过使用云提供商的负载均衡器(一般叫做SLB,Service LoadBalancer)的方式向外暴露服务。
负载 均衡器可以将集群外的流量转发到集群内的节点,后者再转发到Pod, 假如你在 AWS 的 EKS 集群上创建一个 Type 为 LoadBalancer 的 Service。它会自动创建一个 ELB (Elastic Load Balancer) ,并可以根据配置的 IP 池中自动分配一个独立的 IP 地址,可以供外部访问。
在大多数K8s云提供商的环境中,K8s集群可以通过负载均衡器使得LoadBalance Service拥有一个公网IP。以便真正地对外暴露服务。但如果K8s在不支持LoadBalance Service的环境中运行,则不会调配负载均衡器。此时该服务将会表现的像是一个NodePort Service,因为LoadBalance Service是对NodePort Service的拓展。
使用 LoadBalancer 创建服务的步骤:
- 创建一个包含Pod的Deployment。
- 创建一个 Service,其中指定了上述 Deployment 中定义的 Pod 的 selector。
- 在 Service yaml 文件中指定
type: LoadBalancer
。 - 应用 Service 文件并等待 Kubernetes 和云服务提供商之间的交互完成。
- 查看 External IP,它是分配给 Kubernetes 中新创建的 LoadBalancer 的 IP 地址,该地址可以用于访问您的应用程序。
下面是loadbalance service的示例,创建私网SLB,基于K3S的Klipper Load Balancer
# hellok8s-loadbalance-service.yaml
apiVersion: v1
# 资源类型
kind: Service
metadata:
# 资源名称
name: hellok8s-loadbalance-service
spec:
# Service类型
type: LoadBalancer
selector:
app: hellok8s
ports:
- port: 997 # 服务监听端口
targetPort: 3000 # 服务将请求转发到Pod的目标端口
# 在集群各节点所打开的端口, 使得可以通过集群中任一节点IP、nodePort端口号访问该服务
nodePort: 32329
> kubectl get svc -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
hellok8s-loadbalance-service LoadBalancer 10.43.156.107 10.20.30.5,10.20.30.8 997:32329/TCP 104s app=hellok8s
可以看到Service服务的EXTERNAL-IP列显示已经被分配了两个节点的IP,接下来在master节点上做测试
curl http://10.43.156.107:32329 X 无法访问
curl http://10.43.156.107:997 OK 访问正常
curl http://10.20.30.5:997 OK 访问正常
curl http://10.20.30.8:997 OK 访问正常
curl http://10.20.30.5:32329 OK 访问正常
curl http://10.20.30.8:32329 OK 访问正常
Headless
headless服务其实是分配一个固定的DNS域名来访问Pod服务,为pod提供了一个固定的地址,不需要生成ClusterIP和为其做负载均衡。此dns只有在集群内部的pod访问才能访问,集群内节点机器、集群外都不能访问。
创建service文件
# service-clusterip-headless.yaml
apiVersion: v1
kind: Service
metadata:
name: service-hellok8s-clusterip-headless
spec:
type: ClusterIP # 这行是默认的,可省略
# sessionAffinity: ClientIP # or None, 设置会话亲和性(ClientIP表示同一客户端ip的请求会路由到同个Pod)
# sessionAffinityConfig:
# clientIP:
# timeoutSeconds: 3600 # 范围 0~86400,默认10800(3h)
clusterIP: None # None 表示不分配集群IP
selector:
app: hellok8s # 通过selector 选择映射的pod
ports:
- port: 3000 # service端口
targetPort: 3000 # 后端pod端口
创建pod文件(包含curl和nslookup命令),用于测试访问定义好的service
# pod_curl.yaml
apiVersion: v1
kind: Pod
metadata:
name: curl
labels:
app: curl
annotations:
key1: "value1"
description: The `curl` command is a powerful tool used to make HTTP requests from the command line.
It is versatile and supports various protocols, including HTTP, HTTPS, FTP, FTPS, and more.
spec:
containers:
- name: curl-container
image: appropriate/curl
command: [ "sh","-c", "sleep 1h" ]
# 应用配置文件
kubectl apply -f service-clusterip-headless.yaml
# 应用pod文件
kubectl apply -f pod_curl.yaml
# 查询service
> kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.43.0.1 <none> 443/TCP 22h
service-hellok8s-clusterip-headless ClusterIP None <none> 3000/TCP 3s
# 进入curl pod容器,测试通过headless service dns访问对应的pod
> kubectl exec -it curl -- /bin/sh
/ # curl service-hellok8s-clusterip-headless.default.svc.cluster.local:3000
[v3] Hello, Kubernetes!, From host: hellok8s-go-http-7c57cd6487-dx6t4
/ # curl service-hellok8s-clusterip-headless.default.svc.cluster.local:3000
[v3] Hello, Kubernetes!, From host: hellok8s-go-http-7c57cd6487-6rd7q
/ # nslookup service-hellok8s-clusterip-headless.default.svc.cluster.local
nslookup: can't resolve '(null)': Name does not resolve
Name: service-hellok8s-clusterip-headless.default.svc.cluster.local
Address 1: 10.42.1.9 10-42-1-9.service-hellok8s-clusterip-headless.default.svc.cluster.local
Address 2: 10.42.1.10 10-42-1-10.service-hellok8s-clusterip-headless.default.svc.cluster.local
Address 3: 10.42.0.12 10-42-0-12.service-hellok8s-clusterip-headless.default.svc.cluster.local