1. Docs
  2. Pulumi IaC
  3. Clouds
  4. AWS
  5. Guides
  6. ELB

AWS Elastic Load Balancing (ELB)

    Elastic Load Balancing (ELB) automatically distributes incoming application traffic across multiple targets, such as Amazon EC2 instances, containers, IP addresses, and Lambda Functions. It can handle the varying load of your application traffic in a single Availability Zone or across multiple Availability Zones.

    Overview

    Pulumi Crosswalk for AWS ELB provides easy APIs for provisioning Application and Network Load Balancers, and integrates with functionality for other services, including API Gateway, Elastic Container Service (ECS), Lambda, and VPC, to provide configurable network accessibility to the different kinds of compute you will run inside of AWS.

    Elastic Load Balancing offers multiple types of load balancers that all feature the high availability, automatic scaling, and robust security necessary to make your applications fault tolerant:

    • Network Load Balancer (NLB) is best suited for load balancing of Transmission Control Protocol (TCP) and Transport Layer Security (TLS) traffic where extreme performance is required. Operating at the connection level (Layer 4), Network Load Balancer routes traffic to targets within Amazon Virtual Private Cloud (Amazon VPC) and is capable of handling millions of requests per second while maintaining ultra-low latencies. Network Load Balancer is also optimized to handle sudden and volatile traffic patterns.

    • Application Load Balancer (ALB) is best suited for load balancing of HTTP and HTTPS traffic and provides advanced request routing targeted at the delivery of modern application architectures, including microservices, containers, and HTTP/2 traffic. Operating at the individual request level (Layer 7), Application Load Balancer routes traffic to targets within Amazon Virtual Private Cloud (Amazon VPC) based on the content of the request.

    Each kind of load balancer is represented by a class in the awsx.lb module:

    • NetworkLoadBalancer is used for NLBs
    • ApplicationLoadBalancer is used for ALBs.

    These types are similar and support many of the same scenarios. Most examples show using ALBs, however changing to an NLB is usually as simple as swapping out this class. Any differences will be noted below.

    Creating a Load Balancer

    To create a new load balancer, allocate an instance of its class. In addition to creating the load balancer itself, we must also create a listener to let traffic reach it:

    "use strict";
    const awsx = require("@pulumi/awsx");
    
    // Create a load balancer in the default VPC listening on port 80.
    const alb = new awsx.lb.ApplicationLoadBalancer("lb", {
        listener: {
            port: 80,
        },
    });
    
    // Export the resulting URL so that it's easy to access.
    exports.endpoint = alb.loadBalancer.dnsName;
    
    import * as awsx from "@pulumi/awsx";
    
    // Create a load balancer in the default VPC listening on port 80.
    const alb = new awsx.lb.ApplicationLoadBalancer("lb", {
        listener: {
            port: 80,
        },
    });
    
    // Export the resulting URL so that it's easy to access.
    export const endpoint = alb.loadBalancer.dnsName;
    
    import pulumi
    import pulumi_awsx as awsx
    
    # Create a load balancer in the default VPC listening on port 80.
    alb = awsx.lb.ApplicationLoadBalancer(
        "lb",
        awsx.lb.ApplicationLoadBalancerArgs(
            listener=awsx.lb.ListenerArgs(
                port=80,
            ),
        ),
    )
    
    # Export the resulting URL so that it's easy to access.
    pulumi.export("endpoint", alb.load_balancer.dns_name)
    
    package main
    
    import (
    	"github.com/pulumi/pulumi-awsx/sdk/v2/go/awsx/lb"
    	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
    )
    
    func main() {
    	pulumi.Run(func(ctx *pulumi.Context) error {
    
    		// Create a load balancer in the default VPC listening on port 80.
    		alb, err := lb.NewApplicationLoadBalancer(ctx, "lb", &lb.ApplicationLoadBalancerArgs{
    			Listener: &lb.ListenerArgs{
    				Port: pulumi.Int(80),
    			},
    		})
    		if err != nil {
    			return err
    		}
    
    		// Export the resulting URL so that it's easy to access.
    		ctx.Export("endpoint", alb.LoadBalancer.DnsName())
    		return nil
    	})
    }
    
    using Pulumi;
    using Awsx = Pulumi.Awsx;
    using System.Collections.Generic;
    
    return await Deployment.RunAsync(() =>
    {
       // Create a load balancer in the default VPC listening on port 80.
       var alb = new Awsx.Lb.ApplicationLoadBalancer("lb", new()
       {
            Listener = new()
            {
                Port = 80,
            },
       });
    
        // Export the resulting URL so that it's easy to access.
        return new Dictionary<string, object?>
        {
            ["endpoint"] = alb.LoadBalancer.Apply(lb => lb.DnsName),
        };
    });
    
    package myproject;
    
    import com.pulumi.Pulumi;
    import com.pulumi.awsx.lb.ApplicationLoadBalancer;
    import com.pulumi.awsx.lb.ApplicationLoadBalancerArgs;
    import com.pulumi.awsx.lb.inputs.ListenerArgs;
    
    public class App {
        public static void main(String[] args) {
            Pulumi.run(ctx -> {
    
                // Create a load balancer in the default VPC listening on port 80.
                var alb = new ApplicationLoadBalancer("lb", ApplicationLoadBalancerArgs.builder()
                    .listener(ListenerArgs.builder()
                        .port(80)
                        .build())
                    .build());
    
                // Export the resulting URL so that it's easy to access.
                ctx.export("endpoint", alb.loadBalancer().apply(loadBalancer -> loadBalancer.dnsName()));
            });
        }
    }
    
    name: awsx-elb-web-listener-yaml
    runtime: yaml
    description: An example that deploys an ApplicationLoadBalancer listening on port 80.
    
    resources:
      # Create a load balancer in the default VPC listening on port 80.
      alb:
        type: awsx:lb:ApplicationLoadBalancer
        properties:
          listener:
            port: 80
    
    outputs:
      # Export the resulting URL so that it's easy to access.
      endpoint: ${alb.loadBalancer.dnsName}
    

    This load balancer listens on port 80, in our account’s per-region default VPC, using its public subnets, thereby exposing it to the Internet. See below for instructions on how to make your load balancer private or to run in a custom VPC.

    There are a number of additional properties you may set:

    • enableHttp2: Set to true to enable HTTP/2 traffic on your ALB. HTTP/2 is not supported for NLBs.

    • enableDeletionProtection: Set to true to disable deletion of the resource. This can be helpful to avoid accidentally deleting a long-lived, but auto-generated, load balancer URL.

    • idleTimeout: The time in seconds a connection is permitted to be idle before being severed. The default is 60.

    • tags: Can be used to tag your load balancer with metadata about its purpose, for reporting or compliance.

    For the load balancer to do anything useful, we must also specify a target that traffic will be routed to. The target could be an EC2 instance, ECS service, or anything with an IP address, for instance. We will also have to configure SecurityGroups to let traffic flow inside of our VPC on the correct ports.

    Load Balancing EC2 Instance Targets

    To target an EC2 instance with your load balancer, you must do the following:

    1. Open ingress traffic to your load balancer. Explicitly needed for NLB, but not ALB.
    2. Open egress traffic from your EC2 instance to your load balancer (for health checks).
    3. Ensure the security group for your load balancer at least contains the ingress rule from (1).
    4. Create the EC2 instance(s) in the same VPC and ensure the security group contains the egress rule (2).
    5. Attach your load balancer’s target group to the desired EC2 instance(s).

    Aside from those three steps, the code and capabilities of the load balancer are the same as shown above.

    Note that ALBs automatically open ingress traffic to the ports listened on, whereas NLBs do not.

    Here is an example that creates an EC2 instance per availability zone, running a simple Ubuntu web server:

    "use strict";
    const aws = require("@pulumi/aws");
    const awsx = require("@pulumi/awsx");
    
    // Get the default VPC for the current region.
    const vpc = new awsx.ec2.DefaultVpc("default-vpc");
    
    // Create a security group to allow traffic to and from the virtual machine.
    const securityGroup = new aws.ec2.SecurityGroup("web-sg", {
        vpcId: vpc.vpcId,
        ingress: [
            {
                protocol: "tcp",
                fromPort: 80,
                toPort: 80,
                cidrBlocks: ["0.0.0.0/0"],
            },
        ],
        egress: [
            {
                protocol: "-1",
                fromPort: 0,
                toPort: 0,
                cidrBlocks: ["0.0.0.0/0"],
            },
        ],
    });
    
    // Create an ALB in the default VPC listening on port 80.
    const alb = new awsx.lb.ApplicationLoadBalancer("web-traffic", {
        listener: {
            port: 80,
        },
        securityGroups: [securityGroup.id],
    });
    
    vpc.publicSubnetIds.apply(subnetIds => {
        // Get the latest Amazon Linux 2 AMI.
        const ami = aws.ec2.getAmiOutput({
            filters: [{ name: "name", values: ["amzn2-ami-hvm-*"] }],
            owners: ["amazon"],
            mostRecent: true,
        });
    
        // In each VPC subnet, create an EC2 instance and attach it to the ALB.
        subnetIds.forEach((subnetId, i) => {
            const vm = new aws.ec2.Instance(`web-${i}`, {
                ami: ami.id,
                instanceType: "t2.micro",
                subnetId,
                vpcSecurityGroupIds: alb.loadBalancer.securityGroups,
                userData: [`#!/bin/bash`, `echo "Hello World, from Server ${i + 1}!" > index.html`, `nohup python -m SimpleHTTPServer 80 &`].join("\n"),
            });
    
            const attachment = new awsx.lb.TargetGroupAttachment(`attachment-${i}`, {
                targetGroup: alb.defaultTargetGroup,
                instance: vm,
            });
        });
    });
    
    // Export the resulting URL so that it's easy to access.
    exports.endpoint = alb.loadBalancer.dnsName;
    
    import * as aws from "@pulumi/aws";
    import * as awsx from "@pulumi/awsx";
    
    // Get the default VPC for the current region.
    const vpc = new awsx.ec2.DefaultVpc("default-vpc");
    
    // Create a security group to allow traffic to and from the virtual machine.
    const securityGroup = new aws.ec2.SecurityGroup("web-sg", {
        vpcId: vpc.vpcId,
        ingress: [
            {
                protocol: "tcp",
                fromPort: 80,
                toPort: 80,
                cidrBlocks: ["0.0.0.0/0"],
            },
        ],
        egress: [
            {
                protocol: "-1",
                fromPort: 0,
                toPort: 0,
                cidrBlocks: ["0.0.0.0/0"],
            },
        ],
    });
    
    // Create an ALB in the default VPC listening on port 80.
    const alb = new awsx.lb.ApplicationLoadBalancer("web-traffic", {
        listener: {
            port: 80,
        },
        securityGroups: [securityGroup.id],
    });
    
    vpc.publicSubnetIds.apply(subnetIds => {
        // Get the latest Amazon Linux 2 AMI.
        const ami = aws.ec2.getAmiOutput({
            filters: [{ name: "name", values: ["amzn2-ami-hvm-*"] }],
            owners: ["amazon"],
            mostRecent: true,
        });
    
        // In each VPC subnet, create an EC2 instance and attach it to the ALB.
        subnetIds.forEach((subnetId, i) => {
            const vm = new aws.ec2.Instance(`web-${i}`, {
                ami: ami.id,
                instanceType: "t2.micro",
                subnetId,
                vpcSecurityGroupIds: alb.loadBalancer.securityGroups,
                userData: [`#!/bin/bash`, `echo "Hello World, from Server ${i + 1}!" > index.html`, `nohup python -m SimpleHTTPServer 80 &`].join("\n"),
            });
    
            const attachment = new awsx.lb.TargetGroupAttachment(`attachment-${i}`, {
                targetGroup: alb.defaultTargetGroup,
                instance: vm,
            });
        });
    });
    
    // Export the resulting URL so that it's easy to access.
    export const endpoint = alb.loadBalancer.dnsName;
    
    import pulumi
    import pulumi_aws as aws
    import pulumi_awsx as awsx
    
    # Get the default VPC for the current region.
    vpc = awsx.ec2.DefaultVpc("default-vpc")
    
    # Create a security group to allow traffic to and from the virtual machine.
    security_group = aws.ec2.SecurityGroup(
        "web-sg",
        vpc_id=vpc.vpc_id,
        ingress=[
            aws.ec2.SecurityGroupIngressArgs(
                protocol="tcp",
                from_port=80,
                to_port=80,
                cidr_blocks=["0.0.0.0/0"],
            ),
        ],
        egress=[
            aws.ec2.SecurityGroupEgressArgs(
                protocol="-1",
                from_port=0,
                to_port=0,
                cidr_blocks=["0.0.0.0/0"],
            ),
        ],
    )
    
    # Create an ALB in the default VPC listening on port 80.
    alb = awsx.lb.ApplicationLoadBalancer(
        "web-traffic",
        listener=awsx.lb.ListenerArgs(
            port=80,
        ),
        security_groups=[security_group.id],
    )
    
    # Get the latest Amazon Linux 2 AMI.
    ami = aws.ec2.get_ami_output(
        most_recent=True,
        owners=["amazon"],
        filters=[aws.ec2.GetAmiFilterArgs(name="name", values=["amzn2-ami-hvm-*"])],
    )
    
    def create_instance(subnet_id, i):
        vm = aws.ec2.Instance(
            f"web-{str(i)}",
            aws.ec2.InstanceArgs(
                ami=ami.id,
                instance_type="t2.micro",
                subnet_id=subnet_id,
                vpc_security_group_ids=alb.load_balancer.security_groups,
                user_data=f"""#!/bin/bash
                echo 'Hello World, from Server {str(i + 1)}!' > index.html
                nohup python -m SimpleHTTPServer 80 &
                """,
            ),
        )
        attachment = awsx.lb.TargetGroupAttachment(
            f"attachment-{str(i)}",
            awsx.lb.TargetGroupAttachmentArgs(
                target_group=alb.default_target_group,
                instance=vm,
            ),
        )
    
    def create_and_attach_instances(subnet_ids):
        for index, subnet_id in enumerate(subnet_ids):
            create_instance(subnet_id, index)
    
    # In each VPC subnet, create an EC2 instance and attach it to the ALB.
    vpc.public_subnet_ids.apply(create_and_attach_instances)
    
    # Export the resulting URL so that it's easy to access.
    pulumi.export("endpoint", alb.load_balancer.dns_name)
    
    package main
    
    import (
    	"fmt"
    
    	awsec2 "github.com/pulumi/pulumi-aws/sdk/v6/go/aws/ec2"
    	"github.com/pulumi/pulumi-awsx/sdk/v2/go/awsx/ec2"
    	"github.com/pulumi/pulumi-awsx/sdk/v2/go/awsx/lb"
    	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
    )
    
    func main() {
    	pulumi.Run(func(ctx *pulumi.Context) error {
    
    		// Get the default VPC for the current region.
    		vpc, err := ec2.NewDefaultVpc(ctx, "default-vpc", nil)
    		if err != nil {
    			return err
    		}
    
    		// Create a security group to allow traffic to and from the virtual machine.
    		securityGroup, err := awsec2.NewSecurityGroup(ctx, "web-sg", &awsec2.SecurityGroupArgs{
    			VpcId: vpc.VpcId,
    			Ingress: awsec2.SecurityGroupIngressArray{
    				&awsec2.SecurityGroupIngressArgs{
    					Protocol:   pulumi.String("tcp"),
    					FromPort:   pulumi.Int(80),
    					ToPort:     pulumi.Int(80),
    					CidrBlocks: pulumi.StringArray{pulumi.String("0.0.0.0/0")},
    				},
    			},
    			Egress: awsec2.SecurityGroupEgressArray{
    				&awsec2.SecurityGroupEgressArgs{
    					Protocol:   pulumi.String("-1"),
    					FromPort:   pulumi.Int(0),
    					ToPort:     pulumi.Int(0),
    					CidrBlocks: pulumi.StringArray{pulumi.String("0.0.0.0/0")},
    				},
    			},
    		})
    		if err != nil {
    			return err
    		}
    
    		// Create an ALB in the default VPC listening on port 80.
    		alb, err := lb.NewApplicationLoadBalancer(ctx, "lb", &lb.ApplicationLoadBalancerArgs{
    			Listener: &lb.ListenerArgs{
    				Port: pulumi.Int(80),
    			},
    			SecurityGroups: pulumi.StringArray{securityGroup.ID()},
    		})
    		if err != nil {
    			return err
    		}
    
    		// In each VPC subnet, create an EC2 instance and attach it to the ALB.
    		vpc.PublicSubnetIds.ApplyT(func(subnetIds []string) error {
    			for i, subnetId := range subnetIds {
    				suffix := fmt.Sprintf("web-%d", i)
    
    				ami, err := awsec2.LookupAmi(ctx, &awsec2.LookupAmiArgs{
    					Filters: []awsec2.GetAmiFilter{
    						{
    							Name:   "name",
    							Values: []string{"amzn2-ami-hvm-*"},
    						},
    					},
    					Owners:     []string{"amazon"},
    					MostRecent: pulumi.BoolRef(true),
    				})
    				if err != nil {
    					return err
    				}
    
    				vm, err := awsec2.NewInstance(ctx, suffix, &awsec2.InstanceArgs{
    					Ami:                 pulumi.String(ami.Id),
    					InstanceType:        pulumi.String("t2.micro"),
    					SubnetId:            pulumi.String(subnetId),
    					VpcSecurityGroupIds: alb.LoadBalancer.SecurityGroups(),
    					UserData: pulumi.Sprintf(`#!/bin/bash
    						echo "Hello World, from Server %d!" > index.html
    						nohup python -m SimpleHTTPServer 80 &`, i+1),
    				})
    				if err != nil {
    					return err
    				}
    
    				_, err = lb.NewTargetGroupAttachment(ctx, suffix, &lb.TargetGroupAttachmentArgs{
    					TargetGroup: alb.DefaultTargetGroup,
    					Instance:    vm,
    				})
    				if err != nil {
    					return err
    				}
    			}
    
    			return nil
    		})
    
    		// Export the resulting URL so that it's easy to access.
    		ctx.Export("endpoint", alb.LoadBalancer.DnsName())
    		return nil
    	})
    }
    
    using Pulumi;
    using Aws = Pulumi.Aws;
    using Awsx = Pulumi.Awsx;
    using System.Collections.Generic;
    
    return await Deployment.RunAsync(() =>
    {
        // Get the default VPC for the current region.
        var vpc = new Awsx.Ec2.DefaultVpc("default-vpc");
    
        // Create a security group to allow traffic to and from the virtual machine.
        var securityGroup = new Aws.Ec2.SecurityGroup("web-sg", new()
        {
            VpcId = vpc.VpcId,
            Ingress = new[]
            {
                new Aws.Ec2.Inputs.SecurityGroupIngressArgs
                {
                    Protocol = "tcp",
                    FromPort = 80,
                    ToPort = 80,
                    CidrBlocks = new[]
                    {
                        "0.0.0.0/0",
                    },
                },
            },
            Egress = new[]
            {
                new Aws.Ec2.Inputs.SecurityGroupEgressArgs
                {
                    Protocol = "-1",
                    FromPort = 0,
                    ToPort = 0,
                    CidrBlocks = new[]
                    {
                        "0.0.0.0/0"
                    },
                },
            },
        });
    
        // Create an ALB in the default VPC listening on port 80.
        var alb = new Awsx.Lb.ApplicationLoadBalancer("lb", new()
        {
            Listener = new()
            {
                Port = 80,
            },
    
            SecurityGroups = new[] { securityGroup.Id },
        });
    
        vpc.PublicSubnetIds.Apply(subnetIds => {
    
            // Get the latest Amazon Linux 2 AMI.
            var ami = Aws.Ec2.GetAmi.Invoke(new()
            {
                Filters = new[]
                {
                    new Aws.Ec2.Inputs.GetAmiFilterInputArgs
                    {
                        Name = "name",
                        Values = new[] { "amzn2-ami-hvm-*" },
                    },
                },
                Owners = new[] { "amazon" },
                MostRecent = true,
            });
    
            // In each VPC subnet, create an EC2 instance and attach it to the ALB.
            for (var i = 0; i < subnetIds.Length; i++)
            {
                var vm = new Aws.Ec2.Instance($"web-{i}", new()
                {
                    Ami = ami.Apply(result => result.Id),
                    InstanceType = "t2.micro",
                    SubnetId = subnetIds[i],
                    VpcSecurityGroupIds = alb.LoadBalancer.Apply(lb => lb.SecurityGroups),
                    UserData = $@"
                        #!/bin/bash
                        echo ""Hello World, from Server {i + 1}!"" > index.html
                        nohup python -m SimpleHTTPServer 80 &
                    ",
                });
    
                var attachment = new Awsx.Lb.TargetGroupAttachment($"attachment-{i}", new()
                {
                    TargetGroup = alb.DefaultTargetGroup,
                    Instance = vm,
                });
            }
    
            return subnetIds;
        });
    
        // Export the resulting URL so that it's easy to access.
        return new Dictionary<string, object?>
        {
            ["endpoint"] = alb.LoadBalancer.Apply(lb => lb.DnsName),
        };
    });
    
    package myproject;
    
    import com.pulumi.Pulumi;
    import com.pulumi.aws.ec2.Ec2Functions;
    import com.pulumi.aws.ec2.SecurityGroup;
    import com.pulumi.aws.ec2.SecurityGroupArgs;
    import com.pulumi.aws.ec2.inputs.SecurityGroupIngressArgs;
    import com.pulumi.aws.ec2.inputs.SecurityGroupEgressArgs;
    import com.pulumi.aws.ec2.Instance;
    import com.pulumi.aws.ec2.InstanceArgs;
    import com.pulumi.aws.ec2.inputs.GetAmiArgs;
    import com.pulumi.aws.ec2.inputs.GetAmiFilterArgs;
    import com.pulumi.aws.ec2.outputs.GetAmiResult;
    import com.pulumi.awsx.ec2.DefaultVpc;
    import com.pulumi.awsx.lb.ApplicationLoadBalancer;
    import com.pulumi.awsx.lb.ApplicationLoadBalancerArgs;
    import com.pulumi.awsx.lb.TargetGroupAttachment;
    import com.pulumi.awsx.lb.TargetGroupAttachmentArgs;
    import com.pulumi.awsx.lb.inputs.ListenerArgs;
    import com.pulumi.resources.CustomResourceOptions;
    import java.util.List;
    
    public class App {
        public static void main(String[] args) {
            Pulumi.run(ctx -> {
    
                // Get the default VPC for the current region.
                var vpc = new DefaultVpc("default-vpc");
    
                // Create a security group to allow traffic to and from the virtual machine.
                var securityGroup = new SecurityGroup("web-sg", SecurityGroupArgs.builder()
                    .vpcId(vpc.vpcId())
                    .ingress(SecurityGroupIngressArgs.builder()
                        .protocol("tcp")
                        .fromPort(80)
                        .toPort(80)
                        .cidrBlocks(List.of("0.0.0.0/0"))
                        .build())
                    .egress(SecurityGroupEgressArgs.builder()
                        .protocol("-1")
                        .fromPort(0)
                        .toPort(0)
                        .cidrBlocks(List.of("0.0.0.0/0"))
                        .build())
                    .build());
    
                // Create an ALB in the default VPC listening on port 80.
                var alb = new ApplicationLoadBalancer("web-traffic", ApplicationLoadBalancerArgs.builder()
                    .listener(ListenerArgs.builder()
                        .port(80)
                        .build())
                    .securityGroups(securityGroup.id().applyValue(id -> List.of(id)))
                    .build());
    
                // Get the latest Amazon Linux 2 AMI.
                var ami = Ec2Functions.getAmi(GetAmiArgs.builder()
                    .filters(List.of(GetAmiFilterArgs.builder()
                        .name("name")
                        .values(List.of("amzn2-ami-hvm-*"))
                        .build()))
                    .owners(List.of("amazon"))
                    .mostRecent(true)
                    .build());
    
                // In each VPC subnet, create an EC2 instance and attach it to the ALB.
                vpc.publicSubnetIds().applyValue(subnetIds -> {
                    for(int i = 0; i < subnetIds.size(); i++){
                        var subnetId = subnetIds.get(i);
    
                        var vm = new Instance(String.format("web-%s", i), InstanceArgs.builder()
                            .ami(ami.applyValue(GetAmiResult::id))
                            .instanceType("t2.micro")
                            .subnetId(subnetId)
                            .vpcSecurityGroupIds(alb.loadBalancer().apply(lb -> lb.securityGroups()))
                            .userData("#!/bin/bash\n"
                                + String.format("echo \"Hello World, from Server %s!\" > index.html\n", i + 1)
                                + "nohup python -m SimpleHTTPServer 80 &")
                            .build(),
                            CustomResourceOptions.builder()
                                .dependsOn(List.of(securityGroup))
                                .build());
    
                        new TargetGroupAttachment(String.format("attachment-%s", i), TargetGroupAttachmentArgs.builder()
                            .targetGroup(alb.defaultTargetGroup())
                            .instance(vm)
                            .build());
                    }
    
                    return subnetIds;
                });
    
                // Export the resulting URL so that it's easy to access.
                ctx.export("endpoint", alb.loadBalancer().apply(loadBalancer -> loadBalancer.dnsName()));
            });
        }
    }
    
    name: awsx-load-balanced-ec2-instances-yaml
    runtime: yaml
    description: An example that deploys multiple load-balanced VMs in Amazon EC2.
    
    variables:
      amiId:
        fn::invoke:
          function: aws:ec2:getAmi
          arguments:
            filters:
              - name: "name"
                values: ["amzn2-ami-hvm-*"]
            owners: ["amazon"]
            mostRecent: true
          return: id
    
    resources:
      # Get the default VPC for the current region.
      defaultVpc:
        type: awsx:ec2:DefaultVpc
    
      # Create a security group to allow traffic to and from the virtual machine.
      webSg:
        type: aws:ec2:SecurityGroup
        properties:
          vpcId: ${defaultVpc.vpcId}
          ingress:
            - protocol: tcp
              fromPort: 80
              toPort: 80
              cidrBlocks: ["0.0.0.0/0"]
          egress:
            - protocol: "-1"
              fromPort: 0
              toPort: 0
              cidrBlocks: ["0.0.0.0/0"]
      # Create an ALB in the default VPC listening on port 80.
      webTrafficAlb:
        type: awsx:lb:ApplicationLoadBalancer
        properties:
          listener:
            port: 80
          securityGroups:
            - ${webSg.id}
    
      # In each VPC subnet, create an EC2 instance and attach it to the ALB.
    
      web1:
        type: aws:ec2:Instance
        properties:
          ami: ${amiId}
          instanceType: t2.micro
          subnetId: ${defaultVpc.publicSubnetIds[0]}
          vpcSecurityGroupIds:
            - ${webTrafficAlb.loadBalancer.securityGroups[0]}
          userData: |
            #!/bin/bash
            echo "Hello World, from Server 1!" > index.html
            nohup python -m SimpleHTTPServer 80 &        
    
      attachment1:
        type: awsx:lb:TargetGroupAttachment
        properties:
          targetGroup: ${webTrafficAlb.defaultTargetGroup}
          instance: ${web1}
    
      web2:
        type: aws:ec2:Instance
        properties:
          ami: ${amiId}
          instanceType: t2.micro
          subnetId: ${defaultVpc.publicSubnetIds[1]}
          vpcSecurityGroupIds:
            - ${webTrafficAlb.loadBalancer.securityGroups[0]}
          userData: |
            #!/bin/bash
            echo "Hello World, from Server 2!" > index.html
            nohup python -m SimpleHTTPServer 80 &        
    
      attachment2:
        type: awsx:lb:TargetGroupAttachment
        properties:
          targetGroup: ${webTrafficAlb.defaultTargetGroup}
          instance: ${web2}
    
      web3:
        type: aws:ec2:Instance
        properties:
          ami: ${amiId}
          instanceType: t2.micro
          subnetId: ${defaultVpc.publicSubnetIds[2]}
          vpcSecurityGroupIds:
            - ${webTrafficAlb.loadBalancer.securityGroups[0]}
          userData: |
            #!/bin/bash
            echo "Hello World, from Server 3!" > index.html
            nohup python -m SimpleHTTPServer 80 &        
    
      attachment3:
        type: awsx:lb:TargetGroupAttachment
        properties:
          targetGroup: ${webTrafficAlb.defaultTargetGroup}
          instance: ${web3}
    
    # Export the resulting URL so that it's easy to access.
    outputs:
      endpoint: ${webTrafficAlb.loadBalancer.dnsName}
    

    After deploying this using pulumi up, we will have a fully functional endpoint:

    $ for i in {1..5} ; do curl "http://$(pulumi stack output endpoint)" ; done
    Hello World, from Server 1!
    Hello World, from Server 1!
    Hello World, from Server 3!
    Hello World, from Server 2!
    Hello World, from Server 1!
    

    The load balancer creates a default target group that forwards traffic on the same port. If you need to configure the way that traffic is forwarded, health checks, and so on, see Advanced NLB Target Group and Listener Configuration below.

    For more advanced cases, you will most likely want to use EC2 Auto Scaling, rather than hard-coding the number of and placement of VMs. Refer to the API docs for LaunchConfiguration and AutoScalingGroup for details on how to do so.

    Load Balancing ECS Service Targets

    Your ECS service can use ELB to distribute traffic evenly across each of your service’s tasks. To target an ECS service with your load balancer, pass the listener in your task definition’s portMappings:

    "use strict";
    const pulumi = require("@pulumi/pulumi");
    const aws = require("@pulumi/aws");
    const awsx = require("@pulumi/awsx");
    
    const lb = new awsx.lb.ApplicationLoadBalancer("lb");
    const cluster = new aws.ecs.Cluster("cluster");
    
    const service = new awsx.ecs.FargateService("service", {
        cluster: cluster.arn,
        assignPublicIp: true,
        desiredCount: 2,
        taskDefinitionArgs: {
            container: {
                name: "my-service",
                image: "nginx:latest",
                cpu: 128,
                memory: 512,
                essential: true,
                portMappings: [
                    {
                        containerPort: 80,
                        targetGroup: lb.defaultTargetGroup,
                    },
                ],
            },
        },
    });
    
    exports.url = pulumi.interpolate`http://${lb.loadBalancer.dnsName}`;
    
    import * as pulumi from "@pulumi/pulumi";
    import * as aws from "@pulumi/aws";
    import * as awsx from "@pulumi/awsx";
    
    const lb = new awsx.lb.ApplicationLoadBalancer("lb");
    const cluster = new aws.ecs.Cluster("cluster");
    
    const service = new awsx.ecs.FargateService("service", {
        cluster: cluster.arn,
        assignPublicIp: true,
        desiredCount: 2,
        taskDefinitionArgs: {
            container: {
                name: "my-service",
                image: "nginx:latest",
                cpu: 128,
                memory: 512,
                essential: true,
                portMappings: [
                    {
                        containerPort: 80,
                        targetGroup: lb.defaultTargetGroup,
                    },
                ],
            },
        },
    });
    
    export const url = pulumi.interpolate`http://${lb.loadBalancer.dnsName}`;
    
    import pulumi
    import pulumi_aws as aws
    import pulumi_awsx as awsx
    
    lb = awsx.lb.ApplicationLoadBalancer("lb")
    cluster = aws.ecs.Cluster("cluster")
    
    service = awsx.ecs.FargateService("service",
        cluster=cluster.arn,
        assign_public_ip=True,
        desired_count=2,
        task_definition_args=awsx.ecs.FargateServiceTaskDefinitionArgs(
            container=awsx.ecs.TaskDefinitionContainerDefinitionArgs(
                name="my-service",
                image="nginx:latest",
                cpu=128,
                memory=512,
                essential=True,
                port_mappings=[awsx.ecs.TaskDefinitionPortMappingArgs(
                    container_port=80,
                    target_group=lb.default_target_group,
                )],
            ),
        ))
    
    pulumi.export("url", pulumi.Output.concat("http://", lb.load_balancer.dns_name))
    
    package main
    
    import (
    	"github.com/pulumi/pulumi-aws/sdk/v6/go/aws/ecs"
    	ecsx "github.com/pulumi/pulumi-awsx/sdk/v2/go/awsx/ecs"
    	"github.com/pulumi/pulumi-awsx/sdk/v2/go/awsx/lb"
    	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
    )
    
    func main() {
    	pulumi.Run(func(ctx *pulumi.Context) error {
    		lb, err := lb.NewApplicationLoadBalancer(ctx, "lb", nil)
    		if err != nil {
    			return err
    		}
    
    		cluster, err := ecs.NewCluster(ctx, "cluster", nil)
    		if err != nil {
    			return err
    		}
    
    		_, err = ecsx.NewFargateService(ctx, "service", &ecsx.FargateServiceArgs{
    			Cluster:        cluster.Arn,
    			AssignPublicIp: pulumi.Bool(true),
    			DesiredCount:   pulumi.Int(2),
    			TaskDefinitionArgs: &ecsx.FargateServiceTaskDefinitionArgs{
    				Container: &ecsx.TaskDefinitionContainerDefinitionArgs{
    					Name:      pulumi.String("my-service"),
    					Image:     pulumi.String("nginx:latest"),
    					Cpu:       pulumi.Int(128),
    					Memory:    pulumi.Int(512),
    					Essential: pulumi.Bool(true),
    					PortMappings: ecsx.TaskDefinitionPortMappingArray{
    						&ecsx.TaskDefinitionPortMappingArgs{
    							ContainerPort: pulumi.Int(80),
    							TargetGroup:   lb.DefaultTargetGroup,
    						},
    					},
    				},
    			},
    		})
    		if err != nil {
    			return err
    		}
    
    		ctx.Export("url", pulumi.Sprintf("http://%s", lb.LoadBalancer.DnsName()))
    		return nil
    	})
    }
    
    using System.Collections.Generic;
    using Pulumi;
    using Aws = Pulumi.Aws;
    using Awsx = Pulumi.Awsx;
    
    return await Deployment.RunAsync(() =>
    {
        var lb = new Awsx.Lb.ApplicationLoadBalancer("lb");
        var cluster = new Aws.Ecs.Cluster("cluster");
    
        var service = new Awsx.Ecs.FargateService("service", new()
        {
            Cluster = cluster.Arn,
            AssignPublicIp = true,
            TaskDefinitionArgs = new Awsx.Ecs.Inputs.FargateServiceTaskDefinitionArgs
            {
                Container = new Awsx.Ecs.Inputs.TaskDefinitionContainerDefinitionArgs
                {
                    Name = "my-service",
                    Image = "nginx:latest",
                    Cpu = 128,
                    Memory = 512,
                    Essential = true,
                    PortMappings = new()
                    {
                        new Awsx.Ecs.Inputs.TaskDefinitionPortMappingArgs
                        {
                            ContainerPort = 80,
                            TargetGroup = lb.DefaultTargetGroup,
                        },
                    },
                },
            },
        });
    
        return new Dictionary<string, object?>
        {
            ["url"] = lb.LoadBalancer.Apply(loadBalancer => Output.Format($"http://{loadBalancer.DnsName}")),
        };
    });
    
    package myproject;
    
    import com.pulumi.Context;
    import com.pulumi.Pulumi;
    import com.pulumi.core.Output;
    import com.pulumi.awsx.ecr.Repository;
    import com.pulumi.awsx.ecr.RepositoryArgs;
    import com.pulumi.aws.ecs.Cluster;
    import com.pulumi.awsx.lb.ApplicationLoadBalancer;
    import com.pulumi.awsx.ecs.FargateService;
    import com.pulumi.awsx.ecs.FargateServiceArgs;
    import com.pulumi.awsx.ecs.inputs.FargateServiceTaskDefinitionArgs;
    import com.pulumi.awsx.ecs.inputs.TaskDefinitionContainerDefinitionArgs;
    import com.pulumi.awsx.ecs.inputs.TaskDefinitionPortMappingArgs;
    
    public class App {
        public static void main(String[] args) {
            Pulumi.run(App::stack);
        }
    
        public static void stack(Context ctx) {
            var cluster = new Cluster("cluster");
            var lb = new ApplicationLoadBalancer("lb");
    
            var service = new FargateService("service", FargateServiceArgs.builder()
                .cluster(cluster.arn())
                .assignPublicIp(true)
                .taskDefinitionArgs(FargateServiceTaskDefinitionArgs.builder()
                    .container(TaskDefinitionContainerDefinitionArgs.builder()
                        .name("my-service")
                        .image("nginx:latest")
                        .cpu(128)
                        .memory(512)
                        .essential(true)
                        .portMappings(TaskDefinitionPortMappingArgs.builder()
                            .containerPort(80)
                            .targetGroup(lb.defaultTargetGroup())
                            .build())
                        .build())
                    .build())
                .build());
    
            ctx.export("url", Output.format("http://%s", lb.loadBalancer().applyValue(loadBalancer -> loadBalancer.dnsName())));
        }
    }
    
    name: awsx-load-balanced-fargate-nginx-yaml
    runtime: yaml
    resources:
      lb:
        type: awsx:lb:ApplicationLoadBalancer
      cluster:
        type: aws:ecs:Cluster
      service:
        type: awsx:ecs:FargateService
        properties:
          cluster: ${cluster.arn}
          assignPublicIp: true
          taskDefinitionArgs:
            container:
              name: my-service
              image: "nginx:latest"
              cpu: 128
              memory: 512
              essential: true
              portMappings:
                - containerPort: 80
                  targetGroup: ${lb.defaultTargetGroup}
    outputs:
      url: http://${lb.loadBalancer.dnsName}
    

    Pulumi Crosswalk for AWS ECS – those classes in the awsx.ecs package – will automatically create the right ingress and egress rules. If you are using raw aws.ecs, you will need to manually manage the security group ingress and egress rules, much like the EC2 Instance example earlier.

    After deploying this using pulumi up, we will have a fully functional endpoint:

    $ curl http://$(pulumi stack output endpoint)
    <!DOCTYPE html>
    <html>
    <body>
    <h1>Welcome to nginx!</h1>
    </body>
    </html>
    

    This load balancer uses reasonable targeting defaults and health checks. If you’d like to customize these, see Advanced NLB Target Group and Listener Configuration below.

    Although ECS supports both NLB and ALB, ALB offer several features that make them more attractive for ECS:

    • Dynamic host port mapping enables multiple tasks from the same service to use the same container instance.
    • Path-based routing and priority rules allow multiple services to use the same listener port on a single ALB.

    We recommend using ALBs for your ECS services unless it requires a feature that is only available with NLBs.

    For more extensive information about load balancing and ECS Services, refer to AWS’s Service Load Balancing documentation.

    Listening on Private Subnets

    By default, your load balancer will created as internet-facing, meaning it’ll use the VPC’s public subnets and listen for traffic coming from the Internet. If you want to instead keep your load balancer private, servicing traffic inside of your VPC over its private subnets, set the internal property to true:

    "use strict";
    const awsx = require("@pulumi/awsx");
    
    // Create a load balancer in the default VPC listening on port 80.
    const alb = new awsx.lb.ApplicationLoadBalancer("lb", {
        listener: {
            port: 80,
        },
    
        // Configure the load balancer as internal rather than internet-facing.
        internal: true,
    });
    
    // Export the resulting URL so that it's easy to access.
    exports.endpoint = alb.loadBalancer.dnsName;
    
    import * as awsx from "@pulumi/awsx";
    
    // Create a load balancer in the default VPC listening on port 80.
    const alb = new awsx.lb.ApplicationLoadBalancer("lb", {
        listener: {
            port: 80,
        },
    
        // Configure the load balancer as internal rather than internet-facing.
        internal: true,
    });
    
    // Export the resulting URL so that it's easy to access.
    export const endpoint = alb.loadBalancer.dnsName;
    
    import pulumi
    import pulumi_awsx as awsx
    
    # Create a load balancer in the default VPC listening on port 80.
    alb = awsx.lb.ApplicationLoadBalancer(
        "lb",
        awsx.lb.ApplicationLoadBalancerArgs(
            listener=awsx.lb.ListenerArgs(
                port=80,
            ),
    
            # Configure the load balancer as internal rather than internet-facing.
            internal=True,
        ),
    )
    
    # Export the resulting URL so that it's easy to access.
    pulumi.export("endpoint", alb.load_balancer.dns_name)
    
    package main
    
    import (
    	"github.com/pulumi/pulumi-awsx/sdk/v2/go/awsx/lb"
    	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
    )
    
    func main() {
    	pulumi.Run(func(ctx *pulumi.Context) error {
    
    		// Create a load balancer in the default VPC listening on port 80.
    		alb, err := lb.NewApplicationLoadBalancer(ctx, "lb", &lb.ApplicationLoadBalancerArgs{
    			Listener: &lb.ListenerArgs{
    				Port: pulumi.Int(80),
    			},
    
    			// Configure the load balancer as internal rather than internet-facing.
    			Internal: pulumi.Bool(true),
    		})
    		if err != nil {
    			return err
    		}
    
    		// Export the resulting URL so that it's easy to access.
    		ctx.Export("endpoint", alb.LoadBalancer.DnsName())
    		return nil
    	})
    }
    
    using Pulumi;
    using Awsx = Pulumi.Awsx;
    using System.Collections.Generic;
    
    return await Deployment.RunAsync(() =>
    {
       // Create a load balancer in the default VPC listening on port 80.
       var alb = new Awsx.Lb.ApplicationLoadBalancer("lb", new()
       {
            Listener = new()
            {
                Port = 80,
            },
    
            // Configure the load balancer as internal rather than internet-facing.
            Internal =   true,
       });
    
        // Export the resulting URL so that it's easy to access.
        return new Dictionary<string, object?>
        {
            ["endpoint"] = alb.LoadBalancer.Apply(lb => lb.DnsName),
        };
    });
    
    package myproject;
    
    import com.pulumi.Pulumi;
    import com.pulumi.awsx.lb.ApplicationLoadBalancer;
    import com.pulumi.awsx.lb.ApplicationLoadBalancerArgs;
    import com.pulumi.awsx.lb.inputs.ListenerArgs;
    
    public class App {
        public static void main(String[] args) {
            Pulumi.run(ctx -> {
    
                // Create a load balancer in the default VPC listening on port 80.
                var alb = new ApplicationLoadBalancer("lb", ApplicationLoadBalancerArgs.builder()
                    .listener(ListenerArgs.builder()
                        .port(80)
                        .build())
    
                    // Configure the load balancer as internal rather than internet-facing.
                    .internal(true)
                    .build());
    
                // Export the resulting URL so that it's easy to access.
                ctx.export("endpoint", alb.loadBalancer().apply(loadBalancer -> loadBalancer.dnsName()));
            });
        }
    }
    
    name: awsx-elb-private-subnet-yaml
    runtime: yaml
    description: An example that deploys an ApplicationLoadBalancer listening on a private subnet.
    
    resources:
      # Create a load balancer in the default VPC listening on port 80.
      alb:
        type: awsx:lb:ApplicationLoadBalancer
        properties:
          listener:
            port: 80
    
          # Configure the load balancer as internal rather than internet-facing.
          internal: true
    
    outputs:
      # Export the resulting URL so that it's easy to access.
      endpoint: ${alb.loadBalancer.dnsName}
    

    For complete control, you can elect instead to pass in an explicit list of subnets using the subnets property.

    Creating a Load Balancer in a Custom VPC

    Each region contains a default VPC for your account. The load balancers created above will use it automatically, in addition to its default public or private subnets, depending on whether you’ve overridden the default of public using internal.

    If you’d like to create a load balancer for a custom VPC, provision (or look up) the VPC, then use the subnetIds property of the load balancer to associate it with the VPC’s public or private subnet:

    "use strict";
    const pulumi = require("@pulumi/pulumi");
    const aws = require("@pulumi/aws");
    const awsx = require("@pulumi/awsx");
    
    // Allocate (or get) a custom VPC.
    const vpc = new awsx.ec2.Vpc("vpc");
    
    // Create a load balancer in the default VPC listening on port 80.
    const alb = new awsx.lb.ApplicationLoadBalancer("lb", {
        listener: {
            port: 80,
        },
    
        // Associate the load balancer with the VPC's `public` or `private` subnet.
        subnetIds: vpc.publicSubnetIds,
    });
    
    // Export the resulting URL so that it's easy to access.
    exports.endpoint = alb.loadBalancer.dnsName;
    
    import * as awsx from "@pulumi/awsx";
    
    // Allocate (or get) a custom VPC.
    const vpc = new awsx.ec2.Vpc("vpc");
    
    // Create a load balancer in the default VPC listening on port 80.
    const alb = new awsx.lb.ApplicationLoadBalancer("lb", {
        listener: {
            port: 80,
        },
    
        // Associate the load balancer with the VPC's `public` or `private` subnet.
        subnetIds: vpc.publicSubnetIds,
    });
    
    // Export the resulting URL so that it's easy to access.
    export const endpoint = alb.loadBalancer.dnsName;
    
    import pulumi
    import pulumi_awsx as awsx
    
    # Allocate (or get) a custom VPC.
    vpc = awsx.ec2.Vpc("vpc");
    
    # Create a load balancer in the default VPC listening on port 80.
    alb = awsx.lb.ApplicationLoadBalancer(
        "lb",
        awsx.lb.ApplicationLoadBalancerArgs(
            listener=awsx.lb.ListenerArgs(
                port=80,
            ),
    
            # Associate the load balancer with the VPC's `public` or `private` subnet.
            subnet_ids=vpc.public_subnet_ids,
        ),
    )
    
    # Export the resulting URL so that it's easy to access.
    pulumi.export("endpoint", alb.load_balancer.dns_name)
    
    package main
    
    import (
    	"github.com/pulumi/pulumi-awsx/sdk/v2/go/awsx/ec2"
    	"github.com/pulumi/pulumi-awsx/sdk/v2/go/awsx/lb"
    	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
    )
    
    func main() {
    	pulumi.Run(func(ctx *pulumi.Context) error {
    
    		// Allocate (or get) a custom VPC.
    		vpc, err := ec2.NewVpc(ctx, "vpc", nil)
    		if err != nil {
    			return err
    		}
    
    		// Create a load balancer in the default VPC listening on port 80.
    		alb, err := lb.NewApplicationLoadBalancer(ctx, "lb", &lb.ApplicationLoadBalancerArgs{
    			Listener: &lb.ListenerArgs{
    				Port: pulumi.Int(80),
    			},
    
    			// Associate the load balancer with the VPC's `public` or `private` subnet.
    			SubnetIds: vpc.PrivateSubnetIds,
    		})
    		if err != nil {
    			return err
    		}
    
    		// Export the resulting URL so that it's easy to access.
    		ctx.Export("endpoint", alb.LoadBalancer.DnsName())
    		return nil
    	})
    }
    
    using Pulumi;
    using Awsx = Pulumi.Awsx;
    using System.Collections.Generic;
    
    return await Deployment.RunAsync(() =>
    {
       // Allocate (or get) a custom VPC.
       var vpc = new Awsx.Ec2.Vpc("vpc");
    
       // Create a load balancer in the default VPC listening on port 80.
       var alb = new Awsx.Lb.ApplicationLoadBalancer("lb", new()
       {
            Listener = new()
            {
                Port = 80,
            },
    
             // Associate the load balancer with the VPC's `public` or `private` subnet.
            SubnetIds = vpc.PublicSubnetIds,
       });
    
        // Export the resulting URL so that it's easy to access.
        return new Dictionary<string, object?>
        {
            ["endpoint"] = alb.LoadBalancer.Apply(lb => lb.DnsName),
        };
    });
    
    package myproject;
    
    import com.pulumi.Pulumi;
    import com.pulumi.awsx.ec2.Vpc;
    import com.pulumi.awsx.lb.ApplicationLoadBalancer;
    import com.pulumi.awsx.lb.ApplicationLoadBalancerArgs;
    import com.pulumi.awsx.lb.inputs.ListenerArgs;
    
    public class App {
        public static void main(String[] args) {
            Pulumi.run(ctx -> {
    
                // Allocate (or get) a custom VPC.
                var vpc = new Vpc("vpc");
    
                // Create a load balancer in the default VPC listening on port 80.
                var alb = new ApplicationLoadBalancer("lb", ApplicationLoadBalancerArgs.builder()
                    .listener(ListenerArgs.builder()
                        .port(80)
                        .build())
    
                    // Associate the load balancer with the VPC's `public` or `private` subnet.
                    .subnetIds(vpc.publicSubnetIds())
                    .build());
    
                // Export the resulting URL so that it's easy to access.
                ctx.export("endpoint", alb.loadBalancer().apply(loadBalancer -> loadBalancer.dnsName()));
            });
        }
    }
    
    name: awsx-elb-vpc-yaml
    runtime: yaml
    description: An example that deploys an ApplicationLoadBalancer in a VPC.
    
    resources:
      # Allocate (or get) a custom VPC.
      vpc:
        type: awsx:ec2:Vpc
    
      # Create a load balancer in the default VPC listening on port 80.
      alb:
        type: awsx:lb:ApplicationLoadBalancer
        properties:
          listener:
            port: 80
    
          # Associate the load balancer with the VPC's `public` or `private` subnet.
          subnetIds: ${vpc.publicSubnetIds}
    
    outputs:
      # Export the resulting URL so that it's easy to access.
      endpoint: ${alb.loadBalancer.dnsName}
    

    For more information on creating and configuring VPCs, refer to Pulumi Crosswalk for AWS VPC.

    Advanced Load Balancer Listener and Target Group Configuration

    The above examples were simplistic in their usage of target groups and listeners. In many scenarios, that’s all we need. However, target groups and listeners are more powerful than this and have advanced functionality built-in.

    Let’s review the core concepts involved in both NLB and ALB style load balancers:

    • A load balancer serves as the single point of contact for clients. The load balancer distributes incoming application traffic across multiple targets, such as EC2 instances, in multiple availability zones. This increases the availability of your application. You add one or more listeners to your load balancer.

    • A listener checks for connection requests from clients, using the protocol and port that you configure, and forwards requests to one or more target groups, based on the rules that you define. Each rule specifies a target group, condition, and priority. When the condition is met, the traffic is forwarded to the target group. You must define a default rule for each listener, and you can add rules that specify different target groups based on the content of the request (also known as content-based routing).

    • Each target group routes requests to one or more registered targets, such as EC2 instances, using the protocol and port number that you specify. You can register a target with multiple target groups. You can configure health checks on a per target group basis. Health checks are performed on all targets registered to a target group that is specified in a listener rule for your load balancer.

    Many of the examples above leverage smart defaults in the NetworkLoadBalancer and ApplicationLoadBalancer classes. This includes creating target groups automatically that leverage the same inbound port information as the listeners.

    Manually Configuring Listeners

    During the creation of a listener, the listener property will attempt to choose smart defaults based on the scenario of creating the listener against a load balancer or target group, but there are several configuration options available. These include:

    • protocol: NLBs support TCP, TLS, HTTP, and HTTPS, while ALBs support HTTP and HTTPS. If not specified, NLBs default to TCP and ALBs will select HTTP or HTTPS based on the port supplied.

    • certificateArn and sslPolicy: Enables SSL using the given certificate and policy. This policy controls how SSL connections are terminated, among other things. Refer to Create an HTTPS Listener for Your Application Load Balancer for more information.

    • defaultActions: Configure the rules and actions to take in response to traffic reaching your load balancer. By default, that entails forwarding traffic to a target group. However, additional options are available via the ListenerDefaultAction type. You may provide multiple rules:

      • authenticateCognito: Enable Cognito authentication for access through your load balancer. For more information, see Authenticate Users Using and Application Load Balancer.

      • authenticateOidc: Authenticate access through your load balancer using an OpenID Connect (OIDC) compliant identity provider.

      • fixedResponse: Return a custom HTTP response, rather than forwarding traffic. For details, see Fixed-Response Actions.

      • redirect: Redirect from one URL to another. For details, see Redirect Actions.

    As an example of a custom action, the following load balancer redirects HTTP traffic on port 80 to port 8080 by defining two listeners, one configured to redirect to the other:

    "use strict";
    const awsx = require("@pulumi/awsx");
    
    // Create a load balancer in the default VPC.
    const alb = new awsx.lb.ApplicationLoadBalancer("lb", {
        listeners: [
            {
                // Redirect HTTP traffic on port 8080 to port 8081.
                port: 8080,
                protocol: "HTTP",
                defaultActions: [
                    {
                        type: "redirect",
                        redirect: {
                            port: "8081",
                            protocol: "HTTP",
                            statusCode: "HTTP_301",
                        },
                    },
                ],
            },
            {
                // Accept HTTP traffic on port 8081.
                port: 8081,
                protocol: "HTTP",
            },
        ],
    });
    
    // Export the resulting URL so that it's easy to access.
    exports.endpoint = alb.loadBalancer.dnsName;
    
    import * as awsx from "@pulumi/awsx";
    
    // Create a load balancer in the default VPC.
    const alb = new awsx.lb.ApplicationLoadBalancer("lb", {
        listeners: [
            {
                // Redirect HTTP traffic on port 8080 to port 8081.
                port: 8080,
                protocol: "HTTP",
                defaultActions: [
                    {
                        type: "redirect",
                        redirect: {
                            port: "8081",
                            protocol: "HTTP",
                            statusCode: "HTTP_301",
                        },
                    },
                ],
            },
            {
                // Accept HTTP traffic on port 8080.
                port: 8081,
                protocol: "HTTP",
            },
        ],
    });
    
    // Export the resulting URL so that it's easy to access.
    export const endpoint = alb.loadBalancer.dnsName;
    
    import pulumi
    import pulumi_aws as aws
    import pulumi_awsx as awsx
    
    # Create a load balancer in the default VPC.
    alb = awsx.lb.ApplicationLoadBalancer(
        "lb",
        awsx.lb.ApplicationLoadBalancerArgs(
            listeners=[
    
                # Redirect HTTP traffic on port 8080 to port 8081.
                awsx.lb.ListenerArgs(
                    port=8080,
                    protocol="HTTP",
                    default_actions=[
                        aws.lb.ListenerDefaultActionArgs(
                            type="redirect",
                            redirect=aws.lb.ListenerDefaultActionRedirectArgs(
                                port="8081",
                                protocol="HTTP",
                                status_code="HTTP_301",
                            ),
                        ),
                    ],
                ),
    
                # Accept HTTP traffic on port 8081.
                awsx.lb.ListenerArgs(
                    port=8081,
                    protocol="HTTP",
                ),
            ],
        ),
    )
    
    # Export the resulting URL so that it's easy to access.
    pulumi.export("endpoint", alb.load_balancer.dns_name)
    
    package main
    
    import (
    	awslb "github.com/pulumi/pulumi-aws/sdk/v6/go/aws/lb"
    	"github.com/pulumi/pulumi-awsx/sdk/v2/go/awsx/lb"
    	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
    )
    
    func main() {
    	pulumi.Run(func(ctx *pulumi.Context) error {
    
    		// Create a load balancer in the default VPC.
    		alb, err := lb.NewApplicationLoadBalancer(ctx, "lb", &lb.ApplicationLoadBalancerArgs{
    			Listeners: []lb.ListenerArgs{
    
    				// Redirect HTTP traffic on port 8080 to port 8081.
    				{
    					Port:     pulumi.Int(8080),
    					Protocol: pulumi.String("HTTP"),
    					DefaultActions: awslb.ListenerDefaultActionArray{
    						awslb.ListenerDefaultActionArgs{
    							Type: pulumi.String("redirect"),
    							Redirect: awslb.ListenerDefaultActionRedirectArgs{
    								Port:       pulumi.String("8081"),
    								Protocol:   pulumi.String("HTTP"),
    								StatusCode: pulumi.String("HTTP_301"),
    							},
    						},
    					},
    				},
    
    				// Accept HTTP traffic on port 8081.
    				{
    					Port:     pulumi.Int(8081),
    					Protocol: pulumi.String("HTTP"),
    				},
    			},
    		})
    		if err != nil {
    			return err
    		}
    
    		// Export the resulting URL so that it's easy to access.
    		ctx.Export("endpoint", alb.LoadBalancer.DnsName())
    		return nil
    	})
    }
    
    using Pulumi;
    using Aws = Pulumi.Aws;
    using Awsx = Pulumi.Awsx;
    using System.Collections.Generic;
    using System;
    
    return await Deployment.RunAsync(() =>
    {
       // Create a load balancer in the default VPC.
       var alb = new Awsx.Lb.ApplicationLoadBalancer("lb", new()
       {
            Listeners = {
    
                // Redirect HTTP traffic on port 8080 to port 8081.
                new Awsx.Lb.Inputs.ListenerArgs
                {
                    Port = 8080,
                    Protocol = "HTTP",
                    DefaultActions = {
                        new Aws.LB.Inputs.ListenerDefaultActionArgs
                        {
                            Type = "redirect",
                            Redirect = new Aws.LB.Inputs.ListenerDefaultActionRedirectArgs
                            {
                                Port = "8081",
                                Protocol = "HTTP",
                                StatusCode = "HTTP_301",
                            },
                        }
                    }
                },
    
                // Accept HTTP traffic on port 8081.
                new Awsx.Lb.Inputs.ListenerArgs
                {
                    Port = 8081,
                    Protocol = "HTTP",
                },
            },
       });
    
        // Export the resulting URL so that it's easy to access.
        return new Dictionary<string, object?>
        {
            ["endpoint"] = alb.LoadBalancer.Apply(lb => lb.DnsName),
        };
    });
    
    package myproject;
    
    import com.pulumi.Pulumi;
    import com.pulumi.aws.lb.inputs.ListenerDefaultActionArgs;
    import com.pulumi.aws.lb.inputs.ListenerDefaultActionRedirectArgs;
    import com.pulumi.awsx.lb.ApplicationLoadBalancer;
    import com.pulumi.awsx.lb.ApplicationLoadBalancerArgs;
    import com.pulumi.awsx.lb.inputs.ListenerArgs;
    
    public class App {
        public static void main(String[] args) {
            Pulumi.run(ctx -> {
    
                // Create a load balancer in the default VPC.
                var alb = new ApplicationLoadBalancer("lb", ApplicationLoadBalancerArgs.builder()
                    .listeners(new ListenerArgs[] {
    
                        // Redirect HTTP traffic on port 8080 to port 8081.
                        ListenerArgs.builder()
                            .port(8080)
                            .protocol("HTTP")
                            .defaultActions(new ListenerDefaultActionArgs[] {
                                ListenerDefaultActionArgs.builder()
                                    .type("redirect")
                                    .redirect(ListenerDefaultActionRedirectArgs.builder()
                                        .port("8081")
                                        .protocol("HTTP")
                                        .statusCode("HTTP_301")
                                        .build())
                                    .build(),
                            })
                            .build(),
    
                        // Accept HTTP traffic on port 8081.
                        ListenerArgs.builder()
                            .port(8081)
                            .protocol("HTTP")
                            .build(),
                    })
                    .build());
    
                // Export the resulting URL so that it's easy to access.
                ctx.export("endpoint", alb.loadBalancer().apply(loadBalancer -> loadBalancer.dnsName()));
            });
        }
    }
    
    name: awsx-elb-multi-listener-redirect-yaml
    runtime: yaml
    description: An example of an ApplicationLoadBalancer with multiple listeners and a custom redirect action.
    resources:
      # Create a load balancer in the default VPC.
      alb:
        type: awsx:lb:ApplicationLoadBalancer
        properties:
          listeners:
            # Redirect HTTP traffic on port 8080 to port 8081.
            - port: 8080
              protocol: HTTP
              defaultActions:
                - type: redirect
                  redirect:
                    port: "8081"
                    protocol: HTTP
                    statusCode: HTTP_301
    
            # Accept HTTP traffic on port 8081.
            - port: 8081
              protocol: HTTP
    
    outputs:
      # Export the resulting URL so that it's easy to access.
      endpoint: ${alb.loadBalancer.dnsName}
    
    $ curl -I "http://$(pulumi stack output endpoint):8080"
    
    HTTP/1.1 301 Moved Permanently
    Location: http://lb-692829a-1197942792.us-west-2.elb.amazonaws.com:8081/
    

    For more information on listener rules, refer to the AWS documentation about listeners.

    Manually Configuring Target Groups

    A target group is automatically created for each listener that doesn’t override the default action. This group can then be used to load balance any number of targets, including EC2 instances, ECS services, or arbitrary IPs.

    You can also create a target group manually, either by defining a defaultTargetGroup on the load balancer directly or by allocating a TargetGroupAttachment resource. When doing so, the following additional options are available:

    • deregistrationDelay: The amount of time for ELB to wait before changing the state of a load balancer from draining to unused. The range is 0-3600 seconds, and the default is 300. This is the period of time in which an application should gracefully shut down before traffic to it is severed.

    • slowStart: The amount of time for ELB to wait before sending a target its full share of requests. This can give the application time to boot and warm up before it takes traffic. The range is 30-900 seconds or 0 to disable. The default is for slow start to be disabled.

    • stickiness: If enabled (for ALBs only), a cookie will be used to ensure traffic consistently flows to the same targets, provided they remain active.

    • targetType: The type of target you will be using with this target group. The possible values are instance, if targeting an EC2 instance ID directly, or ip, if targeting an IP address. The default is ip. Note also that IP addresses must be routable within your VPC and cannot be public IP addresses (within your VPC’s private subnet range, the RFC 1918 range (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16), and the RFC 6598 range (100.64.0.0/10).

    • healthCheck: Overrides the default health check behavior. The parameters available vary by target protocol and differ considerably between NLB and ALB. This includes:

      • interval: The approximate amount of time, in seconds, between health checks of an individual target. The range is between 5-300 seconds, and defaults to 30 seconds.

      • healthyThreshold: The number of consecutive health checks successes required before considering an unhealthy target healthy. The default is 3.

      • unhealthyThreshold: The number of consecutive health check failures required before considering the target unhealthy. For NLBs, this value must be the same as healthyThreshold. The default is 3.

      • path: For ALB only, the required destination for health check requests. This allows for application level health checking, versus NLBs which only support health checking the availability of the target.

      • timeout: For ALB only, the timeout in seconds for health check requests. The range is 2-60 seconds, and the default value is 5 seconds.

      • matcher: For ALB only, the HTTP codes to use when checking for a successful response from a target. You can specify multiple values (for example, “200,202”) or a range of values (for example, “200-299”).

    • tags: Can be used to tag your target group with metadata about its purpose, for reporting or compliance.

    For more extensive information on ELB target groups, refer to the AWS documentation.

    Additional ELB Resources

    For detailed reference documentation, visit the API docs.

      PulumiUP 2024. Watch On Demand.