1
什么是NetworkPolicy?
如果你希望在 IP 地址或端口层面(OSI 第 3 层或第 4 层)控制网络流量, 则你可以考虑为集群中特定应用使用 Kubernetes 网络策略(NetworkPolicy)。NetworkPolicy 是一种以应用为中心的结构,允许你设置如何允许 Pod 与网络上的各类网络“实体” (我们这里使用实体以避免过度使用诸如“端点”和“服务”这类常用术语, 这些术语在 Kubernetes 中有特定含义)通信。
Pod 可以通信的 Pod 是通过如下三个标识符的组合来辩识的:
其他被允许的 Pods(例外:Pod 无法阻塞对自身的访问)
被允许的名字空间
IP 组块(例外:与 Pod 运行所在的节点的通信总是被允许的, 无论 Pod 或节点的 IP 地址)
在定义基于 Pod 或名字空间的 NetworkPolicy 时,你会使用 选择算符 来设定哪些流量 可以进入或离开与该算符匹配的 Pod。
同时,当基于 IP 的 NetworkPolicy 被创建时,我们基于 IP 组块(CIDR 范围) 来定义策略。
前置条件
网络策略通过网络插件 来实现。要使用网络策略,你必须使用支持 NetworkPolicy 的网络解决方案。创建一个 NetworkPolicy 资源对象而没有控制器来使它生效的话,是没有任何作用的。
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 的入口策略都需要允许连接。如果任何一方不允许连接,建立连接将会失败。
2
NetworkPolicy配置详解
参阅 NetworkPolicy 来了解资源的完整定义。
下面是一个 NetworkPolicy 的示例:
apiVersion: networking.k8s.io/v1kind: NetworkPolicymetadata:name: test-network-policynamespace: defaultspec:podSelector: matchLabels: role: dbpolicyTypes:-Ingress-Egressingress:-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: 6379egress:-to: - ipBlock: cidr: 10.0.0.0/24 ports: - protocol: TCP port: 5978
说明: 除非选择支持网络策略的网络解决方案,否则将上述示例发送到API服务器没有任何效果。
必需字段:与所有其他的 Kubernetes 配置一样,NetworkPolicy 需要 apiVersion、 kind 和 metadata 字段。关于配置文件操作的一般信息,请参考使用 ConfigMap 配置容器, 和对象管理。
spec:NetworkPolicy 规约 中包含了在一个名字空间中定义特定网络策略所需的所有信息。
podSelector:每个 NetworkPolicy 都包括一个 podSelector,它对该策略所 适用的一组 Pod 进行选择。示例中的策略选择带有 "role=db" 标签的 Pod。空的 podSelector 选择名字空间下的所有 Pod。
policyTypes: 每个 NetworkPolicy 都包含一个 policyTypes 列表,其中包含 Ingress 或 Egress 或两者兼具。policyTypes 字段表示给定的策略是应用于 进入所选 Pod 的入站流量还是来自所选 Pod 的出站流量,或两者兼有。如果 NetworkPolicy 未指定 policyTypes 则默认情况下始终设置 Ingress;如果 NetworkPolicy 有任何出口规则的话则设置 Egress。
ingress: 每个 NetworkPolicy 可包含一个 ingress 规则的白名单列表。每个规则都允许同时匹配 from 和 ports 部分的流量。示例策略中包含一条 简单的规则:它匹配某个特定端口,来自三个来源中的一个,第一个通过 ipBlock 指定,第二个通过 namespaceSelector 指定,第三个通过 podSelector 指定。
egress: 每个 NetworkPolicy 可包含一个 egress 规则的白名单列表。每个规则都允许匹配 to 和 port 部分的流量。该示例策略包含一条规则, 该规则将指定端口上的流量匹配到 10.0.0.0/24 中的任何目的地。
所以,该网络策略示例:
隔离 "default" 名字空间下 "role=db" 的 Pod (如果它们不是已经被隔离的话)。
(Ingress 规则)允许以下 Pod 连接到 "default" 名字空间下的带有 "role=db" 标签的所有 Pod 的 6379 TCP 端口:
"default" 名字空间下带有 "role=frontend" 标签的所有 Pod
带有 "project=myproject" 标签的所有名字空间中的 Pod
IP 地址范围为 172.17.0.0–172.17.0.255 和 172.17.2.0–172.17.255.255 (即,除了 172.17.1.0/24 之外的所有 172.17.0.0/16)
(Egress 规则)允许从带有 "role=db" 标签的名字空间下的任何 Pod 到 CIDR 10.0.0.0/24 下 5978 TCP 端口的连接。
3
NetworkPolicy注意事项
在配置网络策略时,有很多细节需要注意,比如上述的示例中,一段关于ingress的from配置:
- from: - ipBlock: cidr: 172.17.0.0/16 except: - 172.17.1.0/24 - namespaceSelector: matchLabels: project: myproject - podSelector: matchLabels: role: frontend#namespaceSelector和podSelector是或的关系,表示两个条件满足一个就可以
需要注意的是在ipBlock、namespaceSelector和podSelector前面都有一个“-”,如果前面没有这个横杠将是另外一个完全不同的概念。可以看一下下面的示例:
- from: - ipBlock: cidr: 172.17.0.0/16 except: - 172.17.1.0/24 - namespaceSelector: matchLabels: project: myproject podSelector: matchLabels: role: frontend#namespaceSelector和podSelector是并且的关系,表示两个条件都满足
此时的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。
4
NetworkPolicy示例1:隔离中间件服务
有一个项目,它有自己数据库MySQL和缓存Redis中间件,我们只希望这个项目的应用能够访问该中间件
假如有一个项目需要通过Ingress进行对外发布,我们想要除了Ingress外,其他任何Namespace下的Pod都不能访问该项目。
假设有一个项目叫nw-demo,里面部署了三个微服务,分别是MySQL、Redis和Nginx。现需要对MySQL、Redis、Nginx进行隔离,分别实现如下效果:
MySQL、Redis只能被该Namespace下的Pod访问;
Nginx可以被Ingress-nginx命名空间下的Pod和该Namespace下的Pod访问;
首先创建该项目所用的Namespace(如果已经存在,或者用其他Namespace测试,也可以不创建):
[root@k8s-master01 ~]# kubectl create ns nw-demonamespace/nw-demo created
创建MySQL服务,MySQL以容器启动时,必须配置root的密码,或者设置密码为空,所以需要设置一个MYSQL_ROOT_PASSWORD的变量:
[root@k8s-master01 ~]# kubectl create deploy mysql --image=registry.cn-beijing.aliyuncs.com/mysql:5.7.23 -n nw-demodeployment.apps/mysql created# 设置密码[root@k8s-master01 ~]# kubectl set env deploy/mysql MYSQL_ROOT_PASSWORD=mysql -n nw-demodeployment.apps/mysql env updated
创建Redis服务:
[root@k8s-master01 ~]# kubectl create deploy redis --image=registry.cn-beijing.aliyuncs.com/redis:5.0.9-alpine3.11 -n nw-demodeployment.apps/redis created
确认容器是否启动:
[root@k8s-master01 ~]# kubectl get po -n nw-demo -owideNAME READY STATUS RESTARTS AGE IP NODE NOMINATEDNODE READINESSGATESmysql-64478b7cf9-qrczt 1/1 Running 0 2m9s 192.170.21.240 k8s-node03.example.local <none> <none>redis-5f69645dc-vvnnj 1/1 Running 0 41s 192.162.55.68 k8s-master01.example.local <none> <none>
在没有配置任何网络策略时,测试下网络的连通性,可以在任意Kubernetes节点上执行telnet命令:
[root@k8s-master01 ~]# telnet 192.170.21.240 3306Trying 192.170.21.240...Connected to 192.170.21.240.Escape character is'^]'.J/}REGq yfCo@Gjumysql_native_passwordConnection closed by foreign host.[root@k8s-master01 ~]# telnet 192.162.55.68 6379Trying 192.162.55.68...Connected to 192.162.55.68.Escape character is'^]'.quit+OKConnection closed by foreign host.
注意: 本文章进行测试的IP和Pod名字可能与实际测试的不一致,需要与实际情况为准
可以看到此时的网络都是可以通信的。接下来可以根据Pod的标签进行网络隔离,首先查看一下Pod的标签:
[root@k8s-master01 ~]# kubectl get po -n nw-demo --show-labelsNAME READY STATUS RESTARTS AGE LABELSmysql-64478b7cf9-qrczt 1/1 Running 0 5m15s app=mysql,pod-template-hash=64478b7cf9redis-5f69645dc-vvnnj 1/1 Running 0 3m47s app=redis,pod-template-hash=5f69645dc
然后根据标签配置网络策略,本示例的配置将MySQL和Redis进行了拆分,配置了两个网络策略,当然也可以给两个Pod配置一个相同的标签,这样就可以使用同一个网络策略进行限制。但是在生产环境中,并不推荐使用同一个网络策略,因为有时候需要更细粒度的策略,同一个网络策略可能会有局限性,也会导致配置失败,所以本示例采用分开的网络策略进行配置:
[root@k8s-master01~]# vim mysql-redis-nw.yamlapiVersion: networking.k8s.io/v1kind: NetworkPolicymetadata:name: mysql-npnamespace: nw-demospec:podSelector: matchLabels: app: mysqlpolicyTypes: -Ingressingress: -from: - namespaceSelector: matchLabels: access-nw-mysql-redis: "true" ports: - protocol: TCP port: 3306---apiVersion: networking.k8s.io/v1kind: NetworkPolicymetadata:name: redis-npnamespace: nw-demospec:podSelector: matchLabels: app: redis policyTypes: -Ingressingress: -from: - namespaceSelector: matchLabels: access-nw-mysql-redis: "true" ports: - protocol: TCP port: 6379
该yaml含有两个NetworkPolicy,其中mysql-np是对具有app=mysql标签的Pod进行管理,redis-np是对具有app=redis标签的Pod进行管理。但是需要注意的是该网络策略的ingress from是以namespaceSelector的标签进行匹配的,并非podSelector,或者是两者的结合。因为在生产环境中,同一个Namespace下可能会有很多不同类型、不同标签的Pod,并且它们可能并不具有一个相同的标签,所以如果通过podSelector进行选择,可能会比较麻烦,因为Pod一旦创建,对其标签的修改是很不方便的(apps/v1一旦创建就不可修改)。而使用namespaceSelector另一个好处是,可以很方便的对某个Namespace下的Pod进行管控,直接给指定Namespace添加标签即可,当然,如果需要更细粒度的管控,也可以结合podSelector使用。
接下来创建该NetworkPolicy:
[root@k8s-master01 ~]# kubectl create -f mysql-redis-nw.yaml -n nw-demonetworkpolicy.networking.k8s.io/mysql-np creatednetworkpolicy.networking.k8s.io/redis-np created[root@k8s-master01 ~]# kubectl get networkpolicy -n nw-demoNAME POD-SELECTOR AGEmysql-np app=mysql 18sredis-np app=redis 18s
创建后宿主机和任何Pod都已不能访问该Namespace下的MySQL和Redis:
[root@k8s-master01 ~]# telnet 192.170.21.240 3306Trying 192.170.21.240...^C[root@k8s-master01 ~]# telnet 192.162.55.68 6379Trying 192.162.55.68...^C
在nw-demo命名空间下创建一个用于测试连通性的工具,然后进行测试,也是不能访问该服务的:
[root@k8s-master01 ~]# kubectl run -ti debug-tools --image=registry.cn-beijing.aliyuncs.com/debug-tools:latest -n nw-demoIf you don't see a command prompt, try pressing enter.(09:43 debug-tools:/) (09:43 debug-tools:/) curl 192.170.21.240:3306 --connect-timeout 3curl: (28) Connection timed out after 3001 milliseconds(28 09:44 debug-tools:/) exitexitSession ended, resume using 'kubectl attach debug-tools -c debug-tools -i -t' command when the pod is running
由于之前的from配置的是namespaceSelector,所以如果想要某一个Namespace下的Pod能够访问,直接给该Namespace添加一个NetworkPolicy中配置的标签即可,比如允许nw-demo命名空间下的所以Pod访问该NetworkPolicy隔离的服务:
[root@k8s-master01 ~]# kubectl label ns nw-demo access-nw-mysql-redis=truenamespace/nw-demo labeled
使用nw-demo命名空间下的debug-tools再次测试:
[root@k8s-master01 ~]# kubectlexec-tidebug-tools-nnw-demo--curl 192.170.21.240:33065.7.23 ,)ar\jÿÿ~V7gKo"Zmysql_native_passwordotpacketsoutoforder
此时nw-demo下的Pod已经可以访问MySQL和Redis,可以对其他Namespace下的Pod进行测试,比如在default命名空间进行测试:
[root@k8s-master01 ~]# kubectl run -ti debug-tools --image=registry.cn-beijing.aliyuncs.com/debug-tools:latest -n default If you don't see a command prompt, try pressing enter.(09:46 debug-tools:/) (09:46 debug-tools:/) curl 192.170.21.240:3306 --connect-timeout 3curl: (28) Connection timed out after 3001 milliseconds(28 09:47 debug-tools:/) exitexitSession ended, resume using 'kubectl attach debug-tools -c debug-tools -i -t' command when the pod is running
可以看到此时default命名空间下的Pod并不能访问nw-demo的服务,如果想要MySQL和Redis对default命名空间开放,只需要添加一个access-nw-mysql-redis=true的标签即可。
相对于传统架构,对中间件的访问限制,在Kubernetes中实现同样的效果,可能配置更加方便且易于管理。
5
NetworkPolicy示例2:服务发布限制
一般情况下,一个项目的服务发布,会把域名的根路径指向前端应用,接口路径指向对应的网关或者微服务。假设现在创建一个Nginx服务充当前端页面,配置网络策略只让Ingress Controller访问该应用:
# 创建应用[root@k8s-master01 ~]# kubectl create deploy nginx --image=registry.cn-beijing.aliyuncs.com/nginx:latest -n nw-demodeployment.apps/nginx created# 暴露服务[root@k8s-master01 ~]# kubectl expose deploy nginx -n nw-demo --port=80service/nginx exposed# 查看创建的服务[root@k8s-master01 ~]# kubectl get svc,po -n nw-demo -l app=nginxNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGEservice/nginx ClusterIP 10.102.194.2 <none> 80/TCP 31sNAME READY STATUS RESTARTS AGEpod/nginx-65bf9b498b-smwhg 1/1 Running 0 61s
在没有任何网络策略的情况下,该服务可以被任何Pod访问:
[root@k8s-master01 ~]# kubectl exec debug-tools -- curl -Is nginx.nw-demoHTTP/1.1 200 OKServer: nginx/1.19.6Date: Sun, 10 Apr 2022 10:13:15 GMTContent-Type: text/htmlContent-Length: 612Last-Modified: Tue, 15 Dec 2020 13:59:38 GMTConnection: keep-aliveETag: "5fd8c14a-264"Accept-Ranges: bytes[root@k8s-master01 ~]# kubectl -n nw-demo exec debug-tools -- curl -Is nginx.nw-demoHTTP/1.1 200 OKServer: nginx/1.19.6Date: Sun, 10 Apr 2022 10:13:34 GMTContent-Type: text/htmlContent-Length: 612Last-Modified: Tue, 15 Dec 2020 13:59:38 GMTConnection: keep-aliveETag: "5fd8c14a-264"Accept-Ranges: bytes
配置网络策略只让Ingress Controller访问该服务:
[root@k8s-master01~]# vim nginx-nw.yamlapiVersion: networking.k8s.io/v1kind: NetworkPolicymetadata:name: nginx-npnamespace: nw-demospec:podSelector: matchLabels: app: nginxpolicyTypes: -Ingressingress: -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生效,只让具有app.kubernetes.io/name=ingress-nginx标签的Namespace下的具有app.kubernetes.io/name=ingress-nginx标签的Pod访问,同时还有一个允许当前Namespace下的Pod访问的策略- podSelector: {} 。需要注意的是,读者的集群中已经安装了相关Ingress,并且需要根据实际的标签进行更改,如果读者集群目前并未安装Ingress,可以完成Ingress的学习后,再来测试该实验。
创建该NetworkPolicy,并测试连通性:
[root@k8s-master01 ~]# kubectl create -f nginx-nw.yaml networkpolicy.networking.k8s.io/nginx-np created# 没有被允许的命名空间无法访问[root@k8s-master01 ~]# kubectl exec debug-tools -- curl --connect-timeout 2 -Is nginx.nw-democommand terminated withexit code 28# 允许范围内的Pod可以访问[root@k8s-master01 ~]# kubectl exec debug-tools -n nw-demo -- curl --connect-timeout 2 -Is nginx.nw-demoHTTP/1.1200 OKServer: nginx/1.19.6Date: Sun, 10 Apr 202210:16:54 GMTContent-Type: text/htmlContent-Length: 612Last-Modified: Tue, 15Dec202013:59:38 GMTConnection: keep-aliveETag: "5fd8c14a-264"Accept-Ranges: bytes[root@k8s-master01 ~]# kubectl get ns -l app.kubernetes.io/name=ingress-nginxNAME STATUS AGEingress-nginx Active 9d[root@k8s-master01 ~]# kubectl get pod -n ingress-nginx -l app.kubernetes.io/name=ingress-nginxNAME READY STATUS RESTARTS AGEingress-nginx-controller-79cd59dd94-qfq5n 1/1 Running 0 4m14s[root@k8s-master01 ~]# kubectl label node k8s-master02.example.local ingress=true[root@k8s-master01 ~]# kubectl get pod -n ingress-nginx -o wideNAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATESingress-nginx-controller-7f9fbd446-58bw2 1/1 Running 0 102s 172.31.3.112 k8s-node02.example.local <none> <none>ingress-nginx-controller-7f9fbd446-hmhng 1/1 Running 0 45s 172.31.3.102 k8s-master02.example.local <none> <none>ingress-nginx-controller-7f9fbd446-ps5hf 1/1 Running 0 45s 172.31.3.113 k8s-node03.example.local <none> <none>#在node上测试有问题,标签打到master上测试正常[root@k8s-master01 ~]# kubectl exec ingress-nginx-controller-7f9fbd446-hmhng -n ingress-nginx -- curl -Is nginx.nw-demoHTTP/1.1200 OKServer: nginx/1.19.6Date: Sun, 10 Apr 202211:27:24 GMTContent-Type: text/htmlContent-Length: 612Last-Modified: Tue, 15Dec202013:59:38 GMTConnection: keep-aliveETag: "5fd8c14a-264"Accept-Ranges: bytes
可以看到Ingress Controller和该Namespace下的Pod可以访问,其他Namespace不可以访问。此时可以创建一个Ingress,然后用域名测试:
[root@k8s-master01 ~]# kubectl create ingress nginx --rule="testnp.com/*=nginx:80" -n nw-demoingress.networking.k8s.io/nginx created[root@k8s-master01 ~]# curl -H "Host:testnp.com" 172.31.3.102<head><title>404 Not Found</title></head><body><center><h1>404 Not Found</h1></center><hr><center>nginx</center></body></html>