새소식

기술/AWS

Terraform을 이용한 VPC 및 Subnet, Routing 생성하기

  • -

Terraform

안녕하세요. 오랜만의 포스팅이네요.

 

오늘은 terraform을 이용하여 AWS에서 Network Zone을 생성해보도록 하겠습니다.

 

준비물 : AWS 계정, IAM로 사용자 생성 및 리소스 권한 부여, Access Key&Secret Access Key, Terraform 설치

 

먼저 Mac 사용자 경우 터미널에서 다음 명령어를 입력하여 terraform을 설치합니다.

brew install terraform

윈도 10 사용자의 경우 아래 홈페이지에서 다운로드 받아주세요.

https://www.terraform.io/downloads.html

다운로드한 terraform.exe을 이용하여 따라 하시면 됩니다.

 

자 테스트 데이터를 저장할 테스트 폴더 및 provider.tf, network.tf 파일을 만들어 주세요.

주의 : provider.tf의 경우 aws의 Access Key 및 Secret Access Key, Region 정보가 들어가며, terraform 실행 시 해당 파일을 참조하기 때문에 이름을 변경하면 안 됩니다.

provider "aws" {
  access_key = "자신의 Key를 입력"
  secret_key = "자신의 Key를 입력"
  region     = "ap-northeast-2"
}

provider "aws"는 AWS를 사용하겠다는 선언입니다.

 

provider를 만들어 준 후 terraform init를 입력합니다.

teichae@DESKTOP-HRC3FHE:~/aws$ terraform init

Initializing the backend...

Initializing provider plugins...
- Checking for available provider plugins...
- Downloading plugin for provider "aws" (hashicorp/aws) 2.27.0...

The following providers do not have any version constraints in configuration,
so the latest version was installed.

To prevent automatic upgrades to new major versions that may contain breaking
changes, it is recommended to add version = "..." constraints to the
corresponding provider blocks in configuration, with the constraint strings
suggested below.

* provider.aws: version = "~> 2.27"

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.

teichae@DESKTOP-HRC3FHE:~/aws$ terraform version
Terraform v0.12.8
+ provider.aws v2.27.0

네, 이제 terraform을 시작할 준비를 마쳤습니다.

 

본 가이드는 Terraform v0.12.8 버전을 기준으로 하며, 버전이 변경되면서 문법이 계속 변경되고 있는 점 참고 부탁드립니다.

 

network.tf를 열어서 다음을 입력합니다.

resource "aws_vpc" "vpc" {
    cidr_block = "10.0.0.0/16" #IPv4 CIDR Block
    enable_dns_hostnames = true #DNS Hostname 사용 옵션, 기본은 false
    tags =  { Name = "vpc"} #tag 입력
}

terraform 두 가지 사용 방식이 있습니다.

 

data는 기존에 생성된 리소스 정보를 불러오는 방식

resource는 리소스를 새로 생성하고 정보를 불러오는 방식

 

resource "aws_vpc" "vpc"는 새로운 aws_vpc를 만든다는 의미이고, 그 뒤의 "vpc"는 리소스의 별칭이며, 앞으로 계속 활용하게 됩니다. 잘 지켜봐 주세요.

 

이제 파일을 저장한 후 커맨드 창에 terraform plan을 입력하여 봅니다.

teichae@DESKTOP-HRC3FHE:~/aws$ terraform plan
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.


------------------------------------------------------------------------

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_vpc.vpc will be created
  + resource "aws_vpc" "vpc" {
      + arn                              = (known after apply)
      + assign_generated_ipv6_cidr_block = false
      + cidr_block                       = "10.0.0.0/16"
      + default_network_acl_id           = (known after apply)
      + default_route_table_id           = (known after apply)
      + default_security_group_id        = (known after apply)
      + dhcp_options_id                  = (known after apply)
      + enable_classiclink               = (known after apply)
      + enable_classiclink_dns_support   = (known after apply)
      + enable_dns_hostnames             = true
      + enable_dns_support               = true
      + id                               = (known after apply)
      + instance_tenancy                 = "default"
      + ipv6_association_id              = (known after apply)
      + ipv6_cidr_block                  = (known after apply)
      + main_route_table_id              = (known after apply)
      + owner_id                         = (known after apply)
      + tags                             = {
          + "Name" = "vpc"
        }
    }

