当前位置: 首页 > news >正文

K8S Service 原理、案例

一、理论介绍

1.1、3W 法则

1、是什么?

  • Service 是一种为一组功能相同的 pod 提供单一不变的接入点的资源。当 Service 存在时,它的IP地址和端口不会改变。客户端通过IP地址和端口号与 Service 建立连接,这些连接会被路由到提供该 Service 的任意一个pod上。通过这种方式,客户端不需要知道每个单独的pod的地址,这样这些pod就可以在集群中随时被创建或移除。

2、为什么需要?

  • Pod 的 IP 地址经常变化。
  • Pod 的 IP 在集群外无法访问。
  • Pod 实例之间的负载均衡。

3、局限性

  • Service 是一种四层代理。
  • 所谓四层,是针对 OSI 七层网络模型来说的。四层对应的是TCP/UDP协议,也就常说的IP+端口。
  • 因此,所谓四层代理就是基于IP+端口的负载均衡;七层就是基于URL等应用层信息的负载均衡。

1.2、基础信息

kubectl explain svc
# svc 是 service 的缩写

  • apiVersion:当前资源使用的 api 版本,与 VERSION 一致。
  • kind:资源类型,跟 KIND 保持一致。
  • metadata:元数据。定义资源名称、标签、注解等。
  • spec:规范、规约。
  • status:最近观察到的 Service 状态。由系统填充。只读。

1.3、ServiceSpec 规约

kubectl explain svc.spec
allocateLoadBalancerNodePorts    <boolean>
clusterIP    <string>
clusterIPs    <[]string>
externalIPs    <[]string>
externalName    <string>
externalTrafficPolicy    <string>
healthCheckNodePort    <integer>
internalTrafficPolicy    <string>
ipFamilies    <[]string>
ipFamilyPolicy    <string>
loadBalancerClass    <string>
loadBalancerIP    <string>
loadBalancerSourceRanges    <[]string>
ports    <[]ServicePort>端口
publishNotReadyAddresses    <boolean>
selector    <map[string]string>标签选择器
sessionAffinity    <string>
sessionAffinityConfig    <SessionAffinityConfig>
trafficDistribution    <string>
type    <string>类型

1.4、Service 类型

kubectl explain svc.spec.type

type 类型有四种:

  • ClusterIP:虚拟集群IP。通过集群的内部 IP 暴露服务,选择该值时服务只能够在集群内部访问。默认类型。
  • NodePort:节点端口。通过每个节点上的 IP 和静态端口(NodePort)暴露服务。 NodePort 服务会路由到自动创建的 ClusterIP 服务。 通过请求 <节点 IP>:<节点端口>,你可以从集群的外部访问一个 NodePort 服务。
  • ExternalName:外部命名空间。通过返回 CNAME 和对应值,可以将服务映射到 externalName 字段的内容(例如,foo.bar.example.com)。 无需创建任何类型代理。
  • LoadBalancer:负载均衡。使用云提供商的负载均衡器向外部暴露服务。 外部负载均衡器可以将流量路由到自动创建的 NodePort 服务和 ClusterIP 服务上。Kubernetes 不直接提供负载均衡组件; 你必须提供一个,或者将你的 Kubernetes 集群与某个云平台集成。

其中 ClusterIP 为默认方式,只能集群内部访问。NodePort、LoadBalancer 则是向外暴露服务的同时将流量路由到 ClusterIP服务。ExternalName 则是CNAME方式进行服务映射。

1.5、Service 端口

kubectl explain svc.spec.ports
appProtocol    <string>
name    <string>
nodePort    <integer>

service 在节点映射的端口。

type 类型是 NodePort 或 LoadBalancer 时才指定。

通常是系统分配,也可以自己指定,范围在 30000-32767

port    <integer> -required-Service 将公开的端口。
protocol    <string>协议。协议类型有 SCTP, TCP, UDP。默认 TCP
targetPort    <IntOrString>pod 端口

二、镜像准备

2.1、镜像准备

docker pull mirrorgooglecontainers/serve_hostname:latest
docker pull alpine:latest
docker pull curlimages/curl

 2.2、镜像导出

