cloudwithbass

[Terraform] 테라폼 연습하기 4: ec2 instance 본문

Terraform

[Terraform] 테라폼 연습하기 4: ec2 instance

여영클 2024. 8. 29. 16:05

현재까지 서브넷, NAT, IGW, Route Table을 테라폼으로 구성했습니다.

이번 포스팅에선 ec2 instance를 구성하고, 서브넷을 다시 모듈화할 생각입니다.

소스 코드는 아래 깃허브에서 확인할 수 있습니다.

https://github.com/Dminus251/practice-terraform/tree/main/demo04-ec2

목차


    1. 서브넷 모듈화하기

    지난 포스팅까진 모듈 내에서 count를 사용해 반복적으로 서브넷을 생성했었지만, 코드를 일관적으로 만들기 위해 루트 모듈에서 반복문을 사용하도록 수정했습니다. 그에 따라 루트 모듈과 서브넷 모듈의 코드가 조금 바뀌었습니다.


     

    moduels/t-aws-public_subnet/main.tf

    resource "aws_subnet" "public_subnets"{
      vpc_id = var.vpc-id
      cidr_block = var.public_subnet-cidr
      availability_zone = var.public_subnet-az
      map_public_ip_on_launch = true
    
      tags = {
        Name = var.public_subnet-name
      }
    }

    public subnet에는 항상 퍼블릭 ip를 할당해야 하므로 map_public_ip_on_launch를 ture로 하드코딩했습니다.


    modules/t-aws-private_subnet/main.tf

    resource "aws_subnet" "private_subnets"{
      vpc_id = var.vpc-id
      cidr_block = var.private_subnet-cidr
      availability_zone = var.private_subnet-az
      tags = {
        Name = var.private_subnet-name
      }
    }

     

    main.tf

    루트 모듈의 main.tf 파일 중 서브넷 관련 모듈입니다.

    서브넷은 확장될 수 있어서 인덱스를 이용했고, 가용 영역은 둘 중 하나만 사용할 것이므로 삼항 연산자를 이용해 지정했습니다.

    module "public_subnet" { #Public Subnet
      source             = "./modules/t-aws-public_subnet"
      count              = length(var.public_subnet-cidr)
      vpc-id             = module.vpc.vpc-id
      public_subnet-cidr = var.public_subnet-cidr[count.index]
      public_subnet-az   = count.index % 2 == 0 ? var.public_subnet-az[0] : var.public_subnet-az[1]
      public_subnet-name = var.public_subnet-name[count.index]
    }
    
    module "private_subnet" { #Private Subnet
      source              = "./modules/t-aws-private_subnet"
      count               = length(var.private_subnet-cidr)
      vpc-id              = module.vpc.vpc-id
      private_subnet-cidr = var.private_subnet-cidr[count.index]
      private_subnet-az   = count.index % 2 == 0 ? var.private_subnet-az[0] : var.private_subnet-az[1]
      private_subnet-name = var.private_subnet-name[count.index]
    }

     

    수정 전과 후를 비교하면 다음과 같습니다.

    수정 전: 모듈 내에서 반복문을 사용해 서브넷 리소스를 생성합니다.

    수정 후: 루트 모듈에서 반복문을 사용해 서브넷 리소스를 생성합니다.


    vars.tf

    루트 모듈의 variable들입니다.

    variable "public_subnet-cidr" {
      type = list(string)
      default = ["10.0.1.0/24", "10.0.3.0/24"]
    }
    
    variable "public_subnet-az" {
      type = list(string)
      default = ["ap-northeast-2a", "ap-northeast-2c"]
    }
    
    variable "public_subnet-name"{
      type = list(string)
      default = ["public-2a", "public-2c"]
    }
    
    variable "private_subnet-cidr" {
      type = list(string)
      default = ["10.0.0.0/24", "10.0.2.0/24"]
    }
    
    variable "private_subnet-az" {
      type = list(string)
      default = ["ap-northeast-2a", "ap-northeast-2c"]
    }
    
    variable "private_subnet-name" {
      type = list(string)
      default = ["private-2a", "private-2c"]
    }

    2. ec2 instance 구성하기

    테라폼 문서를 참고해 코드를 구성합니다.

    저는 제게 필요한 ami, instance_type, subnet_id, availability_zone, key_name attribute를 사용했습니다.


    module/t-aws-ec2/main.tf

    resource "aws_instance" "ec2" {
      ami           = var.ami-ubuntu-id
      instance_type = "t2.micro"
      subnet_id     = var.ec2-subnet
      availability_zone = var.ec2-az
      key_name = var.ec2-key_name
      tags = {
        Name = "ec2-${var.ec2-usage}"
      }
    }

    module/t-aws-ec2/vars.tf

    ami는 제게 익숙한 ubuntu 22.04를 사용했습니다.

    variable "ami-ubuntu-id" {
      type = string
      default = "ami-05d2438ca66594916" #ubuntu 22.04
    }
    
    variable "ec2-subnet" {
      type = string
    }
    
    variable "ec2-az" {
      type = string
    }
    
    variable "ec2-key_name" {
      type = string
    }
    
    variable "ec2-usage" {
      type = string
    }

    3. 루트 모듈 구성하기

    이제 루트 모듈에서 ec2 instance를 구성합니다.


    main.tf

    #key_name이 0827인 key_pair 찾아옴
    data "aws_key_pair" "example" {
      key_name           = "0827"
      include_public_key = true
    }

    이 데이터 블록은 key_name이 0827인 키 페어를 aws에서 가져옵니다.

     

    module "ec2_public" {
      source = "./modules/t-aws-ec2"
      for_each = { for i, subnet in module.public_subnet : i => subnet["public_subnet-id"] } # public subnet을 우선으로 반복
      ec2-subnet   = each.value
      ec2-az       = each.key % 2 == 0 ? "ap-northeast-2a" : "ap-northeast-2c"
      ec2-key_name = data.aws_key_pair.example.key_name
      ec2-usage    = "public-${each.key}"
    }

    public subnet의 ec2 instance를 생성하는 모듈입니다.

     

    line3 for_each = { for i, subnet in module.public_subnet : i => subnet["public_subnet-id"] }

    이 코드를 다음과 같이 두 부분으로나누어서 보겠습니다.

    • for_each = { for i, subnet in module.public_subnet :
    • i => subnet["public_subnet-id"] }

     

    1. for_each = { for i, subnet in module.public_subnet :

    for문의 module.public_subnet에서 i는 인덱스(0부터 시작), subnet은 module.public_subnet의 각 요소입니다.

     

    module.public_subnet은 어떻게 생겼을까요? output을 통해 확인해 보겠습니다.

    #modules/t-aws-public_subnet/output.tf
    output "public_subnet-id" {
       value = aws_subnet.public_subnets.id
    }
    
    #루트 모듈의 output.tf
    output "def" {
      value = module.public_subnet
    }
    
    #result
    def = [
      {
        "public_subnet-id" = "subnet-036debac3fc1714be"
      },
      {
        "public_subnet-id" = "subnet-0d05c132dc8e4b7bb"
      },
    ]

     

    이처럼 module.public_subnet은 map으로 감싸진 list 형식입니다.

    public_subnet을 루트 모듈에서 count로 생성했으므로 public_subnet 모듈의 output들이 map으로 감싸져서 출력됩니다.

     

    만약 public_subnet 모듈에서 output이 여러개면 어떻게 출력될까요?

    public_subnet-test라는 output을 추가해보니, 그 결과는 아래와 같습니다.

    output "public_subnet-test"{
      value = "test"
    }
    
    output "public_subnet-id" {
       value = aws_subnet.public_subnets.id
    }
    
    #result
    def = [
      {
        "public_subnet-id" = "subnet-054e78686a8eb814c"
        "public_subnet-test" = "test"
      },
      {
        "public_subnet-id" = "subnet-0be5de630b7b0f628"
        "public_subnet-test" = "test"
      },
    ]

    이제 원래 코드였던 

     

    for_each = { for i, subnet in module.public_subnet : 를 다시 보겠습니다.

    첫 반복에서

    i = 0

    subnet =  {
        "public_subnet-id" = "subnet-054e78686a8eb814c"
        "public_subnet-test" = "test" }

     

    두 번째 반복에서

    i = 1

    subnet = {
        "public_subnet-id" = "subnet-0be5de630b7b0f628"
        "public_subnet-test" = "test" }

    이 됩니다.

     

    이제 원래 코드인 for_each = { for i, subnet in module.public_subnet : i => subnet["public_subnet-id"] }에서

    다음 부분인 i => subnet["public_subnet-id"] } 부분을 설명하겠습니다.

     

    2. i => subnet["public_subnet-id"] }

    이 부분은 새로운 map을 생성합니다.

     

    i를 key로, subnet["public_subnet-id"]를 value로 사용합니다.

    그러면 위 예제에선 이런 새로운 map이 생성될 것입니다.

      {
        0 = "subnet-054e78686a8eb814c"
        1 = "subnet-0be5de630b7b0f628"
      }

     

    이제 이러한 새로운 map에 대해 for_each문이 실행될 것입니다.

    line4부터 한 번에 보겠습니다.

    #line4 ec2-subnet   = each.value
           ec2-az       = each.key % 2 == 0 ? "ap-northeast-2a" : "ap-northeast-2c"
           ec2-key_name = data.aws_key_pair.example.key_name
           ec2-usage    = "public-${each.key}"

    line4 value인 subnet id를 ec2-subnet 변수에 할당합니다.

    line5 key인 인덱스가 짝수면 ap-northeast-2a, 홀수면 ap-northeast-2c 가용 영역을 사용합니다.

    line6 데이터 블록에서 가져온 키 네임을 사용합니다.

    line7 key인 인덱스를 이름 끝에 붙여서 인스턴스를 구분합니다.

     

    private subnet의 ec2 instance도 같은 방식입니다.

    module "ec2_private" {
      source = "./modules/t-aws-ec2"
      for_each = { for i, subnet in module.private_subnet : i => subnet["private_subnet-id"]}
      ec2-subnet   = each.value
      ec2-az       = each.key % 2 == 0 ? "ap-northeast-2a" : "ap-northeast-2c"
      ec2-key_name = data.aws_key_pair.example.key_name
      ec2-usage    = "private-${each.key}"
    }