테라폼 실전 강좌 #10 도메인·HTTPS·완성: Route 53, ACM 검증, CloudFront와 provider alias

5 분 소요

마지막 조각입니다. 지금 서비스는 ALB의 긴 자동 생성 주소로 HTTP 접속만 됩니다. 이번 편에서 도메인을 연결하고, 인증서를 발급해 HTTPS를 켜고, 정적 자산에 CDN을 달아 #1에서 그린 완성형에 도달합니다. 테라폼 관점의 새 배울 거리는 둘입니다. ACM의 DNS 검증을 리소스로 표현하는 관용구, 그리고 리전이 다른 리소스를 한 코드에서 다루는 provider alias입니다. 도메인은 이미 Route 53에 호스티드 존이 있다고 전제합니다(구입과 존 개념은 도메인·DNS 기초를 참고하세요).

호스티드 존은 데이터 소스로 #

도메인 자체는 이 프로젝트가 만든 것이 아니므로, 기초 #4의 원칙대로 관리 권한 없이 읽기만 합니다.

dns.tf
# dns.tf (새 파일)
data "aws_route53_zone" "main" {
  name = "example.com"
}

ACM 인증서와 DNS 검증 관용구 #

ACM 인증서는 신청만으로 발급되지 않고 도메인 소유 검증을 거칩니다. 콘솔에서는 “레코드를 추가하세요” 버튼을 누르는 그 과정이, 테라폼에서는 리소스 세 개의 관용구가 됩니다. 신청, 검증 레코드, 검증 대기입니다.

dns.tf
resource "aws_acm_certificate" "alb" {
  domain_name       = "app.example.com"
  validation_method = "DNS"

  lifecycle {
    create_before_destroy = true
  }
}

resource "aws_route53_record" "alb_cert_validation" {
  for_each = {
    for dvo in aws_acm_certificate.alb.domain_validation_options : dvo.domain_name => {
      name   = dvo.resource_record_name
      type   = dvo.resource_record_type
      record = dvo.resource_record_value
    }
  }

  zone_id = data.aws_route53_zone.main.zone_id
  name    = each.value.name
  type    = each.value.type
  ttl     = 60
  records = [each.value.record]
}

resource "aws_acm_certificate_validation" "alb" {
  certificate_arn         = aws_acm_certificate.alb.arn
  validation_record_fqdns = [for r in aws_route53_record.alb_cert_validation : r.fqdn]
}

가운데 리소스의 for_each는 기초 #4의 for 표현식이 실전에서 가장 자주 쓰이는 모습입니다. 인증서가 요구하는 검증 레코드 목록을 맵으로 바꿔 레코드를 만듭니다. 마지막 aws_acm_certificate_validation은 실제 리소스를 만드는 것이 아니라 검증 완료까지 기다리는 대기 장치로, 이것을 참조하는 리소스는 인증서가 발급 완료된 뒤에 만들어집니다. 처음 보면 낯설지만 ACM을 코드로 다루는 모든 곳에서 만나는 표준 관용구입니다.

HTTPS 리스너와 리다이렉트 #

발급된 인증서를 ALB에 답니다. #4에서 예고한 대로 80 리스너는 리다이렉트로 바뀝니다.

lb.tf
# lb.tf 수정
resource "aws_lb_listener" "https" {
  load_balancer_arn = aws_lb.main.arn
  port              = 443
  protocol          = "HTTPS"
  certificate_arn   = aws_acm_certificate_validation.alb.certificate_arn

  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.ecs.arn
  }
}

resource "aws_lb_listener" "http" {
  load_balancer_arn = aws_lb.main.arn
  port              = 80
  protocol          = "HTTP"

  default_action {
    type = "redirect"
    redirect {
      port        = "443"
      protocol    = "HTTPS"
      status_code = "HTTP_301"
    }
  }
}

443 리스너가 인증서 ARN을 aws_acm_certificate_validation에서 가져오는 것이 위 관용구의 마무리입니다. ALB SG에 443 인바운드를 추가하는 것도 잊지 않습니다. 이제 https://app.example.com으로 가는 마지막 연결, 별칭 레코드를 답니다.

