상세 컨텐츠

본문 제목

[Terraform] AWS RDS - Terraform 가이드 (3)

Development

by 12기통엔진 2024. 7. 16. 15:49

본문

반응형

Terraform을 처음 이용해보는 사람의 입장에서 작성하는 시리즈다.

해당 글은 AWS와 연결해 접근 가능한 인스턴스를 하나 띄워보는 것을 목표로 한다.

이어지는 시리즈에서 서비스 별 모듈화, 구조 연결 등을 진행할 계획이니 차근차근 따라오면 된다.

 

  1. terraform 디렉토리 생성, main.tf 파일 생성
  2. RDS 클러스터, 인스턴스 정의
  3. Network 리소스 정의
  4. Security 리소스 정의
  5. AWS credential 연결
  6. 배포
  7. 마무리

terraform 디렉토리 생성, main.tf 파일 생성


백엔드 프로젝트의 루트 디렉토리에 terraform 디렉토리를 생성하자.

mkdir terraform

 

해당 디렉토리에 VSCode의 명령어 + N 로 main.tf 파일을 하나 생성한다.

(참고)
이번 글에서는 실행해보는 것을 목표로 하기 때문에 main.tf 파일에 모든 정보를 기재할 예정이다.
다음 글에서부터 variable.tf, provider.tf 파일을 생성해 민감한 정보를 담은 코드를 분리하고
리소스를 역할 별로 디렉토리를 만들어 분류할 예정이다.

 

RDS 클러스터, 인스턴스 정의


  • aws region 정의

in /terraform/main.tf,

# Provider
provider "aws" {
  region = {your_region}
}

 

 

  • db instance 정의

in /terraform/main.tf,

# DB Instance
resource "aws_db_instance" "db_instance" {
  identifier              = {your_db_instance_name}
  instance_class          = "db.t3.micro" // free tier
  engine                  = "mysql"
  engine_version          = "8.0.34"
  username                = {db_username}
  password                = {db_password}
  db_name                 = {db_name}
  allocated_storage       = 20
  skip_final_snapshot     = true
  publicly_accessible     = true
}
 

Terraform Registry

 

registry.terraform.io

공식 문서에서 필요한 프로퍼티를 찾아 기입하자.

identifier AWS console에 노출될 이름을 지정
instance_class Free tier 에 해당하는 "db.t3.micro" 클래스로 정의한다.
engine, engine_version engine, engine_version: 사용할 엔진, 버전을 지정한다. (Docs가 불친절해서 AWS에서 옵션을 보고 기입하자.)
username, password RDS는 master 계정과 동일한 권한의 계정을 추가 생성해 우리에게 제공해준다. 해당 계정의 이름, 비밀번호를 설정하자.
db_name 사용할 db의 이름을 지정한다. 이 옵션을 넣으면 자동으로 db를 만들어준다. 나중에 진입해서 테이블은 만들어놔야 한다.
allocated_storage Storage 양, GB 단위이다.
skip_final_snapshot 해당 DB instance를 삭제할 때 final snapshot을 만들지 물어보는 항목
publicly_accessible AWS 내부 네트워크에서만 통신할건지 물어보는 항목

 

Network 리소스 정의


이제 위에서 정의한 db instance가 배포되었을 때 public network를 통해 접근할 수 있도록 정의해주자.

  • VPC: AWS 클라우드 내에 격리된 네트워크 환경을 제공
  • Subnet: VPC 내에서 IP 주소 범위를 더 작은 논리적 네트워크로 분할
  • Internet Gateway: VPC와 인터넷 간의 양방향 통신을 가능하게 하는 게이트웨이
  • Route Table: VPC 내 네트워크 트래픽 경로를 정의
  • Route Table Association: 서브넷을 특정 라우팅 테이블에 연결
  • Subnet Group: RDS와 같은 특정 AWS 서비스에서 사용되는 논리적 서브넷 집합

in /terraform/main.tf,

# DB Network
data "aws_availability_zones" "available" {}
resource "aws_vpc" "db_vpc" {
  cidr_block = "10.0.0.0/16" 
  
  enable_dns_hostnames = true
  enable_dns_support   = true

  tags = {
    Name = "db-vpc"
  }
}
resource "aws_subnet" "db_subnet" {
  count             = 2
  vpc_id            = aws_vpc.db_vpc.id
  cidr_block        = element(["10.0.1.0/24", "10.0.2.0/24"], count.index)
  availability_zone = element(data.aws_availability_zones.available.names, count.index)

  tags = {
    Name = "db-subnet-${count.index}"
  }
}
resource "aws_internet_gateway" "db_igw" {
  vpc_id = aws_vpc.db_vpc.id

  tags = {
    Name = "db-igw"
  }
}
resource "aws_route_table" "db_route_table" {
  vpc_id = aws_vpc.db_vpc.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.db_igw.id
  }

  tags = {
    Name = "db-route-table"
  }
}
resource "aws_route_table_association" "db_route_table_assoc" {
  count          = 2
  subnet_id      = element(aws_subnet.db_subnet[*].id, count.index)
  route_table_id = aws_route_table.db_route_table.id
}
resource "aws_db_subnet_group" "db_subnet_group" {
  name       = "db-subnet-group"
  subnet_ids = aws_subnet.db_subnet[*].id

  tags = {
    Name = "db-subnet-group"
  }
}

 

  • DB instance와 subnet group 연결
# DB Instance
resource "aws_db_instance" "db_instance" {
...
  db_subnet_group_name    = aws_db_subnet_group.db_subnet_group.name // New!
}

 

Security 리소스 정의


다음은 접근 가능 ip, port를 제한하는 Security Group을 정의한다.

