由于AWS、GCP和Azure提供的服务,开发跨越多个地区的应用程序已经变得相对容易。这很好,因为缓慢的应用程序会扼杀企业。这些应用有一个共同的问题:它们不被多区域数据库架构所支持。
在这篇博客中,我将为让Kubernetes pods在多区域部署中相互对话的问题提供一个解决方案。
让我备份一下:CockroachDB经常被部署在Kubernetes内部。这是因为CockroachDB是一个单一的二进制文件,这使得它非常适合在容器和Kubernetes中运行(你可以在这里阅读更多信息)。然而,当我们希望在多个地区或云供应商之间部署CockroachDB时,这给我们带来了一些挑战。Kubernetes的设计是为我们的容器化工作负载提供一个pod网络,以便相互之间进行交流。这个网络通常不会暴露在Kubernetes集群之外。这是一个问题,因为所有的CockroachDB pods都需要相互交流。
一个典型的部署模式是,每个区域都有自己的Kubernetes集群。这样做是非常合理的,可以确保延迟降到最低,而且如果网络中断,工作节点也不会成为 "孤岛"。这对CockroachDB来说是个问题,因为pod网络在局域网上是不能路由的,当然也不能跨区域看到。我们如何克服这个问题呢?
Kubernetes是一个允许整合许多不同组件的框架。从网络的角度来看,可以按照容器网络接口(CNI)标准来开发接口。这允许我们使用不同的网络插件来为我们提供不同的能力。
在这种情况下,我们需要集群间的pod路由。由超大规模公司开发的CNI允许通过在虚拟网络上给pod一个IP地址来实现。这很有帮助,但是如果我们不在公有云中,或者想在不同的云供应商中对单一的CNI进行标准化呢?在这里,我们可以使用Cilium,这个CNI有一个叫做Cluster Mesh的功能,它允许我们将不同的Kubernetes集群 "网格化",并进行跨集群的pod-to-pod通信。让我们来看看如何更详细地实现这一目标。
设置多区域的Azure网络配置和虚拟机
我们需要做的第一件事是准备基础设施。在这个例子中,我们将使用Azure,但你可以使用其他云,如AWS或GCP,甚至是它们的混合。
为了使其可重复,我使用了Azure CLI 来创建所有的资源,首先是网络。我们创建一个资源组,这只是一个逻辑容器,用来存储我们所有的资源。虽然一个资源组属于一个特定的区域,但它可以包含其他区域的资源,就像我们的情况一样。
az group create --name $rg --location $loc1
我们现在在我们选择的每个区域创建一个虚拟网络。我们给每个虚拟网络一个地址前缀,以及地址前缀范围内的一个子网。所有的区域都没有重叠的地址空间,这对确保路由工作的预期和所有CockroachDB节点能够进行通信非常重要。
az network vnet create -g $rg -l $loc1 -n crdb-$loc1 --address-prefix 10.1.0.0/16 \
--subnet-name crdb-$loc1-sub1 --subnet-prefix 10.1.1.0/24
az network vnet create -g $rg -l $loc2 -n crdb-$loc2 --address-prefix 10.2.0.0/16 \
--subnet-name crdb-$loc2-sub1 --subnet-prefix 10.2.1.0/24
az network vnet create -g $rg -l $loc3 -n crdb-$loc3 --address-prefix 10.3.0.0/16 \
--subnet-name crdb-$loc3-sub1 --subnet-prefix 10.3.1.0/24
默认情况下,虚拟网络是不能相互通信的。因此,我们需要创建虚拟网络对等物,每个区域两个。
az network vnet peering create -g $rg -n $loc1-$loc2-peer --vnet-name crdb-$loc1 \
--remote-vnet crdb-$loc2 --allow-vnet-access --allow-forwarded-traffic --allow-gateway-transit
az network vnet peering create -g $rg -n $loc2-$loc3-peer --vnet-name crdb-$loc2 \
--remote-vnet crdb-$loc3 --allow-vnet-access --allow-forwarded-traffic --allow-gateway-transit
az network vnet peering create -g $rg -n $loc1-$loc3-peer --vnet-name crdb-$loc1 \
--remote-vnet crdb-$loc3 --allow-vnet-access --allow-forwarded-traffic --allow-gateway-transit
az network vnet peering create -g $rg -n $loc2-$loc1-peer --vnet-name crdb-$loc2 \
--remote-vnet crdb-$loc1 --allow-vnet-access --allow-forwarded-traffic --allow-gateway-transit
az network vnet peering create -g $rg -n $loc3-$loc2-peer --vnet-name crdb-$loc3 \
--remote-vnet crdb-$loc2 --allow-vnet-access --allow-forwarded-traffic --allow-gateway-transit
az network vnet peering create -g $rg -n $loc3-$loc1-peer --vnet-name crdb-$loc3 \
--remote-vnet crdb-$loc1 --allow-vnet-access --allow-forwarded-traffic --allow-gateway-transit
在Azure中,网络适配器本身就是一种资源。这些适配器与一个特定的子网和虚拟机相关。
az network public-ip create --resource-group $rg --location $loc1 --name crdb-$loc1-ip1 --sku standard
az network public-ip create --resource-group $rg --location $loc1 --name crdb-$loc1-ip2 --sku standard
az network public-ip create --resource-group $rg --location $loc1 --name crdb-$loc1-ip3 --sku standard
az network public-ip create --resource-group $rg --location $loc2 --name crdb-$loc2-ip1 --sku standard
az network public-ip create --resource-group $rg --location $loc2 --name crdb-$loc2-ip2 --sku standard
az network public-ip create --resource-group $rg --location $loc2 --name crdb-$loc2-ip3 --sku standard
az network public-ip create --resource-group $rg --location $loc3 --name crdb-$loc3-ip1 --sku standard
az network public-ip create --resource-group $rg --location $loc3 --name crdb-$loc3-ip2 --sku standard
az network public-ip create --resource-group $rg --location $loc3 --name crdb-$loc3-ip3 --sku standard
az network nic create --resource-group $rg -l $loc1 --name crdb-$loc1-nic1 --vnet-name crdb-$loc1 --subnet crdb-$loc1-sub1 --network-security-group crdb-$loc1-nsg --public-ip-address crdb-$loc1-ip1
az network nic create --resource-group $rg -l $loc1 --name crdb-$loc1-nic2 --vnet-name crdb-$loc1 --subnet crdb-$loc1-sub1 --network-security-group crdb-$loc1-nsg --public-ip-address crdb-$loc1-ip2
az network nic create --resource-group $rg -l $loc1 --name crdb-$loc1-nic3 --vnet-name crdb-$loc1 --subnet crdb-$loc1-sub1 --network-security-group crdb-$loc1-nsg --public-ip-address crdb-$loc1-ip3
az network nic create --resource-group $rg -l $loc2 --name crdb-$loc2-nic1 --vnet-name crdb-$loc2 --subnet crdb-$loc2-sub1 --network-security-group crdb-$loc2-nsg --public-ip-address crdb-$loc2-ip1
az network nic create --resource-group $rg -l $loc2 --name crdb-$loc2-nic2 --vnet-name crdb-$loc2 --subnet crdb-$loc2-sub1 --network-security-group crdb-$loc2-nsg --public-ip-address crdb-$loc2-ip2
az network nic create --resource-group $rg -l $loc2 --name crdb-$loc2-nic3 --vnet-name crdb-$loc2 --subnet crdb-$loc2-sub1 --network-security-group crdb-$loc2-nsg --public-ip-address crdb-$loc2-ip3
az network nic create --resource-group $rg -l $loc3 --name crdb-$loc3-nic1 --vnet-name crdb-$loc3 --subnet crdb-$loc3-sub1 --network-security-group crdb-$loc3-nsg --public-ip-address crdb-$loc3-ip1
az network nic create --resource-group $rg -l $loc3 --name crdb-$loc3-nic2 --vnet-name crdb-$loc3 --subnet crdb-$loc3-sub1 --network-security-group crdb-$loc3-nsg --public-ip-address crdb-$loc3-ip2
az network nic create --resource-group $rg -l $loc3 --name crdb-$loc3-nic3 --vnet-name crdb-$loc3 --subnet crdb-$loc3-sub1 --network-security-group crdb-$loc3-nsg --public-ip-address crdb-$loc3-ip3
网络配置的最后一部分是网络安全组。这些组从网络角度控制对资源的访问。在这个演示中,我们将允许访问SSH的22号端口、访问Kubernetes API的6443号端口以及Kubernetes的NodePort范围30000-32767。
第一步:在每个区域创建一个NSG。
az network nsg create --resource-group $rg --location $loc1 --name crdb-$loc1-nsg
az network nsg create --resource-group $rg --location $loc2 --name crdb-$loc2-nsg
az network nsg create --resource-group $rg --location $loc3 --name crdb-$loc3-nsg
第二步:允许SSH访问
在每个NSG中创建一个规则,允许SSH访问虚拟机,使我们能够通过SSH和k3sup部署Kubernetes。
az network nsg rule create -g $rg --nsg-name crdb-$loc1-nsg -n NsgRuleSSH --priority 100 \
--source-address-prefixes '*' --source-port-ranges '*' \
--destination-address-prefixes '*' --destination-port-ranges 22 --access Allow \
--protocol Tcp --description "Allow SSH Access to all VMS."
az network nsg rule create -g $rg --nsg-name crdb-$loc2-nsg -n NsgRuleSSH --priority 100 \
--source-address-prefixes '*' --source-port-ranges '*' \
--destination-address-prefixes '*' --destination-port-ranges 22 --access Allow \
--protocol Tcp --description "Allow SSH Access to all VMS."
az network nsg rule create -g $rg --nsg-name crdb-$loc3-nsg -n NsgRuleSSH --priority 100 \
--source-address-prefixes '*' --source-port-ranges '*' \
--destination-address-prefixes '*' --destination-port-ranges 22 --access Allow \
--protocol Tcp --description "Allow SSH Access to all VMS."
第三步。允许Kubernetes API访问
在所有区域创建一个规则,允许访问每个集群的Kubernetes API。这将使我们能够在每个集群中创建所需的资源来运行CockroachDB。
az network nsg rule create -g $rg --nsg-name crdb-$loc1-nsg -n NsgRulek8sAPI --priority 200 \
--source-address-prefixes '*' --source-port-ranges '*' \
--destination-address-prefixes '*' --destination-port-ranges 6443 --access Allow \
--protocol Tcp --description "Allow Kubernetes API Access to all VMS."
az network nsg rule create -g $rg --nsg-name crdb-$loc2-nsg -n NsgRulek8sAPI --priority 200 \
--source-address-prefixes '*' --source-port-ranges '*' \
--destination-address-prefixes '*' --destination-port-ranges 6443 --access Allow \
--protocol Tcp --description "Allow Kubernetes API Access to all VMS."
az network nsg rule create -g $rg --nsg-name crdb-$loc3-nsg -n NsgRulek8sAPI --priority 200 \
--source-address-prefixes '*' --source-port-ranges '*' \
--destination-address-prefixes '*' --destination-port-ranges 6443 --access Allow \
--protocol Tcp --description "Allow Kubernetes API Access to all VMS."
第四步。允许NodePort访问
创建一个规则,开放对Kubernetes NodePort范围的访问,以允许访问我们暴露的任何资源。
az network nsg rule create -g $rg --nsg-name crdb-$loc1-nsg -n NsgRuleNodePorts --priority 300 \
--source-address-prefixes '*' --source-port-ranges '*' \
--destination-address-prefixes '*' --destination-port-ranges 30000-32767 --access Allow \
--protocol Tcp --description "Allow Kubernetes NodePort Access to all VMS."
az network nsg rule create -g $rg --nsg-name crdb-$loc2-nsg -n NsgRuleNodePorts --priority 300 \
--source-address-prefixes '*' --source-port-ranges '*' \
--destination-address-prefixes '*' --destination-port-ranges 30000-32767 --access Allow \
--protocol Tcp --description "Allow Kubernetes NodePort Access to all VMS."
az network nsg rule create -g $rg --nsg-name crdb-$loc3-nsg -n NsgRuleNodePorts --priority 300 \
--source-address-prefixes '*' --source-port-ranges '*' \
--destination-address-prefixes '*' --destination-port-ranges 30000-32767 --access Allow \
--protocol Tcp --description "Allow Kubernetes NodePort Access to all VMS."
请记住,这只是一个演示,并不是为生产使用而设计的,所以在现实世界的场景中要考虑更多的限制性规则。下面是一个描述这些资源的图表。
基础设施的最后一个元素是九个虚拟机,它们将支持Kubernetes的安装。每个区域将部署三个虚拟机。
区域一。
az vm create \
--resource-group $rg \
--location $loc1 \
--name crdb-$loc1-node1 \
--image UbuntuLTS \
--nics crdb-$loc1-nic1 \
--admin-username ubuntu \
--generate-ssh-keys
az vm create \
--resource-group $rg \
--location $loc1 \
--name crdb-$loc1-node2 \
--image UbuntuLTS \
--nics crdb-$loc1-nic2 \
--admin-username ubuntu \
--generate-ssh-keys
az vm create \
--resource-group $rg \
--location $loc1 \
--name crdb-$loc1-node3 \
--image UbuntuLTS \
--nics crdb-$loc1-nic3 \
--admin-username ubuntu \
--generate-ssh-keys
区域二。
az vm create \
--resource-group $rg \
--location $loc2 \
--name crdb-$loc2-node1 \
--image UbuntuLTS \
--nics crdb-$loc2-nic1 \
--admin-username ubuntu \
--generate-ssh-keys
az vm create \
--resource-group $rg \
--location $loc2 \
--name crdb-$loc2-node2 \
--image UbuntuLTS \
--nics crdb-$loc2-nic2 \
--admin-username ubuntu \
--generate-ssh-keys
az vm create \
--resource-group $rg \
--location $loc2 \
--name crdb-$loc2-node3 \
--image UbuntuLTS \
--nics crdb-$loc2-nic3 \
--admin-username ubuntu \
--generate-ssh-keys
区域三。
az vm create \
--resource-group $rg \
--location $loc3 \
--name crdb-$loc3-node1 \
--image UbuntuLTS \
--nics crdb-$loc3-nic1 \
--admin-username ubuntu \
--generate-ssh-keys
az vm create \
--resource-group $rg \
--location $loc3 \
--name crdb-$loc3-node2 \
--image UbuntuLTS \
--nics crdb-$loc3-nic2 \
--admin-username ubuntu \
--generate-ssh-keys
az vm create \
--resource-group $rg \
--location $loc3 \
--name crdb-$loc3-node3 \
--image UbuntuLTS \
--nics crdb-$loc3-nic3 \
--admin-username ubuntu \
--generate-ssh-keys
K3s Kubernetes的部署
在演示中,我们将使用k3s作为Kubernetes分布。这是一个 轻量级的、经过CNCF认证的Kubernetes版本,以单个二进制文件的形式运行。这使得它易于部署和快速启动。部署工具k3sup(称'番茄酱')可以用来将k3s部署到我们的虚拟机上。K3s有一个服务器代理架构。在这个演示中,每个区域将部署一个服务器和两个代理。每个节点将运行Kubernetes的所有角色(控制平面、etcd、worker)。在生产环境中,建议将这些角色分离到独立的基础设施上。
作为安装过程的一部分,Flannel的默认CNI被禁用,以允许部署Cilium。这将是提供连接集群的能力并允许跨集群网络通信的网络供应商。我们可以用以下命令下载并安装k3sup。
curl -sLS https://get.k3sup.dev | sh
sudo install k3sup /usr/local/bin/
k3sup --help
然后我们可以使用Azure CLI将服务器节点的IP地址存储为环境变量,然后部署k3s。
MASTERR1=$(az vm show -d -g $rg -n crdb-$loc1-node1 --query publicIps -o tsv)
k3sup install \
--ip=$MASTERR1 \
--user=ubuntu \
--sudo \
--cluster \
--k3s-channel stable \
--k3s-extra-args '--flannel-backend none --disable-network-policy' \
--merge \
--local-path $HOME/.kube/config \
--context=$clus1
然后用同样的方法把它k3s部署到代理上。
AGENT1R1=$(az vm show -d -g $rg -n crdb-$loc1-node2 --query publicIps -o tsv)
k3sup join \
--ip $AGENT1R1 \
--user ubuntu \
--sudo \
--k3s-channel stable \
--server \
--server-ip $MASTERR1 \
--server-user ubuntu \
--sudo \
--k3s-extra-args '--flannel-backend=none --disable-network-policy'
AGENT2R1=$(az vm show -d -g $rg -n crdb-$loc1-node3 --query publicIps -o tsv)
k3sup join \
--ip $AGENT2R1 \
--user ubuntu \
--sudo \
--k3s-channel stable \
--server \
--server-ip $MASTERR1 \
--server-user ubuntu \
--sudo \
--k3s-extra-args '--flannel-backend=none --disable-network-policy'
现在我们只需要对其他两个区域重复前面的两个步骤。
如何使用Cilium Cluster Mesh
在这个演示中,Cilium CLI被用来部署CNI和配置Cluster Mesh。在生产环境中,可以使用GitOps方法来自动部署。Cilium CLI可以通过以下命令下载和安装。
curl -L --remote-name-all https://github.com/cilium/cilium-cli/releases/latest/download/cilium-linux-amd64.tar.gz{,.sha256sum}
sha256sum --check cilium-linux-amd64.tar.gz.sha256sum
sudo tar xzvfC cilium-linux-amd64.tar.gz /usr/local/bin
rm cilium-linux-amd64.tar.gz{,.sha256sum}
Cilium Cluster Mesh控制平面的架构是基于etcd的。每个集群都维护自己的etcd实例。这包含了该集群的当前状态,来自多个集群的状态从不在etcd中混合。每个集群通过一组代理暴露自己的etcd。在其他集群中运行的代理连接到代理,观察集群状态的变化,并将多集群的变化复制到他们自己的集群中。访问受到TLS证书的保护。从其他集群对一个集群的etcd的访问始终是只读的。这确保了一个集群的故障不会传播到其他集群。配置是通过一个简单的Kubernetes秘密资源进行的,该资源包含远程etcd代理的地址信息,以及集群名称和访问etcd代理所需的证书。
荚的IP路由是多集群功能的主要能力。它允许跨集群的pod通过其pod IP到达对方。Cilium可以在几种模式下运行,执行pod IP路由。所有这些模式都能够执行多集群吊舱IP路由。
隧道模式将吊舱发出的所有网络数据包封装在一个所谓的封装头中。封装头可以由一个VXLAN或Geneve帧组成。然后,这个封装帧通过标准的UDP数据包头进行传输。这个概念类似于VPN隧道。
在底层网络上永远看不到吊舱的IP。网络只看到工作节点的IP地址。这可以简化安装和防火墙规则。
所需的额外网络头将减少网络的理论最大吞吐量。具体的成本取决于配置的MTU,与使用MTU9000的巨型帧相比,使用1500的传统MTU时,成本会更加明显。
CockroachDB在多个地区的部署
在CockroachDB的github资源库 中,有一个Python脚本,可以帮助在多个地区自动部署CockroachDB。我们需要用前面步骤中创建的Kubernetes上下文和区域的名称来更新Python脚本。
contexts = { 'eastus': 'crdb-k3s-eastus', 'westus': 'crdb-k3s-westus', 'northeurpoe': 'crdb-k3s-northeurope',}
regions = { 'eastus': 'eastus', 'westus': 'westus', 'northeurope': 'northeurope',}
保存之后,就可以运行该脚本了。这个脚本在三个区域内部署了运行CockroachDB所需的所有资源。一旦完成,还有最后一个步骤要完成。
在每个Kubernetes集群中,都有一个CoreDNS的部署,它负责在其部署的Kubernetes集群中进行名称解析。但是,如果不对包含CoreDNS配置的ConfigMap进行修改,它就无法解析来自其他Kubernetes集群的名称。该配置可以被更新,以包括其他两个集群的转发器,因此一个集群中的pod可以解析另一个集群中的pod名称。这是CockroachDB所需要的,集群中的所有节点都必须能够相互通信。下面是每个Kubernetes集群中所需要的变化的例子。
westus.svc.cluster.local:53 { # <---- Modify
log
errors
ready
cache 10
forward . IP1 IP2 IP3 { # <---- Modify
}
}
}
northeurope.svc.cluster.local:53 { # <---- Modify
log
errors
ready
cache 10
forward . IP1 IP2 IP3 { # <---- Modify
}
}
将更新的ConfigMaps应用到每个集群中,集群就应该建立起来了。
多区域的网络原则
当你踏上CockroachDB的多区域集群之旅时,记得在开始部署前不要忘记一些基本的网络原则。
- 连接性,你的集群中所有的pod都需要能够相互对话,所以要选择一个能够允许这样做的解决方案。改变它将是破坏性的!DNS!
- 连接性是一件事,但能够执行名称解析也是一个很高的要求。每个集群都有自己的DNS部署,并且不知道其他集群的情况。因此,请确保你也为这个问题做好计划。
一旦你把这些问题解决了,就会很顺利了。这里有一个github repo的链接,其中包括更详细的步骤说明,以及你在自己的订阅中部署这个解决方案所需的所有代码。