dns.tf
resource "aws_route53_record" "app" {
  zone_id = data.aws_route53_zone.main.zone_id
  name    = "app.example.com"
  type    = "A"

  alias {
    name                   = aws_lb.main.dns_name
    zone_id                = aws_lb.main.zone_id
    evaluate_target_health = true
  }
}

provider alias: CloudFront의 us-east-1 요구 #

정적 자산 차례에서 실무의 유명한 함정을 만납니다. CloudFront에 다는 인증서는 리전과 무관하게 반드시 us-east-1의 ACM에 있어야 합니다. 우리 provider는 ap-northeast-2인데 어떻게 할까요? 답은 provider alias입니다. 같은 provider를 다른 설정으로 한 벌 더 등록하는 기능입니다.

providers.tf
# providers.tf에 추가
provider "aws" {
  alias  = "us_east_1"
  region = "us-east-1"

  default_tags {
    tags = {
      Project   = "myapp"
      Env       = var.env
      ManagedBy = "terraform"
    }
  }
}
cdn.tf
resource "aws_acm_certificate" "cdn" {
  provider = aws.us_east_1

  domain_name       = "static.example.com"
  validation_method = "DNS"

  lifecycle {
    create_before_destroy = true
  }
}

리소스에 provider = aws.us_east_1을 지정하면 그 리소스만 버지니아에 만들어집니다. 검증 레코드와 대기 장치는 앞의 관용구와 동일하게 붙습니다(레코드 자체는 기본 provider로, 인증서와 대기는 alias로 만드는 점만 주의합니다). CloudFront 배포는 OAC(Origin Access Control)로 #5의 닫힌 assets 버킷을 읽습니다. 배포 리소스는 인수가 많아 전문은 싣지 않지만 뼈대는 단순합니다. origin이 S3 버킷, 인증서가 방금 만든 cdn 인증서, 버킷 정책이 “이 배포에서 오는 GetObject만 허용"입니다. 버킷은 끝까지 퍼블릭이 아니고, 세상과 만나는 것은 CDN뿐입니다.

완성, 그리고 회고 #

static.example.com의 별칭 레코드까지 달면 #1의 그림이 전부 코드가 되었습니다. 열 편의 여정을 리소스가 아니라 결정으로 돌아보면 이렇습니다. 태그와 백엔드를 첫날 세웠고(#1), 네트워크 계층을 기준으로 보안을 설계했고(#2·#3), 트래픽을 받으며 계층을 갈아탔고(#4·#8), 데이터에는 안전장치를 걸었고(#5), 시크릿을 기록에서 지웠고(#6), 권한을 참조로 좁혔고(#7), 구조를 재생성 없이 리팩터링해 환경을 복제했습니다(#9). 이 결정들은 테라폼 문법이 아니라 인프라 운영의 판단이고, 테라폼은 그 판단을 리뷰와 이력이 있는 코드로 만들어 준 도구였습니다.

실습을 마쳤으면 정리합니다. dev는 destroy 한 번이면 되고, 이것으로 요금 걱정도 끝입니다. 남은 질문은 하나입니다. 이 코드를 팀이 함께, 사람 손의 apply 없이 운영하려면 무엇이 필요할까요?

정리 #

이번 글에서 다룬 내용입니다.

  • 이미 있는 호스티드 존은 데이터 소스로 읽습니다. ACM은 신청, 검증 레코드(for_each 관용구), 검증 대기의 리소스 세 개로 발급합니다
  • HTTPS 리스너는 검증 대기 리소스에서 인증서 ARN을 받아 발급 완료 후에 만들어지고, 80 리스너는 301 리다이렉트로 바뀝니다
  • CloudFront용 인증서는 반드시 us-east-1이어야 하며, provider alias로 한 코드에서 두 리전을 다룹니다
  • CloudFront는 OAC로 닫힌 버킷을 읽습니다. 버킷은 끝까지 퍼블릭이 되지 않습니다
  • 열 편의 산출물은 리소스 목록이 아니라 코드에 남은 운영 판단들입니다. 실습 정리는 destroy 한 번입니다

다음은 테라폼 운영 강좌입니다. PR에서 plan을 리뷰하고 CI가 apply를 실행하는 팀 워크플로부터, drift 대응, 정책과 비용 가드레일까지 일곱 편으로 다루겠습니다.

X