圆点 快速的未来正来到你所在城市的某个活动中。

加入我们 参加 Redis 发布会

使用 Terraform 更新 AWS 自动扩展组并保留所需容量

简介

Terraform 可能是部署基础设施的最佳工具,因此它是将自动缩放组部署到 AWS 的明显选择。我们要从事的项目要求我们在 AWS 中的实例上部署服务。该服务无状态,且配置简单,可通过 cloud-init轻松配置。

我们的目标是使用 Terraform 配置所有内容。每当我们要进行配置更改时,就会更新 Terraform 配置并再次应用。我们希望在进行任何配置更改时获得干净的实例,而不是就地更新。

在整个示例中,我们为简洁起见省去了某些配置元素的创建。其他一些元素会注入为变量,这些元素也未显示于此。

初始配置

首先创建启动模板

# Query for the latest Ubuntu 18.04 AMI in the region
data "aws_ami" "ubuntu" {
  most_recent = true

  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-bionic-18.04-amd64-server-*"]
  }

  filter {
    name   = "virtualization-type"
    values = ["hvm"]
  }

  owners = ["099720109477"] # Canonical
}

resource "aws_launch_template" "mysvc" {
  name                                 = "mysvc"
  ebs_optimized                        = true
  image_id                             = data.aws_ami.ubuntu.id
  instance_initiated_shutdown_behavior = "terminate"
  instance_type                        = "c5.large"
  key_name                             = var.key_name
  network_interfaces {
    associate_public_ip_address = true
    subnet_id                   = var.subnet_id
    security_groups             = var.security_groups
    delete_on_termination       = true
  }
  tag_specifications {
    resource_type = "instance"
    tags = {
      Name          = "mysvc"
    }
  }
  user_data = base64encode(templatefile("${path.module}/user_data.yaml", {
    web_password    = var.web_password,
    ssl_private_key = tls_private_key.mysvc.private_key_pem,
    ssl_certificate = tls_self_signed_cert.mysvc.cert_pem
  }))

  # we don't want to create a new template just because there is a newer AMI
  lifecycle {
    ignore_changes = [
      image_id,
    ]
  }
}

启动模板定义实例的关键元素,还会从用户数据文件加载服务配置。用户数据文件是一个模板,它计算到一个有效的 cloud-init 配置文件。

现在我们创建自动缩放组

resource "aws_autoscaling_group" "mysvc" {
  name                      = "mysvc-${aws_launch_template.mysvc.latest_version}"
  health_check_type         = "ELB"
  health_check_grace_period = 120
  termination_policies      = ["OldestInstance"]
  launch_template {
    id      = aws_launch_template.mysvc.id
    version = aws_launch_template.mysvc.latest_version
  }
  min_size = 1
  max_size = 10

  lifecycle {
    create_before_destroy = true
  }
  target_group_arns = [aws_lb_target_group.svc.arn]
}

这里有一些需要注意的事情

  1. 我们使用启动模板的最新版本为自动缩放组命名。这是为了确保在更新启动模板时我们创建一个新的自动缩放组。
  2. 在创建新的自动缩放组时,我们希望首先创建新的组,然后销毁前一个组,因此我们使用生命周期指令 create_before_destroy = true。由于我们的服务在 ELB 之后,因此这可以确保在配置更改期间不会出现停机时间。

这样很好,完全符合我们的需要。或者是这样吗?

如果我们已将组扩展为具有更多实例,并且更改了配置,会发生什么情况?使用当前配置,我们将用一个仅包含 1 个实例的组替换运行的组(例如,包含 5 个实例)。我们希望能够创建一个包含 5 个实例的新组。

更好的配置

为了保留所需的容量的当前值,我们需要在某种程度上首先获得该值。

让我们获取当前启动模板(如果存在的话)

data "aws_launch_template" "current" {
  filter {
    name   = "launch-template-name"
    values = ["mysvc"]
  }
}

现在让我们查看是否具有使用该启动模板的最新版本的自动缩放组`

data "aws_autoscaling_groups" "current" {
  filter {
    name   = "key"
    values = ["mysvc-template-version"]
  }
  filter {
    name   = "value"
    values = ["${coalesce(data.aws_launch_template.current.latest_version, 0)}"]
  }
}

我们使用到目前为止尚未被添加到自动缩放组的标签。有关自动缩放组资源的更改,请参见下方。

这里的想法可能是 data.aws_launch_template.current.latest_version具有值,也可能是 null。如果存在值,我们会期望获得最多一个元素的列表。如果它是 null,那么我们就查找不存在的自动缩放组,并获得一个长度为零的列表。

现在我们可以在当前自动缩放组上获取信息

data "aws_autoscaling_group" "current" {
  count = length(data.aws_autoscaling_groups.current.names)
  name  = data.aws_autoscaling_groups.current.names[count.index]
}

本质上,使用 count 可创建一到零个资源,具体取决于 data.aws_autoscaling_groups.current.names的大小。

现在我们可以更新自动缩放组资源

resource "aws_autoscaling_group" "mysvc" {
  name                      = "mysvc-${aws_launch_template.mysvc.latest_version}"
  health_check_type         = "ELB"
  health_check_grace_period = 120
  termination_policies      = ["OldestInstance"]
  launch_template {
    id      = aws_launch_template.mysvc.id
    version = aws_launch_template.mysvc.latest_version
  }
  min_size = 1
  max_size = 10

  desired_capacity = length(data.aws_autoscaling_groups.current.names) > 0 ? data.aws_autoscaling_group.current[0].desired_capacity : var.default_desired_capacity

  tag {
    key                 = "mysvc-template-version"
    value               = aws_launch_template.mysvc.latest_version
    propagate_at_launch = false
  }

  lifecycle {
    create_before_destroy = true
  }
  target_group_arns = [aws_lb_target_group.svc.arn]
}

在此,我们通过检查是否可以找到任何当前自动缩放组来定义所需的容量。如果可以,那么我们使用第一个(也是唯一一个)所需容量的自动缩放组值。但是,如果找不到任何当前组,那么我们使用默认值。我们还记得添加我们需要查询的自动缩放组标签。

考虑 Terraform

Terraform 很棒,而且可以用于许多事情。有些需要多一点弯路,但值得拥有一款所有功能的工具。