in /terraform/main.tf,

resource "aws_security_group" "db_sg" {
  description = "Allow DB instance access"
  vpc_id = aws_vpc.db_vpc.id

  ingress {
    from_port   = 3306
    to_port     = 3306
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "db-sg"
  }
}

 

해당 security group은,

ingress - 모든 ip로부터 3306 포트의 통신만 받는다.

egress - 모든 ip, 모든 포트에 정보를 전달 가능하다.

 

  • DB instance와 security group 연결
resource "aws_db_instance" "db_instance" {
  ...
  vpc_security_group_ids  = [aws_security_group.db_sg.id] // New!
}

 

AWS credential 연결


  • AWS IAM user 생성
    • Access Key 발급
    • console 로그인용 user name, password는 선택 사항
  • AWS IAM user 권한 추가
    • user group에 권한을 추가한 뒤 해당 group에 user 추가해도 됨.
    • Policy - 내 권한에 있는 건데, 최적화된 권한은 아니므로 추가로 알아봐야한다.
      • AmazonRDSFullAccess
      • AmazonECS_FullAccess
      • AmazonECSTaskExecutionRolePolicy
      • AmazonElasticContainerRegistryPublicFullAccess
      • AmazonEC2ContainerRegistryFullAccess
      • AmazonEC2FullAccess
      • ElasticLoadBalancingFullAccess
      • CloudWatchLogsFullAccess
      • IAMFullAccess
  • AWS cli 설치 및 로그인
aws configure

Access Key ID, Access Key, region 입력

 

배포


해당 과정에 성공하면 AWS에 인스턴스가 생성된다.

무료 티어의 인스턴스이더라도, 네트워크 송수신 비용이 발생하니 꼭 주의해야한다.

 

아래의 명령어들을 순서대로 터미널에 입력한다.

terraform init

terraform validate

terraform apply

yes를 입력하면 배포를 시도한다. db instance 쪽 오류를 해결하느라 1개만 마지막에 추가됐는데, 문제 없다면 10개의 리소스가 추가된다.

 

배포에 성공했으니, 접속되는지 mysql client로 확인해보자.

필자는 Sequel Ace를 사용하고 있다.

성공!

이제 삭제까지 연습해보자.

terraform destroy

terraform apply 명령어로 생성된 모든 리소스를 제거한다.

 

권한이나 종속관계 문제로 해당 명령어로 삭제되지 않는 경우가 있으니,

로그를 꼭 확인하고 AWS 콘솔에서 확실히 삭제해 불필요한 비용 지출을 막자.

 

마무리


현재까지 디렉토리 구조

/project_root
│
├── terraform
│   ├── main.tf
│   ├── .terraform.lock.hcl
│   ├── terraform.tfstate
│   ├── terraform.tfstate.backup
│   ├── .terraform
│   │   ├── ...

.terraform.lock.hcl, terraform.tfstate, terraform.tfstate.backup 파일, .terraform 디렉토리는 .gitignore에 추가하자.

 

현재 main.tf 파일은 다음과 같을 것이다.

# Provider
provider "aws" {
  region = {your_region}
}

# DB Network
data "aws_availability_zones" "available" {}

resource "aws_vpc" "db_vpc" {
  cidr_block = "10.0.0.0/16" 
  
  enable_dns_hostnames = true
  enable_dns_support   = true

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

resource "aws_subnet" "db_subnet" {
  count             = 2
  vpc_id            = aws_vpc.db_vpc.id
  cidr_block        = element(["10.0.1.0/24", "10.0.2.0/24"], count.index)
  availability_zone = element(data.aws_availability_zones.available.names, count.index)

  tags = {
    Name = "db-subnet-${count.index}"
  }
}

resource "aws_internet_gateway" "db_igw" {
  vpc_id = aws_vpc.db_vpc.id

  tags = {
    Name = "db-igw"
  }
}

resource "aws_route_table" "db_route_table" {
  vpc_id = aws_vpc.db_vpc.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.db_igw.id
  }

  tags = {
    Name = "db-route-table"
  }
}

resource "aws_route_table_association" "db_route_table_assoc" {
  count          = 2
  subnet_id      = element(aws_subnet.db_subnet[*].id, count.index)
  route_table_id = aws_route_table.db_route_table.id
}

resource "aws_db_subnet_group" "db_subnet_group" {
  name       = "db-subnet-group"
  subnet_ids = aws_subnet.db_subnet[*].id

  tags = {
    Name = "db-subnet-group"
  }
}

# DB Security
resource "aws_security_group" "db_sg" {
  description = "Allow DB instance access"
  vpc_id = aws_vpc.db_vpc.id

  ingress {
    from_port   = 3306
    to_port     = 3306
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "db-sg"
  }
}

# DB Instance
resource "aws_db_instance" "db_instance" {
  identifier              = {your_db_instance_name}
  instance_class          = "db.t3.micro" // free tier
  engine                  = "mysql"
  engine_version          = "8.0.34"
  username                = {db_username}
  password                = {db_password}
  db_name                 = {db_name}
  allocated_storage       = 20
  db_subnet_group_name    = aws_db_subnet_group.db_subnet_group.name
  vpc_security_group_ids  = [aws_security_group.db_sg.id]
  skip_final_snapshot     = true
  publicly_accessible     = true
}

 

다음 글에서는 main.tf 하나의 파일을 역할에 맞게 분리, 연결하는 작업을 진행할 것이다.

이 때 variable.tf 등 보안과 관련된 파일을 .gitignore에 추가해 공개되는 것을 막는 작업 등도 포함되어 있으니,

커밋은 다음 글까지 진행한 다음 찍어주자.

반응형

관련글 더보기