使用网络策略控制 Kubernetes 流量

KubernetesBeginner
立即练习

简介

命名空间可以组织资源,但不会自动阻止网络流量。NetworkPolicy 用于定义哪些被选中的 Pod 可以发起或接收连接,前提是集群网络插件能够执行这些策略。本实验已准备好支持策略执行的 Calico 网络。

你将先验证流量默认处于开放状态,然后应用默认拒绝策略,最后仅允许来自获批命名空间且带有指定标签的客户端访问。

本实验遵循安全的策略配置流程:先建立正常工作的基线,再拒绝流量,添加范围最小的例外规则,最后分别测试允许和拒绝的场景。只测试允许的客户端,可能会遗漏策略范围过大的问题。

部署服务器和客户端

在此步骤中,创建独立的应用命名空间和客户端命名空间,部署一个 NGINX 服务器,以及两个持续运行的客户端。

这里有意使用熟悉的命令式操作,这样网络实验的重点不会被无关的 YAML 配置掩盖。其中一个客户端添加标签 role=approved,另一个添加标签 role=untrusted。客户端命名空间添加标签 access=approved。后续策略选择器会同时使用这两个层级的标签。

cd /home/labex/project/network-policy
kubectl create namespace practice-apps
kubectl create namespace practice-clients
kubectl label namespace practice-clients access=approved

创建服务器并将其暴露为服务。kubectl expose 会创建一个 ClusterIP Service,其选择器会匹配 Deployment 自动生成的 Pod 标签 app=protected-web:

kubectl create deployment protected-web -n practice-apps --image=nginx:1.27-alpine --replicas=1
kubectl expose deployment protected-web -n practice-apps --port=80 --target-port=80

创建两个用于诊断的客户端 Pod。它们内部的休眠循环会让 Pod 持续可用,以便反复执行 kubectl exec 测试:

kubectl run 会直接创建独立 Pod。--labels 为 Pod 添加策略选择器所需的身份标签,--restart=Never 防止 kubectl 创建工作负载控制器,而 -- 用于分隔 kubectl 选项和容器内执行的 sh -c 命令。在等待命令中,--all 表示 practice-clients 中的所有 Pod 都必须变为就绪状态。

kubectl run approved-client -n practice-clients --image=busybox:1.36 --image-pull-policy=IfNotPresent --labels=role=approved --restart=Never -- sh -c 'while true; do sleep 30; done'
kubectl run untrusted-client -n practice-clients --image=busybox:1.36 --image-pull-policy=IfNotPresent --labels=role=untrusted --restart=Never -- sh -c 'while true; do sleep 30; done'
kubectl wait --for=condition=Ready pod -n practice-clients --all --timeout=120s
kubectl rollout status deployment/protected-web -n practice-apps --timeout=60s

仅仅将资源分隔到不同命名空间中,并不能阻止两个客户端访问该 Service。

就绪检查命令应显示两个客户端 Pod 和服务器 Deployment 均已准备就绪:

pod/approved-client condition met
pod/untrusted-client condition met
deployment "protected-web" successfully rolled out

此时,标签只是描述客户端身份,并不会限制网络访问。只有当 NetworkPolicy 的选择器使用某个标签后,该标签才会成为流量规则的一部分。

验证流量默认处于开放状态

在此步骤中,让两个客户端分别访问跨命名空间的 Service。完整限定名称中包含 Service 名称和命名空间名称。

在同一命名空间内,只写 protected-web 即可。从 practice-clients 访问时,protected-web.practice-apps 会同时告诉集群 DNS Service 名称及其所属命名空间。应用策略前先测试两个客户端,这样后续访问失败时,就能确认问题来自策略,而不是 DNS 或 Service 配置。

与健康探针实验类似,wget -qO- 会输出响应正文而不显示进度信息。--timeout=3 可避免被拒绝的请求无限期等待,管道后的 grep 只显示可识别的 NGINX 文本。

cd /home/labex/project/network-policy
kubectl exec approved-client -n practice-clients -- wget -qO- --timeout=3 http://protected-web.practice-apps | grep 'Welcome to nginx'
kubectl exec untrusted-client -n practice-clients -- wget -qO- --timeout=3 http://protected-web.practice-apps | grep 'Welcome to nginx'
kubectl get networkpolicies -n practice-apps

