Programming/IaC

[IaC] Production-grade Terraform code

Hayley Shim 2023. 10. 28. 17:52

안녕하세요. CloudNet@ Terraform Study를 진행하며 해당 내용을 이해하고 공유하기 위해 작성한 글입니다. 도서 ‘Terraform: Up & Running(By Yevgeniy Brikman)’ 의 내용 및 스터디 시간 동안 언급된 주요 내용 위주로 간단히 정리했습니다.

프로덕션 수준의 인프라

  • 서버, 데이터 저장소, 로드 밸런서, 보안 기능, 모니터링 및 경고 도구, 파이프라인 구축 및 비즈니스 운영에 필요한 기타 모든 기술을 의미합니다.
  • 트래픽 과부하로 인프라가 중단되거나 장애가 발생하여 데이터가 손실되거나 해커의 침입으로 데이터에 문제가 생겨 비지니스가 중단되지 않는 수준입니다.

프로덕션 수준 인프라 구축에 오랜 시간이 걸리는 이유

  • 아직 산업의 초기 단계이며, ‘Cloud Computing, IaC, DevOps, Docker, k8s’ 등 도구의 출현과 기술이 빠르게 변하고 있으면 충분히 성숙되지 않았습니다.
  • 아직 수행해야 하는 작업의 체크 리스트가 너무 많습니다.

프로덕션 수준 인프라 체크 리스트

“프로덕션 환경으로 전환하는데 필요한 것이 뭐가 있을까요?”

  • 대부분 회사는 프로덕션 단계로 전환하기 위한 요구 사항을 명확하게 정의하고 있지않으며 이는 각 인프라가 약간 다르게 배포되거나 중요한 기능이 누락될 수 있음을 의미합니다.

프로덕션 수준의 인프라 체크리스트 예

  • 대부분의 개발자는 설치, 구성, 프로비저닝 및 배포와 같은 처음 몇가지 작업만 알고 있습니다.
  • 예를 들어 서버가 다운되면 어떻게 될까요? 네트워크 작업(VPC, VPN, 서비스 검색 및 SSH 액세스 설정 등) 도 까다롭습니다. TLS를 사용하여 전송 중인 데이터를 암호화하고 인증 처리를 하고 시크릿 저장 방법을 정하는 것과 같은 보안 작업은 잊어버리는 경우가 많습니다.
  • 프로뎍션 환경 수준에 참고할만한 추가적인 인프라 체크리스트 예시가 있습니다.

[Production Readiness Checklist — 링크1]

[terraform-The Production Grade Infrastructure Checklist 유투브 — 링크2]

프로덕션 수준 인프라 모듈

위에서 인프라 체크리스트를 확인했으니 이를 구현하기 위해 재사용 가능한 모듈을 구축하는 모범 사례를 살펴보겠습니다.

1. 소형 모듈

  • 대형 모듈 단점 : 속도가 느림, 안전하지 않음, 위험성이 높음, 이해하기 어려움, 리뷰하기 어려움, 테스트하기 어려움 등
  • 대체 방안 : 소형 모듈로 작성. 이는 클린 코드와 비슷한 내용입니다.

“ 함수의 첫 번째 규칙은 작아야 한다는 것입니다. 함수의 두 번째 규칙은 그보다 더 작아야 한다는 것입니다. — 로버트 C. 마틴”

지금까지 구축한 webserver-cluster 모듈이 커지기 시작했습니다.

여기서는 아래와 같은 3가지 작업을 처리하고 있습니다.

  • 오토스케일링 그룹(ASG)
  • 애플리케이션 로드 밸런서(ALB)
  • Hello, World 애플리케이션

이를 소형 모듈 3개로 리팩터링 해봅니다.

2. 합성 가능한 모듈

  • 이제 각각 한가지 작업을 잘 수행하는 2개의 소형 모듈, asg-rolling-deploy 모듈과 alb 모듈이 있습니다.
  • 이 모듈들을 어떻게 함께 작동시킬 수 있을까요? 즉, 재사용 가능하고 합성 가능한 모듈(composable module)을 어떻게 구축할까요?

