k8s 网络策略

发布时间 2023-08-29 16:23:12作者: 小吉猫

网络策略介绍

如果你希望在 IP 地址或端口层面(OSI 第 3 层或第 4 层)控制网络流量, 则你可以考虑为集群中特定应用使用 Kubernetes 网络策略(NetworkPolicy)。 NetworkPolicy 是一种以应用为中心的结构,允许你设置如何允许 Pod 与网络上的各类网络“实体” (我们这里使用实体以避免过度使用诸如“端点”和“服务”这类常用术语, 这些术语在 Kubernetes 中有特定含义)通信。 NetworkPolicy 适用于一端或两端与 Pod 的连接,与其他连接无关。
在定义基于 Pod 或名字空间的 NetworkPolicy 时, 你会使用标签选择器来设定哪些流量可以进入或离开与该算符匹配的 Pod。 另外,当创建基于 IP 的 NetworkPolicy 时,我们基于 IP Block(CIDR 范围)来定义策略。

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   # 资源隶属的API群组及版本号
kind: NetworkPolicy                # 资源类型的名称
metadata:                          # 资源元数据
  name <string>                    # 资源名称标识
  namespace <string>               # NetworkPolicy是名称空间级别的资源
spec:                              # 期望的状态
  podSelector <Object>             # 当前规则生效的同一名称空间中的一组目标Pod对象,必选字段。空值表示当前名称空间中的所有Pod资源
  policyTypes <[]string>           # Ingress表示生效ingress字段;Egress表示生效egress字段,同时提供表示二者均有效
  ingress <[]Object>               # 入站流量源端点对象列表,即白名单,空值表示“所有”
    - from <[]Object>              # 具体的端点对象列表,空值表示所有合法端点
      - ipBlock <Object>           # IP地址块范围内的端点,不能与另外两个字段同时使用
          cidr <string>            # cidr 是表示 IPBlock 的字符串,有效示例为“192.168.1.0/24”或“2001:db8::/64”
          except <string>          # except 是不应包含在 IPBlock 中的 CIDR 片段 有效示例为“192.168.1.0/24”或“2001:db8::/64” 超出 cidr 范围的值将被拒绝
      - namespaceSelector <Object> # 匹配的名称空间内的端点
      - podSelector <Object>       # 由Pod标签选择器匹配到的端点,空值表示<none>
      ports <[]Object>             # 具体的端口对象列表,空值表示所有合法端口
        - protocol <string>        # 传输层协议名称,TCP、UDP 或 SCTP。如果没有指定,该字段默认为 TCP。
          port <number>            # 端口号或在container上定义的端口名称,未定义时匹配所有端口。
          endPort <number>         # 端口范围。endPort 字段必须等于或者大于 port 字段的值。只有在定义了 port 时才能定义 endPort。
  engress <[]Object>               # 出站流量目标端点对象列表,即白名单,空值表示“所有”
    - to <[]Object>                # 具体的端点对象列表,空值表示所有合法端点,格式同ingres.from;
      - ipBlock <Object>           # IP地址块范围内的端点,不能与另外两个字段同时使用
          cidr <string>            # cidr 是表示 IPBlock 的字符串,有效示例为“192.168.1.0/24”或“2001:db8::/64”
          except <string>          # except 是不应包含在 IPBlock 中的 CIDR 片段 有效示例为“192.168.1.0/24”或“2001:db8::/64” 超出 cidr 范围的值将被拒绝
      - namespaceSelector <Object> # 匹配的名称空间内的端点
      - podSelector <Object>       # 由Pod标签选择器匹配到的端点,空值表示<none>
      ports <[]Object>             # 具体的端口对象列表,空值表示所有合法端口
        - protocol <string>        # 传输层协议名称,TCP、UDP 或 SCTP。如果没有指定,该字段默认为 TCP。
          port <number>            # 端口号或在container上定义的端口名称,未定义时匹配所有端口。
          endPort <number>         # 端口范围。endPort 字段必须等于或者大于 port 字段的值。只有在定义了 port 时才能定义 endPort。

网络策略术语

▪ Pod组:由NetworkPolicy资源通过Pod标签选择器(spec.podSelector)动态选出的一组Pod资源集合,它们也是该网络策略规则管控的目标,可通过macthLabel或matchExpression类型的标签选择器选定。
▪ Egress规则:出站流量的相关规则,负责管控由选定的Pod组发往其他网络端点的流量,可由流量的目标网络端点(spec.egress.to)和端口(spec.egress.ports)来定义。
▪ Ingress规则:入站流量的相关规则,负责管控可由选定的Pod组所接收的流量,它能够由流量发出的源端点(spec.ingress.from)和流量的目标端口(spec.ingress.ports)来定义。
▪ 对端端点(to, from):与选定的Pod组交互的对端主机,它可由CIDR格式的IP地址块(ipBlock)、网络名称空间选择器(namespaceSelector)来匹配名称空间内的所有Pod对象,甚至也可以是由Pod选择器(podSelector)在指定名称空间中选出的一组特定Pod对象等。

选择器 to 和 from 的行为

可以在 ingress 的 from 部分或 egress 的 to 部分中指定四种选择器:

