16374 字
82 分钟
02.Kubernetes 学习笔记:Service 服务发现与负载均衡

三、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的核心特点:

  1. 稳定的访问入口

    • Service有一个固定的IP地址(ClusterIP)
    • Service有一个固定的DNS名称(如:mysql-service)
    • 无论后端Pod如何变化,Service的IP和名称都不变
  2. 负载均衡

    • 如果有多个Pod副本,Service会自动分发流量
    • 就像银行有多个窗口,叫号系统自动分配
  3. 服务发现

    • 通过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由三个核心组件协作实现:

  1. Service对象本身
  2. kube-proxy(网络代理)
  3. CoreDNS(DNS服务)

完整的工作流程:

flowchart TD A["1. 用户创建Service<br/>kubectl apply -f service.yaml<br/>selector: app=mysql<br/>port: 3306"] --> B B["2. API Server处理<br/>分配ClusterIP: 10.96.100.50<br/>存储到etcd"] --> C C["3. Endpoint Controller<br/>根据selector找到匹配Pod<br/>创建Endpoints:<br/>10.244.1.10:3306<br/>10.244.2.20:3306"] --> D D["4. kube-proxy配置转发<br/>监听Service和Endpoint变化<br/>配置iptables/ipvs规则"] --> E E["5. CoreDNS注册域名<br/>mysql-service → 10.96.100.50"] --> F F["6. 客户端访问<br/>① DNS解析 mysql-service<br/>② 请求 10.96.100.50:3306<br/>③ kube-proxy拦截<br/>④ DNAT转换到Pod IP<br/>⑤ 到达MySQL Pod"]

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规则

Terminal window
# 查看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.default
mysql-service <- 最常用
跨namespace访问:
mysql-service.production

CoreDNS工作原理:

┌─────────────────────────────────────────────────────────────┐
│ 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:

Terminal window
# 查看CoreDNS Pod
kubectl 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-service

1.6 Service如何关联到Pod:Label和Selector#

Label(标签)

Label是附加到K8s对象上的键值对,用于标识和分类资源。

# Pod定义中的Label
metadata:
labels:
app: mysql # 应用名称
version: "8.0" # 版本
env: production # 环境

Selector(选择器)

Selector用于根据Label选择一组资源。

# Service定义中的Selector
spec:
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:

Terminal window
# 查看Pod的标签
kubectl get pods --show-labels
# 根据标签过滤Pod
kubectl get pods -l app=mysql
# 查看Service的Endpoint
kubectl 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: v1
kind: Service
metadata:
name: mysql-service
spec:
type: ClusterIP # 可以省略,默认就是ClusterIP
selector:
app: mysql # 选择标签为app=mysql的Pod
ports:
- port: 3306 # Service对外暴露的端口
targetPort: 3306 # Pod容器的端口
protocol: TCP

重要字段解释:

字段说明示例
portService暴露的端口(客户端访问这个端口)3306
targetPortPod容器监听的端口(流量转发到这个端口)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:

Terminal window
# 创建Service
kubectl 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-service

2.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. 自动创建ClusterIP

YAML示例:

apiVersion: v1
kind: Service
metadata:
name: nginx-nodeport
spec:
type: NodePort
selector:
app: nginx
ports:
- port: 80 # ClusterIP端口
targetPort: 80 # Pod端口
nodePort: 30080 # Node端口(可选,不指定则自动分配)
protocol: TCP

三种访问方式:

Terminal window
# 方式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:30080
curl http://192.168.100.22:30080
curl http://192.168.100.20:30080

NodePort端口范围:

Terminal window
# 默认范围:30000-32767
# 可以在kube-apiserver启动参数中修改:
# --service-node-port-range=30000-32767

使用场景与限制:

✓ 适用场景:
- 开发/测试环境快速暴露服务
- 小规模应用
- 临时访问需求
✗ 不适用场景:
- 生产环境(端口不好记,不优雅)
- 大量服务(端口容易冲突)
- 需要域名和HTTPS(要额外配置)
生产环境建议:
使用LoadBalancer或Ingress

