作者:董卫国,中国移动云能力中心软件研发工程师,专注于云原生、微服务、算力网络等
出于容灾考虑,应用的部署可能会有跨云、云内跨地域可用区(跨 vpc)的多集群的需求,此时容器的通信就出现了一定的困难,如果没有专线,那么机器的内网 IP 一般无法直接通信,CNI 常用的隧道技术如 vxlan/ipip 在公网环境下也可能会失效。在此基础上,Kosmos 基于 IPsec 隧道实现了跨云公网传输场景下的容器网络通信方案,解决了跨公网通信的需求,也兼顾了传输安全问题,下面我们进行简单的演示说明。
1 原理介绍
IPsec 隧道介绍
我们在跨公网打通容器网络的时候使用了 IPsec 隧道的能力,Linux 内核 2.6 版本以上的版本就已经支持了,我们可以简单的使用 ip xfrm 相关的命令构建 IPsec 的转发规则。IPsec 隧道的规则主要是通过 ip xfrm policy 和 ip xfrm state 命令进行构建。ip xfrm policy 命令用于建立 IPsec 的匹配规则,用来定义哪些流量通过 IPsec 加解密处理,通过 tmpl 字段与 ip xfrm state 匹配,ip xfrm state 则用于定义如何加密解密数据包。
注意:由于一些云厂商无法在安全组放通 esp 协议,可能要在安全组中针对两端的公网 IP 设置全部协议全部端口(即填写 ALL 或者 ANY 等) 进行放通。
模拟容器网络通信
本小节,我们就用 IPsec 来模拟容器网络通信,实验环境是云主机,两台云主机的安全组访问对于各自挂载的弹性公网 IP 的放开策略都设置为 ALL
其他信息
我们模拟实现的目标:在 node1 节点上的容器内可以 ping 通 node2 节点的容器内 IP
首先,要模拟容器网络,创建一个网络命名空间并挂载一个 IP,node1 操作如下:
ip netns add ns0# 新增netns
ip link add veth0-ns type veth peer name veth0-br # 新增veth
ip link set veth0-ns netns ns0 # 将veth的一端移动到netns中
# 将netns中的本地环回和veth启动并配置IP
ip netns exec ns0 ip link set lo up
ip netns exec ns0 ip link set veth0-ns up
ip netns exec ns0 ip addr add 10.0.0.1/24 dev veth0-ns # 属于容器网段10.0.0.0/24的某一个IP,10.0.0.1模拟node1上容器的IP
ip netns exec ns0 ip route add default dev veth0-ns # 容器网络内添加默认路由
ip link set veth0-br up
ip route add 10.0.0.1 dev veth0-br
sysctl -w net.ipv4.conf.veth0-br.proxy_arp=1
sysctl -w net.ipv4.ip_forward=1 # 允许转发
复制代码
node2 做类似操作如下:
ip netns add ns0
ip link add veth0-ns type veth peer name veth0-br
ip link set veth0-ns netns ns0
ip netns exec ns0 ip link set lo up
ip netns exec ns0 ip link set veth0-ns up
ip netns exec ns0 ip addr add 10.0.1.1/24 dev veth0-ns # 属于容器网段10.0.1.0/24的某一个IP,和node1上的操作有区别的地方
ip netns exec ns0 ip route add default dev veth0-ns
ip link set veth0-br up
ip route add 10.0.1.1 dev veth0-br #和node1上的操作有区别的地方
sysctl -w net.ipv4.conf.veth0-br.proxy_arp=1
sysctl -w net.ipv4.ip_forward=1
复制代码
在 node1 和 node2 上设置如下环境变量
ID='186'
KEY='0x7482b075e3ec7e11a20e1edc1e9a7a459ada760c' # 加解密密钥
NODE1_NW="172.17.0.2" # node1的内网地址,根据实际情况修改
NODE1_GW="124.221.146.81" # node1的弹性公网IP,根据实际情况修改
NODE2_NW="172.17.0.7" # node2的内网地址,根据实际情况修改
NODE2_GW="124.220.14.201" # node2的弹性公网IP,根据实际情况修改
复制代码
执行如下命令,node1 配置流量的加解密规则:
ip xfrm state add src $NODE1_NW dst $NODE2_GW proto esp spi $ID reqid $ID mode tunnel aead 'rfc4106(gcm(aes))' $KEY 128
ip xfrm state add src $NODE2_GW dst $NODE1_NW proto esp spi $ID reqid $ID mode tunnel aead 'rfc4106(gcm(aes))' $KEY 128
复制代码
参数说明:
src 和 dst 是对应的包的源地址和目的地址,例如:对于 node1 节点发出去的包,源地址是自身的内网节点,目的地址为 node2 的公网地址。
proto esp spi $ID 表示使用 ESP 协议,$ID 可以按需要指定或者随机生成,加解密相关的参数。
mode tunnel 指定使用隧道模式。
aead 'rfc4106(gcm(aes))' $KEY 128 表示使用 aes-gcm 加密,最后的 128 表示 aes-gcm 的 Integrity Check Value (ICV) 长度。
node1 节点配置未加密流量的匹配规则:
ip xfrm policy add src 10.0.0.0/24 dst 10.0.1.0/24 dir out \
tmpl src $NODE1_NW dst $NODE2_GW proto esp reqid $ID mode tunnel
ip xfrm policy add src 10.0.1.0/24 dst 10.0.0.0/24 dir fwd \
tmpl src $NODE2_GW dst $NODE1_NW proto esp reqid $ID mode tunnel
ip xfrm policy add src 10.0.1.0/24 dst 10.0.0.0/24 dir in \
tmpl src $NODE2_GW dst $NODE1_NW proto esp reqid $ID mode tunnel
复制代码
参数说明:
src 和 dst,用来匹配未加密前的 IP 报文源地址和目的地址。
dir 表示 direction,有 out/fwd/in 三种方向。所有出站数据走 out 规则,入站数据走 fwd 和 in 规则。
tmpl 后面的内容表示匹配的 ip xfrm state 规则,以匹配对应的加解密处理。
同理,node2 配置加解密算法和匹配规则:
ip xfrm state add src $NODE2_NW dst $NODE1_GW proto esp spi $ID reqid $ID mode tunnel aead 'rfc4106(gcm(aes))' $KEY 128
ip xfrm state add src $NODE1_GW dst $NODE2_NW proto esp spi $ID reqid $ID mode tunnel aead 'rfc4106(gcm(aes))' $KEY 128
ip xfrm policy add src 10.0.1.0/24 dst 10.0.0.0/24 dir out \
tmpl src $NODE2_NW dst $NODE1_GW proto esp reqid $ID mode tunnel
ip xfrm policy add src 10.0.0.0/24 dst 10.0.1.0/24 dir fwd \
tmpl src $NODE1_GW dst $NODE2_NW proto esp reqid $ID mode tunnel
ip xfrm policy add src 10.0.0.0/24 dst 10.0.1.0/24 dir in \
tmpl src $NODE1_GW dst $NODE2_NW proto esp reqid $ID mode tunnel
复制代码
在 node1 测试 ping node2 的容器 IP,可以看到
注意点:
IPsec 隧道基于内核的规则来运行,并没有产生新的虚拟网卡设备,也无需创建路由。
有些环境 iptables 的默认规则会限制流量转发,可以使用 iptables -P FORWARD ACCEPT 命令临时放开转发,同时关闭防火墙
2 跨公网纳管集群实战
Kosmos 的新版本将封装使用 IPsec 隧道跨公网通信的能力,前面介绍过了 IPsec 隧道的原理,本节我们使用 Kosmos 实战纳管集群,并测试跨公网 Kubernetes 集群的容器网络通信。
环境准备
我们自建了两套 Kubernetes 集群,环境如下:
集群 1
集群 2
其他信息
开始测试
首先准备好集群 1 的 kubeconfig,将集群 1 作为主集群,执行如下命令进行主集群的安装操作
kosmosctl install --kubeconfig /root/master --default-nic eth0 --cni calico --private-image-registry sealos.hub:5000/kosmos-io --ip-family ipv4 --node-elasticip vm-0-7-rockylinux=124.220.61.33,vm-0-10-rockylinux=150.158.88.185
复制代码
参数说明:
参数的 node-elasticip 的值,vm-0-7-rockylinux 和 vm-0-10-rockylinux 都是集群中节点的 nodename,124.220.61.33 和 150.158.88.185 都是对应节点上绑定的弹性公网 IP。
参数 cni 根据实际情况填写,如 calico、flannel 等,参数 default-nic 根据实际情况填写,云主机一般是 eth0。参数 private-image-registry 是测试私有镜像仓库的地址,需要提前下载 Kosmos 相关的镜像传入,如不配置此项则在公网拉取镜像。
参数 kubeconfig 的值/root/master 为集群 1 的 kubeconfig,要注意的是要把其中的 server 地址配置为公网的地址,在这个场景下为 124.220.61.33 ,另外在某些环境如果公网地址没有配置在 apiserver 的证书中,我们可以把 kubeconfig 文件中的 cluster 部分设置为 insecure-skip-tls-verify: true,类似如下形式:
install 命令执行完成后,如下所示
其中/root/slave 为集群 2 的 kubeconfig,要和/root/master 做同样的修改。结果如下所示
执行完以上操作之后,可以在主集群检查下 Pod 状态,如下所示
此时我们可以在任一集群内找一个非 hostnetwork 模式的 Pod,访问另一个集群内的非 hostnetwork 模式的容器的 IP,我们以 coredns 为例,参考如下命令找到 coredns 容器的主进程
crictl ps --name coredns -q # 如果有返回值,说名coredns在当前节点启动,执行如下命令
crictl inspect --output go-template --template "{{ .info.pid }}" $(crictl ps --name coredns -q| head -1) # 此命令的返回值即为coredns容器的主进程,假设返回值为16494,下面要用到
复制代码
使用 nsenter 命令进入容器的主进程的网络命名空间下,此时通过 ip a 命令可以看到 IP 地址为容器的 IP,而不是宿主机的 IP,如下所示
在该网络命名空间下测试 ping 另一个集群内的 coredns 的 Pod,可以成功 ping 通,如下所示
我们应该还记得 100.64.0.0/10 是属于集群 1 的网段,100.128.0.0/10 是属于集群 2 的网段,上面的测试用,IP 100.68.215.196 属于集群 1,IP 100.164.13.193 属于集群 2,至此,我们拉通了集群 1 和集群 2 的容器网络,并做了基本的验证。
IPsec 规则展示
前面我们已经验证了 Kosmos 跨公网容器通信的能力,本小节,我们通过抓包和 ip xfrm 相关命令来验证流量通过 IPsec 隧道转发。
在上面的测试环境中,我们可以在网关节点(Kosmos 网络模块中的概念)查看相关的配置,如下所示网关节点的 ROLE 被标注为"gateway":
通过 ip xfrm policy 和 ip xfrm state 命令查看已配置的内容。
在环境中查看 ip xfrm policy 内容如下所示:
查看 ip xfrm state 内容如下所示:
IPsec 通过 ESP 协议通信,我们在 ping 容器 IP 的同时在对端宿主机网卡上抓包效果如下:
代码实现简单介绍
本节,我们对 Komos 跨公网通信的代码实现做一些简单介绍。
我们在测试的时候,在执行 install 和 join 的时候会输入公网 IP 和节点的映射关系,这个映射关系会体现在 Cluster(Kosmos 网络模块的一个 CRD)中,clusterlink-controller-manager 模块会侦听 Kubernetes 集群的 node 的状态,并把相关的配置同步到 ClusterNode(Kosmos 网络模块的一个 CRD)中,相关代码如下:
elasticIPMap := cluster.Spec.ClusterLinkOptions.NodeElasticIPMap
if len(elasticIPMap) != 0 {
if elasticIPtoParse, ok := elasticIPMap[node.Name]; ok {
_, proto := ParseIP(elasticIPtoParse)
// Now elasticIP only support IPv4
if proto == 4 {
elasticIP = elasticIPtoParse
}
}
}
复制代码
network-manager 模块通过侦听 ClusterNode 的状态进行调和,根据源和目的两端是否都配置了公网 IP 决定是否配置通过 IPsec 隧道进行通信,我们配置 IPsec 规则的代码大致如下
if len(target.Spec.ElasticIP) > 0 && len(n.Spec.ElasticIP) > 0 {
nCluster := ctx.Filter.GetClusterByName(n.Spec.ClusterName)
var nPodCIDRs []string
if nCluster.IsP2P() {
nPodCIDRs = n.Spec.PodCIDRs
} else {
nPodCIDRs = nCluster.Status.ClusterLinkStatus.PodCIDRs
}
nPodCIDRs = FilterByIPFamily(nPodCIDRs, nCluster.Spec.ClusterLinkOptions.IPFamily)
nPodCIDRs = ConvertToGlobalCIDRs(nPodCIDRs, nCluster.Spec.ClusterLinkOptions.GlobalCIDRsMap)
ctx.Results[n.Name].XfrmStates = append(ctx.Results[n.Name].XfrmStates, v1alpha1.XfrmState{
LeftIP: n.Spec.IP,
RightIP: target.Spec.ElasticIP,
ReqID: v1alpha1.ReqID,
PSK: v1alpha1.PSK,
})
............................
for _, ncidr := range nPodCIDRs {
ctx.Results[n.Name].XfrmPolicies = append(ctx.Results[n.Name].XfrmPolicies, v1alpha1.XfrmPolicy{
LeftIP: n.Spec.IP,
LeftNet: ncidr,
RightIP: target.Spec.ElasticIP,
RightNet: cidr,
ReqID: v1alpha1.ReqID,
Dir: int(v1alpha1.IPSECOut),
})
............................
}
}
复制代码
配置完成后,最后会将 IPsec 隧道的规则写在 NodeConfig(Kosmos 网络模块的一个 CRD)中,Kosmos 的 agent 模块会侦听 NodeConfig,根据相关规则在节点上创建相应的规则。
相关代码大致如下
func (n *DefaultNetWork) AddXfrmPolicies(xfrmpolicies []clusterlinkv1alpha1.XfrmPolicy) error {
for _, xfrmpolicy := range xfrmpolicies {
srcIP := net.ParseIP(xfrmpolicy.LeftIP)
dstIP := net.ParseIP(xfrmpolicy.RightIP)
_, srcNet, _ := net.ParseCIDR(xfrmpolicy.LeftNet)
_, dstNet, _ := net.ParseCIDR(xfrmpolicy.RightNet)
reqID := xfrmpolicy.ReqID
var err error
var xfrmpolicydir netlink.Dir
switch v1alpha1.IPSECDirection(xfrmpolicy.Dir) {
case v1alpha1.IPSECOut:
xfrmpolicydir = netlink.XFRM_DIR_OUT
case v1alpha1.IPSECIn:
xfrmpolicydir = netlink.XFRM_DIR_IN
case v1alpha1.IPSECFwd:
xfrmpolicydir = netlink.XFRM_DIR_FWD
}
err = AddXFRMPolicy(srcNet, dstNet, srcIP, dstIP, xfrmpolicydir, reqID)
if err != nil {
return fmt.Errorf("error adding ipsec out policy: %v", err)
}
}
return nil
}
复制代码
关于 IPsec 规则的创建,部分代码我们借鉴了 Flannel,有些区别的是 Flannel 部分借助了 Strongswan(配置 IPsec 规则的开源工具)的能力,而我们在 Kosmos 中直接进行 ip xfrm 相关的操作,这样无需把 Strongswan 的二进制工具打到镜像内,可以减少镜像包的大小,但如果引入 Strongswan 可以更加容易的应对复杂的网络场景,后续我们会考虑引入以处理复杂的跨公网集群网络拓扑。
3 跨云实战
前面我们介绍了 IPsec 模拟容器网通信和跨公网纳管自建集群,本小节,我们将演示一个跨云的多集群案例,纳管在云厂商订购的 Kubernetes 集群。我们将部署一个跨云的 MySQL 实例,其主备实例分别启动在两个云厂商的 Kubernetes 集群中,并测试主备数据同步。
跨云纳管集群
首先我们在移动云订购一个 KCS 集群作为主集群,参考上面章节的命令执行 kosmosctl install 相关的操作以部署主集群的 Kosmos 服务。
然后我们需要部署一套开源的 operator,并使用 Kosmos 的调度器替换 Kubernetes 原始的调度器,方案参考我们的公众号文章:Kosmos 实战系列:MySQL Operator 有状态服务的跨 AZ 集群平滑迁移
接下来我们需要在腾讯云订购一个 TKE 集群(测试中我们订购了 GlobalRouter 作为 CNI 的 TKE 集群)。
最后,在腾讯云和移动云的安全组中对两端公网弹性 IP 和容器网络的网段进行放通。
执行如下命令进行纳管腾讯 TKE 集群作为从集群:
kosmosctl join cluster --name member-tencent --kubeconfig /root/member-kubeconfig --cni globalrouter --default-nic eth0 --ip-family ipv4 --enable-all --node-elasticip 172.17.128.10=$EIP --cluster-pod-cidrs 172.16.0.0/20
复制代码
参数 $EIP 是提前设置的环境变量,请根据实际情况配置、填写。
参数 cni 根据实际情况填写,本次测试中我们的 TKE 集群使用 Global Router 作为 CNI 插件。
cluster-pod-cidrs 是 TKE 容器网络的网段,可以在 TKE 的控制台查到,Global Router 是腾讯 TKE 自有的 CNI 插件,我们暂时没查到好的方式去获取 Pod CIDR,因此配置了纳管集群时人为输入,对于 Calico、Flannel 这种常见的开源 CNI 插件无需人为输入此参数。
执行完上面命令,就完成了集群的纳管,此时集群之间的容器网络就打通了,下面我们测试部署一套 MySQL 主备实例。
测试 MySQL 主备实例
在移动云 KCS 集群中创建如下 MySQL 实例和配套的 secret
apiVersion: mysql.presslabs.org/v1alpha1
kind: MysqlCluster
metadata:
name: mysql-cluster-e2e
namespace: kosmos-e2e
spec:
replicas: 2
secretName: my-secret
image: docker.io/percona:5.7
mysqlVersion: "5.7"
mysqlConf:
podSpec:
tolerations:
- key: "kosmos.io/node"
operator: "Equal"
value: "true"
effect: "NoSchedule"
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- kcs-kosmos-s-gg5dg
- kosmos-member-tencent
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
mysql.presslabs.org/cluster: mysql-cluster-e2e
topologyKey: kubernetes.io/hostname
volumeSpec:
persistentVolumeClaim:
storageClassName: openebs-hostpath
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 0.5Gi
---
apiVersion: v1
data:
ROOT_PASSWORD: aGVsbG93b3JsZA==
kind: Secret
metadata:
name: my-secret
namespace: kosmos-e2e
type: Opaque
复制代码
这样我们就会创建一个 MySQL 主备实例,可以从上面实例的节点亲和性配置中可以看到,主备 MySQL 实例 Pod 分别启动在 KCS 集群中的 kcs-kosmos-s-gg5dg 节点和我们的从集群节点 kosmos-member-tencent(该节点为从集群在主集群中的映射)。
如下所示:
接下来我们登入到腾讯云 TKE 集群中,进入 MySQL 主实例中,在实例中写入数据,如下所示:
此时,在移动云 KCS 集群中登陆 MySQL 的备实例,可以正常查询到数据,如下所示:
前面的章节我们用 ping 命令简单验证了跨公网容器网络的连通性,本节我们部署了 MySQL 的主备实例并测试了数据同步,从应用的层面上实践了跨腾讯云 TKE 和移动云 KCS 的容器网络打通。
测试整体流程效果如下所示:
https://mp.weixin.qq.com/s?__biz=Mzg5Mjc2OTU4MA==&mid=2247486956&idx=1&sn=76d7a796b2a2765739de07c9f6ca2191&chksm=c03849aff74fc0b94b97161d288d4fef4036e9bfc9030bbd243643647ca0f00790c54a4f1439&token=33473390&lang=zh_CN#rd
关于 CNI 插件的适配
除了 IPsec 隧道的构建,跨云容器网络通信还需要考虑到 CNI 的差异,主要有两个方面:
对于第一点,一些常见的 CNI 如 Calico、Flannel,我们已经完成了适配。Calico 的 Pod CIDR 是通过 ippool 管理的,Flannel 的 cidr 是配置在 configmap 中的,Kosmos 的网络模块会根据 ippool 或者特定的 configmap 去配置容器网络的 CIDR。有一些网络插件并不是开源的,我们可能没有好的方法去获取 Pod CIDR,这个时候就需要手动输入了,因此我们配置了 cluster-pod-cidrs 这个参数。
对于第二点,calico 也是通过在 ippool 进行配置的,所以在 Calico 作为 CNI 的 Kubernetes 集群中,会看到 Kosmos 创建的 ippool,不要感到意外,我们会将其的 disabled 值设置为 true,所以 Calico 不会真的使用这个 ippool 去分配 Pod 的 IP。对于一些我们验证过的网络插件,如 Flannel 和 GlobalRouter,我们会在 iptables nat 表的 POSTROUTING 链中设置一条规则,使得发往目的地时不进行地址伪装以保持容器的 IP,这样在 Kosmos 的回程路由才能工作正常。Kosmos 添加的 iptables 规则如下所示:
4 总结
本文我们介绍了使用 Kosmos 打通多云环境下的跨公网通信的 Kubernetes 集群的容器网络,首先是对 IPsec 进行了简单的介绍,然后使用 Kosmos 实践了跨公网的容器网络通信,并对相关的原理和代码实现进行了简单说明,最后我们演示了一个跨移动云 KCS 和腾讯云 TKE 的实操案例。
当前我们的跨公网通信仅支持 IPv4,在未来我们会继续开发 IPv6 相关的适配,对于复杂的网络联通情况也会进行考虑,如 Strongswan 介绍中的 Roadwarrior 等网络拓扑模式,当前流量的加解密是通过 PSK(Pre-shared Key),后续也会考虑其他的加解密模式以适配不同的安全需求。
由于笔者能力所限,难免存在错漏,请各位批评指正。
5 联系我们
扫描二维码联系我们!
评论