podSelector

此选择器将在与 NetworkPolicy 相同的名字空间中选择特定的 Pod,应将其允许作为入站流量来源或出站流量目的地。

namespaceSelector

此选择器将选择特定的名称空间,应将所有 Pod 用作其入站流量来源或出站流量目的地。

namespaceSelector 和 podSelector

一个指定 namespaceSelector 和 podSelector 的 to/from 条目选择特定名字空间中的特定 Pod。
此策略在 from 数组中仅包含一个元素,只允许来自标有 role=client 的 Pod 且该 Pod 所在的名字空间中标有 user=alice 的连接。
 ...
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          user: alice
      podSelector:
        matchLabels:
          role: client
  ...
此策略它在 from 数组中包含两个元素,允许来自本地名字空间中标有 role=client 的 Pod 的连接,或来自任何名字空间中标有 user=alice 的任何 Pod 的连接。
  ...
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          user: alice
    - podSelector:
        matchLabels:
          role: client
  ...

ipBlock

此选择器将选择特定的 IP CIDR 范围以用作入站流量来源或出站流量目的地。 这些应该是集群外部 IP,因为 Pod IP 存在时间短暂的且随机产生。
集群的入站和出站机制通常需要重写数据包的源 IP 或目标 IP。 在发生这种情况时,不确定在 NetworkPolicy 处理之前还是之后发生, 并且对于网络插件、云提供商、Service 实现等的不同组合,其行为可能会有所不同。

对入站流量而言,这意味着在某些情况下,你可以根据实际的原始源 IP 过滤传入的数据包, 而在其他情况下,NetworkPolicy 所作用的 源IP 则可能是 LoadBalancer 或 Pod 的节点等。

对于出站流量而言,这意味着从 Pod 到被重写为集群外部 IP 的 Service IP 的连接可能会或可能不会受到基于 ipBlock 的策略的约束。

默认策略

默认情况下,如果名称空间中不存在任何策略,则所有进出该名称空间中 Pod 的流量都被允许。

默认拒绝所有入站流量

通过创建选择所有 Pod 但不允许任何进入这些 Pod 的入站流量的 NetworkPolicy 来为namespace创建 “default” 隔离策略。
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-ingress
spec:
  podSelector: {}
  policyTypes:
  - Ingress
这确保即使没有被任何其他 NetworkPolicy 选择的 Pod 仍将被隔离以进行入口。 此策略不影响任何 Pod 的出口隔离。

允许所有入站流量

如果你想允许一个namespace中所有 Pod 的所有入站连接,你可以创建一个明确允许的策略。
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-all-ingress
spec:
  podSelector: {}
  ingress:
  - {}
  policyTypes:
  - Ingress
有了这个策略,任何额外的策略都不会导致到这些 Pod 的任何入站连接被拒绝。 此策略对任何 Pod 的出口隔离没有影响。

默认拒绝所有出站流量

通过创建选择所有容器但不允许来自这些容器的任何出站流量的 NetworkPolicy 来为namespace创建 “default” 隔离策略。
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-egress
spec:
  podSelector: {}
  policyTypes:
  - Egress
此策略可以确保即使没有被其他任何 NetworkPolicy 选择的 Pod 也不会被允许流出流量。 此策略不会更改任何 Pod 的入站流量隔离行为。

允许所有出站流量

如果要允许来自名字空间中所有 Pod 的所有连接, 则可以创建一个明确允许来自该名字空间中 Pod 的所有出站连接的策略。
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-all-egress
spec:
  podSelector: {}
  egress:
  - {}
  policyTypes:
  - Egress
有了这个策略,任何额外的策略都不会导致来自这些 Pod 的任何出站连接被拒绝。 此策略对进入任何 Pod 的隔离没有影响。

默认拒绝所有入站和所有出站流量

可以为namespace创建“默认”策略,以通过在该名字空间中创建以下 NetworkPolicy 来阻止所有入站和出站流量。
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  - Egress
此策略可以确保即使没有被其他任何 NetworkPolicy 选择的 Pod 也不会被允许入站或出站流量。

针对某个端口范围

在编写 NetworkPolicy 时,你可以针对一个端口范围而不是某个固定端口。这一目的可以通过使用 endPort 字段来实现,如下例所示:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: multi-port-egress
  namespace: default
spec:
  podSelector:
    matchLabels:
      role: db
  policyTypes:
    - Egress
  egress:
    - to:
        - ipBlock:
            cidr: 10.0.0.0/24
      ports:
        - protocol: TCP
          port: 32000
          endPort: 32768
上面的规则允许名称空间 default 中所有带有标签 role=db 的 Pod 使用 TCP 协议与 10.0.0.0/24 范围内的 IP 通信,只要目标端口介于 32000 和 32768 之间就可以。
使用此字段时存在以下限制:

  1. endPort 字段必须等于或者大于 port 字段的值。
  2. 只有在定义了 port 时才能定义 endPort。
  3. 两个字段的设置值都只能是数字。

NetworkPolicy 局限性

NetworkPolicy API 还无法实现以下面的场景。

