Dynamic 블록으로 AWS 서비스와 태스크 정의하기
1. ECS에서 태스크 정의와 서비스 간략 정리
ECS에서 태스크 정의와 서비스는 컨테이너 기반 애플리케이션을 배포하고 관리하는 핵심 요소다.
태스크 정의는 컨테이너의 실행 구성을 정의하며, 서비스는 여러 인스턴스에서 컨테이너를 실행하고 관리한다. 이 두 요소를 함께 사용하여 애플리케이션을 확장하고 가용성을 보장할 수 있다.
태스크 정의와 서비스는 서로 연동하여 컨테이너화된 애플리케이션을 실행하고 관리한다. 태스크 정의에서는 컨테이너 이미지, 포트 매핑, 리소스 요구 사항 등 컨테이너 실행에 필요한 정보를 정의한다. 서비스는 이러한 태스크 정의를 기반으로 컨테이너를 여러 인스턴스에 배포하고 관리한다
컨테이너 이미지란 컨테이너화된 애플리케이션을 실행하기 위한 파일 시스템과 실행 환경을 포함하는 패키지다. 간단히 말해, 컨테이너 이미지는 애플리케이션과 그 실행에 필요한 모든 것을 포함하는 '배포 가능한 패키지'다.
컨테이너 이미지는 일반적으로 다음과 같은 내용을 포함한다:
1. 실행할 애플리케이션
이미지 내에는 실행할 애플리케이션의 실행 파일과 관련 파일이 포함된다. 이것은 컨테이너가 시작될 때 실행되는 애플리케이션이다. 예를 들어, 스프링 부트 프로젝트라면 실행할 애플리케이션은 주로 JAR 파일이다.
2. 종속성 및 라이브러리
애플리케이션이 실행될 때 필요한 종속성 및 라이브러리 파일도 이미지에 포함된다. 이렇게 함으로써 애플리케이션이 독립적으로 실행될 수 있다.
3. 실행 환경 설정
이미지는 애플리케이션을 실행하는 데 필요한 환경 변수, 설정 파일 및 실행 시 인수와 같은 실행 환경 설정을 포함할 수 있다.
컨테이너 이미지는 일반적으로 컨테이너 레지스트리(예: Docker Hub, Amazon ECR)에 저장되며, ECS와 같은 컨테이너 오케스트레이션 플랫폼에서 사용된다. 태스크 정의에서 컨테이너 이미지를 지정하면 해당 이미지를 기반으로 컨테이너가 생성되고 실행된다.
2. AWS 내부적으로 태스크 정의와 서비스가 돌아가는 순서
테라폼으로 코드를 작성하기 전에 AWS 내부적으로 태스크 정의와 서비스가 어떻게 동작하는지 순서를 알아보자.
1. 태스크 정의 생성
태스크 정의를 생성하여 컨테이너 실행 구성을 정의한다. 이 구성에는 컨테이너 이미지, 포트 매핑, 리소스 요구 사항 등이 포함된다.
2. 서비스 생성
서비스를 생성하면 ECS 클러스터에서 지정된 수의 태스크 인스턴스(컨테이너 실행 환경)를 시작한다. 이것은 애플리케이션의 확장성을 제공한다. 즉, 필요에 따라 더 많은 인스턴스를 자동으로 시작할 수 있다.
3. 컨테이너 배포
서비스는 태스크 정의를 참조하여 각 태스크 인스턴스에 컨테이너를 배포한다. 이 단계에서는 애플리케이션 코드가 실행된다.
4. 건강 상태 확인 및 관리
서비스는 컨테이너 인스턴스의 건강 상태를 주기적으로 확인한다. 이를 통해 비정상적인 상태의 컨테이너 인스턴스를 식별하고, 필요한 경우 재시작하거나 교체한다.
5. 로드 밸런서 연결(선택 사항)
서비스는 로드 밸런서와 연결하여 들어오는 요청을 분산할 수 있다. 이로써 여러 컨테이너 인스턴스 간에 요청이 균등하게 분배되며, 애플리케이션의 확장성과 가용성을 높일 수 있다. 이는 선택사항이다.
3. 태스크 정의 모듈
이제 태스크 정의를 생성하는 모듈에 대해서 알아보자.
resource "aws_ecs_task_definition" "ecs_task_prod" {
family = var.family_name
network_mode = "bridge"
requires_compatibilities = ["EC2"]
execution_role_arn = var.execution_role_arn
cpu = var.cpu
memory = var.memory
container_definitions = var.container_definitions
}
- aws_ecs_task_definition 리소스를 정의하여 ECS 태스크 정의를 생성한다.
- family: 태스크 정의의 이름을 나타낸다. 이것은 태스크 정의를 식별하는 데 사용된다.
- network_mode: 컨테이너 네트워크 모드를 "bridge"로 설정한다. 이 모드는 컨테이너가 호스트의 네트워크 네임스페이스와 분리된 독립적인 네트워크 스택을 갖게 한다.
- requires_compatibilities: ECS 서비스와 호환되는 컴퓨팅 환경을 "EC2"로 지정한다.
- execution_role_arn: ECS 태스크 실행을 위한 IAM 역할 ARN을 설정한다.
- cpu와 memory: 태스크의 CPU 및 메모리 리소스를 설정한다.
- container_definitions: 컨테이너 정의를 JSON 형식으로 받아와서 사용한다. 이 부분에는 각 컨테이너의 실행 구성이 들어간다.
network_mode, requires_compatibilities, container_definitions의 개념을 더 자세히 살펴보자
AWS ECS에서는 다양한 네트워크 모드를 지원하며, 각 네트워크 모드는 다른 네트워킹 환경을 제공한다. 네트워크 모드는 컨테이너가 호스트 및 다른 컨테이너와 어떻게 통신할지를 결정하는 중요한 요소 중 하나다.
주요 ECS 네트워크 모드와 그 역할은 다음과 같다:
bridge 모드
bridge 모드에서 각 컨테이너는 자신만의 독립된 공간을 가진다고 생각할 수 있다. 이는 컨테이너가 호스트의 네트워크 네임스페이스와는 완전히 분리되어 자체 네트워크 스택을 갖게 됨을 의미한다. 결과적으로, 각 컨테이너는 자신만의 고유한 IP 주소를 가지며, 이는 마치 각자 다른 주소를 가진 집에 사는 것과 유사하다.
컨테이너들 사이의 통신은 이러한 독립된 IP 주소와 포트를 통해 이루어진다. 즉, 컨테이너가 다른 컨테이너와 정보를 주고받으려면 각각의 주소를 정확히 알아야 한다. 이런 방식은 컨테이너들이 서로 격리되어야 하거나 각자 독립적인 네트워크 환경이 필요한 상황에 적합하다. 예를 들어, 서로 다른 서비스들이 간섭 없이 독립적으로 작동해야 할 때 bridge 모드가 유리하다.
host 모드
host 모드에서 컨테이너는 마치 호스트와 같은 집에 살고 있는 것처럼 호스트의 네트워크 환경을 공유한다. 이는 컨테이너가 호스트의 네트워크 네임스페이스를 직접 사용한다는 것을 의미한다. 그 결과, 컨테이너와 호스트는 같은 IP 주소를 공유하며, 네트워크 포트도 함께 사용한다.
이렇게 되면, 컨테이너 간 통신은 호스트의 IP 주소와 포트를 통해 이루어진다. 즉, 컨테이너들은 마치 같은 집에 사는 가족처럼 호스트의 네트워크 자원을 공유하고 사용한다. 이 모드는 특히 컨테이너가 호스트의 네트워크와 직접적으로 연결되어야 하는 경우에 유용하다. 예를 들어, 호스트의 네트워크 스택을 공유해야 하는 특정 네트워크 응용 프로그램에서 이 모드는 매우 효과적이다. 이는 컨테이너가 호스트와 동일한 네트워크 환경을 사용해야 할 때 필요한 간소화된 설정과 직접적인 네트워크 액세스를 제공한다.
awsvpc 모드
awsvpc 모드에서 각 컨테이너는 마치 개별적인 집을 가진 것과 같이 고유한 IP 주소를 할당받고, 이를 통해 AWS의 Virtual Private Cloud(VPC) 네트워크 기능을 활용한다. 각 컨테이너는 VPC 내의 가상 네트워크를 통해 서로 통신하게 되며, 이 과정에서 AWS의 다양한 네트워크 보안 기능들을 사용할 수 있다.
예를 들어, 보안 그룹이나 ACL 같은 보안 기능들은 컨테이너가 VPC 내에서 안전하게 통신할 수 있도록 도와준다. 이 모드는 컨테이너들을 VPC 네트워크에 직접 연결함으로써 네트워크 보안과 관리를 용이하게 만들어 주며, 이 때문에 많은 경우에 가장 일반적으로 사용되는 모드다. 각 컨테이너가 자신만의 독립된 주소를 가지고 VPC의 보안과 관리 기능을 최대한 활용할 수 있기 때문에, 특히 네트워크 보안이 중요한 환경에서 선호된다.
각 네트워크 모드는 특정 시나리오 및 요구 사항에 맞게 선택된다. 대부분의 경우, awsvpc 모드가 가장 일반적으로 사용되며, 고급 네트워크 기능 및 보안을 제공한다. 선택한 네트워크 모드는 컨테이너 및 애플리케이션의 특성에 따라 달라질 수 있다.
먼저, AWS ECS에서 컨테이너를 실행할 때 네트워크 모드를 정의해야 한다. 이는 컨테이너가 어떻게 네트워크에 연결되고 외부와 통신할지를 결정하는 중요한 부분이다. 기본적으로 두 가지 주요 네트워크 모드가 있다: awsvpc와 bidge.
awsvpc 모드에서는 각 컨테이너가 자체적인 네트워크 인터페이스를 가지며, 이는 마치 각 컨테이너가 별도의 작은 컴퓨터처럼 독립적으로 네트워크에 연결된 것처럼 작동한다. 그러나 awsvpc 모드의 단점은 컨테이너 포트와 호스트 포트가 일치해야 한다는 것이다.
우리 프로젝트의 핵심 요구 사항 중 하나는 무중단 배포를 구현하는 것이었다. 무중단 배포는 새로운 버전의 애플리케이션을 기존 버전과 병행하여 실행하면서, 사용자에게 서비스 중단 없이 새로운 버전으로 전환하는 방법이다. 이를 위해서는 여러 버전의 애플리케이션을 같은 서버에서 동시에 실행할 수 있어야 한다. awsvpc 모드에서는 포트 번호가 겹치기 때문에 이런 방식의 배포가 어렵다.
반면 bridge 모드에서는 컨테이너가 호스트 서버의 네트워크를 공유하면서도 독립적인 포트 번호를 사용할 수 있다. 이는 컨테이너가 호스트의 다른 포트에 동적으로 연결될 수 있음을 의미하며, 따라서 여러 컨테이너가 동일한 포트 번호를 사용하면서도 서로 다른 호스트 포트에 연결될 수 있다. 이러한 특성 덕분에 bridge 모드는 무중단 배포에 이상적이다. 새로운 버전의 컨테이너가 기존 버전과 겹치지 않으면서도 동일한 호스트에서 실행될 수 있기 때문이다.
또한, bridge 모드는 ALB와의 통합에서도 장점을 가진다. ALB는 들어오는 트래픽을 적절한 컨테이너의 동적 포트로 라우팅 할 수 있으며, 이는 사용자에게 서비스 중단 없이 항상 최신 버전의 애플리케이션을 제공하는 데 도움이 된다.
결국, bridge 네트워크 모드를 선택함으로써 프로젝트는 무중단 배포의 유연성을 획득하고, 각 서비스의 독립적인 실행과 확장성을 지원한다.
이 옵션은 AWS ECS에서 태스크를 실행할 때 사용되는 호환성 유형을 정의한다. 이 설정은 태스크가 어떤 종류의 인프라스트럭처에서 실행될 수 있는지를 결정한다. AWS ECS에서는 주로 두 가지 옵션이 사용된다: EC2와 FARGATE.
EC2 옵션은 사용자가 EC2 인스턴스를 관리하며 태스크의 배치와 스케일링에 대한 더 많은 제어를 가지는 경우에 이상적이다. 이는 세밀한 네트워킹과 보안 설정을 제공하며, 사용자 정의 AMI와 보안 그룹 사용이 가능하다는 장점을 가진다. 반면, FARGATE 옵션은 서버리스 컴퓨팅 환경에서 태스크를 실행하며, AWS가 컴퓨팅 자원을 자동으로 관리한다. 이는 인프라 관리의 복잡성을 제거하고 빠른 배포를 가능하게 하지만, EC2에 비해 유연성이 떨어지고 비용이 높을 수 있다.
우리 프로젝트는 Spring Boot로 구축된 마이크로서비스 아키텍처(MSA) 환경에 기반을 두고 있으며, 여기에는 여러 서버들이 상호 작용하는 구조가 포함된다. 그리고 Application Load Balancer(ALB)를 활용하여 각 서비스 간 효율적인 트래픽 분산과 연결을 관리한다. 이러한 복잡한 설정과 높은 유연성 요구는 EC2 옵션을 선택하는 데 결정적인 요인이다. EC2를 사용함으로써, 우리는 네트워크 설정을 세밀하게 조정할 수 있고, 서로 다른 서비스 간의 효율적인 통신과 통합을 보장할 수 있다. 또한, 사용자 정의 AMI를 통해 필요한 소프트웨어 및 구성을 미리 설정하여 효율적인 배포와 관리가 가능하다.
FARGATE의 서버리스 환경도 매력적이지만, 우리의 경우에는 EC2의 세밀한 제어와 맞춤형 설정이 더 중요했다. EC2는 우리가 필요로 하는 정교한 네트워킹, 보안 설정, 그리고 향상된 성능 관리를 제공한다. 이런 이유로, 우리 프로젝트에서 EC2를 선택했다
container_definitions는 AWS ECS 태스크 정의에서 컨테이너들의 설정을 정의하는 JSON 형식의 문자열이다. 이 JSON 문자열은 컨테이너를 실행하는 데 필요한 모든 세부 정보를 포함한다. 여기에는 컨테이너의 이미지, 사용할 CPU 및 메모리 자원, 환경 변수, 포트 매핑, 볼륨 마운트 등의 설정이 포함될 수 있다.
다음은 container_definitions에 사용할 수 있는 예시 JSON 코드다.
[
{
"name": "my-app-container",
"image": "my-docker-image:latest",
"cpu": 100,
"memory": 128,
"essential": true,
"portMappings": [
{
"containerPort": 80,
"hostPort": 80
}
],
"environment": [
{
"name": "ENV_VAR_NAME",
"value": "Some value"
}
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/my-app",
"awslogs-region": "ap-northeast-2",
"awslogs-stream-prefix": "ecs"
}
}
}
]
- name: 컨테이너의 이름을 정의한다.
- image: 사용할 컨테이너 이미지를 지정한다. 이 이미지는 Docker Hub나 ECR 같은 컨테이너 이미지 저장소에 저장된 이미지가 될 수 있다.
- cpu 및 memory: 컨테이너에 할당될 CPU와 메모리의 양을 지정한다. 이 경우 CPU는 100 단위로, 메모리는 128 메가바이트(MB)로 설정되어 있다.
- essential: 이 옵션이 true로 설정되면, 이 컨테이너가 실패하면, ECS는 태스크 전체를 실패하고 중단시킨다.
- portMappings: 컨테이너와 호스트 간의 포트 매핑을 정의한다. 여기서는 컨테이너의 80번 포트를 호스트의 80번 포트에 매핑하고 있다.
- environment: 컨테이너 내에서 사용될 환경 변수를 설정한다. 이 예시에서는 ENV_VAR_NAME이라는 환경 변수에 "Some value"라는 값을 할당한다.
- logConfiguration: 로그 구성을 정의한다. 이 경우, awslogs 드라이버를 사용하여 AWS의 로그 서비스에 로그를 보내고, 로그 그룹 이름, 로그가 저장될 리전, 스트림 접두사를 설정한다.
이 설정은 AWS ECS에서 컨테이너를 실행할 때 필요한 여러 세부 사항을 포함하며, 각 설정은 컨테이너의 동작 방식과 관리 방법에 영향을 미친다.
모듈 외부에서 태스크 정의의 ARN을 사용할 수 있도록 출력을 정의한 부분이다.
output "ecs_task_definition_arn" {
description = "ARN of the ECS task definition"
value = aws_ecs_task_definition.ecs_task_prod.arn
}
태스크 정의 모듈에서 사용할 변수를 선언한 파일이다.
variable "execution_role_arn" {
description = "Execution role ARN for ECS task execution"
type = string
default = "arn:aws:iam::234234:role/ecsTaskExecutionRole"
}
variable "cpu" {
description = "CPU units for the ECS task"
type = number
default = 256
}
variable "memory" {
description = "Memory for the ECS task (in MiB)"
type = number
default = 512
}
variable "family_name" {
description = "task_name"
type = string
}
variable "container_definitions" {
description = "JSON formatted container definitions for the zipkin service"
type = string
}
- execution_role_arn: ECS 태스크 실행을 위한 IAM 역할 ARN을 설정한다. 이 값을 변경하여 실행 역할을 지정할 수 있다.
- cpu와 memory: 태스크의 CPU 및 메모리 리소스를 설정한다. 이 값을 변경하여 리소스 할당을 조절할 수 있다.
- family_name: 태스크 정의의 이름을 정의한다. 모듈 사용자가 원하는 이름으로 변경할 수 있다.
- container_definitions: 컨테이너 정의를 JSON 형식으로 정의한다.
태스크 모듈을 정의했으니 이제 서비스 모듈을 정의해 보자
4. 서비스 모듈
AWS ECS에서 '서비스'는 태스크(즉, 컨테이너의 집합)를 관리하고, 지정된 수의 태스크 복사본이 지속적으로 실행되도록 하는 역할을 한다. 서비스를 사용하면 애플리케이션의 확장성과 가용성을 보장할 수 있다. 예를 들어, 웹 애플리케이션을 운영 중일 때 트래픽 증가에 따라 자동으로 컨테이너의 수를 늘리거나 줄일 수 있다.
Terraform을 사용하여 ECS 서비스를 생성할 때, 몇 가지 주요 구성 요소가 필요하다:
서비스 이름과 클러스터
서비스를 식별할 이름과 이 서비스가 실행될 ECS 클러스터를 지정한다.
태스크 정의
서비스에서 실행될 태스크의 정의를 지정한다. 이는 앞서 정의한 container_definitions를 포함하는 태스크 정의를 참조한다.
용량 제공자와 로드 밸런서
서비스가 실행될 컴퓨팅 자원을 제어하는 용량 제공자와, 외부 트래픽을 컨테이너로 라우팅 하는 로드 밸런서를 설정할 수 있다.
네트워킹 설정
서비스가 사용할 네트워크 구성을 정의한다. 여기에는 서브넷 ID와 보안 그룹이 포함될 수 있다.
스케일링 정책
트래픽이 증가하거나 감소함에 따라 컨테이너의 수를 자동으로 조정하는 스케일링 정책을 설정할 수 있다.
이제 직접 코드를 작성해 보자.
이 코드는 ECS 서비스를 정의하고 로드 밸런서와 연결하는 내용이다.
resource "aws_ecs_service" "ecs_service" {
name = var.ecs_service_name
cluster = var.ecs_cluster_name
task_definition = var.task_definition
desired_count = 1
force_new_deployment = false
dynamic "capacity_provider_strategy" {
for_each = var.capacity_provider_name != null ? [1] : []
content {
capacity_provider = var.capacity_provider_name
weight = 100
}
}
dynamic "load_balancer" {
for_each = var.target_group_arn != null ? [1] : []
content {
target_group_arn = var.target_group_arn
container_name = var.container_name
container_port = var.container_port
}
}
}
위 코드에서 var.## 로 작성된 부분은 이 모듈을 사용하는 main.tf에서 제공되는 변수를 통해 지정된다.
- name: ECS 서비스의 이름을 지정한다.
- cluster: 서비스가 배포될 ECS 클러스터의 이름을 지정한다.
- task_definition: 사용할 ECS 태스크 정의를 지정한다.
- desired_count: ECS 서비스에서 실행할 태스크의 수를 지정한다. 여기서는 1로 설정되어 있다.
- force_new_deployment: 새 배포를 강제할지 여부를 결정한다. 기본적으로 false로 설정되어 있다.
- dynamic "capacity_provider_strategy": ECS 서비스가 태스크를 배치할 때 사용할 용량 공급자와 그 전략을 정의한다. var.capacity_provider_name이 null이 아니면 활성화된다.
- dynamic "load_balancer": 로드 밸런서 설정을 정의한다. var.target_group_arn이 null이 아닐 때 활성화된다.
여기서 처음 보는 dynamic 블록에 대해서 자세히 살펴보자.
dynamic 블록은 Terraform에서 상당히 유용한 개념으로, 주로 반복되는 코드 블록을 줄이고 유연성을 높이는 데 사용된다. dynamic 블록은 이미 익히 알고 있는 resource와 같은 타입 지정이 아니라, Terraform 구성 내에서 반복되는 코드 구조를 단순화하는 기능을 제공한다.
dynamic 블록은 반복적인 블록을 간소화시켜 주는 기능으로 동일한 유형의 여러 구성 요소를 여러 번 정의해야 하는 경우에 사용된다. 예를 들어, 여러 네트워크 인터페이스 또는 여러 태그를 설정해야 하는 경우 유용하다.
dynamic 블록 내에서 for_each를 사용하여 조건에 따라 반복할 항목의 수를 결정한다. 이는 변수의 값이나 다른 조건에 따라 반복할 요소의 수를 동적으로 결정할 때 사용된다.
또한 모듈을 더 유연하게 만들 수 있으며, 사용자가 특정 상황에 따라 모듈을 다르게 구성할 수 있도록 한다.
- capacity_provider_strategy: var.capacity_provider_name이 null이 아닌 경우에만 용량 공급자 전략을 생성한다.
- load_balancer: var.target_group_arn이 null이 아닌 경우에만 로드 밸런서 설정을 생성한다.
위 코드에서 dynamic 블록을 사용하는 것은 용량 공급자(capacity provider)나 로드 밸런서(load balancer)와 같은 구성 요소를 동적으로 생성하기 위함이다. 여기서 중요한 점은 용량 공급자와 로드 밸런서가 항상 필수적인 구성 요소는 아니라는 것이다.
용량 공급자 (Capacity Provider)
용량 공급자의 역할은 AWS ECS에서 용량 공급자는 클러스터의 태스크를 실행할 수 있는 컴퓨팅 자원을 제공한다. 이는 ECS 클러스터에 연결된 EC2 인스턴스 또는 Fargate와 같은 관리형 서비스가 될 수 있다.
AWS ECS에서 용량 공급자는 권장되지만, 항상 필수적인 것은 아니다. 기본적으로 ECS 서비스는 EC2 또는 Fargate 기반의 용량 공급자를 사용할 수 있으며, 특정한 용량 공급자 전략을 지정하지 않으면 ECS가 기본 전략을 사용한다.
로드 밸런서 (Load Balancer)
로드 밸런서의 역할은 ECS에서 로드 밸런서는 인터넷 트래픽을 ECS 서비스의 여러 태스크 인스턴스에 분산시키는 역할을 한다.
Terraform에서 dynamic 블록을 사용하여 용량 공급자(capacity provider)와 로드 밸런서(load balancer)를 조건부로 구성하는 방식은 ECS 서비스의 다양한 요구 사항에 맞추기 위함이었다. 우리가 서비스 생성 모듈에서 이러한 구성 요소를 dynamic 블록으로 정의한 이유는 모든 ECS 서비스가 용량 공급자나 로드 밸런서가 반드시 필수 항목이 아니기 때문이다. 예를 들어, 내부적으로만 사용되는 서비스나 특정 상황에서 로드 밸런서가 불필요할 수 있다.
이 접근 방식은 Terraform 코드를 더 유연하게 만들고, 용량 공급자나 로드 밸런서와 관련된 변수가 명시되지 않았을 때 에러를 방지한다. 다시 말해 서비스를 생성할 때 용량 공급자나 로드 밸런서가 필요하지 않은 경우(이러한 변수들이 없는 경우)에 해당 구성 요소들이 동적으로 생성되지 않도록 하기 위함이다.
결과적으로, dynamic 블록의 활용은 코드의 복잡성을 줄이고, ECS 서비스를 다양한 상황에 맞게 조정할 수 있게 해 주며, 서비스의 구성을 보다 쉽게 관리할 수 있도록 도와준다.
그럼 이제 dynamic 블록의 작동 방식에 대해서 알아보자
dynamic 블록 내부에서의 코드 작동 방식은 다음과 같다:
for_each 사용
dynamic 블록은 for_each 속성을 사용하여 반복할 요소의 집합을 결정한다. 예를 들어, for_each = var.capacity_provider_name != null ? [1] : []는 var.capacity_provider_name 변수가 null이 아닐 경우 [1] (하나의 요소를 가진 리스트)를 사용하고, null일 경우 빈 리스트 []를 사용한다.
content 블록
for_each로 결정된 각 요소에 대해 content 블록이 실행된다. 이 블록은 실제로 리소스의 속성을 정의하는 데 사용된다. 예를 들어, capacity_provider에는 var.capacity_provider_name의 값이, weight에는 100이 할당된다.
조건부 리소스 생성
for_each가 빈 리스트를 반환하는 경우, dynamic 블록 내의 content는 실행되지 않는다. 이렇게 하면, 특정 조건(예: 특정 변수가 null인 경우)에 따라 리소스의 일부 구성 요소를 생략할 수 있다.
ECS 서비스의 이름을 출력한다. aws_ecs_service.ecs_service.name을 사용하여 해당 값을 반환한다.
output "ecs_service_name" {
value = aws_ecs_service.ecs_service.name
}
각 변수는 ECS 서비스 설정에 필요한 값들을 정의한다. 예를 들어, ecs_cluster_name은 ECS 클러스터의 이름을 지정한다. default = null 설정은 해당 변수가 필수적이지 않음을 나타내며, 모듈을 사용하는 사용자가 이 값을 제공하지 않으면 기본적으로 null로 설정된다.
이 파일에서 default로 지정된 값들은 로드 밸런서나 용량 공급자와 같은 선택적인 서비스 구성 요소들에 대한 변수들을 포함한다. 이러한 변수들에 default = null을 설정함으로써, 사용자가 로드 밸런서나 용량 공급자를 사용하지 않고 ECS 서비스를 생성할 때 해당 변수들을 명시적으로 입력하지 않아도 에러가 발생하지 않도록 한다.
variable "ecs_cluster_name" {
description = "클러스터 이름 (ID 포함)"
default = null
}
variable "ecs_service_name" {
description = "ECS 서비스의 이름"
default = null
}
variable "task_definition" {
description = "JSON 형식의 태스크 정의"
}
variable "capacity_provider_name" {
description = "용량 공급자의 이름"
default = null
}
variable "target_group_arn" {
description = "로드 밸런서를 위한 대상 그룹의 ARN"
default = null
}
variable "container_name" {
description = "태스크 정의 내의 컨테이너 이름"
default = null
}
variable "container_port" {
description = "컨테이너가 수신 대기하는 포트"
default = null
}
variable "asg_arn" {
description = "의존성 관리를 위한 Auto Scaling 그룹 ARN"
type = string
default = null
}
이제 지금까지 만든 모듈을 사용하는 코드를 작성해 보자
5. 루트 디렉토리의 main.tf
이 코드는 위에서 설명한 태스크 정의, 서비스를 생성하는 main.tf 예제 코드다.
provider "aws" {
region = "us-west-2" // 예시 AWS 리전 설정
}
module "network_module" {
source = "./network" // 네트워크 모듈 경로
vpc_id = "vpc-xxxxxx" // 예시 VPC ID
subnet_ids = ["subnet-xxxxx", "subnet-yyyyy"] // 예시 서브넷 ID들
}
output "vpc_id_used" {
value = module.network_module.vpc_id
}
output "subnet_ids_used" {
value = module.network_module.subnet_ids
}
# EC2 런치 템플릿 설정 - 테스트용
module "test_launch_template" {
source = "./ec2_launch_template"
name_prefix = "test-ec2"
image_id = "ami-xxxxxxxxxxxxxxxxx" // 예시 AMI ID
instance_type = "t2.micro"
key_name = "test-keypair"
vpc_security_group_ids = ["sg-xxxxxxxxxxxxxxx"] // 예시 보안 그룹 ID
iam_instance_profile = "testInstanceRole"
ec2_instance_name = "ec2_test"
user_data_file = "test_user_data.sh"
}
# 테스트용 오토 스케일링 그룹 생성
module "test_asg" {
source = "./autoscaling_group"
vpc_subnets = ["subnet-aaaaaa", "subnet-bbbbbb", "subnet-cccccc"] // 예시 서브넷 ID들
launch_template_id = module.test_launch_template.launch_template_id
name_prefix = "test"
}
# ECS 용량 공급자 모듈 생성
module "ecs_capacity_provider" {
source = "./ecs_capacity_provider"
name = "example-ecs-capacity-provider"
auto_scaling_group_arn = "arn:aws:autoscaling:us-west-2:123456789012:autoScalingGroup:xxxx-xxxx-xxxx:autoScalingGroupName/xxxx-xxxx"
maximum_scaling_step_size = 1
minimum_scaling_step_size = 1
status = "ENABLED"
target_capacity = 100
cluster_name = "example-ecs-cluster"
base = 1
weight = 100
}
# 테스트 ECS 클러스터 생성
module "ecs_member_cluster" {
source = "./ecs_cluster"
cluster_name = "example-ecs-cluster"
}
# ALB를 적용하는 ECS 서비스 생성
module "ecs_cluster_service_member" {
source = "./ecs_service"
ecs_service_name = "member-service-prod"
ecs_cluster_name = module.ecs_member_cluster.ecs_cluster_name
asg_arn = "arn:aws:autoscaling:us-west-2:123456789012:autoScalingGroup:yyyy-yyyy-yyyy:autoScalingGroupName/yyyy-yyyy"
task_definition = "arn:aws:ecs:us-west-2:123456789012:task-definition/member-task"
capacity_provider_name = "example-capacity-provider"
target_group_arn = "arn:aws:elasticloadbalancing:us-west-2:123456789012:targetgroup/dummy-tg/xxxxx"
container_name = "dummy-container"
container_port = 8081
}
# ALB를 적용하지 않는 ECS 서비스 생성
module "ecs_cluster_service_zipkin" {
source = "./ecs_service"
ecs_service_name = "zipkin-service-prod"
ecs_cluster_name = "example-zipkin-cluster"
task_definition = "arn:aws:ecs:us-west-2:123456789012:task-definition/zipkin-task"
}
# ECS 태스크 정의: 멤버
module "member_task_definition" {
source = "./task_definition"
family_name = "dummy-member-prod"
execution_role_arn = "arn:aws:iam::123456789012:role/ecsTaskExecutionRole"
cpu = 512
memory = 512
# 컨테이너 정의 JSON 예시
container_definitions = jsonencode([
{
name = "dummy-member-container"
image = "123456789012.dkr.ecr.us-west-2.amazonaws.com/dummy-member"
cpu = 512
memory = 512
essential = true
portMappings = [
{
containerPort = 8081
hostPort = 0
protocol = "tcp"
}
]
secrets = [
// 비밀 데이터 예시
{
name = "SECRET_NAME"
valueFrom = "arn:aws:secretsmanager:us-west-2:123456789012:secret:dummy-secret"
}
// 추가적인 비밀 데이터
]
logConfiguration = {
logDriver = "awslogs"
options = {
"awslogs-group" = "/ecs/dummy-member-prod"
"awslogs-region" = "us-west-2"
"awslogs-stream-prefix" = "ecs"
}
}
}
])
}
이제 거의 마무리까지 왔다. 이제 ALB를 연동하지 않은 서비스에 대해서 매번 생성하고 삭제될 때마다 바뀌는 IP에 접근하기 위해 생성된 EC2에 대해서 route 53 DNS를 연동해 주는 작업이 남았다. 이 부분을 다음 장에서 같이 알아가 보자.
🔻 마지막 루트 디렉토리의 main.tf에서 사용된 다른 모듈이 궁금하다면?? 🔻
테라폼으로 ECS Cluster를 생성할때 사용되는 용량 공급자(capacity provider) 이해하고 적용하기
이 포스트는 Team chillwave에서 사이드 프로젝트 중 적용했던 부분을 다시 공부하며 기록한 것입니다.
시간이 괜찮다면 팀원 '개발자의 서랍'님의 블로그도 한번 봐주세요 :)
'테라폼(Terraform)' 카테고리의 다른 글
엉금엉금 테라폼 적용하기 (7) - 탄력적 IP와 DNS 연동해서 EC2 만들기 (0) | 2023.12.14 |
---|---|
엉금엉금 테라폼 적용하기 (5) - 테라폼으로 ECS Cluster를 생성할때 사용되는 용량 공급자(capacity provider) 이해하고 적용하기 (1) | 2023.12.11 |
엉금엉금 테라폼 적용하기 (4) - Terraform에서 Launch Template, 오토 스케일링 설정하기 (0) | 2023.12.08 |
엉금엉금 테라폼 적용하기 (3) - Terraform AWS Provider 및 VPC, Subnet 설정 (0) | 2023.12.08 |
엉금엉금 테라폼 적용하기 (2) - MacOS에서 테라폼 설치 및 terraform 명령어 사용법 (0) | 2023.12.08 |