Skip to content
cloudemu
Services

§ Documentation

Load Balancer

Emulated load balancers, target groups, listeners, rules, and health checks — driven with the real ELBv2 and cloud LB SDKs

aws ELBazr LBgcp LB

Emulates a managed load balancer — the ELB-style front door that spreads traffic across a pool of backends. You create a load balancer, register targets in a target group, and attach a listener that routes a port to that group; listener rules and per-LB attributes tune how the traffic is steered.

Reach for it when your code provisions load balancers, registers or deregisters targets, or reacts to target health — so you can exercise those paths without a live LB. Because the SDK-compat server speaks the real wire protocol, your production provisioning code runs unchanged against it. Under the hood a load balancer, its target groups, and its listeners are plain named records wired together by ARN.

ProviderServiceSDK-compatDriver
AWSELBv2✓ Liveaws.ELB
AzureLB✓ Liveazure.LB
GCPLB✓ Livegcp.LB

Drive it with the real SDK#

Run cloudemu as a server and point your existing production code at it — the same client, with only the endpoint redirected. Start it with cloudemu serve (or docker run --rm -p 4566:4566 ghcr.io/stackshy/cloudemu), which serves the AWS API on http://localhost:4566:

cloudemu serve   # AWS API on http://localhost:4566

Then point the stock ELBv2 client at that endpoint and create an ALB and a target group exactly as the real client would:

import (
    "github.com/aws/aws-sdk-go-v2/aws"
    elb "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2"
    elbtypes "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2/types"
)

client := elb.NewFromConfig(cfg, func(o *elb.Options) {
    o.BaseEndpoint = aws.String("http://localhost:4566")
})

lb, _ := client.CreateLoadBalancer(ctx, &elb.CreateLoadBalancerInput{
    Name:    aws.String("my-alb"),
    Type:    elbtypes.LoadBalancerTypeEnumApplication,
    Scheme:  elbtypes.LoadBalancerSchemeEnumInternetFacing,
    Subnets: []string{"subnet-a", "subnet-b"},
})

tg, _ := client.CreateTargetGroup(ctx, &elb.CreateTargetGroupInput{
    Name: aws.String("web-targets"), Port: aws.Int32(8080), Protocol: elbtypes.ProtocolEnumHttp,
})

The same pattern works with armnetwork.LoadBalancersClient (Azure) and the GCP forwarding-rule / backend-service REST clients — only the endpoint changes. See the SDK-Compat Server page.

In-process (Go unit tests)#

For Go unit tests written inside cloudemu-aware code, stand the same wire server up in-process with httptest.NewServer and point the client at ts.URL. The Drivers you pass name exactly the drivers this service needs:

import (
    "github.com/stackshy/cloudemu/v2"
    awsserver "github.com/stackshy/cloudemu/v2/server/aws"
)

cloud := cloudemu.NewAWS()
ts := httptest.NewServer(awsserver.New(awsserver.Drivers{ELB: cloud.ELB}))
defer ts.Close()
// point the same elb client at ts.URL instead of the running endpoint

Call the driver directly#

When you don't need to drive a real SDK — for example in cloudemu-only setup code — skip the HTTP hop and call the driver. The load balancer is the entry point; create it, then wire a target group and a listener by ARN:

import lbdriver "github.com/stackshy/cloudemu/v2/services/loadbalancer/driver"

lb, _ := aws.ELB.CreateLoadBalancer(ctx, lbdriver.LBConfig{
    Name: "web-lb", Type: "application", Scheme: "internet-facing",
})

tg, _ := aws.ELB.CreateTargetGroup(ctx, lbdriver.TargetGroupConfig{
    Name: "web-targets", Port: 8080, Protocol: "HTTP",
})

aws.ELB.RegisterTargets(ctx, tg.ARN, []lbdriver.Target{
    {ID: "i-00000001", Port: 8080}, {ID: "i-00000002", Port: 8080},
})

aws.ELB.CreateListener(ctx, lbdriver.ListenerConfig{
    LBARN: lb.ARN, Port: 443, Protocol: "HTTPS", TargetGroupARN: tg.ARN,
})

RegisterTargets takes []lbdriver.Target (an ID plus optional port), and listeners reference the load balancer and target group by their ARN.

Rules, attributes, and target health#

A listener can carry rules that steer requests to different target groups by path or host, and each load balancer has an attribute set you can read and modify. Target health is settable directly so you can assert how your code reacts to a backend going unhealthy:

// Path-based routing rule on a listener.
aws.ELB.CreateRule(ctx, lbdriver.RuleConfig{
    ListenerARN: listener.ARN, Priority: 10,
    Conditions: []lbdriver.RuleCondition{{Field: "path-pattern", Values: []string{"/api/*"}}},
    Actions:    []lbdriver.RuleAction{{Type: "forward", TargetGroupARN: tg.ARN}},
})
rules, _ := aws.ELB.DescribeRules(ctx, listener.ARN)

// Attributes: the typed fields plus an open Extra map for anything AWS adds.
aws.ELB.PutLBAttributes(ctx, lb.ARN, lbdriver.LBAttributes{
    IdleTimeout: 120, DeletionProtection: true,
    Extra: map[string]string{"load_balancing.cross_zone.enabled": "true"},
})
attrs, _ := aws.ELB.GetLBAttributes(ctx, lb.ARN)

// Drive a target unhealthy, then read health back.
aws.ELB.SetTargetHealth(ctx, tg.ARN, "i-00000001", "unhealthy")
health, _ := aws.ELB.DescribeTargetHealth(ctx, tg.ARN)
aws.ELB.DeregisterTargets(ctx, tg.ARN, []lbdriver.Target{{ID: "i-00000002", Port: 8080}})

Behavior & fidelity#

BehaviorWhat happens
Target groups carry health-check configurationHealth status drives which targets are eligible, and SetTargetHealth lets a test flip that state deterministically.
Objects are wired by ARNListeners and rules reference a load balancer and target group by ARN; a target group can back more than one listener.
Attributes keep unknown keysLBAttributes.Extra holds attributes outside the typed set, so a caller reading back its own write gets the right answer even for keys the struct never learned.

SDK-compat — Live#

Real elasticloadbalancingv2, armnetwork.LoadBalancers, and GCP LB clients drive the emulator end-to-end:

ProviderCoverage
AWS ELBv2Load balancers, target groups, listeners, rules, and target health
Azure LBLoad balancers via ARM
GCP LBForwarding rules and backend services via REST

See SDK-Compat for the full per-operation list.

On this page

On this page