Loadbalancing the VMs

Needing to loop over the webservers isn’t very realistic. You will now create a load balancer over them to distribute load evenly.

Now install the AWSX package, a collection of helpers that makes things like configuring load balancing easier:

npm install @pulumi/awsx

And import this package at the top of your program:

import * as awsx from "@pulumi/awsx";

Delete the port 80 ingress rule from your security group, leaving behind only the ICMP rule:

const sg = new aws.ec2.SecurityGroup("web-secgrp", {
    ingress: [
        { protocol: "icmp", fromPort: 8, toPort: 0, cidrBlocks: ["0.0.0.0/0"] },
    ],
});

This is required to ensure the security group ingress rules don’t conflict with the load balancer’s.

Now right after the security group creation, and before the VM creation block, add the load balancer creation:

const alb = new awsx.lb.ApplicationLoadBalancer("web-traffic", {
    external: true,
    securityGroups: [ sg.id ],
});
const listener = alb.createListener("web-listener", { port: 80 });

And then replace the VM creation block with the following:

export const ips: any[] = [];
export const hostnames: any[] = [];
for (const az of aws.getAvailabilityZones().names) {
    const server = new aws.ec2.Instance(`web-server-${az}`, {
        instanceType: "t3.micro",
        securityGroups: [ sg.name ],
        ami: ami,
        availabilityZone: az,
        userData: "#!/bin/bash\n"+
            `echo 'Hello, World -- from ${az}!' > index.html\n` +
            "nohup python -m SimpleHTTPServer 80 &",
        tags: { "Name": "web-server" },
    });
    ips.push(server.publicIp);
    hostnames.push(server.publicDns);

    alb.attachTarget(`web-target-${az}`, server);
}

export const url = listener.endpoint.hostname;

The index.ts file should now have the following contents:

import * as aws from "@pulumi/aws";
import * as awsx from "@pulumi/awsx";

export = async () => {
    const ami = aws.getAmi({
        filters: [{ name: "name", values: ["amzn-ami-hvm-*-x86_64-ebs"] }],
        owners: [ "137112412989" ],
        mostRecent: true,
    }).then(ami => ami.id);

    const sg = new aws.ec2.SecurityGroup("web-secgrp", {
        ingress: [
            { protocol: "icmp", fromPort: 8, toPort: 0, cidrBlocks: ["0.0.0.0/0"] },
        ],
    });

    const alb = new awsx.lb.ApplicationLoadBalancer("web-traffic", {
        external: true,
        securityGroups: [ sg.id ],
    });
    const listener = alb.createListener("web-listener", { port: 80 });

    const ips: any[] = [];
    const hostnames: any[] = [];
    const azs = await aws.getAvailabilityZones()
    for (const az of azs.names) {
        const server = new aws.ec2.Instance(`web-server-${az}`, {
            instanceType: "t3.micro",
            securityGroups: [ sg.name ],
            ami: ami,
            availabilityZone: az,
            userData: "#!/bin/bash\n"+
                `echo 'Hello, World -- from ${az}!' > index.html\n` +
                "nohup python -m SimpleHTTPServer 80 &",
            tags: { "Name": "web-server" },
        });
        ips.push(server.publicIp);
        hostnames.push(server.publicDns);

	    alb.attachTarget(`web-target-${az}`, server);
    }
    return {
	    url: listener.endpoint.hostname,
    };
}

Deploy these updates:

pulumi up

This should result in a fairly large update and, if all goes well, the load balancer’s resulting endpoint URL:

Updating (dev):

     Type                                          Name                             Status      Info
     pulumi:pulumi:Stack                           iac-workshop-dev
 ~   ├─ aws:ec2:SecurityGroup                      web-secgrp                       updated     [diff: ~ingress]
 +   ├─ awsx❌ec2:Vpc                             default-vpc-eb926d81             created
 +   │  ├─ awsx❌ec2:Subnet                       default-vpc-eb926d81-public-0    created
 +   │  └─ awsx❌ec2:Subnet                       default-vpc-eb926d81-public-1    created
 +   └─ aws:lb:ApplicationLoadBalancer             web-traffic                      created
 +      ├─ awsx:lb:ApplicationTargetGroup          web-listener                     created
 +      │  ├─ awsx:lb:TargetGroupAttachment        web-target-us-west-2a            created
 +      │  │  └─ aws:lb:TargetGroupAttachment      web-target-us-west-2a            created
 +      │  ├─ awsx:lb:TargetGroupAttachment        web-target-us-west-2b            created
 +      │  │  └─ aws:lb:TargetGroupAttachment      web-target-us-west-2b            created
 +      │  ├─ awsx:lb:TargetGroupAttachment        web-target-us-west-2c            created
 +      │  │  └─ aws:lb:TargetGroupAttachment      web-target-us-west-2c            created
 +      │  └─ aws:lb:TargetGroup                   web-listener                     created
 +      ├─ awsx❌ec2:SecurityGroup                web-traffic-0                    created
 +      ├─ awsx:lb:ApplicationListener             web-listener                     created
 +      │  ├─ awsx❌ec2:IngressSecurityGroupRule  web-listener-external-0-ingress  created
 +      │  │  └─ aws:ec2:SecurityGroupRule         web-listener-external-0-ingress  created
 +      │  ├─ awsx❌ec2:EgressSecurityGroupRule   web-listener-external-0-egress   created
 +      │  │  └─ aws:ec2:SecurityGroupRule         web-listener-external-0-egress   created
 +      │  └─ aws:lb:Listener                      web-listener                     created
 +      └─ aws:lb:LoadBalancer                     web-traffic                      created

Outputs:
    hostnames: [
        [0]: "ec2-18-197-184-46.us-west-2.compute.amazonaws.com"
        [1]: "ec2-18-196-225-191.us-west-2.compute.amazonaws.com"
        [2]: "ec2-35-158-83-62.us-west-2.compute.amazonaws.com"
    ]
    ips      : [
        [0]: "18.197.184.46"
        [1]: "18.196.225.191"
        [2]: "35.158.83.62"
  + url      : "web-traffic-09348bc-723263075.us-west-2.elb.amazonaws.com"

Resources:
    + 20 created
    ~ 1 updated
    21 changes. 4 unchanged

Duration: 2m33s

Permalink: https://app.pulumi.com/workshops/iac-workshop/dev/updates/3

Now we can curl the load balancer:

for i in {0..10}; do curl $(pulumi stack output url); done

Observe that the resulting text changes based on where the request is routed:

Hello, World -- from us-west-2b!
Hello, World -- from us-west-2c!
Hello, World -- from us-west-2a!
Hello, World -- from us-west-2b!
Hello, World -- from us-west-2b!
Hello, World -- from us-west-2a!
Hello, World -- from us-west-2c!
Hello, World -- from us-west-2a!
Hello, World -- from us-west-2c!
Hello, World -- from us-west-2b!