K8s Lab 把当前仓库文档整理成一个可阅读的网页站点

Repository Reading Site

本轮操作记录:NetworkPolicy 与零信任网络实验

上一轮已经讲清了: 这一轮顺着网络主线继续往下,目标变成: 因此,这轮工作的重点是: 1. 先看集群里已有的 NetworkPolicy 现状 2. 再创建隔离实验环境 3. 逐步验证: 4. 最后把实验结果整理成第五课 --- 因为我不希望新实验建立在“未知现状”上。 如果集群里已经有很多策略,你不先看清楚: 已有策略包括: 当前集群已经存在真实的策略案例

Markdown05-操作记录-NetworkPolicy零信任实验.md2026年4月9日 18:11

本轮操作记录:NetworkPolicy 与零信任网络实验

本轮目标

上一轮已经讲清了:

  • Service
  • DNS
  • kube-proxy
  • CNI
  • VXLAN / IPIP / WireGuard

这一轮顺着网络主线继续往下,目标变成:

把“网络怎么通”变成“网络该不该通”。

因此,这轮工作的重点是:

  1. 先看集群里已有的 NetworkPolicy 现状
  2. 再创建隔离实验环境
  3. 逐步验证:
    • 默认全通
    • default-deny-ingress
    • allow-same-namespace
    • namespaceSelector
    • default-deny-egress
    • allow DNS
    • allow business traffic
  4. 最后把实验结果整理成第五课

Step 1: 查看集群里已经有哪些策略

实际命令

KUBECONFIG=~/.kube/config-k8s-lab kubectl get networkpolicy -A -o wide

为什么先看现状

因为我不希望新实验建立在“未知现状”上。

如果集群里已经有很多策略,你不先看清楚:

  • 容易误把旧策略效果当成新实验效果
  • 容易在错误 namespace 做实验

我看到了什么

已有策略包括:

  • argocd 中多条组件自带策略
  • gitea 中 PostgreSQL 相关策略
  • proddefault-deny-ingress
  • prodallow-same-namespace

我得到的结论

当前集群已经存在真实的策略案例,这说明:

  • Calico 的 NetworkPolicy 能力是活的
  • 集群并不是“只有教学文件,没有现实策略”

但为了避免和已有环境混淆,我仍然决定创建专门实验 namespace。


Step 2: 读取已有文档和 API 能力

实际命令

sed -n '1,240p' phase-2/03-ingress-networkpolicy.md
KUBECONFIG=~/.kube/config-k8s-lab kubectl get ns --show-labels
KUBECONFIG=~/.kube/config-k8s-lab kubectl api-resources | rg 'networkpol|globalnetworkpolicy|networkset|globalnetworkset'

为什么看这三类内容

现有文档

为了保证本轮实验和仓库主线一致。

namespace label

因为 namespaceSelector 会直接依赖 namespace label。

API 资源

为了确认你的集群里除了标准 NetworkPolicy 外,是否还有 Calico 扩展策略资源。

我看到的关键信息

API 里同时存在:

  • networking.k8s.io/v1 NetworkPolicy
  • crd.projectcalico.org/v1 NetworkPolicy
  • GlobalNetworkPolicy
  • NetworkSet
  • GlobalNetworkSet

我得到的结论

这说明:

  • 标准策略能讲
  • Calico 扩展能力也存在

所以第五课里我可以很清楚地区分:

  • 标准语义
  • Calico 增强语义

Step 3: 为什么要创建两个 namespace

新增对象

我新建了:

  • np-demo
  • np-external

以及:

  • np-demo/web + web-svc
  • np-demo/same-ns-client
  • np-external/cross-ns-client

对应文件在:

为什么至少要两个 namespace

因为如果只有一个 namespace,你最多只能演示:

  • 同 namespace 放行/拒绝

但你讲不清:

  • 跨 namespace 流量
  • namespaceSelector
  • 零信任里的环境边界

为什么还要同 namespace 客户端

因为我要同时对比:

  • 同 namespace 客户端
  • 跨 namespace 客户端

这样你才能真正理解:

  • 哪条策略只影响同 namespace
  • 哪条策略影响跨 namespace

Step 4: apply 基础实验对象,但先不加策略

实际命令

KUBECONFIG=~/.kube/config-k8s-lab kubectl apply \
  -f manifests/05-networkpolicy/00-namespace-np-demo.yaml \
  -f manifests/05-networkpolicy/01-namespace-np-external.yaml \
  -f manifests/05-networkpolicy/10-web-deployment.yaml \
  -f manifests/05-networkpolicy/11-web-service.yaml \
  -f manifests/05-networkpolicy/20-same-ns-client.yaml \
  -f manifests/05-networkpolicy/21-cross-ns-client.yaml

为什么先只创建工作负载

