Kubernetes-05 网络详解
Kubernetes 网络详解
系列第五篇。K8s 网络是最复杂但也最重要的部分之一。本篇从 Pod 网络、Service、Ingress 到网络策略,系统讲解 K8s 的网络体系。
目录
1. K8s 网络基础模型
K8s 的网络模型有三个基本要求:
- 所有 Pod 之间可以不经 NAT 直接通信(跨节点也可以)
- 所有 Node 可以与所有 Pod 直接通信(不经 NAT)
- Pod 自己看到的 IP 与其他人看到的 IP 相同(没有 IP 映射)
K8s 网络层次:
┌─────────────────────────────────────────────────────────────┐
│ 集群外部(Internet) │
└───────────────────────────┬─────────────────────────────────┘
│ 80/443
┌───────────────────────────▼─────────────────────────────────┐
│ Ingress / LoadBalancer Service │
│ (7层路由 / 4层负载均衡) │
└───────────────────────────┬─────────────────────────────────┘
│ ClusterIP
┌───────────────────────────▼─────────────────────────────────┐
│ Service │
│ (稳定 VIP + 负载均衡到 Pod) │
└─────────┬─────────────────┬──────────────────┬─────────────┘
│ │ │
┌─────▼────┐ ┌─────▼────┐ ┌─────▼────┐
│ Pod A │ │ Pod B │ │ Pod C │
│10.244.1.2│ │10.244.2.3│ │10.244.3.4│
└──────────┘ └──────────┘ └──────────┘
Node 1 Node 2 Node 3
(192.168.1.1) (192.168.1.2) (192.168.1.3)
2. Pod 网络
2.1 Pod IP
每个 Pod 在创建时都会分配一个集群内唯一的 IP(来自 Pod CIDR,如 10.244.0.0/16)。
kubectl get pods -o wide
# NAME READY STATUS IP NODE
# go-api-7d... 1/1 Running 10.244.1.42 node-1
# go-api-8f... 1/1 Running 10.244.2.15 node-2
Pod IP 的特点:
- Pod 重启后 IP 可能变化
- Pod 迁移到其他节点后 IP 一定变化
- 因此,不要直接使用 Pod IP,应使用 Service
2.2 同 Pod 内容器通信
同一个 Pod 内的容器共享网络命名空间,通过 localhost 直接通信:
// 容器 A(主应用)访问容器 B(sidecar)
resp, err := http.Get("http://localhost:9090/metrics")
2.3 Pod 间通信
// 容器 A(Pod1: 10.244.1.42)访问容器 B(Pod2: 10.244.2.15)
// 可以直接访问,但不推荐(IP 会变)
resp, err := http.Get("http://10.244.2.15:8080/api")
// 推荐使用 Service DNS
resp, err := http.Get("http://service-b:8080/api")
3. Service
Service 是 K8s 中的网络抽象层,提供稳定的访问端点来访问一组 Pod。
3.1 Service 的四种类型
ClusterIP → 只在集群内部访问(默认)
NodePort → 通过节点 IP + 端口访问(开发测试)
LoadBalancer → 通过云厂商负载均衡器访问(生产外部访问)
ExternalName → DNS CNAME,将服务映射到外部 DNS 名
3.2 ClusterIP(默认)
apiVersion: v1
kind: Service
metadata:
name: go-api
namespace: production
spec:
type: ClusterIP # 默认值,可省略
selector:
app: go-api # 选择后端 Pod
ports:
- name: http
port: 80 # Service 端口(ClusterIP:80)
targetPort: 8080 # Pod 端口(容器监听的端口)
protocol: TCP
- name: grpc
port: 9000
targetPort: 9000
# 查看 Service
kubectl get service go-api
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# go-api ClusterIP 10.96.100.150 <none> 80/TCP 5m
# 集群内可以通过 ClusterIP 或 DNS 访问
curl http://10.96.100.150:80
curl http://go-api.production.svc.cluster.local:80
curl http://go-api:80 # 同 Namespace 内可简写
ClusterIP 工作原理:
客户端 Pod → 请求 10.96.100.150:80
↓ kube-proxy 的 iptables/IPVS 规则
随机选择一个后端 Pod(10.244.x.x:8080)
↓
到达目标 Pod
Headless Service(无 ClusterIP):
spec:
clusterIP: None # 设为 None 即为 Headless
Headless Service 不做负载均衡,DNS 查询会直接返回所有 Pod 的 IP 列表。常用于 StatefulSet。
3.3 NodePort
apiVersion: v1
kind: Service
metadata:
name: go-api-nodeport
spec:
type: NodePort
selector:
app: go-api
ports:
- port: 80
targetPort: 8080
nodePort: 30080 # 节点端口范围:30000-32767(不指定则随机分配)
# 通过任意节点 IP + NodePort 访问
curl http://192.168.1.1:30080
curl http://192.168.1.2:30080 # 任意节点都可以
流量路径:
外部请求 → 任意节点 IP:30080
↓ iptables DNAT
Service ClusterIP:80
↓
随机选择一个后端 Pod(可能在任何节点)
缺点:
- 端口范围固定(30000-32767),难以记忆
- 每个 Service 占用一个 NodePort,数量有限
- 只有 4 层(TCP/UDP),无法基于域名/路径路由
适用场景: 开发测试、临时对外暴露服务
3.4 LoadBalancer
apiVersion: v1
kind: Service
metadata:
name: go-api-lb
annotations:
# AWS 特定注解
service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: "true"
spec:
type: LoadBalancer
selector:
app: go-api
ports:
- port: 80
targetPort: 8080
# 限制来源 IP(可选)
loadBalancerSourceRanges:
- "10.0.0.0/8"
- "172.16.0.0/12"
kubectl get service go-api-lb
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
# go-api-lb LoadBalancer 10.96.1.100 52.1.2.3 80:31234/TCP
云厂商的 cloud-controller-manager 会自动创建一个真实的负载均衡器(AWS ELB、阿里云 SLB 等),并将 EXTERNAL-IP 填写进去。
本地测试 LoadBalancer:
# minikube
minikube tunnel # 在另一个终端运行,为 LoadBalancer Service 分配本地 IP
3.5 ExternalName
apiVersion: v1
kind: Service
metadata:
name: external-db
namespace: production
spec:
type: ExternalName
externalName: prod-db.us-east-1.rds.amazonaws.com
集群内访问 external-db 时,DNS 会返回 prod-db.us-east-1.rds.amazonaws.com 的 CNAME。
使用场景: 将集群外部的服务映射为集群内的 Service 名称,方便迁移时切换。
3.6 Endpoints 和 EndpointSlice
Service 通过 Selector 自动维护一个 Endpoints 对象:
kubectl get endpoints go-api
# NAME ENDPOINTS AGE
# go-api 10.244.1.42:8080,10.244.2.15:8080,10.244.3.7:8080 10m
当 Pod 不满足 readinessProbe 时,会自动从 Endpoints 中移除(不再接收流量)。
自定义 Endpoints(无 Selector 的 Service):
# Service(不设置 selector)
apiVersion: v1
kind: Service
metadata:
name: external-mysql
spec:
ports:
- port: 3306
---
# 手动维护 Endpoints
apiVersion: v1
kind: Endpoints
metadata:
name: external-mysql # 必须与 Service 同名
subsets:
- addresses:
- ip: 192.168.100.50 # 外部 MySQL 的 IP
ports:
- port: 3306
4. Ingress
Ingress 是7层(HTTP/HTTPS)的路由规则,将外部请求路由到不同的 Service。
外部请求
↓
Ingress Controller(如 Nginx Ingress Controller)
↓ 根据 Host/Path 路由
各个 Service → 各个 Pod
4.1 为什么需要 Ingress
如果每个服务都用 LoadBalancer,需要多个公网 IP 和多个 LB(费用高)。Ingress 用一个 LB 处理所有 HTTP 流量,根据域名/路径分发。
4.2 安装 Ingress Controller
# Nginx Ingress Controller(最常用)
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.10.0/deploy/static/provider/cloud/deploy.yaml
# 验证
kubectl get pods -n ingress-nginx
# minikube
minikube addons enable ingress
4.3 基本 Ingress 配置
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app-ingress
namespace: production
annotations:
kubernetes.io/ingress.class: "nginx"
# Nginx 特定配置
nginx.ingress.kubernetes.io/proxy-body-size: "10m"
nginx.ingress.kubernetes.io/proxy-read-timeout: "60"
nginx.ingress.kubernetes.io/proxy-connect-timeout: "5"
spec:
# TLS 配置
tls:
- hosts:
- api.myapp.com
- admin.myapp.com
secretName: myapp-tls-secret # 包含 tls.crt 和 tls.key
rules:
# 基于域名路由
- host: api.myapp.com
http:
paths:
- path: /v1/users
pathType: Prefix
backend:
service:
name: user-service
port:
number: 80
- path: /v1/orders
pathType: Prefix
backend:
service:
name: order-service
port:
number: 80
- path: / # 默认路由
pathType: Prefix
backend:
service:
name: go-api
port:
number: 80
- host: admin.myapp.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: admin-service
port:
number: 80
4.4 路径类型
| pathType | 描述 |
|---|---|
Exact |
精确匹配,/foo 不匹配 /foo/ |
Prefix |
前缀匹配,/foo 匹配 /foo、/foo/bar |
ImplementationSpecific |
由 Ingress Controller 决定 |
4.5 TLS 证书配置
# 手动创建 TLS Secret
kubectl create secret tls myapp-tls-secret \
--cert=tls.crt \
--key=tls.key \
-n production
使用 cert-manager 自动续签(Let’s Encrypt):
# 安装 cert-manager 后,只需添加 annotation
metadata:
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
tls:
- hosts:
- api.myapp.com
secretName: myapp-tls-secret # cert-manager 自动创建和续签
4.6 高级 Nginx Ingress 配置
metadata:
annotations:
# 限流
nginx.ingress.kubernetes.io/limit-rps: "100"
nginx.ingress.kubernetes.io/limit-connections: "50"
# 认证(Basic Auth)
nginx.ingress.kubernetes.io/auth-type: basic
nginx.ingress.kubernetes.io/auth-secret: basic-auth
nginx.ingress.kubernetes.io/auth-realm: "Authentication Required"
# CORS
nginx.ingress.kubernetes.io/enable-cors: "true"
nginx.ingress.kubernetes.io/cors-allow-origin: "https://myapp.com"
# 会话保持(基于 Cookie)
nginx.ingress.kubernetes.io/affinity: "cookie"
nginx.ingress.kubernetes.io/session-cookie-name: "INGRESSCOOKIE"
# WebSocket 支持
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
# 重写路径
nginx.ingress.kubernetes.io/rewrite-target: /$2
5. DNS 服务发现
K8s 内置 DNS 服务(CoreDNS),为 Service 和 Pod 提供 DNS 解析。
5.1 Service DNS 规则
<service-name>.<namespace>.svc.cluster.local
# 示例:
go-api.production.svc.cluster.local
mysql.default.svc.cluster.local
同 Namespace 内可简写:
// 完整 DNS
http.Get("http://go-api.production.svc.cluster.local:80")
// 同 Namespace 内简写
http.Get("http://go-api:80")
http.Get("http://go-api.production:80") // 跨 Namespace 简写(带 Namespace 名)
5.2 Pod DNS 规则
<pod-ip>.<namespace>.pod.cluster.local
# 如:10-244-1-42.production.pod.cluster.local(IP 中的点换成横线)
# StatefulSet Pod:
<pod-name>.<service-name>.<namespace>.svc.cluster.local
# 如:mysql-0.mysql.default.svc.cluster.local
5.3 DNS 查询示例
# 在 Pod 内查询 DNS
kubectl exec -it go-api-7d9b4c-xxxxx -- nslookup go-api
kubectl exec -it go-api-7d9b4c-xxxxx -- nslookup go-api.production.svc.cluster.local
# 查看 DNS 配置
kubectl exec -it <pod> -- cat /etc/resolv.conf
# nameserver 10.96.0.10 ← CoreDNS 的 ClusterIP
# search default.svc.cluster.local svc.cluster.local cluster.local
# options ndots:5
5.4 CoreDNS 配置
kubectl get cm coredns -n kube-system -o yaml
可以添加自定义解析、上游 DNS 等配置。
6. NetworkPolicy(网络策略)
默认情况下,K8s 中所有 Pod 可以互相通信。NetworkPolicy 用于限制 Pod 间的网络流量。
注意: NetworkPolicy 需要 CNI 插件支持(Calico、Cilium 等)。Flannel 不支持。
6.1 概念
NetworkPolicy 是白名单机制:
- 一旦为 Pod 定义了 NetworkPolicy,所有不匹配规则的流量都会被拒绝
- 没有 NetworkPolicy 的 Pod 允许所有流量
6.2 拒绝所有入站流量(默认拒绝)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all-ingress
namespace: production
spec:
podSelector: {} # 空选择器:匹配所有 Pod
policyTypes:
- Ingress # 控制入站流量
# 没有 ingress 规则 = 拒绝所有入站
6.3 只允许特定服务访问数据库
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: mysql-policy
namespace: production
spec:
podSelector:
matchLabels:
app: mysql # 限制对象:mysql Pod
policyTypes:
- Ingress # 控制入站(谁可以访问 mysql)
ingress:
# 允许来自 go-api 的访问
- from:
- podSelector:
matchLabels:
app: go-api
ports:
- protocol: TCP
port: 3306
# 允许来自 admin Namespace 的任何 Pod
- from:
- namespaceSelector:
matchLabels:
name: admin
ports:
- protocol: TCP
port: 3306
6.4 限制出站流量
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: go-api-egress
namespace: production
spec:
podSelector:
matchLabels:
app: go-api
policyTypes:
- Egress # 控制出站(go-api 可以访问谁)
egress:
# 允许访问 mysql
- to:
- podSelector:
matchLabels:
app: mysql
ports:
- port: 3306
# 允许访问 Redis
- to:
- podSelector:
matchLabels:
app: redis
ports:
- port: 6379
# 允许 DNS 查询(必须!否则无法解析域名)
- to:
- namespaceSelector: {}
ports:
- protocol: UDP
port: 53
# 允许访问外部 API(如支付网关)
- to:
- ipBlock:
cidr: 0.0.0.0/0
except:
- 10.0.0.0/8 # 排除集群内部网络
ports:
- port: 443
6.5 完整的微服务网络策略示例
# frontend 只能被 Ingress Controller 访问
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: frontend-policy
spec:
podSelector:
matchLabels:
app: frontend
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: ingress-nginx
# backend 只能被 frontend 访问
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: backend-policy
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
# database 只能被 backend 访问
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: database-policy
spec:
podSelector:
matchLabels:
app: database
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: backend
ports:
- port: 5432
7. CNI 插件
CNI(Container Network Interface)是 K8s 的网络插件标准,负责:
- 为 Pod 分配 IP
- 配置 Pod 的网络接口
- 实现跨节点的 Pod 互通
主流 CNI 对比
| CNI | 类型 | NetworkPolicy | 性能 | 适用场景 |
|---|---|---|---|---|
| Flannel | Overlay(VXLAN) | 不支持 | 一般 | 简单环境、学习 |
| Calico | BGP/Overlay | 支持 | 高 | 大多数生产环境 |
| Cilium | eBPF | 支持(增强版) | 最高 | 高性能/安全要求高 |
| Weave | Overlay | 支持 | 一般 | 混合云 |
Calico(最常用):
- 使用 BGP 路由,性能好(不需要封包/解包)
- 支持 NetworkPolicy
- 支持全局网络策略
Cilium(新一代):
- 基于 eBPF,在内核级别处理网络,性能最好
- 支持 L7(HTTP、gRPC)级别的网络策略
- 提供更丰富的可观测性
8. 实战:Go 微服务通信
8.1 场景
两个 Go 服务:user-service 和 order-service,order 需要调用 user 获取用户信息。
8.2 user-service 部署
# user-service.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service
namespace: production
spec:
replicas: 2
selector:
matchLabels:
app: user-service
template:
metadata:
labels:
app: user-service
spec:
containers:
- name: user-service
image: myregistry.io/user-service:v1.0.0
ports:
- containerPort: 8080
name: http
- containerPort: 9090
name: grpc
---
apiVersion: v1
kind: Service
metadata:
name: user-service
namespace: production
spec:
selector:
app: user-service
ports:
- name: http
port: 80
targetPort: 8080
- name: grpc
port: 9090
targetPort: 9090
8.3 Go 代码中的服务发现
package client
import (
"context"
"encoding/json"
"fmt"
"net/http"
"time"
)
// K8s 环境下,直接使用 Service DNS 名
const userServiceURL = "http://user-service.production.svc.cluster.local"
type UserClient struct {
httpClient *http.Client
}
func NewUserClient() *UserClient {
return &UserClient{
httpClient: &http.Client{
Timeout: 5 * time.Second,
Transport: &http.Transport{
MaxIdleConns: 100,
MaxIdleConnsPerHost: 10,
IdleConnTimeout: 90 * time.Second,
},
},
}
}
type User struct {
ID int64 `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
func (c *UserClient) GetUser(ctx context.Context, userID int64) (*User, error) {
url := fmt.Sprintf("%s/users/%d", userServiceURL, userID)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return nil, fmt.Errorf("create request: %w", err)
}
resp, err := c.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("do request: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("user service returned %d", resp.StatusCode)
}
var user User
if err := json.NewDecoder(resp.Body).Decode(&user); err != nil {
return nil, fmt.Errorf("decode response: %w", err)
}
return &user, nil
}
8.4 使用环境变量配置 Service 地址
K8s 会为每个 Service 自动注入环境变量:
# 在 Pod 内查看
env | grep USER_SERVICE
# USER_SERVICE_SERVICE_HOST=10.96.100.150
# USER_SERVICE_SERVICE_PORT=80
# USER_SERVICE_PORT=tcp://10.96.100.150:80
但更推荐直接用 DNS 名,不要依赖这些环境变量(顺序问题、名称复杂)。
8.5 跨 Namespace 访问
// 从 default Namespace 访问 production Namespace 的 user-service
const userServiceURL = "http://user-service.production.svc.cluster.local"
// DNS 格式:<service>.<namespace>.svc.cluster.local
9. 小结
网络组件速查
| 组件 | 层级 | 作用 |
|---|---|---|
| Pod 网络 | L3 | Pod 间直接通信 |
| Service (ClusterIP) | L4 | 集群内负载均衡 |
| Service (NodePort) | L4 | 通过节点端口外部访问 |
| Service (LoadBalancer) | L4 | 云 LB 外部访问 |
| Ingress | L7 | HTTP 路由,域名/路径分发 |
| NetworkPolicy | L3/L4 | 网络访问控制 |
| CoreDNS | DNS | 服务发现 |
| CNI | L2/L3 | 底层网络实现 |
网络故障排查
# 检查 Service 的 Endpoints(为空则说明没有匹配的就绪 Pod)
kubectl get endpoints <service-name>
# 在 Pod 内测试 DNS
kubectl exec -it <pod> -- nslookup <service-name>
# 在 Pod 内测试连通性
kubectl exec -it <pod> -- curl http://<service-name>:<port>
# 测试网络策略
kubectl exec -it <pod-a> -- curl http://<pod-b-ip>:8080
# 查看 kube-proxy 模式
kubectl get cm kube-proxy -n kube-system -o yaml | grep mode
# 临时创建网络测试 Pod
kubectl run netshoot --image=nicolaka/netshoot -it --rm -- bash
xingliuhua