Elastic Load Balancer 실습

서비스를 위한 기본 구성 요소 생성

Load Balancer 생성

Load Balancer는 여러 인스턴스에 트래픽을 분배해주기 위해서 반드시 필요한 리소스입니다. EC2 위에 올라가 있는 대부분의 서비스는 LB를 앞 단에 두는 것이 보통인데, LB의 경우에는 설정할 것이 상당히 많아 코드로 한 번 정리해놓으면 편리하게 생성/관리하실 수 있습니다.

실습에서 생성할 리소스는 아래와 같습니다.

  • Load Balancer

  • Target Group

    • https(443) : redirect to instance

    • http(80) : redirect to 443

  • Route53 Record

    • A record for ELB

Module 구성

보통 서비스를 위한 리소스는 환경별로 동일하게 생성하는 경우가 많습니다. 따라서 같은 코드를 변수만 바꿔서 사용할 수 있도록 Module을 이용하여 생성합니다.

Module을 사용할 때는 최대한 고유 명사를 없애고 변수처리하는 것이 좋습니다. 고유 명사를 변수로 대체하면 이후에 다른 서비스를

_module 폴더의 구조는 아래와 같습니다.

  • outputs.tf : 모듈내에서의 output.

  • service.tf : 모든 리소스 생성 코드가 포함된 파일

  • var_lb.tf : Load Balancer 관련 변수 정의 파일

  • var_sg.tf : Security Group 관련 변수 정의 파일

  • variables.tf : 기타 변수 정의 파일

$ tree
.
└── hello
    ├── outputs.tf
    ├── service.tf
    ├── var_lb.tf
    ├── var_sg.tf
    └── variables.tf 

실제로 리소스를 생성하는 부분인 service.tf 를 확인해보도록 하겠습니다.

vim terraform/services/hello/_module/hello/service.tf
############ Security Group For External LB
resource "aws_security_group" "external_lb" {
  name        = "${var.service_name}-${var.vpc_name}-ext"
  description = "${var.service_name} external LB SG"
  vpc_id      = var.target_vpc

  # Only allow access from IPs or SGs you specifiy in ext_lb_ingress_cidrs variables
  # If you don't want to use HTTPS then remove this block
  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = var.ext_lb_ingress_cidrs
    description = "External service https port"
  }


  # Allow 80 port 
  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = var.ext_lb_ingress_cidrs
    description = "External service http port"
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["10.0.0.0/8"]
    description = "Internal outbound any traffic"
  }

  tags = var.sg_variables.external_lb.tags[var.shard_id]
}


################## Security Group for EC2
resource "aws_security_group" "ec2" {
  name        = "${var.service_name}-${var.vpc_name}"
  description = "${var.service_name} Instance Security Group"
  vpc_id      = var.target_vpc

  # Service Port will be passed via variable.
  ingress {
    from_port = var.service_port
    to_port   = var.service_port 
    protocol  = "tcp"
 
    # Allow external LB Only for ec2 instance
    security_groups = [
        aws_security_group.external_lb.id,
    ]

    description = "Port Open for ${var.service_name}"
  }

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

  tags = {
    Name  = "${var.service_name}-${var.vpc_name}-sg"
    app   = var.service_name 
    stack = var.vpc_name
  }
}

#################### External ALB
resource "aws_lb" "external" {
  name     = "${var.service_name}-${var.shard_id}-ext"
  subnets  = var.public_subnets
  internal = false

  # For external LB,
  # Home SG (Includes Office IPs) could be added if this service is internal service.
  security_groups = [
    aws_security_group.external_lb.id,
    var.home_sg,
  ]

  # For HTTP service, application LB is recommended.
  # You could use other load_balancer_type if you want.
  load_balancer_type = "application"

  tags = var.lb_variables.external_lb.tags[var.shard_id]
}

#################### External LB Target Group 
resource "aws_lb_target_group" "external" {
  name                 = "${var.service_name}-${var.shard_id}-ext"
  port                 = var.service_port
  protocol             = "HTTP"
  vpc_id               = var.target_vpc
  slow_start           = var.lb_variables.target_group_slow_start[var.shard_id]
  deregistration_delay = var.lb_variables.target_group_deregistration_delay[var.shard_id]
  
  # Change the health check setting 
  health_check {
    interval            = 15
    port                = var.healthcheck_port
    path                = "/"
    timeout             = 3
    healthy_threshold   = 3
    unhealthy_threshold = 2
    matcher             = "200"
  }

  tags = var.lb_variables.external_lb_tg.tags[var.shard_id]

}


