Create ARM based Ubuntu EC2 instance with AWS CDK and Java

Hamza Sabljakovic
5 min readDec 5, 2022
AWS CDK

We will start by scaffolding a new CDK project for our infrastructure. The AWS CDK supports a number of different programming languages but the one we are going to use in this tutorial is Java. To get started with Java, run the following command:

mkdir ubuntu-arm && cd ubuntu-arm && cdk init app --language java

For more details or other languages check out the official AWS CDK documentation.

If everything goes well, you should see something similar to this:


Applying project template app for java
# Welcome to your CDK Java project!

This is a blank project for CDK development with Java.

The `cdk.json` file tells the CDK Toolkit how to execute your app.

It is a [Maven](https://maven.apache.org/) based project, so you can open this project with any Maven compatible Java IDE to build and run tests.

## Useful commands

* `mvn package` compile and run tests
* `cdk ls` list all stacks in the app
* `cdk synth` emits the synthesized CloudFormation template
* `cdk deploy` deploy this stack to your default AWS account/region
* `cdk diff` compare deployed stack with current state
* `cdk docs` open CDK documentation

Enjoy!

Initializing a new git repository...
hint: Using 'master' as the name for the initial branch. This default branch name
hint: is subject to change. To configure the initial branch name to use in all
hint: of your new repositories, which will suppress this warning, call:
hint:
hint: git config --global init.defaultBranch <name>
hint:
hint: Names commonly chosen instead of 'master' are 'main', 'trunk' and
hint: 'development'. The just-created branch can be renamed via this command:
hint:
hint: git branch -m <name>
Executing 'mvn package'
✅ All done!

The generated directories and files:

.
├── README.md
├── cdk.context.json
├── cdk.json
├── cdk.out
│ ├── UbuntuArmStack.assets.json
│ ├── UbuntuArmStack.template.json
│ ├── cdk.out
│ ├── manifest.json
│ └── tree.json
├── pom.xml
├── src
│ ├── main
│ │ └── java
│ │ └── com
│ │ └── myorg
│ │ ├── UbuntuArmApp.java
│ │ └── UbuntuArmStack.java
│ └── test
│ └── java
│ └── com
│ └── myorg
│ └── UbuntuArmTest.java
└── target
├── classes
│ └── com
│ └── myorg
│ ├── UbuntuArmApp.class
│ └── UbuntuArmStack.class
├── generated-sources
│ └── annotations
├── generated-test-sources
│ └── test-annotations
├── maven-archiver
│ └── pom.properties
├── maven-status
│ └── maven-compiler-plugin
│ ├── compile
│ │ └── default-compile
│ │ ├── createdFiles.lst
│ │ └── inputFiles.lst
│ └── testCompile
│ └── default-testCompile
│ ├── createdFiles.lst
│ └── inputFiles.lst
├── test-classes
└── ubuntu-arm-0.1.jar

26 directories, 20 files

As you can see, by default, the project name is based on the directory name where the cdk init is run. In this case, the ubuntu-arm.

Now that our project is generated, we can open it in the IDE and start making changes. Do not get overwhelmed by the number of files and directories, the only two relevant classes are:

UbuntuArmApp.java
UbuntuArmStack.java

Since this is a fairly simple setup, all of our code will be inside of the Stack (UbuntuArmStack).

In order to create a new EC2 instance, we will create two other “plumbing” resources. A VPC where we are going to put the instance and a security group with SSH access (TCP on port 22).

Without further ado, this is all the code it takes to create an ARM based Ubuntu instance.

package com.myorg;

import software.amazon.awscdk.Stack;
import software.amazon.awscdk.StackProps;
import software.amazon.awscdk.services.ec2.*;
import software.constructs.Construct;

import java.util.HashMap;
import java.util.Map;


public class UbuntuArmStack extends Stack {
public UbuntuArmStack(final Construct scope, final String id) {
this(scope, id, null);
}

public UbuntuArmStack(final Construct scope, final String id, final StackProps props) {
super(scope, id, props);

Vpc vpc = Vpc.Builder.create(this, id + "-vpc")
.vpcName(id + "-vpc")
.build();

final ISecurityGroup securityGroup = SecurityGroup.Builder.create(this, id + "-sg")
.securityGroupName(id)
.vpc(vpc)
.build();

securityGroup.addIngressRule(Peer.anyIpv4(), Port.tcp(22));

final Map<String, String> armUbuntuAMIs = new HashMap<>();
armUbuntuAMIs.put("eu-west-1", "ami-0d7409d480c699f24");

final IMachineImage armUbuntuMachineImage = MachineImage.genericLinux(armUbuntuAMIs);

final Instance engineEC2Instance = Instance.Builder.create(this, id + "-ec2")
.instanceName(id + "-ec2")
.machineImage(armUbuntuMachineImage)
.securityGroup(securityGroup)
.instanceType(InstanceType.of(
InstanceClass.BURSTABLE4_GRAVITON,
InstanceSize.SMALL
))
.vpcSubnets(
SubnetSelection.builder()
.subnetType(SubnetType.PUBLIC)
.build()
)
.vpc(vpc)
.build();
}
}

Keep in mind that you should update the UbunutArmApp class as well (where the main method is) to select a region of your choice and your account id.

If you want to run a different version of Ubuntu or in a different AWS region, you have to update the AMI id. There is an official Ubuntu EC2 AMI locator where you can easily filter and find the correct AMI.

package com.myorg;

import software.amazon.awscdk.App;
import software.amazon.awscdk.Environment;
import software.amazon.awscdk.StackProps;

public class UbuntuArmApp {
public static void main(final String[] args) {
App app = new App();

new UbuntuArmStack(app, "UbuntuArmStack", StackProps.builder()
.env(Environment.builder()
.account("94841111111") // Change to your account id
.region("eu-west-1") // Change to your region
.build())
.build());

app.synth();
}
}

To deploy, run

cdk deploy

You should get the following output in the terminal

✨  Synthesis time: 9.86s

UbuntuArmStack: building assets...

[0%] start: Building adebbf2a3d3cd768653daefa64b03855f1a48308fbbdd31acc9df882f612f744:94843111111-eu-west-1
[100%] success: Built adebbf2a3d3cd768653daefa64b03855f1a48308fbbdd31acc9df882f612f744:94843111111-eu-west-1

UbuntuArmStack: assets built

This deployment will make potentially sensitive changes according to your current security approval level (--require-approval broadening).
Please confirm you intend to make the following modifications:

IAM Statement Changes
┌───┬────────────────────────────────────────┬────────┬────────────────┬───────────────────────────┬───────────┐
│ │ Resource │ Effect │ Action │ Principal │ Condition │
├───┼────────────────────────────────────────┼────────┼────────────────┼───────────────────────────┼───────────┤
│ + │ ${UbuntuArmStack-ec2/InstanceRole.Arn} │ Allow │ sts:AssumeRole │ Service:ec2.amazonaws.com │ │
└───┴────────────────────────────────────────┴────────┴────────────────┴───────────────────────────┴───────────┘
Security Group Changes
┌───┬──────────────────────────────┬─────┬────────────┬─────────────────┐
│ │ Group │ Dir │ Protocol │ Peer │
├───┼──────────────────────────────┼─────┼────────────┼─────────────────┤
│ + │ ${UbuntuArmStack-sg.GroupId} │ In │ TCP 22 │ Everyone (IPv4) │
│ + │ ${UbuntuArmStack-sg.GroupId} │ Out │ Everything │ Everyone (IPv4) │
└───┴──────────────────────────────┴─────┴────────────┴─────────────────┘
(NOTE: There may be security-related changes not in this list. See https://github.com/aws/aws-cdk/issues/1299)

Do you wish to deploy these changes (y/n)?

Once the deployment is completed, you will see something like this

 ✅  UbuntuArmStack

✨ Deployment time: 204.98s

Stack ARN:
arn:aws:cloudformation:eu-west-1:94843111111:stack/UbuntuArmStack/d3f78a20-74a3-11ed-8290-028c5bfcf989

✨ Total time: 214.84s

Now that our EC2 instance is deployed, we can connect to it using built-in EC2 connect option in the AWS management console.

Connect to EC2 instance uding the built in EC2 instance connect

This will open a new session in the browser with access to the machine

Web browser based terminal access to EC2 instance

If you are following this tutorial as an exercise, remember to clean the created resources after you are done. This can be simply done by running the following CDK command.

cdk destroy

With output

Are you sure you want to delete: UbuntuArmStack (y/n)? y
UbuntuArmStack: destroying...

✅ UbuntuArmStack: destroyed

The full source code can be found on github

--

--