2.3 LoadBalancer:云平台负载均衡器#

什么是LoadBalancer?

LoadBalancer是在NodePort基础上,自动创建云平台的外部负载均衡器,提供统一的外部访问入口。

本质理解:

LoadBalancer = ClusterIP + NodePort + 云平台LB
创建流程:
1. 自动创建ClusterIP
2. 自动创建NodePort
3. 调用云平台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: v1
kind: Service
metadata:
name: nginx-lb
spec:
type: LoadBalancer
selector:
app: nginx
ports:
- port: 80
targetPort: 80

查看LoadBalancer:

Terminal window
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
✗ 功能不可用
✓ 解决方案:MetalLB

MetalLB:裸金属环境的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/v1beta1
kind: IPAddressPool
metadata:
name: first-pool
namespace: metallb-system
spec:
addresses:
- 192.168.100.100-192.168.100.150 # IP范围
---
# Layer 2模式配置
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: l2-adv
namespace: metallb-system
spec:
ipAddressPools:
- first-pool

MetalLB两种模式对比:

特性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指向集群B

DNS解析对比:

普通Service:
┌─────────────────────────────────────┐
│ nslookup nginx-service │
│ 返回:10.96.100.50 (ClusterIP) │
└─────────────────────────────────────┘
ExternalName Service:
┌─────────────────────────────────────┐
│ nslookup nginx-external │
│ 返回: │
│ CNAME db.external.com │
└─────────────────────────────────────┘

YAML示例:

apiVersion: v1
kind: Service
metadata:
name: nginx-external
spec:
type: ExternalName
externalName: mysql.external-domain.com
# 注意:没有selector,没有ports

测试:

Terminal window
# 创建Service
kubectl 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: v1
kind: Service
metadata:
name: nginx-headless
spec:
clusterIP: None # 关键:设置为None
selector:
app: nginx
ports:
- port: 80
targetPort: 80

StatefulSet + Headless Service:

# Headless Service
apiVersion: v1
kind: Service
metadata:
name: web-headless
spec:
clusterIP: None
selector:
app: web
ports:
- port: 80
---
# StatefulSet
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
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: 80

DNS命名规则:

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
✓ 适合有状态应用

测试:

Terminal window
# 创建资源
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
# 访问特定Pod
kubectl 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(如读写分离)
- 根据数据分片选择特定Pod

2.6 Service类型总结对比#

类型ClusterIPNodePortLoadBalancerExternalNameHeadless
ClusterIPNone
访问范围集群内部集群内+NodePort集群内+外部LB转发外部集群内部
外部访问
DNS解析Service IPService IPService IPCNAMEPod IP列表
负载均衡
使用场景内部服务开发测试生产环境外部服务有状态应用
依赖云平台/MetalLB外部DNS

选择建议:

内部服务通信:ClusterIP(默认)
快速暴露服务(测试):NodePort
生产环境对外暴露:LoadBalancer(云平台)或 Ingress
访问外部服务:ExternalName
有状态应用:Headless + StatefulSet

3. Service实战操作#

3.1 将kube-proxy切换到IPVS模式#

在所有节点安装IPVS模块(若未安装):

Terminal window
# 在master、node1、node2上执行
# 加载IPVS内核模块
modprobe -- ip_vs
modprobe -- ip_vs_rr
modprobe -- ip_vs_wrr
modprobe -- ip_vs_sh
modprobe -- nf_conntrack
# 验证
lsmod | grep -e ip_vs -e nf_conntrack
# 设置开机自动加载
cat > /etc/modules-load.d/ipvs.conf <<EOF
ip_vs
ip_vs_rr
ip_vs_wrr
ip_vs_sh
nf_conntrack
EOF
# 安装ipvsadm工具
yum install -y ipvsadm ipset

修改kube-proxy配置:

Terminal window
# 编辑ConfigMap
kubectl edit configmap kube-proxy -n kube-system
# 找到mode字段,修改为:
mode: "ipvs"
ipvs:
scheduler: "rr" # 轮询算法

