六、k8s四层负载均衡Service

WARNING

ClusterIP:默认类型、仅限集群内部(pod或者Service间通信)、外部无法访问

实现机制

  • 分配一个稳定的虚拟 IP(ClusterIP),通过 kube-proxy 维护的 iptables/IPVS 规则实现内部负载均衡。
  • 自动关联标签匹配的 Pod,并通过 Endpoints 动态维护后端 Pod IP 列表

适用场景

  • 微服务架构中的内部通信(如前端调用后端 API)。
  • 数据库、缓存等仅需集群内访问的服务。

NodePort:通过 节点 IP + 静态端口(30000-32767) 暴露服务,允许 集群外部访问

实现机制

  • 在 ClusterIP 基础上,在每个节点上绑定一个固定端口(可手动指定或自动分配)。
  • 外部流量通过 <NodeIP>:<NodePort> 进入,转发到 Service 的 ClusterIP 和端口

适用场景

  • 发/测试环境中的外部访问(如本地调试 Web 应用)。
  • 小规模集群的临时暴露服务(非生产环境)。

ExternalName:将集群内部请求映射到外部 DNS 名称(如云数据库、第三方 API),不代理任何 Pod

实现机制:

  • 使用 DNS CNAME 记录,将 Service 名称解析为外部域名(如 mysql.external.com)。
  • 无 ClusterIP 或端口映射,仅提供 DNS 别名功能。

适用场景:

  • 集群内访问外部服务(如公有云数据库、支付网关)。
  • 隐藏外部服务细节,简化内部调用逻辑

1.service的作用

虽然k8s内部网络中的pod使用ip地址可以直接访问、但是它们的ip地址通常只是临时的、会随着pod的重启和重新调度变化。Server用于将一组 Pod 暴露为统一的访问入口,提供 服务发现负载均衡 功能。它通过虚拟 IP(ClusterIP)和端口对外提供服务,屏蔽 Pod 的动态变化。 在实际的应用场景中,往往会结合Ingress以实现更复杂的代理和负载 均衡需求。

主要功能

  1. 提供固定的IP和DNS名称。当你创建一个Service后,它会分配一个稳 定的虚拟IP地址,可以通过这个地址和Service名称访问服务,无需关心后台 Pod 的IP。
  2. 负载均衡。Service可以让请求分布到多个Pod上,从而实现负载均衡。 例如,当有多个后台Pod时,Service可以基于预定义的负载均衡算法将请求分 发到这些Pod中。
  3. 服务发现。Service通过匹配Label Selector,可以将一组后台Pod当 成一个Service进行管理,这样我们就可以方便地对这些Pod进行管理、监控及 网络访问控制等操作

2.k8s集群中的三类ip地址

1.Node Network(节点网络)

** **物理节点或者虚拟节点的网络,如ens33接口上的网络地址。

ip addr

2. Pod Network(pod网络)

创建的pod具有的ip地址

kubectl get pods -o wide

**3. ClusterNetwork(集群地址,也称为servicenetwork) **

这个地址是虚拟的地址(virtualip),没有配置在某个接口上,只是出现 在service的规则当中。

kubectl get svc kubernetes

3.service的配置

