如何使用 Terraform 在亞馬遜雲科技上建立 ShardingSphere Proxy 高可用叢集?

亞馬遜雲開發者發表於2023-02-14

背景介紹

Terraform

Terraform[1] 是一個 Hashicorp[2] 開源的基礎設施自動化編排工具,使用 IaC 的理念來管理基礎設施的變更,並得到了亞馬遜雲科技,GCP,AZURE 等公有云廠商的支援以及社群提供的各種各樣的 provider,已成為「基礎設施即程式碼」領域最流行的實踐方式之一,Terraform 有以下優點:

? 支援多雲部署 Terraform 適用於多雲方案,將類似的基礎結構部署到阿里雲、其他雲提供商或者本地資料中心。開發人員能夠使用相同的工具和相似的配置檔案同時管理不同雲提供商的資源。

? 自動化管理基礎架構 Terraform 能夠建立模組,可重複使用, 從而減少因人為因素導致的部署和管理錯誤。

? 基礎架構即程式碼 可以用程式碼來管理維護資源,允許儲存基礎設施狀態,從而使使用者能夠跟蹤系統中不同元件所做的更改,並與其他人共享這些配置 。

ShardingSphere-Proxy

Apache ShardingSphere 是一款分散式的資料庫生態系統,可以將任意資料庫轉換為分散式資料庫,並透過資料分片、彈性伸縮、加密等能力對原有資料庫進行增強。

其設計哲學為 Database Plus,旨在構建異構資料庫上層的標準和生態。它關注如何充分利用資料庫的計算和儲存能力,而並非實現一個全新的資料庫。它站在資料庫的上層視角,關注資料庫之間的協作多於它們自身。

ShardingSphere-Proxy 的定位為透明化的資料庫代理,理論上支援任何使用 MySQL、PostgreSQL、openGauss 協議的客戶端運算元據,對異構語言、運維場景更友好。其對應用程式碼是無侵入的,使用者只需更改資料庫的連線串,就可以實現資料分片,讀寫分離等功能,作為資料基礎設施的一部分,其自身的高可用性將非常重要。

使用 Terraform 部署

我們希望您透過 IaC 的方式去部署管理 ShardingSphere Proxy 叢集,去享受 IaC 帶來的好處。基於以上,我們計劃使用 Terraform 建立一個多可用區的 ShardingSphere-Proxy 高可用叢集。除此之外,在開始編寫 Terraform 配置之前,我們先需要了解 ShardingSphere-Proxy 叢集的基本架構圖:

image.png

我們使用 ZooKeeper 來作為 Governance Center。可以看出,ShardingSphere-Proxy 自身是一個無狀態的應用,在實際場景中,對外提供一個負載均衡即可, 由負載均衡去彈性分配各個例項之間的流量。為了保證 ZooKeeper 叢集及 ShardingSphere-Proxy 叢集的高可用,我們將使用以下架構建立:

image.png

ZooKeeper 叢集

定義輸入引數

為了達到可重用配置的目的,我們定義了一系列的變數,內容如下:

variable "cluster_size" {
  type        = number
  description = "The cluster size that same size as available_zones"
}

variable "key_name" {
  type        = string
  description = "The ssh keypair for remote connection"
}

variable "instance_type" {
  type        = string
  description = "The EC2 instance type"
}

variable "vpc_id" {
  type        = string
  description = "The id of VPC"
}

variable "subnet_ids" {
  type        = list(string)
  description = "List of subnets sorted by availability zone in your VPC"
}

variable "security_groups" {
  type        = list(string)
  default     = []
  description = "List of the Security Group, it must be allow access 2181, 2888, 3888 port"
}


variable "hosted_zone_name" {
  type        = string
  default     = "shardingsphere.org"
  description = "The name of the hosted private zone"
}

variable "tags" {
  type        = map(any)
  description = "A map of zk instance resource, the default tag is Name=zk-$${count.idx}"
  default     = {}
}

variable "zk_version" {
  type        = string
  description = "The zookeeper version"
  default     = "3.7.1"
}

