Project: AWS containerized webserver management using Docker and Ansible
Tags: aws, ansible, docker, project
Published: 2023-06-07 00:32:35

Goal: 
To run an Ansible Playbook on top of a Bastion Host Server to configure two EC2 instances into webservers on AWS. We want to use our host machine’s IP for the bastion host, and we want to make sure everything is connected securely over SSH. We will also secure our setup through the use of firewalls AKA security group policies.

Terminology to be familiar with for this project:

What is a Bastion Host?
Also known as a ‘jump box’, a bastion host is a dedicated server that lets authorized users access a private network from an external network, such as the internet. Because of its exposure to potential attack, a bastion host must minimize the chances of penetration.

What is Ansible?
Ansible is a tool used to provision the underlying infrastructure of an environment, virtualized hosts and hypervisors, network devices, and bare metal servers.

What is an Ansible Playbook?
Ansible playbooks are used as a way to repeat, re-usable, simple configurations and multi-machine deployment systems. It is well suited to deploying complex applications. If you need to execute a task more than once, write a playbook and put it under source control.

What is Docker & Docker Hub?
Docker is the world’s largest repository of container images.
Docker Hub is a container image library where you can create an account and access the repository.

You can also be cool and create your own libraries and upload them to add to the repository for other people to use!

NOTE:
Before building any project, you want to plan out and understand what it is you’re trying to do and not only how to make it happen, but how to do it right. Security is job zero and it starts from the beginning of a project.

How do we fulfill our goal?

1. Create our Bastion Host Server using AWS EC2 instance creation
2. Connect our Host Machine to the Bastion Host Server using Gitbash and allow the Bastion Host to use our Host machine’s IP Address
3. We will have a private key made for us and saved to our Host machine when we create our first instance on EC2. We will need to share the private key access with our Bastion Host in order to communicate. The CSP takes care of handling our public key for the pair.
4. We will need to install Docker on our Bastion Host so that we can obtain a specific Ansible container from the repository as well as accessing our playbook.
5. Once Docker is installed and we have installed our Ansible ISO image file, we need to share our private key with our Ansible container so that it can securely communicate with our two EC2 instances/VMs and configure them into webservers.
6. We need to establish connection from our Ansible container to our two VMs and once that is done via ping, then we can run our Ansible Playbook to do whatever we need it to do (in this case, just to install a couple applications and convert our two instances into webservers).

Now that we have a pretty clear idea about what we want to accomplish, it's always helpful to create a nice little diagram for visual reference. I used draw.io since I'm a noob, but you can use whatever you're familiar with.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xjjvgdzsujc7y1kxaspn.png)

We start by launching our Bastion Host using Amazon EC2, which is as easy as searching for 'EC2' from the management console and clicking 'Launch Instance'. During the process I also set up our first firewall/security group rule of allowing only my IP to be able to SSH into the instance over port 22 and create a new key pair for security.

Once that's done, we add two more rules to the same security group:
1 allowing HTTP traffic over port 80 from anywhere (IPv4)
and another allowing All ICMP IPv4 traffic from anywhere

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qhepe8oqutnggxidako6.png)

Now that our Bastion Host Server has been created and our security group is configured appropriately, we will click 'connect' to open up a page with connection options. We'll be using SSH, so we copy our SSH address info from the instance SSH tab and open Gitbash to connect to it from our Host Machine.

CMD: ssh -i “[key-name.pem” ec2-user@[copied instance ID from SSH tab]

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3p6lcv82ecvokim6b375.png)

Pretty bird~

Note: Make sure you are in the directory where your private key is stored on your host machine so that it can be accessed to establish the connection.

Next, we’ll open a new terminal so that we maintain connection with our Bastion Host in one window and use our Host machine in the other.

On our Host machine, we’ll use ‘secure copy’ to copy our private key over to our Bastion Host server. While doing so, we will also create a directory for the key to exist inside its new location.

CMD: scp -i “key-name.pem” key-name.pem ec2-user@[copied instance ID]:/home/ec2-user

