Skip to main content

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.

note

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"
},
}
}