一、网络策略产生背景¶
在 Kubernetes 之前,Pod 之间的网络通信是默认开放的,所有的 Pod 都可以直接与其他 Pod 进行通信,这在某些情况下可能会导致安全风险。
随着 Kubernetes 集群的规模和复杂性增加,对网络通信的安全性需求也变得越来越重要。特别是在多租户环境中,需要确保不同的工作负载之间的通信是受限和授权的。
为了解决这些安全性和可控性问题,Kubernetes 引入了网络策略这一概念。网络策略是一种声明式的资源,允许定义 Pod 之间的通信规则,即哪些 Pod 允许与哪些 Pod 进行通信,以及允许的通信协议和端口等。
使用网络策略,可以根据标签选择器和网络策略规则来控制 Pod 之间的流量。通过限制网络通信,可以减少潜在的攻击面,提高集群的安全性,并确保只有授权的 Pod 可以进行通信。
二、网络策略是什么¶
NetworkPolicy 是一种以应用为中心的结构,允许你设置如何允许 Pod 与网络上的各类网络“实体” (我们这里使用实体以避免过度使用诸如“端点”和“服务”这类常用术语, 这些术语在 Kubernetes 中有特定含义)通信。 NetworkPolicy 适用于一端或两端与 Pod 的连接,与其他连接无关。
Pod 可以通信的 Pod 是通过如下三个标识符的组合来辩识的:
-
其他被允许的 Pods(例外:Pod 无法阻塞对自身的访问)
-
被允许的名字空间
-
IP 组块(例外:与 Pod 运行所在的节点的通信总是被允许的, 无论 Pod 或节点的 IP 地址)
网络策略的规则由网络插件(如Calico、Cilium、Antrea 等)在底层实现和执行。不同的网络插件可能支持略微不同的网络策略规则。
三、Pod隔离类型¶
Pod 有两种隔离: 出口的隔离和入口的隔离。它们涉及到可以建立哪些连接。 这里的“隔离”不是绝对的,而是意味着“有一些限制”。 另外的,“非隔离方向”意味着在所述方向上没有限制。这两种隔离(或不隔离)是独立声明的, 并且都与从一个 Pod 到另一个 Pod 的连接有关。
默认情况下,一个 Pod 的出口是非隔离的,即所有外向连接都是被允许的。如果有任何的 NetworkPolicy 选择该 Pod 并在其 policyTypes 中包含 “Egress”,则该 Pod 是出口隔离的, 我们称这样的策略适用于该 Pod 的出口。当一个 Pod 的出口被隔离时, 唯一允许的来自 Pod 的连接是适用于出口的 Pod 的某个 NetworkPolicy 的 egress 列表所允许的连接。 这些 egress 列表的效果是相加的。
默认情况下,一个 Pod 对入口是非隔离的,即所有入站连接都是被允许的。如果有任何的 NetworkPolicy 选择该 Pod 并在其 policyTypes 中包含 “Ingress”,则该 Pod 被隔离入口, 我们称这种策略适用于该 Pod 的入口。当一个 Pod 的入口被隔离时,唯一允许进入该 Pod 的连接是来自该 Pod 节点的连接和适用于入口的 Pod 的某个 NetworkPolicy 的 ingress 列表所允许的连接。这些 ingress 列表的效果是相加的。
网络策略是相加的,所以不会产生冲突。如果策略适用于 Pod 某一特定方向的流量, Pod 在对应方向所允许的连接是适用的网络策略所允许的集合。 因此,评估的顺序不影响策略的结果。
要允许从源 Pod 到目的 Pod 的连接,源 Pod 的出口策略和目的 Pod 的入口策略都需要允许连接。 如果任何一方不允许连接,建立连接将会失败。
四、网络策略配置示例¶
下面是一个 NetworkPolicy 的示例:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: test-network-policy
namespace: default
spec:
podSelector:
matchLabels:
role: db
policyTypes:
- Ingress
- Egress
ingress:
- from:
- ipBlock:
cidr: 172.17.0.0/16
except:
- 172.17.1.0/24
- namespaceSelector:
matchLabels:
project: myproject
- podSelector:
matchLabels:
role: frontend
ports:
- protocol: TCP
port: 6379
egress:
- to:
- ipBlock:
cidr: 10.0.0.0/24
ports:
- protocol: TCP
port: 5978
说明:除非选择支持网络策略的网络解决方案,否则将上述示例发送到API服务器没有任何效果。
上面参数说明:
apiVersion和kind:这两个字段指定了 NetworkPolicy 资源的 API 版本和类型metadata:这是 NetworkPolicy 元数据,其中包含名称(name)和所属命名空间(namespace)spec:这是 NetworkPolicy 的规格部分,包含定义 NetworkPolicy 规则的内容podSelector:表示该策略对哪些 Pod 生效。示例中配置的是 role=db,代表该策略应用 于该 NetworkPolicy 所在的 Namespace(NetworkPolicy 具有 Namespace 隔离性)下符合 role=db 的 Pod 上。如果该选项为空,则对该 Namespace 下所有的 Pod 生效。policyTypes:网络策略的类型。目前网络策略支持入站(Ingress)和出站(Egress)的 规则配置,出站代表符合 podSelector 规则的 Pod 的出口流量限制,入站是其它 Pod 对 符合 podSelector 规则的 Pod 的访问流量限制。如果未指定 policyTypes,则默认为 Ingress, 如果未指定 policyTypes,且有出口规则的话,也会配置 Egress。- ingress:如 policyTypes 所述,Ingress 是对入口流量的管控。每个 NetworkPolicy 可以 包含一个 Ingress 白名单列表,每个规则都允许匹配 from 和 ports 部分的流量。其中 from 表示访问来源,可以配置为 ipBlock、namespaceSelector、podSelector,如果此项为 空则表示匹配所有来源。另外一个 ports 表示可被访问的端口列表,如果此项为空则表 示匹配所有端口。
- egress:如 policyTypes 所述,Egress 是对出口流量的管控。同样,NetworkPolicy 可以 包含一个 Egress 白名单列表,不同的是 Egress 的可配置参数为 to 和 ports。其中 ports 表示可以访问的目标端口,to 表示可以访问的目的,也可以配置为 ipBlock、 namespaceSelector、podSelector。
- namespaceSelector:匹配具有指定标签的 Namespace 下的所有 Pod。当前示例的 ingress 的 from 参数配置了 namespaceSelector,表示匹配具有 project=myproject 标签的 Namespace 下的所有 Pod。
- ipBlock:表示匹配的 IP 块,可以是单个 IP,也可以是网段,同时也支持 IPV6 格式。 Except 为可选参数,表示从 IPBlock 排除一些地址。
上面示例含义如下:
- 隔离 default 命名空间下具有 role=db 标签的 Pod
- 允许 IP 地址范围为 172.17.0.0–172.17.0.255 和 172.17.2.0–172.17.255.255 访问 default 命名空间下具有 role=db 的 Pod 的 6379 且协议为 TCP 的端口;
- 允许具有 project=myproject 的 Namespace 下的所有 Pod 访问 default 命名空间下具有 role=db 的 Pod 的 6379 且协议为 TCP 的端口;
- 允许 default 命名空间(和 NetworkPolicy 同一命名空间)下具有 role=frontend 标签的 Pod 访问 default 命名空间下具有 role=db 的 Pod 的 6379 且协议为 TCP 的端口;
- 允许带有 role=db 标签的 Pod 可以访问 10.0.0.0/24 网段下的 5978 且协议为 TCP 的端口
五、网络策略使用示例¶
5.1 隔离中间件服务¶
首先先准备一下环境:
1.创建该项目所用的 Namespace
$ kubectl create ns nw-demo
2..创建 MySQL 服务,MySQL 以容器启动时,必须配置 root 的密码,或者设置密码为空,所 以需要设置一个 MYSQL_ROOT_PASSWORD 的变量
$ kubectl create deploy mysql --image=registry.cn-hangzhou.aliyuncs.com/abroad_images/mysql:5.7.23 -n nw-demo
$ kubectl set env deploy/mysql MYSQL_ROOT_PASSWORD=mysql -n nw-demo
3.创建 Redis 服务
$ kubectl create deploy redis --image=registry.cn-hangzhou.aliyuncs.com/abroad_images/redis:5.0.9-alpine3.11 -n nw-demo
4.确认容器是否启动
[root@k8s-master01 ~]# kubectl get po -n nw-demo -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NDE READINESS GATES
mysql-7b759888cb-zq24p 1/1 Running 0 4m13s 172.17.125.48 k8s-node01 <none> <none>
redis-68595bcc66-zrxjz 1/1 Running 0 30s 172.17.125.49 k8s-node01 <none> <none>
5.在没有配置任何网络策略时,测试下网络的连通性,可以在任意 Kubernetes 节点上执行 telnet 命令,可以看到此时的网络都是可以通信的。
[root@k8s-master01 ~]# telnet 172.17.125.48 3306
Trying 172.17.125.48...
Connected to 172.17.125.48.
Escape character is '^]'.
J
5.7.23t)0_pC&2ID~L*0|t+Qmysql_native_password
[root@k8s-master01 ~]# telnet 172.17.125.49 6379
Trying 172.17.125.49...
Connected to 172.17.125.49.
Escape character is '^]'.
上面环境准备完成后,下面配置网络策略进行隔离:
1.查看Pod标签
[root@k8s-master01 ~]# kubectl get po -n nw-demo --show-labels
NAME READY STATUS RESTARTS AGE LABELS
mysql-7b759888cb-zq24p 1/1 Running 0 11m app=mysql,pod-template-hash=7b759888cb
redis-68595bcc66-zrxjz 1/1 Running 0 7m44s app=redis,pod-template-hash=68595bcc66
2.创建网络策略,根据Pod标签拆分MySQL 和 Redis
[root@k8s-master01 ~]# vim mysql-redis-nw.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: mysql-np
namespace: nw-demo
spec:
podSelector:
matchLabels:
app: mysql
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
access-nw-mysql-redis: "true"
ports:
- protocol: TCP
port: 3306
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: redis-np
namespace: nw-demo
spec:
podSelector:
matchLabels:
app: redis
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
access-nw-mysql-redis: "true"
ports:
- protocol: TCP
port: 6379
上面文件说明:
存在两个网络策略,其中 mysql-np 是对具有 app=mysql 标签的 Pod 进行管 理,redis-np 是对具有 app=redis 标签的 Pod 进行管理。同时该网络策略的 ingress from 是以 namespaceSelector 的标签进行匹配的,可以很方便的对某个Namespace下的Pod进行管控。
3.创建该 NetworkPolicy
[root@k8s-master01 ~]# kubectl apply -f mysql-redis-nw.yaml
[root@k8s-master01 ~]# kubectl get -f mysql-redis-nw.yaml -n nw-demo
NAME POD-SELECTOR AGE
mysql-np app=mysql 55s
redis-np app=redis 55s
4.测试是否能访问该 Namespace 下的 MySQL 和 Redis,观察到无法访问
在宿主机上测试是否能访问该 Namespace 下的 MySQL 和 Redis,观察到无法访问
[root@k8s-master01 ~]# telnet 172.17.125.48 3306
Trying 172.17.125.48...
^C
[root@k8s-master01 ~]# telnet 172.17.125.49 6379
Trying 172.17.125.49...
^C
在同命名空间下的Pod上测试是否能访问该 Namespace 下的 MySQL 和 Redis,观察到无法访问
[root@k8s-master01 ~]# kubectl run -ti debug-tools --image=registry.cn-hangzhou.aliyuncs.com/zq-demo/debug-tools:latest -n nw-demo
(127 05:39 debug-tools:/) curl 172.17.125.48:3306
^C
(130 05:40 debug-tools:/) curl 172.17.125.49:6379
^C
5.给该 Namespace 添加一个 NetworkPolicy 中配置的标签
[root@k8s-master01 ~]# kubectl label ns nw-demo access-nw-mysql-redis=true
6.使用 nw-demo 命名空间下的 debug-tools 再次测试,此时 nw-demo 下的 Pod 已经可以访问 MySQL 和 Redis
[root@k8s-master01 ~]# kubectl exec -it debug-tools -n nw-demo -- bash
(05:49 debug-tools:/) curl 172.17.125.48:3306
5.7.23
vv*R▒▒▒rh'|"^5O mysql_native_password▒Got packets out of order(05:50 debug-tools:/)
(05:50 debug-tools:/) curl 172.17.125.49:6379
-ERR wrong number of arguments for 'get' command
7.在 default 命名空间下的Pod进行测试
[root@k8s-master01 ~]# kubectl run -ti debug-tools --image=registry.cn-hangzhou.aliyuncs.com/zq-demo/debug-tools:latest -n default
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
debug-tools 1/1 Running 1 (5s ago) 2m30s
对Mysql和Redis进行访问测试,观察到default命名空间下的Pod无法访问
[root@k8s-master01 ~]# kubectl run -ti debug-tools --image=registry.cn-hangzhou.aliyuncs.com/zq-demo/debug-tools:latest -n default
If you don't see a command prompt, try pressing enter.
(05:53 debug-tools:/)
(05:53 debug-tools:/) curl 172.17.125.48:3306
^C
(130 05:53 debug-tools:/) curl 172.17.125.49:6379
^C
5.2 服务发布限制于 Ingress¶
一般情况下,一个项目的服务发布,会把域名的根路径指向前端应用,接口路径指向对应的网关或者微服务。假设现在创建一个 Nginx 服务充当前端页面,配置网络策略只让 Ingress Controller 访问该应用:
1.创建应用
[root@k8s-master01 ~]# kubectl create deploy nginx --image=registry.cn-hangzhou.aliyuncs.com/abroad_images/nginx:latest -n nw-demo
2.暴露服务
[root@k8s-master01 ~]# kubectl expose deploy nginx -n nw-demo --port=80
3.查看创建的服务
[root@k8s-master01 ~]# kubectl get svc,po -n nw-demo -l app=nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/nginx ClusterIP 10.0.53.183 <none> 80/TCP 87s
NAME READY STATUS RESTARTS AGE
pod/nginx-86df579-s774b 1/1 Running 0 94s
4.测试该服务在没有任何网络策略的情况下,不同命名空间下是否可以被任何 Pod 访问,观察到可以
在default命名空间下进行测试访问
[root@k8s-master01 ~]# kubectl exec -it debug-tools -- curl -Is nginx.nw-demo
HTTP/1.1 200 OK
Server: nginx/1.23.3
Date: Sat, 05 Aug 2023 13:34:09 GMT
Content-Type: text/html
Content-Length: 615
Last-Modified: Tue, 13 Dec 2022 15:53:53 GMT
Connection: keep-alive
ETag: "6398a011-267"
Accept-Ranges: bytes
上面curl参数说明:
-I:这是curl命令的一个选项,全称是--head。它告诉curl只发送一个 HEAD 请求而不获取响应体,只返回服务器响应的头部信息。HEAD 请求类似于 GET 请求,但服务器只返回头部信息,而不返回实际的响应内容。-s:这是curl命令的一个选项,全称是--silent。它告诉curl在执行请求时不显示进度条和错误信息。使用-s选项可以使curl在后台静默执行,不会在终端上产生任何输出
在nw-demo命名空间下进行测试访问
[root@k8s-master01 ~]# kubectl exec -it debug-tools -n nw-demo -- curl -Is nginx.nw-demo
HTTP/1.1 200 OK
Server: nginx/1.23.3
Date: Sat, 05 Aug 2023 13:36:17 GMT
Content-Type: text/html
Content-Length: 615
Last-Modified: Tue, 13 Dec 2022 15:53:53 GMT
Connection: keep-alive
ETag: "6398a011-267"
Accept-Ranges: bytes
5.配置网络策略只让 Ingress Controller 访问该服务,如果没有安装 Ingress Controller ,请参考---再识Ingress中4.1部分进行 Ingress Controller 安装
[root@k8s-master01 ~]# vim nginx-nw.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: nginx-np
namespace: nw-demo
spec:
podSelector:
matchLabels:
app: nginx
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
app.kubernetes.io/name: ingress-nginx
podSelector:
matchLabels:
"app.kubernetes.io/name": ingress-nginx
- podSelector: {}
ports:
- protocol: TCP
port: 80
上面网络策略说明:
- 该网络策略对具有 app=nginx 标签的 Pod 生效
- nw-demo命名空间下的任意Pod都允许访问 nginx.nw-demo
- 只让具有 app.kubernetes.io/name=ingress-nginx 标签的 Namespace 下的具有 app.kubernetes.io/name=ingress-nginx 标签的 Pod 允许访 问 nginx.nw-demo
6.创建该 NetworkPolicy,并测试连通性
[root@k8s-master01 ~]# kubectl create -f nginx-nw.yaml
在default命名空间下,对nginx.nw-demo服务进行访问测试,观察到因不匹配网络策略,导致访问失败
[root@k8s-master01 ~]# kubectl exec debug-tools -- curl --connect-timeout 2 -Is nginx.nw-demo
command terminated with exit code 28
在nw-demo命名空间下,对nginx.nw-demo服务进行访问测试,观察到因匹配网络策略访问成功
[root@k8s-master01 ~]# kubectl exec debug-tools -n nw-demo -- curl --connect-timeout 2 -Is nginx.nw-demo
HTTP/1.1 200 OK
Server: nginx/1.23.3
Date: Sat, 05 Aug 2023 13:44:56 GMT
Content-Type: text/html
Content-Length: 615
Last-Modified: Tue, 13 Dec 2022 15:53:53 GMT
Connection: keep-alive
ETag: "6398a011-267"
Accept-Ranges: bytes
在ingress-nginx命名空间下,对nginx.nw-demo服务进行访问测试
(1)首先需要检查ns是否存在app.kubernetes.io/name=ingress-nginx标签
[root@k8s-master01 ~]# kubectl get ns -l app.kubernetes.io/name=ingress-nginx
NAME STATUS AGE
ingress-nginx Active 6d6h
如果没有执行以下命令进行添加
[root@k8s-master01 ~]# kubectl label ns ingress-nginx app.kubernetes.io/name=ingress-nginx
(2)在已有Pod中进行访问测试,理论上这里可以访问,但是实际上访问不了,算是一个遗留问题吧
[root@k8s-master01 ~]# kubectl get po -n ingress-nginx -l "app.kubernetes.io/name"=ingress-nginx
NAME READY STATUS RESTARTS AGE
ingress-nginx-controller-qrxvk 1/1 Running 0 87m
ingress-nginx-defaultbackend-587dcdcdb8-rklwj 1/1 Running 0 87m
[root@k8s-master01 ~]# kubectl exec -it -n ingress-nginx ingress-nginx-controller-qrxvk -- curl -Is nginx.nw-demo
command terminated with exit code 130
再新起一个ingress-nginx-controller进行访问测试,观察到可以成功访问
[root@k8s-master01 ~]# kubectl label node k8s-node01 ingress=true
[root@k8s-master01 ~]# kubectl get po -n ingress-nginx -l "app.kubernetes.io/name"=ingress-nginx
NAME READY STATUS RESTARTS AGE
ingress-nginx-controller-6577l 1/1 Running 0 76m
ingress-nginx-controller-qrxvk 1/1 Running 0 87m
ingress-nginx-defaultbackend-587dcdcdb8-rklwj 1/1 Running 0 87m
[root@k8s-master01 ~]# kubectl exec -it -n ingress-nginx ingress-nginx-controller-6577l -- curl -Is nginx.nw-demo
HTTP/1.1 200 OK
Server: nginx/1.23.3
Date: Sat, 05 Aug 2023 15:20:19 GMT
Content-Type: text/html
Content-Length: 615
Last-Modified: Tue, 13 Dec 2022 15:53:53 GMT
Connection: keep-alive
ETag: "6398a011-267"
Accept-Ranges: bytes
六、网络策略注意事项¶
在配置网络策略时,有很多细节需要注意,比如上述的示例中,一段关于 ingress 的 from 配 置:
- from:
- ipBlock:
cidr: 172.17.0.0/16
except:
- 172.17.1.0/24
- namespaceSelector:
matchLabels:
project: myproject
- podSelector:
matchLabels:
role: frontend
需要注意的是在 ipBlock、namespaceSelector 和 podSelector 前面都有一个“-”,如果前面没 有这个横杠将是另外一个完全不同的概念。可以看一下下面的示例:
ingress:
- from:
- namespaceSelector:
matchLabels:
user: alice
podSelector:
matchLabels:
role: frontend
此时的 namespaceSelector 有“-”,podSelector 没有“-”,那此时的配置,代表的含义是允 许具有 user=alice 标签的 Namespace 下,并且具有 role=client 标签的所有 Pod 访问, namespaceSelector 和 podSelector 是且的关系。那我们继续看一下示例:
ingress:
- from:
- namespaceSelector:
matchLabels:
user: alice
- podSelector:
matchLabels:
role: client
此时的 namespaceSelector 和 podSelector 都有“-”,配置的含义是允许具有 user=alice 标签 的 Namespace 下的所有 Pod 和当前 Namespace 下具有 role=client 标签的 Pod 访问, namespaceSelector 和 podSelector 是或的关系。
注意:在配置 ipBlock 时,可能也会出现差异性。因为我们在接收或者发送流 量时,很有可能伴随着数据包中源 IP 和目标 IP 的重写,也就是 SNAT 和 DNAT,此时会造成流 量的目标 IP 和源 IP 与配置的 ipBlock 出现了差异性,造成网络策略不生效,所以在配置 IPBlock 时,需要确认网络交换中是否存在源目地址转换,并且 IPBlock 最好不要配置 Pod 的 IP,因为 Pod 发生重建时,它的 IP 地址一般就会发生变更,所以 IPBlock 一般用于配置集群的外部 IP。