\ 带有 Fargate 的 AWS ECS 是一个无服务器计算平台,它使在 AWS 上运行容器化服务比以往任何时候都更容易。在 Fargate 之前,想要将服务部署到 AWS ECS 集群的用户需要管理一个或多个大小相似或不同的 EC2 实例,并弄清楚如何根据需要对其进行扩展。
\ 使用 Fargate,用户只需定义服务需要运行的 CPU 和内存等计算资源,Fargate 将管理在后台运行容器的位置。没有必要设置 EC2 实例。
\ Terraform 是由 Hashicorp 创建的基础设施即代码工具,用于使处理基础设施更加直接和易于管理。 Terraform 文件使用声明性语法,其中用户指定资源及其属性,例如 pod、部署、服务和入口。然后,用户可以利用 Terraform CLI 来预览和应用预期的基础设施。
\ 当需要更改时,用户只需更新并重新应用相同的文件或文件集;然后,Terraform 会根据需要处理资源的创建、更新和删除。
\ 通过学习本教程,您将了解如何使用 Terraform 定义 AWS 资源,以及如何将资源定义转换为在 AWS 上创建的实际资源。当一切启动并运行时,您将在云上运行自己的可扩展“Hello World”服务!
\
Terraform 和 AWS Fargate 的项目依赖项
您将使用Terraform将所有必需的资源部署到 ECS 集群。确保已注册 AWS 账户。 AWS Terraform 提供商将需要凭证才能以编程方式访问您的帐户,因此如果您还没有,请根据这些文档生成它们。
\ 如果您的用户还没有附加任何策略,请随时在下面添加策略。此策略应允许访问所有 AWS 资源,因此您无需担心本教程中的这些资源。
\
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": ["*"], "Resource": "*" } ] }
\ 如果您想在不前往 AWS 仪表板的情况下更深入地了解 Terraform 部署,您可以选择安装AWS CLI 。
\
使用 Terraform 定义 AWS ECS 资源
Terraform 要求用户使用其称为 HCL 的特殊语言,它代表 Hashicorp 配置语言。创建一个名为 terraform-example 的文件夹,将存放 HCL 文件,然后将目录更改为该文件夹。需要定义和安装 Terraform 提供程序以使用某些类型的资源。
\ 本教程将仅使用AWS 提供商。只需几行 HCL 和一个命令即可轻松下载和安装提供程序。
\ 创建一个名为versions.tf
的文件,其中将定义提供程序并添加以下代码:
\
terraform { required_providers { aws = { source = "hashicorp/aws" } } } provider "aws" { region = "us-east-2" access_key = "<your_aws_access_key>" secret_key = "<your_aws_secret_key>" }
\ 请务必将<your_aws_access_key>
和<your_aws_secret_key>
替换为您帐户的密钥。既然定义了所需的提供程序,就可以通过运行命令terraform init
来安装它。
\ 确保该命令在versions.tf
所在的同一文件夹中运行。该命令应打印如下内容,这让您知道 Terraform 已准备好开始创建 AWS 资源:
\
Initializing the backend... Initializing provider plugins... - Reusing previous version of hashicorp/aws from the dependency lock file - Installing hashicorp/aws v3.32.0... - Installed hashicorp/aws v3.32.0 (signed by HashiCorp) Terraform has been successfully initialized! You may now begin working with Terraform. Try running "terraform plan" to see any changes that are required for your infrastructure. All Terraform commands should now work. If you ever set or change modules or backend configuration for Terraform, rerun this command to reinitialize your working directory. If you forget, other commands will detect it and remind you to do so if necessary.
\ 请注意,已在versions.tf
旁边创建了一个名为.terraform
的文件夹。此文件夹是存储已安装提供程序的位置,以供以后的 Terraform 进程使用。
\ 现在运行 Terraform 的先决条件已经不存在了,可以创建 AWS 资源定义。在versions.tf
旁边添加一个名为variables.tf
的文件。
\ 该文件将包含单个变量的定义,稍后将在扩展资源时在命令行中传入该变量。将以下内容添加到variables.tf
:
\
variable "app_count" { type = number default = 1 }
\ 保存并关闭文件。在variables.tf
所在的同一目录中创建另一个名为main.tf
的文件,其中 AWS 资源的资源定义将存在。首先为 AWS 可用区添加一个数据块,例如:
\
data "aws_availability_zones" "available_zones" { state = "available" }
\ 此块将获取您帐户可用的可用区域。这些将用于其他资源定义,并且为了在本教程中保持较小的占用空间,将仅使用两个可用区。
\ 将任务部署到 AWS ECS Fargate 集群时,最好使用多个可用区,因为 Fargate 将通过在可用区之间尽可能均匀地分布相同类型的任务来确保高可用性。
\ 接下来,使用以下代码将资源定义添加到main.tf
:
\
resource "aws_vpc" "default" { cidr_block = "10.32.0.0/16" }
\ 将要创建的资源将在 VPC 中定义。 AWS VPC 提供资源之间的逻辑隔离。
\ 将定义的所有资源都将存在于同一个 VPC 中。接下来将创建四个子网。两个是公共的,另外两个是私有的,每个可用区都有一个。
\ 将子网资源定义添加到main.tf
:
\
resource "aws_subnet" "public" { count = 2 cidr_block = cidrsubnet(aws_vpc.default.cidr_block, 8, 2 + count.index) availability_zone = data.aws_availability_zones.available_zones.names[count.index] vpc_id = aws_vpc.default.id map_public_ip_on_launch = true } resource "aws_subnet" "private" { count = 2 cidr_block = cidrsubnet(aws_vpc.default.cidr_block, 8, count.index) availability_zone = data.aws_availability_zones.available_zones.names[count.index] vpc_id = aws_vpc.default.id }
\ 本应面向公众的事物(例如负载均衡器)将被添加到公共子网中。其他不需要直接与 Internet 通信的东西,例如在 ECS 集群中定义的 Hello World 服务,将被添加到私有子网中。
\ 使用以下 HCL 块定义六个网络资源:
\
resource "aws_internet_gateway" "gateway" { vpc_id = aws_vpc.default.id } resource "aws_route" "internet_access" { route_table_id = aws_vpc.default.main_route_table_id destination_cidr_block = "0.0.0.0/0" gateway_id = aws_internet_gateway.gateway.id } resource "aws_eip" "gateway" { count = 2 vpc = true depends_on = [aws_internet_gateway.gateway] } resource "aws_nat_gateway" "gateway" { count = 2 subnet_id = element(aws_subnet.public.*.id, count.index) allocation_id = element(aws_eip.gateway.*.id, count.index) } resource "aws_route_table" "private" { count = 2 vpc_id = aws_vpc.default.id route { cidr_block = "0.0.0.0/0" nat_gateway_id = element(aws_nat_gateway.gateway.*.id, count.index) } } resource "aws_route_table_association" "private" { count = 2 subnet_id = element(aws_subnet.private.*.id, count.index) route_table_id = element(aws_route_table.private.*.id, count.index) }
\ 这六个资源用于处理与 VPC 外部 Internet 之间的联网和通信。例如,互联网网关完全允许 VPC 和互联网之间进行通信。
\ NAT 网关允许 VPC 内的资源与 Internet 通信,但会阻止从外部源与 VPC 通信。这一切都与路由表关联联系在一起,其中包含 NAT 网关的私有路由表被添加到前面定义的私有子网中。
\ 接下来需要添加安全组,以便以更细粒度的方式允许或拒绝来自负载均衡器和应用程序服务的流量。向main.tf
添加负载均衡器安全组资源,如下所示:
\
resource "aws_security_group" "lb" { name = "example-alb-security-group" vpc_id = aws_vpc.default.id ingress { protocol = "tcp" from_port = 80 to_port = 80 cidr_blocks = ["0.0.0.0/0"] } egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } }
\ 负载均衡器的安全组将只允许在端口 80 上流向负载均衡器的流量,由资源块中的ingress
块定义。
\ 来自负载均衡器的流量将被允许在任何端口上的任何位置使用任何协议以及egress
块中的设置。接下来使用以下代码为负载均衡器添加三个资源:
\
resource "aws_lb" "default" { name = "example-lb" subnets = aws_subnet.public.*.id security_groups = [aws_security_group.lb.id] } resource "aws_lb_target_group" "hello_world" { name = "example-target-group" port = 80 protocol = "HTTP" vpc_id = aws_vpc.default.id target_type = "ip" } resource "aws_lb_listener" "hello_world" { load_balancer_arn = aws_lb.default.id port = "80" protocol = "HTTP" default_action { target_group_arn = aws_lb_target_group.hello_world.id type = "forward" } }
\ 第一个块定义负载均衡器本身,并将其附加到具有负载均衡器安全组的每个可用区中的公共子网。目标组在添加到负载均衡器侦听器时会告诉负载均衡器将端口 80 上的传入流量转发到负载均衡器所连接的任何位置。
\ 在这种情况下,它将是稍后定义的ECS服务。使用以下块定义 ECS 集群:
\
resource "aws_ecs_task_definition" "hello_world" { family = "hello-world-app" network_mode = "awsvpc" requires_compatibilities = ["FARGATE"] cpu = 1024 memory = 2048 container_definitions = <<DEFINITION [ { "image": "heroku/nodejs-hello-world", "cpu": 1024, "memory": 2048, "name": "hello-world-app", "networkMode": "awsvpc", "portMappings": [ { "containerPort": 3000, "hostPort": 3000 } ] } ] DEFINITION }
\ 任务定义定义了 hello world 应用程序应该如何运行。这是指定平台将是 Fargate 而不是 EC2 的地方,因此不需要管理 EC2 实例。
\ 这意味着应该指定正在运行的任务的 CPU 和内存。使用的图像是一个返回“Hello World!”的简单 API。并且可以作为公共 Docker 映像使用。
\ Docker 容器在端口 3000 上公开 API,因此指定为主机和容器端口。网络模式设置为“awsvpc”,它告诉 AWS 在任务运行时应该为其分配一个弹性网络接口和一个私有 IP 地址。
\ 接下来使用以下 HCL 为 ECS 服务创建安全组:
\
resource "aws_security_group" "hello_world_task" { name = "example-task-security-group" vpc_id = aws_vpc.default.id ingress { protocol = "tcp" from_port = 3000 to_port = 3000 security_groups = [aws_security_group.lb.id] } egress { protocol = "-1" from_port = 0 to_port = 0 cidr_blocks = ["0.0.0.0/0"] } }
\ 应用程序任务的安全组指定应将其添加到默认 VPC,并且只允许通过 TCP 到应用程序的 3000 端口的流量。
\ 入口设置还包括负载均衡器的安全组,因为这将允许来自与该安全组一起使用的网络接口的流量。这允许来自任何协议的所有出站流量,如出口设置中所示。
\ 最后添加ECS服务和集群块,如下图:
\
resource "aws_ecs_cluster" "main" { name = "example-cluster" } resource "aws_ecs_service" "hello_world" { name = "hello-world-service" cluster = aws_ecs_cluster.main.id task_definition = aws_ecs_task_definition.hello_world.arn desired_count = var.app_count launch_type = "FARGATE" network_configuration { security_groups = [aws_security_group.hello_world_task.id] subnets = aws_subnet.private.*.id } load_balancer { target_group_arn = aws_lb_target_group.hello_world.id container_name = "hello-world-app" container_port = 3000 } depends_on = [aws_lb_listener.hello_world] }
\ ECS 服务通过集群内的task_definition
和desired_count
属性指定应用程序的任务数量。启动类型是 Fargate,因此不需要 EC2 实例管理。
\ 任务将在network_configuration
块中指定的私有子网中运行,并且可以通过load_balancer
块中定义的负载均衡器从外部世界访问。
\ 最后,在负载均衡器创建之前不应创建服务,因此负载均衡器侦听器包含在depends_on
数组中。
\ 最后一步是在 Terraform 配置中,以使部署的资源更易于测试。如果在 ECS 上运行的应用程序返回带有文本“Hello World!”的空白页面,您将知道一切运行正常。
\ 要访问服务,需要负载均衡器的 URL。您可以在 AWS 控制面板上找到它,但 Terraform 可以使它更容易。在与main.tf
outputs.tf
文件,然后添加以下代码:
\
output "load_balancer_ip" { value = aws_lb.default.dns_name }
\ 运行命令时,此文件将包含在 Terraform 配置中,并且输出将指示 Terraform 在应用计划时打印负载均衡器的 URL。
\ 完成整个 Terraform 配置后,运行命令terraform plan -out="tfplan"
以查看应用配置时将创建的内容。它应该看起来像这样:
\
An execution plan has been generated and is shown below. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # aws_ecs_cluster.main will be created + resource "aws_ecs_cluster" "main" { + arn = (known after apply) + id = (known after apply) ... + main_route_table_id = (known after apply) + owner_id = (known after apply) } Plan: 23 to add, 0 to change, 0 to destroy. Changes to Outputs: + load_balancer_ip = (known after apply) ------------------------------------------------------------------------ This plan was saved to: tfplan To perform exactly these actions, run the following command to apply: terraform apply "tfplan"
\ 如果您对该计划感到满意,请通过运行terraform apply "tfplan"
将配置应用到 AWS。此步骤可能需要几分钟。一旦 Terraform 完成应用计划,输出的底部应如下所示:
\
Apply complete! Resources: 23 added, 0 changed, 0 destroyed. The state of your infrastructure has been saved to the path below. This state is required to modify and destroy your infrastructure, so keep it safe. To inspect the complete state use the `terraform show` command. State path: terraform.tfstate Outputs: load_balancer_ip = "example-lb-1284172108.us-east-2.elb.amazonaws.com"
\ 请注意,最后打印负载平衡器 IP,因为输出被定义为配置的一部分。复制 URL 并将其粘贴到浏览器中。您应该会看到文本“Hello World!”打印在页面的左上角。
\ 做得好!您现在拥有一个由 Terraform 创建的在 AWS ECS 上运行的面向公众的应用程序。
\
使用 Terraform 修改现有 AWS 资源
现在,当预期会有更多的应用程序流量时会发生什么?能够在不停机的情况下水平扩展应用程序可能很有用。出于这个原因,变量app_count
包含在配置中的variables.tf
文件中。
\ ECS 服务资源将desired_count
属性设置为app_count
变量的值,因此您只需将其设置为更高的数字并重新应用配置以水平扩展应用程序。
\ 尝试先使用以下命令计划更改:
\
terraform plan -var app_count=3 -out=tfplan
\ 输出中最重要的部分朝向底部,应该如下所示:
\
An execution plan has been generated and is shown below. Resource actions are indicated with the following symbols: ~ update in-place Terraform will perform the following actions: # aws_ecs_service.hello_world will be updated in-place ~ resource "aws_ecs_service" "hello_world" { ~ desired_count = 1 -> 3 id = "arn:aws:ecs:us-east-2:914808004132:service/example-ecs-cluster/hello-world-service" name = "hello-world-service" tags = {} # (12 unchanged attributes hidden) # (3 unchanged blocks hidden) } Plan: 0 to add, 1 to change, 0 to destroy. ------------------------------------------------------------------------ This plan was saved to: tfplan To perform exactly these actions, run the following command to apply: terraform apply "tfplan"
\ 应用此计划会将应用程序容器的数量增加到三个,从而增加容量。使用命令terraform apply "tfplan"
计划。
\ 计划的输出应该显示只有 ECS 服务资源被修改,看起来类似于下面的输出:
\
aws_ecs_service.hello_world: Modifying... [id=arn:aws:ecs:us-east-2:914808004132:service/example-ecs-cluster/hello-world-service] aws_ecs_service.hello_world: Modifications complete after 1s [id=arn:aws:ecs:us-east-2:914808004132:service/example-ecs-cluster/hello-world-service] Apply complete! Resources: 0 added, 1 changed, 0 destroyed. The state of your infrastructure has been saved to the path below. This state is required to modify and destroy your infrastructure, so keep it safe. To inspect the complete state use the `terraform show` command. State path: terraform.tfstate Outputs: load_balancer_ip = "example-lb-1448833153.us-east-2.elb.amazonaws.com"
\ 如果您想确认扩展已完成,请随时前往 AWS ECS 控制面板,然后选择名为“example-ecs-cluster”的集群。
\ 注意“正在运行的任务计数”应设置为“3 Fargate, 0 EC2”。您的应用程序现在已水平扩展以处理更多流量!
\ 准备好后,您应该清理本教程中使用的资源。要在不采取任何操作的情况下查看将被销毁的内容,请运行命令terraform plan -destroy -out=tfplan
。输出应如下所示:
\
An execution plan has been generated and is shown below. Resource actions are indicated with the following symbols: - destroy Terraform will perform the following actions: # aws_ecs_cluster.main will be destroyed - resource "aws_ecs_cluster" "main" { - arn = "arn:aws:ecs:us-east-2:914808004132:cluster/example-ecs-cluster" -> null - capacity_providers = [] -> null ... - owner_id = "914808004132" -> null - tags = {} -> null } Plan: 0 to add, 0 to change, 23 to destroy. Changes to Outputs: - load_balancer_ip = "example-lb-1448833153.us-east-2.elb.amazonaws.com" -> null ------------------------------------------------------------------------ This plan was saved to: tfplan To perform exactly these actions, run the following command to apply: terraform apply "tfplan"
\ 当你准备好拆除所有东西时,运行命令terraform apply "tfplan"
。
\ 此步骤可能需要几分钟,但完成后,输出的最后一行应该表明所有内容都已按预期销毁,如下所示:
\
Apply complete! Resources: 0 added, 0 changed, 23 destroyed.
\
详细了解 Architect 如何将您的应用程序部署到 AWS 和其他地方
一旦编写了模板并定义了所有资源,Terraform 就可以轻松地将您的应用程序部署到 AWS。但是,当下一个最好的事情出现时会发生什么?当然,Terraform 将能够处理将您的应用程序部署到另一个平台,但这需要更多的维护,并且可能需要对所有 Terraform 模板进行完全重写。
\ 使用 Architect,您的应用程序只需定义一次即可部署到任何地方。在我们的文档中了解有关部署 Architect 组件的更多信息并尝试一下!
\ 如果您有任何问题或意见,请随时通过 Twitter @architect_team与团队联系。
\
也在这里发布。