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

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

出于容灾考虑,应用的部署可能会有跨云、云内跨地域可用区(跨vpc)的多集群的需求,此时容器的通信就出现了一定的困难,如果没有专线,那么机器的内网IP一般无法直接通信,CNI常用的隧道技术如vxlan/ipip在公网环境下也可能会失效。在此基础上,Kosmos基于IPsec隧道实现了跨云公网传输场景下的容器网络通信方案,解决了跨公网通信的需求,也兼顾了传输安全问题,下面我们进行简单的演示说明。

原理介绍

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

主机名弹性公网IP内网IP容器网段规划
node1124.221.146.81172.17.0.210.0.0.0/24
node2124.220.14.201172.17.0.1010.0.1.0/24

其他信息

操作系统版本内核版本
Rocky Linux release 8.64.18.0-477.27.1.el8_8.x86_64

我们模拟实现的目标:在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,可以看到

image

注意点:

  1. IPsec隧道基于内核的规则来运行,并没有产生新的虚拟网卡设备,也无需创建路由。
  2. 有些环境iptables的默认规则会限制流量转发,可以使用iptables -P FORWARD ACCEPT命令临时放开转发,同时关闭防火墙

跨公网纳管集群实战

Kosmos的新版本将封装使用IPsec隧道跨公网通信的能力,前面介绍过了IPsec隧道的原理,本节我们使用Kosmos实战纳管集群,并测试跨公网Kubernetes集群的容器网络通信。

环境准备

我们自建了两套Kubernetes集群,环境如下:

集群1

主机名弹性公网IP内网IPsvc cidrpod cidr
vm-0-7-rockylinux124.220.61.33172.17.0.710.95.0.0/22100.64.0.0/10
vm-0-10-rockylinux150.158.88.185172.17.0.10

集群2

主机名弹性公网IP内网IPsvc cidrpod cidr
vm-16-13-rockylinux119.45.212.9210.206.16.1310.96.0.0/22100.128.0.0/10
vm-16-14-rockylinux43.137.38.210.206.16.14
vm-16-2-rockylinux43.137.2.3510.206.16.2

其他信息

操作系统版本内核版本kubernetes版本CNI容器运行时
Rocky Linux release 8.64.18.0-477.27.1.el8_8.x86_64v1.25.9(使用sealos部署)calico.v3.25.1containerd

开始测试

首先准备好集群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,类似如下形式:

image

install命令执行完成后,如下所示

image

其中/root/slave 为集群2的kubeconfig,要和/root/master做同样的修改。结果如下所示

image

执行完以上操作之后,可以在主集群检查下Pod状态,如下所示

image

此时我们可以在任一集群内找一个非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,如下所示

image

在该网络命名空间下测试ping另一个集群内的coredns的Pod,可以成功ping通,如下所示

image

我们应该还记得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":

image

通过ip xfrm policy 和 ip xfrm state 命令查看已配置的内容。

在环境中查看ip xfrm policy内容如下所示:

image

查看ip xfrm state内容如下所示:

image

IPsec通过ESP协议通信,我们在ping容器IP的同时在对端宿主机网卡上抓包效果如下:

image

代码实现简单介绍

本节,我们对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可以更加容易的应对复杂的网络场景,后续我们会考虑引入以处理复杂的跨公网集群网络拓扑。

跨云实战

前面我们介绍了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(该节点为从集群在主集群中的映射)。

如下所示:

image

接下来我们登入到腾讯云TKE集群中,进入MySQL主实例中,在实例中写入数据,如下所示:

image

此时,在移动云KCS集群中登陆MySQL的备实例,可以正常查询到数据,如下所示:

image

前面的章节我们用ping命令简单验证了跨公网容器网络的连通性,本节我们部署了MySQL的主备实例并测试了数据同步,从应用的层面上实践了跨腾讯云TKE和移动云KCS的容器网络打通。

测试整体流程效果如下所示:
https://live.csdn.net/v/354435?spm=1001.2014.3001.5501

关于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规则如下所示:

image

总结

本文我们介绍了使用Kosmos打通多云环境下的跨公网通信的Kubernetes集群的容器网络,首先是对IPsec进行了简单的介绍,然后使用Kosmos实践了跨公网的容器网络通信,并对相关的原理和代码实现进行了简单说明,最后我们演示了一个跨移动云KCS和腾讯云TKE的实操案例。

当前我们的跨公网通信仅支持IPv4,在未来我们会继续开发IPv6相关的适配,对于复杂的网络联通情况也会进行考虑,如Strongswan介绍中的Roadwarrior等网络拓扑模式,当前流量的加解密是通过PSK(Pre-shared Key),后续也会考虑其他的加解密模式以适配不同的安全需求。

由于笔者能力所限,难免存在错漏,请各位批评指正。

联系我们

扫描二维码联系我们!
image

本文由mdnice多平台发布

  • 26
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值