重启kube-proxy:

Terminal window
# 删除所有kube-proxy Pod,让其自动重建
kubectl delete pod -n kube-system -l k8s-app=kube-proxy
# 查看状态
kubectl get pods -n kube-system | grep kube-proxy

验证IPVS模式:

Terminal window
# 在集群机器上查看IPVS规则
ipvsadm -Ln
# 查看kube-proxy日志
kubectl logs -n kube-system <kube-proxy-pod-name> | grep -i ipvs

3.2 实验准备:改造nginx镜像支持通过Service名称访问数据库#

为什么要这样做?

在生产环境中,我们绝不应该在应用中硬编码数据库的IP地址。原因很简单:

  • Pod的IP是动态变化的,Pod重启后IP会改变
  • 使用Service名称可以实现服务发现,K8s的DNS会自动解析
  • 这是云原生应用的最佳实践

步骤1:创建支持环境变量配置的PHP应用(在harbor机器)

Terminal window
cd /root/k8s-demo/nginx
# 创建新版本的index.php
cat > index.php <<'EOF'
<?php
header('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:构建并推送新镜像

Terminal window
# 构建镜像(使用之前的Dockerfile)
docker build -t reg.westos.org/library/nginx-php:v2 .
# 推送到私有仓库
docker push reg.westos.org/library/nginx-php:v2
# 验证镜像已推送
docker images | grep nginx-php

3.3 实验:使用Pod演示各类Service#

实验目标:

  1. 理解 ClusterIP、NodePort、LoadBalancer、Headless 四种Service类型的区别
  2. 掌握通过Service名称实现服务发现的方法
  3. 学会使用环境变量配置应用

实验架构:

┌─────────────────────────────────────────────────────┐
│ 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

Terminal window
cd /root/k8s-yaml
# 创建MySQL Pod配置文件
cat > mysql-pod.yaml <<EOF
apiVersion: v1
kind: Pod
metadata:
name: mysql-pod
labels:
# 重要!Service通过这个标签找到Pod
app: mysql
tier: backend
spec:
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: 5
EOF
# 创建Pod
kubectl apply -f mysql-pod.yaml
# 查看Pod状态(等待Running)
kubectl get pods -l app=mysql
kubectl get pods mysql-pod -o wide

参数详解:

  • labels: app: mysql:给Pod打标签,Service会通过selector选择这个标签
  • containerPort: 3306:声明容器监听的端口
  • livenessProbe:存活探针,检测MySQL进程是否存在
  • readinessProbe:就绪探针,检测MySQL是否可以接受连接

步骤2:创建ClusterIP Service

Terminal window
# 创建Service配置文件
cat > mysql-service.yaml <<EOF
apiVersion: v1
kind: Service
metadata:
name: mysql-service
spec:
type: ClusterIP
selector:
app: mysql
ports:
- port: 3306
targetPort: 3306
protocol: TCP
name: mysql
EOF
# 创建Service
kubectl apply -f mysql-service.yaml
# 查看Service详细信息
kubectl get svc mysql-service
kubectl describe svc mysql-service
# 查看Endpoints(Service会自动发现后端Pod)
kubectl get endpoints mysql-service

参数详解:

  • selector: app: mysql:通过标签选择器,Service自动发现匹配的Pod
  • port: 3306对外暴露的端口(别人访问Service用这个端口)
  • targetPort: 3306实际监听的端口(流量转发到Pod的这个端口)
  • Endpoints自动创建,记录所有匹配Pod的IP和端口

为什么会自动关联?

1. Pod有标签:app=mysql
2. Service有选择器:selector: app=mysql
3. K8s自动匹配:Service发现所有带app=mysql标签的Pod
4. 创建Endpoints:记录Pod的IP:Port列表
5. 配置负载均衡:kube-proxy配置iptables/ipvs规则

步骤3:测试Service的DNS解析

Terminal window
# 测试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.local

DNS解析规则:

在K8s集群中,Service有多种DNS名称形式:

短名称(同namespace): mysql-service
带namespace : mysql-service.default
完整域名(FQDN): mysql-service.default.svc.cluster.local

步骤4:测试数据库连接

Terminal window
# 使用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)

Terminal window
# 创建配置文件
cat > nginx-pod-1.yaml <<EOF
apiVersion: v1
kind: Pod
metadata:
name: nginx-pod-1
labels:
app: nginx
version: v2
spec:
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: 3
EOF
# 应用配置
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') 会读到这个值

这样配置后:

  1. PHP应用启动时,环境变量 DB_HOST = “mysql-service”
  2. 代码执行 new mysqli($db_host, ...) 时,会连接到 “mysql-service”
  3. K8s的DNS把 “mysql-service” 解析为Service的ClusterIP
  4. Service把流量转发到MySQL Pod

步骤2:创建更多Nginx Pod(模拟多副本)

Terminal window
# 创建第2个Pod
cat > nginx-pod-2.yaml <<EOF
apiVersion: v1
kind: Pod
metadata:
name: nginx-pod-2
labels:
app: nginx
version: v2
spec:
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个Pod
cat > nginx-pod-3.yaml <<EOF
apiVersion: v1
kind: Pod
metadata:
name: nginx-pod-3
labels:
app: nginx
version: v2
spec:
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.yaml
kubectl apply -f nginx-pod-3.yaml
# 查看所有Nginx Pod
kubectl get pods -l app=nginx -o wide

步骤3:创建NodePort Service

Terminal window
# 创建配置文件
cat > nginx-nodeport.yaml <<EOF
apiVersion: v1
kind: Service
metadata:
name: nginx-nodeport
spec:
type: NodePort # 指定为NodePort类型
selector:
app: nginx # 选择所有app=nginx的Pod
ports:
- port: 80 # Service端口
targetPort: 80 # Pod端口
nodePort: 30080 # Node端口(30000-32767)
protocol: TCP
name: http
EOF
# 应用配置
kubectl apply -f nginx-nodeport.yaml
# 查看Service
kubectl 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基础上,额外暴露NodePort
  • nodePort: 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:测试访问和负载均衡

Terminal window
# 从集群外部访问(可以使用任意Node的IP)
curl http://192.168.100.21:30080
curl http://192.168.100.22:30080
curl 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
# ...

观察数据库连接:

Terminal window
# 查看完整输出
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名称的优势

Terminal window
# 查看MySQL Pod当前IP
kubectl get pod mysql-pod -o wide
# 假设IP是: 10.244.1.5
# 删除并重建MySQL Pod(模拟Pod重启)
kubectl delete pod mysql-pod
kubectl 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指向新Pod
kubectl 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:

Terminal window
cat > nginx-loadbalancer.yaml <<EOF
apiVersion: v1
kind: Service
metadata:
name: nginx-loadbalancer
spec:
type: LoadBalancer # 指定为LoadBalancer类型
selector:
app: nginx
ports:
- port: 80
targetPort: 80
protocol: TCP
name: http
EOF
# 应用配置
kubectl apply -f nginx-loadbalancer.yaml
# 查看Service
kubectl 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>,因为没有云平台负载均衡器

访问方式:

Terminal window
# 云平台环境:通过EXTERNAL-IP访问
curl http://<EXTERNAL-IP>
# 本地环境:仍然可以通过NodePort访问
curl http://192.168.100.21:31234

LoadBalancer 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

Terminal window
# 创建3个测试Pod
cat > web-pods.yaml <<EOF
apiVersion: v1
kind: Pod
metadata:
name: web-pod-1
labels:
app: web
role: backend
spec:
containers:
- name: nginx
image: nginx:1.20
ports:
- containerPort: 80
---
apiVersion: v1
kind: Pod
metadata:
name: web-pod-2
labels:
app: web
role: backend
spec:
containers:
- name: nginx
image: nginx:1.20
ports:
- containerPort: 80
---
apiVersion: v1
kind: Pod
metadata:
name: web-pod-3
labels:
app: web
role: backend
spec:
containers:
- name: nginx
image: nginx:1.20
ports:
- containerPort: 80
EOF
# 应用配置
kubectl apply -f web-pods.yaml
# 查看Pod及其IP
kubectl get pods -l app=web -o wide

步骤2:创建Headless Service

Terminal window
cat > web-headless.yaml <<EOF
apiVersion: v1
kind: Service
metadata:
name: web-headless
spec:
clusterIP: None # 关键!设置为None表示Headless
selector:
app: web
ports:
- port: 80
targetPort: 80
EOF
# 应用配置
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解析

Terminal window
# 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.local

Headless 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场景)

