How to create an AWS EC2 Instance with Pulumi using Golang

Pulumi is an open source infrastructure as code tool for creating, deploying, and managing cloud infrastructure. Pulumi works with traditional infrastructures like VMs, networks, and databases, in addition to modern architectures, including containers, Kubernetes clusters, and serverless functions.

It leverages existing programming languages—TypeScript, JavaScript, Python, Go, . NET, Java, and markup languages like YAML—and their native ecosystem to interact with cloud resources through the Pulumi SDK. Pulumi let’s you build cloud applications and infrastructure by combining the safety and reliability of infrastructure as code with the power of familiar programming languages and tools.

In this guide, we will explore how to use golang to create an AWS EC2 instance using Pulumi.

Checkout related content:

Installing golang and pulumi

Before you get started using Pulumi, let’s run through a few quick steps to ensure your environment is set up correctly.

First we will need to install pulumi. If you are on a Linux machine, use this command to install.

curl -fsSL https://get.pulumi.com | sh

If you are on a Mac, you can use homebrew

brew install pulumi/tap/pulumi

Consult the installation page here if you are on a different OS. Confirm the installed version

➜ pulumi version
v3.34.1

Next, install the required language runtime, if you have not already. In my case, I am using golang. Checkout the go download and installation page here.

Check the installed golang version to ensure that it is working as expected

➜ go version
go version go1.18.3 darwin/arm64

Configure Pulumi to access your AWS account

Pulumi requires cloud credentials to manage and provision resources. You must use an IAM user account that has Programmatic access with rights to deploy and manage resources handled through Pulumi.

If you have previously installed and configured the AWS CLI, Pulumi will respect and use your configuration settings.

If you do not have the AWS CLI installed or plan on using Pulumi from within a CI/CD pipeline, retrieve your access key ID and secret access key and then set the AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables on your workstation.

export AWS_ACCESS_KEY_ID=<YOUR_ACCESS_KEY_ID>
export AWS_SECRET_ACCESS_KEY=<YOUR_SECRET_ACCESS_KEY>

As an optional step, if you have multiple AWS profiles set up, you can specify a different profile to use with Pulumi through one of the following methods:

  • Set AWS_PROFILE as an environment variable
  • After creating your project, run pulumi config set aws:profile <profilename>

Create a new project

Now that you have set up your environment by installing Pulumi, installing your preferred language runtime, and configuring your AWS credentials, let’s create your first Pulumi program.

mkdir citizixaws && cd citizixaws
pulumi new aws-go

The pulumi new command creates a new Pulumi project with some basic scaffolding based on the cloud and language specified.

If this is your first time running pulumi new or most other pulumi commands, you will be prompted to log in to the Pulumi service. The Pulumi CLI works in tandem with the Pulumi service in order to deliver a reliable experience. It is free for individual use, with features available for teams. Hitting ENTER at the prompt opens up a web browser allowing you to either sign in or sign up.

After logging in, the CLI will proceed with walking you through creating a new project.

First, you will be asked for a project name and description. Hit ENTER to accept the default values or specify new values.

Next, you will be asked for the name of a stack. Hit ENTER to accept the default value of dev.

Finally, you will be prompted for some configuration values for the stack. For AWS projects, you will be prompted for the AWS region. You can accept the default value or choose another value like us-west-2.

What are projects and stacks? Pulumi projects and stacks let you organize Pulumi code. Consider a Pulumi project to be analogous to a GitHub repo—a single place for code—and a stack to be an instance of that code with a separate configuration. For instance, Project Foo may have multiple stacks for different development environments (Dev, Test, or Prod), or perhaps for different cloud configurations (geographic region for example). See Organizing Projects and Stacks for some best practices on organizing your Pulumi projects and stacks.

After the command completes, the project and stack will be ready.

Review the New Project

Let’s review some of the generated project files:

  • Pulumi.yaml defines the project.

  • Pulumi.dev.yaml contains configuration values for the stack you just initialized.

  • main.go is the Pulumi program that defines your stack resources.

Let’s examine main.go .

package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v5/go/aws/s3"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		// Create an AWS resource (S3 Bucket)
		bucket, err := s3.NewBucket(ctx, "my-bucket", nil)
		if err != nil {
			return err
		}

		// Export the name of the bucket
		ctx.Export("bucketName", bucket.ID())
		return nil
	})
}

This Pulumi program creates a new S3 bucket and exports the name of the bucket.

ctx.Export("bucketName", bucket.ID())

Adding code to create an EC2 Instance

Since we are creating an ec2 instance, replace the code in main.go with the following:

package main