属性名称取值类型说明
apiVersionstringApi版本
kindstring资源类型
metadataobject元数据
metadata.namestring控制器的名称
metadata.namespacestring控制器所属的命名空间,默认值为default
metadata.labels[]List自定义标签列表
metadata.annotation[]List自定义注解列表
specobject规范Service所需行为的规范
spec.clusterIPstring表示分配给该Service的集群内部的IP地址。这个IP 地址可以用来访问该Service提供的服务。
clusterIP可以被配置为一个特定的IP地址,也可以让 Kubernetes自动分配一个可用的IP地址。如果配置为 一个特定的IP地址,需要保证该IP地址未被其他对象 使用,并且在同一子网(subnet)内。
例如,如果设置clusterIP为10.0.0.100,则所有访问 该Service的请求都将被路由到该IP地址。
spec.ports[]object用于服务监听的端口号
spec.ports.appProtocolstring用于指定服务端口上使用的应用层协议。例如HTTP或 TLS。
示例: http服务监听了80端口,targetPort为8080,protocol 为TCP,此外还指定了appProtocol为http。在使用这 种配置时,负载均衡器将根据应用层协议名http来路 由到后端上。
spec:
ports:
-name: http
port: 80
protocol: TCP
targetPort: 8080
appProtocol: http
spec.ports.namestring这个名称并不是必须的,但为了方便管理和使用,通常 会给服务的每个端口指定一个名称。
spec.ports.nodePortintrger用于指定NodePort模式下服务的节点端口号,在 NodePort模式下,Kubernetes会为每个节点分配一个 端口号,将访问此端口的流量转发到Service的端口上。 使用nodePort属性可以指定节点端口号。
示例: Service对象监听了80端口,NodePort模式下使用的 节点端口号为30080。当Kubernetes集群中的任何节点 上的流量通过该端口30080访问该Service时,流量将 被转发到Service的80端口上,然后service会转发 到targetPort上(也就是pod端口上)。
spec:
type: NodePort
ports:
-name: http
port: 80
targetPort: 80
nodePort: 30080
spec.ports.portinteger用于指定服务监听的端口号。
示例: Service对象监听了80端口,protocol为TCP, targetPort为8080。所有发送到80端口的流量都将被 转发到该targetPort上。
spec:
ports:
-name: http
port: 80
protocol: TCP
targetPort: 8080
spec.ports.protocolstring标识服务端口监听的协议类型。 Kubernetes中支持TCP、UDP、SCTP等多种协议类型。 在Service对象中,使用protocol属性指定服务端口 监听的协议类型。默认协议类型为TCP。
spec.ports.targetPortstring用于指定服务的后端容器监听的端口号。 在Kubernetes中,Service对象通常用于代理到后端的 Pod。targetPort属性用于指定后端Pod监听的端口号。 当流量进入该端口时,Service会将其转发到相应的Pod 上。
spec.selectormap[string]string指定当前服务关联的Pod Selector。 PodSelector用于将服务和后端Pod进行关联。当创建 服务时,可以使用selector属性指定服务需要关联哪 些Pod。Kubernetes将会自动为匹配Selector的Pods, 提供网络地址和DNS记录。
spec.typestring指定本地或集群内服务的访问方式。
Service对象可以以三种不同的类型来暴露服务: ClusterIP、NodePort和LoadBalancer。在创建Service 对象时,需要通过指定type属性,来选择合适的访问 方式。
● ClusterIP:默认类型。当指定该类型时, Kubernetes会为该服务创建一个虚拟的IP地址,仅能 从集群内部访问该服务。
● NodePort:当指定该类型时,Kubernetes会为该服 务在每个节点上暴露一个端口号。通过该端口号,可以 从外部访问该服务。同时也可以从集群内部访问。
● LoadBalancer:该类型可用于将容器服务暴露到云 平台的负载均衡器中。在使用该类型时,可以通过云平 台的负载均衡器,将服务暴露到集群外部并允许外部IP 地址的访问
spec.sessionAffinitystring指定服务负载均衡算法。
当创建Service对象时,可以通过指定sessionAffinity 属性,来选择合适保持客户端对同一后端Pod之间会话 的策略,以实现一些业务需求。Kubernetes支持以下两 种负载均衡策略。
● None:默认值。这意味着在多个Pod之间使用的是 基于IP的负载均衡。您可以使用此策略将请求随机分 发到匹配选择器的Pod上,且不受请求者的来源IP影 响。
● ClientIP:这是另外一种Affinity策略,它根据 请求的源IP地址将流量转发到具有相同源IP地址的后 端Pod。 示例: Service对象负责将流量转发到与app=myapp的Pod, 服务没有IP地址,因为它被设置为一个clusterIP=None 集群IPService。服务负载均衡使用的是ClientIP的 Affinity策略。
spec:
clusterIP: None
port:
-name: web
port: 8080
targetPort: 80
selector:
app: myapp
sessionAffinity: ClientIP
spec.sessionAffinityCo nfigobject用于指定高级会话亲和性配置。 默认情况下,Kubernetes通过源IP地址来实现会话亲 和性。但是,在某些情况下,源IP地址可能会发生变 化,从而导致业务系统的不兼容性。为了解决这个问题, Kubernetes提供了sessionAffinityConfig属性。使用 该属性可以让用户配置其他的、更复杂的会话亲和性算 法
spec.sessionAffinityCo nfig.clientIP.timeoutS econdsintrge设置会话超时时间。默认10800秒(三小时)