Terminal window
# 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名称

清理实验资源:

Terminal window
# 清理Nginx Pod和Service
kubectl delete pod nginx-pod-1 nginx-pod-2 nginx-pod-3
kubectl delete svc nginx-nodeport nginx-loadbalancer
# 清理Web Pod和Service
kubectl delete pod web-pod-1 web-pod-2 web-pod-3
kubectl delete svc web-headless
# 保留MySQL Pod和Service供后续实验使用
# kubectl delete pod mysql-pod
# kubectl delete svc mysql-service

3.4 Service实验总结与最佳实践#

四种Service类型对比:

特性ClusterIPNodePortLoadBalancerHeadless
ClusterIPNone
访问范围集群内部集群内+NodePort集群内+外部LB集群内部
外部访问
DNS解析Service IPService IPService IPPod IP列表
负载均衡✗(客户端)
使用场景内部服务开发测试生产环境有状态应用
依赖云平台

生产最佳实践:

  1. 服务发现 - 永远使用Service名称

    # ✅ 正确
    env:
    - name: DB_HOST
    value: "mysql-service"
    # ❌ 错误
    env:
    - name: DB_HOST
    value: "10.244.1.5" # 硬编码IP会在Pod重启后失效
  2. 内部服务使用ClusterIP

    # 数据库、缓存等后端服务不需要对外暴露
    apiVersion: v1
    kind: Service
    metadata:
    name: mysql-service
    spec:
    type: ClusterIP # 或省略,默认就是ClusterIP
    selector:
    app: mysql
  3. 外部访问优先使用Ingress

    生产环境推荐:
    外部 → Ingress Controller → Service (ClusterIP) → Pod
    而不是:
    外部 → NodePort → Pod
  4. 环境变量vs ConfigMap

    # 简单配置:直接使用env
    env:
    - name: DB_HOST
    value: "mysql-service"
    # 复杂配置:使用ConfigMap
    envFrom:
    - configMapRef:
    name: app-config
  5. 健康检查必不可少

    # 确保Pod健康才加入Endpoints
    readinessProbe:
    httpGet:
    path: /health
    port: 80
    initialDelaySeconds: 5
    periodSeconds: 3