Plan: 1 to add, 0 to change, 0 to destroy.

------------------------------------------------------------------------

Note: You didn't specify an "-out" parameter to save this plan, so Terraform
can't guarantee that exactly these actions will be performed if
"terraform apply" is subsequently run.

네, 틀린 점 없이 정상적으로 잘 입력했다면 위와 같은 화면이 나올 것이며, 틀린 점이 있다면 오류를 보여줄 것입니다.

잘못된 예

teichae@DESKTOP-HRC3FHE:~/aws$ terraform plan

Error: Invalid reference

  on network.tf line 3, in resource "aws_vpc" "vpc":
   3:     enable_dns_hostnames = tru

A reference to a resource type must be followed by at least one attribute
access, specifying the resource name.

VPC만으로는 외부와 통신이 되지 않기 때문에 외부와 통신을 할 수 있게 해 줄 Internet Gateway와 Nat Gateway를 생성해보겠습니다.

# 인터넷 게이트웨이 생성
resource "aws_internet_gateway" "terra-igw" {
    vpc_id = aws_vpc.vpc.id #어느 VPC와 연결할 것인지 지정
    tags = { Name = "terra-IGW"}  #태그 설정
}

# NAT 게이트웨이가 사용할 Elastic IP생성
resource "aws_eip" "nat" {
  vpc      = true  #생성 범위 지정
}

# NAT 게이트웨이 생성
resource "aws_nat_gateway" "nat" {
  allocation_id = aws_eip.nat.id #EIP 연결
  subnet_id     = aws_subnet.terra-sub-public1.id #NAT가 사용될 서브넷 지정

  tags = {
    Name = "terra-NAT"
  }
}

이제 VPC를 생성했고, Internet Gateway와 NAT Gateway 또한 생성했습니다.

 

서브넷 생성을 위해 다음을 입력합니다.

# Subnet Public
resource "aws_subnet" "terra-sub-public1" {
    vpc_id = aws_vpc.vpc.id #위에서 생성한 vpc 별칭 입력
    cidr_block = "10.0.10.0/24" #IPv4 CIDER 블럭
    availability_zone = "ap-northeast-2a" #가용영역 지정
    map_public_ip_on_launch = true #퍼블릭 IP 자동 부여 설정
    tags = { Name = "terra-sub-public1"} #태그 설정

}

resource "aws_subnet" "terra-sub-public2" {
    vpc_id = aws_vpc.vpc.id
    cidr_block  = "10.0.20.0/24"
    availability_zone = "ap-northeast-2c"
    map_public_ip_on_launch = true
    tags = { Name = "terra-sub-public2"}
}

# Subnet Private
resource "aws_subnet" "terra-sub-private1" {
    vpc_id = aws_vpc.vpc.id #위에서 생성한 vpc 별칭 입력
    cidr_block = "10.0.11.0/24" #IPv4 CIDER 블럭
    availability_zone = "ap-northeast-2a" #가용영역 지정
    map_public_ip_on_launch = false #퍼블릭 IP 부여를 하지 않습니다.
    tags = { Name = "terra-sub-private1"} #태그 설정
}

resource "aws_subnet" "terra-sub-private2" {
    vpc_id = aws_vpc.vpc.id
    cidr_block  = "10.0.21.0/24"
    availability_zone = "ap-northeast-2c"
    map_public_ip_on_launch = false #퍼블릭 IP 부여를 하지 않습니다.
    tags = { Name = "terra-sub-private2"}
}

웹 서버가 사용할 Public 서브넷과 데이터베이스 서버가 사용할 Private 서브넷을 생성했습니다.

 

서브넷을 생성했지만, 라우팅 경로가 입력되지 않았기 때문에 정상적으로 외부와 통신이 되지 않습니다.

 

그렇기 때문에 라우팅 테이블을 만든 후 서브넷을 부여해 주어야 합니다.

 

이제 라우팅 테이블을 만들어 보겠습니다.

 

