Embedded Cluster Load Balancer
If you deploy an embedded cluster you have the option to use a load balancer for ingress. The load balancer can direct requests to the kots admin UI as well as the Credo AI application with the appropriate configuration.
Please note that these load balancer requirements for running in High Availability (ha) mode for the kubernetes control plane are relevant when setting up a load balancer in front of the Credo AI application or the KOTS admin UI console.
https://kurl.sh/docs/install-with-kurl/system-requirements#load-balancers
Credo AI supports using the bundled Contour application and HTTPProxy resources for exposing the application and optional bundled Minio object storage endpoints on an embedded cluster.
As such, we recommend using a L4 load balancer (NLB) as it can forward traffic on port 443 to the VM instance IP addresses and allow TLS to be terminated at the cluster.
The KOTS admin UI console similarly terminates TLS and can be configured with a hostname and TLS certs. If access through a load balancer is desired configure the backends listeners with port 8800.
If you prefer a L7 application load balancer (ALB), the example terraform configuration below can terminate TLS at the LB and forward traffic to the HTTPProxy endpoint on port 80.
locals {
  # hostnams and dns
  credoai_hostname    = "credoai"
  kots_admin_hostname = "kots"
  domain_name         = "example.com"
  kots_admin_fqdn     = "${local.kots_admin_hostname}.${local.domain_name}"
  route53_zone_id     = "<route53-dns-zone-id>" # for the domain name
  # compute and vpc infrastructure
  instance_1_private_ip      = "<first-vm-instance-ip>"
  instance_2_private_ip      = "<second-vm-instance-ip>"
  vpc_id                     = "<vpc-id>"
  subnet_1_id                = "<first-subnet-id>"
  subnet_2_id                = "<second-subnet-id>"
  subnet_3_id                = "<third-subnet-id>"
  instance_security_group_id = "<instance-security-group-id>" # this security group should allow ingress from the lb
}
module "alb" {
  source = "terraform-aws-modules/alb/aws"
  name                       = "credoai-example"
  name_prefix                = null
  vpc_id                     = local.vpc_id
  subnets                    = [local.subnet_1_id, local.subnet_2_id, local.subnet_3_id]
  drop_invalid_header_fields = false
  enable_deletion_protection = false
  preserve_host_header       = true # critical for contour httpproxy host based routing
  create_security_group      = true
  security_groups            = [local.instance_security_group_id]
  zone_id                    = local.route53_zone_id
  # Security Group
  security_group_ingress_rules = {
    all_http = {
      from_port   = 80
      to_port     = 80
      ip_protocol = "tcp"
      description = "HTTP web traffic"
      cidr_ipv4   = "0.0.0.0/0"
    }
    all_https = {
      from_port   = 443
      to_port     = 443
      ip_protocol = "tcp"
      description = "HTTPS web traffic"
      cidr_ipv4   = "0.0.0.0/0"
    }
  }
  security_group_egress_rules = {
    all = {
      ip_protocol = "-1"
      cidr_ipv4   = "10.0.0.0/16" # this will allow traffic from the lb to any host on a private VPC with typical 10.0.0.0/16 cidr block
    }
  }
  # Listeners
  listeners = {
    http-https-redirect = {
      port     = 80
      protocol = "HTTP"
      redirect = {
        port        = "443"
        protocol    = "HTTPS"
        status_code = "HTTP_301"
      }
    }
    https = {
      port            = 443
      protocol        = "HTTPS"
      certificate_arn = "<acm-certificate-arn>"
      ssl_policy      = "ELBSecurityPolicy-TLS13-1-2-2021-06"
      # by default all requests go to the VM instance target groups
      weighted_forward = {
        target_groups = {
          instance_1 = {
            target_group_key = "tg_1"
            weight           = 50
          },
          instance_2 = {
            target_group_key = "tg_2"
            weight           = 50
          },
        }
      }
      # define a host name routing rule for the kots admin UI
      rules = {
        kots = {
          actions = [
            {
              type = "weighted-forward"
              target_groups = [
                {
                  target_group_key = "tg_kots_1"
                  weight           = 50
                },
                {
                  target_group_key = "tg_kots_2"
                  weight           = 50
                },
              ]
            }
          ]
          conditions = [
            {
              host_header = {
                values = [local.kots_admin_fqdn]
              }
            }
          ]
        }
      }
    }
  }
  target_groups = {
    tg_1 = {
      target_id        = local.instance_1_private_ip
      name             = "instance-1"
      ip_address_type  = "ipv4"
      protocol         = "HTTP"
      protocol_version = "HTTP2"
      port             = 80
      target_type      = "ip"
      health_check = {
        enabled  = true
        matcher  = "404" # no default backend that gives us a better status
        path     = "/"
        port     = "traffic-port"
        protocol = "HTTP"
      }
    },
    tg_2 = {
      target_id        = local.instance_2_private_ip
      name             = "instance-2"
      ip_address_type  = "ipv4"
      protocol         = "HTTP"
      protocol_version = "HTTP2"
      port             = 80
      target_type      = "ip"
      health_check = {
        enabled  = true
        matcher  = "404" # no default backend that gives us a better status
        path     = "/"
        port     = "traffic-port"
        protocol = "HTTP"
      }
    },
    tg_kots_1 = {
      target_id        = local.instance_1_private_ip
      name             = "kots-instance-1"
      ip_address_type  = "ipv4"
      protocol         = "HTTPS"
      protocol_version = "HTTP1" # does not support HTTP2
      port             = 8800
      target_type      = "ip"
      health_check = {
        enabled  = true
        matcher  = "200-299,300-399" # any success or redirect
        path     = "/"
        port     = "traffic-port"
        protocol = "HTTPS"
      }
    },
    tg_kots_2 = {
      target_id        = local.instance_2_private_ip
      name             = "kots-instance-2"
      ip_address_type  = "ipv4"
      protocol         = "HTTPS"
      protocol_version = "HTTP1" # does not support HTTP2
      port             = 8800
      target_type      = "ip"
      health_check = {
        enabled  = true
        matcher  = "200-299,300-399" # any success or redirect
        path     = "/"
        port     = "traffic-port"
        protocol = "HTTPS"
      }
    }
  }
  route53_records = {
    "credoai" = {
      zone_id = local.route53_zone_id
      name    = local.credoai_hostname
      type    = "A"
    },
    "kots" = {
      zone_id = local.route53_zone_id
      name    = local.kots_admin_hostname
      type    = "A"
    },
  }
}