实验收获:

通过本次实验,我们学会了:

  • ✅ 创建和管理四种类型的Service
  • ✅ 使用标签选择器关联Pod和Service
  • ✅ 通过Service名称实现服务发现
  • ✅ 使用环境变量配置应用
  • ✅ 理解K8s的DNS机制
  • ✅ 掌握生产环境的最佳实践

下一步,我们将学习Deployment,用更高级的方式管理Pod副本!

4.总结#

Service核心价值:

  1. 稳定的访问入口

    • 提供固定的ClusterIP和DNS名称
    • Pod IP变化对应用透明
  2. 服务发现

    • 通过DNS自动解析Service名称
    • 无需配置中心
  3. 负载均衡

    • 自动在多个Pod之间分发流量
    • 支持多种算法(IPVS模式)
  4. 配置解耦

    • 应用使用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/v1
kind: Ingress
metadata:
name: my-ingress
spec:
rules:
- host: www.example.com # 域名
http:
paths:
- path: /api # 路径
pathType: Prefix
backend:
service:
name: api-service # 指向的Service
port:
number: 80

Ingress资源只是配置规则,本身不处理流量!

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/v1
kind: Ingress
metadata:
name: multi-domain-ingress
spec:
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-pod

2.2 基于路径的路由#

功能说明: 根据URL路径,将流量转发到不同的Service。