docker save -o serve_hostname.tar.gz mirrorgooglecontainers/serve_hostname:latest
docker save -o alpine.tar.gz alpine:latest
docker save -o curl.tar.gz curlimages/curl

 2.3、镜像导入工作节点 containerd

# k8s31node1 执行
[root@k8s31node1 ~]# ctr -n=k8s.io images import serve_hostname.tar.gz
[root@k8s31node1 ~]# ctr -n=k8s.io images ls|grep serve_hostname
[root@k8s31node1 ~]# ctr -n=k8s.io images import alpine.tar.gz
[root@k8s31node1 ~]# ctr -n=k8s.io images ls|grep alpine
[root@k8s31node1 ~]# ctr -n=k8s.io images import curl.tar.gz
[root@k8s31node1 ~]# ctr -n=k8s.io images ls|grep curl# k8s31node2 执行
[root@k8s31node2 ~]# ctr -n=k8s.io images import serve_hostname.tar.gz
[root@k8s31node2 ~]# ctr -n=k8s.io images ls|grep serve_hostname
[root@k8s31node2 ~]# ctr -n=k8s.io images import alpine.tar.gz
[root@k8s31node2 ~]# ctr -n=k8s.io images ls|grep alpine
[root@k8s31node2 ~]# ctr -n=k8s.io images import curl.tar.gz
[root@k8s31node2 ~]# ctr -n=k8s.io images ls|grep curl

2.4、环境准备

 假设有如下三个节点的 K8S 集群:

k8s31master 是控制节点

k8s31node1、k8s31node2 是工作节点

容器运行时是 containerd

三、实践

3.1、创建 ClusterIP 类型 Service

假设有这么一个部署:

apiVersion: apps/v1
kind: Deployment
metadata:name: hostnames
spec:selector:matchLabels:app: hostnamesreplicas: 3template:metadata:labels:app: hostnamesspec:containers:- name: hostnamesimage: mirrorgooglecontainers/serve_hostnameimagePullPolicy: IfNotPresentports:- containerPort: 9376protocol: TCP

mirrorgooglecontainers/serve_hostname 这个应用的作用,就是每次访问 9376 端口时,返回它自己的 hostname。

3.1.1、在不同的节点上访问 podIP:port

返回了各个 pod 自己的 hostname。

可以看到,在集群内的不同节点上, Pod IP 都能访问。

3.1.2、在不同的容器内访问 podIP:port

假设现在新起一个 pod:

apiVersion: v1
kind: Pod
metadata:name: curl-tools
spec:containers:- name: curl-toolsimage: curlimages/curl:latestimagePullPolicy: IfNotPresentcommand: ["/bin/sh", "-c", "while true; do echo 'Hello from curl-tools'; sleep 30; done"]
  • curlimages/curl 是一个 curl 调试工具。
  • command:容器启动后执行的命令,这里使用一个无限循环,每隔 30 秒输出一次 Hello from curl-tools。
  • 像 Alpine 镜像,或者基于 Alpine 制作的工具镜像,容器内没有运行服务,需要启动后运行一个无限循环,防止容器被 K8S 杀掉。

 进入容器访问 hostnames 服务:

kubectl exec -it curl-tools -- curl 10.244.165.57:9376

可以看到,在集群内的容器之间,Pod IP 都能访问。

3.1.3、在集群外访问 podIP:port

我们再起一台虚拟机 docker1,IP 地址跟 K8S 集群在一个网段。

访问 hostnames 服务:

可以看到,即使 docker1 的 IP 地址跟 K8S 集群在一个网段,但 docker1 没有用类似 kubeadm join 加入过集群,Pod IP 是不能访问的。

3.1.4、误删一个 pod 

kubectl delete pod hostnames-d9d7674f5-2djvf

 可以看到,K8S 又帮我们重新拉起了一个新 pod:hostnames-d9d7674f5-n7mtn,以维持我们 Deployment 控制器希望的副本数 replicas: 3。但是这个新 pod 的 IP,跟原来旧 pod 的 IP 是不一样的。

