Service 与网络模型:集群内服务发现
深入理解 Kubernetes Service 的四种类型、Ingress 控制器、以及基于 DNS 的服务发现机制。
概述
在 Kubernetes 中,Pod 的 IP 是临时的、动态的。Service 提供了稳定的访问入口,屏蔽了后端 Pod 的变化。本文将深入探讨:
学习目标:
- 理解 Service 的设计理念与四种类型
- 掌握 ClusterIP、NodePort、LoadBalancer 的使用场景
- 深入理解 Ingress 和 Ingress Controller
- 理解 CoreDNS 服务发现机制
- 掌握网络策略实现安全隔离
Service 设计理念
为什么需要 Service?
┌─────────────────────────────────────────────────────────────────┐
│ Service 解决的问题 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Pod IP 变化(调度、扩缩容、故障重启) │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ Pod-1 (10.0.0.1) ← 被调度走 │ │
│ │ Pod-2 (10.0.0.2) ← 新建 │ │
│ │ Pod-3 (10.0.0.3) ← 故障重启 │ │
│ │ │ │
│ │ IP 不断变化,客户端无法固定访问 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Service │ │
│ │ │ │
│ │ 稳定的虚拟 IP (ClusterIP) │ │
│ │ 负载均衡到后端 Pod │ │
│ │ 自动跟踪 Pod 变化 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 客户端只需知道 Service 名称 │
│ │
└─────────────────────────────────────────────────────────────────┘
Service 工作原理
┌─────────────────────────────────────────────────────────────────┐
│ Service 架构图 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ │
│ │ Service │ │
│ │ ClusterIP │ │
│ │ 10.96.0.1 │ │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ kube-proxy │ │
│ │ (每个 Node) │ │
│ └────────┬────────┘ │
│ │ │
│ ┌───────────────────┼───────────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Pod │ │ Pod │ │ Pod │ │
│ │10.0.0.1 │ │10.0.0.2 │ │10.0.0.3 │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │
│ 关键组件: │
│ - EndpointSlice:记录后端 Pod IP 和端口 │
│ - kube-proxy:iptables/ipvs 规则实现负载均衡 │
│ │
└─────────────────────────────────────────────────────────────────┘
Service 四种类型
类型概览
┌─────────────────────────────────────────────────────────────────┐
│ Service 类型对比 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ │
│ │ ClusterIP │ │ NodePort │ │ LoadBalancer │ │
│ ├────────────────┤ ├────────────────┤ ├────────────────┤ │
│ │ 默认,仅集群 │ │ 通过 Node IP │ │ 云厂商负载 │ │
│ │ 内访问 │ │ 访问 │ │ 均衡器访问 │ │
│ │ │ │ │ │ │ │
│ │ cluster-in │ │ node-port │ │ lb │ │
│ │ │ │ (30000+) │ │ │ │
│ └────────────────┘ └────────────────┘ └────────────────┘ │
│ │
│ ┌────────────────┐ ┌────────────────┐ │
│ │ ExternalName │ │ Headless │ │
│ ├────────────────┤ ├────────────────┤ │
│ │ CNAME 映射 │ │ 无虚拟 IP │ │
│ │ 外部服务 │ │ 直连 Pod │ │
│ └────────────────┘ └────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
ClusterIP(默认)
# clusterip-example.yaml
apiVersion: v1
kind: Service
metadata:
name: backend-service
namespace: default
spec:
type: ClusterIP
# 集群内部虚拟 IP
clusterIP: 10.96.0.100
selector:
app: backend
ports:
- name: http
protocol: TCP
port: 80 # Service 端口
targetPort: 8080 # Pod 端口
- name: grpc
protocol: TCP
port: 9090
targetPort: 9090
# 访问方式:
# - 集群内其他 Pod:backend-service:80
# - 同 Namespace:http://backend-service
# - 不同 Namespace:http://backend-service.namespace
NodePort
# nodeport-example.yaml
apiVersion: v1
kind: Service
metadata:
name: myapp-nodeport
spec:
type: NodePort
selector:
app: myapp
ports:
- name: http
protocol: TCP
port: 80
targetPort: 8080
# NodePort:30000-32767
nodePort: 30080 # 可选,不指定则随机分配
# 访问方式:
# - 集群外:http://<NodeIP>:30080
# - 任意节点都可以访问
# - 负载分散到多个节点
┌─────────────────────────────────────────────────────────────────┐
│ NodePort 流量路径 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 外部请求 │
│ │ │
│ ▼ │
│ ┌──────────┐ │
│ │ Node IP │ │
│ │:30080 │ │
│ └────┬─────┘ │
│ │ │
│ ▼ (kube-proxy 路由) │
│ ┌──────────┐ │
│ │ Service │ │
│ └────┬─────┘ │
│ │ │
│ ▼ (负载均衡) │
│ ┌─────┬─────┬─────┐ │
│ │ Pod │ Pod │ Pod │ │
│ └─────┴─────┴─────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
LoadBalancer
# loadbalancer-example.yaml
apiVersion: v1
kind: Service
metadata:
name: myapp-lb
spec:
type: LoadBalancer
selector:
app: myapp
ports:
- name: http
protocol: TCP
port: 80
targetPort: 8080
# 特性:
# - 自动创建云厂商负载均衡器
# - 外部通过 LB IP 访问
# - 与云厂商网络集成
# 搭配 External IP(指定出口 IP)
spec:
type: LoadBalancer
loadBalancerIP: "203.0.113.50"
loadBalancerSourceRanges:
- "10.0.0.0/8"
- "203.0.113.0/24"
externalTrafficPolicy: Cluster # 或 Local
healthCheckNodePort: 0
ExternalName
# externalname-example.yaml
apiVersion: v1
kind: Service
metadata:
name: mysql-external
spec:
type: ExternalName
# 直接返回 CNAME 记录
externalName: mysql.prod.example.com
# 使用场景:
# - 集群内服务访问外部服务用固定域名
# - 迁移时保持服务名不变
# - 访问云数据库等托管服务
Headless Service
# headless-example.yaml
apiVersion: v1
kind: Service
metadata:
name: backend-headless
spec:
type: ClusterIP
clusterIP: None # 关键:无 ClusterIP
selector:
app: backend
ports:
- port: 80
targetPort: 8080
# 行为:
# - 不分配 ClusterIP
# - DNS 查询返回 Pod IP(不是 Service IP)
# - 可用于有状态应用的稳定网络标识
┌─────────────────────────────────────────────────────────────────┐
│ Headless vs ClusterIP 对比 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ClusterIP Service: │
│ ┌────────────┐ │
│ │ Service │ → 10.96.0.100 → Pod IPs │
│ │ (虚拟 IP) │ │
│ └────────────┘ │
│ DNS: backend.default.svc.cluster.local → 10.96.0.100 │
│ │
│ Headless Service: │
│ ┌────────────┐ │
│ │ Service │ → 直连 Pod IPs │
│ │ (无 IP) │ │
│ └────────────┘ │
│ DNS: backend.default.svc.cluster.local → [Pod1.IP, Pod2.IP] │
│ │
│ 使用场景: │
│ - Headless + StatefulSet:Pod 有稳定的主机名 │
│ - 自定义负载均衡策略 │
│ - Pod 间直接通信 │
│ │
└─────────────────────────────────────────────────────────────────┘
Ingress:HTTP/HTTPS 入口
Ingress 架构
┌─────────────────────────────────────────────────────────────────┐
│ Ingress 架构图 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 外部请求 │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Ingress │ │
│ │ │ │
│ │ /api/* → backend-service:8080 │ │
│ │ /web/* → frontend-service:80 │ │
│ │ /* → default-backend:80 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ (Ingress Controller 解析) │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Ingress Controller │ │
│ │ (nginx / traefik / contour / 云厂商) │ │
│ │ │ │
│ │ - 监听 Ingress 资源变化 │ │
│ │ - 配置负载均衡规则 │ │
│ │ - 自动更新路由配置 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ backend │ │frontend │ │ default │ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
Ingress 基础配置
# ingress-basic.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myapp-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
ingressClassName: nginx
rules:
- host: myapp.example.com
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: backend-service
port:
number: 8080
- path: /web
pathType: Prefix
backend:
service:
name: frontend-service
port:
number: 80
TLS 配置
# ingress-tls.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myapp-ingress-tls
spec:
ingressClassName: nginx
tls:
- hosts:
- myapp.example.com
secretName: myapp-tls-secret
rules:
- host: myapp.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: myapp-service
port:
number: 80
自动 HTTPS(Let's Encrypt)
# ingress-cert-manager.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myapp-ingress
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
ingressClassName: nginx
tls:
- hosts:
- myapp.example.com
secretName: myapp-tls
rules:
- host: myapp.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: myapp-service
port:
number: 80
---
# ClusterIssuer
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: your-email@example.com
privateKeySecretRef:
name: letsencrypt-prod
solvers:
- http01:
ingress:
class: nginx
路径重写与重定向
# ingress-rewrite.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /api/$2
# 或者
# nginx.ingress.kubernetes.io/app-root: /web
spec:
ingressClassName: nginx
rules:
- http:
paths:
# /v1/api/* → backend:8080/api/*
- path: /v1(/|$)(.*)
pathType: ImplementationSpecific
backend:
service:
name: backend-service
port:
number: 8080
服务发现:DNS 机制
Kubernetes DNS
┌─────────────────────────────────────────────────────────────────┐
│ CoreDNS 工作原理 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ CoreDNS │ │
│ │ │ │
│ │ kube-dns 插件 │ │
│ │ - kube-system namespace │ │
│ │ - Service 域名解析 │ │
│ │ - 集成 Kubernetes API │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ DNS 记录类型 │ │
│ │ │ │
│ │ A记录:Service IP → ClusterIP │ │
│ │ SRV记录:_port._protocol.svc → Service名:Port │ │
│ │ CNAME:ExternalName → externalName │ │
│ │ PTR记录:反向查询 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
DNS 命名规范
┌─────────────────────────────────────────────────────────────────┐
│ FQDN 格式 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ <service>.<namespace>.svc.<clusterdomain> │
│ │
│ 示例: │
│ ┌───────────┐ ┌───────────┐ ┌────────┐ ┌─────────┐ │
│ │ Service │ │ Namespace │ │ svc │ │cluster │ │
│ └───────────┘ └───────────┘ └────────┘ └─────────┘ │
│ │
│ 完整格式:backend-service.default.svc.cluster.local │
│ │
│ 缩写规则: │
│ - 同 Namespace:backend-service(最常用) │
│ - 同集群不同 NS:backend-service.default │
│ - 完整 FQDN:backend-service.default.svc.cluster.local │
│ │
│ 默认 clusterdomain:cluster.local │
│ │
└─────────────────────────────────────────────────────────────────┘
DNS 访问示例
# pod-dns-access.yaml
apiVersion: v1
kind: Pod
metadata:
name: dns-test
spec:
containers:
- name: test
image: busybox:1.36
command:
- sleep
- "3600"
# 在容器内测试 DNS
# kubectl exec -it dns-test -- sh
# 测试 Service 解析
nslookup backend-service.default.svc.cluster.local
# 测试不同 Namespace
nslookup backend-service.other-namespace.svc.cluster.local
# 测试集群外部
nslookup kubernetes.default.svc.cluster.local
多端口 Service 的 SRV 记录
# multi-port-service.yaml
apiVersion: v1
kind: Service
metadata:
name: multi-port-svc
spec:
clusterIP: None # Headless
selector:
app: myapp
ports:
- name: http
port: 80
targetPort: 8080
protocol: TCP
- name: grpc
port: 9090
targetPort: 9090
protocol: TCP
# SRV 记录:
# _http._tcp.multi-port-svc.default.svc.cluster.local
# → multi-port-svc.default.svc.cluster.local:80
# → multi-port-svc.default.svc.cluster.local:9090
网络策略
默认网络模型
┌─────────────────────────────────────────────────────────────────┐
│ Kubernetes 默认网络 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 默认情况下,集群内所有 Pod 可以互相通信 │
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Pod │ │ Pod │ │ Pod │ │
│ │ A │◀────▶│ B │◀────▶│ C │ │
│ │ │ │ │ │ │ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ │ │ │ │
│ └─────────────────┼─────────────────┘ │
│ │ │
│ 所有 Pod 可互连 │
│ │
│ 风险: │
│ - 不安全的微服务可能被攻击 │
│ - 需要网络隔离控制 │
│ │
└─────────────────────────────────────────────────────────────────┘
NetworkPolicy 示例
# 网络策略:只允许 frontend 访问 backend
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: backend-network-policy
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Ingress
- Egress
ingress:
# 允许来自 frontend 的流量
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8080
egress:
# 允许访问数据库
- to:
- podSelector:
matchLabels:
app: database
ports:
- protocol: TCP
port: 5432
# 允许 DNS
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata/name: kube-system
ports:
- protocol: UDP
port: 53
典型网络隔离场景
# 场景1:Namespace 隔离
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: namespace-isolation
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
ingress:
# 只允许同一 Namespace 的流量
- from:
- namespaceSelector: {}
egress:
# 只允许 DNS 出站
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata/name: kube-system
ports:
- protocol: UDP
port: 53
---
# 场景2:允许外部访问
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-external
spec:
podSelector:
matchLabels:
app: web
ingress:
# 允许 HTTP/HTTPS
- ports:
- protocol: TCP
port: 80
- protocol: TCP
port: 443
# 允许来自任何地方
- from: []
网络策略与 Ingress Controller
# 注意:网络策略需要 Ingress Controller 支持
# 某些 CNI 实现(如 Calico)支持更复杂的策略
# 示例:只允许通过 Ingress 访问
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: web-allow-ingress
spec:
podSelector:
matchLabels:
app: web
ingress:
# 允许来自 Ingress Controller 的流量
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: ingress-nginx
ports:
- protocol: TCP
port: 8080
kube-proxy 与负载均衡
kube-proxy 模式
┌─────────────────────────────────────────────────────────────────┐
│ kube-proxy 工作模式 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ │
│ │ iptables │ │ IPVS │ │ userspace │ │
│ ├────────────────┤ ├────────────────┤ ├────────────────┤ │
│ │ 默认模式 │ │ 高性能模式 │ │ 已废弃 │ │
│ │ │ │ │ │ │ │
│ │ 规则匹配 │ │ 哈希表查找 │ │ 代理转发 │ │
│ │ 节点多时效率低 │ │ 支持更多算法 │ │ 性能差 │ │
│ └────────────────┘ └────────────────┘ └────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
iptables 模式
# 查看 kube-proxy 规则
iptables -L -t nat | grep KUBE
# 示例规则
# KUBE-SEP-xxx → 目标 Pod
# KUBE-SVC-xxx → 负载均衡到多个 KUBE-SEP
IPVS 模式配置
# ipvs-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: kube-proxy
namespace: kube-system
data:
config.conf: |
mode: "ipvs"
ipvs:
scheduler: "rr" # 轮询
# 其他算法:lc, sh, dh, sed, nq
常见问题与避坑指南
Q1:Service 无法访问?
# 排查步骤
# 1. 检查 Endpoint 是否存在
kubectl get endpoints <service-name>
# 2. 检查 Pod 是否匹配 Selector
kubectl get pods -l app=backend
kubectl describe service <service-name>
# 3. 检查 Pod 是否运行
kubectl get pods -o wide
# 4. 测试连通性
kubectl exec -it test-pod -- curl -v <service-name>
Q2:Ingress 不生效?
# 检查清单
# 1. Ingress Controller 是否部署
kubectl get pods -n ingress-nginx
# 2. IngressClass 是否配置
kubectl get ingressclass
# 3. 域名解析是否正确
# 添加 /etc/hosts 或配置 DNS
# 4. TLS 证书是否正确
kubectl describe secret <secret-name>
Q3:ExternalTrafficPolicy 如何选择?
# 场景1:保留客户端 IP(Local)
spec:
externalTrafficPolicy: Local
# 优点:保留源 IP
# 缺点:流量可能不均衡(取决于 Pod 分布)
# 场景2:集群级别(Cluster,默认)
spec:
externalTrafficPolicy: Cluster
# 优点:负载均衡
# 缺点:源 IP 丢失(SNAT)
Q4:如何实现金丝雀流量分配?
# 使用 Ingress 实现金丝雀
# 1. 创建金丝雀 Service
apiVersion: v1
kind: Service
metadata:
name: myapp-canary
spec:
selector:
app: myapp
version: canary
ports:
- port: 80
---
# 2. Ingress 配置流量权重
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myapp-ingress
annotations:
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-weight: "10"
spec:
rules:
- host: myapp.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: myapp-canary
port:
number: 80
总结
┌─────────────────────────────────────────────────────────────────┐
│ 核心要点回顾 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Service 类型 │
│ ├── ClusterIP:集群内部访问 │
│ ├── NodePort:通过节点端口访问 │
│ ├── LoadBalancer:云厂商 LB │
│ ├── ExternalName:CNAME 映射 │
│ └── Headless:无虚拟 IP,直连 Pod │
│ │
│ Ingress │
│ ├── HTTP/HTTPS 入口路由 │
│ ├── 需要 Ingress Controller │
│ └── 支持 TLS、重写、灰度 │
│ │
│ DNS 服务发现 │
│ ├── FQDN:service.ns.svc.cluster.local │
│ ├── 简化访问:同 NS 直接用服务名 │
│ └── SRV 记录:多端口服务 │
│ │
│ 网络策略 │
│ ├── 命名空间隔离 │
│ ├── Pod 间访问控制 │
│ └── 出口流量管理 │
│ │
│ kube-proxy │
│ ├── iptables:默认,简单 │
│ └── IPVS:高性能 │
│ │
└─────────────────────────────────────────────────────────────────┘
思考题
- 为什么 Headless Service 的 DNS 返回 Pod IP 而不是 Service IP?这有什么应用场景?
- externalTrafficPolicy=Local 会导致流量不均衡,如何解决?
- NetworkPolicy 在不同 CNI 实现中有何差异?测试过吗?
引用与参考
下篇预告
下一篇文章我们将探讨 ConfigMap 与 Secret,包括:
- 配置管理最佳实践
- Secret 安全存储
- 动态配置更新
- 配置验证与回滚
敬请期待!