1-Service

什么是Service

  • Kubernetes里的每个Service其实就是我们经常提起的微服务架构中的一个微服务
  • 因为pod是会随时重启和关闭,而且IP也会变,所以需要一种资源,让用户能使用统一的IP来实现服务,这就是service
  • SVC是一个Pod的逻辑分组,通过Label Selector,SVC将符合条件的Pod加入自己的调度队列中,当客户访问SVC时,SVC根据轮询算法来调度Pod并返回给客户.当有新Pod加入时,满足条件的也会被加入SVC

为什么要使用Service

k8s中,pod会随着业务的变更而变更(创建、销毁),其IP也会随之变化,所以需要一种资源,让用户能使用统一的IP来访问pod

Service通过Endpoints获取后端pod

  • endpoint是k8s集群中的一个资源对象,存储在etcd中,用来记录一个service对应的所有pod的IP地址。
  • 只有service配置了selector,endpoint controller才会自动创建对应的endpoint对象(与Servic同名);否则就不会创建endpoint对象。
  • 当Pod的地址发生变化时,EndPoints也随之变化。Service接收到请求时,就能通过EndPoints找到请求转发的目标地址。

获取资源Endpoints

kubectl get ep -o wide -A

创建endpoint

apiVersion: v1
kind: endpoints
metadata:
  name: external-service   # 名字要和service一致
subsets:
  - addersses:
    - ip: 11.11.11.11
    - ip: 22.22.22.22
    ports:
    - port: 80

命令行创建service

# 将rc暴露为service
kubectl expose rc nginx --port=80 --target-port=8000

单独ping Service的IP是不通的

  • Kubernetes Service 的 IP 地址并不是一个物理存在的网络接口,而是一个虚拟 IP。Service IP 地址的实现是通过 kube-proxy 创建的 iptables 规则(或其他模式)来实现流量转发的。当你访问 Service 的 IP 地址时,kube-proxy 会将请求转发到该 Service 选择的后端 Pod。
  • ping 命令使用的是 ICMP 协议,而 kube-proxy 通常只处理 TCP 和 UDP 流量。
  • Service 的 IP 地址是虚拟的,并不绑定到实际的网络接口上,所以没有一个实际的主机或设备可以响应 ping 请求。

service的负载均衡

Service 默认使用轮询算法将流量均匀地分配到后端的 Pod 上,不需要额外配置,Service 默认会进行负载均衡。这是通过 kube-proxy 实现的。在不同的代理模式(如 iptables、IPVS)下有不同的实现方式。

配置 kube-proxy 的 IPVS 模式和负载均衡算法

  • 可以直接通过命令行参数启动 kube-proxy,并指定相关选项
kube-proxy --proxy-mode=ipvs --ipvs-scheduler=rr --ipvs-min-sync-period=5s --ipvs-sync-period=30s

--proxy-mode=ipvs         // 设置kube-proxy的模式为IPVS
--ipvs-scheduler=rr       // 设置ipvs的负载均衡算法,默认是rr
--ipvs-min-sync-period=5s // 刷新IPVS规则的最小时间间隔
--ipvs-sync-period=30s    // 刷新IPVS规则的最大时间间隔

创建或编辑 kube-proxy 的配置文件

vim /var/lib/kube-proxy/config.yaml

apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
mode: "ipvs"
ipvs:
  scheduler: "rr"
  minSyncPeriod: "5s"
  syncPeriod: "30s"

更新 kube-proxy 配置

  • 确保 kube-proxy 以该配置文件启动。可以通过静态 Pod 定义或 kube-proxy 的启动参数指定配置文件路径
  • 我使用的是 kubeadm 部署的 Kubernetes 集群,以下是更新 kube-proxy ConfigMap 的步骤
# 编辑 kube-proxy 的 ConfigMap添加或更新如下配置
# 保存并退出编辑器后,kube-proxy 会自动加载新的配置
kubectl -n kube-system edit configmap kube-proxy

kind: KubeProxyConfiguration
apiVersion: kubeproxy.config.k8s.io/v1alpha1
mode: "ipvs"
ipvs:
  scheduler: "rr"
  minSyncPeriod: "5s"
  syncPeriod: "30s"

#检查 kube-proxy 配置
kubectl get configmap -n kube-system kube-proxy -o yaml

#查看 kube-proxy Pod 的日志,确认 IPVS 模式和调度算法
kubectl logs -n kube-system <kube-proxy-pod-name>

#在 kube-proxy 所在的节点上查看 IPVS 规则和调度算法
ipvsadm -Ln

service的会话亲和