因为我要先拿到“默认网络行为”的基线。

如果你一上来就把策略一起 apply 了,后面就不知道:

  • 默认到底是通还是不通
  • 是策略生效了,还是本来就不通

这一步体现的工程原则

做实验时,先建立 baseline,再逐步改单个变量。


Step 5: 等待工作负载 Ready

实际命令

kubectl -n np-demo rollout status deployment/web --timeout=120s
kubectl -n np-demo wait --for=condition=Ready pod/same-ns-client --timeout=120s
kubectl -n np-external wait --for=condition=Ready pod/cross-ns-client --timeout=120s

为什么这一步不能跳

因为如果 Pod 还没 ready,你去测连接:

  • 失败了也不知道是策略问题还是 Pod 没起来

这就是典型的变量污染。

我得到的结果

所有基础对象都准备就绪,可以开始做网络访问实验。


Step 6: 先验证默认全通

实际命令

kubectl -n np-demo get pods,svc -o wide
kubectl -n np-demo exec same-ns-client -- sh -c 'wget -qO- -T 3 http://web-svc | head -n 1 && echo same-ns=success'
kubectl -n np-external exec cross-ns-client -- sh -c 'wget -qO- -T 3 http://web-svc.np-demo.svc.cluster.local | head -n 1 && echo cross-ns=success'
kubectl -n np-external exec cross-ns-client -- nslookup web-svc.np-demo.svc.cluster.local

为什么这组命令同时测三件事

同 namespace HTTP

验证最基本路径。

跨 namespace HTTP

验证默认是否真的跨 namespace 全通。

跨 namespace DNS

把“解析”和“连接”分开验证。

结果

我看到了:

  • same-ns=success
  • cross-ns=success
  • nslookup 正常返回 10.107.52.92

我得到的结论

默认情况下,这两个 namespace 之间没有隔离。

这为后面的 default-deny 实验建立了非常清晰的对照组。


Step 7: 应用 default-deny-ingress

实际命令

kubectl apply -f manifests/05-networkpolicy/30-default-deny-ingress.yaml

为什么先从 ingress deny 开始

因为它是最经典、最基础的一刀。

这条策略的意义是:

  • 先把目标 namespace 保护起来

应用后怎么验证

我执行了:

kubectl -n np-demo exec same-ns-client -- sh -c 'wget -qO- -T 3 http://web-svc >/dev/null && echo same-ns=success || echo same-ns=blocked'
kubectl -n np-external exec cross-ns-client -- sh -c 'wget -qO- -T 3 http://web-svc.np-demo.svc.cluster.local >/dev/null && echo cross-ns=success || echo cross-ns=blocked'
kubectl -n np-demo get networkpolicy

结果

我看到了:

  • same-ns=blocked
  • cross-ns=blocked
  • wget 都是超时

我得到的结论

default-deny-ingress 一旦选中 np-demo 的所有 Pod:

  • 同 namespace 也不再天然被允许
  • 跨 namespace 更不会被允许

这和很多人的直觉相反,但正好体现了策略语义:

  • 被选中的方向,默认不再全通

Step 8: 应用 allow-same-namespace

实际命令

kubectl apply -f manifests/05-networkpolicy/31-allow-same-namespace.yaml

然后再次测试:

kubectl -n np-demo exec same-ns-client -- sh -c 'wget -qO- -T 3 http://web-svc >/dev/null && echo same-ns=success || echo same-ns=blocked'
kubectl -n np-external exec cross-ns-client -- sh -c 'wget -qO- -T 3 http://web-svc.np-demo.svc.cluster.local >/dev/null && echo cross-ns=success || echo cross-ns=blocked'
kubectl -n np-demo get networkpolicy -o yaml

为什么还要看完整 YAML

因为我要把“策略是累加的”这件事写进文档,而不是只凭现象说。

看完整 YAML 可以确认:

  • default-deny-ingress 还在
  • allow-same-namespace 也在

结果

我看到了:

  • same-ns=success
  • cross-ns=blocked

我得到的结论

这完美证明了:

  • default-deny 没有被覆盖
  • 新增规则只是往允许集合里加了一项

这就是标准 NetworkPolicy 的“并集允许”模型。


Step 9: 动态给 namespace 打标签,再用 namespaceSelector 放行

实际命令

kubectl label namespace np-external access=allowed --overwrite
kubectl apply -f manifests/05-networkpolicy/32-allow-from-labeled-namespace.yaml

然后验证:

kubectl get ns np-external --show-labels
kubectl -n np-external exec cross-ns-client -- sh -c 'wget -qO- -T 3 http://web-svc.np-demo.svc.cluster.local >/dev/null && echo cross-ns=success || echo cross-ns=blocked'
kubectl -n np-demo get networkpolicy

