K8s详细学习笔记 第三章:应用访问与服务发现:Service与Ingress
第三章:应用访问与服务发现:Service与Ingress
在第二章中,我们成功地使用Deployment部署了一个高可用的Nginx应用。然而,我们留下了一个关键问题:这些运行在Pod中的应用,如何被其他应用发现和访问?用户又如何从集群外部访问到我们的Nginx欢迎页面?
答案就在于Kubernetes的网络模型核心:Service和Ingress。它们共同构成了K8s中强大而灵活的服务发现和流量路由机制。本章将彻底剖析这两个概念,并带领你通过实践,将我们的Nginx应用真正地暴露给世界。
3.1 Service:集群内部的“负载均衡器”
我们知道,Pod是短暂的、有生命周期的。当一个Pod因为节点故障、应用更新或水平缩容而被销毁并重建时,它的IP地址会发生改变。如果我们的应用(比如一个前端Pod)直接通过IP地址去调用另一个应用(比如一个后端Pod),那么这种依赖关系将是极其脆弱和不可靠的。
Service正是为了解决这个核心痛点而设计的。它为一组功能相同的Pod提供了一个稳定、统一的访问入口。
你可以将Service理解为一个抽象层,它定义了一个逻辑上的Pod集合以及访问这个集合的策略。当一个Service被创建时,它会被分配一个虚拟的、在集群内部唯一的IP地址(称为ClusterIP)和一个DNS名称。这两个标识符在Service的整个生命周期内都是固定不变的。
当集群内的任何客户端(例如另一个Pod)向这个Service的ClusterIP或DNS名称发起请求时,Kubernetes的网络代理(kube-proxy)会拦截这些流量,并以负载均衡的方式将其转发到该Service后端所关联的某个健康的Pod上。
Service是如何找到它应该代理的Pod的?
答案是我们在上一章学习的Label和Selector。Service通过其定义中的selector字段来匹配Pod的label。凡是标签与selector匹配的Pod,都会被自动加入到Service的端点(Endpoints)列表中。这是一个动态的过程:当一个新的、标签匹配的Pod被创建时,它会自动成为Service的后端;当一个Pod被删除或变得不健康时,它会自动从端点列表中移除。
Service的三种主要类型
Kubernetes提供了不同类型的Service,以满足不同的暴露需求。你在创建Service时,通过spec.type字段来指定。
| Service类型 | 描述 | 主要使用场景 |
|---|---|---|
| ClusterIP | 默认类型。Service只在集群内部可见,它会被分配一个内部的ClusterIP。从集群外部无法直接访问。 | 集群内部服务间通信。这是最常用的类型,用于微服务架构中后端服务之间的相互调用(例如,订单服务调用用户服务)。 |
| NodePort | 在ClusterIP的基础上,额外在每个工作节点(Node)的物理IP上开放一个静态的、固定的端口(NodePort)。端口范围通常是30000-32767。任何发送到<NodeIP>:<NodePort>的流量都会被转发到该Service。 |
|
| LoadBalancer | 在NodePort的基础上,进一步与云服务提供商(如AWS, GCP, Azure)的负载均衡器集成。当你创建此类型的Service时,K8s会向云平台请求一个外部负载均衡器,该负载均衡器会被分配一个公网IP地址,并将流量路由到集群中所有节点的NodePort上。 | 将服务暴露给公网的标准方式。当你需要在公有云上为你的应用提供一个稳定的公网访问入口时,应使用此类型。这也是将Ingress Controller暴露给外部的常用方法。 |
重要说明:
NodePort是ClusterIP的超集。创建一个NodePort类型的Service时,K8s会自动创建一个ClusterIP。LoadBalancer是NodePort的超集。创建一个LoadBalancer类型的Service时,K8s会自动创建一个NodePort和一个ClusterIP。流量路径为:外部客户端 -> 云负载均衡器IP -> 任意节点的NodePort -> Service的ClusterIP -> 目标Pod。
3.2 服务发现: K8s如何通过DNS和环境变量实现服务间的通信
现在我们知道Service提供了一个稳定的入口,但一个Pod如何“发现”这个入口的地址呢?Kubernetes主要提供了两种内建的服务发现机制。
1. DNS-based Service Discovery (基于DNS的服务发现) - 推荐方式
这是Kubernetes中最常用也是最推荐的服务发现方式。每个Kubernetes集群内部都会运行一个DNS服务(例如CoreDNS),它负责解析集群内部的服务名称。
当一个Service被创建时,DNS服务会自动为其创建一条DNS记录。这条记录的格式通常是:my-svc.my-namespace.svc.cluster.local
my-svc: Service的名称。my-namespace: Service所在的命名空间。svc.cluster.local: 集群内部的域名后缀,可以自定义。
这个机制的美妙之处在于:
- 同命名空间内: 如果一个Pod和它想访问的Service在同一个
namespace下,它只需要直接使用Service的名称 (my-svc)作为域名即可。Pod内的DNS解析器会自动补全后缀,将其解析为Service的ClusterIP。 - 跨命名空间: 如果需要访问其他
namespace下的服务,则需要使用更完整的域名,如my-svc.my-namespace。
这种方式是动态的和解耦的。你的应用程序代码中只需要硬编码一个稳定的服务名称,而不需要关心其IP地址,这完全符合微服务的设计理念。
2. Environment Variable-based Service Discovery (基于环境变量的服务发现)
这是一种较早的机制。当一个Pod被创建时,kubelet会为在该Pod创建之前就已经存在的所有Service注入一系列环境变量。
例如,如果有一个名为redis-master的Service,其ClusterIP为10.0.0.11,端口为6379,那么在新创建的Pod中,你会看到类似以下的环境变量:
REDIS_MASTER_SERVICE_HOST=10.0.0.11
REDIS_MASTER_SERVICE_PORT=6379
REDIS_MASTER_PORT=tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP=tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP_PROTO=tcp
REDIS_MASTER_PORT_6379_TCP_PORT=6379
REDIS_MASTER_PORT_6379_TCP_ADDR=10.0.0.11
主要缺点:
这种方式有一个致命的缺陷:强依赖于启动顺序。如果你的Pod A依赖于Service B,但Pod A在Service B创建之前就已经启动了,那么Pod A中将不会包含Service B的环境变量。这在复杂的、动态的微服务环境中会造成严重问题。因此,强烈建议优先使用DNS进行服务发现。
3.3 Ingress:集群外部流量的入口
我们已经讨论了如何通过NodePort和LoadBalancer类型的Service将服务暴露给外部。但这两种方式存在一些局限性:
NodePort: 管理和记忆端口号很麻烦,而且通常不允许使用80/443等标准端口,不适合生产环境。LoadBalancer: 每暴露一个服务就需要一个公网IP和一台云负载均衡器,这在有大量服务的场景下成本非常高昂。- 功能局限: Service工作在OSI模型的第四层(传输层),它只关心IP和端口,进行TCP/UDP流量的转发。它无法理解HTTP/HTTPS协议,因此无法实现更高级的路由功能,比如:
- 根据域名进行路由(
api.example.com转发到API服务,shop.example.com转发到商店服务)。 - 根据URL路径进行路由(
example.com/api转发到API服务,example.com/ui转发到前端服务)。 - SSL/TLS证书的终结和管理。
- 根据域名进行路由(
为了解决这些问题,Kubernetes引入了一个更高层次的抽象:Ingress。
Ingress是Kubernetes中一个API对象,它定义了从集群外部访问内部服务的HTTP和HTTPS路由规则。你可以把它看作是集群流量的“总入口”或“API网关”。它工作在OSI模型的第七层(应用层)。
Ingress Controller的重要性
一个非常关键的点是:仅仅在集群中创建一个Ingress资源对象是没有任何效果的!
Ingress资源本身只是一份静态的路由规则声明。你必须在集群中运行一个Ingress Controller(Ingress控制器),它才是真正实现这些规则的“大脑”和“执行者”。
Ingress Controller是一个持续运行的Pod,它会通过Kubernetes API监视集群中所有的Ingress资源。当检测到Ingress资源的创建、更新或删除时,它会动态地更新其内部的负载均衡器配置(例如,修改Nginx的nginx.conf文件),使Ingress规则生效。
常见的Ingress Controller实现包括:
- NGINX Ingress Controller (由Kubernetes社区维护)
- Traefik
- HAProxy Ingress
- 以及各大云厂商提供的专有实现(如GKE Ingress Controller)
工作流程:
- 管理员部署一个Ingress Controller到集群中。这个Controller本身通常通过一个
LoadBalancer或NodePort类型的Service暴露到公网。 - 用户创建一个或多个Service,用于暴露内部的应用(通常是
ClusterIP类型)。 - 用户创建一个Ingress资源,定义路由规则,例如:“所有访问
app.example.com/path的HTTP请求,都应转发到名为my-app-service的服务的80端口”。 - Ingress Controller监听到这个新的Ingress资源,并更新自身的路由配置。
- 外部用户的DNS将
app.example.com解析到Ingress Controller暴露的公网IP。 - 流量到达Ingress Controller,Controller检查请求的Host和Path,根据Ingress规则,将请求代理到后端的
my-app-service,最终到达应用Pod。
通过这种方式,我们可以使用一个公网IP和负载均衡器,来为集群中成百上千个服务提供统一的、基于HTTP/HTTPS的外部访问入口,极大地节约了成本并简化了管理。
3.4 实战: 为Nginx应用创建Service和Ingress,并从外部访问
让我们回到上一章创建的nginx-deployment。现在,我们将一步步为它创建Service和Ingress,最终通过浏览器访问它。
(前提:确保你有一个运行中的K8s集群,并且nginx-deployment已成功部署并运行着3个副本。如果你使用Minikube,请先执行 minikube addons enable ingress 来安装Ingress Controller。)
步骤1:创建一个ClusterIP类型的Service
首先,我们为Nginx Pod创建一个ClusterIP类型的Service,让它们在集群内部可以被稳定地访问。
创建一个名为 nginx-service.yaml 的文件:
# nginx-service.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-service # Service的名称
spec:
type: ClusterIP # 指定Service类型为ClusterIP
selector:
app: nginx # 这个selector必须与Deployment管理的Pod的label完全匹配
ports:
- protocol: TCP
port: 80 # Service暴露的端口
targetPort: 80 # 流量将被转发到Pod内容器的这个端口
应用该文件:
kubectl apply -f nginx-service.yaml
查看Service状态:
kubectl get service nginx-service
# 或者简写为 kubectl get svc nginx-service
你会看到如下输出,其中CLUSTER-IP就是这个Service的虚拟IP:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-service ClusterIP 10.108.112.55 <none> 80/TCP 30s
此时,你还无法从外部访问它。但你可以从集群内部的另一个Pod来测试它:
# 启动一个临时的busybox Pod并进入其shell
kubectl run busybox --rm -it --image=busybox -- sh
# 在busybox的shell中,使用Service的DNS名称来访问Nginx
/ # wget -q -O - http://nginx-service
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
</html>
/ # exit
这证明了我们的Service和DNS服务发现机制工作正常。
步骤2:创建一个Ingress规则
现在,我们来定义Ingress规则,将外部流量路由到我们刚刚创建的nginx-service。
创建一个名为 nginx-ingress.yaml 的文件:
# nginx-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx-ingress
spec:
rules:
- host: nginx.example.com # 我们希望通过这个域名来访问
http:
paths:
- path: / # 访问根路径
pathType: Prefix
backend:
service:
name: nginx-service # 将流量转发到这个Service
port:
number: 80 # Service的端口
应用该文件:
kubectl apply -f nginx-ingress.yaml
查看Ingress状态:
kubectl get ingress
输出应该类似这样,ADDRESS列可能需要一小段时间才会分配好IP地址:
NAME CLASS HOSTS ADDRESS PORTS AGE
nginx-ingress <none> nginx.example.com 192.168.49.2 80 15s
这里的 ADDRESS 就是你的Ingress Controller的入口IP。在Minikube中,它就是Minikube虚拟机的IP。
步骤3:配置本地DNS解析并访问
由于nginx.example.com是一个虚构的域名,你需要告诉你的电脑如何找到它。我们需要修改本地的hosts文件,将域名映射到Ingress Controller的IP地址。
-
获取Ingress IP:
kubectl get ingress命令的ADDRESS列就是。如果是Minikube,也可以用minikube ip命令获取。 -
修改hosts文件:
- Linux/macOS:
sudo nano /etc/hosts - Windows: 以管理员身份打开记事本,编辑
C:\Windows\System32\drivers\etc\hosts
在文件末尾添加一行 (将
192.168.49.2替换为你的实际IP):192.168.49.2 nginx.example.com保存文件。
- Linux/macOS:
步骤4:验证
现在,打开你的浏览器,访问 http://nginx.example.com。
你应该能看到熟悉的Nginx欢迎页面!
祝贺你!你已经成功地通过Service和Ingress,将一个部署在Kubernetes集群深处的应用,以一种健壮、可扩展且符合生产标准的方式暴露给了外部世界。
【重难点】 辨析Service和Ingress的区别与联系,理解不同Service类型的使用场景
这是面试和实际工作中经常被问到和混淆的地方。掌握它们的本质区别至关重要。
核心区别与联系:一个生动的比喻
想象一个大型的、现代化的办公园区(Kubernetes集群):
- Pods: 园区里的各个办公室,员工(容器)在里面工作。办公室的门牌号(Pod IP)可能会因为部门调整而改变。
- Service (ClusterIP): 园区的内部电话分机系统。你想找“财务部”,只需要拨打分机号“8001”(Service的DNS名
finance-service),电话总机(kube-proxy)就会自动帮你转接到当前有人在的财务部办公室(健康的Pod)。这个系统只能在园区内部使用。 - Service (NodePort): 园区为每个部门开设的一个指定的后勤通道(例如,30080号门)。外部的快递员(外部请求)可以直接通过这个门找到对应的部门。但这种方式不正式,也不方便管理,不适合重要客户来访。
- Ingress Controller: 园区的总前台和接待大厅。它有园区唯一的、气派的正门地址(Ingress Controller的公网IP)。所有访客都先到这里。
- Ingress 规则: 摆放在接待大厅里的访客指南/公司名录。它告诉前台接待员(Ingress Controller)如何引导访客:
- “找 Acme 公司的 (Host:
acme.example.com),请去A座(acme-service)。” - “找 Stark 工业的 (Host:
stark.example.com),并且是去研发部的 (/api),请去B座(stark-api-service);如果是去产品展示厅的 (/showcase),请去C座(stark-ui-service)。”
- “找 Acme 公司的 (Host:
总结这个比喻:
- Service 主要负责集群内部的“南北向”(从Service到Pod)和“东西向”(Pod到Pod)的4层网络通信,提供稳定的内部端点和负载均衡。
- Ingress 主要负责集群外部到内部的“南北向”7层流量路由,是HTTP/HTTPS流量的“智能网关”。
- 联系: Ingress依赖于Service。Ingress本身不直接与Pod通信,它的路由规则的最终目的地是一个Service。Ingress将流量转发给匹配的Service,再由Service将流量分发给后端的Pod。
对比表格
| 特性 | Service | Ingress |
|---|---|---|
| OSI模型层级 | 第4层 (传输层) - TCP/UDP | 第7层 (应用层) - HTTP/HTTPS |
| 核心功能 |
|
|
| 作用域 | 集群内部 (主要) | 从集群外部到内部 |
| 通信协议 | 任意基于TCP/UDP的协议 | 主要是HTTP和HTTPS |
| 实现方式 | 由kube-proxy组件在每个节点上通过iptables/IPVS实现 |
需要部署一个单独的Ingress Controller Pod来实现 |
| 成本 (云环境) | LoadBalancer类型为每个服务创建一个云负载均衡器,成本高 |
可通过一个Ingress Controller(对应一个云负载均衡器)管理多个服务,成本效益高 |
不同Service类型的使用场景选择
-
ClusterIP: 默认和首选。当你不需要从集群外部访问服务时,都应该使用它。这是90%的微服务间通信场景的选择。 -
NodePort:- 临时调试: 当你想快速、临时地从你的笔记本电脑访问集群里的某个服务时,这是一个简单的方法。
- 特殊协议: 当你需要暴露一个非HTTP的TCP/UDP服务,且不想使用昂贵的
LoadBalancer时(例如,一个临时的数据库连接)。 - Ingress Controller的入口: Ingress Controller本身也需要被外部访问,通常会使用
NodePort或LoadBalancer类型的Service来暴露自己。
-
LoadBalancer:- 暴露非HTTP服务: 当你需要为一个非HTTP/HTTPS的服务(如数据库、消息队列、游戏服务器)提供一个稳定的、面向公网的IP地址时,这是最直接、最标准的方式。
- 简单场景: 如果你只有一个服务需要暴露,并且不需要复杂的路由规则,使用
LoadBalancer比部署和管理Ingunress更简单。
-
Ingress: 暴露HTTP/HTTPS服务的标准生产方案。
- 当你需要将多个服务聚合在同一个公网IP下时。
- 当你需要根据域名或URL路径进行智能路由时。
- 当你需要集中管理SSL证书,实现HTTPS时。
掌握了Service和Ingress,你就掌握了Kubernetes中服务网络通信的任督二脉,能够自如地构建、连接和暴露你的微服务应用。
K8s 详细学习笔记 目录
以下是整个系列的10章目录,点击章节标题即可跳转阅读:
- K8s详细学习笔记 第一章:K8s入门与核心概念
- K8s详细学习笔记 第二章:部署你的第一个应用:Pod与Deployment
- K8s详细学习笔记 第三章:应用访问与服务发现:Service与Ingress
- K8s详细学习笔记 第四章:应用配置管理:ConfigMap与Secret
- K8s详细学习笔记 第五章:数据持久化:Volume与Persistent Storage
- K8s详细学习笔记 第六章:应用的健康检查与自愈能力
- K8s详细学习笔记 第七章:资源的限制与调度
- K8s详细学习笔记 第八章:应用的扩缩容与更新策略
- K8s详细学习笔记 第九章:K8s安全机制:认证、授权与准入控制
- K8s详细学习笔记 第十章:集群运维与故障排查
更多推荐
所有评论(0)