테라폼으로 AWS 서비스 구축하기 2장. EC2 RDS S3 구축하기 – 1

테라폼으로 EC2 RDS S3의 구성을 구현해봅시다
2021.03.31

시작하며

이전 글에선 테라폼의 설치와 버전 관리 툴인 tfswitch를 설치하고 사용법을 알아보았습니다.
이번에는 EC2 RDS S3를 구축해보며 사용법을 익힙니다.
이 글에서 AWS 가입 및 설정에 대해 부가적인 설명은 하지않습니다.

작업 환경

  • (Ubuntu)WSL2
  • Visual Studio Code
  • Terraform 0.14.9

테라폼의 작동 방식

작동 방식의 자세한 내용은 공식 문서를 참조해주세요
단어 설명은 이곳을 참조해주세요.
테라폼은 terraform core(코어)terraform plugin(플러그인)으로 논리적인 구분이 되어 있습니다.
유저가 CLI를 통하여 코어에 접근하면 코어는 플러그인과 통신하여 사용할 플러그인을 검색 및 로드하게 됩니다.
현재 유저가 조작 가능한 플러그인은 프로바이더뿐입니다.

보통 작업의 흐름은 다음과 같습니다.

  • 프로바이더 작성
  • 초기화
  • 리소스 작성
  • 계획 / 적용

이 중 프로바이더(provider)와 리소스(resource)를 간략하게 설명하면 프로바이더는 사용하게 될 서비스(AWS, GCP, Docker 등)이고 리소스는 AWS로 예를 들자면 EC2, RDS 등과 같이 실제로 구축하게 될 자원(서비스)을 말합니다.

구성도

  • VPC x 1
  • IAM(Role: AmazonSSMManagedInstanceCore) x 1
  • EC2 x 1
  • RDS x 1
  • S3 x 1

시작하기에 앞서

다음 3가지 작업을 완료하여야 합니다.

초기화

프로바이더 설정

우선 프로바이더 파일을 작성합니다.

#provider.tf (구분할 수 있는 이름이면 뭐든 상관없습니다)

terraform {
  required_version = "0.14.9" 

  required_providers {
      aws = {
          source = "hashicorp/aws"
          version = "~> 3.0"
      }
  }
}

provider "aws" {
  region     = "ap-northeats-2"
  # access_key 와 secert_key 를 파일에 적어서 구현할 수는 있지만 권장하지 않습니다.
  # access_key = "my-access-key"
  # secret_key = "my-secret-key"
}

키는 환경 변수로 지정하여 사용합니다.

$ export AWS_ACCESS_KEY_ID="anaccesskey"
$ export AWS_SECRET_ACCESS_KEY="asecretkey"

이어서 초기화를 해줍니다.

$ terraform init
  Terraform has been successfully initialized!

MFA를 사용하고 있는 경우

MFA를 사용하는 경우에는 terraform cli 만으로는 해결되지 않습니다.

저는 assume-role을 이용하여 역할 전환과 MFA를 해결하고 있습니다.

해결하기 위해서 먼저 프로바이더 파일을 작성해줍니다.

#provider.tf
-- provider 부분을 수정하는 것 이외에는 위의 내용과 동일합니다

provider "aws" {
  region = "ap-northeast-2"
}

이 후 assume-role을 이용해서 세션을 설정합니다.

# test 부분에는 본인이 사용할 프로필 명을 적어주세요
$ assume-role test
  MFA code: ---
  export AWS_ACCESS_KEY_ID=
  ~~~
  # Run this to configure your shell:
  # eval $(assume-role test)
$ eval $(assume-role test)

# 다음 커맨드를 실행하여 요청한 계정이 test 역할이 맞는지 확인합니다.
$ aws sts get-caller-identity

결과가 잘 나온다면 초기화를 한 뒤 다음으로 진행합니다.

VPC

VPC 작성

EC2를 생성하기 전 VPC를 작성해보며 plan 과 apply를 알아보겠습니다.

#vpc.tf
resource “aws_vpc” “blogVPC” {
  cidr_block = “10.0.0.0/16”
}

파일을 작성 후 plan을 해봅시다.