“ 한 가지 일을 잘 해내는 프로그램을 여러 개 작성하세요. 그리고 그 프로그램이 함께 작동하게 하세요. 이것이 바로 유닉스 철학입니다. -더그 매클로이”

  • 테라폼 모듈에서 동일한 기본 원칙을 따를 수 있습니다. 모든 것을 입력 변수를 통해 전달하고 모든 것을 출력 변수를 통해 반환하며 간단한 모듈들을 결합해 더 복잡한 모듈을 만들 수 있습니다.
  • modules/cluster/asg-rolling-deploy/variables.tf 에 아래 새로운 입력 변수 4개를 추가합니다.
# subnet_ids 는 asg-rolling-deploy 모듈을 배포할 서브넷을 지정
variable "subnet_ids" {
  description = "The subnet IDs to deploy to"
  type        = list(string)
}
# target_group_arns 과 health_check_type 변수는 
# ASG를 로드 밸런서와 통합하는 방식을 구성
variable "target_group_arns" {
  description = "The ARNs of ELB target groups in which to register Instances"
  type        = list(string)
  default     = []
}variable "health_check_type" {
  description = "The type of health check to perform. Must be one of: EC2, ELB."
  type        = string
  default     = "EC2"
}# user_data 는 사용자 데이터 스크립트를 전달
variable "user_data" {
  description = "The User Data script to run in each Instance at boot"
  type        = string
  default     = null
}
  • modules/cluster/asg-rolling-deploy/outputs.tf 에 유용한 출력 변수 몇 개를 다음과 같이 추가합니다.
  • 보안 그룹에 사용자 정의 규칙을 추가하는 등 새로운 동작을 추가할수 있어서 asg-rolling-deploy 모듈의 재사용성을 높일 수 있습니다.
output "asg_name" {
  value       = aws_autoscaling_group.example.name
  description = "The name of the Auto Scaling Group"
}
output "instance_security_group_id" {
  value       = aws_security_group.instance.id
  description = "The ID of the EC2 Instance Security Group"
}
output "alb_dns_name" {
  value       = aws_lb.example.dns_name
  description = "The domain name of the load balancer"
}
output "alb_http_listener_arn" {
  value       = aws_lb_listener.http.arn
  description = "The ARN of the HTTP listener"
}output "alb_security_group_id" {
  value       = aws_security_group.alb.id
  description = "The ALB Security Group ID"
}
  • 마지막 단계는 asg-rolling-deploy 및 alb 모듈을 사용하여 webserver-cluster 모듈을 ‘Hello, World’ 앱을 배포할 수 있는 hello-world-app 모듈로 변환하는 것입니다.
  • 이를 수행하기 위해 module/services/webserver-cluster의 이름을 module/services/hello-world-app으로 변경합니다.
  • 이때, module/services/hello-world-app/main.tf 에 아래 리소스는 남겨둡니다.
resource "aws_lb_target_group" "asg" {
  name     = var.cluster_name
  port     = var.server_port
  protocol = "HTTP"
  vpc_id   = data.aws_vpc.default.id
  health_check {
    path                = "/"
    protocol            = "HTTP"
    matcher             = "200"
    interval            = 15
    timeout             = 3
    healthy_threshold   = 2
    unhealthy_threshold = 2
  }
}resource "aws_lb_listener_rule" "asg" {
  listener_arn = aws_lb_listener.http.arn
  priority     = 100  condition {
    path_pattern {
      values = ["*"]
    }
  }  action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.asg.arn
  }
}data "terraform_remote_state" "db" {
  backend = "s3"  config = {
    bucket = var.db_remote_state_bucket
    key    = var.db_remote_state_key
    region = "us-east-2"
  }
}data "aws_vpc" "default" {
  default = true
}data "aws_subnets" "default" {
  filter {
    name   = "vpc-id"
    values = [data.aws_vpc.default.id]
  }
}
  • modules/services/hello-world-app/variables.tf 에 아래 변수를 추가합니다.
