Terraform Study #Final Exam

길다고 생각했던 Terraform Study가 드디어 끝이 났다.

혼자 공부를 할 때 어려웠던 부분을 잘 쉽게 설명해주신 gasida님 덕분에 즐거운 스터디 기간이 됐던 것 같다.

오늘은 그 동안 배웠던 내용을 토대로 하나의 프로젝트를 생성해보려고 한다.

요새 AWS 환경에 테스트 중인 AWS Backup을 배포하여 사용해보려고 한다.

실습 단계는 아래와 같이 진행할 예정이다.

1. VPC, Subnet 생성

2. 2대의 EC2 생성 – AMI 백업 목적의 EC2와 EFS을 마운트 할 목적의 EC2 생성(EFS도 같이 생성)

3. RDS 생성

4. Backup vault, Plan 생성 – KMS Key도 같이 생성

5. EFS 마운트 확인

위와 같은 순서로 리소스들을 생성할 예정이다.

대부분의 코드는 기존 챕터 과제와 중간 과제 때 사용했던 코드를 가져와서 이용했다.

전체적인 구조는 리소스를 Modules의 정보를 가져와서 생성하게끔 되어있다.

PRD/DEV는 따로 분류하지 않았다.

1. VPC, Subnet 생성

코드는 아래 열기 버튼을 클릭하면 된다.

VPC와 서브넷을 포함한 네트워크 리소스들은 대부분 이전 프로젝트에서 따와서 작성했다.

서울 Region에 VPC을 생성하고 Zone A, C로 나눈 서브넷을 각각 생성했다.

SG의 경우 SSH을 위한 22, HTTP을 위한 80과 외부 통신을 하기 위해 Egress는 Any로 열어줬다. 

EFS 마운트를 위해 2049도 추가로 열어주었다.

Mysql RDS와 EC2간의 통신을 위한 포트도 추가해야 하는데 이는 아직 DB 데이터가 없어 추후 변경 시에 넣을 예정이다.

locals {
  http_port    = 80
  ssh_port     = 22
  any_port     = 0
  efs_port     = 2049
  any_protocol = "-1"
  tcp_protocol = "tcp"
  my_ip        = ["222.111.4.135/32"]
  all_ips      = ["0.0.0.0/0"]
  all_block    = "0.0.0.0/0"
}

resource "aws_vpc" "prj_vpc" {
  cidr_block       = var.vpc_cidr
  enable_dns_support = true
  enable_dns_hostnames = true

  tags = {
    Name = "${var.prj_name}-vpc"
  }
}

resource "aws_subnet" "prj_subnet1" {
  vpc_id     = aws_vpc.prj_vpc.id
  cidr_block = var.subnet1_cidr

  availability_zone = var.az_a

  tags = {
    Name = "${var.prj_name}-subnet1"
  }
}

resource "aws_subnet" "prj_subnet2" {
  vpc_id     = aws_vpc.prj_vpc.id
  cidr_block = var.subnet2_cidr

  availability_zone = var.az_c

  tags = {
    Name = "${var.prj_name}-subnet2"
  }
}

resource "aws_internet_gateway" "prj_igw" {
  vpc_id = aws_vpc.prj_vpc.id

  tags = {
    Name = "${var.prj_name}-igw"
  }
}


resource "aws_route_table" "prj_rt" {
  vpc_id = aws_vpc.prj_vpc.id

  tags = {
    Name = "${var.prj_name}-rt"
  }
}

resource "aws_route_table_association" "prj_asso1" {
  subnet_id      = aws_subnet.prj_subnet1.id
  route_table_id = aws_route_table.prj_rt.id
}

resource "aws_route_table_association" "prj_asso2" {
  subnet_id      = aws_subnet.prj_subnet2.id
  route_table_id = aws_route_table.prj_rt.id
}

resource "aws_route" "prj_dftrt" {
  route_table_id         = aws_route_table.prj_rt.id
  destination_cidr_block = local.all_block
  gateway_id             = aws_internet_gateway.prj_igw.id
}


