前言
本文仅代表作者的个人观点;
本文的内容仅限于技术探讨,不能直接作为指导生产环境的素材;
本文素材是红帽公司产品技术和手册;
本文分为系列文章,将会有多篇,初步预计将会有8篇。
一、OVS的三种模式
OpenShift的OVS网络组件有三种模式:ovs-subnet、ovs-multitenant、ovs-networkpolicy。
二、OVS网络隔离的实现
在OCP中,如果配置了ovs-multitenant或者ovs-networkpolicy可以实现网络隔离。那么,网络隔离是如何实现的呢?
在ovs-multitenant模式下,每个项目将会被分配一个VNID。
也就是说,不同VNID的pod之间是无法互相通讯的。
但有一种例外,VNID为0的项目,能够默认被所有项目的pod访问,原因何在?
VNID为0的项目是default项目。
default项目的pod是router和registry,它本身就需要其他项目的pod的访问。
在ovs-networkpolicy模式下,每个项目将会被分配一个VNID。
ovs-networkpolicy提供pod级别的隔离。它的隔离策略的特点:
我们分析三种流量的网络隔离策略:
第一种:通一个node上不同项目之间pod的通讯
针对于VNID非0的项目:
1.网络包从一个VNID非0的pod发出;
2.OVS桥br0标记带有项目分配的VNID的数据包,如果
3.OVS桥允许将数据包传递到目标pod
第二种:不同node上不同项目之间pod的通讯
第三种:从本node发出网络包
三、ovs-networkpolicy策略详解
ovs-networkpolicy的配置,目前在OCP中没有图形化界面,需要cli配置。
ovs-networkpolicy的策略很强大,生产上使用需要进行详细和具体的设计。
默认情况下,本项目所有的pod都不能被其他项目的pod访问。
场景1:设置本项目所有的pod的任意端口都不能被外部pod访问。
然后执行:oc create -f ./deny-by-default.yaml -n my_secure_project
场景2:设置本项目所有的pod可以被default项目中的pod访问(路由和镜像仓库)
oc label namespace default name=default
场景3:本项目所有pod之间客户相互访问,但不能被其他项目访问:
场景4:本项目所有pod都能访问项目内pod的80和443端口,其他项目不成:
场景5:本项目所有pod可以访问项目内被标记为红色的pod,其他项目不成:
场景6:所有pod都能访问项目A内的,被标记为红色的pod:
场景7:只允许本项目内,标记为蓝色的pod对标记为红色pod的访问,其他项目不成:
场景8:项目a中的pod,彼此之间可以被访问;项目a中的pod,可以被用户名为alice的项目中的pod访问:
oc label namespace project-a user=bob
oc label namespace project-b user=alice
四、Openshift OVS三种工作模式的设置
OVS的三种工作模式:ovs-subnet、ovs-multitenant、ovs-networkpolicy是如何设置的呢?
在OCP中,OVS模式的设置,在master和node上是分别设置的。
每个Master上的配置文件:
/etc/origin/master/master-config.yaml
默认是redhat/openshift-ovs-subnet,如果想改成租户隔离,则修改为:redhat/openshift-ovs-multitenant
每个Node上的配置文件:/etc/origin/node/node-config.yaml
默认是redhat/openshift-ovs-subnet,如果想改成租户隔离,则修改为:redhat/openshift-ovs-multitenant。
五、Openshift SDN访问流量详解---从外向内
接下来,我们看一下,应用访问容器里的应用的详细介绍。
举个例子,我们从笔记本客户端,要访问myadd.mydomain.org:80。在浏览器输入这个地址以后:
第一步:DNS将会解析这个域名,将它解析成运行routing layer所在的OCP Node IP。这就需要数据中心的DNS,将应用的FQDN,解析成OCP集群物理服务器的IP地址(如果OCP集群有两个router,那需要给两个router所在的两个物理服务器的IP配置一个VIP,然后将应用的FQDN解析成这个VIP)。
第二步:客户端对80端口的访问请求,将会到达routing layer。
第三步:routing查看FQDN对应的enpoints(Pod IP)。如果pod在routing layer所在的ocp节点,那么直接内部通讯。
第四步:如果pod不在routing layer所在的ocp节点,请求将会通过OCP内部的OVS,转到到对应ocpnode上的pod IP:10.1.0.2:8080。
Routing Layer的负载均衡功能
当一个应用对应多个pod,routing layer将会做不同pod之间的负载均衡。负载均衡有三种策略:
roundrobin、leastconn、source。
roundrobin:根据每个pod的权重,平均轮询分配。在不改变routing的默认规则下,每个pod的权重一样,haproxy转发包也是轮着来。
如果我们更改了每个pod的权重,那轮询的时候,也会根据权重来转发请求。如下图:V2和V1是3:2。那么haproxy会给V2发两个包,给V1发一个,周而复始。
leastconn:routing layer转发请求的时候,按照哪个pod的连接数最少,将新的请求发给连接数最少的pod。一般这种方式适合长连接,短链接不建议使用。
source:将源IP地址先进行哈希,然后除以正在运行的pod总权重,然后算出哪个节点接受请求。这确保了只要没有服务器发生故障,相同的客户端IP地址将始终到达同一个pod。
三种方式,可以通过设置routing layer的环境变量来实现。
六、Openshift SDN访问流量详解---从内向外
在OCP中;
设备 | 描述 |
---|---|
br0 | pod链接到ovs的接口 |
lbr0 | 用于Docker的Linux桥接设备。 它会被分配群集pod子网网关地址(10.1.x.1 / 24)。 |
tun0 | OVS内部端口(br0上的端口2)。它也被分配群集子网网关地址,并用于外部网络访问。 OpenShift SDN配置netfilter和路由规则,以支持通过NAT从集群子网到外部网络的访问。 |
vxlan0 | OVS VXLAN设备(br0上的端口1),用于访问远程节点上的容器。 |
vlinuxbr and vovsbr | 两个Linux对等虚拟以太网接口。 将vlinuxbr添加到lbr0,并将vovsbr添加到br0(带有ovs-subnet插件的端口9和带有ovs-multitenant插件的端口3),以便为在OpenShift外部直接使用Docker创建的容器提供连接。 |
那么,Pod的IP如何被分配的呢?
第一步:Docker使用lbr0网桥,OpenShift SDN已为其分配了网关网关地址(10.1.x.1 / 24),给pod分配IP。
第二步:OpenShift将pod的veth接口对的主机端从lbr0桥移动到br0 OVS桥
第三步:将OpenFlow规则添加到OVS数据库,以便路由寻址到新pod的流量能够访问到正确的ovs端口。
第四步:当使用ovs-multitenant插件时,OpenFlow规则将添加到:
1.使用pod的VNID标记来自pod的流量
2.如果流量的VNID与pod的VNID匹配,或者虽然不匹配,但pod所在的项目是是特权项目(如default项目或openshift-infra(VNID 0)),则允许流量进入pod。
使用ovs-networkpolicy时,还会添加OpenFlow规则:将传入对象的标记与规则匹配。如果进入流量负责规则,则允许通过,否则将会被拒绝。
Pod的通讯,分为两大种情况:
针对第一种情况,pod直接的通讯又分为两种情况:
1.1 两个pod在一个OCP Node上:
例如Pod1要和pod2进行通讯(在ovs的br0中转发是依赖于Openflow的流表规则):
通讯流量是这样走的:pod1的eth0→veth1→br0→veth2→pod2的eth0
1.2 两个pod在不同的OCP Node上:
我们举个例子:
Pod1(OCP NodeA)要与Pod3(OCP NodeB)进行通讯。
通讯流量是这样走的:Pod1的eth0→veth1→br0→vxlan0→ OCP nodeA的eth0网卡→OCP NodeB的eth0网卡→vxlan0→br0→veth3→ Pod3的eth0.
2. pod对外通讯:
PodA的eth0→vethA→br0→tun0→ 通过iptables实现NAT→ OCP nodeA的eth0(physical device) → Internet
所以,综上所述,pod通讯的流量总图如下:
总结:
关于iptables的表,可以通过iptables -L进行查看:
#iptables -L
名词解释:
七、OCP的几种对外网络提供方式
Openshift/Docker中,我们会遇到(听到)的几种网络端口模式有:
它们有什么区别,适用于什么场景?我们先看看它们的作用。
1. 什么是hostport?
hostport它指的是:在一个宿主机上运行的一个容器,为了外部能够访问这个容器,将容器的端口与宿主机进行端口映射。而为了避免宿主机上的端口占用,在容器和宿主机做端口映射的时候,通常映射一个比较大的端口号(小端口被系统服务占用)。
我们看一个实验:
在宿主机上启动一个apache的容器,将容器的端口80,映射成宿主机的端口10080.
然后,我们查看这个容器的网络模式,可以看到,该容器使用的是hostport的模式,占用宿主机的端口号是10080.
我们查看容器的IP,地址为:172.17.0.2
接下来,我们验证apache服务。
首先,图形化登录宿主机,访问宿主机的80端口(确保宿主机的httpd服务是停止的),无法访问:
接下里,访问宿主机的10080端口,可以访问apache网页,说明此时访问的服务,是容器中的:
接下来,我们通过外部访问宿主机的域名加10080端口号,可以成功:
接下来,我们再做一个验证,在宿主机上,直接访问容器的IP和80端口,可以通:
截至到目前,我们对hostnetwork应该有一个比较清晰的了解了。它将容器与宿主机的端口做映射,是为了从外部可以访问到容器。而如果容器不需要被外部访问,则不需要做hostport。
2.什么是nodeport?
nodeport与hostport最重要的一个区别是,hostport是针对一个单宿主机的一个容器的;而nodeport是针对于K8S集群而言的。
在Openshift中,我们知道每个pod有一个IP,通常网段是10.开头的;同时OCP中还有service ip。而nodeport指的是:将service ip和端口,映射到OCP集群所有node的node ip和指定的端口号(通常是大端口:30000-32767)。
为什么将service ip和OCP中所有node做映射?
因为service ip在OCP中是跨node的。
我们看一个service的yaml配置文件,这是一个mysql的service:
这个配置的含义是,采用nodeport的方式,将mysql server的IP和node ip映射,serivce的源端口是3306,映射到node的端口是30306(大端口)。
这样配置完毕以后,外部要访问pod,访问的是nodeip:30306。然后访问请求通过iptables的NAT将nodeip和端口转化为:service ip和3306端口,最终请求通过service负载均衡到pod。
nodeport的缺点很明显:宿主机端口浪费和安全隐患,并且数据转发次数较多多。
3. 什么是hostnetwork
在hostnetwork模式下,pod的IP和端口会直接绑定到宿主机的IP和端口。应用访问的时候,访问宿主机的IP和端口号后,这个请求直接转到pod和相同的端口(不经过iptables和SVC)。也就是说,这种情况下,pod的IP就是宿主机的IP,pod暴露哪个端口,宿主机就对外暴露哪个端口。
我们看一下pod的dc:
上面的配置文件中,打开了hostnetwork模式.。pod部署以后,pod的IP直接就是宿主机的IP。
例如,在Openshift中,router就是hostnetwork模式。下图中node.example.com是node,IP是192.168.137.102,pod的IP也是这个:
我们查看pod暴露的端口,有三个:80、443、1936:
再查看pod和node端口关系,port的端口和node的端口也是一致的。
hostnetwork相比于nodeport,好处在于转发路径短。缺点是占用node的实际端口,无法在用一个节点同时运行相同端口的两个pod。同时,hostnetwork无法跨node。
4.什么是router
在Openshift中,有router的概念。router的作用是对外暴露service的FQDN。
那么,router的本质是什么?
router本质上,一个router是以hostnetwork方式运行在一个node上的容器化hproxy,它的pod ip就是所在node的ip,对外暴露的端口就是:80、443、1936。
客户端访问某一个应用,如在浏览器中输入http://productpage-istio-system.apps.example.com,首先外部DNS将这个域名解析成router所在node的IP,即:192.168.137.102。
然后,请求到达router所在的node以后,会查询到对应的route信息,查到route对应的service的名称:httpd。此时,通过查询etcd,获取到service和相关的信息。将请求通过service负载均衡发给后端的pod。
实验展示如下:
查看route名称:
下图显示的是路由信息:
域名productpage-istio-system.apps.example.com转化为svc,名称为productpge,端口是http:
下图为svc的配置,展示svc的端口以及后端pod的端口以及pod的名,productpge
下图显示的是pod的dc,其中显示pod的名称和端口:
也就是说,productpge这个pod,对外暴露的是9080端口给service,名称为productpge,端口为9080。然后,route将service productpge暴露为productpage-istio-system.apps.example.com,并且端口转为80、443、1936,而外部的DNS将productpage-istio-system.apps.example.com可以解析成router所在的node的IP。
所以说,router就是一个以hostnetwork方式运行在node上的容器化haproxy,它占用了node的80、443、1936端口。所以,这也是为什么一个node上只能运行一个router的原因所在。
5.结论
1. 在OCP中,对于http/https类的应用,对往外通过router暴露的FQDN访问即可。
2. 在OCP中,对于非http/https类的应用,如mysql,存在两种情况:
2.1. 不需要对外提供服务,那么前端应用通过内部service ip访问mysql即可,无需对外暴露;
2.2.需要被外部访问,则需要对外暴露端口,存在两种情况:
(1)应用单副本,无需在多个node上运行,优先使用hostnetwork方式
(2)应用多副本,在多个node上运行,使用nodeport的方式。
整体而言,hostnetwork的方式转发路径短,性能比nodeport好。
八、OCP vs K8S: 如何配置一个安全的OCP路由
而K8S有三种被外部访问方式:NodePort,LoadBalancer 和 Ingress。
我们先开看看OCP和K8S在网络访问方面的异同。
1.OCP的Service IP和K8S的Cluster IP
OCP中的service IP,对应的是K8S的 ClusterIP;无论是Service IP和ClusterIP,都无法被外部直接访问。
而Openshift的Nodeport和K8S的Nodeport是十分类似的;
Nodeport在OCP指的是:将service ip和端口,映射到OCP集群所有node的node ip和指定的端口号(通常是大端口:30000-32767)。
OCP:
在Openshift中,我们知道每个pod有一个IP,通常网段是10.开头的;同时OCP中还有service ip。而nodeport指的是:将service ip和端口,映射到OCP集群所有node的node ip和指定的端口号(通常是大端口:30000-32767)。
为什么将service ip和OCP中所有node做映射?
因为service ip在OCP中是跨node的。
我们看一个service的yaml配置文件,这是一个mysql的service:
这个配置的含义是,采用nodeport的方式,将mysql server的IP和node ip映射,serivce的源端口是3306,映射到node的端口是30306(大端口)。
这样配置完毕以后,外部要访问pod,访问的是nodeip:30306。然后访问请求通过iptables的NAT将nodeip和端口转化为:service ip和3306端口,最终请求通过service负载均衡到pod。
K8S:Nodeport在K8S的定义如下,从描述看,与OCP的nodeport相同:
NodePort: Exposes the service on each Node’s IP at a static port (the NodePort). A ClusterIP service, to which the NodePort service will route, is automatically created. You’ll be able to contact the NodePort service, from outside the cluster, by requesting <NodeIP>:<NodePort>.
在K8S中,还有一个概念:kube-proxy。为service增加proxy,是为了service在集群,被通过API方式访问:
$ kubectl proxy --port=8080
然后可以通过这种方式在内部访问服务:
http://localhost:8080/api/v1/proxy/namespaces/default/services/my-internal-service:http/
k8s的最开始版本使用的userspace模式,所有的客户端请求svc,都需要先经过iptables,然后在经过kube-proxy到pod,性能很差。
所以在后期版本中K8S增加了iptables方式,在iptables方式下,客户端请求svc会直接经过iptabls转发到pod,而不需要再经过kube-proxy,提高了性能。
但是kube-proxy的缺点是仅支持轮询的负载方式,而且一样存在iptables规则过多导致的性能(iptables是自上而下的匹配,一旦规则多会很慢)。所以目前社区在研究IPVS方式。与iptables类似, ipvs也基于 netfilter 。但ipvs使用哈希表作为底层数据结构,并在内核空间工作,因此IPVS重定向交通速度更快,具有更好的性能。
IPVS支持如下负载均衡策略:
rr: round-robinlc: least connection
dh: destination hashing
sh: source hashing
sed: shortest expected delay
nq: never queue
目前,Openshift安装的时候,模式使用Proxy-mode: iptable模式,也可以修改为Proxy-mode: userspace模式,但没这个必要(因为userspace模式已废弃)。
目前Proxy-mode: ipvs由于在K8S 1.10目前是beta版本,因此在OCP中还没有支持。在K8S正式发布IPVS功能后,相信OCP会同步支持。
2、OCP的router和K8S的Ingress
OCP的router和K8S的Ingress是十分类似的,router和Ingress都是对外暴露http/https类域名。
OCP:
outer本质上,一个router是以hostnetwork方式运行在一个node上的容器化hproxy,它的pod ip就是所在node的ip,对外暴露的端口就是:80、443、1936。
客户端访问某一个应用,如在浏览器中输入http://productpage-istio-system.apps.example.com,首先外部DNS将这个域名解析成router所在node的IP,即:192.168.137.102。
然后,请求到达router所在的node以后,会查询到对应的route信息,查到route对应的service的名称:httpd。此时,通过查询etcd,获取到service和相关的信息。将请求通过service负载均衡发给后端的pod。
K8S:
区别是,OCP用的是容器化的haproxy做router;K8S默认用GCE/Nginx做这件事。
查看ingress的配置:
我们看一下router的dc,我们可以看到,router的安全策略更多一些。
3、K8S的Loadbalance模式
LoadBalancer是K8S借助于cloud provider提供的LoadBalancer,实现被外部网络访问。
LoadBalancer: Exposes the service externally using a cloud provider’s load balancer. NodePort and ClusterIP services, to which the external load balancer will route, are automatically created.
Service的Loadbalancer指定以后,Loadbalancer会将各类访问请求转发到service,HTTP,TCP,UDP,Websocket,gRPC 或其它任意种类。Loadbalancer将会被cloud provider分配一个IP地址。
我们来看一个service的yaml文件,其中制定了loadbalancer:
4、Openshift的路由安全
上面内容已经提到,OCP使用router方式对外暴露80/443/1936端口,为web类应用提供对外访问。
那么,路由的安全如何保证呢?
默认情况,我们expose server,会生成一个域名,这个域名的端口是80:
浏览器访问:
OCP提供三种路由安全策略:Edge Termination(边界终止)、Pass-through Termination(直通终止)、Re-encryption Termination(重加密终止)
Edge Termination(边界终止)的模式,是将安全证书加密到路由上。
Pass-through Termination(直通终止)。这种模式,安全加密不设置在路由上,而是设置在pod中通过证书加密。
Re-encryption Termination(重加密终止),这种模式指的是pod和路由上同时加密,但使用不同的证书。
无论使用哪种方式,创建安全路由以后,应用FQDN的80端口将不能被访问。
我们通过实验进行验证。
我们先生成一个key并进行签名:
然后将旧的路由删掉:
创建新的边界路由:
创建好以后,再度通过80端口方式,失败:
通过443访问,出现安全提示:
添加证书后,可以访问:
5、结论
Kubernetes vs Openshift,谁的网络更安全?
实际上,由于OCP是基于K8S,并且红帽写了大量的K8S代码(2017年K8S代码贡献量第一),因此OCP的网络架构和K8S可以说系出同源。
八、生产环境中的网络规划
OCP如何做网络规划这个问题,很多人问过我。我们举个例子来说明。
一个客户有10台物理服务器,前3台做Master,后7台做node节点。存储使用NAS。
网络规划:
网络1:默认的Openshift集群内部使用的网络(不与网络冲突即可)
在这个网络中,有两个网段:Service IP网段和Pod IP网段(通通过编辑
/etc/origin/master/master-config.yaml进行修改)。
ServiceIP默认网段是172.30.0.0/16
Pod IP的默认网段是:10.128.0.0/14
这两个网段,都不需要分配数据中心IP。
网络2:生产环境业务网络:共需要12个IP。
其中:10个物理服务器,每个都需要1个IP。而因为Master节点是三个,需要有高可用,因此需要一个VIP。客户端访问Master的时候(通过浏览器、命令行),访问这个VIP即可。此外,为了保证routing layer的高可以用,在两个物理服务器上配置分别部署两个routing,然后给两台服务服务器配置一个VIP。
网络3:NAS网络。
需要保证10台物理服务器都可以与NAS网络正常通讯,因此需要配置与NAS网络可通讯的IP地址,每个服务器需要一个。
网络4:服务器硬件管理网络。
如果是HP服务器,硬件管理口是ilo,如果是联想的服务器,管理口是IMM。也需要10个IP。
因此,物理服务器配置,建议每个服务器至少配置两个双口网卡。前两个网卡做NIB,配置的是网络2:生产网络IP。后两个双口网卡配置NIB,配置网络3的IP,负责与NAS通讯。服务器一般有单独的物理管理口,不需要PCI网卡提供端口。
魏新宇