使用 Terraform 在 AWS Fargate 上部署 Rust 应用
在当今快速发展的技术环境中,基础设施即代码 (Infrastructure as Code, IaC) 已成为高效、可扩展和可维护的云基础设施部署的基石。IaC 涉及通过机器可读的脚本文件,而不是通过物理硬件配置或交互式配置工具来管理和配置计算基础设施。这实现了基础设施部署和管理的自动化,从而降低了人为错误的风险并提高了部署速度。
在本文中,我们将探讨如何使用 Terraform 在 AWS Fargate 上部署使用 loco 构建的 Rust 应用。我们将从创建一个新项目并选择 Rest API 模板开始:
前提条件
要在 AWS Fargate 上部署我们的应用,我们需要安装以下工具:
- Docker - Docker 是一个容器化平台,允许您将应用程序及其所有依赖项打包到一个标准化的软件开发单元中。
- Terraform - Terraform 是一款开源的基础设施即代码软件工具,使您能够安全且可预测地创建、更改和改进基础设施。
- AWS CLI - AWS 命令行界面 (Command Line Interface, CLI) 是一个用于管理您的 AWS 服务的统一工具。
创建 Docker 镜像
要为我们的应用创建 Docker 镜像,我们将使用 loco CLI。cargo loco generate deployment 命令将为我们的应用创建一个 Docker 镜像。它还会为我们创建一个 Dockerfile,我们可以用它来构建镜像。
现在,我们可以构建 Docker 镜像,该镜像将用于在 AWS Fargate 上部署我们的应用。
)
=> [internal]
=> => transferring
=> => writing
=> => naming
设置 AWS
要在 AWS Fargate 上部署我们的应用,我们需要创建一个 AWS 账户并设置 AWS CLI。您可以在这里创建一个 AWS 账户。
您还需要安装 AWS CLI。您可以在这里找到有关如何执行此操作的说明。
最后,您需要创建一个 IAM 用户以与 AWS CLI 一起使用。您可以在这里找到有关如何执行此操作的说明。
现在,我们可以使用我们刚刚创建的 IAM 用户的凭证配置 AWS CLI。
在 ECR 上创建仓库
要在 AWS Fargate 上部署我们的应用,我们需要在 ECR 上创建一个仓库。您可以通过运行以下命令来执行此操作:
{
}
将 Docker 镜像推送到 ECR
现在,我们可以将 Docker 镜像推送到 ECR。您可以通过运行以下命令来执行此操作:
-1. 登录到 ECR
|
-2. 标记 Docker 镜像
-3. 将 Docker 镜像推送到 ECR
创建 Terraform 的 main.tf 文件
这是主要的 Terraform 文件,将用于在 AWS Fargate 上部署我们的应用。它将创建以下资源:
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.0"
}
archive = {
source = "hashicorp/archive"
version = "~> 2.2.0"
}
}
required_version = "~> 1.0"
}
# 配置 AWS Provider
provider "aws" {
region = "us-east-1" // 更改为您的区域
access_key = "<您的访问密钥>" // 更改为您的访问密钥
secret_key = "your secret key" // 更改为您的秘密密钥
}
resource "aws_ecr_repository" "myapp" {
name = "myapp"
}
resource "aws_ecs_cluster" "myapp_cluster" {
name = "myapp_cluster"
}
resource "aws_cloudwatch_log_group" "myapp" {
name = "/ecs/myapp"
}
resource "aws_ecs_task_definition" "myapp_task" {
family = "myapp-task"
container_definitions = <<DEFINITION
[
{
"name": "myapp-task",
"image": "${aws_ecr_repository.myapp.repository_url}",
"essential": true,
"portMappings": [
{
"containerPort": 5150
}
],
"command": ["start"],
"memory": 512,
"cpu": 256,
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-region": "us-east-2",
"awslogs-group": "/ecs/myapp",
"awslogs-stream-prefix": "ecs"
}
}
}
]
DEFINITION
requires_compatibilities = ["FARGATE"]
network_mode = "awsvpc"
memory = 512
cpu = 256
execution_role_arn = aws_iam_role.ecsTaskExecutionRole.arn
}
resource "aws_iam_role" "ecsTaskExecutionRole" {
name = "ecsTaskExecutionRoleMyapp"
assume_role_policy = data.aws_iam_policy_document.assume_role_policy.json
}
data "aws_iam_policy_document" "assume_role_policy" {
statement {
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["ecs-tasks.amazonaws.com"]
}
}
}
resource "aws_iam_role_policy_attachment" "ecsTaskExecutionRole_policy" {
role = aws_iam_role.ecsTaskExecutionRole.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
}
resource "aws_alb" "myapp" {
name = "myapp-lb"
internal = false
load_balancer_type = "application"
enable_deletion_protection = true
subnets = [
aws_subnet.public_d.id,
aws_subnet.public_e.id,
]
security_groups = [
aws_security_group.http.id,
aws_security_group.https.id,
aws_security_group.egress_all.id,
]
depends_on = [aws_internet_gateway.igw]
}
resource "aws_security_group" "load_balancer_security_group" {
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_lb_target_group" "myapp" {
name = "myapp-tg"
port = 5150
protocol = "HTTP"
target_type = "ip"
vpc_id = aws_vpc.myapp_vpc.id
health_check {
enabled = true
path = "/_health"
matcher = "200,202"
}
depends_on = [aws_alb.myapp]
}
resource "aws_alb_listener" "myapp_http" {
load_balancer_arn = aws_alb.myapp.arn
port = "80"
protocol = "HTTP"
default_action {
type = "redirect"
redirect {
port = "443"
protocol = "HTTPS"
status_code = "HTTP_301"
}
}
}
resource "aws_alb_listener" "myapp_https" {
load_balancer_arn = aws_alb.myapp.arn
port = "443"
protocol = "HTTPS"
ssl_policy = "ELBSecurityPolicy-2016-08"
certificate_arn = "<您的证书 ARN>" // 更改为您的证书 ARN
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.myapp.arn
}
}
output "alb_url" {
value = "https://${aws_alb.myapp.dns_name}"
}
resource "aws_ecs_service" "myapp" {
name = "myapp-service"
cluster = aws_ecs_cluster.myapp_cluster.id
task_definition = aws_ecs_task_definition.myapp_task.arn
launch_type = "FARGATE"
desired_count = 1
load_balancer {
target_group_arn = aws_lb_target_group.myapp.arn
container_name = aws_ecs_task_definition.myapp_task.family
container_port = 5150
}
network_configuration {
assign_public_ip = false
security_groups = [
aws_security_group.egress_all.id,
aws_security_group.ingress_api.id,
]
subnets = [
aws_subnet.private_d.id,
aws_subnet.private_e.id,
]
}
}
resource "aws_security_group" "service_security_group" {
ingress {
from_port = 0
to_port = 0
protocol = "-1"
security_groups = ["${aws_security_group.load_balancer_security_group.id}"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
此文件将创建以下资源:
- 我们应用的 ECR 仓库
- 我们应用的 ECS 集群
- 我们应用的 ECS 任务定义
- 我们应用的 ECS 服务
现在,我们需要创建一个 network.tf 文件来定义我们应用的网络配置。此文件将创建以下资源:
resource "aws_vpc" "myapp_vpc" {
cidr_block = "10.0.0.0/16"
}
resource "aws_subnet" "public_d" {
vpc_id = aws_vpc.myapp_vpc.id
cidr_block = "10.0.1.0/25"
availability_zone = "us-east-2a"
tags = {
"Name" = "public | us-east-2a"
}
}
resource "aws_subnet" "private_d" {
vpc_id = aws_vpc.myapp_vpc.id
cidr_block = "10.0.2.0/25"
availability_zone = "us-east-2b"
tags = {
"Name" = "private | us-east-2b"
}
}
resource "aws_subnet" "public_e" {
vpc_id = aws_vpc.myapp_vpc.id
cidr_block = "10.0.1.128/25"
availability_zone = "us-east-2c"
tags = {
"Name" = "public | us-east-2c"
}
}
resource "aws_subnet" "private_e" {
vpc_id = aws_vpc.myapp_vpc.id
cidr_block = "10.0.2.128/25"
availability_zone = "us-east-2c"
tags = {
"Name" = "private | us-east-2c"
}
}
resource "aws_route_table" "public" {
vpc_id = aws_vpc.myapp_vpc.id
tags = {
"Name" = "public"
}
}
resource "aws_route_table" "private" {
vpc_id = aws_vpc.myapp_vpc.id
tags = {
"Name" = "private"
}
}
resource "aws_route_table_association" "public_d_subnet" {
subnet_id = aws_subnet.public_d.id
route_table_id = aws_route_table.public.id
}
resource "aws_route_table_association" "private_d_subnet" {
subnet_id = aws_subnet.private_d.id
route_table_id = aws_route_table.private.id
}
resource "aws_route_table_association" "public_e_subnet" {
subnet_id = aws_subnet.public_e.id
route_table_id = aws_route_table.public.id
}
resource "aws_route_table_association" "private_e_subnet" {
subnet_id = aws_subnet.private_e.id
route_table_id = aws_route_table.private.id
}
resource "aws_eip" "nat" {
vpc = true
}
resource "aws_internet_gateway" "igw" {
vpc_id = aws_vpc.myapp_vpc.id
}
resource "aws_nat_gateway" "ngw" {
subnet_id = aws_subnet.public_d.id
allocation_id = aws_eip.nat.id
depends_on = [aws_internet_gateway.igw]
}
resource "aws_route" "public_igw" {
route_table_id = aws_route_table.public.id
destination_cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.igw.id
}
resource "aws_route" "private_ngw" {
route_table_id = aws_route_table.private.id
destination_cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.ngw.id
}
resource "aws_security_group" "http" {
name = "http"
description = "HTTP traffic" # HTTP 流量
vpc_id = aws_vpc.myapp_vpc.id
ingress {
from_port = 80
to_port = 80
protocol = "TCP"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_security_group" "https" {
name = "https"
description = "HTTPS traffic" # HTTPS 流量
vpc_id = aws_vpc.myapp_vpc.id
ingress {
from_port = 443
to_port = 443
protocol = "TCP"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_security_group" "egress_all" {
name = "egress-all"
description = "Allow outbound traffic" # 允许出站流量
vpc_id = aws_vpc.myapp_vpc.id
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_security_group" "ingress_api" {
name = "ingress-api"
description = "Allow ingress to App" # 允许访问 App 的入站流量
vpc_id = aws_vpc.myapp_vpc.id
ingress {
from_port = 5150
to_port = 5150
protocol = "TCP"
cidr_blocks = ["0.0.0.0/0"]
}
}
网络配置将负责创建在 AWS Fargate 上部署我们的应用所需的所有网络基础设施。我建议您阅读 AWS Fargate 文档 以了解其工作原理,您也可以阅读 Terraform 文档关于 AWS Fargate 和 AWS VPC。
所以,现在我们有了主要的 Terraform 文件和我们应用的网络配置文件。我们现在可以在 AWS Fargate 上部署我们的应用了。
在 AWS Fargate 上部署应用
要在 AWS Fargate 上部署我们的应用,我们需要运行以下命令:
-1. 初始化 Terraform
-2. 规划部署
-3. 应用部署
现在,我们可以通过访问来自 alb_url 输出的 url 来访问我们的应用。
结论
在本文中,我们探讨了如何使用 Terraform 在 AWS Fargate 上部署使用 loco 构建的 Rust 应用。我们从创建一个新项目并选择 Rest API 模板开始。然后,我们为我们的应用创建了 Docker 镜像并将其推送到 ECR。最后,我们为我们的应用创建了主要的 Terraform 文件和网络配置文件,并在 AWS Fargate 上部署了它。
这种方法使我们能够以快速可靠的方式在 AWS Fargate 上部署我们的应用。它还使我们能够通过添加更多实例来轻松扩展我们的应用。