Skip to content

3.2 Set Up Load Balancing

The load balancer module (modules/loadbalancer.pkl) creates a Network Load Balancer (NLB) that provides access to the Kubernetes API and Talos API on the control plane nodes. This is deployed as part of formae apply via main.pkl.

Step 1: Configure Load Balancer Variables

Open vars.pkl (or your environment override) and set the NLB parameters:

formae/cluster/aws/vars.pkl
/// Deploy NLB as internal (true) or internet-facing (false).
nlbInternal: Boolean = true

/// Prevent accidental NLB deletion (disable for teardown workflows).
enableDeletionProtection: Boolean = false

/// Distribute NLB traffic across all AZs.
enableCrossZoneLoadBalancing: Boolean = true

Internal NLB (nlbInternal = true) — requires VPN or bastion host to reach the control plane from outside the VPC. Use this for production.

Internet-facing NLB (nlbInternal = false) — the NLB gets a public DNS name. Combine with allowedAdminCidr in the security group to restrict access. Use this for demo/testing:

formae/cluster/aws/envs/demo.pkl
// In envs/demo.pkl
nlbInternal = false
enableDeletionProtection = false
enableCrossZoneLoadBalancing = false    // Single AZ, not needed

Health Check Tuning

formae/cluster/aws/vars.pkl
/// NLB health check interval in seconds.
healthCheckInterval: Int = 10

/// NLB health check timeout in seconds.
healthCheckTimeout: Int = 5

/// Consecutive successful checks before marking target healthy.
healthyThreshold: Int = 2

/// Consecutive failed checks before marking target unhealthy.
unhealthyThreshold: Int = 2

Step 2: Understand the Module

The module is at modules/loadbalancer.pkl. It imports the network and compute modules to reference subnet IDs and instance IDs.

Network Load Balancer

formae/cluster/aws/modules/loadbalancer.pkl
local apiNlb = new loadbalancer.LoadBalancer {
  label = "\(vars.environment)-talos-api-nlb"
  name = "\(vars.environment)-talos-api-nlb"
  scheme = if (vars.nlbInternal) "internal" else "internet-facing"
  type = "network"
  subnets = network.publicSubnetIds
  loadBalancerAttributes {
    new loadbalancer.LoadBalancerAttribute {
      key = "deletion_protection.enabled"
      value = if (vars.enableDeletionProtection) "true" else "false"
    }
    new loadbalancer.LoadBalancerAttribute {
      key = "load_balancing.cross_zone.enabled"
      value = if (vars.enableCrossZoneLoadBalancing) "true" else "false"
    }
  }
  tags = makeTags(new Mapping {
    ["Name"] = "\(vars.environment)-talos-api-nlb"
    ["Purpose"] = "kubernetes-api"
  })
}

Key points:

  • scheme — toggles between "internal" and "internet-facing" based on vars.nlbInternal
  • subnets — placed in public subnets (from network.publicSubnetIds)
  • Attributes are key-value pairs passed as LoadBalancerAttribute objects

Target Groups

Two target groups forward traffic to all control plane instances:

Kubernetes API (port 6443):

formae/cluster/aws/modules/loadbalancer.pkl
local k8sApiTg = new targetgroup.TargetGroup {
  label = "\(vars.environment)-talos-api-tg"
  name = "\(vars.environment)-talos-api-tg"
  port = 6443
  protocol = "TCP"
  vpcId = network.vpcId
  targetType = "instance"
  healthCheckEnabled = true
  healthCheckIntervalSeconds = vars.healthCheckInterval
  healthCheckPort = "6443"
  healthCheckProtocol = "TCP"
  healthCheckTimeoutSeconds = vars.healthCheckTimeout
  healthyThresholdCount = vars.healthyThreshold
  unhealthyThresholdCount = vars.unhealthyThreshold
  targetGroupAttributes {
    new targetgroup.TargetGroupAttribute {
      key = "deregistration_delay.timeout_seconds"
      value = "30"
    }
    new targetgroup.TargetGroupAttribute {
      key = "preserve_client_ip.enabled"
      value = "true"
    }
  }
  targets {
    for (_idx, instId in compute.controlPlaneInstanceIds) {
      new targetgroup.TargetDescription {
        id = instId
        port = 6443
      }
    }
  }
}

Key points:

  • targets — dynamically populated from compute.controlPlaneInstanceIds using a for loop
  • preserve_client_ip.enabled = "true" — the NLB preserves the original source IP
  • deregistration_delay.timeout_seconds = "30" — allows in-flight requests to complete before removing a target

Talos API (port 50000) follows the same pattern but on port 50000.

Listeners

Each listener forwards TCP traffic from the NLB to its target group:

formae/cluster/aws/modules/loadbalancer.pkl
local k8sApiListener = new listener.Listener {
  label = "\(vars.environment)-talos-api-listener"
  loadBalancerArn = apiNlb.res.loadBalancerArn
  port = 6443
  protocol = "TCP"
  defaultActions {
    new listener.Action {
      type = "forward"
      targetGroupArn = k8sApiTg.res.targetGroupArn
    }
  }
}

Step 3: Module Exports

formae/cluster/aws/modules/loadbalancer.pkl
/// NLB DNS name for external access.
nlbDnsName = apiNlb.res.dnsName

/// Kubernetes API endpoint URL.
kubernetesApiEndpoint = "https://\(apiNlb.res.dnsName):6443"

/// Talos API endpoint.
talosApiEndpoint = "\(apiNlb.res.dnsName):50000"

These exported values are used when configuring talosctl and kubectl after deployment:

# Configure talosctl to use the NLB endpoint
talosctl config endpoint <nlbDnsName>

# The kubeconfig will use the NLB DNS as the server URL
# https://<nlbDnsName>:6443

Customisation Summary

What to Change Where Variable
Internal vs internet-facing vars.pkl nlbInternal
Deletion protection vars.pkl enableDeletionProtection
Cross-zone load balancing vars.pkl enableCrossZoneLoadBalancing
Health check interval vars.pkl healthCheckInterval
Health check timeout vars.pkl healthCheckTimeout
Healthy/unhealthy thresholds vars.pkl healthyThreshold, unhealthyThreshold

Warning

The deregistration delay (30s) and client IP preservation are hardcoded in the module. To change these, edit modules/loadbalancer.pkl directly.