Terraform을 사용하여 AWS ECS 클러스터 만들기

안녕하세요, 테라form과 AWS에 대한 경험을 공유하고 싶습니다.본고에서, 나는 AWS에 인프라를 구축하고 그 위에 NodeJS 응용 프로그램을 배치하는 데 사용된 자원을 묘사할 것이다.

리소스
내가 배치해야 할 응용 프로그램은 단일 NodeJS 응용 프로그램이기 때문에, 배치하고 신축성을 가지기 위해, 나는 자동 축소 도구가 있는 용기를 사용하여 CPU와 메모리 사용 상황에 따라 응용 프로그램을 축소하기로 결정했다.AWS에서 이 환경을 구축하기 위해 아래 나열된 서비스를 사용했습니다.
  • 독점 네트워크 및 네트워크(서브넷, 인터넷 그룹...)
  • 탄성 용기 등록표
  • 신축성 컨테이너 서비스
  • 어플리케이션 로드 밸런서
  • 자동 배율 조정
  • 구름 관찰

  • 지형 초기 배치
    내가 사용하는 지형 배치는 매우 간단하다.첫 번째 단계는 지형 상태를 저장하기 위해 AWS S3에 저장통을 만드는 것입니다.이것은 필수적인 것이 아니지만, 만약 누군가가 이 인프라 시설을 유지해야 한다면, 우리의 생활은 더욱 수월해질 것이다.이 구성의 파일main.tf입니다.
    # main.tf | Main Configuration
    
    terraform {
      required_providers {
        aws = {
          source  = "hashicorp/aws"
          version = "2.70.0"
        }
      }
    
      backend "s3" {
        bucket = "terraform-state-bucket"
        key    = "state/terraform_state.tfstate"
        region = "us-east-1"
      }
    }
    
    provider "aws" {
      region     = var.aws_region
      access_key = var.aws_access_key
      secret_key = var.aws_secret_key
    }
    
    provider 부분에 변수를 사용했다.우리는 atfvars에서 변수를 정의할 수 있다.잠시 후에 나는 이 문장에서 해석할 것이다.
    업데이트: 이 초기 설정을 사용하면 실행 terraform init 만 가능합니다.

    독점 네트워크 및 네트워크
    VPC를 만들고 네트워크 리소스를 구성해 보겠습니다.다음 예제 코드는 VPC를 생성합니다.
    # vpc.tf | VPC Configuration
    
    resource "aws_vpc" "aws-vpc" {
      cidr_block           = "10.10.0.0/16"
      enable_dns_hostnames = true
      enable_dns_support   = true
      tags = {
        Name        = "${var.app_name}-vpc"
        Environment = var.app_environment
      }
    }
    
    네트워크를 연결하기 위해서는 전유 네트워크 안에서 공공과 사유 서브네트워크, 그리고 공공 서브네트워크의 인터넷 스위치와 루트표를 만들 필요가 있다.다음 예시에서 이 자원을 만들 것입니다
    # networking.tf | Network Configuration
    
    resource "aws_internet_gateway" "aws-igw" {
      vpc_id = aws_vpc.aws-vpc.id
      tags = {
        Name        = "${var.app_name}-igw"
        Environment = var.app_environment
      }
    
    }
    
    resource "aws_subnet" "private" {
      vpc_id            = aws_vpc.aws-vpc.id
      count             = length(var.private_subnets)
      cidr_block        = element(var.private_subnets, count.index)
      availability_zone = element(var.availability_zones, count.index)
    
      tags = {
        Name        = "${var.app_name}-private-subnet-${count.index + 1}"
        Environment = var.app_environment
      }
    }
    
    resource "aws_subnet" "public" {
      vpc_id                  = aws_vpc.aws-vpc.id
      cidr_block              = element(var.public_subnets, count.index)
      availability_zone       = element(var.availability_zones, count.index)
      count                   = length(var.public_subnets)
      map_public_ip_on_launch = true
    
      tags = {
        Name        = "${var.app_name}-public-subnet-${count.index + 1}"
        Environment = var.app_environment
      }
    }
    
    resource "aws_route_table" "public" {
      vpc_id = aws_vpc.aws-vpc.id
    
      tags = {
        Name        = "${var.app_name}-routing-table-public"
        Environment = var.app_environment
      }
    }
    
    resource "aws_route" "public" {
      route_table_id         = aws_route_table.public.id
      destination_cidr_block = "0.0.0.0/0"
      gateway_id             = aws_internet_gateway.aws-igw.id
    }
    
    resource "aws_route_table_association" "public" {
      count          = length(var.public_subnets)
      subnet_id      = element(aws_subnet.public.*.id, count.index)
      route_table_id = aws_route_table.public.id
    }
    

    컨테이너 등록 및 ECS 클러스터
    이제 컨테이너 레지스트리와 ECS 클러스터를 만들 때가 되었습니다.먼저 다음 코드로 컨테이너 레지스트리를 만듭니다.
    # ecr.tf | Elastic Container Repository
    
    resource "aws_ecr_repository" "aws-ecr" {
      name = "${var.app_name}-${var.app_environment}-ecr"
      tags = {
        Name        = "${var.app_name}-ecr"
        Environment = var.app_environment
      }
    }
    
    ECR은 배포할 응용 프로그램의 Docker 이미지를 저장하는 저장소입니다.Docker에 익숙하면 Docker Hub처럼 작동합니다.로컬에서 Docker 이미지를 구축하여 ECR로 전송하거나 CI/CD 플랫폼을 사용하여 구현할 수 있습니다.
    이제 ECS 클러스터, 서비스 및 작업 정의를 작성합니다.
    서비스는 우리가 집단에서 여러 작업을 동시에 실행하고 유지할 수 있도록 설정하는 것이다.컨테이너는 서비스에서 작업을 실행하는 데 사용되는 작업에 의해 정의됩니다.
    ECS 클러스터를 생성하기 전에 서비스가 ECR에서 이미지를 추출할 수 있도록 IAM 정책을 작성해야 합니다.
    # iam.tf | IAM Role Policies
    
    resource "aws_iam_role" "ecsTaskExecutionRole" {
      name               = "${var.app_name}-execution-task-role"
      assume_role_policy = data.aws_iam_policy_document.assume_role_policy.json
      tags = {
        Name        = "${var.app_name}-iam-role"
        Environment = var.app_environment
      }
    }
    
    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/AmazonEC2ContainerServiceforEC2Role"
    }
    
    이제 ECS를 위해 필요한 것을 만듭니다.먼저 ECS 클러스터를 만듭니다.
    resource "aws_ecs_cluster" "aws-ecs-cluster" {
      name = "${var.app_name}-${var.app_environment}-cluster"
      tags = {
        Name        = "${var.app_name}-ecs"
        Environment = var.app_environment
      }
    }
    
    컨테이너 로그를 가져오려면 CloudWatch에 로그 그룹을 만들었습니다.
    resource "aws_cloudwatch_log_group" "log-group" {
      name = "${var.app_name}-${var.app_environment}-logs"
    
      tags = {
        Application = var.app_name
        Environment = var.app_environment
      }
    }
    
    AWS FARGATE와 호환되는 작업 정의를 만들었습니다. 인프라를 더욱 잘 활용하기 위해 이렇게 하고 싶습니다.
    data "template_file" "env_vars" {
      template = file("env_vars.json")
    }
    
    resource "aws_ecs_task_definition" "aws-ecs-task" {
      family = "${var.app_name}-task"
    
      container_definitions = <<DEFINITION
      [
        {
          "name": "${var.app_name}-${var.app_environment}-container",
          "image": "${aws_ecr_repository.aws-ecr.repository_url}:latest",
          "entryPoint": [],
          "environment": ${data.template_file.env_vars.rendered},
          "essential": true,
          "logConfiguration": {
            "logDriver": "awslogs",
            "options": {
              "awslogs-group": "${aws_cloudwatch_log_group.log-group.id}",
              "awslogs-region": "${var.aws_region}",
              "awslogs-stream-prefix": "${var.app_name}-${var.app_environment}"
            }
          },
          "portMappings": [
            {
              "containerPort": 8080,
              "hostPort": 8080
            }
          ],
          "cpu": 256,
          "memory": 512,
          "networkMode": "awsvpc"
        }
      ]
      DEFINITION
    
      requires_compatibilities = ["FARGATE"]
      network_mode             = "awsvpc"
      memory                   = "512"
      cpu                      = "256"
      execution_role_arn       = aws_iam_role.ecsTaskExecutionRole.arn
      task_role_arn            = aws_iam_role.ecsTaskExecutionRole.arn
    
      tags = {
        Name        = "${var.app_name}-ecs-td"
        Environment = var.app_environment
      }
    }
    
    data "aws_ecs_task_definition" "main" {
      task_definition = aws_ecs_task_definition.aws-ecs-task.family
    }
    
    작업 정의에 대한 관찰 결과, 나는 Terraformdata 함수를 사용하여 JSON 파일에서 정의한 환경 변수를 설정하고 있다. (AWS EKS 또는 다른 방식으로 기밀을 저장하는 것은 개선이 필요하다.)
    자, 이제 ECS 서비스를 만들겠습니다.
    resource "aws_ecs_service" "aws-ecs-service" {
      name                 = "${var.app_name}-${var.app_environment}-ecs-service"
      cluster              = aws_ecs_cluster.aws-ecs-cluster.id
      task_definition      = "${aws_ecs_task_definition.aws-ecs-task.family}:${max(aws_ecs_task_definition.aws-ecs-task.revision, data.aws_ecs_task_definition.main.revision)}"
      launch_type          = "FARGATE"
      scheduling_strategy  = "REPLICA"
      desired_count        = 1
      force_new_deployment = true
    
      network_configuration {
        subnets          = aws_subnet.private.*.id
        assign_public_ip = false
        security_groups = [
          aws_security_group.service_security_group.id,
          aws_security_group.load_balancer_security_group.id
        ]
      }
    
      load_balancer {
        target_group_arn = aws_lb_target_group.target_group.arn
        container_name   = "${var.app_name}-${var.app_environment}-container"
        container_port   = 8080
      }
    
      depends_on = [aws_lb_listener.listener]
    }
    
    용기와의 외부 연결을 피하기 위해 보안 그룹도 정의했다.
    resource "aws_security_group" "service_security_group" {
      vpc_id = aws_vpc.aws-vpc.id
    
      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"]
        ipv6_cidr_blocks = ["::/0"]
      }
    
      tags = {
        Name        = "${var.app_name}-service-sg"
        Environment = var.app_environment
      }
    }
    

    어플리케이션 로드 밸런서
    다음 단계는 부하 균형기를 설정하는 것입니다.ECS 구성에서 알 수 있듯이 이 구성에는 a load_balancer에 대한 참조가 있습니다.
    resource "aws_alb" "application_load_balancer" {
      name               = "${var.app_name}-${var.app_environment}-alb"
      internal           = false
      load_balancer_type = "application"
      subnets            = aws_subnet.public.*.id
      security_groups    = [aws_security_group.load_balancer_security_group.id]
    
      tags = {
        Name        = "${var.app_name}-alb"
        Environment = var.app_environment
      }
    }
    
    이제 부하 균형기에 안전 그룹을 추가합니다
    resource "aws_security_group" "load_balancer_security_group" {
      vpc_id = aws_vpc.aws-vpc.id
    
      ingress {
        from_port        = 80
        to_port          = 80
        protocol         = "tcp"
        cidr_blocks      = ["0.0.0.0/0"]
        ipv6_cidr_blocks = ["::/0"]
      }
    
      egress {
        from_port        = 0
        to_port          = 0
        protocol         = "-1"
        cidr_blocks      = ["0.0.0.0/0"]
        ipv6_cidr_blocks = ["::/0"]
      }
      tags = {
        Name        = "${var.app_name}-sg"
        Environment = var.app_environment
      }
    }
    
    부하 균형기 목표 그룹을 만들어야 합니다. 부하 균형기와 용기를 연결합니다.
    resource "aws_lb_target_group" "target_group" {
      name        = "${var.app_name}-${var.app_environment}-tg"
      port        = 80
      protocol    = "HTTP"
      target_type = "ip"
      vpc_id      = aws_vpc.aws-vpc.id
    
      health_check {
        healthy_threshold   = "3"
        interval            = "300"
        protocol            = "HTTP"
        matcher             = "200"
        timeout             = "3"
        path                = "/v1/status"
        unhealthy_threshold = "2"
      }
    
      tags = {
        Name        = "${var.app_name}-lb-tg"
        Environment = var.app_environment
      }
    }
    
    여기서 매우 중요한 점은 path 중의 속성health_check이다.이것은 응용 프로그램의 루트입니다. 부하 균형기는 이 루트를 사용하여 응용 프로그램의 상태를 검사합니다.
    마지막으로 out 부하 밸런서에 HTTP 탐지기를 만듭니다.
    resource "aws_lb_listener" "listener" {
      load_balancer_arn = aws_alb.application_load_balancer.id
      port              = "80"
      protocol          = "HTTP"
    
      default_action {
        type             = "forward"
        target_group_arn = aws_lb_target_group.target_group.id
      }
    }
    

    자동 배율 조정
    그래서 자동 축소는 내가 개발하고 있는 응용 프로그램에 있어서 없어서는 안 될 것이다.AWS에서 그것을 설정하려면, 나는 자동 축소 목표와 두 개의 간단한 자동 축소 정책을 만들어야 한다.하나는 CPU 사용량에 따라 확장하고, 다른 하나는 메모리 사용량에 사용됩니다.
    # autoscaling.tf | Auto Scaling Group
    
    resource "aws_appautoscaling_target" "ecs_target" {
      max_capacity       = 2
      min_capacity       = 1
      resource_id        = "service/${aws_ecs_cluster.aws-ecs-cluster.name}/${aws_ecs_service.aws-ecs-service.name}"
      scalable_dimension = "ecs:service:DesiredCount"
      service_namespace  = "ecs"
    }
    
    resource "aws_appautoscaling_policy" "ecs_policy_memory" {
      name               = "${var.app_name}-${var.app_environment}-memory-autoscaling"
      policy_type        = "TargetTrackingScaling"
      resource_id        = aws_appautoscaling_target.ecs_target.resource_id
      scalable_dimension = aws_appautoscaling_target.ecs_target.scalable_dimension
      service_namespace  = aws_appautoscaling_target.ecs_target.service_namespace
    
      target_tracking_scaling_policy_configuration {
        predefined_metric_specification {
          predefined_metric_type = "ECSServiceAverageMemoryUtilization"
        }
    
        target_value = 80
      }
    }
    
    resource "aws_appautoscaling_policy" "ecs_policy_cpu" {
      name               = "${var.app_name}-${var.app_environment}-cpu-autoscaling"
      policy_type        = "TargetTrackingScaling"
      resource_id        = aws_appautoscaling_target.ecs_target.resource_id
      scalable_dimension = aws_appautoscaling_target.ecs_target.scalable_dimension
      service_namespace  = aws_appautoscaling_target.ecs_target.service_namespace
    
      target_tracking_scaling_policy_configuration {
        predefined_metric_specification {
          predefined_metric_type = "ECSServiceAverageCPUUtilization"
        }
    
        target_value = 80
      }
    }
    
    따라서 메모리나 cpu의 사용률이 사용률의 80%에 이르면 응용 프로그램이 확대될 것이다.이 값보다 낮을 때 프로그램은 비례에 따라 축소됩니다.

    변량
    나는 네가 우리가 지형 설정 파일에 많은 변수를 사용했다는 것을 이미 알아차렸다고 믿는다.변수를 사용하기 위해 variables.tf라는 파일을 만들었습니다.이 파일은 변수 정의만 있습니다.
    # variables.tf | Auth and Application variables
    
    variable "aws_access_key" {
      type        = string
      description = "AWS Access Key"
    }
    
    variable "aws_secret_key" {
      type        = string
      description = "AWS Secret Key"
    }
    
    variable "aws_region" {
      type        = string
      description = "AWS Region"
    }
    
    variable "aws_cloudwatch_retention_in_days" {
      type        = number
      description = "AWS CloudWatch Logs Retention in Days"
      default     = 1
    }
    
    variable "app_name" {
      type        = string
      description = "Application Name"
    }
    
    variable "app_environment" {
      type        = string
      description = "Application Environment"
    }
    
    variable "cidr" {
      description = "The CIDR block for the VPC."
      default     = "10.0.0.0/16"
    }
    
    variable "public_subnets" {
      description = "List of public subnets"
    }
    
    variable "private_subnets" {
      description = "List of private subnets"
    }
    
    variable "availability_zones" {
      description = "List of availability zones"
    }
    
    각 변수의 값은 terraform.tfvars라는 파일에 정의됩니다.
    aws_region        = "us-east-1"
    aws_access_key    = "your aws access key"
    aws_secret_key    = "your aws secret key"
    
    # these are zones and subnets examples
    availability_zones = ["us-east-1a", "us-east-1b"]
    public_subnets     = ["10.10.100.0/24", "10.10.101.0/24"]
    private_subnets    = ["10.10.0.0/24", "10.10.1.0/24"]
    
    # these are used for tags
    app_name        = "node-js-app"
    app_environment = "production"
    
    이 파일은 내 저장소에 제출되지 않았습니다.나는 로컬에서 그것을 만들고 S3를 사용하여 접근을 관리하고 버전을 제어했다.그것은 아직 약간의 개선이 필요하다. 나는 더욱 개선할 것이다.
    업데이트: 현재 모든 프로필을 정확하게 작성한 후 명령terraform plan을 실행하여 변경 사항을 검사하고 terraform apply 변경 사항을 검사하고 적용합니다.
    너도 데이터베이스의 상황을 물어볼 수 있다.이 프로젝트에서, 나는 MongoCloud에 그룹을 만들고, 증거를 환경에 두었다.
    온전한 코드는 나의 [Github]에서 찾을 수 있다.( https://github.com/thnery/terraform-aws-template )

    고마워요 고마워요
    이 글을 읽어 주셔서 감사합니다.나는 그것이 쓸모가 있기를 바란다.만약 당신에게 어떤 피드백이 있다면 저에게 알려 주세요.

    좋은 웹페이지 즐겨찾기