Kubernetes: NetworkPolicy 的实践应用
一、Network Policy 是什么,在云原生领域有和作用
Network Policy 是 Kubernetes 官方提出来的一种网络策略的规范,用户通过编写符合对应规范的规则来控制 k8s 集群内 L3 和 L4 层的网络流量。
NetworkPolicy 主要的功能就是实现在云原生领域的容器网络管控它给用户提供了几点最常用的能力
- 容器网络的出入网管控,最常用的就是控制业务的对公网访问
- 不同业务直接的网络资源隔离,随着集群规模的变大,不可避免的会存在多租户的场景,不同租户之间的网络隔离 network policy 可以做到
- 云原生领域的安全演练能力,例如模拟某些网络异常,可以直接通过NetworkPolicy 功能实现
二、Network Policy 和容器网络关系
云原生领域经过多年的发展,上层的应用各种服务层出不穷,作为云原生底层的基础服务,容器网络也面临更多安全和网络管控的需求。比如在大规模集群中不同业务希望互相禁止访问,一些内部服务希望禁止公网访问或被公网访问。kubernetes 对应这些类似需求给出的解决方案就是 Network Policy 。Network Policy 的实现依赖于容器网络,但是因为不同的 CNI 技术方案和组网方式的不同,Network Policy 实现方式也是不同的,所以 kubernetes 官方只给出了一个 Network Policy 的格式规范,并没有给出具体的实现方案,示例如下:
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 原生的Network Policy
kubernetes 原生的 Network Policy 功能相对简单只支持 namespace 级别,功能且相对简单,支持 L3 和 L4 基础的网络策略。
3.1 匹配规则
- Ingress : 顾名思义,外部流量流入 Pod
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
Ingress
通过 From 关键字来配置规则,可以通过ipBlock
、namespaceSelector
、podSelector
、Ports
来选择该策略所使用的对象。
podSelector:此选择器将在与 NetworkPolicy 相同的 namespace 中选择特定的 Pod,应将其允许作为入站流量来源或出站流量目的地。
namespaceSelector:此选择器将选择特定的 namespace,应将所有 Pod 用作其入站流量来源或出站流量目的地。
namespaceSelector 和 podSelector:一个指定 namespaceSelector
和 podSelector
的 to
/from
条目选择特定 namespace 中的特定 Pod。
Egress
: Pod 的出向流量
Egress 匹配策略和 ingress 基本一致,只是from
字段改为to
3.2 官方案例
- 默认拒绝所有的入站流量,在 default namespace 下生效
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
spec:
podSelector: {}
policyTypes:
- Ingress
- 允许所有入站流量
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-all-ingress
spec:
podSelector: {}
ingress:
- {}
policyTypes:
- Ingress
- 默认拒绝所有的出站流量
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-egress
spec:
podSelector: {}
policyTypes:
- Egress
- 允许所有出站
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-all-egress
spec:
podSelector: {}
egress:
- {}
policyTypes:
- Egress
- 默认拒绝所有的入站和出站流量
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
四、开源组件拓展Network Policy
kubernetes 提供的原生的Network Policy 所能实现的功能相对较少,很难满足日益复杂的容器网络管控需求,所以各家CNI 基本上都有自己标准的Network Policy
4.1 Calico Network Policy
Calico 的Network Policy 相较于Kubernetes 原生的Network Policy 有两点需要特别注意,第一个是增加了集群级别的网络策略,第二个是对于流量的处理处理正常的allow 和 deny 还多了一个log 用来记录被network policy 匹配到的流量 注:这里只介绍Calico 开源版本,企业版本的calico 不在本文的介绍范围内。
- 集群级别的网络策略 相较于k8s 原生的namespace 级别的网络策略,集群级别网络策略是一种非namespace 资源,可以应用于独立于命名空间的任何类型的端点(pod、VM、主机接口)。
apiVersion: projectcalico.org/v3
kind: GlobalNetworkPolicy
metadata:
name: allow-tcp-port-6379
- 标准的Calico Network Policy 在以下示例中,传入到应用程序的 TCP 流量被拒绝,并且每个连接尝试都记录到系统日志中
apiVersion: projectcalico.org/v3
kind: NetworkPolicy
metadata:
name: allow-tcp-6379
namespace: production
spec:
selector: role == 'database'
types:
- Ingress
- Egress
ingress:
- action: Log
protocol: TCP
source:
selector: role == 'frontend'
- action: Deny
protocol: TCP
source:
selector: role == 'frontend'
4.2 Cilium Network Policy
Cilium CNI 提供的Network Policy 的功能相对较多,它不仅支持3层、四层、7层,同时还支持通过自定义实体身份来选择Network Policy 生效位置
4.2.1 三层Network Policy
****Cilium 的 Network Policy 支持多种模式,这里只选两个经典的来介绍
4.2.1.1 基于 ipBlock
此示例显示所有含 app=myservice 的 Pod 访问 20.1.1.1/32 和除了 10.96.0.0/12 所有 10.0.0.0/8 范围的地址
apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
name: "cidr-rule"
spec:
endpointSelector:
matchLabels:
app: myService
egress:
- toCIDR:
- 20.1.1.1/32
- toCIDRSet:
- cidr: 10.0.0.0/8
except:
- 10.96.0.0/12
4.2.1.2 基于 DNS 下面的示例表示,允许 default namespace 下标签为 app=test-app 的 pod 向 kube-system namespace 下标签为 k8s:k8s-app=kube-dns 的 pod 的 53 端口发送请求”my-remote-service.com“的域名解析请求
apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
name: "to-fqdn"
spec:
endpointSelector:
matchLabels:
app: test-app
egress:
- toEndpoints:
- matchLabels:
"k8s:io.kubernetes.pod.namespace": kube-system
"k8s:k8s-app": kube-dns
toPorts:
- ports:
- port: "53"
protocol: ANY
rules:
dns:
- matchPattern: "*"
- toFQDNs:
- matchName: "my-remote-service.com"
4.2.2 四层的Cilium Network Policy
四层的网络策略和 K8S 原生的规范除个别字段名称之外基本一致,所以只给出一个基本的例子 以下规则限制所有具有app=myService
标签的 pod 只能使用端口 80-444 上的 TCP 向任何第 3 层目的地发送数据包:
apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
name: "l4-port-range-rule"
spec:
endpointSelector:
matchLabels:
app: myService
egress:
- toPorts:
- ports:
- port: "80"
endPort: 444
protocol: TCP
4.2.3 七层的Cilium Network Policy
7层的Network Policy 目前支持的有 HTTP 协议,Kafka 相关以及DNS相关
// L7Rules is a union of port level rule types. Mixing of different port
// level rule types is disallowed, so exactly one of the following must be set.
// If none are specified, then no additional port level rules are applied.
type L7Rules struct {
// HTTP specific rules.
//
// +optional
HTTP []PortRuleHTTP `json:"http,omitempty"`
// Kafka-specific rules.
//
// +optional
Kafka []PortRuleKafka `json:"kafka,omitempty"`
// DNS-specific rules.
//
// +optional
DNS []PortRuleDNS `json:"dns,omitempty"`
}
4.2.3.1 HTTP 相关的L7 Network Policy
http 相关的NetworkPolicy 可以匹配path、method、host、headers 等信息,使用起来非常灵活
- 基于 url& path
下面的例子允许从带有“env=prod”标签的 Pod 到带有“app=service”标签的 Pod 的 GET 请求访问 URL /public,但对其他任何 URL 或使用其他方法的请求将被拒绝。除端口 80 以外的其他端口上的请求将被丢
apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
name: "rule1"
spec:
description: "Allow HTTP GET /public from env=prod to app=service"
endpointSelector:
matchLabels:
app: service
ingress:
- fromEndpoints:
- matchLabels:
env: prod
toPorts:
- ports:
- port: "80"
protocol: TCP
rules:
http:
- method: "GET"
path: "/public"
- 基于 path & method & header
下面的例子限制了所有带有标签“app=myService”的 Pod 只能通过 TCP 协议接收端口 80 上的数据包。在使用这个端口进行通信时,仅允许以下两个 API :GET /path1 和 PUT /path2,并且必须在 HTTP 头“X-My-Header”中设置值为 true。
apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
name: "l7-rule"
spec:
endpointSelector:
matchLabels:
app: myService
ingress:
- toPorts:
- ports:
- port: '80'
protocol: TCP
rules:
http:
- method: GET
path: "/path1$"
- method: PUT
path: "/path2$"
headers:
- 'X-My-Header: true'
4.2.3.2 KafKa相关的L7 Network Policy(beta) • 允许使用“produce” role 创建 topic
apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
name: "rule1"
spec:
description: "enable empire-hq to produce to empire-announce and deathstar-plans"
endpointSelector:
matchLabels:
app: kafka
ingress:
- fromEndpoints:
- matchLabels:
app: empire-hq
toPorts:
- ports:
- port: "9092"
protocol: TCP
rules:
kafka:
- role: "produce"
topic: "deathstar-plans"
- role: "produce"
topic: "empire-announce"
• 允许使用 apiKeys 生成 topic
apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
name: "rule1"
spec:
description: "enable empire-hq to produce to empire-announce and deathstar-plans"
endpointSelector:
matchLabels:
app: kafka
ingress:
- fromEndpoints:
- matchLabels:
app: empire-hq
toPorts:
- ports:
- port: "9092"
protocol: TCP
rules:
kafka:
- apiKey: "apiversions"
- apiKey: "metadata"
- apiKey: "produce"
topic: "deathstar-plans"
- apiKey: "produce"
topic: "empire-announce"
4.2.3.3 DNS 相关的L7 Network Policy
下面的示例表示,允许 default namespace 下标签为 org=alliance 的 pod 向 kube-system namespace 下标签为 k8s:k8s-app=kube-dns 的 pod 的 53 端口发送请求”my-remote-service.com“的域名解析请求,允许的域名为 cilium.io *. cilium.io .api.cilium.io 同时允许 cilium.io sub.cilium.io service1.api.cilium.io specialservice.api.cilium.io 的外部请求
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: "tofqdn-dns-visibility"
spec:
endpointSelector:
matchLabels:
any:org: alliance
egress:
- toEndpoints:
- matchLabels:
"k8s:io.kubernetes.pod.namespace": kube-system
"k8s:k8s-app": kube-dns
toPorts:
- ports:
- port: "53"
protocol: ANY
rules:
dns:
- matchName: "cilium.io"
- matchPattern: "*.cilium.io"
- matchPattern: "*.api.cilium.io"
- toFQDNs:
- matchName: "cilium.io"
- matchName: "sub.cilium.io"
- matchName: "service1.api.cilium.io"
- matchPattern: "special*service.api.cilium.io"
toPorts:
- ports:
- port: "80"
protocol: TCP
4.2.4 集群联邦(Cluster Federation)的Cilium Network Policy
kubernetes 在 1.8 版本起就声称单个集群最多可支持 5000 个 node 和 15 万个 Pod,但是实际上我们关注近几年互联网上因为服务崩溃而上的几次热搜就应该明白,大规模集群一旦底层组件出现异常,带来的影响是非常大的。这个时候我们就需要用到集群联邦(Cluster Federation)。有了集群联邦,同样就需要考虑联邦级别的 Network Policy ,恰巧 Cilium 就提供了对应的这一能力。 以下策略说明如何允许特定 pod 在两个集群之间进行通信。
apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
name: "allow-cross-cluster"
spec:
description: "Allow x-wing in cluster1 to contact rebel-base in cluster2"
endpointSelector:
matchLabels:
name: x-wing
io.cilium.k8s.policy.cluster: cluster1
egress:
- toEndpoints:
- matchLabels:
name: rebel-base
io.cilium.k8s.policy.cluster: cluster2