使用场景:

场景:一个统一的API网关
- /api/users → 用户服务
- /api/orders → 订单服务
- /api/products → 商品服务
- /static → 静态文件服务
优势:
✓ 统一域名(如api.example.com)
✓ 路径清晰,符合RESTful规范
✓ 便于API管理和版本控制

配置示例:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: path-based-ingress
spec:
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: 80

pathType详解:

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/users

2.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: v1
kind: Secret
metadata:
name: example-tls
type: kubernetes.io/tls
data:
tls.crt: LS0tLS1CRUdJTi... # Base64编码的证书
tls.key: LS0tLS1CRUdJTi... # Base64编码的私钥
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: https-ingress
spec:
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:

Terminal window
# 方式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/v1
kind: Ingress
metadata:
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: 80

2.4 URL重写和重定向#

功能说明: 修改请求路径或重定向到其他URL。

使用场景1:路径重写

场景:前端路径和后端路径不一致
用户访问:http://api.example.com/v1/users
后端期望:http://api.example.com/users
需要去掉 /v1 前缀

路径重写配置:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: rewrite-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
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/v1
kind: Ingress
metadata:
name: redirect-ingress
annotations:
nginx.ingress.kubernetes.io/permanent-redirect: https://new.example.com
spec:
rules:
- host: old.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: dummy-service # 实际不会访问
port:
number: 80

2.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/v1
kind: Ingress
metadata:
name: app-ingress
spec:
rules:
- host: www.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: app-v1 # 稳定版本
port:
number: 80
---
# 金丝雀Ingress(新版本v2)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
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/v1
kind: Ingress
metadata:
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

测试:

Terminal window
# 普通用户访问(到v1)
curl http://www.example.com
# 测试人员访问(到v2)
curl -H "canary: true" http://www.example.com

2.6 A/B测试#

功能说明: 根据用户特征,将流量导向不同版本,比较效果。

使用场景:

场景:测试新UI设计是否提升转化率
A版本:原有UI
B版本:新UI设计
策略:
- 50%用户看A版本
- 50%用户看B版本
收集数据:
- A版本转化率:5%
- B版本转化率:8%
结论:B版本更好,全量上线B版本

A/B测试 vs 金丝雀发布:

金丝雀发布:
- 目标:验证稳定性
- 逐步增加流量
- 出问题立即回滚
A/B测试:
- 目标:比较效果
- 流量比例固定
- 收集数据分析

配置示例:基于Cookie的A/B测试

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
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

测试:

Terminal window
# 用户A(看到A版本)
curl http://www.example.com
# 用户B(看到B版本)
curl --cookie "ab_test=version_b" http://www.example.com

2.7 其他高级功能#

1. 速率限制(Rate Limiting)

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
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: 80

2. IP白名单/黑名单

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
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: 80

3. 基本认证(Basic Auth)

Terminal window
# 创建密码文件
htpasswd -c auth admin
# 输入密码
# 创建Secret
kubectl create secret generic basic-auth --from-file=auth
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
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: 80

4. 跨域配置(CORS)

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
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: 80

2.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 Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-ingress-controller
namespace: ingress-nginx
spec:
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 Service
apiVersion: v1
kind: Service
metadata:
name: ingress-nginx
namespace: ingress-nginx
spec:
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/v1
kind: DaemonSet
metadata:
name: nginx-ingress-controller
namespace: ingress-nginx
spec:
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:

Terminal window
# 给Node打标签
kubectl label node node1 ingress=true
kubectl 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: v1
kind: Service
metadata:
name: ingress-nginx
namespace: ingress-nginx
spec:
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+DeploymentDaemonSet+hostNetworkNodePort+Deployment
适用环境云平台裸金属/私有云测试环境
外部访问LB公网IP:80/443Node IP:80/443Node 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

Terminal window
kubectl label node node1 ingress=true
kubectl label node node2 ingress=true

步骤2:下载并修改部署文件

Terminal window
# 下载官方部署文件
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:部署