# public routing
resource "aws_route_table" "terra-public1" {
  vpc_id = aws_vpc.vpc.id #VPC 별칭 입력
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = "aws_internet_gateway.terra-igw.id" #Internet Gateway 별칭 입력
  }
  tags = { Name = "terra-public1" } #태그 설정
}

resource "aws_route_table" "terra-public2" {
  vpc_id = aws_vpc.vpc.id #VPC 별칭 입력
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = "aws_internet_gateway.terra-igw.id" #Internet Gateway 별칭 입력
  }
  tags = { Name = "terra-public2" } #태그 설정
}



# private routing
resource "aws_route_table" "terra-private1" {
  vpc_id = aws_vpc.vpc.id #VPC 별칭 입력
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = "aws_nat_gateway.nat.id" #NAT Gateway 별칭 입력
  }
  tags = { Name = "terra-private1" } #태그 설정
}

resource "aws_route_table" "terra-private2" {
  vpc_id = aws_vpc.vpc.id #VPC 별칭 입력
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = "aws_nat_gateway.nat.id" #NAT Gateway 별칭 입력
  }
  tags = { Name = "terra-private2" } #태그 설정
}

 

이제 라우팅 테이블도 완성했으니, 서브넷에도 연결해줍니다.

resource "aws_route_table_association" "terra-routing-public1" {
  subnet_id      = aws_subnet.terra-sub-public1.id
  route_table_id = aws_route_table.terra-public1.id
}

resource "aws_route_table_association" "terra-routing-public2" {
  subnet_id      = aws_subnet.terra-sub-public2.id
  route_table_id = aws_route_table.terra-public2.id
}

resource "aws_route_table_association" "terra-routing-private1" {
  subnet_id      = aws_subnet.terra-sub-private1.id
  route_table_id = aws_route_table.terra-private1.id
}

resource "aws_route_table_association" "terra-routing-private2" {
  subnet_id      = aws_subnet.terra-sub-private2.id
  route_table_id = aws_route_table.terra-private2.id
}

자 이제, 길고 길었던 Network Zone이 완성되었습니다.

 

이제 terraform plan을 입력하여, 오탈자나 잘못된 구문이 없는지 내가 원했던 인프라 구성이 잘 나오는지 확인합니다.

 

오탈자 및 잘못 지정된 내용이 없다면 terraform apply를 하여 실제 프로비저닝을 실행합니다.

 