variable "environment" {
  description = "The name of the environment we're deploying to"
  type        = string
}
  • 전에 작성한 asg-rolling-deploy 모듈을 hello-world-app 모듈에 추가합니다
module "asg" {
  source = "../../cluster/asg-rolling-deploy"
  cluster_name  = "hello-world-${var.environment}"
  ami           = var.ami
  instance_type = var.instance_type  user_data     = templatefile("${path.module}/user-data.sh", {
    server_port = var.server_port
    db_address  = data.terraform_remote_state.db.outputs.address
    db_port     = data.terraform_remote_state.db.outputs.port
    server_text = var.server_text
  })  min_size           = var.min_size
  max_size           = var.max_size
  enable_autoscaling = var.enable_autoscaling  subnet_ids        = data.aws_subnets.default.ids
  target_group_arns = [aws_lb_target_group.asg.arn]
  health_check_type = "ELB"
  
  custom_tags = var.custom_tags
}
  • 그리고 이전에 생성한 alb 모듈을 hello-world-app 모듈에 추가합니다
module "alb" {
  source = "../../networking/alb"
  alb_name   = "hello-world-${var.environment}"
  subnet_ids = data.aws_subnets.default.ids
}
  • environment 입력 변수를 사용해 모든 리소스의 네임스페이스가 hello-world-stage, hello-world-prod 같은 환경을 기준으로 지정되도록 명명 규칙을 시행합니다.
  • 이 코드는 또한 이전에 추가한 새로운 subnet_ids, target_group_arns, health_check_type, and user_data 변수를 적절한 값으로 설정합니다.
  • 다음으로 이 앱에 대한 ALB 대상 그룹 및 리스너 규칙을 구성합니다.
  • name 에서 environment 를 사용하도록 module/services/hello-world-app/main.tf에서 aws_lb_target_group 리소스를 업데이트합니다.
resource "aws_lb_target_group" "asg" {
  name     = "hello-world-${var.environment}"
  port     = var.server_port
  protocol = "HTTP"
  vpc_id   = data.aws_vpc.default.id
  health_check {
    path                = "/"
    protocol            = "HTTP"
    matcher             = "200"
    interval            = 15
    timeout             = 3
    healthy_threshold   = 2
    unhealthy_threshold = 2
  }
}
  • 이제 aws_lb_listener_rule 리소스의 listener_arn 매개 변수가 ALB 모듈의 alb_http_listener_arn 출력을 가리키도록 업데이트합니다.
resource "aws_lb_listener_rule" "asg" {
  listener_arn = module.alb.alb_http_listener_arn
  priority     = 100
  condition {
    path_pattern {
      values = ["*"]
    }
  }  action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.asg.arn
  }
}
  • 마지막으로 asg-rolling-deploy 및 alb 모듈의 중요한 출력을 hello-world-app 모듈의 출력으로 전달합니다.
output "alb_dns_name" {
  value       = module.alb.alb_dns_name
  description = "The domain name of the load balancer"
}
output "asg_name" {
  value       = module.asg.asg_name
  description = "The name of the Auto Scaling Group"
}output "instance_security_group_id" {
  value       = module.asg.instance_security_group_id
  description = "The ID of the EC2 Instance Security Group"
}

3. 테스트 가능한 모듈

  • asg-rolling-deploy, alb, hello-world-app의 세 가지 모듈 형식으로 많은 코드를 작성했습니다. 다음 단계는 코드가 실제로 작동하는지 확인하는 것입니다.
  • examples/asg/main.tf 작성 : asg-rolling-deploy 모듈을 사용하여 크기가 1인 ASG를 배포해봅니다. [실습 코드]

4. 릴리스 가능한 모듈

  • 모듈을 작성하고 테스트한 후 다음 단계는 릴리스(release)하는 것입니다.
  • 테라폼 0.13 validation blocks 은 입력 변수를 체크합니다.

[참고 : 실습 코드 ]

 

 

 

blog migration project

written in 2022.12.4

https://medium.com/techblog-hayleyshim/iac-production-grade-terraform-code-4417b98185e3