resource "aws_security_group" "prj_sg" {
  name = "${var.prj_name}-sg"
  vpc_id      = aws_vpc.prj_vpc.id

  ingress {
    from_port   = local.http_port
    to_port     = local.http_port
    protocol    = local.tcp_protocol
    cidr_blocks = local.all_ips
  }

  ingress {
    from_port   = local.ssh_port
    to_port     = local.ssh_port
    protocol    = local.tcp_protocol
    cidr_blocks = local.my_ip
  }
  ingress {
description = "EFS mount target"
from_port   = local.efs_port
to_port     = local.efs_port
protocol    = local.tcp_protocol
cidr_blocks = local.all_ips
  }

  egress {
    from_port   = local.any_port
    to_port     = local.any_port
    protocol    = local.any_protocol
    cidr_blocks = local.all_ips
  }
  tags = {
    Name = "${var.prj_name}-sg"
  }
}

2. 2대의 EC2 생성

AMI 백업만을 진행할 목적의 EC2 1대와 EFS을 백업할 목적의 EC2를 1대 각각 배포한다.

Keypair는 스터디 진행 중인 맥북의 ssh key로 생성할 수 있게 하였다.

앞단에는 ALB을 두어 http 통신이 가능하게끔 했다.

이때 EFS도 같이 생성을 하고 자동 백업은 실행되지 않게끔 해둔 상태로 생성을 진행했다.

EFS 백업 목적의 EC2에는 user data에 EFS을 마운트하는 명령어도 추가하였다.

EFS 마운트는 해당 EC2의 서브넷에서만 가능하게 하였다.

코드는 아래에서 확인 가능하다.

<hide/>
locals {
  http_protocol = "HTTP"
  ec2_file_system_local_mount_path = "/mnt/efs"
}

data "aws_ami" "prj_amazonlinux2" {
  most_recent = true
  filter {
    name   = "owner-alias"
    values = ["amazon"]
  }

  filter {
    name   = "name"
    values = ["amzn2-ami-hvm-*-x86_64-ebs"]
  }

  owners = ["amazon"]
}

resource "aws_lb" "prj_alb" {
  name               = "${var.prj_name}-alb"
  load_balancer_type = "application"
  subnets            = [aws_subnet.prj_subnet1.id, aws_subnet.prj_subnet2.id]
  security_groups = [aws_security_group.prj_sg.id]

  tags = {
    Name = "${var.prj_name}-alb"
  }
}

resource "aws_lb_listener" "prj_http" {
  load_balancer_arn = aws_lb.prj_alb.arn
  port              = local.http_port
  protocol          = local.http_protocol

  <em># By default, return a simple 404 page</em>
  default_action {
    type = "fixed-response"

    fixed_response {
      content_type = "text/plain"
      message_body = "404: page not found - T101 Study"
      status_code  = 404
    }
  }
}

resource "aws_lb_target_group" "prj_albtg" {
  name = "${var.prj_name}-albtg"
  port     = local.http_port
  protocol = local.http_protocol
  vpc_id   = aws_vpc.prj_vpc.id

  health_check {
    path                = "/"
    protocol            = local.http_protocol
    matcher             = "200-299"
    interval            = 5
    timeout             = 3
    healthy_threshold   = 2
    unhealthy_threshold = 2
  }
}

resource "aws_lb_target_group_attachment" "prj_albrg_atch1" {
  target_group_arn = "${aws_lb_target_group.prj_albtg.arn}"
  target_id        = "${aws_instance.ami_backup_ec2.id}"
  port             = 80
}
resource "aws_lb_target_group_attachment" "prj_albrg_atch2" {
  target_group_arn = "${aws_lb_target_group.prj_albtg.arn}"
  target_id        = "${aws_instance.efs_backup_ec2.id}"
  port             = 80
}

resource "aws_key_pair" "prj_keypair" { 
  key_name = "ec2-kp"
  public_key = file("~/.ssh/id_rsa.pub") 
}