1. 强制集群内部流量经过某公用网关(这种场景最好通过服务网格或其他代理来实现);
2. 与 TLS 相关的场景(考虑使用服务网格或者 Ingress 控制器);
3. 特定于节点的策略(您可以对这些策略使用 CIDR 表示法,但不能专门通过节点的 Kubernetes 身份来定位节点);
4. 基于名字来选择服务(不过,你可以使用 标签 来选择目标 Pod 或名字空间,这也通常是一种可靠的替代方案);
5. 创建或管理由第三方来实际完成的“策略请求”;
6. 实现适用于所有名字空间或 Pods 的默认策略(某些第三方 Kubernetes 发行版本或项目可以做到这点);
7. 高级的策略查询或者可达性相关工具 ;
8. 生成网络安全事件日志的能力(例如,被阻塞或接收的连接请求);
9. 显式地拒绝策略的能力(目前,NetworkPolicy 的模型默认采用拒绝操作, 其唯一的能力是添加允许策略);
10. 禁止本地回路或指向宿主的网络流量(Pod 目前无法阻塞 localhost 访问, 它们也无法禁止来自所在节点的访问请求)。

管控入站流量示例

NetworkPolicy资源将dev名称空间中满足标签选择器app=demoapp的所有Pod对象定义为Pod组,通过Ingress规则定义了该Pod组上入站流量规则。
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: demoapp-ingress
namespace: dev # 网络策略生效的名称空间
spec:
  podSelector:                 # 定义本地Pod组的标签选择器
    matchLabels:
      app: demoapp
  policyTypes: ["Ingress"]     # 仅生效Ingress规则
  ingress:
    - from:                    # 规则1:可访问Pod组上任意端口的流量源
      - namespaceSelector:     # 流量源之一:指定名称空间中的所有端点
          matchExpressions:
            - key: name
              operator: In
              values: [dev, kube-system, logs, monitoring, kubernetes-dashboard]
      - ipBlock:               # 流量源之二:指定网络地址范围内的所有端点
          cidr: 10.244.0.0/24
    - from:                    # 规则2:可访问Pod组的80端口的流量源
      - namespaceSelector:     # 流量源,除default名称空间之外的其他所有名称空间中的端点
          matchExpressions:
            - {key: name, operator: NotIn, values: [default]}
      ports:
        - protocol: TCP
          port: 80

管控出站流量示例

NetworkPolicy资源为匹配标签app=demoapp的Pod组通过Egress规则限制了可外发请求流量的白名单,它仅能访问指定端点的特定服务端口。
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: demoapp-egress
  namespace: dev
spec:
  podSelector:                 # 定义本地Pod组的标签选择器
    matchLabels:
      app: demoapp
  policyTypes: ["Egress"]      # 仅生效Egress规则
    egress:
      - to:                    # 规则1:仅生效于UDP协议的53号端口;不限制流量目标
        ports:
          - protocol: UDP
            port: 53
      - to:                    # 规则2:仅生效于TCP协议的6379端口
        - podSelector:         # 流量目标:当前名称空间中匹配指定标签的Pod对象
            matchLabels:
              app: redis       # 访问redis数据存储服务
         ports:
           - protocol: TCP
             port: 6379
      - to:                    # 规则3:仅生效于TCP协议的80端口
        - podSelector:         # 流量目标:当前名称空间中匹配指定标签的Pod对象
            matchLabels:
              app: demoapp     # 同一组Pod内彼此间可互相访问
        ports:
          - protocol: TCP
            port: 80

按标签选择多个命名空间示例

为stage名称空间创建了一个名为default的NetworkPolicy资源。
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default
  namespace: stage
spec:
  podSelector: {}                        # 当前名称空间中的所有Pod对象
  policyTypes: ["Ingress", "Egress"]     # Ingress和Egress规则同时生效
    ingress:
      - from:                            # 入站规则1:开放所有端口
        - namespaceSelector:             # 流量源:来自指定名称空间中的所有源端点
            matchExpressions:
            - key: name
              operator: In
              values: [stage,kube-system,logs,monitoring,kubernetes-dashboard]
    egress:
      - to:                              # 出站规则1:开放对任意外部端点上UDP协议53端口的访问
        ports:
          - protocol: UDP
            port: 53
      - to:                              # 出站规则2:仅生效于TCP协议的443端口
        - namespaceSelector:             # 流量目标:指定名称空间内的指定Pod对象
            matchLabels:
              name: kube-system
          podSelector:
            matchLabels:
              omponent: kube-apiserver
         ports:                          # 端口列表
           - protocol: TCP
             port: 443
      - to:                              # 出站规则3:生效的所有端口
        - namespaceSelector:             # 流量目标:当前名称空间中的所有端点
            matchLabels:
              name: stage

管控入站和出站流量示例

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
该网络策略示例:

1. 隔离 default 名字空间下 role=db 的 Pod (如果它们不是已经被隔离的话)。

2. (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)
3. (Egress 规则)允许 default 名字空间中任何带有标签 role=db 的 Pod 到 CIDR 10.0.0.0/24 下 5978 TCP 端口的连接。

参考文档

https://kubernetes.io/docs/concepts/services-networking/network-policies/