构建多个网络互通的 AWS EKS 集群

本文以构建 3 个集群为例,介绍了如何构建多个 AWS EKS 集群,并配置集群之间的网络互通,为跨 Kubernetes 集群部署 TiDB 集群作准备。

如果仅需要部署 TiDB 集群到一个 AWS EKS 集群,请参考在 AWS EKS 上部署 TiDB 集群文档。

环境准备

部署前,请确认已完成以下环境准备:

要验证 AWS CLI 的配置是否正确,请运行 aws configure list 命令。如果此命令的输出显示了 access_keysecret_key 的值,则 AWS CLI 的配置是正确的。否则,你需要重新配置 AWS CLI。

第 1 步:启动 Kubernetes 集群

定义三个 EKS 集群的配置文件分别为 cluster_1.yamlcluster_2.yamlcluster_3.yaml,并使用 eksctl 命令创建三个 EKS 集群。

  1. 定义集群 1 的配置文件,并创建集群 1。

    将如下配置保存为 cluster_1.yaml 文件,其中 ${cluster_1} 为 EKS 集群的名字,${region_1} 为部署 EKS 集群到的 Region,${cidr_block_1} 为 EKS 集群所属的 VPC 的 CIDR block。

    1. apiVersion: eksctl.io/v1alpha5
    2. kind: ClusterConfig
    3. metadata:
    4. name: ${cluster_1}
    5. region: ${region_1}
    6. # nodeGroups ...
    7. vpc:
    8. cidr: ${cidr_block_1}

    节点池 nodeGroups 字段的配置可以参考创建 EKS 集群和节点池一节。

    执行以下命令创建集群 1 :

    1. eksctl create cluster -f cluster_1.yaml

    执行上述命令后,等待 EKS 集群创建完成,以及节点组创建完成并加入进去,耗时约 5~20 分钟。可参考 eksctl 文档了解更多集群配置选项。

  2. 以集群 1 的配置文件为例,定义集群 2 与集群 3 的配置文件,并通过 eksctl 命令创建集群 2 与集群 3。

    需要注意,每个集群所属的 VPC 的 CIDR block 必须 与其他集群不重叠。

    后文中:

    • ${cluster_1}${cluster_2}${cluster_3} 分别代表三个集群的名字。
    • ${region_1}${region_2}${region_3} 分别代表三个集群所处的 Region。
    • ${cidr_block_1}${cidr_block_2}${cidr_block_3} 分别代表三个集群所属的 VPC 的 CIDR block。
  3. 在所有集群创建完毕后,你需要获取每个集群的 Kubernetes Context,以方便后续使用 kubectl 命令操作每个集群。

    1. kubectl config get-contexts

    点击查看输出,其中的 `NAME` 项就是你需要使用的 context 。

    1. CURRENT NAME CLUSTER AUTHINFO NAMESPACE * pingcap@tidb-1.us-west-1.eksctl.io tidb-1.us-west-1.eksctl.io pingcap@tidb-1.us-west-1.eksctl.io pingcap@tidb-2.us-west-2.eksctl.io tidb-2.us-west-2.eksctl.io pingcap@tidb-2.us-west-2.eksctl.io pingcap@tidb-3.us-east-1.eksctl.io tidb-3.us-east-1.eksctl.io pingcap@tidb-3.us-east-1.eksctl.io

    后文中,${context_1}${context_2}${context_3} 分别代表各个集群的 context。

第 2 步:配置网络

设置 VPC peering

为了联通三个集群的网络,你需要为每两个集群所在的 VPC 创建一个 VPC peering。关于 VPC peering,可以参考 AWS 官方文档

  1. 通过 eksctl 命令,得到每个集群所在的 VPC 的 ID。以集群 1 为例:

    1. eksctl get cluster ${cluster_1} --region ${region_1}

    点击查看示例输出,其中 `VPC` 项就是该集群所在 VPC 的 ID。

    1. NAME VERSION STATUS CREATED VPC SUBNETS SECURITYGROUPS tidb-1 1.20 ACTIVE 2021-11-22T06:40:20Z vpc-0b15ed35c02af5288 subnet-058777d55881c4095, subnet-06def2041b6fa3fa0,subnet-0869c7e73e09c3174,subnet-099d10845f6cbaf82,subnet-0a1a58db5cb087fed, subnet-0f68b302678c4d36b sg-0cb299e7ec153c595

    后文中,${vpc_id_1}${vpc_id_2}${vpc_id_3} 分别代表各个集群所在的 VPC 的 ID。

  2. 构建集群 1 与集群 2 的 VPC peering。

    1. 按照 AWS VPC peering 文档创建 VPC peering。${vpc_id_1} 作为 requester VPC,${vpc_id_2} 作为 accepter VPC 。

    2. 按照 AWS VPC Peering 文档完成 VPC peering 的构建。

  3. 以步骤 2 为例,构建集群 1 与集群 3 的 VPC peering,以及集群 2 与集群 3 的 VPC peering。

  4. 按照更新路由表文档,更新三个集群的路由表。

    你需要更新集群使用的所有 Subnet 的路由表。每个路由表需要添加两个路由项,以集群 1 的某个路由表为例:

    DestinationTargetStatusPropagated
    ${cidr_block_2}${vpc_peering_id_12}ActiveNo
    ${cidr_block_3}${vpc_peering_id_13}ActiveNo

    每个路由项的 Destination 为另一个集群的 CIDR block,Target 为这两个集群的 VPC peering ID。

