在互联网飞速发展的今天,网络已经成为人们日常生活和企业生产经营所不可或缺的一部分。尤其是在新冠疫情的背景之下,疫情防控、远程教育、视频会议、在线订购等网络应用越来越广泛。网络给我们带来便利的同时,各种安全威胁也在十面埋伏。
[1] 网络分层治理概述
对于企业而言,需要建立好内外部的网络防御体系,防止因某个部分的侵入而导致整个系统的崩溃。基于网络系统之间逻辑关联性、物理位置、功能特性等划分好安全层次和网络区域,在网络中部署安全产品与策略时就可以做到有的放矢。
通常我们采用层次分析法,按照不同的安全等级将网络划分成用户访问区域、Web应用区域、数据存储区域、管理控制区域等区域。不同安全层次等级之间由于存在较大安全级差,区域之间和区域内部均利用硬件或软件的安全系统进行隔离防御,以形成一个“垂直分层”+“水平分区”的网络安全治理模型。在Kubernetes集群网络中实现这种模型似乎有点麻烦,尤其是在有虚拟机和容器混合的场景。今天云君给大家演示一种基于multus-cni与kube-ipam来实现Kubernetes集群网络区域分层架构,将用户区域、Web网络安全区域、与数据库网络安全区域进行分层隔离,中间亦可以辅以防火墙/WAF等安全设备进行网络安全的管控,希望对你的企业网络安全治理有一定启发与帮助。
在上图中的kubernetes集群中,每个Pod均具有2个虚拟网卡接口:eth0、net1。其中eth0接口作为外界用户访问web pod的网络接口,而net1接口是附加的容器网卡,用于web Pod到database Pod的内部网络通信。
关于multus-cni与Kube-ipam的简介:
-
Multus-cni支持同时添加多个网络接口到kubernetes环境中的Pod。这样的部署方式有利于安全人员把应用网络和数据库等多个网络区域进行相互隔离,有效控制容器集群网络架构。
-
Kube-ipam支持给kubernetes集群中的Pod固定IP地址。一些场景往往对IP地址有依赖,需要使用固定IP地址的Pod,可以使用kube-ipam轻松解决这类问题。例如,mysql主从架构的时候,主database与从database之间的同步;例如集群HA的时候,两个节点之间检测通信等;例如某些安全防护设备,需要基于IP地址进行网络安全访问策略限制的场景等。
[2] Multus与Kube-ipam的部署
2.1 安装基础CNI插件
基础CNI插件是kubernetes集群内容器基本通信所需的,这里你可以根据自己选所设计网络环境来自由选择。例如在本次体验的网络拓扑中,我们web区域采用了flannel网络、database区域采用了macvlan网络,那么我们将给kubernetes集群同时安装上flannel CNI插件和macvlan CNI插件。
安装macvlan CNI插件:
macvlan CNI插件是在containernetworking中,所以我们可以直接将containernetworking/plugins的二进制文件下载下来,放到/opt/cni/bin目录即可。
# wget https://github.com/containernetworking/plugins/releases/download/v0.9.1/cni-plugins-linux-amd64-v0.9.1.tgz
# tar-zxvf cni-plugins-linux-amd64-v0.9.1.tgz -C /opt/cni/bin/
安装flannel CNI插件:
可以使用daemonset来部署flanneld,也可以使用二进制方式部署。下面简单演示二进制方式部署的方法:
创建flanneld所需的subnet网段
# etcdctl --endpoints=https://192.168.1.11:2379,https://192.168.1.12:2379,https://192.168.1.13:2379 --ca-file=/etc/kubernetes/ssl/k8s-root-ca.pem --cert-file=/etc/kubernetes/ssl/kubernetes.pem --key-file=/etc/kubernetes/ssl/kubernetes-key.pem set /kubernetes/network/config '{"Network":"10.244.0.0/16","SubnetLen":24,"Backend":{"Type":"vxlan"}}'
下载flanneld软件包,并放到/opt/cni/bin目录
# wget https://github.com/flannel-io/flannel/releases/download/v0.11.0/flannel-v0.11.0-linux-amd64.tar.gz
# tar -zxvf flannel-v0.11.0-linux-amd64.tar.gz -C /opt/cni/bin/
编辑/etc/systemd/system/flanneld.service
# cat /etc/systemd/system/flanneld.service
[Unit]
Description=Flanneldoverlay address etcd agent
After=network.target
After=network-online.target
Wants=network-online.target
After=etcd.service
Before=docker.service
[Service]
Type=notify
ExecStart=/opt/cni/bin/flanneld\
-etcd-cafile=/etc/kubernetes/ssl/k8s-root-ca.pem\
-etcd-certfile=/etc/kubernetes/ssl/kubernetes.pem\
-etcd-keyfile=/etc/kubernetes/ssl/kubernetes-key.pem\
-etcd-endpoints=https://192.168.1.11:2379,https://192.168.1.12:2379,https://192.168.1.13:2379\
-etcd-prefix=/kubernetes/network \
-ip-masq
ExecStartPost=/usr/local/bin/mk-docker-opts.sh-k DOCKER_NETWORK_OPTIONS -d /run/flannel/docker
Restart=always
RestartSec=5
StartLimitInterval=0
[Install]
WantedBy=multi-user.target
RequiredBy=docker.service
修改/etc/systemd/system/docker.service,注意增加EnvironmentFile,并修改ExecStart参数:
...
[Service]
EnvironmentFile=/run/flannel/docker
ExecStart=/usr/bin/dockerd$DOCKER_NETWORK_OPTIONS
...
说明:如果你是使用kube-install工具一键安装的kubernetes集群,macvlan CNI插件和flannel CNI插件均会被自动部署好,那么上面这两步操作就可以省略。
2.2 Kube-ipam的安装部署
你可以通过下载或编译获得kube-ipam的二进制文件,然后将kube-ipam的二进制文件拷贝到kubernetes node主机的/opt/cni/bin/ 目录中。
# wget https://github.com/cloudnativer/kube-ipam/releases/download/v0.2.0/kube-ipam-v0.2.0-x86.tgz
# tar -zxvf kube-ipam-v0.2.0-x86.tgz
# mv kube-ipam-v0.2.0-x86/kube-ipam /opt/cni/bin/kube-ipam
2.3 Multus-cni的安装部署
下载multus-cni包:
# wget https://github.com/k8snetworkplumbingwg/multus-cni/releases/download/v3.8/multus-cni_3.8_linux_amd64.tar.gz
把解压出来的二进制文件拷贝到所有Kubernetes的worker节点的/opt/cni/bin目录
# tar -zxvf multus-cni_3.8_linux_amd64.tar.gz
# mv multus-cni_3.8_linux_amd64/multus-cni /opt/cni/bin/
[3] CNI配置的清理与生效
3.1 清理旧CNI配置
为了确保主机环境的干净,请先执行如下命令删除kubernetes node主机上的已有的旧CNI配置:
# rm -rf /etc/cni/net.d/*
3.2 重建CNI新配置
然后重新创建/etc/cni/net.d/10-multus.conf,编辑内容如下:
# cat /etc/cni/net.d/10-multus.conf
{
"cniVersion":"0.3.1",
"name":"multus-demo",
"type": "multus-cni",
"delegates": [
{
"name":"k8snet1",
"type":"flannel",
"masterplugin": true,
"delegate": {
"isDefaultGateway":true,
"bridge":"docker0",
"mtu": 1400
}
},
{
"name":"k8snet2",
"type":"macvlan",
"master": "eth0",
"ipam": {
"name": "kube-subnet",
"type":"kube-ipam",
"kubeConfig":"/etc/kubernetes/ssl/kube.kubeconfig",
"etcdConfig": {
"etcdURL":"https://192.168.1.11:2379,https://192.168.1.12:2379,https://192.168.1.13:2379",
"etcdCertFile": "/etc/kubernetes/ssl/kubernetes.pem",
"etcdKeyFile":"/etc/kubernetes/ssl/kubernetes-key.pem",
"etcdTrustedCAFileFile":"/etc/kubernetes/ssl/k8s-root-ca.pem"
},
"subnet":"10.188.0.0/16",
"rangeStart":"10.188.0.10",
"rangeEnd":"10.188.0.200",
"gateway":"10.188.0.1",
"routes": [{
"dst":"0.0.0.0/0"
}],
"resolvConf":"/etc/resolv.conf"
}
}
]
}
multus使用"delegates"的概念将多个CNI插件组合起来,并且指定一个masterplugin来作为POD的主网络并且被Kubernetes所感知。
3.3 重启kubelet服务
编辑好CNI配置之后,重启kubelet服务:
#systemctl restart kubelet
[4] 创建各层Pod和service
为kubernetes集群安装和配置好CNI插件之后,接下来就可以开始创建Pod容器了。
4.1 创建Database Pod
首先,我们来创建存储数据的MySQL Pod,由于它是有状态的,所以我们会挂载持久卷以及固定容器的IP地址。
# cat db.yaml
apiVersion:apps/v1
kind:StatefulSet
metadata:
name: database-1
spec:
selector:
matchLabels:
app: database-1
serviceName: database-1
template:
metadata:
labels:
app: database-1
annotations:
kube-ipam.ip: "10.188.0.218"
kube-ipam.netmask: "255.255.0.0"
kube-ipam.gateway:"10.188.0.1"
spec:
terminationGracePeriodSeconds: 10
containers:
name: database1
- image: mysql:5.6
env:
- name: MYSQL_ROOT_PASSWORD
value: password
ports:
- containerPort: 3306
name: mysql
volumeMounts:
- mountPath: "/var/lib/mysql"
name: mystorage
volumes:
- name: mystorage
nfs:
path: /storage/db001
server: 172.17.0.180
---
apiVersion:apps/v1
kind:StatefulSet
metadata:
name: database-2
spec:
selector:
matchLabels:
app: database-2
serviceName: database-2
template:
metadata:
labels:
app: database-2
annotations:
kube-ipam.ip: "10.188.0.219"
kube-ipam.netmask: "255.255.0.0"
kube-ipam.gateway:"10.188.0.1"
spec:
terminationGracePeriodSeconds: 10
containers:
name: database2
- image: mysql:5.6
env:
- name: MYSQL_ROOT_PASSWORD
value: password
ports:
- containerPort: 3306
name: mysql
volumeMounts:
- mountPath: "/var/lib/mysql"
name: mystorage
volumes:
- name: mystorage
nfs:
path: /storage/db002
server: 172.17.0.180
使用kubectl apply命令创建:
#
# kubectl apply -f db.yaml
statefulset.apps/databasecreated
#
# kubectl get pod
NAME READY STATUS RESTARTS AGE NODE
database-1-0 1/1 Running 0 2m5s 192.168.1.13
database-2-0 1/1 Running 0 2m5s 192.168.1.12
#
此时,Database区域网络的两个database数据库就创建好了。登录database-1-0容器,你会看见net1网卡的IP地址为10.188.0.218:
#
# kubectl exec -it database-1-0 -- ip address
3:eth0@if10: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1400 qdiscnoqueue state UP
link/ether fa:0c:af:f7:ca:4a brdff:ff:ff:ff:ff:ff
inet 10.244.12.5/24 brd 10.244.5.255 scopeglobal eth0
valid_lft forever preferred_lft forever
4:net1@if2: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueuestate UP
link/ether 72:17:7d:5d:fd:fa brdff:ff:ff:ff:ff:ff
inet 10.188.0.218/16 brd 10.188.255.255scope global net1
valid_lft forever preferred_lft forever
#
登录database-2-0容器,你会看见net1网卡的IP地址为10.188.0.219:
#
# kubectl exec -it database-2-0 -- ip address
3:eth0@if10: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1400 qdiscnoqueue state UP
link/ether fa:0c:af:f7:ca:4d brdff:ff:ff:ff:ff:ff
inet 10.244.5.6/24 brd 10.244.5.255 scopeglobal eth0
valid_lft forever preferred_lft forever
4:net1@if2: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueuestate UP
link/ether 72:17:7d:5d:fd:fc brdff:ff:ff:ff:ff:ff
inet 10.188.0.219/16 brd 10.188.255.255scope global net1
valid_lft forever preferred_lft forever
#
这两个Pod的IP地址是固定不变的,两个Pod在删除、漂移、重启之后,重新起来的Pod依然保持原有的IP地址固定不变。例如我们把database-2-0这个数据库Pod销毁掉:
#
# kubectl delete pod database-2-0
pod"database-2-0" deleted
#
# kubectl get pod
NAME READY STATUS RESTARTS AGE NODE
database-1-0 1/1 Running 0 20m 192.168.1.13
database-2-0 0/1 ContainerCreating 0 1s 192.168.1.12
#
此时kubernetes会重建一个IP地址为10.244.0.219,名称为database-2-0的Pod:
#
# kubectl get pod
NAME READY STATUS RESTARTS AGE NODE
database-1-0 1/1 Running 0 20m 192.168.1.13
database-2-0 1/1 Running 0 4s 192.168.1.14
#
查看重新启动Pod的IP地址,我们发现net1网卡的IP地址依然为10.188.0.219:
#
# kubectl exec -it database-2-0 -- ip address
3:eth0@if10: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1400 qdiscnoqueue state UP
link/ether fa:0c:af:f7:ca:4e brdff:ff:ff:ff:ff:ff
inet 10.244.5.8/24 brd 10.244.5.255 scopeglobal eth0
valid_lft forever preferred_lft forever
4:net1@if2: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueuestate UP
link/ether 72:17:7d:5d:fd:fc brdff:ff:ff:ff:ff:ff
inet 10.188.0.219/16 brd 10.188.255.255scope global net1
valid_lft forever preferred_lft forever
#
两个数据库Pod可以互相通过固定IP地址进行集群的通信操作,比如HA切换、主从同步(由于与虚机效果无差异,所以这里就不贴出具体过程了)等。
4.2 创建Web Pod
有了数据库基建,接下来我们来创建上层的Web应用容器,Web应用不需要固定IP地址,而且是可以任意弹性伸缩和方便服务暴露到外网的。
# catweb.yaml
apiVersion:apps/v1
kind:Deployment
metadata:
labels:
app:web
name: web
spec:
replicas: 2
selector:
matchLabels:
app: web
strategy: {}
template:
metadata:
labels:
app: web
spec:
containers:
- image: nginx:1.7.9
imagePullPolicy: IfNotPresent
name: web-c
ports:
name: web
- containerPort: 80
使用kubectl apply命令创建:
# kubectl apply -f web.yaml
deployment.apps/webcreated
#
#
# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE NODE
database-1-0 1/1 Running 0 2m5s 192.168.1.13
database-2-0 1/1 Running 0 2m5s 192.168.1.14
web-5fd8684df7-8c7zb 1/1 Running 0 3h17m 192.168.1.12
web-5fd8684df7-p9g8s 1/1 Running 0 3h17m 192.168.1.14
#
4.3 创建service或ingress
为了让用户可以访问到Web网络区域中的Nginx Web服务,我们可以使用ingress或nodeport service来暴露服务,或者也可以通过路由打通web区域到交换机的网络等方式。下面贴出前面两种常见方式的代码:
例如,给Web Pod配置nodeport的service:
# cat web-svc.yaml
apiVersion:v1
kind:Service
metadata:
name: web-svc
labels:
app: web
spec:
type: NodePort
selector:
app: web
ports:
- port: 80
protocol:TCP
使用kubectl apply命令创建:
#
# kubectl apply -f web-svc.yaml
service/web-svccreated
#
#
# kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.254.0.1 <none> 443/TCP 7d22h
web-svc ClusterIP 10.254.150.15 <none> 80:18370/TCP 6m4s
#
例如,配置ingress到Web网络区域的Web service:
#
# cat web-ingress.yaml
apiVersion:networking.k8s.io/v1
kind:Ingress
metadata:
name: web-ingress
spec:
defaultBackend:
service:
name: web-svc
port:
number: 80
使用kubectl apply命令创建:
#
[root@localhost~]# kubectl apply -f web-ingress.yaml
ingress.networking.k8s.io/web-ingresscreated
#
[5] 验证网络隔离与访问
经过上面的配置之后,用户就可以通过ingress、nodeport service或路由等方式来访问web服务;同时web pod也可以通过Database区域网络,调用有状态的database数据库。外面的用户是不能直接访问Database区域中的数据库的,此时还可以布设区域安全设备对其网络成分进行监测与防护。
另外,Database区域网络的database Pod也可以互相通过固定IP地址进行集群的通信操作等,database Pod类似于一个轻量级的虚拟机效果。采用这种结构,尤其是在虚拟机和容器混合的网络中,安全防护设备(不论新老产品)均可以直接识别网络五元组并进行防护控制。
5.1 用户通过service访问Web
通过ingress或service来访问到web服务,效果如下:
如果你没有浏览器环境,也可以使用curl在命令行进行测试:
#
# curl http://192.168.1.12:18370
<!DOCTYPEhtml>
<html>
<head>
<title>Welcometo nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial,sans-serif;
}
</style>
</head>
<body>
<h1>Welcometo nginx!</h1>
<p>Ifyou see this page, the nginx web server is successfully installed and
working.Further configuration is required.</p>
<p>Foronline documentation and support please refer to
<ahref="http://nginx.org/">nginx.org</a>.<br/>
Commercialsupport is available at
<ahref="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thankyou for using nginx.</em></p>
</body>
</html>
#
5.2 Web Pod通过固定IP访问Database
查看database-2 Pod的net1网卡,10.188.0.219为固定IP地址
#
# kubectl exec -it database-2-0 -- ip address
3:eth0@if9: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1400 qdisc noqueuestate UP
link/ether 26:16:04:dc:82:fe brdff:ff:ff:ff:ff:ff
inet 10.244.69.7/24 brd 10.244.69.255 scopeglobal eth0
valid_lft forever preferred_lft forever
4:net1@if2: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueuestate UP
link/ether c6:17:6c:da:ee:e9 brdff:ff:ff:ff:ff:ff
inet 10.188.0.219/16 brd 10.188.255.255scope global net1
valid_lft forever preferred_lft forever
#
Web Pod可以通过database区域网络,访问固定IP地址的database服务
#
# kubectl exec -it web-5fd8684df7-8c7zb -- ping 10.188.0.219
PING10.188.0.219 (10.188.0.219): 48 data bytes
56 bytesfrom 10.188.0.219: icmp_seq=0 ttl=64 time=0.720 ms
56 bytesfrom 10.188.0.219: icmp_seq=1 ttl=64 time=0.341 ms
56 bytesfrom 10.188.0.219: icmp_seq=2 ttl=64 time=0.485 ms
56 bytesfrom 10.188.0.219: icmp_seq=3 ttl=64 time=0.389 ms
56 bytesfrom 10.188.0.219: icmp_seq=4 ttl=64 time=0.454 ms
^C---10.188.0.219 ping statistics ---
5 packetstransmitted, 5 packets received, 0% packet loss
round-tripmin/avg/max/stddev = 0.341/0.478/0.720/0.131 ms
5.3 database区域的Pod通过固定IP互访
database区域网络内的database-1与database-2都拥有固定IP地址,两个数据库Pod之间可以互相通过固定IP进行集群的通信操作。例如,mysql主从架构的时候,主database-1与从database-2之间的同步等。
#
# kubectl exec -it database-1-0 -- ip address
3:eth0@if9: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1400 qdisc noqueuestate UP
link/ether 2a:64:f3:b8:18:01 brdff:ff:ff:ff:ff:ff
inet 10.244.5.5/24 brd 10.244.5.255 scopeglobal eth0
valid_lft forever preferred_lft forever
4:net1@if2: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueuestate UP
link/ether fe:9d:18:25:9c:a1 brdff:ff:ff:ff:ff:ff
inet 10.188.0.218/16 brd 10.188.255.255scope global net1
valid_lft forever preferred_lft forever
使用database-2 Pod可以正常访问database-1 Pod网络:
#
# kubectl exec -it database-2-0 -- /bin/bash
root@database-2-0:/#
root@database-2-0:/#ip address
3:eth0@if10: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1400 qdiscnoqueue state UP
link/ether ba:32:1c:33:dc:a6 brdff:ff:ff:ff:ff:ff
inet 10.244.69.7/24 brd 10.244.69.255 scopeglobal eth0
valid_lft forever preferred_lft forever
4:net1@if2: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueuestate UP
link/ether 86:3b:09:c8:31:93 brdff:ff:ff:ff:ff:ff
inet 10.188.0.219/16 brd 10.188.255.255scope global net1
valid_lft forever preferred_lft forever
root@database-2-0:/#
root@database-2-0:/#
root@database-2-0:/#ping 10.188.0.218
PING10.188.0.218 (10.188.0.218): 48 data bytes
56 bytesfrom 10.188.0.218: icmp_seq=0 ttl=64 time=0.335 ms
56 bytesfrom 10.188.0.218: icmp_seq=1 ttl=64 time=0.246 ms
56 bytesfrom 10.188.0.218: icmp_seq=2 ttl=64 time=0.484 ms
56 bytesfrom 10.188.0.218: icmp_seq=3 ttl=64 time=0.371 ms
^C---10.188.0.218 ping statistics ---
4 packetstransmitted, 4 packets received, 0% packet loss
round-tripmin/avg/max/stddev = 0.246/0.359/0.484/0.085 ms
[6] 结束语
本文提供了网络垂直分层与水平分区治理的思路,以及一种在Kubernetes集群网络中落地实操的例子,利用固定IP地址特性来兼容新老网络安全设备产品。也许你认为云原生网络中还有其他更好的方式方法来实现类似效果,但并不是所有企业的网络环境和文化都能将你的梦想落地。在某些场景下,本文所述的方法也许是折衷的选择。
参考文档:
-
https://github.com/k8snetworkplumbingwg/multus-cni
-
https://github.com/cloudnativer/kube-ipam
-
https://github.com/k8snetworkplumbingwg/multus-cni/releases
-
https://github.com/cloudnativer/kube-install
-
https://cloudnativer.github.io/kube-ipam.html