variable "zk_config" {
  default = {
    client_port = 2181
    zk_heap     = 1024
  }

  description = "The default config of zookeeper server"
}

這些變數也可以在下面安裝 ShardingSphere-Proxy 叢集時更改。

配置 ZooKeeper 叢集

ZooKeeper 服務例項我們使用了亞馬遜雲科技原生的 amzn2-ami-hvm 映象,我們使用 count 引數來部署 ZooKeeper 服務,它指示 Terraform 建立的 ZooKeeper 叢集的節點數量為 var.cluster_size 。

在建立 ZooKeeper 例項時,我們使用了 ignore_changes 引數來忽略人為的更改 tag ,以避免在下次執行 Terraform 時例項被重新建立。

可使用 cloud-init 來初始化 ZooKeeper 相關配置,具體內容見 [3]。我們為每個 ZooKeeper 服務都建立了對應的域名,應用只需要使用域名即可,以避免 ZooKeeper 服務重啟導致 ip 地址更改帶來的問題。

data "aws_ami" "base" {
  owners = ["amazon"]

  filter {
    name   = "name"
    values = ["amzn2-ami-hvm-*-x86_64-ebs"]
  }

  most_recent = true
}

data "aws_availability_zones" "available" {
  state = "available"
}

resource "aws_network_interface" "zk" {
  count           = var.cluster_size
  subnet_id       = element(var.subnet_ids, count.index)
  security_groups = var.security_groups
}

resource "aws_instance" "zk" {
  count         = var.cluster_size
  ami           = data.aws_ami.base.id
  instance_type = var.instance_type
  key_name      = var.key_name

  network_interface {
    delete_on_termination = false
    device_index          = 0
    network_interface_id  = element(aws_network_interface.zk.*.id, count.index)
  }

  tags = merge(
    var.tags,
    {
      Name = "zk-${count.index}"
    }
  )

  user_data = base64encode(templatefile("${path.module}/cloud-init.yml", {
    version     = var.zk_version
    nodes       = range(1, var.cluster_size + 1)
    domain      = var.hosted_zone_name
    index       = count.index + 1
    client_port = var.zk_config["client_port"]
    zk_heap     = var.zk_config["zk_heap"]
  }))

  lifecycle {
    ignore_changes = [
      # Ignore changes to tags.
      tags
    ]
  }
}

data "aws_route53_zone" "zone" {
  name         = "${var.hosted_zone_name}."
  private_zone = true
}

resource "aws_route53_record" "zk" {
  count   = var.cluster_size
  zone_id = data.aws_route53_zone.zone.zone_id
  name    = "zk-${count.index + 1}"
  type    = "A"
  ttl     = 60
  records = element(aws_network_interface.zk.*.private_ips, count.index)
}

定義輸出

在成功執行 terraform apply 後會輸出 ZooKeeper 服務例項的 IP 及對應的域名。

output "zk_node_private_ip" {
  value       = aws_instance.zk.*.private_ip
  description = "The private ips of zookeeper instances"
}

output "zk_node_domain" {
  value       = [for v in aws_route53_record.zk.*.name : format("%s.%s", v, var.hosted_zone_name)]
  description = "The private domain names of zookeeper instances for use by ShardingSphere Proxy"
}

ShardingSphere-Proxy 叢集

定義輸入引數

定義輸入引數的目的也是為了達到配置可重用的目的。

variable "cluster_size" {
  type        = number
  description = "The cluster size that same size as available_zones"
}

variable "shardingsphere_proxy_version" {
  type        = string
  description = "The shardingsphere proxy version"
}

variable "shardingsphere_proxy_asg_desired_capacity" {
  type        = string
  default     = "3"
  description = "The desired capacity is the initial capacity of the Auto Scaling group at the time of its creation and the capacity it attempts to maintain. see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-as-group.html#cfn-as-group-desiredcapacitytype, The default value is 3"
}