更新实例的安全组

  1. 更新集群 1 的安全组:

    1. 进入 AWS Security Groups 控制台,找到集群 1 的安全组。安全组命名类似于 eksctl-${cluster_1}-cluster/ClusterSharedNodeSecurityGroup

    2. 在安全组中添加 Inbound rules,以允许来自集群 2 和集群 3 的流量:

      TypeProtocolPort rangeSourceDescription
      All trafficAllAllCustom ${cidr_block_2}Allow cluster 2 to communicate with cluster 1
      All trafficAllAllCustom ${cidr_block_3}Allow cluster 3 to communicate with cluster 1
  2. 按照步骤 1,更新集群 2 与集群 3 的安全组。

配置负载均衡器

每个集群的 CoreDNS 服务需要通过一个网络负载均衡器暴露给其他集群。本节介绍如何配置负载均衡器。

  1. 创建 Load Balancer Service 定义文件 dns-lb.yaml,其文件内容如下:

    1. apiVersion: v1
    2. kind: Service
    3. metadata:
    4. labels:
    5. k8s-app: kube-dns
    6. name: across-cluster-dns-tcp
    7. namespace: kube-system
    8. annotations:
    9. service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: "true"
    10. service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
    11. service.beta.kubernetes.io/aws-load-balancer-internal: "true"
    12. spec:
    13. ports:
    14. - name: dns
    15. port: 53
    16. protocol: TCP
    17. targetPort: 53
    18. selector:
    19. k8s-app: kube-dns
    20. type: LoadBalancer
  2. 在每个集群中部署 Load Balancer Service。

    1. kubectl --context ${context_1} apply -f dns-lb.yaml
    2. kubectl --context ${context_2} apply -f dns-lb.yaml
    3. kubectl --context ${context_3} apply -f dns-lb.yaml
  3. 获取各集群的负载均衡器的名字,并等待所有集群的负载均衡器变为 Active 状态。

    使用以下命令来查询三个集群的负载均衡器的名字。

    1. lb_name_1=$(kubectl --context ${context_1} -n kube-system get svc across-cluster-dns-tcp -o jsonpath="{.status.loadBalancer. ingress[0].hostname}" | cut -d - -f 1)
    2. lb_name_2=$(kubectl --context ${context_2} -n kube-system get svc across-cluster-dns-tcp -o jsonpath="{.status.loadBalancer. ingress[0].hostname}" | cut -d - -f 1)
    3. lb_name_3=$(kubectl --context ${context_3} -n kube-system get svc across-cluster-dns-tcp -o jsonpath="{.status.loadBalancer. ingress[0].hostname}" | cut -d - -f 1)

    执行以下命名来查看三个集群的负载均衡器的状态,所有的命令输出结果都为 “active” 时,表明负载均衡器为 Active 状态。

    1. aws elbv2 describe-load-balancers --names ${lb_name_1} --region ${region_1} --query 'LoadBalancers[*].State' --output text
    2. aws elbv2 describe-load-balancers --names ${lb_name_2} --region ${region_2} --query 'LoadBalancers[*].State' --output text
    3. aws elbv2 describe-load-balancers --names ${lb_name_3} --region ${region_3} --query 'LoadBalancers[*].State' --output text

    点击查看期望输出

    1. active active active
  4. 查询各集群的负载均衡器关联的 IP 地址。

    以集群 1 为例,执行下面命令查询集群 1 的负载均衡器关联的所有 IP 地址。

    1. aws ec2 describe-network-interfaces --region ${region_1} --filters Name=description,Values="ELB net/${lb_name_1}*" --query 'NetworkInterfaces[*].PrivateIpAddress' --output text

    点击查看期望输出

    1. 10.1.175.233 10.1.144.196

    后文中,将各集群的负载均衡器关联 IP 地址称为 ${lb_ip_list_1}${lb_ip_list_2}${lb_ip_list_3}

    不同 Region 的负载均衡器可能有着不同数量的 IP 地址。例如上述示例中,${lb_ip_list_1} 就是 10.1.175.233 10.1.144.196

配置 CoreDNS

为了让集群中的 Pod 能够访问其他集群的 Service,你需要配置每个集群的 CoreDNS 服务,使其能够转发 DNS 请求给其他集群的 CoreDNS 服务。