实战:创建两个普通的pod

//编写yaml文件
vi pod_test.yaml


apiVersion: apps/v1          # 使用的 API 版本
kind: Deployment             # 资源类型为 Deployment
metadata:
  name: my-nginx             # Deployment 名称
spec:
  selector:
    matchLabels:
      run: my-nginx          # 选择器标签,用于匹配 Pod
  replicas: 2                # 副本数量
  template:
    metadata:
      labels:
        run: my-nginx        # Pod 标签,需与 selector 匹配
    spec:
      containers:
      - name: my-nginx       # 容器名称
        image: nginx         # 使用的镜像
        imagePullPolicy: IfNotPresent  # 镜像拉取策略
        ports:
        - containerPort: 80  # 容器暴露的端口

//更新资源清单文件
kubectl apply -f pod_test.yaml

//查看pod的ip地址
kubectl get pods -o wide

//请求pod ip地址、查看结果(注意替换地址)
curl 10.244.85.197:80
curl 10.244.58.199:80

//需要注意的是,pod虽然定义了容器端口,但是不会使用调度到该节点上的
80端口,也不会使用任何特定的NAT规则去路由流量到Pod上。这意味着可以
在同一个节点上运行多个Pod,使用相同的容器端口,并且可以从集群中任何其
他的Pod或节点上使用IP的方式访问到它们。

//删除其中一个pod、查看pod标签(注意替换地址)
kubectl delete pods my-nginx-5f7668c4b-72hj9

//查看pod、发现重新生成了一个pod、ip地址发生了变化
kubectl get pods -o wide

实战一:创建type类型为ClusterIp的Service

目标:使用Deployment控制器创建2个副本,创建service类型为ClusterIP 来代理Deployment控制器所创建的副本。

//查看pod标签
kubectl get pods --show-labels

//创建service资源清单文件
vi service_ClusterIP.yaml

apiVersion: v1               # Kubernetes API 版本
kind: Service                # 资源类型为 Service
metadata:
  name: my-nginx             # Service 名称
  labels:
    run: my-nginx            # Service 标签(用于分类)
spec:
  type: ClusterIP            # Service 类型(默认集群内访问)
  ports:
  - name: tcp-80             # 端口名称(便于识别)
    port: 80                 # Service 对外暴露的端口
    protocol: TCP            # 使用的协议(TCP/UDP/SCTP)
    targetPort: 80           # 容器内实际监听的端口
    appProtocol: http        # 应用层协议(供 LB 识别)
  selector:
    run: my-nginx            # 选择器标签(匹配后端 Pod)

//创建service资源
kubectl apply -f service_ClusterIP.yaml

1.用于创建名为my-nginx的Service,该Service用于将外部流量引入到名为my-nginx
的Deployment中。
2.在配置文件中,我们定义了Service的metadata和spec部分。metadata部分定义了
Service的名称和label“run:my-nginx”。
3.在spec部分,我们定义了Service的类型为ClusterIP,表示只能从Kubernetes集群
内部访问该Service。我们还定义了端口,将Service的80端口绑定到目标容器的80端口,
同时指定appProtocol为http。
4.最后,我们定义了selector,来指定该Service映射到哪些Pod上。在这个例子中,