Terminal window
kubectl apply -f deploy.yaml
kubectl get pods -n ingress-nginx -o wide

3.2 实验1:基于域名路由#

给containerd配置代理:

在此之前,为了方便我们下面使用docker官方的镜像,由于国内无法直接拉取官方镜像,我们需要给节点的containerd配置代理,设置我们的私有仓库reg.westos.org不走代理。

Terminal window
#在每个k8s节点上运行,换成你的代理地址
mkdir -p /etc/systemd/system/containerd.service.d
cat <<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"
EOF
sudo systemctl daemon-reload
sudo systemctl restart containerd

步骤1:创建两个应用Pod:

Terminal window
# 创建nginx应用Pod(2个副本)
cat > nginx-app-pods.yaml <<EOF
apiVersion: v1
kind: Pod
metadata:
name: nginx-app-1
labels:
app: nginx-app
spec:
containers:
- name: nginx
image: nginx:1.20
ports:
- containerPort: 80
---
apiVersion: v1
kind: Pod
metadata:
name: nginx-app-2
labels:
app: nginx-app
spec:
containers:
- name: nginx
image: nginx:1.20
ports:
- containerPort: 80
EOF
kubectl apply -f nginx-app-pods.yaml
# 创建httpd应用Pod(2个副本)
cat > httpd-app-pods.yaml <<EOF
apiVersion: v1
kind: Pod
metadata:
name: httpd-app-1
labels:
app: httpd-app
spec:
containers:
- name: httpd
image: httpd:2.4
ports:
- containerPort: 80
---
apiVersion: v1
kind: Pod
metadata:
name: httpd-app-2
labels:
app: httpd-app
spec:
containers:
- name: httpd
image: httpd:2.4
ports:
- containerPort: 80
EOF
kubectl apply -f httpd-app-pods.yaml
# 等待Pod就绪
kubectl get pods -l app=nginx-app
kubectl get pods -l app=httpd-app

步骤2:创建Service:

Terminal window
# 创建nginx-service
cat > nginx-service.yaml <<EOF
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
selector:
app: nginx-app
ports:
- port: 80
targetPort: 80
EOF
kubectl apply -f nginx-service.yaml
# 创建httpd-service
cat > httpd-service.yaml <<EOF
apiVersion: v1
kind: Service
metadata:
name: httpd-service
spec:
selector:
app: httpd-app
ports:
- port: 80
targetPort: 80
EOF
kubectl apply -f httpd-service.yaml
# 查看Service和Endpoints
kubectl get svc
kubectl get endpoints
# 创建基于域名的Ingress
cat > domain-ingress.yaml <<EOF
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: domain-ingress
spec:
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: 80
EOF
kubectl apply -f domain-ingress.yaml
# 配置hosts(测试机器)
echo "192.168.100.21 nginx.example.com httpd.example.com" >> /etc/hosts
# 测试
curl http://nginx.example.com
curl http://httpd.example.com
# 清理
kubectl delete ingress domain-ingress
kubectl delete svc nginx-service httpd-service
kubectl delete pod nginx-app-1 nginx-app-2 httpd-app-1 httpd-app-2

3.3 实验2:基于路径路由 + URL重写#

前提:使用实验1创建的Pod和Service,如果已清理则需要重新创建

Terminal window
# 创建基于路径的Ingress
cat > path-rewrite-ingress.yaml <<EOF
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: path-rewrite
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /\$2
spec:
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: 80
EOF
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-rewrite

3.4 实验3:HTTPS配置#

前提:使用实验1创建的nginx-service,如果已清理则需要重新创建

Terminal window
# 生成自签名证书
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout tls.key -out tls.crt \
-subj "/CN=secure.example.com"
# 创建TLS Secret
kubectl create secret tls example-tls --cert=tls.crt --key=tls.key
# 创建HTTPS Ingress
cat > https-ingress.yaml <<EOF
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
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: 80
EOF
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-ingress
kubectl delete secret example-tls

3.5 实验4:金丝雀发布#

步骤1:创建v1版本Pod(3个副本,红色页面):

