Kubernetes Network Policy 怎么配置和使用?

文章导读
Previous Quiz Next 在 Kubernetes 中,Pod 之间的网络通信默认是开放的。这意味着任何 Pod 都可以无限制地与任何其他 Pod 通信。虽然这在早期开发阶段没问题,但在生产环境中会成为巨大的安全隐患。我们需要一种方法来控制“谁能与谁通信”。这
📋 目录
  1. A Kubernetes Network Policies 是什么?
  2. B 为什么使用 Network Policies?
  3. C Network Policies 的工作原理
  4. D 前提条件
  5. E 设置环境
  6. F 测试 Pod 到 Pod 的通信
  7. G 创建基本 Network Policy(拒绝所有)
  8. H 允许特定通信
  9. I 实际应用:保护前端和 MySQL 后端
  10. J 部署 MySQL
A A

使用 Network Policies 保护 Kubernetes



Previous
Quiz
Next

在 Kubernetes 中,Pod 之间的网络通信默认是开放的。这意味着任何 Pod 都可以无限制地与任何其他 Pod 通信。虽然这在早期开发阶段没问题,但在生产环境中会成为巨大的安全隐患。我们需要一种方法来控制“谁能与谁通信”。这就是 Kubernetes Network Policies 的用武之地。

在本章中,我们将学习 Network Policies 是什么、为什么重要,以及如何使用它们。我们还将构建一个真实世界的示例,来保护前端应用和 MySQL 数据库。

Kubernetes Network Policies 是什么?

Network Policies 是 Kubernetes 资源,用于基于规则控制 Pod 与其他网络端点之间的流量。

使用 Network Policies,我们可以:

  • 仅允许特定 Pod 相互通信。
  • 基于 Pod 标签、namespace 和端口限制流量。
  • 保护敏感服务,如数据库。
  • 在集群内部构建“Zero Trust”网络。

可以将它们想象成 Kubernetes 的防火墙

重要提示: Network Policies 只有在你的 Kubernetes 集群支持它们时才会生效。许多 CNI 插件如 Calico、Cilium、Weave Net 和 Kube-router 都支持 Network Policies。

为什么使用 Network Policies?

没有 Network Policies 时:

  • 任何被入侵的 Pod 都可以横向移动并攻击其他服务。
  • Secrets、数据库和内部 API 都会暴露。

使用 Network Policies 时:

  • 服务仅与授权客户端通信。
  • 在发生安全事件时,我们可以限制影响范围。
  • Kubernetes 应用变得更安全且合规。

Network Policies 的工作原理

Network Policies 定义了两种主要流量类型:

  • Ingress Traffic: 进入 Pod 的流量。
  • Egress Traffic: 从 Pod 发出的流量。

每个 Policy 可以:

  • 通过标签选择 Pod。
  • 允许/拒绝来自特定 Pod、namespace 或 IP 块的流量。
  • 指定允许的端口和协议(TCP/UDP)。

默认行为:

  • 如果没有 Network Policy 选择某个 Pod,则允许所有流量。
  • 如果一个或多个 Network Policy 选择了某个 Pod,则该 Pod 仅允许策略许可的流量,其他所有流量默认被拒绝。

前提条件

在继续之前,请确保:

  • 你有一个运行中的 Kubernetes 集群(Minikube、KIND 或真实集群)。
  • 你的集群安装了支持 Network Policies 的 CNI 插件(例如 Calico、Cilium 或 Weave Net)。

注意: 如果使用 Minikube,可以这样启动并使用 Calico:

$ minikube start --network-plugin=cni --cni=calico

设置环境

我们先部署两个基本应用来模拟通信。

创建 namespace 用于隔离:

$ kubectl create namespace secure-app

输出

namespace/secure-app created

部署后端 Pod:

$ kubectl run backend --image=nginx --namespace=secure-app --labels="app=backend" --port=80

输出

pod/backend created

暴露它:

$ kubectl expose pod backend --port=80 --namespace=secure-app

输出

service/backend exposed

部署前端 Pod:

$ kubectl run frontend --image=nginx --namespace=secure-app --labels="app=frontend" --port=80

输出

pod/frontend created

确认 Pod 是否正在运行:

$ kubectl get pods -n secure-app

输出

NAME       READY   STATUS    RESTARTS   AGE
backend    1/1     Running   0          4m51s
frontend   1/1     Running   0          3m59s

测试 Pod 到 Pod 的通信

默认情况下,frontend 可以轻松访问 backend。

在 frontend pod 内打开一个 shell:

$ kubectl exec -n secure-app -it frontend -- /bin/sh

在 pod 内,尝试 curl backend 服务。我们应该会收到来自 nginx 服务器的 HTML 响应:

# curl backend

输出

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

创建基本 Network Policy(拒绝所有)

现在让我们先锁定所有访问。

创建一个名为 deny-all.yaml 的文件:

	apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny-all
  namespace: secure-app
spec:
  podSelector: {}
  policyTypes:
  - Ingress

此配置的作用:

  • 针对所有 pod(空的 podSelector: {})。
  • 拒绝所有入站流量(Ingress),除非明确允许。

应用它:

$ kubectl apply -f deny-all.yaml

输出

networkpolicy.networking.k8s.io/deny-all created

再次测试

再次从 frontend curl backend:

# curl backend

输出

curl: (7) Failed to connect to backend port 80: Connection refused

允许特定通信

现在让我们只允许来自特定 Pod 的流量。

创建一个名为 allow-frontend-to-backend.yaml 的文件:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-frontend-to-backend
  namespace: secure-app