//查看创建的service资源、Cluster-IP 就是我们生成的service的虚拟ip
kubectl get svc my-nginx

//测试访问、在k8s任意节点访问service的IP:端口就可以把请求代理到后端pod
得出结论:clister-ip+port  == pod-ip+port
curl 10.10.178.9:80

扩展1:访问10.10.178.9:80是如何将流量转发给后端pod的

//查看描述
kubectl describe svc my-nginx

//查看endpoints信息
 kubectl get endpoints my-nginx

 //查看ipvs80端口
ipvsadm -ln | grep 80

当我们创建一个K8s service后、service会自动创建一个虚拟IP、在serrvice的spec中、我们使用selector对象来指定哪些pod是这个service的后端、k8s就会为service自动创建一个相关的Endpoints对象、用来记录这个service管理pod的ip地址和端口号信息

当客户端发起请求时、请求先到达k8s集群节点上的kube-proxy组件、kube-porxy组件会根据service监听的端口号和协议、选择对应的后端Endpoints地址和端口、然后转发请求到这些后端pod上面、kube-proxy还会通过iptables规则或者ipvs负载均衡算法等方式来做请求的负载

当service的后端pod数量发生变化时、Endpoints对象也会自动更新

扩展2:集群的FQDN(非常重要)

WARNING

service只要创建完成,我们就可以直接解析它的服务名,每一个服务创建完成后都会 在集群dns中动态添加一个资源记录,添加完成后我们就可以解析了,资源记录格式是:

SVC_NAME.NS_NAME.DOMAIN.LTD.

SVC名称.命名空间名称.域名后缀,集群默认的域名后缀是svc.cluster.local。

就像我们上面创建的my-nginx这个服务,它的完整名称解析就是: my-nginx.default.svc.cluster.local

//查询
kubectl exec -it my-nginx-5f7668c4b-cnf2b --bash root@my-nginx-5f7668c4b-cnf2b :/# curl my-nginx.default.svc.cluster.local

实战二:创建type类型为NodePort的Service

目标:使用Deployment控制器创建2个副本,创建service类型为NodePort 来代理Deployment控制器所创建的副本

先创建pod、再创建service文件、创建pod的过程为实战里的代码过程、在上面、这里不再给出、仅提供创建svc的部分

/查看pod标签
kubectl get pods --show-labels

//创建service资源清单文件
vi service_NodePort.yaml

apiVersion: v1               # Kubernetes API 版本
kind: Service                # 资源类型为 Service
metadata:
  name: my-nginx             # Service 名称
  labels:
    run: my-nginx            # Service 标签(用于资源分类)
spec:
  type: NodePort             # Service 类型(通过节点端口暴露服务)
  ports:
  - port: 80                 # Service 虚拟端口(集群内访问使用)
    protocol: TCP            # 网络协议类型(TCP/UDP/SCTP)
    targetPort: 80           # 容器实际监听的端口
    nodePort: 30380          # 节点暴露端口(范围30000-32767)
  selector:
    run: my-nginx            # 选择器标签(匹配具有相同标签的Pod)

//创建service资源
kubectl apply -f service_NodePort.yaml



//查看创建的service资源、Cluster-IP 就是我们生成的service的虚拟ip
kubectl get svc my-nginx

//测试访问、在集群内访问
curl 10.10.228.115

//在集群外访问
在集群外部访问node节点IP+service代理的端口,即可代理到Pod
浏览器访问192.168.128.11:30380

扩展1:访问192.168.128.11:30380是如何将流量转发给后端pod的