$ terraform plan
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.blogVPC will be created
  + resource “aws_vpc” “blogVPC” {
      + arn                              = (known after apply)
      + assign_generated_ipv6_cidr_block = false
      + cidr_block                       = “10.0.0.0/16”
      --생략--

plan은 실제로 리소스를 작성하기 전 리소스가 어떻게 생성되는지를 확인하는 명령어입니다.

CloudFormation의 create-change-set , execute-change-set 조합과 같습니다.

단순하게 plan 명령어만 입력하면 되기 때문에 CloudFormation을 사용하는 것보다 훨씬 빠르고 편하게 결과를 예측할 수 있습니다.

문제가 없다면 apply를 해보겠습니다.

$ 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_vpc.blogVPC will be created
    + resource “aws_vpc” “blogVPC” {
        + arn                              = (known after apply)
        + assign_generated_ipv6_cidr_block = false
        + cidr_block                       = “10.0.0.0/16”
        --생략--
  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
  aws_vpc.blogVPC: Creating...
  aws_vpc.blogVPC: Creation complete after 1s [id=vpc-0435738e88a43cc6e]

vpc가 성공적으로 생성되었습니다.

AWS 콘솔에서 서울 리전에 VPC가 작성되었는지 확인해봅니다.

이미 작성한 리소스를 변경하고 싶다면 파일에 내용을 작성 후 적용하면 됩니다.

한번 vpc에 Name 태그를 설정해보겠습니다.

#vpc.tf
resource “aws_vpc” “blogVPC” {
  cidr_block = “10.0.0.0/16”

  tags = {
    Name = "blog-vpc"
  }
}

그리고 plan을 실행합니다.

$ terraform plan
  aws_vpc.blogVPC: Refreshing state... [id=vpc-0435738e88a43cc6e]
  -- 생략 --
  Plan: 0 to add, 1 to change, 0 to destroy.

변경된 내용이 나오고 어떻게 적용되는지 확인할 수 있습니다.

적용하기 위해 apply를 실행한 후 AWS 콘솔에서 VCP를 보면 Name 태그가 작성된 것을 확인할 수 있습니다.

서브넷 작성

EC2에 사용할 1개의 퍼블릭 서브넷과 RDS에 사용할 2개의 프라이빗 서브넷을 작성합니다.

파일은 사용자가 나누고 싶은대로 나누면 됩니다. 저는 AWS 리소스 별로(VPC, EC2, RDS) 나누겠습니다.

#vpc.tf
resource “aws_subnet” “blogPublicSubnet” {
  vpc_id = “$(aws_vpc.blogVPC.id)”
  cidr_block = “10.0.0.0/24"
  availability_zone = “ap-northeast-2c”
  tags = {
    Name = “blog-public-subnet”
  }
}
resource “aws_subnet” “blogPrivateSubnet1” {
  vpc_id = “$(aws_vpc.blogVPC.id)”
  cidr_block = “10.0.1.0/24"
  availability_zone = “ap-northeast-2c”
  tags = {
    Name = “blog-private-subnet1”
  }
}
resource “aws_subnet” “blogPrivateSubnet2” {
  vpc_id = “$(aws_vpc.blogVPC.id)”
  cidr_block = “10.0.2.0/24"
  availability_zone = “ap-northeast-2a”
  tags = {
    Name = “blog-private-subnet2”
  }
}

다른 IaaC와 비슷하게 vpc_id를 보시면 이미 설정했던 aws_vpc의 리소스의 이름을 지정해서 바로 사용할 수 있습니다.

인터넷 게이트웨이(IGW) 작성

인터넷 게이트웨이는 VPC에 연결하여 VPC가 인터넷과 통신할 수 있도록 해줍니다.

# vpc.tf
resource “aws_internet_gateway” “blogIGW” {
  vpc_id = aws_vpc.blogVPC.id
  tags = {
    Name = “blog-IGW”
  }
}

라우팅 테이블 작성

VPC를 생성하면 기본 라우팅 테이블이 함께 만들어집니다. 하지만 VPC를 보호하기 위해 기본 라우팅 테이블은 초기 설정을 그대로 두고 사용하지 않는 것을 권장합니다.

라우팅 테이블을 작성하고 이를 서브넷과 연결해줍니다.

#vpc.tf
## 퍼블릭 라우팅 테이블 정의
resource “aws_route_table” “blogPublicRoute” {
  vpc_id = aws_vpc.blogVPC.id
  route {
    cidr_block = “0.0.0.0/0”
    gateway_id = aws_internet_gateway.blogIGW.id
  }
  tags = {
    Name = “blog-public-route”
  }
}
## 프라이빗 라우팅 테이블 정의
resource “aws_route_table” “blogPrivateRoute1” {
  vpc_id = aws_vpc.blogVPC.id

  tags = {
    Name = “blog-private-route”
  }
}

resource “aws_route_table” “blogPrivateRoute2” {
  vpc_id = aws_vpc.blogVPC.id

  tags = {
    Name = “blog-private-route”
  }
}

## 퍼블릭 라우팅 테이블 연결
resource “aws_route_table_association” “blogPublicRTAssociation” {
  subnet_id = aws_subnet.blogPublicSubnet.id
  route_table_id = aws_route_table.blogPublicRoute.id
}
## 프라이빗 라우팅 테이블 연결
resource “aws_route_table_association” “blogPrivateRTAssociation1” {
  subnet_id = aws_subnet.blogPrivateSubnet1.id
  route_table_id = aws_route_table.blogPrivateRoute1.id
}
resource “aws_route_table_association” “blogPrivateRTAssociation2” {
  subnet_id = aws_subnet.blogPrivateSubnet2.id
  route_table_id = aws_route_table.blogPrivateRoute2.id
}

보안 그룹 작성

마지막으로 보안 그룹을 작성해주면 VPC에서의 작업은 끝입니다.(VPC가 제일 오래 걸립니다 ㅎㅎ..)

테라폼에서는 보안 그룹을 설정할 때 인라인으로 규칙을 추가해주거나 aws_security_group_rule으로 추가할 수 있습니다.

만약 인라인으로 지정하게 된다면 한 보안 그룹에서 인바운드로 허용하는 액세스원이 다른 보안 그룹인 경우 이를 지정 할 수 없습니다.

# vpc.tf
## 퍼블릭 보안 그룹
resource "aws_security_group" "blogPublicSG" {
  vpc_id = aws_vpc.blogVPC.id
  name = "blog public SG"
  description = "blog public SG"
  tags = {
    Name = "log public SG"
  }
}
## 프라이빗 보안 그룹
resource "aws_security_group" "blogPrivateSG" {
  vpc_id = aws_vpc.blogVPC.id
  name = "blog private SG"
  description = "blog private SG"
  tags = {
    Name = "log private SG"
  }
}
## 퍼블릭 보안 그룹 규칙
resource "aws_security_group_rule" "blogPublicSGRulesHTTPingress" {
  type = "ingress"
  from_port = 80
  to_port = 80
  protocol = "TCP"
  cidr_blocks = [ "0.0.0.0/0" ]
  security_group_id = aws_security_group.blogPublicSG.id
  lifecycle {
    create_before_destroy = true
  }
}
resource "aws_security_group_rule" "blogPublicSGRulesHTTPegress" {
  type = "egress"
  from_port = 80
  to_port = 80
  protocol = "TCP"
  cidr_blocks = [ "0.0.0.0/0" ]
  security_group_id = aws_security_group.blogPublicSG.id
  lifecycle {
    create_before_destroy = true
  }
}
resource "aws_security_group_rule" "blogPublicSGRulesHTTPSingress" {
  type = "ingress"
  from_port = 443
  to_port = 443
  protocol = "TCP"
  cidr_blocks = [ "0.0.0.0/0" ]
  security_group_id = aws_security_group.blogPublicSG.id
  lifecycle {
    create_before_destroy = true
  }
}
resource "aws_security_group_rule" "blogPublicSGRulesHTTPSegress" {
  type = "egress"
  from_port = 443
  to_port = 443
  protocol = "TCP"
  cidr_blocks = [ "0.0.0.0/0" ]
  security_group_id = aws_security_group.blogPublicSG.id
  lifecycle {
    create_before_destroy = true
  }
}
resource "aws_security_group_rule" "blogPublicSGRulesSSHingress" {
  type = "ingress"
  from_port = 22
  to_port = 22
  protocol = "TCP"
  cidr_blocks = [ "---.---.---.---/32" ]
  security_group_id = aws_security_group.blogPublicSG.id
  lifecycle {
    create_before_destroy = true
  }
}
resource "aws_security_group_rule" "blogPublicSGRulesSSHegress" {
  type = "egress"
  from_port = 22
  to_port = 22
  protocol = "TCP"
  cidr_blocks = [ "---.---.---.---/32" ]
  security_group_id = aws_security_group.blogPublicSG.id
  lifecycle {
    create_before_destroy = true
  }
}
### Private Security Group rules
resource "aws_security_group_rule" "blogPrivateSGRulesRDSingress" {
  type = "ingress"
  from_port = 3306
  to_port = 3306
  protocol = "TCP"
  security_group_id = aws_security_group.blogPrivateSG.id
  source_security_group_id = aws_security_group.blogPublicSG.id
  lifecycle {
    create_before_destroy = true
  }
}
resource "aws_security_group_rule" "blogPrivateSGRulesRDSegress" {
  type = "egress"
  from_port = 3306
  to_port = 3306
  protocol = "TCP"
  security_group_id = aws_security_group.blogPrivateSG.id
  source_security_group_id = aws_security_group.blogPublicSG.id
  lifecycle {
    create_before_destroy = true
  }
}

마지막으로 apply를 하여 결과를 확인합니다.

마치며

여기까지 따라오시느라 고생하셨습니다. 이 글의 가장 긴 부분이 지났네요.

글이 너무 길어지는 관계로 나머지 부분은 다음 글에서 이어서 작성하겠습니다.

일어서서 스트레칭도 하시고 커피도 한잔 마시고 다음으로 넘어가 주세요.


내용의 피드백 및 오탈자 제보는 언제나 환영합니다! must01940(G메일) 으로 보내주시길 바랍니다.

긴 글 읽어주셔서 감사합니다!

글 목록

0장. 테라폼(terraform)이 뭐야?

1장. 테라폼 설치하기

2장. EC2 RDS S3 구축하기 – 1 - 해당 글

2장. EC2 RDS S3 구축하기 – 2