K8s 网络通关手册:Ingress-Nginx 生产级路由 + Calico 三种隧道模式 + NetworkPolicy 白名单实战
摘要 :容器网络是 Kubernetes 最容易"配了却不生效"的一块。本文把三件事讲透:第一 ,Ingress 作为七层入口,从控制器工作流程到 pathType、主机名匹配的判定规则,再用 7 组可直接复制的生产规则(多域名、多路径、路径裁剪、TLS 强制跳转、限流、跨域、白名单、灰度)落地;第二 ,容器网络从 Docker 单机 veth pair 讲到跨主机 Underlay/Overlay,横向对比 Flannel、Calico、Weave、Macvlan,并拆解 Calico 的 IPIP 与 VXLAN 封装差异、Always/Never/CrossSubnet 三种模式的选型与切换;第三,NetworkPolicy 的隔离模型与四类选择器,配合 10 组实验验证"谁能访问谁"。全文按"先原理、后实操"组织,所有命令与 YAML 验证过可直接执行,末尾附高频踩坑清单。
实验环境 :Kubernetes v1.30(兼容 1.26--1.30)、ingress-nginx v1.11.2、Calico(BGP/IPIP 可切换)、MetalLB 提供 LoadBalancer。示例域名
laoma.cloud、集群节点网段10.1.8.0/24、Pod 网段10.224.0.0/16,请按自己的环境替换。
一、先搞懂 Ingress:它到底解决什么问题
1.1 Ingress 的定位
Ingress 可为 Service 提供外部可访问的 URL、对流量做负载均衡、终止 SSL/TLS,以及基于名称的虚拟托管。真正干活的是 Ingress 控制器(Ingress Controller),它通常借用负载均衡器、边缘路由器或其他前端来承载流量。
一句话理解层级关系:
- Service:四层抽象,解决"一组 Pod 怎么被稳定访问";
- Ingress:七层抽象,解决"不同域名/路径怎么分给不同 Service";
- Ingress Controller:真正跑着的反向代理(本文用 Nginx)。
Ingress 本身不会随意公开端口或协议。要把 HTTP/HTTPS 以外的服务开放到 Internet,仍然应该用 NodePort 或 LoadBalancer 类型的 Service。
1.2 为什么必须单独部署控制器
Ingress 控制器不像 kube-controller-manager 里的其他控制器那样随集群自动启动,它是独立部署的工作负载。Kubernetes 官方目前支持和维护 AWS、GCE、Nginx 三种实现,本次实验使用 ingress-nginx。
1.3 ingress-nginx 的五步工作流程
这是理解后面所有"改了 YAML 却没生效"问题的根子:
- 先部署 Ingress Controller 实体(相当于前端的 Nginx);
- 再创建 Ingress 资源(相当于一份 Nginx 配置声明);
- Controller 与 kube-apiserver 交互,动态获取集群中的全部 Ingress 规则,解析后生成 proxy 配置;
- 把生成的配置写进 ingress proxy Pod 里,如果 Pod 跑的是 Nginx,就落到
/etc/nginx/nginx.conf; - reload 服务生效。
记住一句话:Ingress 本质是七层 HTTP/HTTPS 代理配置,不是流量本身。
二、Ingress 的三条匹配铁律(先看规则,再写 YAML)
2.1 pathType:三种路径类型,不写就过不了校验
Ingress 中每个路径都必须有对应的路径类型,未明确设置 pathType 的路径无法通过合法性检查。
| 类型 | 匹配方式 | 说明 |
|---|---|---|
| Prefix | 基于 / 分隔的 URL 前缀匹配 |
区分大小写,按路径元素逐个比对;/aaa/bb 不匹配 /aaa/bbb |
| Exact | 精确匹配 URL 路径 | 区分大小写;/foo 不匹配 /foo/ |
| ImplementationSpecific | 匹配方法取决于 IngressClass | 具体实现可当作 Prefix 或 Exact 处理 |
高频判定示例,考试和实战都爱问:
| 类型 | 路径 | 请求路径 | 是否匹配 |
|---|---|---|---|
| Prefix | / |
所有路径 | 是 |
| Exact | /foo |
/foo |
是 |
| Exact | /foo |
/foo/ |
否 |
| Prefix | /aaa/bb |
/aaa/bbb |
否(按元素切分,不是字符串前缀) |
| Prefix | /aaa/bbb |
/aaa/bbbxyz |
否(字符串前缀不匹配) |
| Prefix | /aaa/bbb |
/aaa/bbb/ccc |
是(匹配子路径) |
| Prefix | /、/aaa |
/aaa/ccc |
是(匹配更长的 /aaa) |
| Prefix | /aaa |
/ccc |
否(走默认后端) |
| 混合 | /foo(Prefix) + /foo(Exact) |
/foo |
是(优选 Exact) |
2.2 多重匹配:最长优先,同长比类型
多条路径命中同一请求时:匹配路径最长者优先 ;若长度相同,Exact 优先于 Prefix。
2.3 主机名匹配:精确 vs 通配
主机名可以精确匹配(foo.bar.com),也可以用通配符(*.foo.com)。通配符只覆盖一个 DNS 标签。
| 主机 | Host 头部 | 是否匹配 |
|---|---|---|
*.foo.com |
bar.foo.com |
是(同后缀) |
*.foo.com |
baz.bar.foo.com |
否(跨了两个标签) |
*.foo.com |
foo.com |
否(标签数不够) |
2.4 IngressClass:控制器多了怎么区分
Ingress 可以由不同控制器实现,每个 Ingress 应当通过 ingressClassName 引用一个 IngressClass。把某个 IngressClass 标记为集群默认后,未写 ingressClassName 的 Ingress 会自动归到它名下。
bash
# 标记为集群默认 IngressClass
root@master30:~# kubectl patch ingressclass nginx -p '{"metadata":{"annotations":{"ingressclass.kubernetes.io/is-default-class":"true"}}}'
对应的 YAML 形态:
bash
root@master30:~# vim ingressclass.yaml
apiVersion: networking.k8s.io/v1 # IngressClass API版本
kind: IngressClass # 资源类型:IngressClass
metadata:
name: nginx # IngressClass名称,对应ingress-nginx控制器
annotations:
ingressclass.kubernetes.io/is-default-class: "true" # 标记为集群默认IngressClass,Ingress不写ingressClassName时自动选用
spec:
controller: k8s.io/ingress-nginx # 绑定的ingress-nginx控制器标识
踩坑提醒 :spec.ingressClassName 一定别漏写,否则规则可能落到别的控制器上,表现为"YAML 对但就是不生效"。
2.5 没有 rules 的 Ingress:defaultBackend 兜底
一个 Ingress 可以不写任何 rules,只指定一个默认后端。此时所有发往该入口 IP 的流量都会转给 defaultBackend 列出的 Service------相当于 Nginx 里那个"兜底 server"。
bash
root@master30:~# vim default-backend-ingress.yaml
apiVersion: networking.k8s.io/v1 # Ingress 资源API版本
kind: Ingress # 资源类型:Ingress七层流量规则
metadata:
name: test-ingress # Ingress资源名称
spec:
ingressClassName: nginx # 引用IngressClass,名字对应前面IngressClass的metadata.name(nginx)
defaultBackend: # 默认后端:所有不匹配任何路径/域名的流量,全部转发到这里
service:
name: webapp01 # 目标Service名称
port:
number: 80 # Service端口
三个使用要点:
- 只写
defaultBackend的 Ingress 不区分域名和路径,任何 Host 头都能命中,适合做统一的 404/维护页兜底; - 与带
rules的 Ingress 并存时,只有rules全部匹配失败才落到defaultBackend; defaultBackend与rules写在同一个 Ingress 里同样合法,前者负责兜底、后者负责分流。
三、部署 ingress-nginx(前置条件 + 四段式资源)
3.1 部署前提
- 需要外部入口地址,本次环境用 MetalLB 提供
LoadBalancer类型的 Service; - 必须已经部署一个 Ingress 控制器实现,即 ingress-nginx。
3.2 版本对应关系
Ingress-NGINX v1.10--v1.11 系列适配 Kubernetes 1.26--1.30,底层 Nginx 版本 1.25.x,Alpine 3.19--3.20。选版本先对齐集群版本,别只看"最新"。
3.3 部署与镜像替换
bash
# 下载离线包并解压
root@master30:~# wget https://laoma.cloud/class/course-materials/softwares/stage03/ingress-nginx-controller-v1.11.2.tar.gz
root@master30:~# tar -xf ingress-nginx-controller-v1.11.2.tar.gz
# 查看资源用到的镜像
root@master30:~# grep image: ingress-nginx-controller-v1.11.2/deploy/static/provider/cloud/deploy.yaml|uniq
image: registry.k8s.io/ingress-nginx/controller:v1.11.2@sha256:d5f8217feeac4887cb1ed21f27c2674e58be06bd8f5184cacea2a69abaf78dce
image: registry.k8s.io/ingress-nginx/kube-webhook-certgen:v1.4.3@sha256:a320a50cc91bd15fd2d6fa6de58bd98c1bd64b9a6f926ce23a600d87043455a3
# 去掉 sha256 摘要并替换镜像仓库,便于内网离线拉取
root@master30:~# sed -ir 's#@sha256.*##;s/registry.k8s.io/hub.laoma.cloud/' \
ingress-nginx-controller-v1.11.2/deploy/static/provider/cloud/deploy.yaml
# 部署
root@master30:~# kubectl apply -f ingress-nginx-controller-v1.11.2/deploy/static/provider/cloud/deploy.yaml
3.4 deploy.yaml 到底创建了哪四类东西
- 独立命名空间
ingress-nginx,与其他业务隔离; - ConfigMap:把控制器通用配置统一成对象管理。创建 Pod 时绑定 ConfigMap,Pod 内应用可直接引用,常用于设置环境变量、命令行参数、生成配置文件;
- RBAC 授权:创建 ServiceAccount、ClusterRole、Role、RoleBinding、ClusterRoleBinding,让控制器有权监听集群内 Ingress 变化;
- ingress-controller 工作负载:把新增 Ingress 转成代理配置的执行者。
3.5 验收部署结果
bash
root@master30:~# kubectl get all -n ingress-nginx
重点关注三行信息:
ingress-nginx-controllerPod 为 Running;ingress-nginx-controllerService 类型是 LoadBalancer ,并拿到了 EXTERNAL-IP(本例10.1.8.40),后面所有域名都解析到这个地址;- 两个
admission-create/admission-patchJob 状态为 Completed,它们是做 Webhook 证书初始化的,跑完即退,正常现象。
四、7 组生产级 Ingress 规则实战
4.0 环境准备:两个站点 + 两个 Service
bash
# 命名空间与上下文
root@master30:~# kubectl create ns ingress
root@master30:~# kubectl config set-context --current --namespace ingress
# webapp01:两个副本
root@master30:~# kubectl create deployment webapp01 --image=hub.laoma.cloud/library/httpd --replicas=2
root@master30:~# kubectl get pod -L app
# 写入各自的主页内容,便于区分是哪个 Pod 应答
root@master30:~# kubectl exec -it webapp01-57b8567f7c-kq56c -- bash -c "echo hello webapp01 pod1 > htdocs/index.html"
root@master30:~# kubectl exec -it webapp01-57b8567f7c-vzqj7 -- bash -c "echo hello webapp01 pod2 > htdocs/index.html"
root@master30:~# kubectl expose deployment webapp01 --port=80 --target-port=80
# webapp02:额外造一个 games 子目录,用来验证路径分流
root@master30:~# kubectl create deployment webapp02 --image=hub.laoma.cloud/library/httpd --replicas=2
root@master30:~# kubectl get pod | grep webapp02
root@master30:~# kubectl exec -it webapp02-598c447b5b-4j76n -- bash -c "
echo hello webapp02 pod1 > htdocs/index.html
mkdir htdocs/games
echo hello webapp02 game1 > htdocs/games/index.html"
root@master30:~# kubectl exec -it webapp02-598c447b5b-dddwh -- bash -c "
echo hello webapp02 pod2 > htdocs/index.html
mkdir htdocs/games
echo hello webapp02 game2 > htdocs/games/index.html"
root@master30:~# kubectl expose deployment webapp02 --port=80 --target-port=80
4.1 规则一:多域名虚拟主机(一个域名一个后端)
场景:多个业务域名,各自转发到自己的 Service。
bash
root@master30:~# vim multi-host-ingress.yaml
apiVersion: networking.k8s.io/v1 # Ingress API版本
kind: Ingress # 资源类型:Ingress
metadata:
name: multi-host-ingress # Ingress名称:多域名主机Ingress
spec:
ingressClassName: nginx # 指定使用名称为nginx的IngressClass,交给ingress-nginx控制器处理
rules: # 流量规则列表
- host: webapp01.laoma.cloud # 域名1:访问这个域名
http:
paths:
- path: / # 匹配根路径 /
pathType: Prefix # 前缀匹配:以 / 开头全部命中
backend:
service:
name: webapp01 # 转发到service webapp01
port:
number: 80 # service端口80
- host: webapp02.laoma.cloud # 域名2:访问这个域名
http:
paths:
- path: / # 匹配根路径 /
pathType: Prefix
backend:
service:
name: webapp02 # 转发到service webapp02
port:
number: 80
root@master30:~# kubectl apply -f multi-host-ingress.yaml
root@master30:~# kubectl describe ingress multi-host-ingress
验证:客户端把两个域名都指向 EXTERNAL-IP,然后直接 curl 域名(80 端口默认,无需带端口号)。
bash
echo '10.1.8.40 webapp01.laoma.cloud' >> /etc/hosts
echo '10.1.8.40 webapp02.laoma.cloud' >> /etc/hosts
curl webapp01.laoma.cloud
curl webapp01.laoma.cloud
curl webapp02.laoma.cloud
curl webapp02.laoma.cloud
预期输出在 hello webapp01 pod1 / hello webapp01 pod2 之间交替,说明 Ingress 后面还做了一层轮询负载均衡。
bash
root@master30:~# kubectl delete ingress multi-host-ingress
4.2 规则二:同一域名多路径分流
场景 :一个域名下,不同路径转发给不同微服务(/games 走 webapp02)。
bash
root@master30:~# vim multi-path-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: multi-path-ingress
spec:
ingressClassName: nginx
rules:
- host: www.laoma.cloud
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: webapp01
port:
number: 80
- path: /games
pathType: Prefix
backend:
service:
name: webapp02
port:
number: 80
root@master30:~# kubectl apply -f multi-path-ingress.yaml
root@master30:~# kubectl describe ingress multi-path-ingress
验证 :/ 命中 webapp01,/games/ 命中 webapp02,靠的就是"最长路径优先"。
bash
echo '10.1.8.40 www.laoma.cloud' >> /etc/hosts
curl www.laoma.cloud
curl www.laoma.cloud
curl www.laoma.cloud/games/
curl www.laoma.cloud/games/
bash
root@master30:~# kubectl delete ingress multi-path-ingress
4.3 规则三:路径裁剪(等价 Nginx 的 proxy_pass /)
场景 :前端访问 /api/xxx,后端只认 /xxx,需要把前缀剥掉。这是生产环境最常见的一条重写规则。
bash
root@master30:~# vim path-trimming-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: path-trimming-ingress
annotations:
# 开启正则匹配,(.*) 捕获分组才会生效
nginx.ingress.kubernetes.io/use-regex: "true"
# 路径重写:$1捕获(.*)里的内容,把捕获部分拼到/后面
nginx.ingress.kubernetes.io/rewrite-target: /$1
spec:
ingressClassName: nginx
rules:
- host: www.laoma.cloud
http:
paths:
- path: /webapp01/(.*)
# 正则路径必须用 ImplementationSpecific
pathType: ImplementationSpecific
backend:
service:
name: webapp01
port:
number: 80
- path: /webapp02/(.*)
pathType: ImplementationSpecific
backend:
service:
name: webapp02
port:
number: 80
root@master30:~# kubectl apply -f path-trimming-ingress.yaml
root@master30:~# kubectl describe ingress path-trimming-ingress
验证与效果对照:
- 客户端请求
www.laoma.cloud/webapp01/→ 后端收到/;命中 webapp01 首页。 - 客户端请求
www.laoma.cloud/webapp02/→ 后端收到/,命中 webapp02 首页。
bash
curl www.laoma.cloud/webapp01/
curl www.laoma.cloud/webapp01/
curl www.laoma.cloud/webapp02/
curl www.laoma.cloud/webapp02/
三个必须避开的坑:
- 不写
use-regex: "true",(.*)会被当普通字符串,路径匹配不上; - 路径写成
/webapp01(.*)少了斜杠,会连/webapp01xxx一起匹配; - 后端如果是 Web 应用,自身重定向到
/login这类绝对路径时,浏览器会跳出前缀导致 404,需要后端配置 base path 或补proxy-redirect类注解。
bash
root@master30:~# kubectl delete ingress path-trimming-ingress
4.4 规则四:绑定 TLS 证书 + 强制 HTTPS
第一步:生成自签证书并落成 Secret(生产环境换成正式证书即可,Secret 用法一致)。
bash
root@master30:~# openssl genrsa -out www.key 2048
root@master30:~# openssl req -new -key www.key -out www.csr \
-subj "/C=CN/ST=JS/L=NJ/O=LM/OU=DEVOPS/CN=www.laoma.cloud/emailAddress=webadmin@laoma.cloud"
root@master30:~# openssl x509 -req -days 3650 -in www.csr -signkey www.key -out www.crt
root@master30:~# kubectl create secret tls www-tls --cert=./www.crt --key=./www.key
第二步:Ingress 里挂证书并强制跳转。这是生产标配:访问 80 端口也自动跳到 443。
bash
root@master30:~# vim tls-ingress.yaml
apiVersion: networking.k8s.io/v1 # Ingress API版本
kind: Ingress # 资源类型:Ingress
metadata:
name: tls-ingress # Ingress名称:TLS HTTPS加密 + 强制跳转
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true" # 开启HTTP自动跳HTTPS
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"# 强制HTTPS跳转,即使后端服务本身是http
spec:
ingressClassName: nginx # 使用nginx这个IngressClass,交给ingress-nginx控制器
tls: # TLS证书配置块
- hosts:
- www.laoma.cloud # 证书对应的域名
secretName: www-tls # 存放证书的Secret名称(提前建好)
rules:
- host: www.laoma.cloud # 访问域名
http:
paths:
- path: / # 匹配所有根路径
pathType: Prefix # 前缀匹配
backend:
service:
name: webapp01 # 转发后端服务webapp01
port:
number: 80 # 后端端口80
root@master30:~# kubectl apply -f tls-ingress.yaml
root@master30:~# kubectl describe ingress tls-ingress
describe 输出里要能看到 TLS: www-tls terminates www.laoma.cloud 这一行,才算证书挂对了。
验证 :-L 跟随 301/302/307/308 重定向,-k 跳过自签证书校验。
bash
curl -Lk http://www.laoma.cloud/
curl -Lk http://www.laoma.cloud/
curl -k https://www.laoma.cloud/
curl -k https://www.laoma.cloud/
bash
root@master30:~# kubectl delete ingress tls-ingress
4.5 规则五:限流防刷 + 超时优化
场景:防 CC 攻击、保护后端慢接口。这两个注解作用在 Ingress 上,对同一 Ingress 的所有后端生效。
bash
root@master30:~# vim limit-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: limit-ingress
annotations:
# 单 IP 最大并发连接数
nginx.ingress.kubernetes.io/limit-connections: "50"
# 单 IP 每秒请求数
nginx.ingress.kubernetes.io/limit-rps: "20"
# 连接后端超时(秒)
nginx.ingress.kubernetes.io/proxy-connect-timeout: "10"
# 后端响应超时,长任务接口要放大
nginx.ingress.kubernetes.io/proxy-read-timeout: "60"
# 向后端发送请求超时
nginx.ingress.kubernetes.io/proxy-send-timeout: "60"
spec:
ingressClassName: nginx
rules:
- host: www.laoma.cloud
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: webapp01
port:
number: 80
4.6 规则六:跨域配置(前后端分离必备)
bash
root@master30:~# vim cors-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: cors-ingress
annotations:
nginx.ingress.kubernetes.io/enable-cors: "true" # 开启CORS跨域支持
nginx.ingress.kubernetes.io/cors-allow-origin: "*" # 允许所有来源域名访问(*代表不限)
nginx.ingress.kubernetes.io/cors-allow-methods: "GET,POST,PUT,DELETE,OPTIONS" # 允许的请求方法
spec:
ingressClassName: nginx
rules:
- host: api.laoma.cloud
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: webapp01
port:
number: 80
生产建议把
cors-allow-origin: "*"收窄成明确的前端域名,避免接口被任意站点调用。
4.7 规则七:白名单、真实 IP、缓存、灰度、错误页
白名单(管理后台、内部系统只放行公司内网):
bash
root@master30:~# vim whitelist-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: whitelist-ingress
annotations:
# 只放行 10.1.8.0/24 与本地回环
nginx.ingress.kubernetes.io/whitelist-source-range: "10.1.8.0/24,127.0.0.1/32"
spec:
ingressClassName: nginx
rules:
- host: admin.laoma.cloud
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: webapp01
port:
number: 80
真实客户端 IP 透传(后端日志想拿到真实来源):
bash
root@master30:~# vim realip-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: realip-ingress
annotations:
# 开启传递 X-Forwarded-For 请求头,把客户端真实IP传给后端Pod里的服务
nginx.ingress.kubernetes.io/x-forwarded-for: "true"
# 信任哪些网段传来的 XFF 头
nginx.ingress.kubernetes.io/proxy-real-ip-cidr: "10.0.0.0/8"
spec:
ingressClassName: nginx
rules:
- host: www.laoma.cloud
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: webapp01
port:
number: 80
静态资源缓存:
bash
root@master30:~# vim cache-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: cache-ingress
annotations:
# 开启Ingress-Nginx代理缓存功能(总开关)
nginx.ingress.kubernetes.io/proxy-cache: "true"
# 缓存规则:状态码200、302的响应,缓存有效期10分钟
nginx.ingress.kubernetes.io/proxy-cache-valid: "200 302 10m"
spec:
ingressClassName: nginx
rules:
- host: static.laoma.cloud
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: webapp02
port:
number: 80
金丝雀灰度(90% 走稳定版,10% 走新版本,权重按需调):
bash
root@master30:~# vim canary-ingress.yaml
apiVersion: networking.k8s.io/v1 # Ingress API版本
kind: Ingress # 资源类型Ingress
metadata:
name: canary-ingress # Ingress名称:灰度Ingress(金丝雀)
annotations:
nginx.ingress.kubernetes.io/canary: "true" # 开启灰度标记,告诉ingress-nginx这是灰度规则
nginx.ingress.kubernetes.io/canary-weight: "10" # 分配10%流量到这个Ingress
spec:
ingressClassName: nginx # 使用名称为nginx的IngressClass
rules:
- host: www.laoma.cloud # 域名必须和主Ingress完全相同!
http:
paths:
- path: / # 匹配根路径
pathType: Prefix # 前缀匹配
backend:
service:
name: webapp02 # 灰度流量转发到新版本服务 webapp02
port:
number: 80 # 服务端口
bash
# 配套主 Ingress(必须一起存在才生效!)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: main-ingress
spec:
ingressClassName: nginx
rules:
- host: www.laoma.cloud
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: webapp01 # 老版本服务,承接剩余90%流量
port:
number: 80
自定义后端错误页(同时可用于接管 502/503,避免暴露默认报错页):
bash
root@master30:~# vim errors-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: errors-ingress
annotations:
# 自定义错误页面:当后端返回这些状态码时,启用自定义错误页
nginx.ingress.kubernetes.io/custom-http-errors: "404,500,502,503"
# 错误页服务
nginx.ingress.kubernetes.io/default-service: "error-svc"
spec:
ingressClassName: nginx
rules:
- host: www.laoma.cloud
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: webapp01
port:
number: 80
HTTPS 透传(后端 Service 自身就是 HTTPS,Ingress 不解密、直接转发加密流量):
bash
root@master30:~# vim passthrough-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: passthrough-ingress
annotations:
# 关键:透传 TLS,Ingress 不做解密,证书由后端自己持有
nginx.ingress.kubernetes.io/ssl-passthrough: "true"
# 指定 ingress-nginx 和后端 service 通信时使用 HTTPS 协议
nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
spec:
ingressClassName: nginx
rules:
- host: www.laoma.cloud
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: webapp01
# 后端端口要是 443,且 Secret 不需要在这里挂载
port:
number: 443
ssl-passthrough与普通tls:互斥:透传模式下 Ingress 看不到明文,也无法做基于路径的七层改写,命中条件只能靠 SNI。启用透传还需控制器开启--enable-ssl-passthrough参数。
4.8 生产高频注解速查
注解(nginx.ingress.kubernetes.io/ 前缀) |
作用 |
|---|---|
| force-ssl-redirect: "true" | 80 端口强制跳转 HTTPS |
| rewrite-target: /$1 | 路径重写、裁剪路由前缀 |
| use-regex: "true" | 开启正则路径匹配 |
| whitelist-source-range | IP 白名单,多个网段用逗号分隔 |
| limit-rps / limit-connections | 单 IP 限速、防 CC |
| enable-cors / cors-allow-origin | 前后端跨域 |
| proxy-read-timeout | 解决长接口超时断开 |
| x-forwarded-for / proxy-real-ip-cidr | 后端获取真实客户端 IP |
| proxy-cache / proxy-cache-valid | 静态资源缓存与有效期 |
| canary / canary-weight | 金丝雀权重分流 |
| ssl-passthrough / backend-protocol | HTTPS 透传,Ingress 不解密 |
| custom-http-errors | 自定义错误页接管 |
4.9 收尾清理
bash
root@master30:~# kubectl delete ns ingress
五、容器网络:从单机 veth pair 到跨主机 Overlay
5.1 Docker / Containerd 的单机网络
Docker 的网络接口默认都是虚拟接口,优势是转发效率极高:Linux 在内核中做数据复制完成虚拟接口间的转发,发送接口缓存中的包被直接复制到接收接口缓存,无需经过外部物理网络设备交换。
Docker 服务启动时默认创建一个 docker0 Linux 网桥,并在本地主机与容器内各创建一个虚拟接口,成对连通------这就是 veth pair 。docker0 自带 IP 与掩码,让主机与容器能通过网桥互通。
Containerd 与 Docker 原理一致:创建容器时在主机与容器内各建一个虚拟接口并连通,只是没有 docker0 这套守护进程绑定。
外部主机要访问容器,需要通过端口映射实现。
5.2 跨主机通信:Underlay 与 Overlay 的取舍
跨主机网络意味着把不同主机上的容器用同一个虚拟网络连起来,两种基本模型:
- Underlay :直接使用底层物理网络转发 Pod 流量,无隧道封装,性能优于 Overlay;
- Overlay :在宿主机网络之上构建虚拟隧道(典型是 VXLAN),把数据包封装到 UDP 里传输。开销比 Underlay 大,但 Overlay 支持更多二层网段,能更好利用已有网络,并避免物理交换机 MAC 表耗尽。
5.3 四大 CNI 方案横向对比
| 特性 | Flannel | Calico | Macvlan | Open vSwitch | 直接路由 |
|---|---|---|---|---|---|
| 方案特性 | 通过 flannel0 虚拟设备管理 docker0 | 基于 BGP 的纯三层网络 | 基于内核 macvlan 技术 | 基于隧道的虚拟路由器 | 基于内核 vRouter |
| 对底层网络要求 | 三层互通 | 三层互通 | 二层互通 | 三层互通 | 二层互通 |
| 配置难度 | 简单,基于 etcd | 简单,基于 etcd | 简单,直接用宿主网络,需仔细规划 IP | 复杂,需手工配各节点 bridge | 简单,需规划每个 Node 的 IP |
| 网络性能 | host-gw 优于 VXLAN | BGP 模式最优,IPIP 略低 | 损耗可忽略 | 损耗较小 | 损耗较小 |
| 连通性限制 | 无 | 不支持 BGP 的网络环境无法使用 | macvlan 容器无法与宿主机网络通信 | 无 | 无法大二层互通的环境不可用 |
拆到每种方案的核心机制:
Flannel :host-gw 走 Underlay,节点上写静态路由,目标 Pod 网段下一跳指向目标宿主机 IP,无任何封装、性能高,但要求底层能转发 Pod 网段路由(公有云大多不支持);VXLAN 走 Overlay,跨节点报文封装 UDP VXLAN 头,只要求宿主机 IP 连通,公有云通用,但有封包解包开销。Flannel 只负责连通,不提供 NetworkPolicy。
Calico :BGP 模式用 BGP 在节点间交换 Pod 路由,原生 IP 转发、性能最优,前提是网络允许发布 BGP 路由;底层不支持 BGP 时启用 IPIP/VXLAN 隧道。Calico 最大亮点是原生支持 NetworkPolicy。
Weave:默认 Overlay,节点间建 UDP 隧道封装 Pod 流量并自动发现节点。特色是内置 DNS、支持 NetworkPolicy、可混跑加密隧道;短板是隧道持续占 CPU,大规模性能弱于 Flannel VXLAN 与 Calico。
Macvlan:属于 Underlay。在宿主机物理网卡上虚拟多张独立网卡,每个 Pod 有独立 MAC,直接接入物理局域网,无隧道封装,延迟极低。限制有三:宿主机网卡不能开桥接;同一父网卡下的 macvlan 子接口之间无法直接互通(同宿主机 Pod 互通缺陷);IP 规划复杂易冲突。
场景选型可以记成一句话:
| CNI | 网络模型 | 是否封装 | NetworkPolicy | 适用场景 |
|---|---|---|---|---|
| Flannel | host-gw / VXLAN | VXLAN 封装,host-gw 不封装 | 不支持 | 简单优先、无需 Pod 访问控制的中小集群 |
| Calico | BGP / IPIP / VXLAN | IPIP/VXLAN 封装,BGP 不封装 | 原生支持 | 需要网络策略,自建机房或公有云通用 |
| Weave Net | UDP 隧道 Overlay | UDP 封装 | 支持 | 测试与小规模集群,部署简单无需 etcd |
| Macvlan | Underlay 二层直连 | 不封装 | 取决于配套 CNI | Pod 需占用局域网独立 IP、低延迟业务 |
5.4 CNM 与 CNI:两套抽象模型
容器网络方案多(flannel、calico、kube-ovn、weave),容器平台也多(Kubernetes、OpenShift、rkt),因此需要一个抽象接口层,把"网络配置方案"与"容器平台方案"解耦。
CNM(Container Network Model)由 Docker 提出,在 libnetwork 项目中采用,按该模型开发的 driver 能与 docker daemon 协同工作。它把网络抽象成三类组件:
- Sandbox:容器的网络栈,含 interface、路由表和 DNS 设置,标准实现是 Linux Network Namespace;一个 Sandbox 可包含来自不同 Network 的 Endpoint;
- Endpoint:把 Sandbox 接入 Network,典型实现是 veth pair;一个 Endpoint 只能属于一个网络、一个 Sandbox;
- Network:一组 Endpoint 的集合,同 Network 的 Endpoint 可直接通信,实现可以是 Linux Bridge、VLAN 等。
以 docker bridge driver 为例:默认网络 bridge 与自定义网络 my_net2 分别由 docker0、br-xxxx 两个 Linux Bridge 实现;每个容器有一个由 Network Namespace 实现的 Sandbox;每个容器一个由 veth pair 实现的 Endpoint,一端挂 Bridge,另一端是容器内的 eth0。
CNI (Container Network Interface)由 Google 与 CoreOS 联合制定,是 Kubernetes 采用的规范:定义容器与网络插件之间的通信标准。CNI 有两个概念------容器 (独立的 Linux 网络命名空间)与网络(拥有各自唯一 IP 的互联实体集合)。插件分两类:CNI Plugin 负责配置容器网络,IPAM Plugin 负责分配 IP,IPAM 作为 CNI 插件的一部分协同工作。
| 特点 | CNM | CNI |
|---|---|---|
| 标准规范 | Libnetwork | cni |
| 最小单元 | 容器 | Pod |
| 对守护进程的依赖 | 依赖 dockerd | 不依赖任何守护进程 |
| 跨主机通信 | 依赖外部 KV 数据库 | 使用自身 KV 数据库 |
| 灵活程度 | 被 Docker 绑定 | 插件可随意替换 |
六、Calico 深入:IPIP 与 VXLAN,三种模式怎么选
6.1 两种隧道模式的封装差异
IPIP 是三层隧道:把内层 IP 包完整封装进外层 IP 包,虚拟网卡为 tunl0,封装结构是「外层 IP 头(节点 IP)+ 内层 IP 头(Pod IP)+ 应用数据」,只封装 IP,不携带二层帧。
VXLAN 是二层隧道:走 UDP,默认端口 4789,虚拟网卡为 vxlan.calico,封装结构是「外层 IP + UDP 头 + VXLAN 头(VNI)+ 完整二层帧」,保留完整二层,支持广播、组播。
| 项目 | IPIP | VXLAN |
|---|---|---|
| 隧道类型 | 三层 IP 隧道 | 二层 UDP 隧道 |
| 传输载体 | IP 协议号 4 | UDP 4789 |
| 是否带二层 MAC | 不带 | 完整二层帧 |
| 包头开销 | 较小 | 更大,MTU 需更小 |
| 云厂商限制 | 很多公有云封禁协议号 4,IPIP 直接不通 | UDP 端口一般放行,兼容性更好 |
| 广播/组播 | 不支持 | 支持 |
| Calico 配置 | ipipMode: Always、vxlanMode: Never | ipipMode: Never、vxlanMode: Always |
| 故障排查 | 抓包简单,只有内外两层 IP | 包结构更复杂 |
| 适用场景 | 私有机房、底层放行 IPIP | 公有云、底层不放行 IPIP |
选型判断:
- 选 IPIP:私有物理机房且不拦截协议号 4;只需单播 Pod 通信;追求更小包头开销。缺点是公有云(阿里云/AWS 等)默认拦 IPIP,直接不通。
- 选 VXLAN:公有云环境;需要二层广播能力;底层不支持透传协议号 4。缺点是头部更大、MTU 要调小、性能略低于 IPIP。
6.2 三种工作模式:Always / Never / CrossSubnet
ipipMode 与 vxlanMode 取值完全对称:
- Always(IPIP 默认) :所有跨节点 Pod 流量封装隧道包,路由下一跳指向
tunl0;节点只需三层可达,底层无需感知 Pod 网段; - Never(BGP 原生 / Underlay):不封装,依靠 BGP 在节点间同步 Pod 网段路由,路由下一跳是节点物理网卡;要求节点间二层互通;
- CrossSubnet(混合):同子网节点之间不封装(走 BGP 原生),跨子网节点之间自动启用隧道封装。
必须记住的一点:无论是否开启隧道,Calico 都会运行 BGP 进程同步 Pod 网段;隧道开关只决定数据包是否封装。
模式选择:
- Never:无封装、性能优,抓包能直接看到 Pod 原生 IP;
- Always:节点分布在不同子网、无法二层互通;公有云部署;不想依赖底层支持 BGP;
- CrossSubnet:企业机房节点跨多个子网,同网段直路由、跨网段自动走隧道------最省事也最常用。
6.3 查看当前工作模式
IPPool 是 Calico 控制隧道模式的核心资源。
bash
# 方法 1:直接看 IPPool 资源
root@master30:~# kubectl get ippool -o yaml | egrep 'ipip|vxlan'
# 方法 2:看宿主机路由表的下一跳设备
root@master30:~# ip route | grep '/26 via'
判读规则(关键):路由条目末尾是 dev tunl0 → IPIP 隧道模式,数据包走 IPIP 封装;是 dev ens32(物理网卡)→ BGP 原生路由模式。
bash
# IPIP 模式下的典型输出
10.224.19.0/26 via 10.1.8.31 dev tunl0 proto bird onlink
10.224.113.128/26 via 10.1.8.32 dev tunl0 proto bird onlink
6.4 修改工作模式(含备份与回滚)
风险提示:改 IPPool 会立刻改变集群跨节点 Pod 转发行为,生产环境必须在业务低峰操作并提前备份。
bash
# 步骤 1:备份
root@master30:~# kubectl get ippool default-ipv4-ippool -o yaml > ippool-backup.yaml
# 步骤 2:编辑,修改 ipipMode / vxlanMode 字段
root@master30:~# kubectl edit ippool default-ipv4-ippool
bash
root@master30:~# vim ippool-never.yaml
apiVersion: crd.projectcalico.org/v1
kind: IPPool
metadata:
name: default-ipv4-ippool
spec:
cidr: 10.224.0.0/16
# 切换为 BGP 原生模式
ipipMode: Never
vxlanMode: Never
natOutgoing: true
nodeSelector: all()
root@master30:~# calicoctl apply -f ippool-never.yaml
配置下发后等 30--60 秒自动生效;长时间不生效可重启 calico-node:
bash
root@master30:~# kubectl rollout restart daemonset calico-node -n kube-system
bash
# 步骤 3:验证路由表已切到物理网卡
root@master30:~# ip route | grep '/26'
bash
# 预期:下一跳设备从 tunl0 变成物理网卡
10.224.19.0/26 via 10.1.8.31 dev ens32 proto bird
10.224.113.128/26 via 10.1.8.32 dev ens32 proto bird
七、NetworkPolicy:把 Pod 变成"默认拒绝 + 白名单"
7.1 默认连通性与隔离模型
默认情况下集群网络是完全放通的:集群外部能访问集群内应用,集群内应用能访问外部主机,各个 namespace 之间没有任何隔离策略。要在 IP 与端口层面做控制,就用 NetworkPolicy。
Pod 有两种隔离方向,且默认都是"非隔离":
- 出口 Egress :默认所有外向连接允许。一旦有 NetworkPolicy 选中该 Pod 且
policyTypes含Egress,该 Pod 的出口即被隔离,唯一允许的是各策略egress列表放行的连接; - 入口 Ingress :默认所有入站连接允许。一旦被选中且含
Ingress,唯一允许的入站是来自该 Pod 所在节点的连接 以及各策略ingress列表放行的连接。
三条必须记住的语义:
- 策略是相加的,不会冲突。适用于某方向的策略取并集,评估顺序不影响结果;
- 连接要通,源出口与目标入口必须同时放行,任一方不允许即失败;
- 模型是纯白名单,只能"添加允许",没有显式拒绝语句。
bash
# 查看策略生效范围(POD-SELECTOR 是绑定对象,最容易设错)
root@master30:~# kubectl get netpol -n web
7.2 规约字段逐个拆解
bash
root@master30:~# vim netpol-full-example.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: test-network-policy
namespace: default
spec:
# 策略作用对象:选中带 role=db 标签的 Pod;空 {} 表示本 ns 全部 Pod
podSelector:
matchLabels:
role: db
policyTypes:
- Ingress # 管别人进来到这些 Pod
- Egress # 管这些 Pod 出去
ingress:
- from:
# 来源一:按 IP 段,except 从大段里挖掉黑名单
- ipBlock:
cidr: 172.17.0.0/16
except:
- 172.17.1.0/24
# 来源二:按命名空间标签
- namespaceSelector:
matchLabels:
project: myproject
# 来源三:按同命名空间 Pod 标签
- podSelector:
matchLabels:
role: frontend
# 且只允许目标端口 TCP 6379
ports:
- protocol: TCP
port: 6379
egress:
- to:
- ipBlock:
cidr: 10.0.0.0/24
ports:
- protocol: TCP
port: 5978
字段含义:
podSelector:策略作用在哪些 Pod 上(不是限制谁能访问,别搞反);policyTypes:未指定时默认始终含Ingress,有 egress 规则时补Egress;ingress:白名单规则列表,每条规则要同时匹配from和ports才放行;egress:白名单规则列表,每条规则要同时匹配to和port。
7.3 四种选择器(含最容易混淆的一条)
podSelector:仅在本 NetworkPolicy 所在命名空间内选 Pod;namespaceSelector:选命名空间,其下所有 Pod 作为来源/目的地;namespaceSelector+podSelector:选特定命名空间中的特定 Pod。注意写在同一个数组元素里是**"与",分写成两个数组元素是"或"**:
bash
root@master30:~# vim selector-and-vs-or.yaml
# 写法 A(与):来自 user=alice 命名空间中 role=client 的 Pod
ingress:
- from:
- namespaceSelector:
matchLabels:
user: alice
podSelector:
matchLabels:
role: client
bash
root@master30:~# vim selector-or-example.yaml
# 写法 B(或):来自任意命名空间的 user=alice Pod,或者本命名空间 role=client 的 Pod
ingress:
- from:
- namespaceSelector:
matchLabels:
user: alice
- podSelector:
matchLabels:
role: client
ipBlock:按 CIDR 选集群外部 IP,支持except挖掉子段。这里的except必须是cidr的子集,写外面的段不会报错但也不生效。
重要提醒:集群的入站出站机制常会重写源/目标 IP(kube-proxy、LoadBalancer 等),且 NetworkPolicy 处理发生在重写之前还是之后不确定。入站时被作用的"源 IP"可能是 LoadBalancer 地址或 Pod 所在节点 IP,而不是客户端真实 IP。
7.4 实验环境:三个 Pod、两个命名空间
bash
root@master30:~# kubectl create ns web
root@master30:~# kubectl create ns laoma
root@master30:~# kubectl config set-context --current --namespace web
# web 命名空间:web1 / web2 / test
root@master30:~# kubectl run web1 --image=hub.laoma.cloud/library/nginx --image-pull-policy=IfNotPresent
root@master30:~# kubectl exec -it web1 -- bash -c 'echo Hello web1 > /usr/share/nginx/html/index.html'
root@master30:~# kubectl run web2 --image=hub.laoma.cloud/library/nginx --image-pull-policy=IfNotPresent
root@master30:~# kubectl exec -it web2 -- bash -c 'echo Hello web2 > /usr/share/nginx/html/index.html'
root@master30:~# kubectl expose pod web1 --port=80 --target-port=80 --type=NodePort
root@master30:~# kubectl expose pod web2 --port=80 --target-port=80 --type=NodePort
root@master30:~# kubectl run test --image=hub.laoma.cloud/library/nginx
# laoma 命名空间:同名测试 Pod
root@master30:~# kubectl run test -n laoma --image=hub.laoma.cloud/library/nginx
打策略前的基线(全部应通):
bash
# 同命名空间互访
root@master30:~# kubectl exec test -- curl -s web1
root@master30:~# kubectl exec test -- curl -s web2
# 跨命名空间访问
root@master30:~# kubectl exec -n laoma test -- curl -s web1.web
root@master30:~# kubectl exec -n laoma test -- curl -s web2.web
# 集群内访问 ClusterIP
curl 10.103.36.143
# 集群外访问 NodePort
curl http://10.1.8.30:30790
7.5 按 Pod 标签限定
示例 1:只允许同命名空间 run=test 的 Pod 访问 run=web1 的 80 端口。
bash
root@master30:~# vim netpol-pod.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: my-network-policy
spec:
podSelector:
matchLabels:
run: web1 # 选中目标Pod:标签run=web1,是被保护的对象
policyTypes:
- Ingress # 只管控入站流量(别人访问web1)
ingress:
- from:
- podSelector:
matchLabels:
run: test # 允许来源:标签run=test的Pod
ports:
- protocol: TCP
port: 80 # 只允许访问TCP 80端口
root@master30:~# kubectl apply -f netpol-pod.yaml
root@master30:~# kubectl get netpol
验证结果与判读:
bash
# test 可以访问 web1
root@master30:~# kubectl exec test -- curl -s web1
# 其他 ns 的 pod 一律超时(exit code 28)
root@master30:~# kubectl exec test -n laoma -- curl -s --connect-timeout 3 web1.web
# 改掉 test 的标签后,同 ns 也访问不了了
root@master30:~# kubectl label pod test run=web2 --overwrite
root@master30:~# kubectl get pods --show-labels
root@master30:~# kubectl exec test -- curl -s --connect-timeout 3 web1
这一步是理解 NetworkPolicy 的关键:标签一改,白名单立即失效,证明策略绑定的是标签而不是 Pod 名字。超时而不是拒绝,是因为策略默认丢弃。
示例 2:允许同命名空间所有 Pod 访问 run=web1 的 80 端口。
bash
root@master30:~# vim netpol-ns-all.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: my-network-policy
spec:
podSelector:
matchLabels:
run: web1
policyTypes:
- Ingress
ingress:
- from:
# 空 podSelector 表示本命名空间全部 Pod
- podSelector: {}
ports:
- protocol: TCP
port: 80
root@master30:~# kubectl apply -f netpol-ns-all.yaml
root@master30:~# kubectl exec test -- curl -s web1
root@master30:~# kubectl exec web2 -- curl -s web1
示例 3:允许 run=test 访问本命名空间所有 Pod 的 80 端口 ------注意 podSelector: {} 挪到了外层,作用对象变成全部 Pod。
bash
root@master30:~# vim netpol-all-target.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: my-network-policy
spec:
podSelector: {}
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
run: test
ports:
- protocol: TCP
port: 80
7.6 按命名空间标签限定
示例 1:只允许带 project=myproject 标签的命名空间访问 web1。
bash
root@master30:~# vim netpol-ns.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: my-network-policy
namespace: web # 策略本身属于 web 命名空间
spec:
podSelector:
matchLabels:
run: web1 # 在 web 命名空间里,选中标签 run=web1 的Pod(被保护对象)
policyTypes:
- Ingress # 管控入站流量
ingress:
- from:
- namespaceSelector:
matchLabels:
project: myproject # 来源条件:命名空间带有标签 project:myproject
ports:
- protocol: TCP
port: 80 # 允许访问TCP 80端口
root@master30:~# kubectl apply -f netpol-ns.yaml
验证逻辑:打策略后 web 与 laoma 两个 ns 都访问不了;给 laoma 打上标签后立即可通。
bash
# 两个 ns 都失败
root@master30:~# kubectl exec test -- curl -s web1
root@master30:~# kubectl exec -n laoma test -- curl -s web1.web
# 给 ns 打标签后恢复
root@master30:~# kubectl label namespaces laoma project=myproject
root@master30:~# kubectl exec -n laoma test -- curl -s web1.web
示例 2:允许所有命名空间访问 web1 的 80 端口。
bash
root@master30:~# vim netpol-ns-any.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: my-network-policy
namespace: web
spec:
podSelector:
matchLabels:
run: web1
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector: {}
ports:
- protocol: TCP
port: 80
root@master30:~# kubectl apply -f netpol-ns-any.yaml
root@master30:~# kubectl exec test -- curl -s web1
root@master30:~# kubectl exec -n laoma test -- curl -s web1.web
7.7 不限端口、端口范围与多条件
不写 ports = 放行全部端口,此时连 ICMP 也能通,但 Service 层仍然只暴露 80。
bash
root@master30:~# vim netpol-all-ports.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: my-network-policy
namespace: web
spec:
podSelector:
matchLabels:
run: web1
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
run: test
# 注意:这里故意不写 ports
root@master30:~# kubectl apply -f netpol-all-ports.yaml
root@master30:~# kubectl get pods -o wide
bash
# Pod IP 可 ping 通
root@master30:~# kubectl run --rm -it ubuntu -l run=test --image ubuntu -- bash
# 容器内执行
apt update && apt install -y iputils-ping
ping -c1 10.224.193.67
# 但 ping Service 不通,因为 Service 只开放 80 端口
ping web1 -c1
exit
端口范围用 endPort(要求 CNI 支持,Calico 支持):
bash
cat <<'EOF' > netpol-port-range.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: my-network-policy
namespace: web
spec:
podSelector:
matchLabels:
run: web1
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
run: test
ports:
- protocol: TCP
port: 32000
endPort: 32768
EOF
多条件:满足任意一个 from 即放行(或关系)。
bash
cat <<'EOF' > netpol-multi-from.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: my-network-policy
namespace: web
spec:
podSelector:
matchLabels:
run: web1
policyTypes:
- Ingress
ingress:
- from:
- ipBlock:
cidr: 10.1.1.0/24
- namespaceSelector:
matchLabels:
project: myproject
- podSelector:
matchLabels:
run: test
ports:
- protocol: TCP
port: 80
EOF
7.8 五套默认策略模板(复制即用)
bash
# 1) 默认允许所有入站
cat <<'EOF' > allow-all-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-all-ingress
spec:
podSelector: {}
policyTypes:
- Ingress
ingress:
- {}
EOF
# 2) 默认拒绝所有入站(不影响出站隔离行为)
cat <<'EOF' > default-deny-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
spec:
podSelector: {}
policyTypes:
- Ingress
EOF
# 3) 默认允许所有出站
cat <<'EOF' > allow-all-egress.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-all-egress
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- {}
EOF
# 4) 默认拒绝所有出站(不影响入站隔离行为)
cat <<'EOF' > default-deny-egress.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-egress
spec:
podSelector: {}
policyTypes:
- Egress
EOF
# 5) 双向全拒(零信任基线)
cat <<'EOF' > default-deny-all.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
EOF
生产建议:先用第 5 套做兜底全拒,再按业务逐条加放行规则,而不是反过来"先全通再补拒绝"------NetworkPolicy 根本没有拒绝语句。
7.9 NetworkPolicy 做不到的事
- 特定于节点的策略;
- 基于名字(DNS 域名)的策略;
- 真正意义上的"全局默认策略"(策略本身是命名空间级的,
podSelector: {}只覆盖本 ns); - 显式拒绝能力------模型默认拒绝,唯一能力是添加允许;
- 禁止本地回环或指向宿主机的流量:Pod 无法阻塞 localhost 访问,也无法禁止来自所在节点的访问请求。
7.10 清理
bash
root@master30:~# kubectl delete ns web laoma
八、高频踩坑清单(实测总结)
| 现象 | 真正原因 | 处理办法 |
|---|---|---|
| NetworkPolicy 写了却完全不拦 | CNI 是 Flannel,原生不支持 NetworkPolicy | 换成 Calico / Cilium,kubectl get pods -n kube-system 先确认 |
| 策略 YAML 正确,但目标 Pod 没被约束 | podSelector 标签与 Pod 实际标签不一致 |
kubectl get pod --show-labels 逐字核对,run:test 与 app:test 不是一回事 |
| 容器内 curl 127.0.0.1 通,外部访问超时 | 服务只监听 127.0.0.1,或策略把源 IP 拦了 | ss -tlnp 确认监听 0.0.0.0:80;再核对策略源段 |
| 访问 NodePort 被策略误拦 | kube-proxy 做 SNAT,Pod 看到的源 IP 变成节点 IP,原客户端 IP 丢失 | 直接 curl PodIP 做 ipBlock 实验;或给 Service 加 externalTrafficPolicy: Local 保留源 IP |
| ipBlock 放行了客户端网段还是不通 | 访问路径经过 ClusterIP/NodePort,源 IP 已被重写 | 同上,实验一律用 PodIP 直连 |
| Ingress 改完不生效 | 漏写 ingressClassName,或控制器未抓到变更 |
kubectl describe ingress 看 Ingress Class 与 Events 里的 Sync |
| 正则路径不匹配 | 忘开 use-regex: "true",或 pathType 写成 Prefix |
正则必须配 ImplementationSpecific |
| 路径裁剪后接口 404 | 后端返回绝对路径重定向,前缀丢了 | 后端配 base path,或补 proxy-redirect 类注解 |
kubectl label ns web project=- 报 invalid label value |
删除标签写法是 key-,不能带等号 |
用 kubectl label ns web project-,或用 patch 置 null |
| 标签删掉又自动回来 | 有策略/控制器或准入 Webhook 在回写 | kubectl patch ns web -p '{"metadata":{"labels":{"project":null}}}' 验证,再查自动化控制器 |
九、总结
三块内容其实是同一条链路上的三个层次:
- Ingress 解决"外部请求进来后,按域名和路径分给谁"------七层入口;
- Calico 解决"Pod 的包跨节点怎么走"------三层/二层数据面,Underlay 拼性能、Overlay 拼兼容;
- NetworkPolicy 解决"谁能访问谁"------在 Calico 数据面上执行的白名单规则。
排障时按这个顺序走:先确认流量从哪进(Ingress / NodePort / PodIP)、再看源 IP 有没有被 SNAT 改写、最后才怀疑策略本身。绝大多数"策略不生效"都不是 YAML 写错,而是观察到的源 IP 和你以为的不一样。