NodePort类型的Service除了会创建一个ClusterIP地址外,还会分配一个静态端口 号,供集群外部访问Service。集群外部的请求将会先访问Kubernetes集群节点的该端口, 然后被重定向到Service相应的Pod上。 具体来说,当Kubernetes集群中的某个节点的kube-proxy组件监听到NodePort端口 的请求,它会转发请求到Service代理的ClusterIP地址,并根据Service中的selector 获得要转发给的Pod的IP地址和端口号。之后,kube-proxy会使用网络地址转换(NAT) 技术将节点的IP地址和NodePort转换成Pod的IP地址和容器内应用的服务端口号,最后 将请求转发到Pod上。如下图

需要注意的是,如果有多个节点,流量到达每个节点上的NodePort端口时,都会转发到相同的Pod上。当Pod的IP地址发生变化时,节点上的kube-proxy组件会自动刷新其 iptables规则,确保新的PodIP地址可以正常被转发

实战三:创建type类型为 ExternalName 的Service

先创建pod、再创建service文件、创建pod的过程为实战里的代码过程、在上面、这里不再给出、仅提供创建svc的部分

//创建svc文件
 vi baidu-svc.yaml

apiVersion: v1               # Kubernetes API 版本
kind: Service                # 资源类型为 Service
metadata:
  name: baidu                # Service 名称(集群内DNS使用该名称)
spec:
  type: ExternalName         # Service 类型(DNS别名映射)
  externalName: www.baidu.com  # 外部服务域名(将作为CNAME记录)

//应用yaml文件
kubectl apply-f baidu-svc.yaml

//查看svc、发现并没有虚拟ip
 kubectl get svc baidu

 //创建pod、进行测试、访问的时候被拒绝了、因为百度服务器不允许我们做代理
  kubectl run busybox --image busybox:latest --restart=Never --rm -it busybox --sh

   /# pingbaidu.default.svc.cluster.local
   

实战四:无标签选择器的Service

创建一个service,不使用标签选择器,通过创建endpoints指定IP和PORT, 然后和SVC进行绑定。 实战:k8s集群引用外部的mysql数据库

实战:k8s集群引用外部的mysql数据库

//在k8s-node-01上安装mysql数据库
yum install mariadb-server -y
systemctl restart mariadb
netstat -tlun | grep 3306
mysql

//在master节点创建yaml文件
vi mysql_service.yaml

apiVersion: v1               # Kubernetes API 版本
kind: Service                # 资源类型为 Service
metadata:
  name: mysql                # Service 名称(用于集群内服务发现)
spec:
  type: ClusterIP            # 服务类型(默认集群内访问)
  ports:
  - port: 3306               # Service 暴露的端口

//更新资源清单文件
kubectl apply -f mysql_service.yaml

//查看创建的mysql的SVC
kubectl get svc | grep mysql

//查看描述
kubectl describe svc mysql

//创建Endpoints的yaml文件注意endpoints的名称必须和service的名称对应起来
vi mysql_endpoint.yaml

apiVersion: v1               # Kubernetes API 版本
 kind:Endpoints                # 资源类型为 Service
metadata:
  name: mysql                # Service 名称(用于集群内服务发现)
spec:
  type: ClusterIP            # 服务类型(默认集群内访问)
  ports:
  - port: 3306     

//对yaml文件的说明
创建一个名为mysql的Endpoints对象的示例,它将一个或多个IP地址和端口映射到了一个命名的mysql服务上。
这个mysql服务可以在集群内部被其它Pod的容器使用,进而访问这些IP地址上的服务。
该清单表明了这个Endpoints对象的名称是mysql,它只有一个子集(subset),包含了一个IP地址192.168.
128.11和一个端口3306。
其中,metadata字段包含了Endpoints对象的元数据信息,例如名称、标签、注释等等;subsets字段是Endpo
ints对象的核心,它包含了Endpoints对象映射的IP地址和端口信息,可以有多个子集,每个子集可以映射多个IP地址和端口。

//更新资源清单文件
kubectl apply -f mysql_endpoint.yaml

//查看创建的mysql SVC
kubectl describe svc mysql

//在集群节点进行测试
mysql -h 10.102.14.71