使用 Network Policies 保护 Kubernetes
在 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 集群中加强安全。