variable "shardingsphere_proxy_asg_max_size" {
  type        = string
  default     = "6"
  description = "The maximum size of ShardingSphere Proxy Auto Scaling Group. The default values is 6"
}

variable "shardingsphere_proxy_asg_healthcheck_grace_period" {
  type        = number
  default     = 120
  description = "The amount of time, in seconds, that Amazon EC2 Auto Scaling waits before checking the health status of an EC2 instance that has come into service and marking it unhealthy due to a failed health check. see https://docs.aws.amazon.com/autoscaling/ec2/userguide/health-check-grace-period.html"
}

variable "image_id" {
  type        = string
  description = "The AMI id"
}

variable "key_name" {
  type        = string
  description = "the ssh keypair for remote connection"
}

variable "instance_type" {
  type        = string
  description = "The EC2 instance type"
}

variable "vpc_id" {
  type        = string
  description = "The id of your VPC"
}

variable "subnet_ids" {
  type        = list(string)
  description = "List of subnets sorted by availability zone in your VPC"
}

variable "security_groups" {
  type        = list(string)
  default     = []
  description = "List of The Security group IDs"
}

variable "lb_listener_port" {
  type        = string
  description = "lb listener port"
}

variable "hosted_zone_name" {
  type        = string
  default     = "shardingsphere.org"
  description = "The name of the hosted private zone"
}

variable "zk_servers" {
  type        = list(string)
  description = "The Zookeeper servers"
}

配置 AutoScalingGroup

我們將建立一個 AutoScalingGroup 來讓其管理 ShardingSphere-Proxy 例項,AutoScalingGroup 的健康檢查型別被更改為 "ELB",在負載均衡對例項執行健康檢查失敗後,AutoScalingGroup 能及時移出壞的節點。

在建立 AutoScallingGroup 時會忽略以下更改,分別為:load_balancers 、 target_group_arns 。我們同樣使用 cloud-init 來配置 ShardingSphere-Proxy 例項,具體內容見[4]。

resource "aws_launch_template" "ss" {
  name                                 = "shardingsphere-proxy-launch-template"
  image_id                             = var.image_id
  instance_initiated_shutdown_behavior = "terminate"
  instance_type                        = var.instance_type
  key_name                             = var.key_name
  iam_instance_profile {
    name = aws_iam_instance_profile.ss.name
  }

  user_data = base64encode(templatefile("${path.module}/cloud-init.yml", {
    version    = var.shardingsphere_proxy_version
    version_elems = split(".", var.shardingsphere_proxy_version)
    zk_servers = join(",", var.zk_servers)
  }))

  metadata_options {
    http_endpoint               = "enabled"
    http_tokens                 = "required"
    http_put_response_hop_limit = 1
    instance_metadata_tags      = "enabled"
  }

  monitoring {
    enabled = true
  }

  vpc_security_group_ids = var.security_groups

  tag_specifications {
    resource_type = "instance"

    tags = {
      Name = "shardingsphere-proxy"
    }
  }
}

resource "aws_autoscaling_group" "ss" {
  name                      = "shardingsphere-proxy-asg"
  availability_zones        = data.aws_availability_zones.available.names
  desired_capacity          = var.shardingsphere_proxy_asg_desired_capacity
  min_size                  = 1
  max_size                  = var.shardingsphere_proxy_asg_max_size
  health_check_grace_period = var.shardingsphere_proxy_asg_healthcheck_grace_period 
  health_check_type         = "ELB"

  launch_template {
    id      = aws_launch_template.ss.id
    version = "$Latest"
  }

  lifecycle {
    ignore_changes = [load_balancers, target_group_arns]
  }
}

配置負載均衡

透過上一步建立好的 AutoScalingGroup 會 attach 到負載均衡上,經過負載均衡的流量會自動路由到 AutoScalingGroup 建立的 ShardingSphere-Proxy 例項上。

