尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

【Kubernetes从入门到精通】第15篇:Service——K8s的服务发现和负载均衡

【Kubernetes从入门到精通】第15篇:Service——K8s的服务发现和负载均衡 上一篇【第14篇】ReplicaSet——Deployment背后的“影子武士“下一篇【第16篇】Service的负载均衡原理——kube-proxy到底干了什么摘要在K8s里Pod的IP就像酒店的房间号——你今天住808明天退房再来可能住1206了。Pod每次重建IP都会变。那你的前端服务怎么找到后端的API Pod总不能每次Pod重启都改配置文件吧Service就是来解决这个寻人问题的——它给Pod群分配一个固定的虚拟IPClusterIP你永远连这个IP就行Service在后端帮你把流量转发到正确的Pod上。这篇文章从Service的核心问题出发把四种Service类型ClusterIP、NodePort、LoadBalancer、ExternalName给你掰开揉碎讲清楚然后深入Endpoints机制、Headless Service的特殊用法最后聊聊Service和Label Selector是怎么协作的。一、Service解决了什么问题——“固定电话号码”先感受一下没Service时的痛苦【没有Service的世界——Pod IP地狱】 Time 0: Pod创建 Time 10m: Pod挂掉重建 ┌─────────────────┐ ┌─────────────────┐ │ backend-pod-1 │ │ backend-pod-1 │ │ IP: 10.244.1.5 │ │ IP: 10.244.2.9 │ ← IP变了 └─────────────────┘ └─────────────────┘ ▲ ▲ │ │ ┌───────┴────────┐ ┌───────┴────────┐ │ frontend-pod │ │ frontend-pod │ │ 配置文件 │ │ 配置文件 │ │ BACKEND_URL │ │ BACKEND_URL │ │ 10.244.1.5:8080│ ← 配置写死了 │ 10.244.2.9:8080│ ← 得手动改 └────────────────┘ └────────────────┘ 用户为什么页面打不开了 你因为后端Pod重启了IP变了前端配置文件还是旧的... 用户那这K8s到底有什么用 你有了Service之后世界就美好了【有Service的世界——固定电话号码模式】 不管后端Pod怎么变Service的IP和DNS名永远不变 ┌──────────────────────────────────────────────────────┐ │ Service │ │ 名称backend-svc │ │ ClusterIP10.96.100.50永远不变 │ │ DNSbackend-svc.default.svc.cluster.local│ │ │ │ selector: │ │ app: backend ← 通过Label找到后端Pod │ └────┬─────────────┬─────────────┬─────────────────────┘ │ │ │ ▼ ▼ ▼ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ backend │ │ backend │ │ backend │ │ Pod-1 │ │ Pod-2 │ │ Pod-3 │ │ IP:...1.5│ │ IP:...2.3│ │ IP:...3.7│ └──────────┘ └──────────┘ └──────────┘ frontend-pod 永远连 backend-svc:8080不用管后面有几个Pod、IP是什么要点Service的核心价值就一句话——给一组Pod提供一个固定的访问入口虚拟IP DNS名并自动将流量负载均衡到健康的Pod上。你不需要知道Pod有几个、IP是什么、加了一个还是死了一个——Service帮你管。二、四种Service类型——从内部用到全世界访问Service有四种类型适用范围从集群内部到公网暴露。它们的核心区别在于生成的虚拟IP能被谁访问到。【四种Service类型的作用域】 外网 ┌──────────────────────────────────────────────────┐ │ LoadBalancer │ │ 云服务商分配公网LB │ │ 外部用户通过公网IP访问 │ │ ┌────────────────────────────────────────────┐ │ │ │ NodePort │ │ │ │ 每个Node开一个端口 (30000-32767) │ │ │ │ 外网/内网 都能通过 NodeIP:Port 访问 │ │ │ │ ┌──────────────────────────────────────┐ │ │ │ │ │ ClusterIP │ │ │ │ │ │ 集群内部虚拟IP │ │ │ │ │ │ 只有集群内的Pod/Node能访问 │ │ │ │ │ └──────────────────────────────────────┘ │ │ │ └────────────────────────────────────────────┘ │ └──────────────────────────────────────────────────┘ ExternalName不创建虚拟IP直接返回一个CNAME记录 用于把集群内的请求指向外部服务2.1 ClusterIP——“内网专线”默认类型只分配一个集群内部IP。集群中的Pod和Node可以通过这个IP访问Service集群外访问不到。apiVersion:v1kind:Servicemetadata:name:backend-svcspec:type:ClusterIP# 默认类型可以省略selector:app:backend# 找哪些Podports:-name:httpprotocol:TCPport:8080# Service自己的端口targetPort:8080# Pod上监听的端口# 创建Servicekubectl apply-fbackend-svc.yaml kubectl get svc# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE# backend-svc ClusterIP 10.96.100.50 none 8080/TCP 10s# 在集群内任意Pod中访问kubectl run tmp--imagebusybox--rm-it--wget-qO- http://10.96.100.50:8080# 或者用DNS名更推荐kubectl run tmp--imagebusybox--rm-it--wget-qO- http://backend-svc:80802.2 NodePort——“给集群开了个后门”在ClusterIP的基础上在每个Node上开放一个固定端口范围30000-32767。外部通过任意NodeIP:NodePort就能访问到Service。【NodePort 流量路径】 外部用户 │ │ http://192.168.1.10:30080 ▼ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ Node-1 │ │ Node-2 │ │ Node-3 │ │ :30080 │ │ :30080 │ │ :30080 │ │ ┌───┐ │ │ ┌───┐ │ │ ┌───┐ │ │ │Pod│ │ │ │Pod│ │ │ │Pod│ │ │ └───┘ │ │ └───┘ │ │ └───┘ │ └─────────────┘ └─────────────┘ └─────────────┘ 不管流量打到哪个Node的:30080最终都会路由到后端Pod 即使Pod只在Node-1上你访问Node-3的:30080也能通 Node-3会把流量转发给Node-1上的PodapiVersion:v1kind:Servicemetadata:name:frontend-svcspec:type:NodePortselector:app:frontendports:-name:httpport:80# Service端口targetPort:8080# Pod端口nodePort:30080# Node上的端口可指定不指定系统随机分配要点NodePort有一个局限性——你只能用30000-32767范围的端口而且每个端口在整个集群中只能被一个Service占用。更重要的是NodePort不提供负载均衡如果外部用户直接访问某个Node的NodePort而那个Node挂了流量就断了。生产环境通常会在NodePort前面再加一层外部LoadBalancer。2.3 LoadBalancer——“正规的对外开放”LoadBalancer在NodePort的基础上由云服务商AWS、阿里云、腾讯云等自动创建一个外部的负载均衡器分配一个公网IP。这是生产环境的标准做法。apiVersion:v1kind:Servicemetadata:name:public-frontendspec:type:LoadBalancerselector:app:frontendports:-name:httpport:80targetPort:8080【LoadBalancer 架构】 互联网 │ ▼ ┌─────────────────┐ │ 云负载均衡器 │ ← 云商自动创建AWS ELB/ALB、阿里云SLB等 │ 公网IP: 1.2.3.4 │ └────────┬────────┘ │ 分发流量到各Node的NodePort ▼ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ Node :30080│ │ Node :30080│ │ Node :30080│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │ │ │ ▼ ▼ ▼ ┌──────┐ ┌──────┐ ┌──────┐ │ Pod │ │ Pod │ │ Pod │ └──────┘ └──────┘ └──────┘kubectl get svc public-frontend# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)# public-frontend LoadBalancer 10.96.100.60 1.2.3.4 80:30080/TCP# 外部用户访问 http://1.2.3.4 → 路由到后端的frontend Pod2.4 ExternalName——“借花献佛”ExternalName不创建ClusterIP不创建Endpoints。它只在集群DNS里加一条CNAME记录把你对Service的访问重定向到一个外部域名。apiVersion:v1kind:Servicemetadata:name:external-dbspec:type:ExternalNameexternalName:rds-prod.xxxxxx.us-east-1.rds.amazonaws.com# 外部RDS地址# 集群内的Pod用 external-db 这个DNS名就能访问外部RDS# K8s DNS返回的不是IP而是CNAME → rds-prod.xxxxx.us-east-1.rds.amazonaws.comkubectl run tmp--imagebusybox--rm-it--nslookupexternal-db# external-db.default.svc.cluster.local canonical name rds-prod.xxxxx...要点ExternalName的妙用在于——你在代码里写死external-db就行哪天数据库迁移到了新地址改一下Service的externalName就行代码不用动。四种类型对比类型访问范围分配IP类型是否需要云服务商典型场景ClusterIP仅集群内部虚拟IPClusterIP❌ 不需要微服务间互相调用NodePort集群内外均可NodeIP:NodePort❌ 不需要开发测试、临时暴露LoadBalancer公网公网IP ClusterIP✅ 需要生产环境对外服务ExternalName集群内部DNS不分配IP仅CNAME❌ 不需要代理外部服务三、Endpoints——Service和Pod之间的通讯录Service是怎么知道该把流量发给哪些Pod的答案就是Endpoints——它在Service和Pod之间充当通讯录的角色。【Endpoints 机制】 ┌──────────────────────────────────────────────────────┐ │ Service │ │ name: backend-svc │ │ ClusterIP: 10.96.100.50 │ │ selector: {app: backend} │ └────────┬─────────────────────────────────────────────┘ │ │ Service Controller 持续监控 │ 哪些Pod匹配 selector: appbackend ▼ ┌──────────────────────────────────────────────────────┐ │ Endpoints │ │ name: backend-svc ← 跟Service同名 │ │ │ │ subsets: │ │ - addresses: │ │ - ip: 10.244.1.5 ← Pod-1 的 IP Port │ │ - ip: 10.244.2.3 ← Pod-2 的 IP Port │ │ - ip: 10.244.3.7 ← Pod-3 的 IP Port │ │ ports: │ │ - port: 8080 │ └──────────────────────────────────────────────────────┘# 查看Service对应的Endpointskubectl get endpoints# NAME ENDPOINTS AGE# backend-svc 10.244.1.5:8080,10.244.2.3:8080,10.244.3.7:8080 5m# 当你删除一个Pod后Endpoints自动更新kubectl delete pod backend-pod-1 kubectl get endpoints backend-svc# 10.244.2.3:8080,10.244.3.7:8080 ← 少了一个# 同时RS创建了新Pod: 10.244.2.9:8080# 几秒后 Endpoints 自动加上新的EndpointSlice在K8s 1.21版本中大的Endpoints会被拆成多个EndpointSlice避免单个Endpoints对象过大导致API Server性能问题。这是自动的你不需要手动管理。要点Endpoints是自动管理的——你不需要手动创建或修改它。Service Controller会根据Service的selector实时更新Endpoints列表。Pod就绪了Readiness Probe通过→ 加入Endpoints。Pod挂了或Readiness失败 → 从Endpoints移除。这就是Service的自动发现机制。无选择器的Service——手动管理Endpoints你还可以创建不带selector的Service然后手动创建Endpoints对象。这在代理外部服务时很常用# Service没有selectorapiVersion:v1kind:Servicemetadata:name:external-redisspec:ports:-port:6379---# Endpoints手动指定目标地址apiVersion:v1kind:Endpointsmetadata:name:external-redis# 名字必须跟Service一样subsets:-addresses:-ip:192.168.1.100# 外部Redis服务器的IP-ip:192.168.1.101ports:-port:6379四、Headless Service——“我不要虚拟IP”有时候你不想让Service分配ClusterIP——比如你要直接获取每个Pod的IP来做客户端负载均衡。这时用Headless Service设clusterIP: None。【Headless Service vs 普通Service】 普通Service Headless Service ┌─────────────────────┐ DNS查询 backend-headless │ Service │ │ │ ClusterIP:10.96.100.5│ ▼ └──────────┬──────────┘ ┌─────────────────────┐ │ │ DNS直接返回Pod IP列表│ DNS查询 backend-svc │ 10.244.1.5 │ │ │ 10.244.2.3 │ ▼ │ 10.244.3.7 │ 10.96.100.50 └─────────────────────┘ (一个IP) 你的应用拿到所有Pod的IP 你的应用只需要连一个IP 自己做负载均衡或分片apiVersion:v1kind:Servicemetadata:name:backend-headlessspec:clusterIP:None# ← 关键设为None就是Headlessselector:app:backendports:-port:8080# 解析Headless Service的DNSkubectl run tmp--imagebusybox--rm-it--nslookupbackend-headless# Name: backend-headless.default.svc.cluster.local# Address 1: 10.244.1.5 ← Pod-1 的IP# Address 2: 10.244.2.3 ← Pod-2 的IP# Address 3: 10.244.3.7 ← Pod-3 的IP# 直接返回了所有Pod的IP没有虚拟IPHeadless Service的典型用途场景为什么用HeadlessStatefulSet每个Pod有稳定的网络标识pod-0.svc、pod-1.svc…客户端负载均衡应用拿到所有Pod IP自己做负载均衡如gRPCKafka/ZK/ES集群集群节点间需要互相发现、互相通信自定义服务发现你想自己实现服务发现逻辑五、Service的DNS——“给你一个域名”K8s内置了DNS服务CoreDNS为每个Service自动注册DNS记录。Pod可以通过DNS名访问Service【K8s DNS 记录规则】 普通Service service-name.namespace.svc.cluster.local 例如backend-svc.default.svc.cluster.local 同Namespace内简写 backend-svc ← 直接写Service名就行 跨Namespace访问 backend-svc.team-backend.svc.cluster.local# 在Pod里直接ping Service名kubectlexec-itfrontend-pod --pingbackend-svc# PING backend-svc.default.svc.cluster.local (10.96.100.50)# 跨命名空间访问kubectlexec-itfrontend-pod --wget-qO- http://backend-svc.team-backend:8080要点在K8s里写微服务间的调用地址永远用service-name:port不要硬编码IP。Service名就是应用的永久地址IP变了DNS自动跟着变。六、完整的Service实战——一个前后端示例# backend-deployment.yaml —— 后端DeploymentapiVersion:apps/v1kind:Deploymentmetadata:name:backendlabels:app:backendspec:replicas:3selector:matchLabels:app:backendtemplate:metadata:labels:app:backendspec:containers:-name:apiimage:myapi:v1.0ports:-containerPort:8080---# backend-service.yaml —— 后端ServiceClusterIP仅集群内访问apiVersion:v1kind:Servicemetadata:name:backend-svcspec:type:ClusterIPselector:app:backendports:-port:8080targetPort:8080---# frontend-deployment.yaml —— 前端DeploymentapiVersion:apps/v1kind:Deploymentmetadata:name:frontendlabels:app:frontendspec:replicas:2selector:matchLabels:app:frontendtemplate:metadata:labels:app:frontendspec:containers:-name:webimage:myweb:v1.0ports:-containerPort:80env:-name:API_URLvalue:http://backend-svc:8080# ← 通过Service名访问后端---# frontend-service.yaml —— 前端ServiceNodePort对外暴露apiVersion:v1kind:Servicemetadata:name:frontend-svcspec:type:NodePortselector:app:frontendports:-port:80targetPort:80nodePort:30080# 部署全部kubectl apply-fbackend-deployment.yaml kubectl apply-fbackend-service.yaml kubectl apply-ffrontend-deployment.yaml kubectl apply-ffrontend-service.yaml# 验证kubectl get deploy,svc,endpoints# 前端通过 http://NodeIP:30080 访问# 前端代码里写 http://backend-svc:8080 调用后端——完全不用管Pod IP本篇小结Service是K8s网络体系的地基搞懂它后面Ingress、NetworkPolicy才能顺利推进核心价值给Pod群分配固定IP和DNS名Pod怎么变Service的入口都不变四种类型ClusterIP内部、NodePort开端口、LoadBalancer公网LB、ExternalNameDNS别名EndpointsService和Pod之间的通讯录由Service Controller自动维护Headless Service不分配虚拟IPDNS直接返回Pod IP列表适合StatefulSet和客户端负载均衡DNS服务名.命名空间.svc.cluster.local——永远用服务名别硬编码IP下一篇咱们深入Service的底层——kube-proxy到底是怎么把流量从Service的虚拟IP翻译到真实Pod IP的iptables和IPVS两种模式有什么区别为什么大集群要选IPVS上一篇【第14篇】ReplicaSet——Deployment背后的“影子武士“下一篇【第16篇】Service的负载均衡原理——kube-proxy到底干了什么
返回列表