为什么先 label namespace

因为 namespaceSelector 不是按 namespace 名字直接匹配,而是按:

  • namespace 的 label

这点必须在实验里让用户真正看见。

结果

我看到了:

  • np-external 标签变成 access=allowed
  • cross-ns=success

我得到的结论

这说明:

  • namespace 身份边界可以通过 label 动态表达
  • namespaceSelector 的规则不是死写死记,而是可治理的

这在平台设计里非常重要。


Step 10: 进入 egress 实验,先加 default-deny-egress

实际命令

kubectl apply -f manifests/05-networkpolicy/40-default-deny-egress-client.yaml

然后验证:

kubectl -n np-external exec cross-ns-client -- nslookup kubernetes.default.svc.cluster.local
kubectl -n np-external exec cross-ns-client -- sh -c 'wget -qO- -T 3 http://web-svc.np-demo.svc.cluster.local >/dev/null && echo http=success || echo http=blocked'
kubectl -n np-external get networkpolicy -o wide

为什么这里先测 DNS

因为 egress deny 最容易先打挂的就是 DNS。

这是生产里极高频的坑。

结果

我看到了:

nslookup

失败:

connection timed out; no servers could be reached

wget

失败,但错误不是 timeout,而是:

bad address 'web-svc.np-demo.svc.cluster.local'

我得到的结论

这里有两个非常关键的教学点:

  1. DNS 本身也是网络流量,会被 egress policy 拦住
  2. 应用访问失败不一定是业务端口被挡,也可能是名字根本解析不出来

这一步对建立排障分层特别重要。


Step 11: 只恢复 DNS

实际命令

kubectl apply -f manifests/05-networkpolicy/41-allow-dns-egress-client.yaml

然后测试:

kubectl -n np-external exec cross-ns-client -- nslookup web-svc.np-demo.svc.cluster.local
kubectl -n np-external exec cross-ns-client -- sh -c 'wget -qO- -T 3 http://web-svc.np-demo.svc.cluster.local >/dev/null && echo http=success || echo http=blocked'
kubectl -n np-external get networkpolicy -o yaml

为什么只恢复 DNS,而不一起恢复 HTTP

因为我要让“DNS 和业务流量是两种独立白名单”这个事实变得非常清楚。

结果

我看到了:

  • nslookup 恢复成功
  • wget 仍然 timeout

我得到的结论

DNS 白名单恢复后:

  • 名字能解了
  • 但业务流量仍出不去

这正好说明:

  • DNS 放行 ≠ 应用流量放行

Step 12: 再恢复业务 HTTP 出站

实际命令

kubectl apply -f manifests/05-networkpolicy/42-allow-web-egress-client.yaml

然后验证:

kubectl -n np-external exec cross-ns-client -- sh -c 'wget -qO- -T 3 http://web-svc.np-demo.svc.cluster.local >/dev/null && echo http=success || echo http=blocked'
kubectl -n np-external get networkpolicy
kubectl get networkpolicy -A | rg 'np-demo|np-external'

为什么最后还看全局策略列表

因为我要把这轮实验最终形成的“策略全景”固化下来,方便教学总结:

  • 目标端有哪些 ingress 策略
  • 源端有哪些 egress 策略

结果

我看到:

  • http=success
  • np-demo 中 3 条 ingress 相关策略
  • np-external 中 3 条 egress 相关策略

我得到的结论

到这里,完整零信任实验链条已经闭环:

  • 默认全通
  • ingress default deny
  • ingress allow same namespace
  • ingress allow labeled namespace
  • egress default deny
  • egress allow dns
  • egress allow web

Step 13: 这轮为什么没有直接使用 Calico 的 GlobalNetworkPolicy

你的集群确实支持:

  • GlobalNetworkPolicy

我在 API 资源里也确认到了。

但这一轮我故意没有一上来就用它,原因是:

先把标准语义吃透更重要

如果你连标准 NetworkPolicy 都还没吃透,就上:

  • 显式 deny
  • order
  • global scope

只会把认知复杂度抬太高。

工程上也应该先掌握最小通用能力

标准 networking.k8s.io/v1 是:

  • 更通用
  • 更容易迁移
  • 更容易面试和团队沟通的基础能力

所以这一轮是故意先打牢地基。


Step 14: 将结果写成第五课文档

基于这一轮所有实验和证据,我写入了:

这份文档里,我重点讲清了:

  • 默认全通
  • 被策略选中后才进入隔离状态
  • 顶层 podSelector 的真正含义
  • 标准 NetworkPolicy 的“累加允许”模型
  • Ingress / Egress 的方向语义
  • DNS 为什么必须单独考虑
  • namespaceSelector / podSelector 的组合语义
  • 标准策略和 Calico 扩展策略的区别