Terminal window
cat > app-v1-pods.yaml <<EOF
apiVersion: v1
kind: Pod
metadata:
name: app-v1-1
labels:
app: myapp
version: v1
spec:
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: v1
kind: Pod
metadata:
name: app-v1-2
labels:
app: myapp
version: v1
spec:
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: v1
kind: Pod
metadata:
name: app-v1-3
labels:
app: myapp
version: v1
spec:
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
EOF
kubectl apply -f app-v1-pods.yaml
# 创建v1 Service
cat > app-v1-service.yaml <<EOF
apiVersion: v1
kind: Service
metadata:
name: app-v1-service
spec:
selector:
app: myapp
version: v1
ports:
- port: 80
EOF
kubectl apply -f app-v1-service.yaml

步骤2:创建v2版本Pod(3个副本,绿色页面):

Terminal window
cat > app-v2-pods.yaml <<EOF
apiVersion: v1
kind: Pod
metadata:
name: app-v2-1
labels:
app: myapp
version: v2
spec:
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: v1
kind: Pod
metadata:
name: app-v2-2
labels:
app: myapp
version: v2
spec:
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: v1
kind: Pod
metadata:
name: app-v2-3
labels:
app: myapp
version: v2
spec:
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
EOF
kubectl apply -f app-v2-pods.yaml
# 创建v2 Service
cat > app-v2-service.yaml <<EOF
apiVersion: v1
kind: Service
metadata:
name: app-v2-service
spec:
selector:
app: myapp
version: v2
ports:
- port: 80
EOF
kubectl apply -f app-v2-service.yaml
# 等待所有Pod就绪
kubectl get pods -l app=myapp

创建主Ingress和金丝雀Ingress:

Terminal window
# 主Ingress(v1)
cat > canary-main.yaml <<EOF
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-main
spec:
ingressClassName: nginx
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: app-v1-service
port:
number: 80
EOF
# 金丝雀Ingress(v2,10%流量)
cat > canary-test.yaml <<EOF
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
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: 80
EOF
kubectl apply -f canary-main.yaml
kubectl 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-canary
kubectl delete svc app-v1-service app-v2-service
kubectl delete pod app-v1-1 app-v1-2 app-v1-3 app-v2-1 app-v2-2 app-v2-3

3.6 实验5:基于Header的金丝雀(内部测试)#

前提:使用实验4创建的Pod和Service,如果已清理则需要重新创建

Terminal window
# 创建基于Header的金丝雀Ingress
cat > canary-header.yaml <<EOF
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
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: 80
EOF
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-header

3.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"

基本认证:

Terminal window
htpasswd -c auth admin
kubectl create secret generic basic-auth --from-file=auth
metadata:
annotations:
nginx.ingress.kubernetes.io/auth-type: basic
nginx.ingress.kubernetes.io/auth-secret: basic-auth

3.8 常用管理命令#

Terminal window
# 查看Ingress
kubectl get ingress
kubectl 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.21

4. 总结#

Ingress核心价值:

  1. 统一入口 - 一个IP服务多个应用,节省成本
  2. 7层路由 - 基于域名、路径、Header智能路由
  3. SSL集中管理 - 统一证书配置,简化管理
  4. 企业级功能 - 金丝雀、A/B测试、限流、认证

生产环境建议:

✓ 裸金属:DaemonSet + hostNetwork + 外部LB
✓ 云平台:LoadBalancer + Deployment
✓ 配置资源限制和监控
✓ 使用cert-manager自动管理证书
✓ 配置备份和灾难恢复计划

Ingress vs Service对比:

特性ServiceIngress
OSI层4层7层
路由IP+端口域名+路径
成本每服务1个LB多服务共享1个入口
功能负载均衡路由+SSL+金丝雀等
场景内部通信外部访问

02.Kubernetes 学习笔记:Service 服务发现与负载均衡
https://dev-null-sec.github.io/posts/02-k8s学习笔记-service服务发现与负载均衡/
作者
DevNull
发布于
2024-11-09
许可协议
CC BY-NC-SA 4.0