Search code examples
amazon-web-servicesterraformamazon-ecsterraform-provider-awsaws-application-load-balancer

In ecs blue green code deploy, how should I handle routing traffic to https?


I'm using terraform to configure ECS and CI/CD.

There are two target groups, blue and green:

resource "aws_lb_target_group" "target_group_blue" {
  name             = "${local.resource_id}-blue"
  port             = var.target_group_port
  protocol_version = var.target_group_protocol_version
  protocol         = var.target_group_protocol
  vpc_id           = var.vpc_id
  target_type      = "ip"

  health_check {
    enabled             = true
    path                = var.healthcheck_uri
    interval            = var.healthcheck_interval
    protocol            = var.target_group_protocol
    healthy_threshold   = 5
    unhealthy_threshold = 2
    timeout             = 20
  }

  tags = local.tags
}

resource "aws_lb_target_group" "target_group_green" {
  name             = "${local.resource_id}-green"
  port             = var.target_group_port
  protocol_version = var.target_group_protocol_version
  protocol         = var.target_group_protocol
  vpc_id           = var.vpc_id
  target_type      = "ip"

  health_check {
    enabled             = true
    path                = var.healthcheck_uri
    interval            = var.healthcheck_interval
    protocol            = var.target_group_protocol
    healthy_threshold   = 5
    unhealthy_threshold = 2
    timeout             = 20
  }

  tags = local.tags
}

The listener is configured as follows. Ports 80 and 443 to use for production deployment, and port 8080 to use for testing traffic.

resource "aws_lb_listener" "http_listener" {
  load_balancer_arn = aws_lb.loadbalancer.arn
  port              = "80"
  protocol          = "HTTP"

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

  lifecycle {
    ignore_changes = [default_action]
  }

  tags = local.tags
}

resource "aws_lb_listener" "http_test_listener" {
  load_balancer_arn = aws_lb.loadbalancer.arn
  port              = "8080"
  protocol          = "HTTP"

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

  lifecycle {
    ignore_changes = [default_action]
  }

  tags = local.tags
}

resource "aws_lb_listener" "https_listener" {
  load_balancer_arn = aws_lb.loadbalancer.arn
  port              = "443"
  protocol          = "HTTPS"
  ssl_policy        = "ELBSecurityPolicy-2016-08"
  certificate_arn   = var.certificate_arn

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

  tags = local.tags
}

The code deploy group is as follows.

resource "aws_codedeploy_deployment_group" "deployment_group" {
  app_name               = aws_codedeploy_app.deploy.name
  deployment_config_name = aws_codedeploy_deployment_config.config_deploy.deployment_config_name
  deployment_group_name  = local.resource_id
  service_role_arn       = aws_iam_role.codedeploy_role.arn

  auto_rollback_configuration {
    enabled = true
    events  = ["DEPLOYMENT_FAILURE"]
  }

  blue_green_deployment_config {
    deployment_ready_option {
      action_on_timeout    = "CONTINUE_DEPLOYMENT"
      wait_time_in_minutes = 0
    }

    terminate_blue_instances_on_deployment_success {
      action                           = "TERMINATE"
      termination_wait_time_in_minutes = 5
    }
  }

  deployment_style {
    deployment_option = "WITH_TRAFFIC_CONTROL"
    deployment_type   = "BLUE_GREEN"
  }

  ecs_service {
    cluster_name = aws_ecs_cluster.ecs_cluster.name
    service_name = aws_ecs_service.ecs_service.name
  }

  load_balancer_info {
    target_group_pair_info {
      prod_traffic_route {
        listener_arns = [aws_lb_listener.http_listener.arn]
      }

      test_traffic_route {
        listener_arns = [aws_lb_listener.http_test_listener.arn]
      }

      target_group {
        name = aws_lb_target_group.target_group_blue.name
      }

      target_group {
        name = aws_lb_target_group.target_group_green.name
      }
    }
  }
}

This way, the blue-green targetgroup replacement works fine for the 80 port listener. However, targetgroup replacement does not occur for the 443 listener.

Whenever deploy, blue-green is applied to both 80 and 443 listeners, I want it to work without problems on https domain endpoint.

Are there any recommended best practices for this?


Solution

  • The listener_arns setting in CodeDeploy is a list. So you could be adding both listeners to that:

          prod_traffic_route {
            listener_arns = [aws_lb_listener.http_listener.arn,
                             aws_lb_listener.https_listener.arn]
          }
    

    Then both would be switched over to the new deployment at the same time.


    However, you asked about "best practice", and the best practice for serving HTTPS from the load balancer is to always serve HTTPS. In other words: don't ever allow insecure traffic. If insecure traffic comes in, convert it to secure traffic.

    To do that you would configure the HTTP listener to simply issue a redirect to your HTTPS listener, like so:

    resource "aws_lb_listener" "http_listener" {
      load_balancer_arn = aws_lb.loadbalancer.arn
      port              = "80"
      protocol          = "HTTP"
    
      default_action {
        type = "redirect"
    
        redirect {
          port        = "443"
          protocol    = "HTTPS"
          status_code = "HTTP_301"
        }
      }
    }
    

    Now any requests to your HTTP listener will receive a redirect response that sends them over to the HTTPS listener. After configuring that, you would only need to register the HTTPS listener with CodeDeploy:

          prod_traffic_route {
            listener_arns = [aws_lb_listener.https_listener.arn]
          }