会话亲和功能(Session Affinity)确保来自同一个客户端的所有请求被路由到同一个后端 Pod

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app: my-app
  ports:
    - protocol: TCP
      port: 80
      targetPort: 9376
  sessionAffinity: ClientIP  # 表示基于客户端 IP 进行会话亲和
  sessionAffinityConfig:
    clientIP:
      timeoutSeconds: 10800  # 超时时间,单位是秒,默认为 10800 秒(3 小时)

Service的类型

可以通过spec.type设定service的类型

  • ClusterIP:虚拟的服务IP地址,只能在Kubemetes集群内部对Pod进行访问,在Node上kube-proxy通过设置的iptables规则进行转发。
  • NodePort:使用宿主机的端口,使客户端可以通过各<节点 IP>:<节点端口>访问pod中的服务。
  • LoadBalancer:使用外接负载均衡器完成到服务的负载分发,需要在spec.status.loadBalancer字段指定外部负载均衡器的IP地址,并同时定义nodePort和clusterIP,用于公有云环境。
  • ExternalName:通过返回CNAME和对应值,可以将服务映射到externalName字段的内容(例如,foo.bar.example.com)。无需创建任何类型代理。(让pod访问k8s集群外部的服务)

9e9cb24785233db646944eb2a1d7784e.png

创建NodePort类型的Service

客户端可以通过各<节点 IP>:<节点端口>访问pod中的服务
可以修改API Server的--service-node-port-range=8000-9000的参数,修改默认NodePort的范围

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app: svc-nginx-pod
  type: NodePort
  clusterIP: 10.10.10.1 # 虚拟服务IP地址。如果不指定,则系统会自动分配
  ports:
    - port: 80          # service的端口
      targetPort: 8080  # pod的目标端口
      nodePort: 30123   # K8S节点的端口
  selector:
    app: myApp

targetPort和port通常设置成一样的
port为必填字段,如果没有设置targetPort,则targetPort和port相同
如果没有设置nodePort,K8s会分配一个端口号(默认:30000-32767)

创建ClusterIP类型的Service

  • ClusterIP只能在K8S集群内部对Pod进行访问
  • 在Service创建的请求中,可以通过设置spec.clusterIP字段来指定自己的集群IP地址。
  • 用户选择的IP地址必须合法,并且这个IP地址在service-cluster-ip-range CIDR范围内,这对API服务器来说是通过一个标识来指定的。如果IP地址不合法,API服务器会返回HTTP状态码422,表示值不合法。
apiVersion: v1
kind: Service
metadata:
  name: nginx-svc
  namesapce: default
  labels:
    app: nginx-svc
  sessionAffinity: ClientIP  # 这种方式会使服务代理将来自于同一个client IP的请求转发至同一个pod。非必选项
spec:
  type: ClusterIP            # 不指定type,默认是ClusterIP
  clusterIP: 10.10.10.1      # 虚拟服务IP地址。如果不指定,则系统会自动分配
  ports:
  - name: nginx
    port: 80
    targetPort: 8080
  selector:
    app: svc-nginx-pod       # 有该标签的pod都属于该服务

在Service中指定多个端口

标签选择器应用于整个service,不能对每个端口做单独配置,如果不同的pod有不同的端口映射关系,需要创建两个service

apiVersion: v1
kind: Service
metadata:
  name: kubia
spec:
  ports:
  - name: dns
    port: 53
    targetPort: 53
    protocol: UDP
  - name: https
    port: 443
    targetPort: 443
    protocol: TCP
  selector:
    app: kubia

将Service的targetPort指定为pod端口的name,即使pod更换端口也无需更改Service的配置
下面例子中,pod中的容器原端口为8080,如果要改为80,service中的tartgetPort无需改变

vim test-Service.yaml
apiVersion: v1
kind: Service
spec:
  ports:
  - name: http
    port: 80
    targetPort: pod-http-port
  - name: https
    port: 443
    targetPost: pod-https-port

vim test-Pod.yaml
apiVersion: v1
kind: Pod
spec:
  containers:
  - name: kubia
    ports:
    - name: pod-http-port
      containerPort: 8080
    - name: pod-https-port
      containerPort: 8443

创建LoadBalancer类型的Service

  • 这个Service会指向关联至Kubernetes集群外部的,切实存在的某个负载均衡器设备,该设备通过工作节点之上的NodePort向集群内部发送请求流量。
  • 这种类型的优势是它能够把来自集群外部客户端的请求调度至工作节点的NodePort之上,而不是依赖于客户端自行决定连接至哪个节点,从而避免了因客户端指定的节点故障而导致的服务不可用
  • LoadBalancer用于将外部流量负载均衡到集群内部的 Pod 上。当你将 Service 的类型设置为 LoadBalancer 时,Kubernetes 将尝试创建一个外部负载均衡器,并为其配置一个唯一的外部 IP 地址或 DNS 名称,以便外部客户端可以访问 Service。
apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  type: LoadBalancer
  loadBalancerSourceRanges:    # 指定IP地址范围的设备可以访问集群内的服务
  - 192.168.24.1/32
  - 192.168.24.5/32
  selector:
    app: my-app
  ports:
  - protocal: TCP
    port: 80
    targetPort: 8080

创建ExternalName 类型的Service

  • ExternalName 类型的Service不会创建任何负载均衡器、节点端口或集群 IP。相反,它允许你将 Service 映射到集群外部的域名,以便你的应用程序能够通过该域名访问外部服务。
  • 当你创建一个 ExternalName 类型的 Service 时,Kubernetes 会为该 Service 分配一个 DNS 条目,并将其指向你指定的外部域名。这样,当你的应用程序通过该 Service 的 DNS 名称访问时,Kubernetes 将自动将请求路由到指定的外部域名。

外部服务Service可以将Kubernetes外部的一个进程,或将另一个Kubernetes集群或Namespace中的pod作为它的端点,适用于以下情况:

  • 希望在生产环境中使用Kubernetes外部的数据库集群,但测试环境使用Kubernetes内部的数据库
  • 希望Service指向另一个名字空间(Namespace)中或其它Kubernetes集群中的pod。
  • 正在将工作负载迁移到Kubernetes。在评估该方法时,你仅在Kubernetes中运行一部分后端。

当你的应用程序通过 my-external-service 这个 Service 的 DNS 名称访问时,Kubernetes 将自动将请求路由到 example.com 这个外部域名

apiVersion: v1
kind: Service
metadata:
  name: my-external-service
spec:
  type: ExternalName
  externalName: example.com  # 集群外部的应用

ExternalName

  • 用来定义集群外部资源,把外部资源划入service的管辖范围内。
  • 把集群内部和外部的访问名称做映射。就是用KubeDNS将该service和ExternalName做一个Map,KubeDNS返回一个CNAME记录
  • cname的概念,这个不是k8s里的,与cname相关的还有A记录,就是通过域名来解析出它对应的ip,cname就是把这个域名起个别名,通过别名也能解析出对应的ip,这个在CDN中用的到

创建ExternalName 类型的Service示例

实现外部服务Service只需要不配置标签选择器即可(将service.spec.selector设置为空,或者不配置)
可以在本地进行测试:
(1)创建一个不带标签选择器的Service,该Service不会选择后端的Pod,系统也不会自动创建Endpoint。
(2)需要手动创建一个和该Service同名的Endpoint,用于指向实际的后端访问地址。

# 创建外部服务
yum install nginx
systemctl start nginx
echo '<h1>nginx 10.1.1.11</h1>' > /usr/share/nginx/html/index.html


# 创建Service和Endpoints
* 如果在ServicePort中定义了“name”字段,则EndpointPort中的“name”字段的值必须和它一样,否则将不能正常访问服务。
vim ep-nginx-svc.yaml
apiVersion: v1
kind: Service
metadata:
  name: ep-nginx-svc    #Service和Endpoint的名字要相同
spec:
  ports:
  - port: 18888
    targetPort: 80
    name: nginx
---
apiVersion: v1
kind: Endpoints
metadata:
  name: ep-nginx-svc    #Service和Endpoint的名字要相同
subsets:
- addresses:
  - ip: 10.1.1.11       #外部服务所在机器的IP地址
  ports:
  - port: 80            #外部服务监听的端口
    name: nginx

# 应用yaml文件并查看svc
kubectl apply -f ep-nginx-svc.yaml
kubectl get svc -o wide -A

# 查看svc可以确定svc的地址后endpoint的地址
kubectl describe svc ep-nginx-svc

# 查看endpoint
kubectl get ep -o wide -A

# 验证,访问svc_ip:svc_port。会请求到endpoint
curl 10.20.199.164:18888
<h1>nginx 10.1.1.11</h1>

service的限制

servic默认只有4层负载均衡能力(即ip+端口),可以通过添加ingress来实现7层负载均衡(即域名或主机名)