Note: Be sure not to forget the new directory at the end of the command. You may name the directories however you'd like. This is just what I used.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wnaw3f2k7a09ry7nnoin.png)

If this worked, when we go back to our connected Bastion Host window, we should see the key when we use ‘ls’ to search the directory.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9jx7ryyvzb17s778hct7.png)

Before we close our Host terminal, let's check our IP address using ipconfig command and make a note of it. It should match our Private IPv4 Address on the instance home page.

Returning to our Bastion Host, we will now install Docker and start it on our machine!

Commands to update VM and install Docker package:
CMD: sudo yum update
CMD: sudo yum install docker -y

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eyw4vu12wvu6c3ciyx2d.png)

Once complete, we can start Docker:
CMD: sudo service docker start

Now that docker is installed and started, we can check the networks on it:

CMD: sudo docker network ls

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xy1r1xmq7cit5wim9jxu.png)

Since we want Ansible to run inside of our host network and we want our Bastion Host to be running off of the same IP address as our machine we’re hosting the instance on, we should check the addresses associated with it as well.

Once the networks match, it's time to pull our Ansible container from docker.

CMD: sudo docker run -it --network host cyberxsecurity/ansible /bin/bash

To break this command down, ‘docker run’ means to create the ansible container using the docker image we define later in the command (cybersecurity/ansible), connect to or initialize it (it), place it within this specified network (--network host), and run it for me (run). 

We’re downloading the provided ISO image file for our Ansible container that someone else created in Docker as a container and running it—in this case ‘cyberxsecurity/ansible’. –network host tells it which network we want to add Ansible under and ‘bin/bash’ tells it what programming language we want to use to communicate with Ansible when we want to run configuration commands on our machines.

Once the image is added on top of our Bastion Host within our designated network, we should automatically see that our username has changed to the root of the Ansible container. 


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qpi4gd345qpmt0ocb6h3.png)

It should also be followed by the network address we provided. Let’s check and see if they match!

CMD: ifconfig

In the terminal, the root should show the IP of your host machine. If it doesn't, you'll have to re-do the previous steps.

Now that our Host machine and our Bastion Host machine are (hopefully) on the same network along with our Ansible container, if we were to add additional servers, they should all be able to communicate with each other over SSH using our security group firewall settings, right? At least, that's the idea!

Before that can happen though, we need to copy our private key into our newly added Ansible container, so that it can communicate with our servers.

Keeping our Ansible window open and connected, if we open up a new terminal via command prompt and type in the command to copy our private key and then using another specific command, we can check the containerID for our new Ansible container that we added on top of our Bastion Host inside of our network.

First, we'll open a new gitbash terminal to connect once again using SSH to our Bastion Host.

We will then use a command to check our Container ID:
CMD: sudo docker ps

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b68q5b3fe1dcxgqha2py.png)

Now we need to use a command to copy our key into our Ansible container (using the ID above). We will also define the path it gets copied to.

CMD: sudo docker cp key-name.pem [container ID goes here]:/root/key-name.pem

To check if the key made it to our container, we can return to our terminal window running Ansible and use ‘ls’.

Now the fun part! Let’s create our two servers that we'll be configuring using our Ansible playbook!

The two servers will be the same as our Bastion Host, with the same private key pair and security group, just different names.

Note:
If you want to save time, create a new instance and then while you're in the window viewing your instances, check the box for the one you just created, click on the 'Actions' Dropdown, then click 'Image and Templates' then click 'Launch More Like this'. Just remember to change the name of the instance and ensure it has the same key pair and security group, then create!

You should have something like this:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/atmcgr62udvxxfhytumb.png)

Now that we have our new servers, we need to test their connectivity within our network and if our Ansible container can communicate with them.

We’ll start with testing out our first server, aptly named, ‘Server1’.

We’ll do this by copying the private IPv4 address from the instances page after checking the box for our instance.

Then we will return to our Ansible machine and from root, we will ping our machine!

CMD: ping [IPv4 address of 1st machine]