在没有匹配目标 Pod 的 NetworkPolicy 时,入口流量默认允许通过。这可以纠正常见误解:命名空间本身并不是网络安全边界。

两个请求都应匹配 Welcome to nginx,而 kubectl get networkpolicies 应报告没有资源。这就是已知正常的基线,接下来你会有意改变它。

<title>Welcome to nginx!</title>
<h1>Welcome to nginx!</h1>
<title>Welcome to nginx!</title>
<h1>Welcome to nginx!</h1>
No resources found in practice-apps namespace.

页面文本重复出现,证明在开始进行网络隔离之前,两个客户端身份都可以访问同一个 Service。

应用入口流量默认拒绝策略

在此步骤中,选择 practice-apps 中的所有 Pod,并且不允许任何入口流量来源。

NetworkPolicy 只有对其选择的 Pod,以及 policyTypes 中列出的流量方向才具有限制作用。这里的 podSelector: {} 会选择 practice-apps 中的所有 Pod,而不配置任何 ingress 允许规则,就会形成入口流量默认拒绝。

cd /home/labex/project/network-policy
cat <<'EOF' > default-deny.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: {name: default-deny-ingress, namespace: practice-apps}
spec:
  podSelector: {}
  policyTypes: [Ingress]
EOF
kubectl apply -f default-deny.yaml
kubectl exec approved-client -n practice-clients -- wget -qO- --timeout=3 http://protected-web.practice-apps || echo 'blocked as expected'

空的 podSelector 会选择策略所在命名空间中的所有 Pod。预期出现的超时说明入口流量已被拒绝,并不表示 Service 消失了。

|| echo ... 会处理 wget 预期的非零退出状态,让终端能够继续执行。这里的失败结果正是受控实验中的证据。服务器 Pod 仍然处于就绪状态,发生变化的只有网络访问控制。

networkpolicy.networking.k8s.io/default-deny-ingress created
wget: download timed out
command terminated with exit code 1
blocked as expected

由于被拒绝的数据包不会收到应用层响应,因此出现超时是预期结果。最后一行表明,Shell 已将这个预期中的失败转换为易于阅读的结果。

允许一个获批客户端访问

在此步骤中,添加一条允许规则,要求来源同时满足获批命名空间和获批 Pod 标签。

NetworkPolicy 采用叠加方式生效。默认拒绝策略仍然保留,而第二条策略只额外允许一个符合条件的来源。被选中的服务器 Pod 仅在来源命名空间和来源 Pod 都匹配时,才会接受目标端口为 80 的 TCP 流量。

cd /home/labex/project/network-policy
cat <<'EOF' > allow-approved.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: {name: allow-approved-client, namespace: practice-apps}
spec:
  podSelector: {matchLabels: {app: protected-web}}
  policyTypes: [Ingress]
  ingress:
    - from:
        - namespaceSelector: {matchLabels: {access: approved}}
          podSelector: {matchLabels: {role: approved}}
      ports:
        - {protocol: TCP, port: 80}
EOF
kubectl apply -f allow-approved.yaml
kubectl exec approved-client -n practice-clients -- wget -qO- --timeout=3 http://protected-web.practice-apps | grep 'Welcome to nginx'
kubectl exec untrusted-client -n practice-clients -- wget -qO- --timeout=3 http://protected-web.practice-apps || echo 'untrusted client blocked'

由于两个选择器位于同一个 from 条目中,因此必须同时匹配。若将它们写在不同条目中,则表示逻辑或关系。

获批客户端的请求应再次成功,而不受信任客户端的请求仍应超时。这一对结果证明策略具有选择性,而不是简单地重新开放所有入口流量。

networkpolicy.networking.k8s.io/allow-approved-client created
<title>Welcome to nginx!</title>
<h1>Welcome to nginx!</h1>
wget: download timed out
command terminated with exit code 1
untrusted client blocked

请将结果作为一组来理解:先是 approved-client 返回页面内容,然后是 untrusted-client 请求超时。仅看到成功请求,并不能证明规则范围足够精确。

总结

你已经验证了命名空间默认不会隔离网络流量,应用了入口流量默认拒绝策略,并通过命名空间、Pod、协议和端口条件的组合,仅允许一个客户端访问。