写点什么

Kosmos 实战系列:有状态服务(MySQL)跨云灾备实战

作者:畅聊云原生
  • 2023-12-26
    江苏
  • 本文字数:8534 字

    阅读完需:约 28 分钟

作者:董卫国,中国移动云能力中心软件研发工程师,专注于云原生、微服务、算力网络等

出于容灾考虑,应用的部署可能会有跨云、云内跨地域可用区(跨 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# 新增netnsip link add veth0-ns type veth peer name veth0-br # 新增vethip link set veth0-ns netns ns0 # 将veth的一端移动到netns中# 将netns中的本地环回和veth启动并配置IPip netns exec ns0 ip link set lo upip netns exec ns0 ip link set veth0-ns upip 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上容器的IPip netns exec ns0 ip route add default dev veth0-ns # 容器网络内添加默认路由ip link set veth0-br upip route add 10.0.0.1 dev veth0-brsysctl -w net.ipv4.conf.veth0-br.proxy_arp=1sysctl -w net.ipv4.ip_forward=1 # 允许转发
复制代码

node2 做类似操作如下:

ip netns add ns0ip link add veth0-ns type veth peer name veth0-brip link set veth0-ns netns ns0ip netns exec ns0 ip link set lo upip netns exec ns0 ip link set veth0-ns upip 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-nsip link set veth0-br upip route add 10.0.1.1 dev veth0-br #和node1上的操作有区别的地方sysctl -w net.ipv4.conf.veth0-br.proxy_arp=1sysctl -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 128ip 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 tunnelip 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 tunnelip 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 128ip xfrm state add src $NODE1_GW dst $NODE2_NW proto esp spi $ID reqid $ID mode tunnel aead 'rfc4106(gcm(aes))' $KEY 128ip 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 tunnelip 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 tunnelip 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,可以看到


注意点:

  1. IPsec 隧道基于内核的规则来运行,并没有产生新的虚拟网卡设备,也无需创建路由。

  2. 有些环境 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/v1alpha1kind: MysqlClustermetadata:  name: mysql-cluster-e2e  namespace: kosmos-e2espec:  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: v1data:  ROOT_PASSWORD: aGVsbG93b3JsZA==kind: Secretmetadata:  name: my-secret  namespace: kosmos-e2etype: 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 插件的 Pod CIDR 的管理方式可能有所不同

  • 一个是容器访问非本机器的容器网络地址会进行地址伪装。

对于第一点,一些常见的 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 联系我们


扫描二维码联系我们!


发布于: 刚刚阅读数: 8
用户头像

还未添加个人签名 2023-10-13 加入

还未添加个人简介

评论

发布
暂无评论
Kosmos实战系列:有状态服务(MySQL)跨云灾备实战_畅聊云原生_InfoQ写作社区