k8s NetworkPolicy 实战
NetworkPolicy 是一种以应用为中心的结构,允许设置如何允许 Pod 与网络上的各类网络“实体” (这里使用实体以避免过度使用诸如“端点”和“服务”这类常用术语, 这些术语在 Kubernetes 中有特定含义)通信。
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 资源对象而没有控制器来使它生效的话,是没有任何作用的
- 高版本 kubectl
需要安装 kubectl 高版本,用于创建 ingress 等资源
docker create --entrypoint=sh --name kubectl bitnami/kubectl:1.24.8
docker cp kubectl:/opt/bitnami/kubectl/bin/kubectl ./kubectl
- 需要安装 kubectl 插件列表如下
- ns
- ingress-nginx
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 配置详解
说明:除非选择支持网络策略的网络解决方案,否则将发送到 API 服务器没有任何效果
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
-
必需字段:与所有其他的 Kubernetes 配置一样,NetworkPolicy 需要 apiVersion、 kind 和 metadata 字段
-
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 端口的连接
在配置网络策略时,有很多细节需要注意,比如上述的示例中,一段关于 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
示例
首先创建该项目所用的 Namespace
> kubectl create ns nw-demo
namespace/nw-demo created
切换默认命名空间
kubectl ns nw-demo
隔离中间件服务
- 有一个项目,它有自己数据库 MySQL 和缓存 Redis 中间件,只希望这个项目的应用能够访问该中间件
- 假如有一个项目需要通过 Ingress 进行对外发布,想要除了 Ingress 外,其他任何 Namespace 下的 Pod 都不能访问该项目。
假设有一个项目叫 nw-demo,里面部署了三个微服务,分别是 MySQL、Redis 和 Nginx。现需要对 MySQL、Redis、Nginx 进行隔离,分别实现如下效果:
- MySQL、Redis 只能被该 Namespace 下的 Pod 访问;
- Nginx 可以被
ingress-nginx
命名空间下的 Pod 和该 Namespace 下的 Pod 访问;
创建 MySQL 服务,MySQL 以容器启动时,必须配置 root 的密码,或者设置密码为空,所以需要设置一个 MYSQL_ROOT_PASSWORD
的变量:
kubectl create deploy mysql --image=docker.io/library/mysql:8.0.31
kubectl set env deploy/mysql MYSQL_ROOT_PASSWORD=mysql
创建 redis 服务
kubectl create deploy redis --image=docker.io/library/redis:7.0.5-bullseye
确认容器是否启动:
> kubectl get po -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
mysql-664f9868b-5s6g9 1/1 Running 0 14s 10.160.199.5 dev-chhli <none> <none>
redis-6466b54d6d-8zpt6 0/1 ContainerCreating 0 69s 10.160.199.10 dev-chhli <none> <none>
在没有配置任何网络策略时,测试下网络的连通性,可以在任意 Kubernetes 节点上执行 telnet 命令:
# 进行测试的IP和Pod名字可能与实际测试的不一致,需要与实际情况为准
> telnet 10.160.199.5 3306
Trying 10.160.199.5...
Connected to 10.160.199.5.
Escape character is '^]'.
> telnet 10.160.199.10 6379
Trying 10.160.199.10...
Connected to 10.160.199.10.
Escape character is '^]'.
然后根据标签配置网络策略,本示例的配置将MySQL和Redis进行了拆分,配置了两个网络策略,当然也可以给两个Pod配置一个相同的标签,这样就可以使用同一个网络策略进行限制
但是在生产环境中,并不推荐使用同一个网络策略,因为有时候需要更细粒度的策略,同一个网络策略可能会有局限性,也会导致配置失败,所以本示例采用分开的网络策略进行配置:
mysql-nw.yaml
mysql-np 是对具有app=mysql
标签的 Pod 进行管理
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
redis-nw.yaml
redis-np 是对具有app=redis
标签的 Pod 进行管理
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
但是需要注意的是该网络策略的 ingress from 是以 namespaceSelector 的标签进行匹配的,并非 podSelector,或者是两者的结合。
因为在生产环境中,同一个 Namespace 下可能会有很多不同类型、不同标签的 Pod,并且它们可能并不具有一个相同的标签,所以如果通过 podSelector 进行选择,可能会比较麻烦,因为 Pod 一旦创建,对其标签的修改是很不方便的(apps/v1 一旦创建就不可修改)
而使用 namespaceSelector 另一个好处是,可以很方便的对某个 Namespace 下的 Pod 进行管控,直接给指定 Namespace 添加标签即可,当然,如果需要更细粒度的管控,也可以结合 podSelector 使用
创建后宿主机和任何 Pod 都已不能访问该 Namespace 下的 MySQL 和 Redis
> telnet 10.160.199.5 3306
Trying 10.160.199.5...
> telnet 10.160.199.10 6379
Trying 10.160.199.10...
在 nw-demo 命名空间下创建一个用于测试连通性的工具,然后进行测试,也是不能访问该服务的:
kubectl run -ti --rm debug-tools --image=docker.io/library/busybox:1.35
> telnet 10.160.199.5 3306
由于之前的 from 配置的是 namespaceSelector,所以如果想要某一个 Namespace 下的 Pod 能够访问,直接给该 Namespace 添加一个 NetworkPolicy 中配置的标签即可,比如允许 nw-demo
命名空间下的所以 Pod 访问该 NetworkPolicy 隔离的服务:
> kubectl label ns nw-demo access-nw-mysql-redis=true
namespace/nw-demo labeled
使用 nw-demo
命名空间下的 debug-tools 再次测试:
> telnet 10.160.199.5 3306
此时 nw-demo
下的 Pod 已经可以访问 MySQL 和 Redis,可以对其他 Namespace 下的 Pod 进行测试,比如在 default 命名空间进行测试:
kubectl run -ti --rm debug-tools --image=docker.io/library/busybox:1.35 -n default
可以看到此时 default 命名空间下的 Pod 并不能访问 nw-demo
的服务,如果想要 MySQL 和 Redis 对 default 命名空间开放,只需要添加一个 access-nw-mysql-redis=true
的标签即可。
相对于传统架构,对中间件的访问限制,在 Kubernetes 中实现同样的效果,可能配置更加方便且易于管理
服务发布限制
一般情况下,一个项目的服务发布,会把域名的根路径指向前端应用,接口路径指向对应的网关或者微服务。
假设现在创建一个 Nginx 服务充当前端页面,配置网络策略只让 Ingress Controller 访问该应用:
创建应用
kubectl create deploy nginx --image=docker.io/library/nginx:1.23.2-alpine
暴露服务
kubectl expose deploy nginx --port=80
查看创建的服务
kubectl get svc,po -l app=nginx
在没有任何网络策略的情况下,该服务可以被任何 Pod 访问:
# 统一命名空间
kubectl run -ti --rm debug-tools --image=docker.io/curlimages/curl:7.86.0 -- sh
> curl -kIs nginx.nw-demo
# 在 default 命名空间测试
kubectl run -ti --rm debug-tools -n default --image=docker.io/curlimages/curl:7.86.0 -- sh
> curl -kIs nginx.nw-demo
配置网络策略只让 Ingress Controller 访问该服务:
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:
name: ingress-nginx
podSelector:
matchLabels:
"app.kubernetes.io/name": ingress-nginx
- podSelector: {}
ports:
- protocol: TCP
port: 80
注意: 该条策略对具有 app=nginx
标签的Pod生效,只让具有 name=ingress-nginx
标签的 Namespace 下的具有 app.kubernetes.io/name=ingress-nginx
标签的 Pod 访问(需要根据实际的 Ingress 标签进行更改),同时还有一个允许当前 Namespace 下的 Pod 访问的策略 - podSelector: {}
。
测试在没有被允许的命名空间无法访问
# 在 default 命名空间测试
kubectl run -i -t --rm debug-tools -n default --image=docker.io/curlimages/curl:7.86.0 -- sh
/ $ curl --max-time 10 -kIs nginx.nw-demo
/ $ echo $?
28
测试在允许范围内的 Pod 可以访问
# 在当前命名空间测试
kubectl run -ti --rm debug-tools --image=docker.io/curlimages/curl:7.86.0 -- sh
/ $ curl --max-time 10 -kIs nginx.nw-demo
/ $ echo $?
HTTP/1.1 200 OK
Server: nginx/1.23.2
Date: Fri, 09 Dec 2022 08:00:33 GMT
Content-Type: text/html
Content-Length: 615
Last-Modified: Wed, 19 Oct 2022 10:28:53 GMT
Connection: keep-alive
ETag: "634fd165-267"
Accept-Ranges: bytes
在 ingress 的 pod 中测试
kubectl ingress-nginx -n ingress-nginx exec -i --deployment ingress-nginx-controller-controller -- curl --max-time 10 -kIs nginx.nw-demo
可以看到 Ingress Controller 和该 Namespace 下的 Pod 可以访问,其他 Namespace 不可以访问。
此时可以创建一个 Ingress,然后用域名测试:
kubectl create ingress nginx --rule="testnp.com/*=nginx:80"
# 查看 ingress 列表
kubectl ingress-nginx ingresses
测试访问
# 以实际的 ingress 访问 ip 为准
curl -H "Host:testnp.com" http://10.24.2.14:80
更多推荐
所有评论(0)