resource "aws_instance" "ami_backup_ec2" {

  depends_on = [
    aws_internet_gateway.prj_igw
    
  ]

  ami                         = data.aws_ami.prj_amazonlinux2.id
  associate_public_ip_address = true
  instance_type               = var.instance_type
  vpc_security_group_ids      = ["${aws_security_group.prj_sg.id}"]
  subnet_id                   = aws_subnet.prj_subnet1.id
key_name = aws_key_pair.prj_keypair.key_name
  user_data = <<-EOF
              <em>#!/bin/bash</em>
              wget https://busybox.net/downloads/binaries/1.31.0-defconfig-multiarch-musl/busybox-x86_64
              mv busybox-x86_64 busybox
              chmod +x busybox
              RZAZ=$(curl http://169.254.169.254/latest/meta-data/placement/availability-zone-id)
              IID=$(curl 169.254.169.254/latest/meta-data/instance-id)
              LIP=$(curl 169.254.169.254/latest/meta-data/local-ipv4)
              echo "<h1>RegionAz($RZAZ) : Instance ID($IID) : Private IP($LIP) : Web Server</h1>" > index.html
              nohup ./busybox httpd -f -p 80 &
              EOF

  user_data_replace_on_change = true

  tags = {
    Name = "${var.prj_name}-amibackup-ec2"
    backup = "enable"
  }
}

resource "aws_instance" "efs_backup_ec2" {

  depends_on = [
    aws_internet_gateway.prj_igw
    
  ]

  ami                         = data.aws_ami.prj_amazonlinux2.id
  associate_public_ip_address = true
  instance_type               = var.instance_type
  vpc_security_group_ids      = ["${aws_security_group.prj_sg.id}"]
  subnet_id                   = aws_subnet.prj_subnet2.id
key_name = aws_key_pair.prj_keypair.key_name

  user_data = <<-EOF
              <em>#!/bin/bash</em>
              wget https://busybox.net/downloads/binaries/1.31.0-defconfig-multiarch-musl/busybox-x86_64
              mv busybox-x86_64 busybox
              chmod +x busybox
              RZAZ=$(curl http://169.254.169.254/latest/meta-data/placement/availability-zone-id)
              IID=$(curl 169.254.169.254/latest/meta-data/instance-id)
              LIP=$(curl 169.254.169.254/latest/meta-data/local-ipv4)
              echo "<h1>RegionAz($RZAZ) : Instance ID($IID) : Private IP($LIP) : Web Server</h1>" > index.html
              nohup ./busybox httpd -f -p 80 &
              mkdir -p ${local.ec2_file_system_local_mount_path}
              yum install -y amazon-efs-utils
              mount -t efs -o iam,tls ${aws_efs_file_system.prj_efs.id} ${local.ec2_file_system_local_mount_path}
              echo "${aws_efs_file_system.prj_efs.id} ${local.ec2_file_system_local_mount_path} efs _netdev,tls,iam 0 0" >> /etc/fstab
              <em># Creating demo content for other services</em>
              mkdir -p ${local.ec2_file_system_local_mount_path}/fargate
              mkdir -p ${local.ec2_file_system_local_mount_path}/lambda
              df -h > ${local.ec2_file_system_local_mount_path}/fargate/demo.txt
              df -h > ${local.ec2_file_system_local_mount_path}/lambda/demo.txt
              chown ec2-user:ec2-user -R ${local.ec2_file_system_local_mount_path}
              EOF

  user_data_replace_on_change = true

  tags = {
    Name = "${var.prj_name}-amibackup-ec2"
    <em>#backup = "enable"</em>
  }
}

resource "aws_lb_listener_rule" "prj_albrule" {
  listener_arn = aws_lb_listener.prj_http.arn
  priority     = 100

  condition {
    path_pattern {
      values = ["*"]
    }
  }

  action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.prj_albtg.arn
  }
}

resource "aws_efs_file_system" "prj_efs" {
  encrypted = false
  tags = {
    Name = "${var.prj_name}-efs"
    backup = "enable"
  }
}

resource "aws_efs_backup_policy" "prj_efs_backup_policy" {
  file_system_id = aws_efs_file_system.prj_efs.id

  backup_policy {
    status = "DISABLED"
  }
}

resource "aws_efs_mount_target" "prj_efs_backup_mt" {
  file_system_id = aws_efs_file_system.prj_efs.id
  subnet_id      = aws_subnet.prj_subnet2.id
  security_groups = ["${aws_security_group.prj_sg.id}"]
}

3. RDS 생성

RDS는 별다른 설정 없이 우선 기본값으로 생성하였다.

추후 html 코드 등을 수정하여 DB값을 불러올 때 수정할 예정이다.

<hide/>

resource "aws_db_instance" "prj_rds" {
  <em>#name = "${var.prj_name}-prjdb"</em>
  db_name           = "tfstudyfprjdb"
  allocated_storage = 8
  engine = "mysql"
  engine_version = "5.7"
  instance_class = "db.t2.micro"
  username = ""
  password = ""
  db_subnet_group_name = aws_db_subnet_group.prj_dbsubnetgr.name
}

resource "aws_db_subnet_group" "prj_dbsubnetgr" {
    Name = "${var.prj_name}-dbsubnetgr"
  subnet_ids = [aws_subnet.prj_subnet1.id, aws_subnet.prj_subnet2.id]
}

4. Backup 리소스 생성

Backup Vault을 생성하기 전에 KMS Key을 생성하였다.

이후 Vault을 생성하고 Plan과 리소스 종류를 결정 지어줬다.

백업 선택은 Tag에 backup:enable이 입력 된 리소스들에 한해 백업을 진행할 수 있도록 하였다.

백업 스케쥴은 2시간 단위로 백업이 진행 될 수 있도록 Cron 표현식을 사용하였다.


resource "aws_kms_key" "prj_kmskey" {
  description             = "KMS key for backup vault"
}

resource "aws_backup_vault" "prj_kmskey" {
  name = "${var.prj_name}-backupvault"
  kms_key_arn = aws_kms_key.prj_kmskey.arn
}

resource "aws_backup_selection" "prj_backup_sel" {
  iam_role_arn = "arn:aws:iam::536033748497:role/aws-service-role/backup.amazonaws.com/AWSServiceRoleForBackup"
  name = "${var.prj_name}-backupsel"
  plan_id      = aws_backup_plan.prj_backup_plan.id

  selection_tag {
    type  = "STRINGEQUALS"
    key   = "backup"
    value = "enalbe"
  }

}

resource "aws_backup_plan" "prj_backup_plan" {
  name = "${var.prj_name}-backupplan"

  rule {
    rule_name         = "${var.prj_name}-backuprule-ami"
    target_vault_name = aws_backup_vault.prj_kmskey.name
    schedule          = "cron(0 0/2 1/1 * ? *)"
    
    lifecycle {
      delete_after = 2
    }
  }

    rule {
    rule_name         = "${var.prj_name}-backuprule-efs"
    target_vault_name = aws_backup_vault.prj_kmskey.name
    schedule          = "cron(0 0/2 1/1 * ? *)"
    
    lifecycle {
      delete_after = 2
    }
  }
  rule {
    rule_name         = "${var.prj_name}-backuprule-rds"
    target_vault_name = aws_backup_vault.prj_kmskey.name
    schedule          = "cron(0 0/2 1/1 * ? *)"

    lifecycle {
      delete_after = 2
    }
  }

  advanced_backup_setting {
    backup_options = {
      WindowsVSS = "enabled"
    }
    resource_type = "EC2"
  }
}

5. EFS 마운트 확인

배포가 전부 완료 되면 EC2에 접근하여 EFS Mount가 잘 됐는지 확인한다.

마운트는 물론 폴더 생성하는 스크립트까지 잘 작동한 것을 확인할 수 있다.

6. 회고

RDS을 활용하지 못해 아쉬웠다.

백업의 경우에도 시간이 부족해 백업이 잘 작동하고 이걸 바탕으로 복원하는 테스트까진 진행해보지 못했다.

콘솔 상에선 작동해봤지만 코드로 진행해보지 못해서 아쉽다.

테라폼을 쓰다보면 리소스 관리가 수월하다는 것을 알 수 있다. 하지만 단점도 분명 존재하는 것 같다.

예를 들면, 콘솔에서 UI을 바탕으로 생성할 때는 손 쉽게 구조가 보이지만 테라폼으로 하려면 코드 몇 줄만으로는 생성이 어렵다는 것을 알 수 있게 된다.

물론 테라폼 Docs에 어느정도 기술이 되어있지만 이것만 보고는 진행하기가 어려울 것으로 생각 된다.

plan과 apply을 계속 실행해보며 오류를 찾아서 해결해나가는 과정이 필요하지 않을까 싶다.

그런 과정 속에서 RDS나 ASG같이 삭제되고 재생성되는 리소스들의 시간이 오래 걸리는 부분도 단점이 아닐까 싶다.

그럼에도 불구하고 편리하게 리소스를 배포하고 관리할 수 있다는 점에서는 분명 좋은 툴이라고 생각이 든다.

앞으로 더 다양한 리소스를 테라폼을 통해 배포하는 과정을 진행해봐야겠다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다