StatefulSet 的使用:
Headless Service 通常与 StatefulSet 一起使用。StatefulSet 需要为每个 Pod 分配一个稳定的网络标识,并且这些 Pod 需要能够通过 DNS 进行互相通信。Headless Service 能够满足这些需求,因为它不会分配一个单一的 Cluster IP,而是为每个 Pod 创建一个独立的 DNS 记录。例如,在一个 ZooKeeper、Kafka、Cassandra 或者其他有状态服务的集群中,每个 Pod 都需要一个稳定的网络标识,这时就需要使用 Headless Service。

headless service(无头服务)概述

  • 它没有Cluster IP,如果解析Headless Service的DNS域名,则返回的是该Service对应的全部Pod的Endpoint列表。
  • 无头服务不会分配Cluster IP,kube-proxy不会处理它们,而且平台也不会为它们进行负载均衡和路由。DNS如何实现自动配置,依赖于Service是否定义了标签选择器。

应用

  • Headless Service 可以用于服务发现

当服务客户端需要知道所有可用的后端 Pod 的 IP 地址时,客户端可以通过查询 DNS 记录来获取所有后端 Pod 的 IP 地址列表。例如,在一个分布式系统中,各个服务实例需要相互发现和通信,可以使用 Headless Service 来实现。

  • Headless Service 可以用于自定义负载均衡

在某些情况下,您可能希望绕过 Kubernetes 的内置负载均衡机制,直接对 Pod 进行负载均衡。通过 Headless Service,客户端可以直接获取后端 Pod 的 IP 地址列表,从而实现自定义的负载均衡策略。

Headless Service与StatefulSet

  • StatefulSet就是使用Headless Service为客户端返回多个服务地址的。
  • StatefulSet在Headless Service的基础上又为StatefulSet控制的每个Pod实例都创建了一个DNS域名, 这个域名的格式为:(podname).(headless service name)
  • 比如一个3节点的Kafka的StatefulSet集群对应的Headless Service的名称为kafka, StatefulSet的名称为kafka, 则StatefulSet里的3个Pod的DNS名称分别为kafka-0.kafka、 kafka-1.kafka、 kafka-3.kafka, 这些DNS名称可以直接在集群的配置文件中固定下来

实现Headless Service

  • 将 spec.clusterIP 的值设置为"None"。即不为Service设置ClusterIP(入口IP地址),仅通过Label Selector将后端的Pod列表返回给调用的客户端
# 1. 使用 kubectl expose 命令创建
# 假设已经有一个名为 myapp 的 Deployment,--cluster-ip=None 表示这是一个 Headless Service
kubectl expose deployment myapp --name=myapp-headless --port=80 --cluster-ip=None

# 2. 使用 kubectl apply 命令创建
vim myapp-headless.yaml

apiVersion: v1
kind: Service
metadata:
  name: myapp-headless
spec:
  clusterIP: None
  selector:
    app: myapp
  ports:
  - port: 80
    targetPort: 80

kubectl apply -f myapp-headless.yaml

# 3. 使用 kubectl create 命令创建
kubectl create service clusterip myapp-headless --tcp=80:80 --cluster-ip=None

headless没有IP,通过域名访问,在结果中可以看到域名对应的后端IP

yum -y install bind-utils
dig -t A myapp-headless.default.svc.cluster.local. @10.244.0.3

# myapp-headless.default.svc.cluster.local
# myapp-headless       svc名称
# default              当前命名空间
# svc.cluster.local    集群域名(默认)
# 10.96.0.10           coreDNS的地址

kubectl get pod -n kube-system -o wide  # 可以查看coreDNS的域名

Service.Spec.externalTrafficPolicy

它是 Kubernetes 中 Service 资源的一个字段,这个字段主要用于控制从外部进入集群的流量在内部的路由方式,它会影响流量到达 Service 的 Pod 的路径,并有可能影响服务的性能和客户端的 IP 地址可见性。它有两个可选值:

Cluster(默认值)
当 externalTrafficPolicy 设置为 Cluster 时:

  • 外部流量会先到达某个 Node,然后由该 Node 的 kube-proxy 进行负载均衡,转发到集群内的任何一个 Service 后端 Pod。
  • 这种模式下,流量可以在多个节点之间分发,从而实现更好的负载均衡和资源利用。
  • 但是,客户端的原始 IP 地址在这种模式下不会保留,Pod 看到的源 IP 地址将是 kube-proxy 的 IP 地址。

Local
当 externalTrafficPolicy 设置为 Local 时:

  • 外部流量只会被转发到当前 Node 上的 Service 后端 Pod(如果有的话),不会在其他 Node 之间进行转发。
  • 这种模式下,客户端的原始 IP 地址能够保留,Pod 可以看到实际的客户端源 IP 地址。
  • 但是,如果某个 Node 上没有 Service 后端 Pod,这个 Node 将不会接受外部流量,这可能导致不均衡的流量分配。

