某公司因网络策略错误导致全站宕机 图解Kubernetes Pod Service Ingress通信原理 小白也能懂的避坑手册
先讲个真事儿,上个月某电商公司搞大促,凌晨两点全站崩了。
排查了半天发现,是运维同学手抖,把 Kubernetes 里的 NetworkPolicy 规则写反了——本来是想”允许流量进入”,结果配成了”拒绝所有流量”。结果就是,用户请求到了 Ingress 控制器那里,死活进不去后面的 Pod,整个站直接挂掉。
这个案例告诉我们一个道理:K8s 的网络模型看着简单,其实坑挺多的。
今天咱们就一步步把这个事儿掰开揉碎了讲清楚,保准你看完以后,再也不用怕配错网络策略了。
先搞清楚三个”角色”是谁
在 K8s 的世界里,流量从用户那边过来,要经过三道关卡:
用户浏览器
|
v
Ingress(大门)
|
v
Service(前台接待)
|
v
Pod(干活的后厨)
Ingress 就像是商场的大门,负责把外面的流量引进来。
Service 像是前台接待,负责把来访者引到正确的柜台。
Pod 就是真正干活的柜台,里面跑着你的业务代码。
这三个东西之间的关系,理解透了,后面的配置就一目了然。
Pod:最小工作单位
Pod 是 K8s 里最小的调度单元,你可以把它想象成一个集装箱,里面跑着你的应用容器。
每个 Pod 都有自己的 IP 地址,但是这个 IP 只在 Pod 内部和集群内部有效,外面是访问不到的。
# 一个简单的 Pod 定义
apiVersion: v1
kind: Pod
metadata:
name: my-app
labels:
app: my-app
spec:
containers:
- name: my-app-container
image: my-app:latest
ports:
- containerPort: 8080
注意这个 containerPort: 8080,这是 Pod 内部应用监听的端口。
实际生产里,你很少直接创建 Pod,一般都是通过 Deployment 来管理。Pod 可能随时被销毁重建,IP 也会变。这就是为什么需要 Service 的存在。
Service:流量分发器
既然 Pod 的 IP 会变,那用户怎么找到正确的 Pod 呢?
这时候 Service 就出场了。它像是一个负载均衡器,把流量分发到后面的一组 Pod 上。
# 创建 Service
apiVersion: v1
kind: Service
metadata:
name: my-app-service
spec:
selector:
app: my-app # 这个标签选择器很关键
ports:
- protocol: TCP
port: 80 # Service 监听的端口
targetPort: 8080 # 转发到 Pod 的端口
type: ClusterIP # 集群内部访问
这里有几个关键点:
selector:Service 通过标签选择器找到对应的 Pod。如果你的 Pod 标签和这里对不上,Service 就找不到任何 Pod,流量就会丢。
port 和 targetPort 的区别:port 是 Service 暴露的端口,targetPort 是 Pod 实际监听的端口。这两个可以不一样,有时候会用来做端口映射。
type:ClusterIP 是默认的,只在集群内部访问。如果要对外暴露,可以用 NodePort 或 LoadBalancer。
实际运行的时候,K8s 会在每个节点上启动一个叫做 kube-proxy 的小组件,它负责把 Service 的流量转发到后面的 Pod。你可以理解成每个节点上都挂了一个小型的负载均衡器。
Ingress:智能路由专家
Service 只能做到按端口转发,但如果你有很多服务,每个服务都要配一个 Service,管理起来就很麻烦。
Ingress 就是来解决这个问题的。它可以根据域名和路径做智能路由。
# 创建 Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- host: www.example.com
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: api-service
port:
number: 80
- path: /
pathType: Prefix
backend:
service:
name: web-service
port:
number: 80
这个配置的意思是:访问 www.example.com/api 的请求,转发到 api-service;其他请求转发到 web-service。
注意这里用的注解是 nginx.ingress.kubernetes.io,这说明集群里部署的是 Nginx Ingress Controller。不同的 Ingress Controller 注解方式不一样,别搞混了。
Ingress 本身不是 K8s 的原生资源,它需要配合 Ingress Controller 才能工作。常见的有 Nginx、Traefik、Contour 等。你可以把它理解为一个专门处理 HTTP/HTTPS 流量的反向代理。
完整的流量链路
把上面三个东西串起来,完整的请求路径是这样的:
用户请求: https://www.example.com/api/user
|
v
DNS 解析到 Ingress Controller 的 IP
|
v
Ingress Controller 收到请求,匹配路由规则
| host: www.example.com
| path: /api
v
转发到 my-app-service (ClusterIP)
|
v
kube-proxy 把流量分发到后端某个 Pod
|
v
Pod (10.244.1.5:8080) 处理请求,返回响应
每一步都有可能出问题,下面咱们逐个分析常见的坑。
坑一:标签选择器对不上
这是新手最常踩的坑。
# Service 里这样写
spec:
selector:
app: my-app
# 但是你的 Pod 定义是这样的
metadata:
labels:
app: my-application # 差了 app,就找不到啦!
排查方法:
# 查看 Service 选中的 Pod 数量
kubectl get endpoints my-app-service
# 输出示例
NAME ENDPOINTS AGE
my-app-service 10.244.1.5:8080,10.244.1.6:8080,10.244.1.7:8080 10d
如果 ENDPOINTS 是空的,说明 Service 找不到任何 Pod,赶紧检查标签。
坑二:端口映射搞错了
# Service 配置
spec:
selector:
app: my-app
ports:
- port: 80
targetPort: 3000 # 转发到 Pod 的 3000 端口
但是你的 Pod 里应用监听的是 8080 端口,不是 3000。
# Pod 里
spec:
containers:
- name: my-app
image: my-app:latest
ports:
- containerPort: 8080 # 实际监听的是 8080
这样 Service 会把流量转发到 Pod 的 3000 端口,但 Pod 上没有人监听这个端口,请求就丢了。
排查方法:
# 进入 Pod 查看实际监听的端口
kubectl exec -it my-app-pod -- netstat -tlnp
# 或者用 ss 命令
kubectl exec -it my-app-pod -- ss -tlnp
确保 Service 的 targetPort 和 Pod 里容器实际监听的端口一致。
坑三:NetworkPolicy 写反了
这就是开头那个公司全站宕机的原因。
NetworkPolicy 是用来控制 Pod 之间、Pod 和外部之间通信的规则。默认情况下,K8s 里所有 Pod 都可以互相通信。如果你不想让某些 Pod 被外部访问,就需要配置 NetworkPolicy。
# 错误的 NetworkPolicy:拒绝所有入站流量
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all
spec:
podSelector: {} # 匹配所有 Pod
policyTypes:
- Ingress
# 没有 ingress 规则,意味着拒绝所有入站流量!
# 正确的 NetworkPolicy:只允许特定流量进入
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-api
spec:
podSelector:
matchLabels:
app: api-service
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: web-service # 只允许 web-service 的 Pod 访问
ports:
- protocol: TCP
port: 8080
注意看这两个的区别。第一个策略写了 podSelector: {} 但没有指定 ingress 规则,这会拒绝所有入站流量,包括 Ingress Controller 转发过来的请求。
排查方法:
# 查看当前命名空间的所有 NetworkPolicy
kubectl get networkpolicy
# 查看具体的规则详情
kubectl describe networkpolicy deny-all
如果你不确定自己的 NetworkPolicy 会不会阻断流量,可以先用宽泛的规则测试:
# 测试用:允许所有入站流量
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-all
spec:
podSelector: {}
policyTypes:
- Ingress
ingress:
- {} # 空规则表示允许所有
测试没问题后,再逐步收紧规则。
坑四:Ingress Controller 没部署
有些朋友配好了 Ingress 资源,发现流量就是进不来。去查一下:
# 查看 Ingress Controller 是否在运行
kubectl get pods -n ingress-nginx
# 输出示例
NAME READY STATUS RESTARTS AGE
nginx-ingress-controller-5d8f6c7b4-abc12 1/1 Running 0 5d
如果没部署,需要安装对应的 Ingress Controller。以 Nginx 为例:
# 部署 Nginx Ingress Controller
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/baremetal/deploy.yaml
或者用 Helm 安装:
# 添加仓库
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
# 安装
helm install ingress-nginx ingress-nginx/ingress-nginx \
--namespace ingress-nginx \
--create-namespace
坑五:外部流量进不来
有时候 Ingress、Service、Pod 都配置对了,但外部还是访问不了。这时候要考虑几个问题:
1. 查看 Ingress Controller 的暴露方式
# Ingress Controller 的 Service 定义
apiVersion: v1
kind: Service
metadata:
name: ingress-nginx
namespace: ingress-nginx
spec:
type: LoadBalancer # 或者 NodePort
ports:
- name: http
port: 80
targetPort: 80
- name: https
port: 443
targetPort: 443
如果是 type: LoadBalancer,云商会给你分配一个外部 IP。如果是 type: NodePort,需要通过节点的端口访问。
# 查看外部 IP
kubectl get svc -n ingress-nginx
# 输出示例
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ingress-nginx LoadBalancer 10.96.0.1 203.0.113.5 80:30080/TCP,443:31443/TCP 5d
2. 检查防火墙和安全组
如果你有云服务器,需要在安全组里开放对应的端口。很多坑不是 K8s 的问题,是云服务商的防火墙挡在前面。
3. DNS 解析
确保你的域名解析到了 Ingress Controller 的外部 IP。
# 测试 DNS 解析
nslookup www.example.com
# 测试从集群外能否访问
curl -I http://203.0.113.5
调试工具集
遇到问题别慌,这几个命令能帮你快速定位:
# 查看 Pod 状态
kubectl get pods -o wide
# 查看 Pod 的日志
kubectl logs my-app-pod
kubectl logs -f my-app-pod # 实时跟踪日志
# 查看 Service 对应的 Endpoints
kubectl get endpoints my-app-service
# 进入 Pod 调试
kubectl exec -it my-app-pod -- bash
# 从 Pod 内部测试网络连通性
kubectl run -it --rm debug --image=nicolaka/netshoot -- bash
# 进入后可以用 curl、nslookup、nc 等工具测试
# 查看 Ingress 详情
kubectl describe ingress my-ingress
# 查看 NetworkPolicy
kubectl describe networkpolicy my-app-policy
# 查看 Service 的详细信息
kubectl describe service my-app-service
其中 nicolaka/netshoot 这个镜像特别好用,里面集成了各种网络调试工具,遇到网络问题丢进去一个 Pod 就能用。
完整的配置示例
下面是一个完整的、可以正常工作的配置。你可以直接拿去参考:
# 1. Deployment:创建 3 个 Pod 副本
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 3
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: my-app
image: my-app:1.0.0
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
# 2. Service:把流量分发到 Pod
apiVersion: v1
kind: Service
metadata:
name: my-app-service
spec:
selector:
app: my-app
ports:
- protocol: TCP
port: 80
targetPort: 8080
type: ClusterIP
# 3. Ingress:根据域名和路径路由
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: nginx
rules:
- host: www.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-app-service
port:
number: 80
# 4. NetworkPolicy:只允许 Ingress Controller 访问 Pod
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: my-app-network-policy
spec:
podSelector:
matchLabels:
app: my-app
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: nginx-ingress-controller # 只允许 Ingress Controller 的 Pod
ports:
- protocol: TCP
port: 8080
这几个配置文件配合使用,整个链路就通了。你可以根据实际情况调整标签和端口。
总结几句掏心窝的话
K8s 的网络模型学习曲线确实有点陡,但掌握原理之后就简单了。核心就记住三句话:
Pod 有 IP 但会变,所以用 Service 做稳定入口。
Service 能负载均衡,但不懂域名和路径,所以用 Ingress 做智能路由。
NetworkPolicy 默认不生效,但一旦生效就会严格拦截,配错了就是全站宕机。
下次配网络策略之前,先在测试环境跑一遍,用前面提到的调试工具确认每一步都通了,再推到生产环境。那个公司要是早点做测试,就能避免凌晨两点被叫起来救火的噩梦了。
有什么具体问题,欢迎评论区留言,咱们一起讨论。