import (
	"fmt"

	"github.com/pulumi/pulumi-aws/sdk/v5/go/aws/ec2"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	instanceDetails := map[string]string{
		"ubuntu2204srv": "ami-07b5347738d362307",
	}
	pulumi.Run(func(ctx *pulumi.Context) error {

		for key, val := range instanceDetails {

			sg, err := ec2.NewSecurityGroup(ctx, key, &ec2.SecurityGroupArgs{
				Description: pulumi.String(fmt.Sprintf("SG for instance %v", key)),
				VpcId:       pulumi.String("vpc-xxxxxxxx"),
				Egress: ec2.SecurityGroupEgressArray{
					ec2.SecurityGroupEgressArgs{
						Protocol:   pulumi.String("-1"),
						FromPort:   pulumi.Int(0),
						ToPort:     pulumi.Int(0),
						CidrBlocks: pulumi.StringArray{pulumi.String("0.0.0.0/0")},
					},
				},
				Ingress: ec2.SecurityGroupIngressArray{
					ec2.SecurityGroupIngressArgs{
						Protocol:   pulumi.String("tcp"),
						FromPort:   pulumi.Int(0),
						ToPort:     pulumi.Int(65535),
						CidrBlocks: pulumi.StringArray{pulumi.String("0.0.0.0/0")},
					},
				},
			})

			if err != nil {
				return fmt.Errorf("could not create sg for %v: %v", key, err)
			}

			instance, err := ec2.NewInstance(ctx, key, &ec2.InstanceArgs{
				Ami:          pulumi.String(val),
				InstanceType: pulumi.String("t3.medium"),
				Tags: pulumi.StringMap{
					"Name": pulumi.String(key),
				},
				AssociatePublicIpAddress: pulumi.Bool(true),
				KeyName:                  pulumi.String("eutychus@main"),
				UserData: pulumi.String(fmt.Sprintf(`
					#!/bin/bash -e
					sudo apt update
					sudo apt -y upgrade
					sudo hostnamectl set-hostname %v.citizix.com
					sudo yum install -y vim wget curl
				`, key)),
				SubnetId:            pulumi.String("subnet-xxxxxxxx"),
				VpcSecurityGroupIds: pulumi.StringArray{sg.ID()},
			})
			if err != nil {
				return fmt.Errorf("could not create instance %v: %v", key, err)
			}

			ctx.Export(fmt.Sprintf("%v:id", key), instance.ID())
			ctx.Export(fmt.Sprintf("%v:publicIP", key), instance.PublicIp)
			ctx.Export(fmt.Sprintf("%v:privateIP", key), instance.PrivateIp)
		}
		return nil
	})
}

In the code above, we are defining a map of instances with the name and their ids. We are then looping the instances and for each creating a security group and an instance. Please note that the instance will be created in an existing VPC and a subnet that supports instances with public IPs. Please update accordingly.

Finally, we are exporting the instance details.

Deploy the Stack

Let’s go ahead and deploy your stack:

pulumi up

This command evaluates your program and determines the resource updates to make. First, a preview is shown that outlines the changes that will be made when you run the update:

➜ pulumi up
Previewing update (unstable)

View Live: https://app.pulumi.com/citizix/citizixaws/unstable/previews/ffr6shggs-e273-4570-9e1a-392656603dc4

     Type                      Name                 Plan
 +   pulumi:pulumi:Stack       citizixaws-unstable  create
 +   ├─ aws:ec2:SecurityGroup  ubuntu2204srv        create
 +   └─ aws:ec2:Instance       ubuntu2204srv        create

Resources:
    + 3 to create

Do you want to perform this update?  [Use arrows to move, enter to select, type to filter]
  yes
> no
  details

Once the preview has finished, you are given three options to choose from. Choosing details will show you a rich diff of the changes to be made. Choosing yes will create your new Security group and Ec2 instance in AWS. Choosing no will return you to the user prompt without performing the update operation.

Do you want to perform this update? yes
Updating (unstable)

View Live: https://app.pulumi.com/citizix/citizixaws/unstable/updates/6

     Type                      Name                 Status
 +   pulumi:pulumi:Stack       citizixaws-unstable  created
 +   ├─ aws:ec2:SecurityGroup  ubuntu2204srv        created
 +   └─ aws:ec2:Instance       ubuntu2204srv        created

Outputs:
    ubuntu2204srv:id       : "i-02ff31a067b824bd8"
    ubuntu2204srv:privateIP: "10.2.40.220"
    ubuntu2204srv:publicIP : "35.160.127.123"

Resources:
    + 3 created

Duration: 34s

Remember the output you defined in the previous step? That stack output can be seen in the Outputs: section of your update. You can access your outputs from the CLI by running the pulumi stack output [property-name] command. For example you can print the name of your bucket with the following command:

➜ pulumi stack output ubuntu2204srv:publicIP
35.12.127.11

Running that command will print out the public IP for the instance.

If you are using the Pulumi Service backend, you can follow the “View Live” link displayed in the CLI output. This will open the update in the Pulumi Service, where you can view the output and explore detailed information about your stack such as its activity, resources, and configuration.

Next time you make an update, you can run pulumi up to preview and apply the changes.

Destroying the stack

Now that you’ve seen how to deploy changes to our program, let’s clean up and tear down the resources that are part of your stack.

To destroy resources, run the following:

$ pulumi destroy

You’ll be prompted to make sure you really want to delete these resources. This can take a minute or two; Pulumi waits until all resources are shut down and deleted before it considers the destroy operation to be complete.

➜ pulumi destroy
Previewing destroy (unstable)

View Live: https://app.pulumi.com/citizix/citizixaws/unstable/previews/ffr6shggs-c456-4ed9-be36-2acf0738705e

     Type                      Name                 Plan
 -   pulumi:pulumi:Stack       citizixaws-unstable  delete
 -   ├─ aws:ec2:Instance       ubuntu2204srv        delete
 -   └─ aws:ec2:SecurityGroup  ubuntu2204srv        delete

Outputs:
  - ubuntu2204srv:id       : "i-02ff31a067b824bd8"
  - ubuntu2204srv:privateIP: "10.2.40.220"
  - ubuntu2204srv:publicIP : "35.12.127.11"

Resources:
    - 3 to delete

Do you want to perform this destroy?  [Use arrows to move, enter to select, type to filter]
  yes
> no
  details

To delete the stack itself, run pulumi stack rm. Note that this removes the stack entirely from the Pulumi Service, along with all of its update history.

Conclusion

Congratulations! You’ve successfully provisioned some cloud resources using Pulumi. By completing this guide you have successfully:

  • Created a Pulumi new project.
  • Created a new security group
  • Provisioned a new EC2 instance.
  • Attached the security group to the instances
  • Destroyed the resources you’ve provisioned.
Last updated on Oct 14, 2024 11:46 +0300
comments powered by Disqus
Citizix Ltd
Built with Hugo
Theme Stack designed by Jimmy