resource "aws_lb_target_group" "ss_tg" {
  name               = "shardingsphere-proxy-lb-tg"
  port               = var.lb_listener_port
  protocol           = "TCP"
  vpc_id             = var.vpc_id
  preserve_client_ip = false

  health_check {
    protocol = "TCP"
    healthy_threshold = 2
    unhealthy_threshold = 2
  }

  tags = {
    Name = "shardingsphere-proxy"
  }
}

resource "aws_autoscaling_attachment" "asg_attachment_lb" {
  autoscaling_group_name = aws_autoscaling_group.ss.id
  lb_target_group_arn    = aws_lb_target_group.ss_tg.arn
}


resource "aws_lb_listener" "ss" {
  load_balancer_arn = aws_lb.ss.arn
  port              = var.lb_listener_port
  protocol          = "TCP"

  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.ss_tg.arn
  }

  tags = {
    Name = "shardingsphere-proxy"
  }
}

配置域名

我們將建立預設為 proxy.shardingsphere.org 的內部域名,實際內部指向到上一步建立的負載均衡。

data "aws_route53_zone" "zone" {
  name         = "${var.hosted_zone_name}."
  private_zone = true
}

resource "aws_route53_record" "ss" {
  zone_id = data.aws_route53_zone.zone.zone_id
  name    = "proxy"
  type    = "A"

  alias {
    name                   = aws_lb.ss.dns_name
    zone_id                = aws_lb.ss.zone_id
    evaluate_target_health = true
  }
}

配置 CloudWatch

我們將透過 STS 去建立包含 CloudWatch 許可權的角色,角色會附加到由 AutoScalingGroup 建立的 ShardingSphere-Proxy 例項上,其執行日誌會被 CloudWatch Agent 採集到 CloudWatch 上。

預設會建立名為 shardingsphere-proxy.log 的 log_group,CloudWatch 的具體配置見 [5]。

resource "aws_iam_role" "sts" {
  name = "shardingsphere-proxy-sts-role"

  assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": "sts:AssumeRole",
      "Principal": {
        "Service": "ec2.amazonaws.com"
      },
      "Effect": "Allow",
      "Sid": ""
    }
  ]
}
EOF
}

resource "aws_iam_role_policy" "ss" {
  name = "sharidngsphere-proxy-policy"
  role = aws_iam_role.sts.id

  policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": [
        "cloudwatch:PutMetricData",
        "ec2:DescribeTags",
        "logs:PutLogEvents",
        "logs:DescribeLogStreams",
        "logs:DescribeLogGroups",
        "logs:CreateLogStream",
        "logs:CreateLogGroup"
      ],
      "Effect": "Allow",
      "Resource": "*"
    }
  ]
}
EOF
}

resource "aws_iam_instance_profile" "ss" {
  name = "shardingsphere-proxy-instance-profile"
  role = aws_iam_role.sts.name
}

部署

在建立完所有的 Terraform 配置後就可以部署 ShardingSphere-Proxy 叢集了。在實際部署之前,推薦您使用如下命令去檢查配置是否按預期執行。

terraform plan

在確認完計劃後,就可以去真正的執行了,執行如下命令:

terraform apply

完整的程式碼可以在 [6] 找到。更多的內容請檢視我們的網站 [7]。

測試

測試的目標是證明建立的叢集是可用的, 我們使用一個簡單 case:使用 DistSQL 新增兩個資料來源及建立一個簡單的分片規則,然後插入資料,查詢能返回正確的結果。

預設我們會建立一個 proxy.shardingsphere.org 的內部域名, ShardingSphere-Proxy 叢集的使用者名稱和密碼都是 root。

image.png

注:DistSQL(Distributed SQL)是 ShardingSphere 特有的操作語言,它與標準 SQL 的使用方式完全一致,用於提供增量功能的 SQL 級別操作能力, 詳細說明見 [8]。

總結

Terraform 是幫助你實現 IaC 的有效工具,使用 Terraform 對迭代 ShardingSphere-Proxy 叢集將非常有用。希望這篇文章能夠幫助到對 ShardingSphere 以及 Terraform 感興趣的人。

引用

閱讀原文:https://dev.amazoncloud.cn/co...

相關文章