OCI Load Balancer: SSL Termination, Path-Based Routing, and Backend Health with Terraform

The OCI Application Load Balancer operates at layer 7 and handles everything the Network Load Balancer does not: TLS termination, HTTP path routing, header transformation, WAF integration, SSL cipher policy enforcement, and session persistence. This post covers building a production load balancer configuration in Terraform with SSL termination using OCI Certificates, path-based routing to multiple backend sets, and custom health check configuration.

Step 1: Load Balancer Provisioning

resource "oci_load_balancer_load_balancer" "production_lb" {
  compartment_id = var.compartment_id
  display_name   = "production-application-lb"
  shape          = "flexible"

  shape_details {
    minimum_bandwidth_in_mbps = 100
    maximum_bandwidth_in_mbps = 1000
  }

  is_private             = false
  subnet_ids             = [var.public_subnet_id]
  network_security_group_ids = [var.lb_nsg_id]

  defined_tags = { "Operations.Environment" = "production", "Operations.ManagedBy" = "terraform" }
}

output "lb_public_ip" {
  value = oci_load_balancer_load_balancer.production_lb.ip_addresses[0].ip_address
}

Step 2: Backend Sets

resource "oci_load_balancer_backend_set" "orders_api" {
  load_balancer_id = oci_load_balancer_load_balancer.production_lb.id
  name             = "orders-api-backend-set"
  policy           = "ROUND_ROBIN"

  health_checker {
    protocol            = "HTTP"
    url_path            = "/health"
    port                = 8080
    return_code         = 200
    interval_ms         = 10000
    timeout_in_millis   = 3000
    retries             = 3

    # Only consider backend healthy on stable response
    is_force_plain_text = true
  }

  session_persistence_configuration {
    cookie_name         = "orders-session"
    disable_fallback    = false
  }
}

resource "oci_load_balancer_backend" "orders_api_1" {
  load_balancer_id = oci_load_balancer_load_balancer.production_lb.id
  backendset_name  = oci_load_balancer_backend_set.orders_api.name
  ip_address       = var.orders_api_instance_1_ip
  port             = 8080
  weight           = 50
  backup           = false
  drain            = false
  offline          = false
}

resource "oci_load_balancer_backend" "orders_api_2" {
  load_balancer_id = oci_load_balancer_load_balancer.production_lb.id
  backendset_name  = oci_load_balancer_backend_set.orders_api.name
  ip_address       = var.orders_api_instance_2_ip
  port             = 8080
  weight           = 50
  backup           = false
  drain            = false
  offline          = false
}

resource "oci_load_balancer_backend_set" "payments_api" {
  load_balancer_id = oci_load_balancer_load_balancer.production_lb.id
  name             = "payments-api-backend-set"
  policy           = "LEAST_CONNECTIONS"

  health_checker {
    protocol            = "HTTP"
    url_path            = "/health"
    port                = 8081
    return_code         = 200
    interval_ms         = 10000
    timeout_in_millis   = 3000
    retries             = 3
  }
}

Step 3: HTTPS Listener with Path Routing

resource "oci_load_balancer_cipher_suite" "modern_tls" {
  load_balancer_id = oci_load_balancer_load_balancer.production_lb.id
  name             = "modern-tls-cipher-suite"

  ciphers = [
    "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
    "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
    "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
    "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
    "TLS_AES_128_GCM_SHA256",
    "TLS_AES_256_GCM_SHA384"
  ]
}

resource "oci_load_balancer_rule_set" "api_path_routing" {
  load_balancer_id = oci_load_balancer_load_balancer.production_lb.id
  name             = "api-path-routing"

  items {
    action = "REDIRECT"
    conditions {
      attribute_name  = "PATH"
      attribute_value = "/api/orders"
      operator        = "PREFIX_MATCH"
    }
    redirect_uri {
      protocol = "HTTPS"
      host     = "{host}"
      port     = "443"
      path     = "/api/v2/orders"
      query    = "{query}"
    }
    response_code = 308
  }
}

resource "oci_load_balancer_listener" "https" {
  load_balancer_id         = oci_load_balancer_load_balancer.production_lb.id
  name                     = "https-listener"
  default_backend_set_name = oci_load_balancer_backend_set.orders_api.name
  port                     = 443
  protocol                 = "HTTP"
  rule_set_names           = [oci_load_balancer_rule_set.api_path_routing.name]

  ssl_configuration {
    certificate_ids         = [var.tls_certificate_id]
    server_order_preference = "ENABLED"
    cipher_suite_name       = oci_load_balancer_cipher_suite.modern_tls.name
    protocols               = ["TLSv1.2", "TLSv1.3"]
    verify_peer_certificate = false
  }

  connection_configuration {
    idle_timeout_in_seconds = 60
  }
}

resource "oci_load_balancer_listener" "http_redirect" {
  load_balancer_id         = oci_load_balancer_load_balancer.production_lb.id
  name                     = "http-to-https-redirect"
  default_backend_set_name = oci_load_balancer_backend_set.orders_api.name
  port                     = 80
  protocol                 = "HTTP"
  rule_set_names           = ["http-to-https-redirect-ruleset"]
}

Step 4: Unhealthy Backend Alarm

resource "oci_monitoring_alarm" "lb_unhealthy_backend" {
  compartment_id        = var.compartment_id
  display_name          = "lb-unhealthy-backend"
  is_enabled            = true
  metric_compartment_id = var.compartment_id
  namespace             = "oci_lbaas"
  query                 = "UnHealthyBackendServers[5m]{loadBalancerId = 'LB_OCID'}.max() > 0"
  severity              = "CRITICAL"
  pending_duration      = "PT5M"
  destinations          = [var.ops_notification_topic_id]
  body                  = "One or more backends on the production load balancer are failing health checks. Check application health endpoints and instance connectivity."
}

Operational Notes

Set the health check interval to 10 seconds and retries to 3 for production backends. This marks a backend unhealthy after 30 seconds of failures and removes it from rotation. Too aggressive an interval generates a high volume of health check requests to your backends. Too lenient an interval means unhealthy backends serve client requests for too long before being removed. 10 seconds with 3 retries is a good production balance.

Use OCI Certificates Service to manage the TLS certificate rather than uploading the certificate directly to the load balancer. When the certificate auto-renews in Certificates Service, the load balancer picks up the new version automatically on the next TLS handshake without any configuration change. Certificates uploaded directly to the load balancer require a manual update when they expire.

Regards,
Osama

#OCI #OracleCloud #LoadBalancer #SSL #TLS #Terraform #IaC #TechBlog #Oracle #PlatformEngineering #CloudNetworking #OracleCloudInfrastructure #PathRouting #HealthCheck #CipherSuite #SessionPersistence #Certificates #HighAvailability #HTTPS #WebTraffic

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.