倘若我们是调用这些 pod 服务的客户端,在 pod 扩缩容期间,维护这些 pod IP 的代价是非常大的。所以我们需要一个稳定的接入层,它的 IP 地址、端口不变,让它来代理后端的一组 pod,而我们程序只需要跟这个接入层打交道就可以。这个接入层,就是 Service。

 3.1.5、新建一个 ClusterIP 类型 Service

apiVersion: v1
kind: Service
metadata:name: hostnames-svc
spec:type: ClusterIPselector:app: hostnamesports:- port: 80protocol: TCPtargetPort: 9376
  • spec.selector:Service 通过标签选择器来查找 app=hostnames 标签的 Pod。
  • port: 80  表示该服务的可用端口。
  • targetPort: 9376 表示服务将连接转发的 Pod 端口。
  • port 跟 targetPort 配合起来表示 这个 Service 的 80 端口,代理的是 Pod 的 9376 端口。
  •  查看 service
kubectl get svc

  •  访问 service

连续三次不断地访问 Service 的 CLUSTER-IP 和 端口 80:

  • 依次返回了三个 Pod 的 hostname。
  • 请求 Service IP:port 跟直接访问 Pod IP:port 的结果一样,这说明 Service 可以把请求代理到它所关联的后端 Pod。
  • 这也印证了 Service 提供的是 Round Robin (轮询) 方式的负载均衡。

  • 查看 endpoints

 K8S 创建 Service 的时候,如果 Service 带有 selector 选择器,则 K8S 会创建一个与 Service 同名的 Endpoints 对象。selector 选中的 Pod 的 IP 和 端口,都会记录在 Endpoints 中。当一个新的 Pod 被创建并且它的标签匹配了某个 Service 的选择器时,该 Pod 的 IP 和端口会被添加到对应的 Endpoints 对象中;同样地,当 Pod 被删除时,它也会从 Endpoints 中移除。Endpoints 通常由 Service 引用, 以定义可以将流量发送到哪些 Pod。

kubectl get ep hostnames-svc
# ep endpoints 缩写
kubectl get ep hostnames-svc -oyaml

 需要注意的是,只有处于 Running 状态,且 readinessProbe 检查通过的 Pod,才会出现在 Service 的 Endpoints 列表里。并且,当某一个 Pod 出现问题时,Kubernetes 会自动把它从 Service 里摘除掉。

 3.1.6、集群外访问 Service ClusterIP:port

 在集群外的虚拟机 192.168.40.111 访问 10.103.168.44:80

可以看到,访问不到 Service。如果希望集群外的机器能访问,需要创建 NodePort 类型的 Service 或 LoadBalancer 类型的 Service。

 3.1.7、通过 FQDN 访问服务

在 Kubernetes (K8S) 环境中,FQDN(Fully Qualified Domain Name,完全限定域名)是指集群内部服务的完整域名,它包含了服务名、命名空间以及可选的服务后缀,用于唯一标识集群中的一个服务。

当你在 Kubernetes 中创建一个服务时,Kubernetes 会为该服务分配一个 FQDN。这个 FQDN 的格式通常是:

<service-name>.<namespace>.svc.cluster.local
  • service-name 是你给服务起的名字。
  • namespace 是服务所在的命名空间,默认是 default,除非你在创建服务时指定了不同的命名空间。
  • svc 是固定的,代表这是一个服务。
  • cluster.local 是集群的默认域,大多数情况下是这个值,但如果你的集群配置了不同的域名,则会有所不同。

例如,我们上面 hostnames-svc 服务,它的完整域名是:

hostnames-svc.default.svc.cluster.local

我们在集群的其他 pod 中,就可以使用这个域名访问服务:

可以省略命名空间和 svc.cluster.local,因为已经帮我们解析了。

在节点上不能解析这个域名,Kubernetes 的 DNS 服务通常只服务于集群中的 Pod,而节点本身并不自动配置为使用它:

使用 FQDN 可以让 Kubernetes 集群中的其他服务通过 DNS 解析来找到并访问你的服务,而不需要知道服务的具体 IP 地址。这有助于实现服务发现和服务间的通信,并且提高了服务部署的灵活性和可移植性。在 Kubernetes 中,CoreDNS 通常被用来提供这种 DNS 服务发现的功能。

 3.1.8、Service 原理

我们以 3.1.2 小节 curl-tools 容器(假设运行在 node2 上)访问 Service Cluster_IP : port 为例,来讲解 Service 的工作原理。

首先介绍一下几个组件:

  • kube - proxy:是 Kubernetes 集群中每个节点上运行的一个组件。它负责在节点上维护网络规则,实现了 Service 的代理和负载均衡功能,确保客户端可以通过 Service 的 IP 和端口访问到对应的后端 Pod。kube-proxy 会监视 API Server 中 Service 和 Endpoints 对象的变化。当有新的 Service 或 Endpoints 对象创建、更新或删除时,kube-proxy 会收到通知,并相应地更新节点上的网络规则(iptables)。kube-proxy 有 iptables 模式跟 ipvs 模式。

  • Service:是 Kubernetes 提供的一种抽象层,它定义了一组 Pod 的逻辑集合以及访问这些 Pod 的策略。Service 为 Pod 提供了一个稳定的 IP 地址(ClusterIP)和端口号,使得客户端可以通过这个稳定的地址来访问后端的 Pod,而不需要关心具体 Pod 的 IP 地址和生命周期。
  • Endpoints:是 Kubernetes 中的一个资源对象,它记录了 Service 对应的所有后端 Pod 的 IP 地址和端口信息。每当 Pod 的数量或状态发生变化时,Endpoints 对象会自动更新。

curl-tools 容器内访问 Service IP : port 流程:

  1. node2 上的 kube-proxy 监视 API Server 中 Service 和 Endpoints 对象的变化,更新节点上的网络规则(iptables)。
  2. 客户端 curl-tools 请求 Service IP : port,请求包目的地 Destination 初始设置为服务的IP和端口(10.103.168.44:80)。发送到网络之前,node2 的内核会根据配置在该节点上的 iptables 规则处理数据包。内核会检查数据包是否匹配任何这些 iptables 规则。其中有个规则规定如果有任何数据包的目的地IP等于10.103.168.44、目的地端口等于80,那么数据包的目的地IP和端口应该被替换为随机选中的 hostnames pod的IP和端口。
  3. 本例中的数据包满足规则,故而它的IP:端口被改变了。假设 pod hostnames-d9d7674f5-tjwzp 被轮询算法随机选中了,所以数据包的目的地IP变更为 10.244.165.56,端口改为9376(Service中定义的目标端口)。就好像是客户端 curl-tools 直接发送数据包给 hostnames-d9d7674f5-tjwzp 而不是通过 Service。

3.1.9、iptables Or IPVS

kube-proxy 通过 iptables 处理 Service 的过程,其实需要在宿主机上设置相当多的 iptables 规则。而且,kube-proxy 还需要在控制循环里不断地刷新这些规则来确保它们始终是正确的。

当宿主机上有大量 Pod 的时候,成百上千条 iptables 规则不断地被刷新,很明显会影响到整体性能。

IPVS 模式的工作原理,其实跟 iptables 模式类似。区别只在于当我们创建了前面的 Service 之后,kube-proxy 首先会在宿主机上创建一个虚拟网卡(叫作:kube-ipvs0),并为它分配 Service VIP 作为 IP 地址,如下所示:

 

而接下来,kube-proxy 就会通过 Linux 的 IPVS 模块,为这个 IP 地址设置三个 IPVS 虚拟主机,并设置这三个虚拟主机之间使用轮询模式 (rr) 来作为负载均衡策略。我们可以通过 ipvsadm 查看到这个设置,如下所示:

ipvsadm -ln
-l:这是 ipvsadm 命令的一个选项,代表 “list”,即列出当前 IPVS 规则。使用该选项可以查看已经配置的虚拟服务器(Virtual Server)及其对应的真实服务器(Real Server)信息。
-n:同样是 ipvsadm 命令的选项,代表 “numeric”,表示以数字形式显示地址和端口,而不是将 IP 地址解析为域名、端口号解析为服务名。使用这个选项可以避免 DNS 解析和服务名查找的过程,更直观地显示规则信息。

