另一篇讲述的文章地址:
NetworkPolicy网络策略以及举例说明 https://www.cnblogs.com/sanduzxcvbnm/p/14779916.html
Kubernetes网络模型及CNI插件
Kubernetes设计了一种网络模型,它要求所有容器都能够通过一个扁平的网络平面直接进行通信(在同一IP网络中),无论它们是否运行于集群中的同一节点。不过,在Kubernetes集群中,IP地址分配是以Pod对象为单位,而非容器,同一Pod内的所有容器共享同一网络名称空间。
Docker容器的网络模型
Docker容器网络的原始模型主要有三种:Bridge(桥接)、Host(主机)及Container(容器)。
- Bridge模型借助于虚拟网桥设备为容器建立网络连接
- Host模型则设定容器直接共享使用节点主机的网络名称空间
- Container模型则是指多个容器共享同一个网络名称空间,从而彼此之间能够以本地通信的方式建立连接
Docker守护进程首次启动时,它会在当前节点上创建一个名为docker0的虚拟网桥设备,并默认配置其使用172.17.0.0/16网络,该网络是Bridge模型的一种实现,也是创建Docker容器时默认使用的网络模型。
创建Docker容器时,默认有四种网络可供选择使用,从而表现出了四种不同类型的容器
然而,在生产环境中使用容器技术时,跨节点的容器间通信反倒更为常见,可根据网络类型将其实现方式简单划分为如下几种。
- 为各Docker节点创建物理网络桥接接口,设定各节点上的容器使用此桥设备从而直接暴露于物理网络中。
- 配置各节点上的容器直接共享使用其节点的网络名称空间。
- 将容器接入指定的桥设备,如docker0,并设置其借助NAT机制进行通信。
第三种方案是较为流行且默认的解决方案。不过,此种方案的IPAM(IP Address Management)是基于容器主机本地范围进行的,每个节点上的容器都将从同一个网络172.17.0.0/16中获取IP地址,这就意味着不同Docker主机上的容器可能会使用相同的地址,因此它们也就无法直接通信。
解决此问题的通行方式是为NAT。所有接入到此桥设备上的容器均会被NAT隐藏,它们发往Docker主机外部的所有流量都会在执行过源地址转换后发出,并且默认是无法直接接收节点之外的其他主机发来的请求的。若要接入Docker主机外部的流量,则需要事先通过目标地址转换甚至额外的端口转换将其暴露于外部网络中.
故此,传统的解决方案中,多节点上的Docker容器间通信依赖于NAT机制转发实现。这种解决方案在网络规模庞大时将变得极为复杂、对系统资源的消耗较大且转发效率低下。此外,docker host的端口也是一种稀缺资源,静态分配和映射极易导致冲突,而动态分配又很容易导致模型的进一步复杂化。
Kubernetes网络模型
Kubernetes的网络模型主要可用于解决四类通信需求:同一Pod内容器间的通信(Container to Container)、Pod间的通信(Pod to Pod)、Service到Pod间的通信(Service to Pod)以及集群外部与Service之间的通信(external to Service)。
(1)容器间通信
Pod对象内的各容器共享同一网络名称空间,它通常由构建Pod对象的基础架构容器所提供,所有运行于同一个Pod内的容器彼此之间可通过lo接?完成交互.
(2)Pod间通信
各Pod对象需要运行于同一个平面网络中,每个Pod对象拥有一个集群全局唯一的地址并可直接用于与其他Pod进行通信,此网络也称为Pod网络。
另外,运行Pod的各节点也会通过桥接设备等持有此平面网络中的一个IP地址,这就意味着Node到Pod间的通信也可在此网络上直接进行。因此,Pod间的通信或Pod到Node间的通信比较类似于同一IP网络中主机间进行的通信。
(3)Service与Pod间的通信
Service资源的专用网络也称为集群网络(Cluster Network),需要在启动kube-apiserver时经由“--service-cluster-ip-range”选项进行指定,而每个Service对象在此网络中均拥一个称为Cluster-IP的固定地址。管理员或用户对Service对象的创建或更改操作由API Server存储完成后触发各节点上的kube-proxy,并根据代理模式的不同将其定义为相应节点上的iptables规则或ipvs规则,借此完成从Service的Cluster-IP与Pod-IP之间的报文转发。
(4)集群外部到Pod对象之间的通信
将集群外部的流量引入到Pod对象的方式有受限于Pod所在的工作节点范围的节点端口(nodePort)和主机网络(hostNetwork)两种,以及工作于集群级别的NodePort或LoadBalancer类型的Service对象。不过,即便是四层代理的模式也要经由两级转发才能到达目标Pod资源:请求流量首先到达外部负载均衡器,由其调度至某个工作节点之上,而后再由工作节点的netfilter(kube-proxy)组件上的规则(iptables或ipvs)调度至某个目标Pod对象。
Pod网络的实现方式
每个Pod对象内的基础架构容器均使用一个独立的网络名称空间,并共享给同一Pod内的其他容器使用。每个名称空间均有其专用的独立网络协议栈及其相关的网络接口设备。一个网络接口仅能属于一个网络名称空间,于是,运行多个Pod必然要求使用多个网络名称空间,也就需要用到多个网络接口设备。不过,一个易于实现的方案是使用软件实现的伪网络接口及模拟线缆将其连接至物理接口。伪网络接口的实现方案常见的有虚拟网桥、多路复用及硬件交换三种。
- 虚拟网桥:创建一对虚拟以太网接口(veth),一个接入容器内部,另一个留置于根名称空间内并借助于Linux内核桥接功能或OpenVSwitch(OVS)关联至真实的物理接口。
- 多路复用:多路复用可以由一个中间网络设备组成,它暴露了多个虚拟接口,可使用数据包转发规则来控制每个数据包转到的目标接口。MAC VLAN为每个虚拟接口配置一个MAC地址并基于此地址完成二层报文收发,而IP VLAN是基于IP地址的并使用单个MAC,从而使其更适合VM。
- 硬件交换:现今市面上的大多数NIC都支持单根I/O虚拟化(SR-IOV),它是创建虚拟设备的一种实现方式。每个虚拟设备自身均表现为一个独立的PCI设备,并有着自己的VLAN及与硬件强制关联的QoS。SR-IOV提供了接近硬件级别的性能,但在公共云中通常是不可用的。
无论上述哪种方式应用于容器环境中,其实现过程都需要大量的操作步骤。不过,目前Kubernetes支持使用CNI插件来编排网络,以实现Pod及集群网络管理功能的自动化。每次Pod被初始化或删除时,kubelet都会调用默认的CNI插件创建一个虚拟设备接口附加到相关的底层网络,为其设置IP地址、路由信息并将其映射到Pod对象的网络名称空间。
配置网络接口时,kubelet将Pod对象的名称和名称空间作为CNI_ARGS变量的一部分进行传递(如“K8S_POD_NAMESPACE=default;K8S_POD_NAME=myapp-6d9f48c5d9-n77qp;”)。它可以定义每个Pod对象或Pod网络名称空间的网络配置
网络策略
Kubernetes的网络策略功能由其所使用的网络插件实现,因此,仅在使用那些支持网络策略功能的网络插件时才能够配置网络策略,如Calico、Canal及kube-router等。
Calico的calico/kube-controllers即为Calico项目中用于将用户定义的网络策略予以实现的组件,它主要依赖于节点的iptables来实现访问控制功能
策略控制器用于监控创建Pod时所生成的新API端点,并按需为其附加网络策略。当发生需要配置策略的事件时,侦听器会监视到变化,控制器随即响应以进行接口配置和策略应用。
Pod的网络流量包含“流入”(Ingress)和“流出”(Egress)两种方向,每种方向的控制策略则包含“允许”和“禁止”两种。默认情况下,Pod处于非隔离状态,它们的流量可以*来去。一旦有策略通过选择器规则将策略应用于Pod,那么所有未经明确允许的流量都将被网络策略拒绝,不过,其他未被选择器匹配到的Pod不受影响。
Kubernetes自1.8版本起才支持Egress网络策略,此前的版本仅支持Ingress网络策略。
配置网络策略
Kubernetes系统中,报文流入和流出的核心组件是Pod资源,因此它们也是网络策略功能生效的主要目标,因此,NetworkPolicy对象也要使用标签选择器事先选择出一组Pod资源作为控制对象。一般来说,NetworkPolicy是定义在一组Pod资源上的用于管控入站流量的“Ingress规则”,或者说是管理出站流量的“Egress规则”,也可以是两者组合定义,而仅部分生效还是全部生效则需要由spec.policyTypes予以定义
默认情况下,Pod对象既可以接受来自任何来源的流量,也能够向外部发出期望的所有流量。
而附加网络策略机制后,Pod对象会因NetworkPolicy对象的选定而被隔离:一旦名称空间中有任何NetworkPolicy对象匹配了某特定的Pod对象,则该Pod将拒绝Network-Policy所不允许的一切连接请求,而那些未被任何NetworkPolicy对象匹配到的其他Pod对象仍可接受所有流量。
因此,就特定的Pod集合来说,入站和出站流量默认均处于放行状态,除非有规则能够明确匹配到它。然而,一旦在spec.policyTypes中指定了有效的规则类型,却在networkpolicy.spec字段中嵌套定义了没有任何规则的Ingress或Egress字段时,则表示拒绝相关方向上的一切流量。
定义网络策略时常用到的术语及说明具体如下:
- Pod组:由网络策略通过Pod选择器选定的一组Pod的集合,它们是规则生效的目标Pod;可由NetworkPolicy对象通过macthLabel或matchExpression选定。
- Ingress:入站流量,即由其他网络端点发往特定Pod组的流量,通常由流量发出的源站点(from)和流量的目标端口所定义。
- Egress:出站流量,即由特定的Pod组发往其他网络端点的流量,通常由流量的目标网络端点(to)和端口(ports)来进行定义。
- 端点(to,from):流量目标和流量源相关的组件,它可以是CIDR格式的IP地址块(ipBlock)、网络名称空间选择器(namespaceSelector)匹配的名称空间,或Pod选择器(podSelector)匹配的Pod组。
- 端口(ports):TCP或UDP的端口号。
无论是Ingress还是Egress流量,与选定的某Pod组通信的另一方都可使用“网络端点”予以描述,它通常是某名称空间中的一个或一组Pod资源,由namespaceSelector选定名称空间后,经由ipBlock或podSelector进行指定。
在Ingress规则中,网络端点也称为“源端点”,它们用from字段进行标识,而在Egress规则中,网络端点也称为“目标端点”,它们用to字段进行标识。不过,在未定义Ingress或Egress规则时,相关方向的流量均为“允许”,即默认为为隔离状态。而一旦在networkpolicy.spec中明确给出了Ingress或Egress字段,则它们的from或to字段的值就成了白名单列表,而空值意味着所有端点,即不限制访问。
管控入站流量
以提供服务为主要目的的Pod对象通常是请求流量的目标对象,但它们的服务未必应该为所有网络端点所访问,这就有必要对它们的访问许可施加控制。networkpolicy.spec中嵌套的Ingress字段用于定义入站流量规则,就特定的Pod集合来说,入站流量默认处于放行状态,除非在所有入站策略中,至少有一条规则能够明确匹配到它。Ingress字段的值是一个对象列表,它主要由以下两个字段组成。
Ingress字段的值是一个对象列表,它主要由以下两个字段组成。
- from<[]Object>:可访问当前策略匹配到的Pod对象的源地址对象列表,多个项目之间的逻辑关系为“逻辑或”的关系;若未设置此字段或其值为空,则匹配一切源地址(默认的访问策略为不限制);如果此字段至少有一个值,那么它将成为放行的源地址白名单,仅来源于此地址列表中的流量允许通过。
- ports<[]Object>:当前策略匹配到的Pod集合的可被访问的端口对象列表,多个项目之间的逻辑关系为“逻辑或”的关系;若未设置此字段或其值为空,则匹配Pod集合上的所有端口(默认的访问策略为不限制);如果此字段至少有一个值,那么它将成为允许被访问的Pod端口白名单列表,仅入站流量的目标端口处于此列表中方才准许通过。
NetworkPolicy资源属于名称空间级别,它的有效作用范围为其所属的名称空间。
1.设置默认的Ingress策略(拒绝/允许所有入口流量)
用户可以创建一个NetworkPolicy来为名称空间设置一个“默认”的隔离策略,该策略选择所有的Pod对象,而后允许或拒绝任何到达这些Pod的入站流量。
下面的策略示例,其通过policyTypes字段指明要生效Ingress类型的规则,但未定义任何Ingress字段,因此不能匹配到任一源端点,从而拒绝所有入站流量:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all-ingress # 没有设置命令空间则默认命令空间为default
spec:
podSelector: {} # 空的 podSelector 表示选择命名空间下的所有pod
policyTypes: ["Ingress"] # 入口网络策略
# 未设置进入条件,也就是不允许任何进入
若要将默认策略设置为允许所有的入站流量,则只需要显式定义Ingress字段,并将其值设置为空以匹配所有源端点即可,没有为入站流量定义任何规则时,本身的默认规则即为允许访问,因此允许所有相关的入站流量时,本身无须定义默认规则.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-all-ingress
spec:
podSelector: {}
policyTypes: ["Ingress"]
ingress:
- {}
实践中,通常将默认策略设置为拒绝所有的入站流量,而后显式放行允许的源端点的入站流量。
2.放行特定的入站流量
在Ingress规则中嵌套from和ports字段即可匹配特定的入站流量,仅定义from字段时将隐含本地Pod资源组的所有端口,而仅定义ports字段时则表示隐含所有的源端点。from和ports同时定义时表示隐含“逻辑与”关系,它将匹配那些同时满足from和ports的定义的入站流量,即那些来自from指定的源端点,访问由当前NetworkPolicy的podSelector匹配的当前名称空间的Pod资源组上所指定的ports的请求。
from字段的值是一个对象列表,它可嵌套使用ipBlock、namespaceSelector和podSelector字段来定义流量来源,此三个字段匹配Pod资源的方式各有不同,同时使用两个或以上的字段,彼此之间隐含“逻辑或”关系。
- ipBlock
- namespaceSelector
- podSelector
- ports字段的值也是一个对象列表,它嵌套port和protocol来定义流量的目标端口,即由NetworkPolicy匹配到的当前名称空间内的所有Pod资源上的端口。
- port
:端口号或在Container上定义的端口名称,未定义时匹配所有端口。 - protocol
:传输层协议的名称,TCP或UDP,默认为TCP。
示例定义了如何开放myapp pod资源给相应的源站点访问:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-myapp-ingress
namespace: default
spec:
podSelector:
matchLabels:
app: myapp
policyTypes: ["Ingress"]
ingress:
- from:
- ipBlock:
cidr: 10.244.0.0/16
except:
- 10.244.3.0/24
- podSelector:
matchLabels:
app: myapp
ports:
- protocol: TCP
port: 80
它将default名称空间中拥有标签“app=myapp”的Pod资源的80/TCP端口开放给10.244.0.0/16网络内除10.244.3.0/24子网中的所有源端点,以及当前名称空间中拥有标签“app=myapp”的所有Pod资源访问,其他未匹配到的源端点的流量则取决于其他网络策略的定义,若没有任何匹配策略,则默认为允许访问。
管控出站流量
通常应该将出站流量的默认策略设置为准许通过。但如果有必要对其实施精细管理,仅放行那些有对外请求需要的Pod对象的出站流量,则也可先为名称空间设置“禁止所有”默认策略,而后定义明确的“准许”策略。
networkpolicy.spec中嵌套的Egress字段用于定义入站流量规则,就特定的Pod集合来说,出站流量一样默认处于放行状态,除非在所有入站策略中至少有一条规则能够明确匹配到它。
Egress字段的值是一个字段列表,它主要由以下两个字段组成。
- to<[]Object> :由当前策略匹配到的Pod资源发起的出站流量的目标地址列表,多个项目之间为“或”(OR)关系;若未定义或字段值为空则意味着应用于所有目标地址(默认为不限制);若明确给出了主机地址列表,则只有目标地址匹配列表中的主机地址的出站流量被放行。
- ports<[]Object> :出站流量的目标端口列表,多个端口之间为“或”(OR)关系;若未定义或字段值为空则意味着应用于所有端口(默认为不限制);若明确给出了端口列表,则只有目标端口匹配列表中的端口的出站流量被放行。
Egress规则中,to和ports字段的值都是对象列表格式,它们可内嵌的字段分别与Ingress规则中的from和ports相同,区别仅是作用到的流量方向相反。
1.设置默认Egress策略
通过创建一个NetworkPolicy对象来为名称空间设置一个默认的隔离策略,该策略选择所有的Pod对象,而后允许或拒绝由这些Pod发出的所有出站流量。
通过policyTypes字段指明要生效Egress类型的规则,但未定义任何Egress字段,因此不能匹配到任何目标端点,从而拒绝所有的出站流量:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all-egress
spec:
podSelector: {} # 该命名空间下的所有pod
policyTypes: ["Egress"] # 出口网络策略
# 未设置出口条件,也就是不允许任何出口流量
实践中,需要进行严格隔离的环境通常将默认策略设置为拒绝所有出站流量,而后显式放行允许到达的目标端点的出站流量。
2.放行特定的出站流量
示例中定义了一个Egress规则,它对来自拥有“app=tomcat”的Pod对象的,到达标签为“app=nginx”的Pod对象的80端口,以及到达标签为“app=mysql”的Pod对象的3306端口的流量给予放行:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-tomcat-egress
namespace: default
spec:
podSelector:
matchLabels:
app: tomcat
policyTypes: ["Egress"]
egress:
- to:
- podSelector:
matchLabels:
app: nginx
ports:
- protocol: TCP
port: 80
- to:
- podSelector:
matchLabels:
app: mysql
ports:
- protocol: TCP
port: 3306
需要注意的是,此配置清单中仅定义了出站规则,将入站流量的默认规则设置为拒绝所有时,还应该为具有标签“app=tomcat”的Pod对象放行入站流量。
隔离名称空间
彼此隔离所有的名称空间,但应该允许它们都能够与kube-system名称空间中的Pod资源进行流量交换,以实现监控和名称解析等各种管理功能。
示例为default名称空间定义了相关的规则,在出站和入站流量默认均为拒绝的情况下,它用于放行名称空间内部的各Pod对象之间的通信,以及与kube-system名称空间内各Pod间的通信:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: namespace-deny-all
namespace: default
spec:
policyTypes: ["Ingress","Egress"]
podSelector: {}
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: namespace-
namespace: default
spec:
policyTypes: ["Ingress","Egress"]
podSelector: {}
ingress:
- from:
- namespaceSelector:
matchExpressions:
- key: name
operator: In
values: ["default","kube-system"]
egress:
- to:
- namespaceSelector:
matchExpressions:
- key: name
operator: In
values: ["default","kube-system"]
需要注意的是,有些管理员可能会把一些系统附件部署到专有的名称空间中,例如把Prometheus监控系统部署到prom名称空间中等,所有这类的具有管理功能的附件所在的名称空间与每一个特定名称空间的出入流量都应该被放行。
网络策略应用案例
假设有名为testing的名称空间内运行着一组nginx Pod和一组myapp Pod。要求实现如下目标。
1)myapp Pod仅允许来自nginx Pod的流量访问其80/TCP端口,但可以向nginx Pod的所有端口发出出站流量。
2)nginx Pod允许任何源端点对其80/TCP端口的访问,并能够向任意端点发出出站流量。
3)myapp Pod和nginx Pod都可与kube-system名称空间的任意Pod进行任何类型的通信,以便于可以使用由kube-dns提供的名称解析服务等。
出站和?站的默认策略均为“禁?”。
下面是测试实现步骤。
第一步: 创建testing名称空间,并于其内基于Deployment控制器创建用于测试用的nginx Pod和myapp Pod各一个,创建相关Pod资源时顺便为其创建与Deployment控制器同名的Service资源
~]$ kubectl create namespace testing
~]$ kubectl run nginx --image=nginx:alpine --replicas=1 --namespace=testing --port 80 --expose --labels app=nginx
~]$ kubectl run myapp --image=ikubernetes/myapp:v1 --replicas=1 --namespace=testing --port 80 --expose --labels app=myapp
为了便于在网络策略规则中引?kube-system名称空间,这?为其添加标签“ns=kube-system”
~]$ kubectl label namespace kube-system ns=kube-system
待相关资源创建完成后,即可通过与其相关的Service资源的名称访问相关的服务。
找一pod客户端分别测试访问nginx和myapp的服务,名称空间的默认网络策略为准许访问,接下来确认其访问请求可正常通过
第二步: 定义网络策略清单文件(testing-netpol-denyall.yaml),将testing名称空间的入站及出站的默认策略修改为拒绝访问,并再一次进行访问测试
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all-traffic
namespace: testing
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
接下来,将testing-netpol-denyall.yaml定义的默认策略deny-all-traffic予以应用,为testing名称空间设置默认的网络策略
$ kubectl apply -f testing-netpol-denyall.yaml
回到第一步使用的交互式客户端,再次对nginx和myapp发起访问测试。为了避免长时间等待,这里为curl命令添加--connect-timeout
选项为其定义连接超时时长。
由下面的命令可知,此时无法再访问到nginx和myapp的相关服务
/ # curl --connect-timeout 2 nginx.testing
curl: (28) connect() timed out!
/ # curl --connect-timeout 2 myapp.testing
curl: (28) connect() timed out!
/ #
第三步: 定义流量放行规则配置清单nginx-allow-all.yaml,放行nginx Pod之上80/TCP端口的所有流量
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: nginx-allow-all
namespace: testing
spec:
podSelector:
matchLabels:
app: nginx
ingress:
- ports:
- port: 80
- from:
- namespaceSelector:
matchLabels:
ns: kube-system
egress:
- to:
policyTypes:
- Ingress
- Egress
将上述清单文件中定义的网络策略应用至集群中以创建相应的网络策略
~]$ kubectl apply -f nginx-allow-all.yaml
而后再次回到交互式测试客户端发起访问请求进行测试,nginx已经能够正常访问,这同时也意味着由kube-system名称空间中的kube-dns进行的名称解析服务也为可用状态
第四步: 定义网络策略配置清单myapp-allow.yaml,放行testing名称空间中来自nginx Pod的发往myapp Pod的80/TCP的访问流量,以及myapp Pod发往nginx Pod的所有流量。另外,允许myapp Pod与kube-system名称空间的任何Pod进行交互的所有流量
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: myapp-allow
namespace: testing
spec:
podSelector:
matchLabels:
app: myapp
ingress:
- from:
- podSelector:
matchLabels:
app: nginx
ports:
- port: 80
- from:
- namespaceSelector:
matchLabels:
ns: kube-system
egress:
- to:
- podSelector:
matchLabels:
app: nginx
- to:
- namespaceSelector:
matchLabels:
ns: kube-system
policyTypes:
- Ingress
- Egress
创建清单中定义的网络策略
~]$ kubectl apply -f myapp-allow.yaml
networkpolicy.networking.k8s.io "myapp-allow" configured
然后切换至此前在专用终端中创建的临时使用的交互式Pod,对myapp Pod发起访问请求。由下面的命令结果可知,其访问被拒绝
# curl --connect-timeout 2 http://myapp.testing
curl: (28) connect() timed out!
/ #
myapp Pod仅允许来自nginx Pod对其80/TCP端口的访问,于是,这里进入testing名称空间中的是nginx Pod的交互式接口,使用wget命令对myapp Pod发起访问请求
~]$ kubectl exec -it nginx-b477df957-jb2nf -n testing -- /bin/sh
/ # wget http://myapp.testing -O - -q
Hello MyApp | Version: v1 | <a href="hostname.html">Pod Name</a>
/ #