K8S外部如何访问集群内的服务(pod、service)

由于Pod和Service都是Kubernetes集群范围内的虚拟概念,所以集群外的客户端系统无法通过Pod的IP地址或者Service的虚拟IP地址和虚拟端口号访问它们。通常可以通过以下方式进行访问Kubernetes集群内的服务:
1、将Pod或Service的端口映射到物理机:
    ·在容器端口配置中设置hostPort
    ·或者在Pod配置中设置hostNetwork: true
2、将Service端口映射到物理机:
    ·将Service配置为nodePort方式
    ·将Service配置为loadBalancet方式

实现从集群外部访问Pod或Service

方法一、将容器的端口映射到物理机

方式1 设置容器级别的hostPort

  • 通过设置容器级别的hostPort,将容器应用的端口号映射到物理机上。
  • 只能通过pod所在的宿主机物理地址IP访问。
···
      containers:
      - name: network-nginx-container
        image: nginx:latest
        imagePullPolicy: Never
        command: ["/usr/sbin/nginx", "-g", "daemon off;"]
        ports:
        - containerPort: 80
          hostPort: 18081    #指定hostPort
        volumeMounts:
        - mountPath: /usr/share/nginx/html
          name: html

方式2、设置Pod级别的hostNetwork

  • 通过设置Pod级别的hostNetwork=true,该Pod中所有容器的端口号都将被直接映射到物理机上。
  • 只能通过pod所在的宿主机物理地址IP访问。
  • 在设置hostNetwork=true时需要注意:
        1.在容器的ports定义部分,如果不指定hostPort,则默认hostPort等于containerPort。
        2.在容器的ports定义部分,如果指定了hostPort,则hostPort必须等于containerPort的值。
···
      containers:
      - name: network-nginx-container
        image: nginx:latest
        imagePullPolicy: Never
        command: ["/usr/sbin/nginx", "-g", "daemon off;"]
        ports:
        - containerPort: 80    #!
          hostPort: 80         #!
        volumeMounts:
        - mountPath: /usr/share/nginx/html
          name: html
      hostNetwork: true        #!

方法二、将Service的端口号映射到物理机

方式1 设置Service的类型为NodePort

  • 通过设置nodePort映射到物理机,同时设置Service的类型为NodePort。

方式2 设置Service的类型为LoadBalancer

  • 通过设置LoadBalancer映射到云服务商提供的LoadBalancer地址。这种用法仅用于在公有云服务提供商的云平台上设置Service的场景。

什么是服务暴露

Service的IP地址仅在集群内部可达,需要将服务暴露到外部网络中接收各类客户端的访问,这种操作也称为发布服务到外部网络中或服务暴露

服务暴露的三种方法

nodeport类型的service
loadbalance类型的service
ingress资源

1813a66403c4ed277638561186bf82f0.png
1)apiserver通过kube-proxy监听服务和endpoint(Endpoint是用户订阅主题时,指定接收消息的终端地址)
2)kube-proxy负责监控匹配的pod的信息,判断pod的label是否,并写入到iptables的规则中
3)当客户端访问svc时,会根据iptables中的规则来导向至后端的pod的地址

什么是服务发现

Pod客户端中的应用得知某个特定Service资源的IP和端口的过程称为服务发现

客户端的pod怎么知道service的端口和IP?

通过环境变量
如果服务早于pod的创建,pod上的进程可以根据环境变量获得服务的IP和端口
如果服务晚于pod的创建,可以删除现有pod,RC创建出新pod后会记录在环境变量中
一般变量名的形式为:{SVCNAME}_SERVICE_HOST、{SVCNAME}_SERVICE_PORT
局限性在于,必须在同一名称空间,Pod在Service之后才创建

通过DNS
ClusterDNS是Kubernetes系统之上用于名称解析和服务发现的核心组件
pod是否使用内部的DNS服务器由pod.spec的dnsPolicy属性决定
默认情况下,集群内各Pod资源会配置其作为名称解析服务器,并在其DNS搜索列表中包含它所属名称空间的域名后缀
每个Service对象相关的DNS记录包含两个:
    {SVCNAME}.{NAMESPACE}.{CLUSTER_DOMAIN}
    {SVCNAME}.{NAMESPACE}.svc.{CLUSTER_DOMAIN}

posted @   立勋  阅读(26)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· Vue3状态管理终极指南:Pinia保姆级教程
点击右上角即可分享
微信分享提示