Cloud9 is a web based IDE running inside an AWS VPC. Benefits of running a development environment inside AWS is the ability to get access to AWS services, without having to expose these to outside of your AWS account. AWS Cloud9 also provides the possibility for collaborative development. Team members can watch and write code simultaneously and chat with each other in the Cloud9 IDE.
In this article I will show how to set up an AWS Cloud9 instance, create and attach a custom security group rule and attach an Elastic (static) IP address with Terraform. I will also show an example of a script for automating the process of configuration your Cloud9 instance.
This article already assumes you have Terraform installed, if not, how to install Terraform shows how to do this. It is also necessary that you have Terraform with AWS set up, if not, set up Terraform with AWS, explains how this can be done.
Terraform code for Cloud9
Let's start with writing some Terraform code to create our Cloud9 instance. Using your favourite code editor, create a new project and a file named cloud9.tf under project root. Add the following content.
resource "aws_cloud9_environment_ec2" "cloud9_instance" {
name = "cloud9_instance"
instance_type = "t2.medium"
automatic_stop_time_minutes = 30
tags = {
Terraform = "true"
}
}
Let's go through the resource block in this piece of code, and have a look at the properties we have set.
- Name will be the name of our instance.
- Instance_type is the size of our Cloud9 instance. T2 instance are a good choice for a variety of general-purpose workloads including micro-service development and small to medium databases.
- Automatic_stop_time_minutes is the number of minutes the instance stays turned on without any activity.
- Tags. It is normally good practice to add tags to our AWS instances. In this example, I add the tag, Terraform, so I'll know the instance has been created with Terraform when viewing the instance in the AWS console.
Setting up Security Groups
In order for us to be able to integrate with other AWS services, expose APIs or even just be able to browse a website we run locally on Cloud9, we need to create an ingress in a AWS security group rule and add this to the default Cloud9 security rule. Create a new file named security_groups.tf and add the following content to it:
data "aws_security_group" "cloud9_secgroup" {
filter {
name = "tag:aws:cloud9:environment"
values = [
aws_cloud9_environment_ec2.cloud9_instance.id
]
}
}
resource "aws_security_group_rule" "tcp_8080" {
type = "ingress"
from_port = 8080
to_port = 8080
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
ipv6_cidr_blocks = ["::/0"]
security_group_id = data.aws_security_group.cloud9_secgroup.id
}
Let's explain what is going on in this code snippet. First we have to get our Cloud9's default security group by using a filter argument (this used to be very poorly documented in the Terraform documentation, hopefully Hashicorp has improved their docs). The output from the aws_security_group data block call will give us the id of the Cloud9 security group, which we need in the aws_security_group_rule resource block.
In this example, I've chosen to create an ingress rule and open inbound traffic for port 8080. This will allow you to visit port 8080 in a web browser for example.
- type: Defines the type of rule we want to create. Since we want to open up for incoming traffic, we use "ingress".
- from_port, to_port: Defines the range of ports we want to open. Since we only want 8080, we set both to 8080.
- protocol: Defines which protocol to allow. Most common are TCP and UDP.
- cidr_blocks: A range of Ipv4 addresses, in CIDR block notation. This allows for traffic from the specified Ipv4 addresses.
- ipv6_cidr_blocks: A range of Ipv6 addresses, in CIDR block notation. This allows for traffic from the specified Ipv6 addresses.
- security_group_id: Which security group we want to apply this rule to.
Adding an Elastic IP
Now, we'll create and attach an EIP address to our instance. The IP address of our Cloud9 instance changes every time we take it down and set it back up again. We are now going to create and attach an EIP (Elastic IP) address to our Cloud9, so our Cloud9 instance keeps the same IP address at all times. This can be done by creating a file called eip.tf and add the following content.
data "aws_instance" "cloud9_instance" {
filter {
name = "tag:aws:cloud9:environment"
values = [
aws_cloud9_environment_ec2.cloud9_instance.id,
]
}
}
# Create elastic ip
resource "aws_eip" "cloud9_eip" {
instance = data.aws_instance.cloud9_instance.id
vpc = true
}
The first data source block of code is necessary since we need the id of our Cloud9 instance. We do this same way we retrieved the security group id. Then, we can add the aws_eip resource which will create an EIP and attach it using the id of the instance. In this example, we do not specify the address we want to use, so AWS will just choose a random EIP from its public_ipv4_pool. You can read more about AWS EIP here.
- instance: Specifies which instance we want to attach our EIP to, using its instance id.
- vpc: If the EIP is from the same vpc as the instance, set this to true. This is true since we do not specify our own EIP address.
Creating automation script
Now, we'll go through one example of how an automation script can be written and used. We'll create a file called, cloud9-setup.sh, and add the following content.
#!/bin/bash
set -e
if [ ! -f /home/ec2-user/environment/cloud9-setup/resume-after-reboot ]
then
### Code that should run before shell is rebooted. ###
script="bash /home/ec2-user/environment/cloud9-setup/cloud9-setup.sh"
echo "$script" >> ~/.bashrc
sudo touch /home/ec2-user/environment/cloud9-setup/resume-after-reboot
source ~/.bashrc
else
sed -i '/bash/d' ~/.bashrc
sudo rm -f /home/ec2-user/environment/cloud9-setup/resume-after-reboot
source ~/.bash_profile
### Code that should run after shell is restarted. ###
fi
Since I want to install Pyenv and then use Pyenv to set my Python version, the shell needs a reboot in between those steps for Pyen to work. In our script, we want to reboot the shell, and continue the script afterwards. The code above is therefore necessary for this to work. Here is how it works:
Before the shell reboots.
- First, we set an if statement. This checks for our a file called resume-after-reboot. This file acts as a flag, to tell the script which part to run. When we first run it, the file does not exist so we run the first part. This would be where we would installed e.g Pyenv.
- Secondly, after we install Pyenv and we want to set python version with Pyenv we need to reboot the shell. In order to do this we assign the cloud9-script.sh script to a variable called script. We then echo this variable into ~/.bashrc. This is what makes sure the script runs when the shell reboots.
- Since we don't want the script to run the first part once again after the shell is rebooted, we add the flag file resume-after-reboot.
- Finally, we reboot the shell with the source ~/.bashrc command.
After the shell reboots
- We first remove the script variable from our ~/.bashrc file.
- Then, we remove our flag file.
- Finally, we reboot the shell with source ~/.bash_profile since that's where we'll add Pyenv.
Now, we'll go on to installing software such as Pyenv, git repositories and set some environment variables in our Cloud9 environment. We'll add the following to the first part of the if-statement in the file cloud9-setup.sh we created above.
- First move we into the environment directory (can be done anywhere, I just chose to use this location in this article. This is where our git repositories will be).
- We then run sudo yum update -y to update any available updates on the system. We also install some packages that are required for Pyenv to work.
- Then, we check to see if Pyenv is already installed, if not, run the commands to install it.
- Lastly, we set some environment variables. We use the cat <<EOF syntax since we want to add multi-line text into a file with bash.
Now, we add the code that should run after the shell has been rebooted. We'll add the code after the source ~/.bash_profile command under the else-condition.
### Installing python.3.8.0 ###
python_version=$(python --version)
file=$python_version
if [ "$file" == "Python 3.8.0" ];
then
echo "Python 3.8.0 is already installed."
else
pyenv install 3.8.0
fi
pyenv global 3.8.0
python_version=$(python --version)
file=$python_version
echo "$file"
### Install Pipenv ###
pip install pipenv --user
cd my-git-repo
pipenv install -dev
- First we check to see what Python version is already being used. If Python 3.8.0 is already installed and used, we skip the install. If not, we install Python 3.8.0 using the pyenv install command.
- We then set global python version to 3.8.0 using the pyenv global command. After this we echo out the version of python we are currently using (should be 3.8.0 at this point).
- We also install pipenv using Pip. We add the --user to the command to avoid being root for pipenv to be installed.
- Finally, we change directory to our git repository and run the command pipenv install -dev to install all of our packages we have in our Pipfile.
This is only one of many applications that we could do with this script. I usually store this script in a git repository but you could store it wherever you'd like. Whenever you take down and set back up your Cloud9 instance, just run this script to automate some of the internal Cloud9 configuration.
Conclusion
Now, we should have a Cloud9 instance up and running. This Cloud9 instance has an EIP attached to it and keeps this address even after the Cloud9 instance has been taken down. Our Cloud9 ingress has opened up for traffic on port 8081. We've also created a script that we can run each time we set up our Cloud9 instance to install git repos, pyenv, set Environment variables etc.