Skip to content

AWS Global Accelerator With Pulumi Crosswalk

I’ve been digging into Pulumi lately, as it simplifies many aspects of infrastructure automation that Terraform struggles with. One of the things that makes Pulumi really great is their Crosswalk feature, which allows you to create what they call “day one” infrastructure with minimal code.

In this example, I’ll be building out an AWS Global Accelerator with a very simple HTTP static webpage frontend. The code is located on my Github here.


Web Servers


const pulumi = require('@pulumi/pulumi');
const aws = require('@pulumi/aws');
const awsx = require('@pulumi/awsx');

const vpc = new awsx.ec2.Vpc('custom', {
  cidrBlock: '172.16.8.0/24',
  numberOfAvailabilityZones: 'all',
  numberOfNatGateways: 1,
});

const sg = new awsx.ec2.SecurityGroup('webserver-sg', {vpc});

sg.createIngressRule('http-access', {
  location: new awsx.ec2.AnyIPv4Location(),
  ports: new awsx.ec2.TcpPorts(80),
  description: 'allow HTTP access from anywhere',
});

sg.createIngressRule('https-access', {
  location: new awsx.ec2.AnyIPv4Location(),
  ports: new awsx.ec2.TcpPorts(443),
  description: 'allow HTTPS access from anywhere',
});

sg.createEgressRule('outbound-access', {
  location: new awsx.ec2.AnyIPv4Location(),
  ports: new awsx.ec2.AllTcpPorts(),
  description: 'allow outbound access to anywhere',
});

const ami = pulumi.output(aws.getAmi({
  filters: [{
    name: 'name',
    values: ['amzn-ami-hvm-*-x86_64-ebs'],
  }],
  owners: ['137112412989'], // This owner ID is Amazon
  mostRecent: true,
}));

const role = new aws.iam.Role('instance-role', {
  assumeRolePolicy: {
    'Version': '2008-10-17',
    'Statement': [
      {
        'Sid': '',
        'Effect': 'Allow',
        'Principal': {'Service': 'ec2.amazonaws.com'},
        'Action': 'sts:AssumeRole',
      },
    ],
  },
  path: '/',
});

const rolePolicyAttachment = new aws.iam.RolePolicyAttachment('rpa', {
  role: role,
  policyArn: aws.iam.ManagedPolicies.AmazonSSMManagedInstanceCore,
});

const profile = new aws.iam.InstanceProfile('instance-profile', {role});

const userData =
  `#!/bin/bash
echo 'Hello, World!<br>' > index.html
REGION=$(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone | sed 's/\\(.*\\)[a-z]/\\1/')
echo "You are in region: \${REGION}<br>" >> index.html
PRIVATE_IP=$(curl http://169.254.169.254/latest/meta-data/local-ipv4)
echo "Private IP Address: \${PRIVATE_IP}<br>" >> index.html
PUBLIC_IP=$(curl http://169.254.169.254/latest/meta-data/public-ipv4)
echo "Public IP Address: \${PUBLIC_IP}<br>" >> index.html
nohup python -m SimpleHTTPServer 80 &`;

const server = new aws.ec2.Instance('webserver', {
  instanceType: 't3a.micro',
  vpcSecurityGroupIds: [sg.id], // reference the security group resource above
  subnetId: pulumi.output(vpc.publicSubnetIds)[0],  // reference the public subnet from the custom vpc above
  ami: ami.id,
  userData: userData,
  iamInstanceProfile: profile,
});

exports.publicIp = server.publicIp;
exports.publicHostName = server.publicDns;
exports.instanceId = server.id;

So what the heck is going on here?

  1. We are using Pulumi Crosswalk to create a VPC with CIDR 172.16.8.0/24. This VPC will create public and private subnets in all availability zones, and also provide us with a NAT gateway. And yes, all in 4 lines of code.
  2. Creating a Security Group with a few rules we will need.
  3. Get an AMI for our web servers (in this case the Amazon AMI). This is very similar to how Terraform does it.
  4. Create an IAM instance profile with SSM permissions.
  5. Lastly, create the server along with some user data that will show a simple web page with details of the server you are reaching.

Accelerator

Now we can create the accelerator.

const pulumi = require('@pulumi/pulumi');
const aws = require('@pulumi/aws');
const awsx = require('@pulumi/awsx');

const accelerator = new aws.globalaccelerator.Accelerator('example', {
  name: 'mbo-accelerator',
  enabled: true,
  ipAddressType: 'IPV4',
});

const listener = new aws.globalaccelerator.Listener('80', {
  acceleratorArn: accelerator.id,
  clientAffinity: 'SOURCE_IP',
  protocol: 'TCP',
  portRanges: [{
    fromPort: 80,
    toPort: 80,
  }],
});

const config = new pulumi.Config();

const europeInstance = new pulumi.StackReference(config.require('europeStack'));
const europeInstanceId = europeInstance.getOutput('instanceId');
const europeEndpoint = new aws.globalaccelerator.EndpointGroup('europe', {
  listenerArn: listener.id,
  healthCheckPath: '/',
  endpointGroupRegion: 'eu-central-1',
  endpointConfigurations: [{
    clientIpPreservationEnabled: true,
    endpointId: europeInstanceId,
    weight: 100,
  }],
});

const westInstance = new pulumi.StackReference(config.require('westStack'));
const westInstanceId = westInstance.getOutput('instanceId');
const westEndpoint = new aws.globalaccelerator.EndpointGroup('oregon', {
  listenerArn: listener.id,
  healthCheckPath: '/',
  endpointGroupRegion: 'us-west-2',
  endpointConfigurations: [{
    clientIpPreservationEnabled: true,
    endpointId: westInstanceId,
    weight: 100,
  }],
});


exports.publicDnsName = accelerator.dnsName;
exports.listenerId = listener.id;

As you can see, Pulumi allows you to reference stacks. We are using the StackReference feature of Pulumi to get the Public IP addresses of each server so we can attach them to the accelerator.


Conclusion

Hopefully I’ve helped convince you that Pulumi makes it easier to do things that would otherwise take more Terraform code (or a custom Terraform module) to deploy. If you’d like to see the full code, you can go to my Github and see it. If you’d like to deploy it, there are instructions in the README for that.

Thanks for reading!

Published inInfrastructure as CodeProgramming

Be First to Comment

Leave a Reply

Your email address will not be published. Required fields are marked *