你可以通过修改 CoreDNS 对应的 ConfigMap 来进行配置。如需了解更多配置项,参考 Customizing DNS Service

  1. 修改集群 1 的 CoreDNS 配置。

    1. 备份当前的 CoreDNS 配置:

      1. kubectl --context ${context_1} -n kube-system get configmap coredns -o yaml > ${cluster_1}-coredns.yaml.bk
    2. 修改 ConfigMap:

      1. kubectl --context ${context_1} -n kube-system edit configmap coredns

      修改 data.Corefile 字段如下,其中 ${namespace_2}${namespace_3} 分别为集群 2 和集群 3 将要部署的 TidbCluster 所在的 namespace。

      构建多个网络互通的 AWS EKS 集群 - 图1警告

      因为 EKS 集群无法修改集群的 cluster domain,因此我们需要使用 namespace 作为 DNS 请求转发的识别条件,所以 ${namespace_1}$ {namespace_2}${namespace_3} 必须不一样。

      1. apiVersion: v1
      2. kind: ConfigMap
      3. # ...
      4. data:
      5. Corefile: |
      6. .:53 {
      7. # ... 默认配置不修改
      8. }
      9. ${namespace_2}.svc.cluster.local:53 {
      10. errors
      11. cache 30
      12. forward . ${lb_ip_list_2} {
      13. force_tcp
      14. }
      15. }
      16. ${namespace_3}.svc.cluster.local:53 {
      17. errors
      18. cache 30
      19. forward . ${lb_ip_list_3} {
      20. force_tcp
      21. }
      22. }
    3. 等待 CoreDNS 重新加载配置,大约需要 30s 左右。

  2. 以步骤 1 为例,修改集群 2 和集群 3 的 CoreDNS 配置。

    对于每个集群的 CoreDNS 配置,你需要将 ${namespace_2}${namespace_3} 修改为另外两个集群将要部署 TidbCluster 的 namespace,将配置中的 IP 地址配置为另外两个集群的 Load Balancer 的 IP 地址。

后文中,使用 ${namespace_1}${namespace_2}${namespace_3} 分别代表各个集群的将要部署的 TidbCluster 所在的 namespace。

第 3 步:验证网络连通性

在部署 TiDB 集群之前,你需要先验证多个集群之间的网络连通性。

  1. 将下面定义保存到 sample-nginx.yaml 文件。

    1. apiVersion: v1
    2. kind: Pod
    3. metadata:
    4. name: sample-nginx
    5. labels:
    6. app: sample-nginx
    7. spec:
    8. hostname: sample-nginx
    9. subdomain: sample-nginx-peer
    10. containers:
    11. - image: nginx:1.21.5
    12. imagePullPolicy: IfNotPresent
    13. name: nginx
    14. ports:
    15. - name: http
    16. containerPort: 80
    17. restartPolicy: Always
    18. ---
    19. apiVersion: v1
    20. kind: Service
    21. metadata:
    22. name: sample-nginx-peer
    23. spec:
    24. ports:
    25. - port: 80
    26. selector:
    27. app: sample-nginx
    28. clusterIP: None
  2. 在三个集群对应的命名空间下部署 NGINX 服务。

    1. kubectl --context ${context_1} -n ${namespace_1} apply -f sample-nginx.yaml
    2. kubectl --context ${context_2} -n ${namespace_2} apply -f sample-nginx.yaml
    3. kubectl --context ${context_3} -n ${namespace_3} apply -f sample-nginx.yaml
  3. 访问其他集群的 NGINX 服务,验证网络是否连通。

    以验证集群 1 到集群 2 的网络连通性为例,执行以下命令。

    1. kubectl --context ${context_1} exec sample-nginx -- curl http://sample-nginx.sample-nginx-peer.${namespace_2}.svc.cluster.local:80

    如果输出为 NGINX 的欢迎页面,那么就表明网络是正常连通的。

  4. 验证完成后,执行以下命令删除 NGINX 服务。

    1. kubectl --context ${context_1} -n ${namespace_1} delete -f sample-nginx.yaml
    2. kubectl --context ${context_2} -n ${namespace_2} delete -f sample-nginx.yaml
    3. kubectl --context ${context_3} -n ${namespace_3} delete -f sample-nginx.yaml

第 4 步:部署 TiDB Operator

每个集群的 TidbCluster CR 由当前集群的 TiDB Operator 管理,因此每个集群都需要部署 TiDB Operator。

参考在 Kubernetes 上部署 TiDB Operator 部署 TiDB Operator 到每个 EKS 集群。区别在于,你需要通过命令 kubectl --context ${context}helm --kube-context ${context} 来为每个 EKS 集群部署 TiDB Operator。

第 5 步:部署 TiDB 集群

参考跨多个 Kubernetes 集群部署 TiDB 集群,为每个集群部署一个 TidbCluster CR。需要注意的是:

  • 必须将各集群的 TidbCluster 部署到配置 CoreDNS 一节中对应的 namespace 下,否则 TiDB 集群运行将会失败。
  • 各集群的 cluster domain 必须 设置为 “cluster.local”。

例如,部署初始集群的 TidbCluster CR 到集群 1 时,将 metadata.namespace 指定为 ${namespace_1}:

  1. apiVersion: pingcap.com/v1alpha1
  2. kind: TidbCluster
  3. metadata:
  4. name: ${tc_name_1}
  5. namespace: ${namespace_1}
  6. spec:
  7. # ...
  8. clusterDomain: "cluster.local"
  9. acrossK8s: true

探索更多