spec:
  podSelector:
    matchLabels:
      app: backend
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: frontend
    ports:
    - protocol: TCP
      port: 80

解释:

  • 选择带有标签 app=backend 的 pod。
  • 允许来自带有标签 app=frontend 的 pod 的入站流量。
  • 仅限于 TCP 端口 80。

应用它:

$ kubectl apply -f allow-frontend-to-backend.yaml

输出

networkpolicy.networking.k8s.io/allow-frontend-to-backend created

再次测试

在 frontend pod 内运行:

# curl backend

输出

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

实际应用:保护前端和 MySQL 后端

让我们深入探讨一下。假设我们有:

  • 一个前端应用(app: frontend),运行在端口 3000 上。
  • 一个 MySQL 数据库(app: mysql),运行在端口 3306 上。

我们的目标:

  • 只有前端应用可以访问 MySQL。
  • 其他 Pod 不得访问 MySQL。

让我们一步步实现它。

部署 MySQL

创建一个名为 mysql-deployment.yaml 的文件:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: mysql
spec:
  selector:
    matchLabels:
      app: mysql
  replicas: 1
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - name: mysql
        image: mysql:5.7
        env:
        - name: MYSQL_ROOT_PASSWORD
          value: "password"
        ports:
        - containerPort: 3306

应用它:

$ kubectl apply -f mysql-deployment.yaml

输出

deployment.apps/mysql created

检查 MySQL deployment 是否正常运行:

$ kubectl get deployments

输出

NAME    READY   UP-TO-DATE   AVAILABLE   AGE
mysql   1/1     1            1           5m42s

检查 pods 以确保 MySQL 正在运行:

$ kubectl get pods

输出:

NAME                     READY   STATUS    RESTARTS   AGE
mysql-6ddd846c5d-f6z5q   1/1     Running   0          6m5s

部署前端应用

创建一个名为 frontend-deployment.yaml 的文件:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend
spec:
  selector:
    matchLabels:
      app: frontend
  replicas: 1
  template:
    metadata:
      labels:
        app: frontend
    spec:
      containers:
      - name: frontend
        image: node:14
        command: ["node", "-e", "require('http').createServer((req,res)=>res.end('Hello')).listen(3000)"]
        ports:
        - containerPort: 3000

应用它:

$ kubectl apply -f frontend-deployment.yaml

输出

deployment.apps/frontend created

确认 frontend deployment 和 pod 是否正常运行:

$ kubectl get deployments

输出

NAME       READY   UP-TO-DATE   AVAILABLE   AGE
frontend   0/1     1            0           9s
mysql      1/1     1            1           11m
$ kubectl get pods

输出

NAME                        READY   STATUS    RESTARTS   AGE
frontend-58b97ffdc4-lhgvb   1/1     Running   0          2m15s
mysql-6ddd846c5d-f6z5q      1/1     Running   0          13m

为 MySQL 创建 Network Policy

创建一个名为 mysql-network-policy.yaml 的文件:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: mysql-network-policy
spec:
  podSelector:
    matchLabels:
      app: mysql
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: frontend
    ports:
    - protocol: TCP
      port: 3306

其作用:

  • 只有带有标签 app=frontend 的 Pods 才能通过 3306 端口与带有标签 app=mysql 的 Pods 通信。

应用它:

$ kubectl apply -f mysql-network-policy.yaml

输出:

networkpolicy.networking.k8s.io/mysql-network-policy created

确认 network policy 是否正在运行:

$ kubectl get networkpolicies

输出

NAME                   POD-SELECTOR   AGE
mysql-network-policy   app=mysql      40s

出口控制(可选但重要)

Ingress 控制入站流量。Egress 控制出站流量。

让我们创建一个策略来限制 pods 访问外部互联网:

创建一个名为 deny-egress.yaml 的文件:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny-egress
  namespace: secure-app
spec:
  podSelector: {}
  policyTypes:
  - Egress

应用它:

$ kubectl apply -f deny-egress.yaml

输出:

networkpolicy.networking.k8s.io/deny-egress created

现在在 pod 内部,尝试 curl google.com 将失败:

$ curl google.com

输出

curl: (7) Failed to connect to google.com port 80: Connection refused

注意事项

在 Kubernetes 中应用 Network Policies 时,请注意以下几点 -

  • Network Policy 一旦应用就是“仅允许”模式,其他所有流量默认被拒绝。
  • 策略执行依赖于 CNI 插件。确保你的 CNI 支持它。
  • Egress 规则对于防止 Pods 访问互联网至关重要。
  • 谨慎使用标签。策略在很大程度上依赖于标签。
  • 从简单开始,然后逐步构建复杂的策略。

Network Policies 的最佳实践

以下是为使用 network policies 保护 Kubernetes 安全的一组最佳实践 -

  • 首先应用“默认拒绝所有入站流量”和“默认拒绝所有出站流量”。
  • 明确允许所需服务之间的流量。
  • 使用 namespaces 进一步隔离环境(dev、staging、prod)。
  • 在推送到生产环境之前仔细测试 policies。
  • 监控流量以识别缺失或多余的 policies。

结论

通过使用 Network Policies,我们为 Kubernetes 应用程序添加了一层强大的安全防护。这就像在集群内部筑起墙壁和大门。我们可以精确控制流量并防止未经授权的访问。

在本章中,我们学习了 Network Policies 的工作原理、如何应用它们,并展示了一个真实世界的示例,用于保护前端和 MySQL 后端。下一步是通过编写自己的 policies 来练习,并在你的 Kubernetes 集群中加强安全。