本轮命令清单

KUBECONFIG=~/.kube/config-k8s-lab kubectl get networkpolicy -A -o wide
sed -n '1,240p' phase-2/03-ingress-networkpolicy.md
KUBECONFIG=~/.kube/config-k8s-lab kubectl get ns --show-labels
KUBECONFIG=~/.kube/config-k8s-lab kubectl api-resources | rg 'networkpol|globalnetworkpolicy|networkset|globalnetworkset'

KUBECONFIG=~/.kube/config-k8s-lab kubectl apply \
  -f manifests/05-networkpolicy/00-namespace-np-demo.yaml \
  -f manifests/05-networkpolicy/01-namespace-np-external.yaml \
  -f manifests/05-networkpolicy/10-web-deployment.yaml \
  -f manifests/05-networkpolicy/11-web-service.yaml \
  -f manifests/05-networkpolicy/20-same-ns-client.yaml \
  -f manifests/05-networkpolicy/21-cross-ns-client.yaml

KUBECONFIG=~/.kube/config-k8s-lab kubectl -n np-demo rollout status deployment/web --timeout=120s
KUBECONFIG=~/.kube/config-k8s-lab kubectl -n np-demo wait --for=condition=Ready pod/same-ns-client --timeout=120s
KUBECONFIG=~/.kube/config-k8s-lab kubectl -n np-external wait --for=condition=Ready pod/cross-ns-client --timeout=120s

KUBECONFIG=~/.kube/config-k8s-lab kubectl -n np-demo get pods,svc -o wide
KUBECONFIG=~/.kube/config-k8s-lab kubectl -n np-demo exec same-ns-client -- sh -c 'wget -qO- -T 3 http://web-svc ...'
KUBECONFIG=~/.kube/config-k8s-lab kubectl -n np-external exec cross-ns-client -- sh -c 'wget -qO- -T 3 http://web-svc.np-demo.svc.cluster.local ...'
KUBECONFIG=~/.kube/config-k8s-lab kubectl -n np-external exec cross-ns-client -- nslookup web-svc.np-demo.svc.cluster.local

KUBECONFIG=~/.kube/config-k8s-lab kubectl apply -f manifests/05-networkpolicy/30-default-deny-ingress.yaml
KUBECONFIG=~/.kube/config-k8s-lab kubectl apply -f manifests/05-networkpolicy/31-allow-same-namespace.yaml
KUBECONFIG=~/.kube/config-k8s-lab kubectl label namespace np-external access=allowed --overwrite
KUBECONFIG=~/.kube/config-k8s-lab kubectl apply -f manifests/05-networkpolicy/32-allow-from-labeled-namespace.yaml

KUBECONFIG=~/.kube/config-k8s-lab kubectl apply -f manifests/05-networkpolicy/40-default-deny-egress-client.yaml
KUBECONFIG=~/.kube/config-k8s-lab kubectl apply -f manifests/05-networkpolicy/41-allow-dns-egress-client.yaml
KUBECONFIG=~/.kube/config-k8s-lab kubectl apply -f manifests/05-networkpolicy/42-allow-web-egress-client.yaml

KUBECONFIG=~/.kube/config-k8s-lab kubectl -n np-demo get networkpolicy
KUBECONFIG=~/.kube/config-k8s-lab kubectl -n np-demo get networkpolicy -o yaml
KUBECONFIG=~/.kube/config-k8s-lab kubectl -n np-external get networkpolicy -o wide
KUBECONFIG=~/.kube/config-k8s-lab kubectl -n np-external get networkpolicy -o yaml
KUBECONFIG=~/.kube/config-k8s-lab kubectl get networkpolicy -A | rg 'np-demo|np-external'

本轮最重要的结论

结论一:默认全通是现实,不是理论

我已经用跨 namespace 客户端验证过了。

结论二:策略一旦选中 Pod,该方向就进入“默认拒绝 + 允许列表”

这是 NetworkPolicy 最核心的语义。

结论三:标准 NetworkPolicy 是“允许并集”,不是顺序覆盖

这一点已经通过 default-deny-ingress + allow-same-namespace 实验验证。

结论四:Egress 不只是“能不能访问业务”,还直接决定 DNS 会不会活着

这点已经通过 nslookupwget bad address 的真实现象验证。

结论五:真正的连接放行,要同时考虑源侧 egress 和目标侧 ingress

只看一边是不够的。


下一步建议

继续推进时,最自然的下一课应该进入“身份和权限”:

  • ServiceAccount
  • RBAC
  • ConfigMap / Secret
  • API 访问身份

因为网络边界只是平台安全的一部分。

你要走向专家,就必须把:

  • 网络边界
  • 身份边界
  • 权限边界

三条线一起串起来。*** End Patch