可以看到,这三个 IPVS 虚拟主机的 IP 地址和端口,对应的正是三个被代理的 Pod。rr 表示轮询 round robin。

这时候,任何发往 10.103.168.44:80 的请求,就都会被 IPVS 模块转发到某一个后端 Pod 上了。 而相比于 iptables,IPVS 在内核中的实现其实也是基于 Netfilter 的 NAT 模式,所以在转发这一层上,理论上 IPVS 并没有显著的性能提升。但是,IPVS 并不需要在宿主机上为每个 Pod 设置 iptables 规则,而是把对这些“规则”的处理放到了内核态,从而极大地降低了维护这些规则的代价。

不过需要注意的是,IPVS 模块只负责上述的负载均衡和代理功能。而一个完整的 Service 流程正常工作所需要的包过滤、SNAT 等操作,还是要靠 iptables 来实现。

 3.1.10、Endpoints 与 readinessProbe 就绪探针

  • 编写服务 svc-hellok8s.yaml
apiVersion: v1
kind: Service
metadata:name: hellok8s-svc
spec:type: ClusterIPselector:app: hellok8sports:- port: 80protocol: TCPtargetPort: 8080
  •  执行并监控
kubectl apply -f svc-hellok8s.yaml
# -w 表示持续监控,注意这个时候不要关闭终端
kubectl get ep hellok8s-svc -w

  • 编写部署  deploy-hellok8s.yaml
apiVersion: apps/v1
kind: Deployment
metadata:name: hellok8s
spec:replicas: 3selector:matchLabels:app: hellok8sversion: "1.0"template:metadata:labels:app: hellok8sversion: "1.0"spec:containers:- name: hellok8simage: hellok8s:1.0imagePullPolicy: IfNotPresentports:- containerPort: 8080readinessProbe:httpGet:path: /port: 8080initialDelaySeconds: 15periodSeconds: 10

initialDelaySeconds: 表示容器启动后延迟多少秒,开始就绪探测。这里特意设置成 15 秒,为了观察就绪探针没有完成时, Endpoints 的列表里,会不会有 pod 的 IP。

  •  在另一个终端运行部署
