三、Service 详解
1. Service是什么,为什么需要它
1.1 回顾Pod的问题
在上一章,我们学会了创建Pod,并且让nginx通过数据库的IP地址(比如10.244.1.10)进行连接。但这里有个大问题:
Pod的IP是不稳定的!
想象一下这些场景:
- Pod重启了,IP地址会变
- Pod被调度到其他节点,IP地址会变
- Pod被删除重建,IP地址还会变
- Deployment管理多个Pod副本,每个Pod的IP都不一样
这就像你朋友搬家了,但没告诉你新地址。 你每次想找他,都得重新打听他住哪儿,这太麻烦了!
我们需要一个稳定的”门牌号”,无论Pod怎么变化,都能通过这个门牌号找到它。这就是Service的作用。
1.2 Service是什么
Service(服务) 是Kubernetes中一个抽象概念,它定义了一组Pod的逻辑集合,以及访问这些Pod的策略。
用生活化的比喻来理解:
想象一个大型超市:
Pod = 收银员(可能会换班、休假、离职)Service = 收银台的"2号窗口"标识
顾客(客户端)只需要认准"2号窗口",不用关心今天是哪个收银员在值班。超市可以随意调整收银员,只要保证2号窗口有人服务就行。Service的核心特点:
-
稳定的访问入口
- Service有一个固定的IP地址(ClusterIP)
- Service有一个固定的DNS名称(如:mysql-service)
- 无论后端Pod如何变化,Service的IP和名称都不变
-
负载均衡
- 如果有多个Pod副本,Service会自动分发流量
- 就像银行有多个窗口,叫号系统自动分配
-
服务发现
- 通过Service名称就能访问,无需记住IP
- 集群内的DNS自动解析Service名称
1.3 为什么需要Service
问题1:Pod IP不稳定
没有Service的情况:┌─────────────┐ ┌─────────────┐│ Nginx Pod │ -------> │ MySQL Pod ││ │ 硬编码IP │ 10.244.1.10│└─────────────┘ └─────────────┘ ↓ Pod重启 ┌─────────────┐ │ MySQL Pod │ │ 10.244.2.20│ <- 新IP,nginx连不上了! └─────────────┘
有Service的情况:┌─────────────┐ ┌──────────────┐ ┌─────────────┐│ Nginx Pod │ -------> │ MySQL Service│ -------> │ MySQL Pod ││ │ 稳定域名 │ mysql-svc │ 自动追踪 │ 任何IP都行 │└─────────────┘ └──────────────┘ └─────────────┘问题2:多副本负载均衡
没有Service:怎么知道有几个Pod?要手动配置负载均衡┌──────────┐ ???│ Client │ --------> ┌──────────┐└──────────┘ │ Pod 1 │ └──────────┘ ┌──────────┐ │ Pod 2 │ └──────────┘ ┌──────────┐ │ Pod 3 │ └──────────┘
有Service:自动负载均衡┌──────────┐ ┌─────────────┐│ Client │ --------> │ Service │ ----轮询----> ┌──────────┐└──────────┘ │ 自动分发流量 │ │ Pod 1 │ └─────────────┘ ┌─────────> ┌──────────┐ │ │ Pod 2 │ └─────────> └──────────┘ ┌──────────┐ │ Pod 3 │ └──────────┘问题3:服务发现困难
传统方式:需要配置中心、注册中心(如Consul、Eureka)┌──────────┐ 1.注册 ┌─────────────┐│ MySQL │ -----------> │ 注册中心 │└──────────┘ └─────────────┘ ↑ 2.查询 ┌──────────┐ │ Nginx │ └──────────┘
K8s方式:Service + CoreDNS 自动搞定┌──────────┐ ┌──────────┐│ MySQL │ <--自动发现-- │ Service │ <-- DNS解析 -- Nginx└──────────┘ └──────────┘ 只需要知道服务名1.4 Service的工作原理
Service由三个核心组件协作实现:
- Service对象本身
- kube-proxy(网络代理)
- CoreDNS(DNS服务)
完整的工作流程:
1.5 Service与关联组件详解
1.5.1 kube-proxy:Service的实现者
{{ … }}
kube-proxy是什么?
kube-proxy是运行在每个Node上的网络代理组件,负责实现Service的网络转发功能。
kube-proxy的工作模式:
模式1:iptables模式(默认)
原理:使用Linux iptables规则实现流量转发
工作流程:请求:curl http://10.96.100.50:3306 ↓iptables规则拦截(PREROUTING链) ↓DNAT转换:10.96.100.50 -> 10.244.1.10(随机选择一个Pod) ↓到达Pod
优点: ✓ 性能好,在内核层面工作 ✓ 不需要额外模块
缺点: ✗ Service/Pod数量多时,iptables规则会爆炸式增长 ✗ 规则匹配是线性的,性能下降 ✗ 负载均衡算法单一(只有随机)示例:查看iptables规则
# 查看Service相关的iptables规则iptables -t nat -L | grep mysql-service
# 你会看到类似这样的规则:# -A KUBE-SERVICES -d 10.96.100.50/32 -p tcp -m tcp --dport 3306 -j KUBE-SVC-XXXXX# -A KUBE-SVC-XXXXX -m statistic --mode random --probability 0.5 -j KUBE-SEP-AAAA# -A KUBE-SVC-XXXXX -j KUBE-SEP-BBBB# -A KUBE-SEP-AAAA -p tcp -j DNAT --to-destination 10.244.1.10:3306# -A KUBE-SEP-BBBB -p tcp -j DNAT --to-destination 10.244.2.20:3306模式2:IPVS模式(推荐,生产环境必备)
原理:使用Linux IPVS(IP Virtual Server)内核模块
IPVS是什么? - Linux内核的四层负载均衡器 - 专为高性能负载均衡设计 - 比iptables快得多
工作流程:请求:curl http://10.96.100.50:3306 ↓IPVS规则拦截(内核netfilter) ↓根据负载均衡算法选择后端(可选rr、lc、dh等) ↓DNAT转换:10.96.100.50 -> 10.244.1.10 ↓到达Pod
优点: ✓ 性能极高,使用hash表查找,O(1)复杂度 ✓ 支持多种负载均衡算法: - rr(轮询) - lc(最少连接) - dh(目标地址hash) - sh(源地址hash) - sed(最短期望延迟) - nq(永不排队) ✓ 支持大规模集群(可处理数万Service) ✓ 更好的性能:连接调度更快
缺点: ✗ 需要加载内核模块 ✗ 配置稍复杂(但在生产环境中这不是问题)iptables vs IPVS 性能对比:
场景:1000个Service,每个Service有10个Pod
iptables模式: - 规则数量:10,000+条 - 规则匹配:线性查找 O(n) - 流量转发延迟:随Service数量增加显著上升 - 创建/删除Service:需要重建大量规则,耗时
IPVS模式: - 规则数量:1,000个虚拟服务 + 10,000个后端 - 规则匹配:哈希查找 O(1) - 流量转发延迟:恒定,不受Service数量影响 - 创建/删除Service:只操作对应条目,快速DNAT是什么?
DNAT(Destination Network Address Translation,目标网络地址转换)
简单理解:把数据包的目标地址改掉
原始请求: 源IP: 10.244.3.5 (nginx pod) 目标IP: 10.96.100.50 (Service ClusterIP) 目标端口: 3306
经过DNAT转换后: 源IP: 10.244.3.5 (不变) 目标IP: 10.244.1.10 (改成了Pod IP) 目标端口: 3306 (不变)
这个转换对客户端是透明的,nginx pod不知道目标地址被改了。完整的网络包流转:
┌──────────────────────────────────────────────────────────────┐│ Nginx Pod (10.244.3.5) ││ 执行:mysql_connect("mysql-service", ...) │└──────────────────────────────────────────────────────────────┘ ↓ DNS解析┌──────────────────────────────────────────────────────────────┐│ CoreDNS ││ mysql-service -> 10.96.100.50 │└──────────────────────────────────────────────────────────────┘ ↓ 发送TCP连接请求┌──────────────────────────────────────────────────────────────┐│ 数据包(出站) ││ SRC: 10.244.3.5:随机端口 ││ DST: 10.96.100.50:3306 │└──────────────────────────────────────────────────────────────┘ ↓ 经过kube-proxy(iptables/IPVS)┌──────────────────────────────────────────────────────────────┐│ DNAT转换 ││ 规则:10.96.100.50:3306 -> 10.244.1.10:3306 (Pod1) │└──────────────────────────────────────────────────────────────┘ ↓┌──────────────────────────────────────────────────────────────┐│ 数据包(转换后) ││ SRC: 10.244.3.5:随机端口 ││ DST: 10.244.1.10:3306 <- 已经改成Pod IP │└──────────────────────────────────────────────────────────────┘ ↓ 路由到目标Node┌──────────────────────────────────────────────────────────────┐│ MySQL Pod (10.244.1.10) ││ 接收连接,处理请求 │└──────────────────────────────────────────────────────────────┘ ↓ 返回响应┌──────────────────────────────────────────────────────────────┐│ 响应数据包 ││ SRC: 10.244.1.10:3306 ││ DST: 10.244.3.5:随机端口 │└──────────────────────────────────────────────────────────────┘ ↓ 经过kube-proxy(反向SNAT)┌──────────────────────────────────────────────────────────────┐│ SNAT转换(自动) ││ 10.244.1.10:3306 -> 10.96.100.50:3306 ││ (让nginx以为是Service回复的) │└──────────────────────────────────────────────────────────────┘ ↓┌──────────────────────────────────────────────────────────────┐│ Nginx Pod收到响应 ││ 看到的源地址:10.96.100.50:3306 ││ (完全不知道实际是10.244.1.10) │└──────────────────────────────────────────────────────────────┘1.5.2 CoreDNS:Service的服务发现
CoreDNS是什么?
CoreDNS是Kubernetes集群的DNS服务器,负责为Service提供域名解析。
DNS记录格式:
完整格式:<service-name>.<namespace>.svc.<cluster-domain>
示例:mysql-service.default.svc.cluster.local
简写方式(同namespace内):mysql-service.defaultmysql-service <- 最常用
跨namespace访问:mysql-service.productionCoreDNS工作原理:
┌─────────────────────────────────────────────────────────────┐│ 1. Pod创建时,kubelet自动配置DNS ││ ││ Pod内的 /etc/resolv.conf: ││ nameserver 10.96.0.10 <- CoreDNS的Service IP ││ search default.svc.cluster.local svc.cluster.local ││ options ndots:5 │└─────────────────────────────────────────────────────────────┘ ↓┌─────────────────────────────────────────────────────────────┐│ 2. 应用发起DNS查询 ││ curl http://mysql-service:3306 │└─────────────────────────────────────────────────────────────┘ ↓┌─────────────────────────────────────────────────────────────┐│ 3. DNS查询发送到CoreDNS (10.96.0.10) ││ 查询:mysql-service.default.svc.cluster.local 的A记录 │└─────────────────────────────────────────────────────────────┘ ↓┌─────────────────────────────────────────────────────────────┐│ 4. CoreDNS监听etcd,实时知道所有Service信息 ││ 返回:10.96.100.50 (Service的ClusterIP) │└─────────────────────────────────────────────────────────────┘ ↓┌─────────────────────────────────────────────────────────────┐│ 5. 应用使用解析出的IP访问 ││ curl http://10.96.100.50:3306 │└─────────────────────────────────────────────────────────────┘查看CoreDNS:
# 查看CoreDNS Podkubectl get pods -n kube-system | grep coredns
# 查看CoreDNS配置kubectl get configmap coredns -n kube-system -o yaml
# 测试DNS解析kubectl run test-dns --image=reg.westos.org/library/busybox:1.30.1 --rm -it --restart=Never -- nslookup mysql-service1.6 Service如何关联到Pod:Label和Selector
Label(标签)
Label是附加到K8s对象上的键值对,用于标识和分类资源。
# Pod定义中的Labelmetadata: labels: app: mysql # 应用名称 version: "8.0" # 版本 env: production # 环境Selector(选择器)
Selector用于根据Label选择一组资源。
# Service定义中的Selectorspec: selector: app: mysql # 选择所有 app=mysql 的Pod完整的关联流程:
┌─────────────────────────────────────────────────────────────┐│ 1. 定义Pod,打上标签 ││ ││ apiVersion: v1 ││ kind: Pod ││ metadata: ││ name: mysql-pod-1 ││ labels: ││ app: mysql <- 标签1 ││ version: "8.0" <- 标签2 │└─────────────────────────────────────────────────────────────┘ ↓┌─────────────────────────────────────────────────────────────┐│ 2. 定义Service,使用Selector ││ ││ apiVersion: v1 ││ kind: Service ││ metadata: ││ name: mysql-service ││ spec: ││ selector: ││ app: mysql <- 选择器:匹配 app=mysql 的Pod ││ ports: ││ - port: 3306 │└─────────────────────────────────────────────────────────────┘ ↓┌─────────────────────────────────────────────────────────────┐│ 3. Endpoint Controller自动匹配 ││ ││ 查找所有 app=mysql 的Pod: ││ - mysql-pod-1 (10.244.1.10) ✓ 匹配 ││ - mysql-pod-2 (10.244.2.20) ✓ 匹配 ││ - nginx-pod (10.244.3.5) ✗ 不匹配(标签不同) ││ ││ 创建Endpoint对象: ││ mysql-service: ││ - 10.244.1.10:3306 ││ - 10.244.2.20:3306 │└─────────────────────────────────────────────────────────────┘ ↓┌─────────────────────────────────────────────────────────────┐│ 4. Pod动态变化,自动更新 ││ ││ 新增Pod:mysql-pod-3 (标签: app=mysql) ││ → Endpoint自动添加:10.244.1.30:3306 ││ ││ Pod删除:mysql-pod-1 ││ → Endpoint自动移除:10.244.1.10:3306 ││ ││ Pod未就绪(Readiness探针失败): ││ → Endpoint临时移除,不再转发流量 │└─────────────────────────────────────────────────────────────┘Selector的匹配规则:
# 1. 精确匹配(AND关系)selector: app: mysql version: "8.0"# 选择:app=mysql 且 version=8.0 的Pod
# 2. 集合匹配(Service不支持,Deployment支持)selector: matchLabels: app: mysql matchExpressions: - key: version operator: In values: ["8.0", "8.1"]# 选择:app=mysql 且 version in (8.0, 8.1)查看Label和Endpoint:
# 查看Pod的标签kubectl get pods --show-labels
# 根据标签过滤Podkubectl get pods -l app=mysql
# 查看Service的Endpointkubectl get endpoints mysql-service
# 详细信息kubectl describe endpoints mysql-service为什么使用Label/Selector而不是直接指定Pod名称?
直接指定Pod名称的问题: ✗ Pod名称会变(重建后名称不同) ✗ 无法动态添加新Pod ✗ 无法实现自动扩缩容 ✗ 管理困难(要手动维护列表)
Label/Selector的优势: ✓ 松耦合:Service不关心具体哪个Pod ✓ 动态:新Pod自动加入,旧Pod自动移除 ✓ 灵活:可以根据不同维度选择(环境、版本等) ✓ 符合K8s声明式理念2. Service的类型详解
Kubernetes提供了五种Service类型,每种类型适用于不同的访问场景。
2.1 ClusterIP:集群内部访问(默认类型)
什么是ClusterIP?
ClusterIP是Service的默认类型,K8s会为Service分配一个集群内部的虚拟IP地址,这个IP只能在集群内部访问。
典型使用场景:
场景:前端应用访问后端数据库
┌────────────────── K8s Cluster ──────────────────┐│ ││ ┌──────────┐ ClusterIP ┌──────────┐ ││ │ Nginx │ --------------> │ MySQL │ ││ │ Pod │ mysql-service │ Pods │ ││ └──────────┘ 10.96.100.50 └──────────┘ ││ ││ 特点:只能集群内部访问,外部无法直接访问 │└─────────────────────────────────────────────────┘YAML示例:
apiVersion: v1kind: Servicemetadata: name: mysql-servicespec: type: ClusterIP # 可以省略,默认就是ClusterIP selector: app: mysql # 选择标签为app=mysql的Pod ports: - port: 3306 # Service对外暴露的端口 targetPort: 3306 # Pod容器的端口 protocol: TCP重要字段解释:
| 字段 | 说明 | 示例 |
|---|---|---|
| port | Service暴露的端口(客户端访问这个端口) | 3306 |
| targetPort | Pod容器监听的端口(流量转发到这个端口) | 3306 |
| protocol | 协议类型 | TCP/UDP |
port 和 targetPort 的区别:
示例场景:Service port=80, targetPort=8080
客户端请求:curl http://my-service:80 ↓ Service接收(port: 80) ↓ kube-proxy转发 ↓ 到达Pod容器(targetPort: 8080)
好处: - Service对外统一使用标准端口(如80) - 容器内部可以用任意端口(如8080) - 便于端口标准化管理查看ClusterIP:
# 创建Servicekubectl apply -f mysql-service.yaml
# 查看Service列表kubectl get svc# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE# mysql-service ClusterIP 10.96.100.50 <none> 3306/TCP 1m
# 查看详细信息kubectl describe svc mysql-service
# 查看Endpoint(后端Pod列表)kubectl get endpoints mysql-service2.2 NodePort:通过节点端口暴露服务
什么是NodePort?
NodePort在ClusterIP的基础上,会在每个Node节点上开放一个固定端口(范围:30000-32767),外部可以通过<任意NodeIP>:<NodePort>访问服务。
工作原理:
外部访问流程:
外部用户 ↓ 访问:http://192.168.100.21:30080┌──────────────────── K8s Cluster ────────────────────┐│ ││ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐││ │ Node1 │ │ Node2 │ │ Node3 │││ │ :30080 ←────┼──│ :30080 │ │ :30080 │││ └─────────────┘ └─────────────┘ └─────────────┘││ │ ││ └────────→ kube-proxy转发 ││ ↓ ││ ┌──────────────┐ ││ │ Service │ ││ │ ClusterIP │ ││ └──────────────┘ ││ ↓ ││ ┌───────────┼───────────┐ ││ ↓ ↓ ↓ ││ ┌────────┐ ┌────────┐ ┌────────┐ ││ │ Pod1 │ │ Pod2 │ │ Pod3 │ ││ └────────┘ └────────┘ └────────┘ ││ │└─────────────────────────────────────────────────────┘
特点: 1. 可以通过任意Node的IP访问 2. 即使Pod不在该Node上也能访问 3. 自动创建ClusterIPYAML示例:
apiVersion: v1kind: Servicemetadata: name: nginx-nodeportspec: type: NodePort selector: app: nginx ports: - port: 80 # ClusterIP端口 targetPort: 80 # Pod端口 nodePort: 30080 # Node端口(可选,不指定则自动分配) protocol: TCP三种访问方式:
# 方式1:集群内通过ClusterIP访问curl http://10.96.100.60:80
# 方式2:集群内通过Service名称访问curl http://nginx-nodeport:80
# 方式3:集群外通过NodePort访问(任意Node IP都可以)curl http://192.168.100.21:30080curl http://192.168.100.22:30080curl http://192.168.100.20:30080NodePort端口范围:
# 默认范围:30000-32767# 可以在kube-apiserver启动参数中修改:# --service-node-port-range=30000-32767使用场景与限制:
✓ 适用场景: - 开发/测试环境快速暴露服务 - 小规模应用 - 临时访问需求
✗ 不适用场景: - 生产环境(端口不好记,不优雅) - 大量服务(端口容易冲突) - 需要域名和HTTPS(要额外配置)
生产环境建议: 使用LoadBalancer或Ingress2.3 LoadBalancer:云平台负载均衡器
什么是LoadBalancer?
LoadBalancer是在NodePort基础上,自动创建云平台的外部负载均衡器,提供统一的外部访问入口。
本质理解:
LoadBalancer = ClusterIP + NodePort + 云平台LB
创建流程:1. 自动创建ClusterIP2. 自动创建NodePort3. 调用云平台API创建外部负载均衡器4. 外部LB指向所有Node的NodePort工作原理(云平台环境):
┌─────────────────────────────────────────────────┐│ 外部用户 │└─────────────────────────────────────────────────┘ │ │ http://lb-12345.elb.amazonaws.com ↓┌─────────────────────────────────────────────────┐│ 云平台负载均衡器 (AWS ELB/阿里云SLB/腾讯云CLB) ││ 外部IP: 1.2.3.4 │└─────────────────────────────────────────────────┘ │ ┌────────────┼────────────┐ │ │ │ ↓ :30080 ↓ :30080 ↓ :30080 ┌─────────┐ ┌─────────┐ ┌─────────┐ │ Node1 │ │ Node2 │ │ Node3 │ └─────────┘ └─────────┘ └─────────┘ │ │ │ └────────────┼────────────┘ ↓ ┌──────────────┐ │ Service │ │ ClusterIP │ └──────────────┘ ↓ ┌───────────┼───────────┐ ↓ ↓ ↓ ┌────────┐ ┌────────┐ ┌────────┐ │ Pod1 │ │ Pod2 │ │ Pod3 │ └────────┘ └────────┘ └────────┘YAML示例:
apiVersion: v1kind: Servicemetadata: name: nginx-lbspec: type: LoadBalancer selector: app: nginx ports: - port: 80 targetPort: 80查看LoadBalancer:
kubectl apply -f nginx-lb.yaml
kubectl get svc nginx-lb# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE# nginx-lb LoadBalancer 10.96.100.80 1.2.3.4 80:30123/TCP 2m# ↑ 云平台分配的外部IP
# 外部访问curl http://1.2.3.4云平台 vs 裸金属(本地集群):
云平台(AWS、阿里云、Azure、GCP等): ┌────────────────────────────────────────┐ │ 1. 创建LoadBalancer Service │ │ 2. Cloud Controller Manager检测到 │ │ 3. 调用云平台API创建LB │ │ 4. 云平台返回外部IP │ │ 5. K8s更新Service的EXTERNAL-IP字段 │ └────────────────────────────────────────┘ ✓ 完全自动化 ✓ 有真实外部IP ✓ 云平台管理,高可用 ✗ 需要付费
裸金属/本地集群: ┌────────────────────────────────────────┐ │ 1. 创建LoadBalancer Service │ │ 2. 没有Cloud Controller Manager │ │ 3. 无法调用云平台API │ │ 4. EXTERNAL-IP一直显示<pending> │ │ 5. LoadBalancer功能无法使用 │ └────────────────────────────────────────┘ ✗ 无外部LB ✗ 功能不可用 ✓ 解决方案:MetalLBMetalLB:裸金属环境的LoadBalancer实现
MetalLB是什么?
MetalLB是一个为裸金属Kubernetes集群提供LoadBalancer实现的开源项目,让本地集群也能使用LoadBalancer类型的Service。
MetalLB工作原理:
┌─────────────────────────────────────────────────┐│ 1. 创建LoadBalancer Service │└─────────────────────────────────────────────────┘ ↓┌─────────────────────────────────────────────────┐│ 2. MetalLB Controller监听 ││ - 从IP池分配一个外部IP ││ - 更新Service的EXTERNAL-IP字段 │└─────────────────────────────────────────────────┘ ↓┌─────────────────────────────────────────────────┐│ 3. MetalLB Speaker(DaemonSet,每个Node运行) ││ - Layer 2模式:通过ARP响应宣告IP ││ - BGP模式:通过BGP协议宣告路由 │└─────────────────────────────────────────────────┘MetalLB配置示例:
# IP地址池apiVersion: metallb.io/v1beta1kind: IPAddressPoolmetadata: name: first-pool namespace: metallb-systemspec: addresses: - 192.168.100.100-192.168.100.150 # IP范围
---# Layer 2模式配置apiVersion: metallb.io/v1beta1kind: L2Advertisementmetadata: name: l2-adv namespace: metallb-systemspec: ipAddressPools: - first-poolMetalLB两种模式对比:
| 特性 | Layer 2模式 | BGP模式 |
|---|---|---|
| 原理 | ARP欺骗,让Node声称拥有该IP | 通过BGP协议宣告路由 |
| 配置难度 | 简单,无需额外设备 | 需要支持BGP的路由器 |
| 单点问题 | 有(一个Node响应,故障时切换) | 无(ECMP真正的负载均衡) |
| 性能 | 受限于单Node带宽 | 更高,多Node分担 |
| 适用场景 | 小规模集群、简单网络 | 大规模集群、企业网络 |
| 故障切换 | ~10秒 | 秒级 |
Layer 2模式示例:
IP池:192.168.100.100-192.168.100.110
创建第1个LoadBalancer Service: → MetalLB分配:192.168.100.100 → Node1的Speaker响应该IP的ARP请求 → 外部访问192.168.100.100流量全部进入Node1 → Node1的kube-proxy转发到Pod
优点:简单,无需额外配置缺点:单Node处理所有流量,有单点瓶颈2.4 ExternalName:DNS CNAME映射
什么是ExternalName?
ExternalName是一种特殊的Service,它不选择Pod,而是返回一个DNS CNAME记录,将服务名映射到外部域名。
使用场景:
场景1:访问外部服务 应用需要访问外部数据库:db.external.com → 创建ExternalName Service指向该域名 → 应用代码使用Service名称 → 便于将来迁移到集群内部(只需修改Service定义)
场景2:服务迁移过渡期 旧服务在集群外:old-service.external.com → 先用ExternalName指向外部 → 逐步迁移到K8s内部 → 完成后改为ClusterIP类型 → 应用代码无需修改
场景3:跨集群服务引用 集群A需要访问集群B的服务 → 在集群A创建ExternalName指向集群BDNS解析对比:
普通Service:┌─────────────────────────────────────┐│ nslookup nginx-service ││ 返回:10.96.100.50 (ClusterIP) │└─────────────────────────────────────┘
ExternalName Service:┌─────────────────────────────────────┐│ nslookup nginx-external ││ 返回: ││ CNAME db.external.com │└─────────────────────────────────────┘YAML示例:
apiVersion: v1kind: Servicemetadata: name: nginx-externalspec: type: ExternalName externalName: mysql.external-domain.com # 注意:没有selector,没有ports测试:
# 创建Servicekubectl apply -f nginx-external.yaml
# 查看(没有ClusterIP)kubectl get svc nginx-external# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE# nginx-external ExternalName <none> mysql.external-domain.com <none> 1m
# DNS解析测试kubectl run test --image=reg.westos.org/library/busybox:1.30.1 --rm -it --restart=Never -- nslookup nginx-external# Server: 10.96.0.10# Name: nginx-external.default.svc.cluster.local# Address 1: mysql.external-domain.com# ↑ 返回CNAME,不是IP特点总结:
优点: ✓ 便于服务迁移(外部→内部) ✓ 应用代码使用统一的Service名称 ✓ 符合K8s服务发现模式
缺点: ✗ 不支持端口映射 ✗ 不提供负载均衡 ✗ 依赖外部DNS解析 ✗ 不经过kube-proxy
注意:ExternalName不创建Endpoint,纯DNS层面映射2.5 Headless Service:无头服务
什么是Headless Service?
Headless Service是一种特殊的ClusterIP Service,通过设置clusterIP: None,使DNS直接返回Pod IP列表,而不是Service IP。
为什么需要Headless Service?
普通Service的限制: - DNS只返回Service的ClusterIP - 客户端无法获知后端Pod列表 - 无法直接连接特定Pod - 必须通过Service代理
某些应用的需求: ✓ StatefulSet需要稳定的网络标识 ✓ 数据库主从复制(需要直连特定实例) ✓ 分布式系统(节点互相发现) ✓ 客户端自己做负载均衡DNS解析对比:
普通Service:┌─────────────────────────────────────┐│ nslookup nginx-service ││ 返回:10.96.100.50 (ClusterIP) │└─────────────────────────────────────┘
Headless Service:┌─────────────────────────────────────┐│ nslookup nginx-headless ││ 返回: ││ 10.244.1.10 (Pod1) ││ 10.244.2.20 (Pod2) ││ 10.244.3.30 (Pod3) │└─────────────────────────────────────┘YAML示例:
apiVersion: v1kind: Servicemetadata: name: nginx-headlessspec: clusterIP: None # 关键:设置为None selector: app: nginx ports: - port: 80 targetPort: 80StatefulSet + Headless Service:
# Headless ServiceapiVersion: v1kind: Servicemetadata: name: web-headlessspec: clusterIP: None selector: app: web ports: - port: 80
---# StatefulSetapiVersion: apps/v1kind: StatefulSetmetadata: name: webspec: serviceName: web-headless # 关联Headless Service replicas: 3 selector: matchLabels: app: web template: metadata: labels: app: web spec: containers: - name: nginx image: nginx:1.20 ports: - containerPort: 80DNS命名规则:
StatefulSet Pod的DNS格式:<pod-name>.<service-name>.<namespace>.svc.cluster.local
示例: web-0.web-headless.default.svc.cluster.local web-1.web-headless.default.svc.cluster.local web-2.web-headless.default.svc.cluster.local
特点: ✓ 每个Pod有固定的DNS名称 ✓ Pod重建后DNS名称不变 ✓ 可以直接访问特定Pod ✓ 适合有状态应用测试:
# 创建资源kubectl apply -f web-headless.yaml
# DNS测试kubectl run test --image=reg.westos.org/library/busybox:1.30.1 --rm -it --restart=Never -- \ nslookup web-headless
kubectl run test --image=reg.westos.org/library/busybox:1.30.1 --rm -it --restart=Never -- \ nslookup web-0.web-headless
# 访问特定Podkubectl run test --image=reg.westos.org/library/busybox:1.30.1 --rm -it --restart=Never -- \ nslookup web-0.web-headless# Address 1: 10.244.1.10典型使用场景:
1. 有状态应用(StatefulSet) - MySQL主从集群 - MongoDB副本集 - Redis Cluster - Kafka集群
2. 分布式系统 - Elasticsearch(节点互相发现) - ZooKeeper(选举和数据同步) - Etcd集群
3. 客户端负载均衡 - 应用自己选择Pod(如读写分离) - 根据数据分片选择特定Pod2.6 Service类型总结对比
| 类型 | ClusterIP | NodePort | LoadBalancer | ExternalName | Headless |
|---|---|---|---|---|---|
| ClusterIP | ✓ | ✓ | ✓ | ✗ | None |
| 访问范围 | 集群内部 | 集群内+NodePort | 集群内+外部LB | 转发外部 | 集群内部 |
| 外部访问 | ✗ | ✓ | ✓ | ✗ | ✗ |
| DNS解析 | Service IP | Service IP | Service IP | CNAME | Pod IP列表 |
| 负载均衡 | ✓ | ✓ | ✓ | ✗ | ✗ |
| 使用场景 | 内部服务 | 开发测试 | 生产环境 | 外部服务 | 有状态应用 |
| 依赖 | 无 | 无 | 云平台/MetalLB | 外部DNS | 无 |
选择建议:
内部服务通信:ClusterIP(默认)快速暴露服务(测试):NodePort生产环境对外暴露:LoadBalancer(云平台)或 Ingress访问外部服务:ExternalName有状态应用:Headless + StatefulSet3. Service实战操作
3.1 将kube-proxy切换到IPVS模式
在所有节点安装IPVS模块(若未安装):
# 在master、node1、node2上执行
# 加载IPVS内核模块modprobe -- ip_vsmodprobe -- ip_vs_rrmodprobe -- ip_vs_wrrmodprobe -- ip_vs_shmodprobe -- nf_conntrack
# 验证lsmod | grep -e ip_vs -e nf_conntrack
# 设置开机自动加载cat > /etc/modules-load.d/ipvs.conf <<EOFip_vsip_vs_rrip_vs_wrrip_vs_shnf_conntrackEOF
# 安装ipvsadm工具yum install -y ipvsadm ipset修改kube-proxy配置:
# 编辑ConfigMapkubectl edit configmap kube-proxy -n kube-system
# 找到mode字段,修改为:mode: "ipvs"ipvs: scheduler: "rr" # 轮询算法重启kube-proxy:
# 删除所有kube-proxy Pod,让其自动重建kubectl delete pod -n kube-system -l k8s-app=kube-proxy
# 查看状态kubectl get pods -n kube-system | grep kube-proxy验证IPVS模式:
# 在集群机器上查看IPVS规则ipvsadm -Ln
# 查看kube-proxy日志kubectl logs -n kube-system <kube-proxy-pod-name> | grep -i ipvs3.2 实验准备:改造nginx镜像支持通过Service名称访问数据库
为什么要这样做?
在生产环境中,我们绝不应该在应用中硬编码数据库的IP地址。原因很简单:
- Pod的IP是动态变化的,Pod重启后IP会改变
- 使用Service名称可以实现服务发现,K8s的DNS会自动解析
- 这是云原生应用的最佳实践
步骤1:创建支持环境变量配置的PHP应用(在harbor机器)
cd /root/k8s-demo/nginx
# 创建新版本的index.phpcat > index.php <<'EOF'<?phpheader('Content-Type: text/plain; charset=utf-8');
// 从环境变量读取配置(生产最佳实践)$db_host = getenv('DB_HOST') ?: 'localhost';$db_user = getenv('DB_USER') ?: 'root';$db_pass = getenv('DB_PASS') ?: '';$db_name = getenv('DB_NAME') ?: 'testdb';
echo "=== Nginx-PHP Container Info ==="."\\n";echo "nginx v2"\\n";echo "Hostname: " . gethostname() . "\\n";echo "Server IP: " . $_SERVER['SERVER_ADDR'] . "\\n";echo "\\n";echo "=== Database Configuration ==="."\\n";echo "DB Host: $db_host\\n";echo "DB User: $db_user\\n";echo "DB Name: $db_name\\n";echo "\\n";echo "=== Database Connection Test ==="."\\n";
// 尝试连接数据库$conn = @new mysqli($db_host, $db_user, $db_pass, $db_name);if ($conn->connect_error) { echo "Connection FAILED: " . $conn->connect_error . "\\n";} else { echo "Database connected successfully!\\n"; echo "MySQL Version: " . $conn->server_info . "\\n"; $conn->close();}?>EOF这段代码做了什么?
getenv('DB_HOST'):从环境变量读取数据库地址,这样我们就可以传入Service名称- 使用
@符号抑制连接错误,改为友好提示 - 输出清晰的信息,方便调试
步骤2:构建并推送新镜像
# 构建镜像(使用之前的Dockerfile)docker build -t reg.westos.org/library/nginx-php:v2 .
# 推送到私有仓库docker push reg.westos.org/library/nginx-php:v2
# 验证镜像已推送docker images | grep nginx-php3.3 实验:使用Pod演示各类Service
实验目标:
- 理解 ClusterIP、NodePort、LoadBalancer、Headless 四种Service类型的区别
- 掌握通过Service名称实现服务发现的方法
- 学会使用环境变量配置应用
实验架构:
┌─────────────────────────────────────────────────────┐│ Service Types ││ ││ Nginx Pods ──→ MySQL Pod ││ (通过Service名称访问数据库) ││ ││ ClusterIP : 集群内部访问 ││ NodePort : 暴露到节点端口 ││ LoadBalancer : 云平台负载均衡(可选) ││ Headless : DNS直接解析到Pod IP │└─────────────────────────────────────────────────────┘实验1 Service - 集群内部访问数据库
这是什么?
- ClusterIP是默认的Service类型
- 为Service分配一个集群内部的虚拟IP(只能在集群内访问)
- 所有访问这个IP的流量会被负载均衡到后端Pod
使用场景:
- 数据库、缓存等后端服务
- 微服务之间的内部调用
步骤1:创建MySQL Pod
cd /root/k8s-yaml
# 创建MySQL Pod配置文件cat > mysql-pod.yaml <<EOFapiVersion: v1kind: Podmetadata: name: mysql-pod labels: # 重要!Service通过这个标签找到Pod app: mysql tier: backendspec: containers: - name: mysql image: reg.westos.org/library/mysql:8.0 ports: - containerPort: 3306 name: mysql env: - name: MYSQL_ROOT_PASSWORD value: "Westos123" - name: MYSQL_DATABASE value: "testdb" # 健康检查 livenessProbe: tcpSocket: port: 3306 initialDelaySeconds: 30 periodSeconds: 10 readinessProbe: exec: command: - sh - -c - "mysqladmin ping -u root -p\${MYSQL_ROOT_PASSWORD}" initialDelaySeconds: 10 periodSeconds: 5EOF
# 创建Podkubectl apply -f mysql-pod.yaml
# 查看Pod状态(等待Running)kubectl get pods -l app=mysqlkubectl get pods mysql-pod -o wide参数详解:
labels: app: mysql:给Pod打标签,Service会通过selector选择这个标签containerPort: 3306:声明容器监听的端口livenessProbe:存活探针,检测MySQL进程是否存在readinessProbe:就绪探针,检测MySQL是否可以接受连接
步骤2:创建ClusterIP Service
# 创建Service配置文件cat > mysql-service.yaml <<EOFapiVersion: v1kind: Servicemetadata: name: mysql-servicespec: type: ClusterIP selector: app: mysql ports: - port: 3306 targetPort: 3306 protocol: TCP name: mysqlEOF
# 创建Servicekubectl apply -f mysql-service.yaml
# 查看Service详细信息kubectl get svc mysql-servicekubectl describe svc mysql-service
# 查看Endpoints(Service会自动发现后端Pod)kubectl get endpoints mysql-service参数详解:
selector: app: mysql:通过标签选择器,Service自动发现匹配的Podport: 3306对外暴露的端口(别人访问Service用这个端口) targetPort: 3306实际监听的端口(流量转发到Pod的这个端口) - Endpoints
自动创建,记录所有匹配Pod的IP和端口
为什么会自动关联?
1. Pod有标签:app=mysql2. Service有选择器:selector: app=mysql3. K8s自动匹配:Service发现所有带app=mysql标签的Pod4. 创建Endpoints:记录Pod的IP:Port列表5. 配置负载均衡:kube-proxy配置iptables/ipvs规则步骤3:测试Service的DNS解析
# 测试DNS解析(Service名称 → IP)kubectl run test-dns --image=reg.westos.org/library/busybox:1.30.1 --rm -it --restart=Never -- \ nslookup mysql-service
# 预期输出:# Server: 10.96.0.10# Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local## Name: mysql-service# Address 1: 10.96.100.10 mysql-service.default.svc.cluster.local
# 测试完整域名kubectl run test-dns --image=reg.westos.org/library/busybox:1.30.1 --rm -it --restart=Never -- \ nslookup mysql-service.default.svc.cluster.localDNS解析规则:
在K8s集群中,Service有多种DNS名称形式:
短名称(同namespace): mysql-service带namespace : mysql-service.default完整域名(FQDN): mysql-service.default.svc.cluster.local步骤4:测试数据库连接
# 使用MySQL客户端测试连接kubectl run mysql-client --image=reg.westos.org/library/mysql:8.0 --rm -it --restart=Never -- \ mysql -h mysql-service -uroot -pWestos123 -e "SELECT version();"
# 预期输出:# +-----------+# | version() |# +-----------+# | 8.0.x |# +-----------+组合运用:这个实验展示了什么?
- ✅ Pod通过标签和Service关联
- ✅ Service提供稳定的访问入口(名称和IP不变)
- ✅ DNS自动解析Service名称到ClusterIP
- ✅ 后端Pod可以动态变化,Service自动更新Endpoints
实验2 Service - 通过Service名称访问数据库
这是什么?
- NodePort在ClusterIP的基础上,额外在每个Node上开放一个端口(30000-32767)
- 可以通过
<NodeIP>:<NodePort>从集群外部访问Service - 流量路径:外部 → NodePort → Service → Pod
使用场景:
- 开发测试环境快速暴露服务
- 没有LoadBalancer的环境
步骤1:创建Nginx Pod(通过Service名称连接MySQL)
# 创建配置文件cat > nginx-pod-1.yaml <<EOFapiVersion: v1kind: Podmetadata: name: nginx-pod-1 labels: app: nginx version: v2spec: containers: - name: nginx-php image: reg.westos.org/library/nginx-php:v2 ports: - containerPort: 80 env: # 关键!使用Service名称而不是IP - name: DB_HOST value: "mysql-service" # ← 这里使用Service名称! - name: DB_USER value: "root" - name: DB_PASS value: "Westos123" - name: DB_NAME value: "testdb" # 健康检查 readinessProbe: httpGet: path: / port: 80 initialDelaySeconds: 5 periodSeconds: 3EOF
# 应用配置kubectl apply -f nginx-pod-1.yaml
# 查看Pod状态kubectl get pods nginx-pod-1 -o wide
# 查看Pod日志(检查是否有错误)kubectl logs nginx-pod-1重点理解:环境变量的作用
env:- name: DB_HOST value: "mysql-service" # PHP代码中 getenv('DB_HOST') 会读到这个值这样配置后:
- PHP应用启动时,环境变量
DB_HOST= “mysql-service” - 代码执行
new mysqli($db_host, ...)时,会连接到 “mysql-service” - K8s的DNS把 “mysql-service” 解析为Service的ClusterIP
- Service把流量转发到MySQL Pod
步骤2:创建更多Nginx Pod(模拟多副本)
# 创建第2个Podcat > nginx-pod-2.yaml <<EOFapiVersion: v1kind: Podmetadata: name: nginx-pod-2 labels: app: nginx version: v2spec: containers: - name: nginx-php image: reg.westos.org/library/nginx-php:v2 ports: - containerPort: 80 env: - name: DB_HOST value: "mysql-service" - name: DB_USER value: "root" - name: DB_PASS value: "Westos123" - name: DB_NAME value: "testdb"EOF
# 创建第3个Podcat > nginx-pod-3.yaml <<EOFapiVersion: v1kind: Podmetadata: name: nginx-pod-3 labels: app: nginx version: v2spec: containers: - name: nginx-php image: reg.westos.org/library/nginx-php:v2 ports: - containerPort: 80 env: - name: DB_HOST value: "mysql-service" - name: DB_USER value: "root" - name: DB_PASS value: "Westos123" - name: DB_NAME value: "testdb"EOF
# 应用配置kubectl apply -f nginx-pod-2.yamlkubectl apply -f nginx-pod-3.yaml
# 查看所有Nginx Podkubectl get pods -l app=nginx -o wide步骤3:创建NodePort Service
# 创建配置文件cat > nginx-nodeport.yaml <<EOFapiVersion: v1kind: Servicemetadata: name: nginx-nodeportspec: type: NodePort # 指定为NodePort类型 selector: app: nginx # 选择所有app=nginx的Pod ports: - port: 80 # Service端口 targetPort: 80 # Pod端口 nodePort: 30080 # Node端口(30000-32767) protocol: TCP name: httpEOF
# 应用配置kubectl apply -f nginx-nodeport.yaml
# 查看Servicekubectl get svc nginx-nodeport# 输出示例:# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE# nginx-nodeport NodePort 10.96.200.100 <none> 80:30080/TCP 10s
# 查看Endpoints(应该看到3个Pod)kubectl get endpoints nginx-nodeport参数详解:
type: NodePort:在ClusterIP基础上,额外暴露NodePortnodePort: 30080:指定Node端口(不指定则自动分配30000-32767范围内的端口)port: 80的端口 targetPort: 80的端口
访问路径理解:
外部客户端 ↓192.168.100.21:30080 (Node1的30080端口) ↓iptables/ipvs规则 ↓nginx-nodeport Service (ClusterIP: 10.96.200.100:80) ↓负载均衡到其中一个Pod ↓nginx-pod-1 (10.244.1.10:80)nginx-pod-2 (10.244.2.20:80)nginx-pod-3 (10.244.1.30:80)步骤4:测试访问和负载均衡
# 从集群外部访问(可以使用任意Node的IP)curl http://192.168.100.21:30080curl http://192.168.100.22:30080curl http://192.168.100.20:30080
# 多次访问,观察负载均衡效果(Hostname会变化)for i in {1..6}; do echo "=== Request $i ===" curl -s http://192.168.100.21:30080 | grep "Hostname:" echo ""done
# 预期输出:# === Request 1 ===# Hostname: nginx-pod-1# === Request 2 ===# Hostname: nginx-pod-3# === Request 3 ===# Hostname: nginx-pod-2# ...观察数据库连接:
# 查看完整输出curl http://192.168.100.21:30080
# 预期输出:# === Nginx-PHP Container Info ===# Hostname: nginx-pod-1# Server IP: 10.244.1.10## === Database Configuration ===# DB Host: mysql-service# DB User: root# DB Name: testdb## === Database Connection Test ===# Database connected successfully!# MySQL Version: 8.0.x实验验证:
- ✅ 外部可以通过NodePort访问服务
- ✅ 流量自动负载均衡到3个Nginx Pod
- ✅ Nginx Pod通过Service名称成功访问MySQL
- ✅ 即使MySQL Pod重启IP变化,连接依然正常
步骤5:验证Service名称的优势
# 查看MySQL Pod当前IPkubectl get pod mysql-pod -o wide# 假设IP是: 10.244.1.5
# 删除并重建MySQL Pod(模拟Pod重启)kubectl delete pod mysql-podkubectl apply -f mysql-pod.yaml
# 等待Pod就绪kubectl get pods mysql-pod -o wide# 新IP可能是: 10.244.2.15 (IP已改变!)
# 再次访问Nginx,数据库连接依然正常!curl http://192.168.100.21:30080
# 原因:Nginx使用的是"mysql-service"名称,不是IP# Service会自动更新Endpoints指向新Podkubectl get endpoints mysql-service关键点理解:
如果硬编码IP: Nginx → 10.244.1.5 (旧IP) → 连接失败 ❌
使用Service名称: Nginx → mysql-service → DNS解析 → Service ClusterIP → 10.244.2.15 (新IP) → 连接成功 ✅实验3 Service - 云平台负载均衡器
这是什么?
- LoadBalancer在NodePort基础上,进一步创建云平台的负载均衡器
- 分配一个外部可访问的IP地址
- 适用于公有云环境(AWS ELB、Azure LB、GCP LB等)
使用场景:
- 生产环境对外提供服务
- 需要固定的外部IP和更好的负载均衡
注意: 本地环境或私有云默认不支持LoadBalancer,可以使用MetalLB实现。
创建LoadBalancer Service:
cat > nginx-loadbalancer.yaml <<EOFapiVersion: v1kind: Servicemetadata: name: nginx-loadbalancerspec: type: LoadBalancer # 指定为LoadBalancer类型 selector: app: nginx ports: - port: 80 targetPort: 80 protocol: TCP name: httpEOF
# 应用配置kubectl apply -f nginx-loadbalancer.yaml
# 查看Servicekubectl get svc nginx-loadbalancer
# 在云平台上,EXTERNAL-IP会显示分配的公网IP# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE# nginx-loadbalancer LoadBalancer 10.96.200.200 1.2.3.4 80:31234/TCP 1m
# 本地环境会显示<pending>,因为没有云平台负载均衡器访问方式:
# 云平台环境:通过EXTERNAL-IP访问curl http://<EXTERNAL-IP>
# 本地环境:仍然可以通过NodePort访问curl http://192.168.100.21:31234LoadBalancer vs NodePort vs ClusterIP:
LoadBalancer = ClusterIP + NodePort + 云平台LB ↓外部LB (1.2.3.4:80) → NodePort (任意Node:31234) → Service (ClusterIP) → Pod实验4 Service - DNS直接解析到Pod IP
这是什么?
- Headless Service不分配ClusterIP(
clusterIP: None) - DNS直接返回所有后端Pod的IP列表,而不是Service IP
- 客户端可以直接连接到Pod,实现客户端负载均衡
使用场景:
- 有状态应用(StatefulSet)需要访问特定Pod
- 客户端需要知道所有Pod的IP(如数据库主从、Redis集群)
- 自定义负载均衡策略
步骤1:创建测试用的Web Pod
# 创建3个测试Podcat > web-pods.yaml <<EOFapiVersion: v1kind: Podmetadata: name: web-pod-1 labels: app: web role: backendspec: containers: - name: nginx image: nginx:1.20 ports: - containerPort: 80---apiVersion: v1kind: Podmetadata: name: web-pod-2 labels: app: web role: backendspec: containers: - name: nginx image: nginx:1.20 ports: - containerPort: 80---apiVersion: v1kind: Podmetadata: name: web-pod-3 labels: app: web role: backendspec: containers: - name: nginx image: nginx:1.20 ports: - containerPort: 80EOF
# 应用配置kubectl apply -f web-pods.yaml
# 查看Pod及其IPkubectl get pods -l app=web -o wide步骤2:创建Headless Service
cat > web-headless.yaml <<EOFapiVersion: v1kind: Servicemetadata: name: web-headlessspec: clusterIP: None # 关键!设置为None表示Headless selector: app: web ports: - port: 80 targetPort: 80EOF
# 应用配置kubectl apply -f web-headless.yaml
# 查看Service(注意CLUSTER-IP是None)kubectl get svc web-headless# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE# web-headless ClusterIP None <none> 80/TCP 5s步骤3:测试DNS解析
# DNS解析(返回所有Pod的IP,而不是Service IP)kubectl run test-dns --image=reg.westos.org/library/busybox:1.30.1 --rm -it --restart=Never -- \ nslookup web-headless
# 预期输出:# Server: 10.96.0.10# Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local## Name: web-headless# Address 1: 10.244.1.20 web-pod-1.web-headless.default.svc.cluster.local# Address 2: 10.244.2.30 web-pod-2.web-headless.default.svc.cluster.local# Address 3: 10.244.1.40 web-pod-3.web-headless.default.svc.cluster.localHeadless vs 普通Service的DNS对比:
普通ClusterIP Service: nslookup mysql-service → 返回: 10.96.100.10 (Service的ClusterIP)
Headless Service: nslookup web-headless → 返回: 10.244.1.20, 10.244.2.30, 10.244.1.40 (所有Pod的IP)步骤4:直接访问特定Pod(StatefulSet场景)
# Headless Service常与StatefulSet配合使用# 每个Pod有固定的DNS名称: <pod-name>.<service-name>
# 示例:假设我们有StatefulSet创建的Pod# web-0, web-1, web-2# 可以通过以下DNS直接访问:# web-0.web-headless.default.svc.cluster.local# web-1.web-headless.default.svc.cluster.local# web-2.web-headless.default.svc.cluster.local
# 测试(虽然我们用的是普通Pod,但原理相同)kubectl run test --image=reg.westos.org/library/busybox:1.30.1 --rm -it --restart=Never -- \ wget -qO- web-pod-1.web-headless实验总结:
- ✅ Headless Service不分配ClusterIP
- ✅ DNS返回所有Pod的IP列表
- ✅ 适合客户端自己选择要连接的Pod
- ✅ StatefulSet中每个Pod有固定的DNS名称
清理实验资源:
# 清理Nginx Pod和Servicekubectl delete pod nginx-pod-1 nginx-pod-2 nginx-pod-3kubectl delete svc nginx-nodeport nginx-loadbalancer
# 清理Web Pod和Servicekubectl delete pod web-pod-1 web-pod-2 web-pod-3kubectl delete svc web-headless
# 保留MySQL Pod和Service供后续实验使用# kubectl delete pod mysql-pod# kubectl delete svc mysql-service3.4 Service实验总结与最佳实践
四种Service类型对比:
| 特性 | ClusterIP | NodePort | LoadBalancer | Headless |
|---|---|---|---|---|
| ClusterIP | ✓ | ✓ | ✓ | None |
| 访问范围 | 集群内部 | 集群内+NodePort | 集群内+外部LB | 集群内部 |
| 外部访问 | ✗ | ✓ | ✓ | ✗ |
| DNS解析 | Service IP | Service IP | Service IP | Pod IP列表 |
| 负载均衡 | ✓ | ✓ | ✓ | ✗(客户端) |
| 使用场景 | 内部服务 | 开发测试 | 生产环境 | 有状态应用 |
| 依赖 | 无 | 无 | 云平台 | 无 |
生产最佳实践:
-
服务发现 - 永远使用Service名称
# ✅ 正确env:- name: DB_HOSTvalue: "mysql-service"# ❌ 错误env:- name: DB_HOSTvalue: "10.244.1.5" # 硬编码IP会在Pod重启后失效 -
内部服务使用ClusterIP
# 数据库、缓存等后端服务不需要对外暴露apiVersion: v1kind: Servicemetadata:name: mysql-servicespec:type: ClusterIP # 或省略,默认就是ClusterIPselector:app: mysql -
外部访问优先使用Ingress
生产环境推荐:外部 → Ingress Controller → Service (ClusterIP) → Pod而不是:外部 → NodePort → Pod -
环境变量vs ConfigMap
# 简单配置:直接使用envenv:- name: DB_HOSTvalue: "mysql-service"# 复杂配置:使用ConfigMapenvFrom:- configMapRef:name: app-config -
健康检查必不可少
# 确保Pod健康才加入EndpointsreadinessProbe:httpGet:path: /healthport: 80initialDelaySeconds: 5periodSeconds: 3
实验收获:
通过本次实验,我们学会了:
- ✅ 创建和管理四种类型的Service
- ✅ 使用标签选择器关联Pod和Service
- ✅ 通过Service名称实现服务发现
- ✅ 使用环境变量配置应用
- ✅ 理解K8s的DNS机制
- ✅ 掌握生产环境的最佳实践
下一步,我们将学习Deployment,用更高级的方式管理Pod副本!
4.总结
Service核心价值:
-
稳定的访问入口
- 提供固定的ClusterIP和DNS名称
- Pod IP变化对应用透明
-
服务发现
- 通过DNS自动解析Service名称
- 无需配置中心
-
负载均衡
- 自动在多个Pod之间分发流量
- 支持多种算法(IPVS模式)
-
配置解耦
- 应用使用Service名称,不关心具体IP
- 便于实现蓝绿部署、灰度发布
生产环境建议:
✓ 使用IPVS模式(高性能)✓ 所有内部服务使用ClusterIP✓ 配置使用ConfigMap管理✓ 应用通过Service名称访问其他服务✓ 外部访问使用Ingress(而非NodePort)✓ 有状态应用使用Headless Service + StatefulSet四、Ingress:K8s的7层网关
1. Ingress是什么?为什么需要它?
1.1 Service暴露服务的局限性
在前面的章节中,我们学习了如何使用Service暴露服务。但在生产环境中,Service有一些明显的局限性:
NodePort的问题:
场景:你有10个微服务需要对外暴露
使用NodePort:┌─────────────────────────────────────────────────┐│ 用户需要记住10个不同的端口: ││ - 用户服务:http://k8s.example.com:30001 ││ - 订单服务:http://k8s.example.com:30002 ││ - 商品服务:http://k8s.example.com:30003 ││ - 支付服务:http://k8s.example.com:30004 ││ ... 还有6个 │└─────────────────────────────────────────────────┘
问题: ✗ 端口难记,用户体验差 ✗ 不支持域名访问 ✗ 无法使用标准HTTP/HTTPS端口(80/443) ✗ 无法实现基于域名/路径的路由 ✗ 无法统一管理SSL证书 ✗ 端口数量有限(30000-32767)LoadBalancer的问题:
场景:你有10个微服务需要对外暴露
使用LoadBalancer:┌─────────────────────────────────────────────────┐│ 需要创建10个LoadBalancer(云平台): ││ - 用户服务:LB1 (IP: 1.2.3.4) ││ - 订单服务:LB2 (IP: 1.2.3.5) ││ - 商品服务:LB3 (IP: 1.2.3.6) ││ ... 还有7个 │└─────────────────────────────────────────────────┘
问题: ✗ 每个LoadBalancer都要收费(按小时/按流量) ✗ 10个服务 = 10个LB = 成本高昂 ✗ 需要10个公网IP(IP资源浪费) ✗ 管理复杂(10个LB需要分别配置) ✗ 无法实现智能路由(域名、路径、Header)理想的解决方案:
用户期望的访问方式:
- 用户服务:http://user.example.com- 订单服务:http://order.example.com- 商品服务:http://api.example.com/products- 支付服务:http://api.example.com/payment
特点: ✓ 使用标准端口(80/443) ✓ 使用友好的域名 ✓ 支持路径路由 ✓ 统一管理SSL证书 ✓ 只需要1个公网IP ✓ 统一入口,便于管理这就是Ingress的价值所在!
1.2 Ingress是什么?
用生活场景来理解:
想象一个大型购物中心:
没有Ingress(使用NodePort/LoadBalancer):┌────────────────────────────────────────────┐│ 每个商铺都有自己独立的大门: ││ - 星巴克:从东门进,上3楼,右转 ││ - 优衣库:从西门进,下地下1层 ││ - 电影院:从北门进,坐电梯到5楼 ││ ││ 顾客:我只是想买杯咖啡,为什么这么复杂? │└────────────────────────────────────────────┘
有了Ingress(统一入口):┌────────────────────────────────────────────┐│ 购物中心只有1个主入口: ││ 顾客进门后,看指示牌: ││ → 要喝咖啡?往左走,3楼星巴克 ││ → 要买衣服?往右走,地下1层优衣库 ││ → 要看电影?坐电梯,5楼电影院 ││ ││ Ingress = 购物中心的"智能指示牌" │└────────────────────────────────────────────┘技术定义:
Ingress是什么?
Ingress是Kubernetes的API对象,用于管理外部访问集群内服务的规则。
核心特点:1. 7层(HTTP/HTTPS)流量管理2. 基于域名、路径、Header等进行智能路由3. 统一的外部访问入口4. 支持SSL/TLS终止5. 支持负载均衡、重定向、重写等高级功能
类比: Service = 4层负载均衡(IP + Port) Ingress = 7层负载均衡(域名 + 路径 + Header)1.3 Ingress的组成部分
Ingress不是一个单独的组件,它由两部分组成:
1. Ingress资源(配置规则)
# 这是一个Ingress资源的示例apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: my-ingressspec: rules: - host: www.example.com # 域名 http: paths: - path: /api # 路径 pathType: Prefix backend: service: name: api-service # 指向的Service port: number: 80Ingress资源只是配置规则,本身不处理流量!
2. Ingress Controller(流量处理器)
Ingress Controller是什么?
Ingress Controller是一个实际运行的Pod,它:1. 监听Kubernetes API,读取Ingress资源2. 根据Ingress规则配置反向代理(如Nginx)3. 接收外部流量,按规则转发到对应Service
常见的Ingress Controller: - Nginx Ingress Controller(最流行) - Traefik - HAProxy - Kong - Istio Gateway - 云平台的Ingress(如AWS ALB、Azure Application Gateway)
类比: Ingress资源 = 交通规则(规则定义) Ingress Controller = 交警(执行规则)1.4 Ingress工作原理
完整的流量转发流程:
外部用户访问:http://www.example.com/api/users
步骤1:DNS解析┌─────────────────────────────────────────┐│ 用户浏览器 ││ 输入:www.example.com ││ ↓ ││ DNS解析:www.example.com → 1.2.3.4 ││ (Ingress Controller的外部IP) │└─────────────────────────────────────────┘ ↓步骤2:到达Ingress Controller┌─────────────────────────────────────────┐│ Ingress Controller Pod ││ (运行Nginx/Traefik等反向代理) ││ ││ 接收请求: ││ Host: www.example.com ││ Path: /api/users │└─────────────────────────────────────────┘ ↓步骤3:匹配Ingress规则┌─────────────────────────────────────────┐│ Ingress Controller查找规则: ││ ││ ✓ 找到匹配的Ingress: ││ - host: www.example.com ││ - path: /api ││ - backend: api-service:80 │└─────────────────────────────────────────┘ ↓步骤4:转发到Service┌─────────────────────────────────────────┐│ Ingress Controller转发请求到: ││ http://api-service:80/api/users ││ (Service的ClusterIP) │└─────────────────────────────────────────┘ ↓步骤5:Service负载均衡到Pod┌─────────────────────────────────────────┐│ Service (ClusterIP) ││ 通过kube-proxy/IPVS负载均衡: ││ → Pod1: 10.244.1.10:8080 ││ → Pod2: 10.244.2.20:8080 ││ → Pod3: 10.244.3.30:8080 │└─────────────────────────────────────────┘ ↓步骤6:Pod处理请求并返回响应┌─────────────────────────────────────────┐│ Pod响应 → Service → Ingress Controller ││ → 用户浏览器 │└─────────────────────────────────────────┘用流程图表示:
外部用户 ↓ (1) HTTP请求:www.example.com/api┌──────────────────────────────┐│ Ingress Controller Pod ││ (Nginx/Traefik等) ││ 外部IP: 1.2.3.4 │└──────────────────────────────┘ ↓ (2) 根据Ingress规则路由┌──────────────────────────────┐│ Service (ClusterIP) ││ api-service: 10.96.100.50 │└──────────────────────────────┘ ↓ (3) 负载均衡┌─────────┐ ┌─────────┐ ┌─────────┐│ Pod1 │ │ Pod2 │ │ Pod3 │└─────────┘ └─────────┘ └─────────┘1.5 Ingress与Service的关系
Ingress和Service是配合使用的,不是替代关系!
架构层次:
外部用户 ↓Ingress(7层路由) ↓Service(4层负载均衡) ↓Pod(应用容器)
职责分工:
Ingress的职责: ✓ 接收外部HTTP/HTTPS流量 ✓ 基于域名、路径、Header路由 ✓ SSL/TLS终止 ✓ 重写、重定向 ✓ 统一访问入口
Service的职责: ✓ 为Pod提供稳定的ClusterIP ✓ 在Pod之间进行负载均衡 ✓ 服务发现(DNS) ✓ 健康检查为什么不能跳过Service,Ingress直接转发到Pod?
技术上可以,但不推荐:
问题1:Pod IP不稳定 - Pod重启/扩缩容时IP会变化 - Ingress需要频繁更新配置
问题2:失去Service的负载均衡能力 - Service的健康检查 - Service的会话保持 - Service的故障转移
问题3:违背Kubernetes设计理念 - Service是Pod的抽象层 - 所有访问Pod的流量都应该经过Service
最佳实践: Ingress → Service → Pod 这是标准的三层架构1.6 Ingress vs Service对比
| 特性 | Service (NodePort/LB) | Ingress |
|---|---|---|
| OSI层 | 4层(TCP/UDP) | 7层(HTTP/HTTPS) |
| 路由能力 | IP + 端口 | 域名 + 路径 + Header |
| 成本 | 每个服务1个LB(贵) | 多个服务共享1个入口(省钱) |
| 端口 | 需要不同端口 | 统一80/443端口 |
| SSL | 每个Service单独配置 | 统一管理证书 |
| 域名 | 不支持 | 原生支持 |
| 高级功能 | 无 | 重写、重定向、金丝雀等 |
| 使用场景 | 内部服务、简单暴露 | 生产环境外部访问 |
典型架构对比:
传统方式(Service LoadBalancer):┌─────────────────────────────────────────┐│ 用户服务:LB1 (1.2.3.4:80) → Service1 ││ 订单服务:LB2 (1.2.3.5:80) → Service2 ││ 商品服务:LB3 (1.2.3.6:80) → Service3 ││ ││ 成本:3个LoadBalancer ││ IP:3个公网IP ││ 管理:分散配置 │└─────────────────────────────────────────┘
Ingress方式:┌─────────────────────────────────────────┐│ Ingress (1.2.3.4:80/443) ││ ├─ user.example.com → Service1 ││ ├─ order.example.com → Service2 ││ └─ api.example.com/products → Svc3 ││ ││ 成本:1个LoadBalancer(或免费方案) ││ IP:1个公网IP ││ 管理:统一配置 │└─────────────────────────────────────────┘1.7 Ingress的本质
Ingress Controller的本质是什么?
Ingress Controller = Nginx/Traefik + 自动配置器
以Nginx Ingress Controller为例:
传统Nginx配置(手动编写):┌──────────────────────────────────────┐│ server { ││ listen 80; ││ server_name www.example.com; ││ location /api { ││ proxy_pass http://backend:8080; ││ } ││ } │└──────────────────────────────────────┘
Ingress Controller的魔法:┌──────────────────────────────────────┐│ 1. 监听Kubernetes API ││ 2. 读取Ingress资源 ││ 3. 自动生成Nginx配置文件 ││ 4. 重载Nginx(无需手动操作) ││ 5. 持续监听,配置变化自动更新 │└──────────────────────────────────────┘
你只需要: - 创建Ingress YAML - kubectl apply -f ingress.yaml - 剩下的交给Ingress Controller!Ingress Controller做了什么?
┌─────────────────────────────────────────────┐│ Ingress Controller的工作流程: ││ ││ 1. 启动时: ││ - 创建Nginx/Traefik容器 ││ - 监听Kubernetes API ││ ││ 2. 发现Ingress资源时: ││ - 读取Ingress规则 ││ - 转换为Nginx配置 ││ - 更新Nginx配置文件 ││ - 执行nginx -s reload ││ ││ 3. Ingress变化时: ││ - 检测到变化(增/删/改) ││ - 重新生成配置 ││ - 热更新(不中断现有连接) ││ ││ 4. 处理流量时: ││ - 接收HTTP/HTTPS请求 ││ - 匹配Ingress规则 ││ - 转发到对应Service ││ - 返回响应给客户端 │└─────────────────────────────────────────────┘小结
Ingress解决了什么问题?
1. 统一外部访问入口 - 一个公网IP服务所有应用 - 节省成本(云平台LoadBalancer)
2. 基于7层的智能路由 - 域名路由:user.example.com → 用户服务 - 路径路由:/api/orders → 订单服务 - Header路由:移动端 → 移动服务
3. 简化管理 - 统一管理SSL证书 - 统一配置访问策略 - 声明式配置(YAML)
4. 企业级功能 - 金丝雀发布 - 灰度发布 - A/B测试 - 流量分割下一步: 我们将详细学习Ingress能实现哪些功能,以及不同的部署方式。
2. Ingress功能详解
2.1 基础功能:基于域名的路由
功能说明: 根据不同的域名,将流量转发到不同的Service。
使用场景:
场景:一个公司有多个子系统- 用户系统:user.example.com- 订单系统:order.example.com- 商品系统:product.example.com
传统方式:需要3个LoadBalancer(贵!)Ingress方式:1个Ingress搞定(省钱!)配置示例:
apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: multi-domain-ingressspec: rules: # 规则1:用户系统 - host: user.example.com http: paths: - path: / pathType: Prefix backend: service: name: user-service port: number: 80
# 规则2:订单系统 - host: order.example.com http: paths: - path: / pathType: Prefix backend: service: name: order-service port: number: 80
# 规则3:商品系统 - host: product.example.com http: paths: - path: / pathType: Prefix backend: service: name: product-service port: number: 80路由流程:
用户访问:user.example.com ↓Ingress检测Host头:user.example.com ↓匹配规则1 → 转发到user-service ↓user-service → user-pod
用户访问:order.example.com ↓Ingress检测Host头:order.example.com ↓匹配规则2 → 转发到order-service ↓order-service → order-pod2.2 基于路径的路由
功能说明: 根据URL路径,将流量转发到不同的Service。
使用场景:
场景:一个统一的API网关- /api/users → 用户服务- /api/orders → 订单服务- /api/products → 商品服务- /static → 静态文件服务
优势: ✓ 统一域名(如api.example.com) ✓ 路径清晰,符合RESTful规范 ✓ 便于API管理和版本控制配置示例:
apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: path-based-ingressspec: rules: - host: api.example.com http: paths: # 用户API - path: /api/users pathType: Prefix backend: service: name: user-service port: number: 8080
# 订单API - path: /api/orders pathType: Prefix backend: service: name: order-service port: number: 8080
# 商品API - path: /api/products pathType: Prefix backend: service: name: product-service port: number: 8080
# 静态文件 - path: /static pathType: Prefix backend: service: name: static-service port: number: 80pathType详解:
| pathType | 匹配规则 | 示例 |
|---|---|---|
| Prefix | 前缀匹配 | /api 匹配 /api/users、/api/orders |
| Exact | 精确匹配 | /api 只匹配 /api,不匹配 /api/users |
| ImplementationSpecific | 由Ingress Controller决定 | 取决于具体实现 |
匹配示例:
配置:path: /api, pathType: Prefix
✓ 匹配: - /api - /api/ - /api/users - /api/users/123
✗ 不匹配: - /apis - /v1/api
配置:path: /api, pathType: Exact
✓ 匹配: - /api
✗ 不匹配: - /api/ - /api/users2.3 HTTPS和SSL/TLS终止
功能说明: Ingress可以在边缘终止SSL/TLS,后端Service使用HTTP通信。
使用场景:
场景:需要HTTPS加密访问
传统方式: - 每个Service配置SSL证书 - 证书管理分散 - 证书更新复杂
Ingress方式: - 证书统一配置在Ingress - 证书集中管理 - 后端Service无需关心SSL工作原理:
外部用户 ↓ HTTPS (443端口,加密)Ingress Controller ↓ HTTP (80端口,明文)Service ↓Pod
SSL终止发生在Ingress Controller层配置示例:
apiVersion: v1kind: Secretmetadata: name: example-tlstype: kubernetes.io/tlsdata: tls.crt: LS0tLS1CRUdJTi... # Base64编码的证书 tls.key: LS0tLS1CRUdJTi... # Base64编码的私钥
---apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: https-ingressspec: tls: - hosts: - www.example.com - api.example.com secretName: example-tls # 引用证书Secret rules: - host: www.example.com http: paths: - path: / pathType: Prefix backend: service: name: web-service port: number: 80创建TLS Secret:
# 方式1:从证书文件创建kubectl create secret tls example-tls \ --cert=path/to/tls.crt \ --key=path/to/tls.key
# 方式2:使用Let's Encrypt自动生成(需要cert-manager)# 后面实操环节会演示HTTPS重定向(HTTP自动跳转HTTPS):
apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: https-redirect-ingress annotations: nginx.ingress.kubernetes.io/ssl-redirect: "true" # 自动重定向 nginx.ingress.kubernetes.io/force-ssl-redirect: "true"spec: tls: - hosts: - www.example.com secretName: example-tls rules: - host: www.example.com http: paths: - path: / pathType: Prefix backend: service: name: web-service port: number: 802.4 URL重写和重定向
功能说明: 修改请求路径或重定向到其他URL。
使用场景1:路径重写
场景:前端路径和后端路径不一致
用户访问:http://api.example.com/v1/users后端期望:http://api.example.com/users
需要去掉 /v1 前缀路径重写配置:
apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: rewrite-ingress annotations: nginx.ingress.kubernetes.io/rewrite-target: /$2spec: rules: - host: api.example.com http: paths: - path: /v1(/|$)(.*) pathType: ImplementationSpecific backend: service: name: api-service port: number: 80路径重写示例:
用户请求:/v1/users/123 ↓ 匹配正则:/v1(/|$)(.*) ↓ $2 = users/123 ↓ rewrite-target: /$2转发到后端:/users/123
用户请求:/v1/orders ↓转发到后端:/orders使用场景2:永久重定向
场景:域名迁移或统一入口
旧域名:old.example.com新域名:new.example.com
需要将旧域名访问自动跳转到新域名重定向配置:
apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: redirect-ingress annotations: nginx.ingress.kubernetes.io/permanent-redirect: https://new.example.comspec: rules: - host: old.example.com http: paths: - path: / pathType: Prefix backend: service: name: dummy-service # 实际不会访问 port: number: 802.5 金丝雀发布(Canary Deployment)
功能说明: 将一小部分流量导向新版本,验证稳定后再全量发布。
使用场景:
场景:发布新版本应用v2,但不确定是否稳定
传统方式:直接全量发布 → 出问题影响所有用户金丝雀方式:先导5%流量到v2 → 稳定后逐步增加 → 最终100%
类比:煤矿工人带金丝雀下井,金丝雀对有毒气体敏感 如果金丝雀死了,说明环境有问题
金丝雀发布:新版本就是"金丝雀" 先让少量用户体验新版本 如果出问题,影响范围小金丝雀发布策略:
1. 基于流量比例 - 10%流量 → v2版本 - 90%流量 → v1版本
2. 基于Header - 如果Header包含 canary=true → v2版本 - 否则 → v1版本 (内部测试人员使用)
3. 基于Cookie - 如果Cookie包含 user_type=vip → v2版本 - 否则 → v1版本 (VIP用户先体验)配置示例1:基于权重的金丝雀
# 主Ingress(稳定版本v1)apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: app-ingressspec: rules: - host: www.example.com http: paths: - path: / pathType: Prefix backend: service: name: app-v1 # 稳定版本 port: number: 80
---# 金丝雀Ingress(新版本v2)apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: app-canary annotations: nginx.ingress.kubernetes.io/canary: "true" nginx.ingress.kubernetes.io/canary-weight: "10" # 10%流量spec: rules: - host: www.example.com http: paths: - path: / pathType: Prefix backend: service: name: app-v2 # 新版本 port: number: 80流量分配:
100个请求: → 90个请求到app-v1(稳定版本) → 10个请求到app-v2(金丝雀版本)
观察v2稳定后,逐步调整权重: canary-weight: 10 → 30 → 50 → 80 → 100
最终v2完全替代v1: 删除主Ingress,金丝雀Ingress改为主Ingress配置示例2:基于Header的金丝雀
apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: app-canary-header annotations: nginx.ingress.kubernetes.io/canary: "true" nginx.ingress.kubernetes.io/canary-by-header: "canary" nginx.ingress.kubernetes.io/canary-by-header-value: "true"spec: rules: - host: www.example.com http: paths: - path: / pathType: Prefix backend: service: name: app-v2 port: number: 80测试:
# 普通用户访问(到v1)curl http://www.example.com
# 测试人员访问(到v2)curl -H "canary: true" http://www.example.com2.6 A/B测试
功能说明: 根据用户特征,将流量导向不同版本,比较效果。
使用场景:
场景:测试新UI设计是否提升转化率
A版本:原有UIB版本:新UI设计
策略: - 50%用户看A版本 - 50%用户看B版本
收集数据: - A版本转化率:5% - B版本转化率:8%
结论:B版本更好,全量上线B版本A/B测试 vs 金丝雀发布:
金丝雀发布: - 目标:验证稳定性 - 逐步增加流量 - 出问题立即回滚
A/B测试: - 目标:比较效果 - 流量比例固定 - 收集数据分析配置示例:基于Cookie的A/B测试
apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: app-ab-test annotations: nginx.ingress.kubernetes.io/canary: "true" nginx.ingress.kubernetes.io/canary-by-cookie: "ab_test" nginx.ingress.kubernetes.io/canary-by-cookie-value: "version_b"spec: rules: - host: www.example.com http: paths: - path: / pathType: Prefix backend: service: name: app-version-b port: number: 80测试:
# 用户A(看到A版本)curl http://www.example.com
# 用户B(看到B版本)curl --cookie "ab_test=version_b" http://www.example.com2.7 其他高级功能
1. 速率限制(Rate Limiting)
apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: rate-limit-ingress annotations: nginx.ingress.kubernetes.io/limit-rps: "10" # 每秒10个请求 nginx.ingress.kubernetes.io/limit-connections: "5" # 最多5个并发连接spec: rules: - host: api.example.com http: paths: - path: / pathType: Prefix backend: service: name: api-service port: number: 802. IP白名单/黑名单
apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: whitelist-ingress annotations: nginx.ingress.kubernetes.io/whitelist-source-range: "10.0.0.0/8,192.168.1.0/24"spec: rules: - host: admin.example.com http: paths: - path: / pathType: Prefix backend: service: name: admin-service port: number: 803. 基本认证(Basic Auth)
# 创建密码文件htpasswd -c auth admin# 输入密码
# 创建Secretkubectl create secret generic basic-auth --from-file=authapiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: auth-ingress annotations: nginx.ingress.kubernetes.io/auth-type: basic nginx.ingress.kubernetes.io/auth-secret: basic-auth nginx.ingress.kubernetes.io/auth-realm: "Authentication Required"spec: rules: - host: admin.example.com http: paths: - path: / pathType: Prefix backend: service: name: admin-service port: number: 804. 跨域配置(CORS)
apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: cors-ingress annotations: nginx.ingress.kubernetes.io/enable-cors: "true" nginx.ingress.kubernetes.io/cors-allow-origin: "https://frontend.example.com" nginx.ingress.kubernetes.io/cors-allow-methods: "GET, POST, PUT, DELETE"spec: rules: - host: api.example.com http: paths: - path: / pathType: Prefix backend: service: name: api-service port: number: 802.8 Ingress Controller部署方式
Ingress Controller有多种部署方式,适用于不同的环境和需求。
部署方式1:LoadBalancer + Deployment
适用场景: 云平台环境(AWS、阿里云、腾讯云等)
架构图:
外部用户 ↓云平台LoadBalancer (公网IP: 1.2.3.4) ↓Ingress Controller Service (LoadBalancer类型) ↓Ingress Controller Pods (Deployment, 2-3副本) ↓应用Service ↓应用Pods特点:
优点: ✓ 高可用(多副本 + LB自动分发流量) ✓ 自动故障转移 ✓ 云平台管理LoadBalancer ✓ 简单易用
缺点: ✗ 云平台LoadBalancer收费 ✗ 依赖云平台 ✗ 不适用于裸金属环境部署示例:
# Ingress Controller DeploymentapiVersion: apps/v1kind: Deploymentmetadata: name: nginx-ingress-controller namespace: ingress-nginxspec: replicas: 2 # 多副本高可用 selector: matchLabels: app: nginx-ingress template: metadata: labels: app: nginx-ingress spec: containers: - name: nginx-ingress-controller image: k8s.gcr.io/ingress-nginx/controller:v1.0.0 args: - /nginx-ingress-controller - --configmap=$(POD_NAMESPACE)/nginx-configuration
---# LoadBalancer ServiceapiVersion: v1kind: Servicemetadata: name: ingress-nginx namespace: ingress-nginxspec: type: LoadBalancer # 云平台自动创建LB ports: - name: http port: 80 targetPort: 80 - name: https port: 443 targetPort: 443 selector: app: nginx-ingress部署方式2:DaemonSet + hostNetwork
适用场景: 裸金属环境(本地机房、私有云)
架构图:
外部用户 ↓物理机/虚拟机 (直接访问Node IP) ↓Ingress Controller Pod (每个Node运行一个) ↓ hostNetwork模式(使用Node的网络栈)Node的80/443端口 ↓应用Service ↓应用Pods特点:
优点: ✓ 无需LoadBalancer(省钱) ✓ 直接使用Node的80/443端口 ✓ 性能好(无额外网络层) ✓ 适用于裸金属环境
缺点: ✗ 每个Node只能运行一个Ingress Pod ✗ Node的80/443端口被占用 ✗ 需要外部负载均衡器(如Nginx/HAProxy)分发到各Node部署示例:
apiVersion: apps/v1kind: DaemonSetmetadata: name: nginx-ingress-controller namespace: ingress-nginxspec: selector: matchLabels: app: nginx-ingress template: metadata: labels: app: nginx-ingress spec: hostNetwork: true # 关键:使用Node网络 dnsPolicy: ClusterFirstWithHostNet containers: - name: nginx-ingress-controller image: k8s.gcr.io/ingress-nginx/controller:v1.0.0 args: - /nginx-ingress-controller ports: - name: http containerPort: 80 hostPort: 80 # 绑定Node的80端口 - name: https containerPort: 443 hostPort: 443 # 绑定Node的443端口 nodeSelector: ingress: "true" # 只在特定Node运行标记Node运行Ingress:
# 给Node打标签kubectl label node node1 ingress=truekubectl label node node2 ingress=true
# 验证kubectl get nodes --show-labels | grep ingress外部负载均衡配置(Nginx示例):
# 在外部Nginx上配置upstream k8s_ingress { server 192.168.100.21:80; # node1 server 192.168.100.22:80; # node2}
server { listen 80; server_name *.example.com;
location / { proxy_pass http://k8s_ingress; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; }}部署方式3:NodePort + Deployment
适用场景: 测试环境、小规模集群
架构图:
外部用户 ↓任意Node IP:NodePort (如192.168.100.21:30080) ↓Ingress Controller Service (NodePort类型) ↓Ingress Controller Pods ↓应用Service ↓应用Pods特点:
优点: ✓ 无需LoadBalancer ✓ 简单易用 ✓ 适合测试环境
缺点: ✗ 端口不是标准80/443 ✗ 需要记住NodePort ✗ 不适合生产环境部署示例:
apiVersion: v1kind: Servicemetadata: name: ingress-nginx namespace: ingress-nginxspec: type: NodePort ports: - name: http port: 80 targetPort: 80 nodePort: 30080 # 外部访问Node:30080 - name: https port: 443 targetPort: 443 nodePort: 30443 selector: app: nginx-ingress三种部署方式对比
| 特性 | LoadBalancer+Deployment | DaemonSet+hostNetwork | NodePort+Deployment |
|---|---|---|---|
| 适用环境 | 云平台 | 裸金属/私有云 | 测试环境 |
| 外部访问 | LB公网IP:80/443 | Node IP:80/443 | Node IP:30080 |
| 成本 | 需要付费LB | 免费(需外部LB) | 免费 |
| 高可用 | 云平台LB自动实现 | 需外部LB | 无(单点) |
| 端口 | 标准80/443 | 标准80/443 | 非标准端口 |
| 生产推荐 | ✓ | ✓ | ✗ |
生产环境推荐:
云平台:LoadBalancer + Deployment - AWS EKS、阿里云ACK、腾讯云TKE等 - 自动创建ELB/SLB/CLB - 高可用、易管理
裸金属/私有云:DaemonSet + hostNetwork - 自建机房、私有云 - 配合外部Nginx/HAProxy/Keepalived - 成本低、性能好
混合方案:DaemonSet + hostNetwork + MetalLB - 裸金属环境 - 使用MetalLB提供LoadBalancer能力 - 结合两种方案的优点小结
Ingress功能总结:
基础功能: ✓ 基于域名路由 ✓ 基于路径路由 ✓ HTTPS/SSL终止
高级功能: ✓ URL重写/重定向 ✓ 金丝雀发布 ✓ A/B测试 ✓ 速率限制 ✓ IP白名单 ✓ 认证授权 ✓ CORS跨域
部署方式: ✓ LoadBalancer + Deployment(云平台) ✓ DaemonSet + hostNetwork(裸金属) ✓ NodePort + Deployment(测试)下一步: 我们将通过实际操作,部署Ingress Controller并演示各种功能。
3. Ingress实战操作
3.1 部署Nginx Ingress Controller(DaemonSet模式)
步骤1:标记运行Ingress的Node
kubectl label node node1 ingress=truekubectl label node node2 ingress=true步骤2:下载并修改部署文件
# 下载官方部署文件wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.8.1/deploy/static/provider/baremetal/deploy.yaml
# 关键修改:# 1. Deployment改为DaemonSet# 2. 添加 hostNetwork: true# 3. 添加 dnsPolicy: ClusterFirstWithHostNet# 4. 添加 nodeSelector: ingress: "true"# 5. 镜像改为国内源(可选)步骤3:部署
kubectl apply -f deploy.yamlkubectl get pods -n ingress-nginx -o wide3.2 实验1:基于域名路由
给containerd配置代理:
在此之前,为了方便我们下面使用docker官方的镜像,由于国内无法直接拉取官方镜像,我们需要给节点的containerd配置代理,设置我们的私有仓库reg.westos.org不走代理。
#在每个k8s节点上运行,换成你的代理地址mkdir -p /etc/systemd/system/containerd.service.dcat <<EOF > /etc/systemd/system/containerd.service.d/proxy.conf[Service]Environment="HTTP_PROXY=http://192.168.10.141:7897"Environment="HTTPS_PROXY=http://192.168.10.141:7897"Environment="NO_PROXY=localhost,127.0.0.1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,.svc,.cluster.local,reg.westos.org"EOFsudo systemctl daemon-reloadsudo systemctl restart containerd步骤1:创建两个应用Pod:
# 创建nginx应用Pod(2个副本)cat > nginx-app-pods.yaml <<EOFapiVersion: v1kind: Podmetadata: name: nginx-app-1 labels: app: nginx-appspec: containers: - name: nginx image: nginx:1.20 ports: - containerPort: 80---apiVersion: v1kind: Podmetadata: name: nginx-app-2 labels: app: nginx-appspec: containers: - name: nginx image: nginx:1.20 ports: - containerPort: 80EOF
kubectl apply -f nginx-app-pods.yaml
# 创建httpd应用Pod(2个副本)cat > httpd-app-pods.yaml <<EOFapiVersion: v1kind: Podmetadata: name: httpd-app-1 labels: app: httpd-appspec: containers: - name: httpd image: httpd:2.4 ports: - containerPort: 80---apiVersion: v1kind: Podmetadata: name: httpd-app-2 labels: app: httpd-appspec: containers: - name: httpd image: httpd:2.4 ports: - containerPort: 80EOF
kubectl apply -f httpd-app-pods.yaml
# 等待Pod就绪kubectl get pods -l app=nginx-appkubectl get pods -l app=httpd-app步骤2:创建Service:
# 创建nginx-servicecat > nginx-service.yaml <<EOFapiVersion: v1kind: Servicemetadata: name: nginx-servicespec: selector: app: nginx-app ports: - port: 80 targetPort: 80EOF
kubectl apply -f nginx-service.yaml
# 创建httpd-servicecat > httpd-service.yaml <<EOFapiVersion: v1kind: Servicemetadata: name: httpd-servicespec: selector: app: httpd-app ports: - port: 80 targetPort: 80EOF
kubectl apply -f httpd-service.yaml
# 查看Service和Endpointskubectl get svckubectl get endpoints
# 创建基于域名的Ingresscat > domain-ingress.yaml <<EOFapiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: domain-ingressspec: ingressClassName: nginx rules: - host: nginx.example.com http: paths: - path: / pathType: Prefix backend: service: name: nginx-service port: number: 80 - host: httpd.example.com http: paths: - path: / pathType: Prefix backend: service: name: httpd-service port: number: 80EOF
kubectl apply -f domain-ingress.yaml
# 配置hosts(测试机器)echo "192.168.100.21 nginx.example.com httpd.example.com" >> /etc/hosts
# 测试curl http://nginx.example.comcurl http://httpd.example.com
# 清理kubectl delete ingress domain-ingresskubectl delete svc nginx-service httpd-servicekubectl delete pod nginx-app-1 nginx-app-2 httpd-app-1 httpd-app-23.3 实验2:基于路径路由 + URL重写
前提:使用实验1创建的Pod和Service,如果已清理则需要重新创建
# 创建基于路径的Ingresscat > path-rewrite-ingress.yaml <<EOFapiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: path-rewrite annotations: nginx.ingress.kubernetes.io/rewrite-target: /\$2spec: ingressClassName: nginx rules: - host: api.example.com http: paths: - path: /nginx(/|$)(.*) pathType: ImplementationSpecific backend: service: name: nginx-service port: number: 80 - path: /httpd(/|$)(.*) pathType: ImplementationSpecific backend: service: name: httpd-service port: number: 80EOF
kubectl apply -f path-rewrite-ingress.yaml
# 配置hosts(测试机器)echo "192.168.100.21 api.example.com" >> /etc/hosts
# 测试curl http://api.example.com/nginx # 转发到nginx-service/curl http://api.example.com/httpd # 转发到httpd-service/
# 清理kubectl delete ingress path-rewrite3.4 实验3:HTTPS配置
前提:使用实验1创建的nginx-service,如果已清理则需要重新创建
# 生成自签名证书openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ -keyout tls.key -out tls.crt \ -subj "/CN=secure.example.com"
# 创建TLS Secretkubectl create secret tls example-tls --cert=tls.crt --key=tls.key
# 创建HTTPS Ingresscat > https-ingress.yaml <<EOFapiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: https-ingress annotations: nginx.ingress.kubernetes.io/ssl-redirect: "true"spec: ingressClassName: nginx tls: - hosts: - secure.example.com secretName: example-tls rules: - host: secure.example.com http: paths: - path: / pathType: Prefix backend: service: name: nginx-service port: number: 80EOF
kubectl apply -f https-ingress.yaml
# 配置hosts(测试机器)echo "192.168.100.21 secure.example.com" >> /etc/hosts
# 测试curl -k https://secure.example.com # HTTPS访问curl -I http://secure.example.com # HTTP自动跳转
# 清理kubectl delete ingress https-ingresskubectl delete secret example-tls3.5 实验4:金丝雀发布
步骤1:创建v1版本Pod(3个副本,红色页面):
cat > app-v1-pods.yaml <<EOFapiVersion: v1kind: Podmetadata: name: app-v1-1 labels: app: myapp version: v1spec: containers: - name: app image: nginx:1.20 command: ["/bin/sh", "-c"] args: - echo '<h1 style="color:red">Version 1</h1>' > /usr/share/nginx/html/index.html && nginx -g 'daemon off;' ports: - containerPort: 80---apiVersion: v1kind: Podmetadata: name: app-v1-2 labels: app: myapp version: v1spec: containers: - name: app image: nginx:1.20 command: ["/bin/sh", "-c"] args: - echo '<h1 style="color:red">Version 1</h1>' > /usr/share/nginx/html/index.html && nginx -g 'daemon off;' ports: - containerPort: 80---apiVersion: v1kind: Podmetadata: name: app-v1-3 labels: app: myapp version: v1spec: containers: - name: app image: nginx:1.20 command: ["/bin/sh", "-c"] args: - echo '<h1 style="color:red">Version 1</h1>' > /usr/share/nginx/html/index.html && nginx -g 'daemon off;' ports: - containerPort: 80EOF
kubectl apply -f app-v1-pods.yaml
# 创建v1 Servicecat > app-v1-service.yaml <<EOFapiVersion: v1kind: Servicemetadata: name: app-v1-servicespec: selector: app: myapp version: v1 ports: - port: 80EOF
kubectl apply -f app-v1-service.yaml步骤2:创建v2版本Pod(3个副本,绿色页面):
cat > app-v2-pods.yaml <<EOFapiVersion: v1kind: Podmetadata: name: app-v2-1 labels: app: myapp version: v2spec: containers: - name: app image: nginx:1.20 command: ["/bin/sh", "-c"] args: - echo '<h1 style="color:green">Version 2 - Canary</h1>' > /usr/share/nginx/html/index.html && nginx -g 'daemon off;' ports: - containerPort: 80---apiVersion: v1kind: Podmetadata: name: app-v2-2 labels: app: myapp version: v2spec: containers: - name: app image: nginx:1.20 command: ["/bin/sh", "-c"] args: - echo '<h1 style="color:green">Version 2 - Canary</h1>' > /usr/share/nginx/html/index.html && nginx -g 'daemon off;' ports: - containerPort: 80---apiVersion: v1kind: Podmetadata: name: app-v2-3 labels: app: myapp version: v2spec: containers: - name: app image: nginx:1.20 command: ["/bin/sh", "-c"] args: - echo '<h1 style="color:green">Version 2 - Canary</h1>' > /usr/share/nginx/html/index.html && nginx -g 'daemon off;' ports: - containerPort: 80EOF
kubectl apply -f app-v2-pods.yaml
# 创建v2 Servicecat > app-v2-service.yaml <<EOFapiVersion: v1kind: Servicemetadata: name: app-v2-servicespec: selector: app: myapp version: v2 ports: - port: 80EOF
kubectl apply -f app-v2-service.yaml
# 等待所有Pod就绪kubectl get pods -l app=myapp创建主Ingress和金丝雀Ingress:
# 主Ingress(v1)cat > canary-main.yaml <<EOFapiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: app-mainspec: ingressClassName: nginx rules: - host: app.example.com http: paths: - path: / pathType: Prefix backend: service: name: app-v1-service port: number: 80EOF
# 金丝雀Ingress(v2,10%流量)cat > canary-test.yaml <<EOFapiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: app-canary annotations: nginx.ingress.kubernetes.io/canary: "true" nginx.ingress.kubernetes.io/canary-weight: "10"spec: ingressClassName: nginx rules: - host: app.example.com http: paths: - path: / pathType: Prefix backend: service: name: app-v2-service port: number: 80EOF
kubectl apply -f canary-main.yamlkubectl apply -f canary-test.yaml
# 配置hosts(测试机器)echo "192.168.100.21 app.example.com" >> /etc/hosts
# 测试流量分配(10%流向v2)for i in {1..100}; do curl -s http://app.example.com | grep -o "Version [0-9]"; done | sort | uniq -c# 输出:约90个Version 1,约10个Version 2
# 逐步增加权重(可选)kubectl patch ingress app-canary -p '{"metadata":{"annotations":{"nginx.ingress.kubernetes.io/canary-weight":"30"}}}'kubectl patch ingress app-canary -p '{"metadata":{"annotations":{"nginx.ingress.kubernetes.io/canary-weight":"50"}}}'
# 清理kubectl delete ingress app-main app-canarykubectl delete svc app-v1-service app-v2-servicekubectl delete pod app-v1-1 app-v1-2 app-v1-3 app-v2-1 app-v2-2 app-v2-33.6 实验5:基于Header的金丝雀(内部测试)
前提:使用实验4创建的Pod和Service,如果已清理则需要重新创建
# 创建基于Header的金丝雀Ingresscat > canary-header.yaml <<EOFapiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: app-canary-header annotations: nginx.ingress.kubernetes.io/canary: "true" nginx.ingress.kubernetes.io/canary-by-header: "X-Canary" nginx.ingress.kubernetes.io/canary-by-header-value: "true"spec: ingressClassName: nginx rules: - host: app.example.com http: paths: - path: / pathType: Prefix backend: service: name: app-v2-service port: number: 80EOF
kubectl apply -f canary-header.yaml
# 测试curl http://app.example.com # 普通用户 -> v1(红色)curl -H "X-Canary: true" http://app.example.com # 测试人员 -> v2(绿色)
# 清理kubectl delete ingress app-canary-header3.7 其他高级功能示例
速率限制:
metadata: annotations: nginx.ingress.kubernetes.io/limit-rps: "5"IP白名单:
metadata: annotations: nginx.ingress.kubernetes.io/whitelist-source-range: "192.168.100.0/24"基本认证:
htpasswd -c auth adminkubectl create secret generic basic-auth --from-file=authmetadata: annotations: nginx.ingress.kubernetes.io/auth-type: basic nginx.ingress.kubernetes.io/auth-secret: basic-auth3.8 常用管理命令
# 查看Ingresskubectl get ingresskubectl describe ingress <name>
# 查看Ingress Controller日志kubectl logs -n ingress-nginx <pod-name> -f
# 查看生成的Nginx配置kubectl exec -n ingress-nginx <pod-name> -- cat /etc/nginx/nginx.conf
# 调试curl -v -H "Host: example.com" http://192.168.100.214. 总结
Ingress核心价值:
- 统一入口 - 一个IP服务多个应用,节省成本
- 7层路由 - 基于域名、路径、Header智能路由
- SSL集中管理 - 统一证书配置,简化管理
- 企业级功能 - 金丝雀、A/B测试、限流、认证
生产环境建议:
✓ 裸金属:DaemonSet + hostNetwork + 外部LB✓ 云平台:LoadBalancer + Deployment✓ 配置资源限制和监控✓ 使用cert-manager自动管理证书✓ 配置备份和灾难恢复计划Ingress vs Service对比:
| 特性 | Service | Ingress |
|---|---|---|
| OSI层 | 4层 | 7层 |
| 路由 | IP+端口 | 域名+路径 |
| 成本 | 每服务1个LB | 多服务共享1个入口 |
| 功能 | 负载均衡 | 路由+SSL+金丝雀等 |
| 场景 | 内部通信 | 外部访问 |