#################### Listener for HTTPS service
resource "aws_lb_listener" "external_443" {
  load_balancer_arn = aws_lb.external.arn
  port              = "443"
  protocol          = "HTTPS"

  # If you want to use HTTPS, then you need to add certificate_arn here.
  certificate_arn = var.acm_external_ssl_certificate_arn

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


#################### Listener for HTTP service
resource "aws_lb_listener" "external_80" {
  load_balancer_arn = aws_lb.external.arn
  port              = "80"
  protocol          = "HTTP"
  
  # This is for redirect 80. 
  # This means that it will only allow HTTPS(443) traffic
  default_action {
    type = "redirect"

    redirect {
      port        = "443"
      protocol    = "HTTPS"
      # 301 -> Permanant Movement
      status_code = "HTTP_301"
    }
  }
}

#################### Route53 Record
resource "aws_route53_record" "external_dns" {
  zone_id        = var.route53_external_zone_id
  name           = var.domain_name
  type           = "A"
  set_identifier = var.aws_region

  latency_routing_policy {
    region = var.aws_region
  }

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

환경 구분

_module 폴더와 같은 path에 환경별로 폴더를 만드시면 됩니다. 아래는 dev와 prod를 위한 예시입니다. 자세히 보시면 아시겠지만, 각 환경 내의 파일 구성은 완전히 동일합니다. (단, 변수 값은 당연히 다릅니다.) 이렇게 구성하면, 새로운 환경이 필요한 경우 손쉽게 동일한 구성의 리소스를 생성하실 수 있습니다.

.
├── _module
│   └── hello
│       ├── outputs.tf
│       ├── service.tf
│       ├── var_lb.tf
│       ├── var_sg.tf
│       └── variables.tf
├── dayoned_apnortheast2
│   ├── backend.tf
│   ├── output.tf -> ../output.tf
│   ├── provider.tf -> ../provider.tf
│   ├── remote_state.tf
│   ├── service.tf
│   ├── terraform.tfvars
│   ├── var_global.tf -> ../../../variables/var_global.tf
│   ├── var_lb.tf -> ../variables/var_lb.tf
│   ├── var_route53.tf -> ../../../variables/var_route53.tf
│   ├── var_sg.tf -> ../variables/var_sg.tf
│   └── variables.tf -> ../variables.tf
├── dayonep_apnortheast2
│   ├── backend.tf
│   ├── output.tf -> ../output.tf
│   ├── provider.tf -> ../provider.tf
│   ├── remote_state.tf
│   ├── service.tf
│   ├── terraform.tfvars
│   ├── var_global.tf -> ../../../variables/var_global.tf
│   ├── var_lb.tf -> ../variables/var_lb.tf
│   ├── var_route53.tf -> ../../../variables/var_route53.tf
│   ├── var_sg.tf -> ../variables/var_sg.tf
│   └── variables.tf -> ../variables.tf
├── output.tf
├── provider.tf
├── variables
│   ├── var_lb.tf
│   └── var_sg.tf
└── variables.tf

이 중에서 모듈을 사용하는 service.tf 파일을 살펴보도록 하겠습니다.

vim terraform/services/hello/dayonep_apnortheast2/service.tf
# Use module for service
module "hello" {
  source = "../_module/hello"
  
  # Name of service
  service_name              = "hello"

  # Port for service and healthcheck
  service_port              = 8080
  healthcheck_port          = 8080
  
  # VPC Information via remote_state
  shard_id                  = data.terraform_remote_state.vpc.outputs.shard_id
  public_subnets            = data.terraform_remote_state.vpc.outputs.public_subnets
  private_subnets           = data.terraform_remote_state.vpc.outputs.private_subnets
  aws_region                = data.terraform_remote_state.vpc.outputs.aws_region
  vpc_cidr_numeral          = data.terraform_remote_state.vpc.outputs.cidr_numeral
  route53_internal_domain   = data.terraform_remote_state.vpc.outputs.route53_internal_domain
  route53_internal_zone_id  = data.terraform_remote_state.vpc.outputs.route53_internal_zone_id
  target_vpc                = data.terraform_remote_state.vpc.outputs.vpc_id
  vpc_name                  = data.terraform_remote_state.vpc.outputs.vpc_name
  billing_tag               = data.terraform_remote_state.vpc.outputs.billing_tag

  # Domain Name 
  # This will be the prefix of record 
  # e.g) hello.dayonedevops.com
  domain_name               = "hello"

  # Route53 variables
  acm_external_ssl_certificate_arn  = var.r53_variables.prod.star_dayonedevops_com_acm_arn_apnortheast2
  route53_external_zone_id          = var.r53_variables.prod.dayonedevops_com_zone_id

  # Resource LoadBalancer variables
  lb_variables                      = var.lb_variables

  # Security Group variables
  sg_variables                      = var.sg_variables  

  # Home Security Group via remote_state
  home_sg                       = data.terraform_remote_state.vpc.outputs.aws_security_group_home_id

  # CIDR for external LB
  # Control allowed IP for external LB 
  ext_lb_ingress_cidrs = [
    "0.0.0.0/0"
  ]
}

생성이 완료된 후 Autoscaling만 target group에 붙이면 실제 서비스를 https://hello.dayonedevops.com 를 통해 운영하실 수 있습니다.

Last updated