kubectl apply -f deploy-hellok8s.yaml

    可以看到,在 pod 还没就绪前,endpoints 列表里面是不会有 pod IP 的。

    • 删除部署
    kubectl delete -f deploy-hellok8s.yaml

    可以看到 pod 被删除,endpoints 列表也会移除 pod IP。

     3.1.11、原理图

    3.2、创建 NodePort 类型 Service

    假设我们现在有如下 三个 pod:

    kubectl get pod -l app=hostnames -owide

     IP 分别是 10.244.9.57、10.244.9.59、10.244.165.3

    3.2.1、编写服务

    apiVersion: v1
    kind: Service
    metadata:name: hostnames-nodeport
    spec:type: NodePortselector:app: hostnamesports:- port: 80protocol: TCPtargetPort: 9376nodePort: 32000
    • spec.type:NodePort 通过每个节点上的 IP 和静态端口(NodePort)暴露服务。
    • spec.ports.nodePort:指定节点上暴露的端口 32000。

     3.2.2、访问服务

    在浏览器中、或者任何能访问到集群三个节点的机器上,访问 节点IP:32000

    curl 192.168.40.10:32000
    curl 192.168.40.20:32000
    curl 192.168.40.30:32000

    3.2.3、原理

    每一个节点的防火墙规则里面,都有一条 节点IP:32000 的转发规则。转发到三个 pod上。

    创建 NodePort 类型的 Service 会默认帮我们创建 Cluster_IP

    它的数据转发方式,跟 3.1 节讲的是一样的,走 kube-ipvs0 虚拟网桥。

    如果节点上有安装 docker,NodePort 不会走节点IP端口,会默认走 docker0 网桥,然后数据再通过 docker0 转发给 pod。

    3.3、创建 ExternalName 类型 Service

    3.3.1、场景分析

    Service,是无法代理到不同名称空间下的 Pod 的。

    假设我们默认名称空间下,有三个这样的 Pod:

    kubectl get pod -l app=hostnames -A --show-labels

    在名称空间 external-demo 下,有这样一个 Service:

    apiVersion: v1
    kind: Service
    metadata:name: external-svc-anamespace: external-demo
    spec:type: ClusterIPselector:app: hostnamesports:- port: 80protocol: TCPtargetPort: 9376

     Service 在 external-demo 名称空间下,它的标签选择器选择了上面的三个 Pod。

     查看 Service 详情:

    kubectl describe svc external-svc-a -n=external-demo

    可以看到它并没有代理到任何 Pod。

     此时,不管是在节点上直接访问 Service ClusterIP 10.100.211.127:80

    还是在 external-demo 名称空间下的 Pod:

    apiVersion: v1
    kind: Pod
    metadata:name: curl-toolsnamespace: external-demo
    spec:containers:- name: curl-toolsimage: curlimages/curl:latestimagePullPolicy: IfNotPresentcommand: ["/bin/sh", "-c", "while true; do echo 'Hello from curl-tools'; sleep 30; done"]
    kubectl exec -it -n=external-demo curl-tools -- /bin/sh

    都访问不到任何 Pod。

    那么,external-demo 名称空间下的 Pod 要如何通过 Service 访问 default 名称空间下的 Pod 呢?

    3.3.2、ExternalName 类型 Service

    在 external-demo 名称空间下新建一个 Service:

    apiVersion: v1
    kind: Service
    metadata:name: external-svc-bnamespace: external-demo
    spec:type: ExternalNameexternalName: hostnames-svc.default.svc.cluster.localselector:app: hostnamesports:- port: 80protocol: TCP
    • type: ExternalName。
    • externalName:指定 default 名称空间下的完全限定服务名。

    相当于给 hostnames-svc 服务创建了一个软连接。

    • targetPort:在这种情况下可以忽略。
    kubectl get svc -n=external-demo

    此时,在 external-demo 名称空间下的 curl-tools 就可以直接访问这个服务,请求会被代理到 default 名称空间下的 Pod:

    四、参考资料

    官网:虚拟 IP 和服务代理

    Kubernetes教程(五)---Service 的几种访问方式

    相关文章:

  • 【AIGC】基础篇:VS Code 配置 Python 命令行参数调试debug超详细教程
  • MySQL日期函数的详细教程(包含常用函数及其示例)
  • JavaWeb:JavaScript
  • 苹果新规生效:即日起不再接受iOS 17 SDK编译的应用提交
  • [密码学实战]商用密码产品密钥体系架构:从服务器密码机到动态口令系统
  • 源码分析之Leaflet中Icon类
  • 【EDA】EDA中聚类(Clustering)和划分(Partitioning)
  • 《算法笔记》4.2小节——算法初步->哈希
  • 观测云数据在Grafana展示的最佳实践
  • Milvus(7):Schema、主字段和自动识别
  • Apache Spark 源码解析
  • Go 语言 TCP 端口扫描器实现与 Goroutine 池原理
  • Modbus主从通信功能码与报文格式
  • 如何在SpringBoot中通过@Value注入Map和List并使用YAML配置?
  • 电子学会—青少年软件编程 python一级等级考试真题—2025年03月
  • 20242817-李臻-课上测试:信号处理
  • uniapp自定义封装tabbar
  • TDengine 数据缓存技术
  • iOS/Flutter混合开发之PlatformView配置与使用
  • 记录一次无界微前端的简单使用
  • 观察|上海算力生态蓬勃发展,如何助力千行百业数智化转型升级
  • 专访倪军:人要有终身学习能力,一张文凭无法像以往支撑那么多年
  • 小马智行彭军:今年是Robotaxi量产元年,有望3年后盈亏平衡
  • 为什么猛起身会头晕?你的身体在发出这个警报
  • 封江晚开江早,东北地区主要江河上一冰封期冰层较常年偏薄
  • 东阿至聊城公交票价取消八折优惠:运行成本高昂