You should know right away if it worked or not.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mqoq7iw46bdev8jw34h4.png)

Now we just need to SSH from our Ansible root to 'Server1'.

CMD: ssh -i key-name.pem ec2-user@[IPv4 private server address]

Huh, it isn't connecting, is it? Any idea why that might be?

If you guessed it had something to do with our security group aka firewall configurations, then you’d be correct!

Let’s fix that!

As of now, our inbound rules say that anything trying to connect with our network via SSH outside of our own IP address will be denied.

We need to add a new inbound rule that allows traffic traveling through SSH from our Ansible container. For that, we will need to find out the IP address tied to Ansible. We can see the IP right after the root, so just copy that. We'll also add '/32' to the end of it to make sure we have enough ground to cover.

Now we can try again!

CMD: ssh -i key-name.pem ec2-user@[IPv4 private Server1 address]


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h6nv3skzp51b0fvp217i.png)

Success!

Repeat this process until you are able to SSH into all servers you wish to connect.

Everything Ansible does is through SSH, so the procedure of connecting to each individual server using SSH cannot be skipped. As demonstrated, just because you can ping something, doesn’t mean it is going to be able to communicate across the network.

We were able to SSH successfully into our two instances! So now we can use ansible to configure our servers into webservers for hosting whatever we want!

Let’s return to our Bastion Host and Ansible machine terminals.

In the Ansible terminal, we’ll look at two important files that we need to configure. Remember to back out of the Server using the 'exit' command to get back to ansible root before using the following command.

CMD: ls /etc/ansible

You should see a hosts file. We need to edit this file...

CMD: nano /etc/ansible/hosts


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7w600rixdjxg08hjix2d.png)

Let’s locate the header ‘[webservers]’, remove the hashtag so that it isn’t ignored, and paste the two Private IPv4 addresses from our servers on their own lines below the header.

Adding these addresses tells Ansible which machines will receive the configurations we add to Ansible’s configuration file. 

It should end up looking like this:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3n1p2o4js17f37n6akq5.png)

Remember to save the changes made to the hosts and configuration files! You can do so multiple ways, but I like to use CTRL+X (exit), Y (yes) and then Enter.

Next, we’ll try a special ping command unique to ansible to make sure our settings are correct:

CMD: ansible -m ping all --key-name.pem

You should get an error, because we haven’t edited the configuration file. Let’s do that now.

CMD: nano /etc/ansible/ansible.cfg

We are going to locate ‘remote user’ and change it from ‘root’ to our AWS username, ‘ec2-user’

Before:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j9571o6grabk64uv71l2.png)

After:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6mjwgnxmr291o52gcxt0.png)

Save and exit.

Try to run the command again. If successful, you should now be able to create a Playbook and run it on your connected machines!

Note:
It’s important to know that the commands associated with playbooks change depending on the Operating System and the configurations you want to perform. Keep this in mind whenever working with Ansible Playbooks on different operating systems and cloud environments!

Let’s use our playbook command to run our code that will convert our two instances into webservers!

First, we’ll create a file called webservers.yml in the /etc/ansible directory:
CMD: nano /etc/ansible/webservers.yml

The following will be copied and pasted into our new yml file:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2ogrcxwgvbwiw4xfnlxv.png)

This is all we need to do here, so we can save the file, and now we are ready to convert our servers!

Before that, let’s check and see the results of what we have done so far!

Go ahead and copy-paste the PUBLIC IP addresses for each of your servers into your web browser to check and see if they are Apache webservers. 

They aren’t, right? But they are about to be with a single command that runs our playbook!

CMD: ansible-playbook /etc/ansible/webservers.yml --key-name.pem -u ec2-user

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dfoq3a8xo7e6f5g3eywk.png)

Congratulations! You have just used Ansible to configure two EC2 instance servers into webservers to host a website or other fun stuff on AWS! Do with it what you will! Or delete all your resources and do it all over again for practice!

NOTE: If you don't plan on using them right away, I would suggest deleting your resources so that you do not incur an unwanted bill. :)