tei.chae@Tei-MacBookPro test % terraform apply

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_eip.nat will be created
  + resource "aws_eip" "nat" {
      + allocation_id     = (known after apply)
      + association_id    = (known after apply)
      + domain            = (known after apply)
      + id                = (known after apply)
      + instance          = (known after apply)
      + network_interface = (known after apply)
      + private_dns       = (known after apply)
      + private_ip        = (known after apply)
      + public_dns        = (known after apply)
      + public_ip         = (known after apply)
      + public_ipv4_pool  = (known after apply)
      + vpc               = true
    }

  # aws_internet_gateway.terra-igw will be created
  + resource "aws_internet_gateway" "terra-igw" {
      + id       = (known after apply)
      + owner_id = (known after apply)
      + tags     = {
          + "Name" = "terra-igw"
        }
      + vpc_id   = (known after apply)
    }

  # aws_nat_gateway.nat will be created
  + resource "aws_nat_gateway" "nat" {
      + allocation_id        = (known after apply)
      + id                   = (known after apply)
      + network_interface_id = (known after apply)
      + private_ip           = (known after apply)
      + public_ip            = (known after apply)
      + subnet_id            = (known after apply)
      + tags                 = {
          + "Name" = "terra-NAT"
        }
    }

  # aws_route_table.terra-private1 will be created
  + resource "aws_route_table" "terra-private1" {
      + id               = (known after apply)
      + owner_id         = (known after apply)
      + propagating_vgws = (known after apply)
      + route            = [
          + {
              + cidr_block                = "0.0.0.0/0"
              + egress_only_gateway_id    = ""
              + gateway_id                = (known after apply)
              + instance_id               = ""
              + ipv6_cidr_block           = ""
              + nat_gateway_id            = ""
              + network_interface_id      = ""
              + transit_gateway_id        = ""
              + vpc_peering_connection_id = ""
            },
        ]
      + tags             = {
          + "Name" = "terra-private1"
        }
      + vpc_id           = (known after apply)
    }

  # aws_route_table.terra-private2 will be created
  + resource "aws_route_table" "terra-private2" {
      + id               = (known after apply)
      + owner_id         = (known after apply)
      + propagating_vgws = (known after apply)
      + route            = [
          + {
              + cidr_block                = "0.0.0.0/0"
              + egress_only_gateway_id    = ""
              + gateway_id                = (known after apply)
              + instance_id               = ""
              + ipv6_cidr_block           = ""
              + nat_gateway_id            = ""
              + network_interface_id      = ""
              + transit_gateway_id        = ""
              + vpc_peering_connection_id = ""
            },
        ]
      + tags             = {
          + "Name" = "terra-private2"
        }
      + vpc_id           = (known after apply)
    }

  # aws_route_table.terra-public1 will be created
  + resource "aws_route_table" "terra-public1" {
      + id               = (known after apply)
      + owner_id         = (known after apply)
      + propagating_vgws = (known after apply)
      + route            = [
          + {
              + cidr_block                = "0.0.0.0/0"
              + egress_only_gateway_id    = ""
              + gateway_id                = (known after apply)
              + instance_id               = ""
              + ipv6_cidr_block           = ""
              + nat_gateway_id            = ""
              + network_interface_id      = ""
              + transit_gateway_id        = ""
              + vpc_peering_connection_id = ""
            },
        ]
      + tags             = {
          + "Name" = "terra-public1"
        }
      + vpc_id           = (known after apply)
    }

  # aws_route_table.terra-public2 will be created
  + resource "aws_route_table" "terra-public2" {
      + id               = (known after apply)
      + owner_id         = (known after apply)
      + propagating_vgws = (known after apply)
      + route            = [
          + {
              + cidr_block                = "0.0.0.0/0"
              + egress_only_gateway_id    = ""
              + gateway_id                = (known after apply)
              + instance_id               = ""
              + ipv6_cidr_block           = ""
              + nat_gateway_id            = ""
              + network_interface_id      = ""
              + transit_gateway_id        = ""
              + vpc_peering_connection_id = ""
            },
        ]
      + tags             = {
          + "Name" = "terra-public2"
        }
      + vpc_id           = (known after apply)
    }

  # aws_route_table_association.terra-routing-private1 will be created
  + resource "aws_route_table_association" "terra-routing-private1" {
      + id             = (known after apply)
      + route_table_id = (known after apply)
      + subnet_id      = (known after apply)
    }

  # aws_route_table_association.terra-routing-private2 will be created
  + resource "aws_route_table_association" "terra-routing-private2" {
      + id             = (known after apply)
      + route_table_id = (known after apply)
      + subnet_id      = (known after apply)
    }

  # aws_route_table_association.terra-routing-public1 will be created
  + resource "aws_route_table_association" "terra-routing-public1" {
      + id             = (known after apply)
      + route_table_id = (known after apply)
      + subnet_id      = (known after apply)
    }

  # aws_route_table_association.terra-routing-public2 will be created
  + resource "aws_route_table_association" "terra-routing-public2" {
      + id             = (known after apply)
      + route_table_id = (known after apply)
      + subnet_id      = (known after apply)
    }

  # aws_subnet.terra-sub-private1 will be created
  + resource "aws_subnet" "terra-sub-private1" {
      + arn                             = (known after apply)
      + assign_ipv6_address_on_creation = false
      + availability_zone               = "ap-northeast-2a"
      + availability_zone_id            = (known after apply)
      + cidr_block                      = "10.0.11.0/24"
      + id                              = (known after apply)
      + ipv6_cidr_block                 = (known after apply)
      + ipv6_cidr_block_association_id  = (known after apply)
      + map_public_ip_on_launch         = false
      + owner_id                        = (known after apply)
      + tags                            = {
          + "Name" = "terra-sub-private1"
        }
      + vpc_id                          = (known after apply)
    }

  # aws_subnet.terra-sub-private2 will be created
  + resource "aws_subnet" "terra-sub-private2" {
      + arn                             = (known after apply)
      + assign_ipv6_address_on_creation = false
      + availability_zone               = "ap-northeast-2c"
      + availability_zone_id            = (known after apply)
      + cidr_block                      = "10.0.21.0/24"
      + id                              = (known after apply)
      + ipv6_cidr_block                 = (known after apply)
      + ipv6_cidr_block_association_id  = (known after apply)
      + map_public_ip_on_launch         = false
      + owner_id                        = (known after apply)
      + tags                            = {
          + "Name" = "terra-sub-private2"
        }
      + vpc_id                          = (known after apply)
    }

  # aws_subnet.terra-sub-public1 will be created
  + resource "aws_subnet" "terra-sub-public1" {
      + arn                             = (known after apply)
      + assign_ipv6_address_on_creation = false
      + availability_zone               = "ap-northeast-2a"
      + availability_zone_id            = (known after apply)
      + cidr_block                      = "10.0.10.0/24"
      + id                              = (known after apply)
      + ipv6_cidr_block                 = (known after apply)
      + ipv6_cidr_block_association_id  = (known after apply)
      + map_public_ip_on_launch         = true
      + owner_id                        = (known after apply)
      + tags                            = {
          + "Name" = "terra-sub-public1"
        }
      + vpc_id                          = (known after apply)
    }

  # aws_subnet.terra-sub-public2 will be created
  + resource "aws_subnet" "terra-sub-public2" {
      + arn                             = (known after apply)
      + assign_ipv6_address_on_creation = false
      + availability_zone               = "ap-northeast-2c"
      + availability_zone_id            = (known after apply)
      + cidr_block                      = "10.0.20.0/24"
      + id                              = (known after apply)
      + ipv6_cidr_block                 = (known after apply)
      + ipv6_cidr_block_association_id  = (known after apply)
      + map_public_ip_on_launch         = true
      + owner_id                        = (known after apply)
      + tags                            = {
          + "Name" = "terra-sub-public2"
        }
      + vpc_id                          = (known after apply)
    }

  # aws_vpc.terra-vpc will be created
  + resource "aws_vpc" "terra-vpc" {
      + arn                              = (known after apply)
      + assign_generated_ipv6_cidr_block = false
      + cidr_block                       = "10.0.0.0/16"
      + default_network_acl_id           = (known after apply)
      + default_route_table_id           = (known after apply)
      + default_security_group_id        = (known after apply)
      + dhcp_options_id                  = (known after apply)
      + enable_classiclink               = (known after apply)
      + enable_classiclink_dns_support   = (known after apply)
      + enable_dns_hostnames             = true
      + enable_dns_support               = true
      + id                               = (known after apply)
      + instance_tenancy                 = "default"
      + ipv6_association_id              = (known after apply)
      + ipv6_cidr_block                  = (known after apply)
      + main_route_table_id              = (known after apply)
      + owner_id                         = (known after apply)
      + tags                             = {
          + "Name" = "terra-vpc"
        }
    }

Plan: 16 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes or no

 

저는 yes를 해서 실제로 프로비저닝을 바로 적용하였습니다.

 

 

사실 terraform은 문법에 어려움이 있어서, 접근하기가 그리 쉬운 툴은 아닙니다.

 

다만 이 툴을 사용해야 하는 이유는 서비스가 커질수록 이전에 생성한 리소스에 대한 히스토리나, 관리, 복제, 삭제 등을 계획적으로 진행하기 위해서 사용해야 한다고 생각합니다.

 

또한 코드 형식으로 작성되었기 때문에 코드 리뷰를 하며, 왜 이런 계획을 세웠는지에 대해 공유할 수 있고,

 

terraform을 사용하기 위해선 AWS에 대한 더 많은 이해력이 필요하기 때문에, AWS를 이용하는 분이라면 꼭 추천하는 툴입니다.

 

다음엔 EC2와 보안 그룹, ALB를 추가하는 내용으로 포스팅해보도록 하겠습니다.

 

감사합니다. :)

 

Full Code는 아래에서 참고해주세요. :)

https://github.com/teichae/terraform/blob/network/network.tf

Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.