An Old Noob #8 - Building an Ansible Test Environment in AWS
Tags: beginners, aws, devops, tutorial
Published: 2022-07-01 00:40:17

It was recommended by a colleague that i might enjoy delving a bit into [Ansible](https://www.ansible.com/) and suggested i check out  the [Complete DevOps Ansible Automation Training](https://www.udemy.com/course/complete-ansible-devops-automation-training/) instructed by [Imran Afzal](https://www.linkedin.com/in/imran-afzal-4092473/).  I can already see why this course was recommended, the instructor is very knowledgeable and easy to follow.  

In the course, however, he explains how to create a test environment using virtualization software from your local machine, which is easily going to hit the needs of the wider audience.  In the interest of the... thinner audience (?), i decided i'd build mine in AWS.  

Everything here is using the AWS Management Console and commonly-known IT tools which are easily downloaded and installed.

First thing after logging into the AWS Management Console is to create an EC2 Security Group, which is really just an odd name for server-level firewall (my 2¢).  "But we haven't created our EC2 instances yet," you ask?  This is true, and you'll see why this is first on my list (i hope) as i continue.  

![AWS EC2 Security Group](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qe8754r9vylzfqf7w5ge.png)

I gave my Security Group a descriptive name and then configured the only inbound rule needed, SSH over port 22 from any IPv4 address.  If you're doing this from home, i recommend using My IP instead as this adds a significant layer of security to your environment without too much hassle.  I'm accessing this instance from different networks, so i went this route.  

Once the Security Group is created, it's back to the EC2 dashboard to configure the first instance for launch.

![AWS Launch Instance](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/08u5q43ip6nc5zz268lz.png)

Exciting, no?  

It's time to choose a descriptive name and an OS for my Ansible control node.  Instructor Afzal installs CentOS on Oracle VirtualBox VMs in his course, so i chose to install CentOS as well.  This wasn't visible in the AWS Quick Start list so i clicked 'Browse more AMIs' (Amazon Machine Image).

![AWS EC2 Console](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fgifg0ghqhw8upn0u57u.png)

A quick search for 'CentOS 7' and we have results.

![CentOS 7 AMI Search](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ho5l60qr421akmr30rnx.png)


![CentOS 7 AMI Search Result](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xn1aeuiuya08o25bpjss.png)


![CentOS 7 AMI Confirm](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6vt7f9fl0gpf3siwq194.png)

After the steps to select the OS it's time to choose the Instance type, basically how much hardware-level performance do you want to pay for?  I chose t2.small per the instructor's recommendations for the VirtualBox VMs (1 vCPU, 2GB memory).  The math comes out to $8.64 USD per month if this instance runs 24/7 in a 30-day month (if you're curious).  

![AWS Instance Types](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9w0nq39eu7jup8urdd6r.png)

When asked for a key pair i chose to create new.

![AWS Instance Key Pair Creation](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/40dwm7azcjcczzqhl7s7.png)

This is an asymmetric encryption method that i'll use to log into my instance once it's ready.  The public key is stored on the instance, the private key i keep, well... private.  

I gave my key pair a not-so-descriptive name this time around, left RSA selected as the encryption protocol and then chose .ppk as the key file format.  This is because i'm connecting from a Windows client and will be using [PuTTY](https://www.putty.org/) for this purpose.  


![AWS EC2 Key Pair Settings](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1xark1l6h2z3s28b4zu1.png)

:warning: _You should be prompted to download your .ppk file!  Be sure to do so as this is your only chance._

Onto the Network settings, this is where having already built your Security Group comes in handy - just Select existing security group, choose the group you created and we're done with this section.  Otherwise you're building out traffic rules in a poorly-suggested Security Group name (again, my 2¢) in the midst of doing all these other steps.

![AWS Instance Network Settings](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nh22ul2hymr2zomdfs8l.png)

My next step is to configure storage for the root drive of my instance.  I also want to configure some Advanced details.

![AWS EC2 Console](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wjsadnwatj99efsmyt9v.png)

There's a bit to do in Advanced details, i couldn't grab it all in a concise screenshot and didn't want to edit anything together to avoid possible confusion. 

- change Hostname type to Resource name
- change Shutdown behavior to Stop
- change Termination protection to Enable
- and change Tenancy to Shared  

The console offers explanations of each setting, so i won't go into those here.  Next it's just confirmation your instance is configured correctly and launch.

![AWS EC2 Launch Instance](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/70qn6cif9m2swv932br8.png)

For my client machine, i built a replica of the control node above only changing the hostname to something identifiable, like Client1.  Easy enough, now i've got 2 almost identical CentOS 7 AWS instances.

Now to connect to my control node, I allocate an Elastic IP to my VPC from Amazon's pool of IPv4 addresses and associate it with my control node instance.

![AWS Elastic IP](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x0so7mv7c86hn0yyvgqr.png)

![AWS Associate Elastic IP](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/itze1pamlkicnm9xb11z.png)

At this point i was prompted that i might want to associate an Internet gateway with my VPC to connect my private IPs in the VPC to the internet, and this is correct.  So i created an Internet gateway and confirmed connectivity to my control node from my local machine.

![Powershell Test-NetConnection](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8t0xzi5er7y0j643h3vt.png)

Now i'm ready to remote into my instance (whew!).  As i mentioned, i'll be using PuTTY for this.  If you've used PuTTY before (or even if not), this is pretty straightforward for the most part: enter the Elastic IP address, confirm port 22 and SSH connection type.  Then scroll down to the Connection category and select Auth.

![PuTTY Main Screen](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t8203nmwqrchgknsyhfg.png)

Here i browsed for my .ppk file that i saved when i created my key pair for my instances.  

![PuTTY Auth Screen](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xxj2zmcjzvy31a5zw98w.png)

> :bulb: **Tip:** Saving this PuTTY configuration will make things easier later on.

With this i was able to log into my control node for the first time using default OS credentials and authenticating to the public key on the instance.

![SSH Session Login](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/01c5bjvrrhetkoec0mzd.png)

Next i installed Ansible and confirmed python is installed on my instance (as it should be) using the following commands:

```bash
yum install epel-release
yum install ansible ansible-doc
python --version
```

If python's not installed:

```bash
yum install python -y
```

I can confirm the Ansible install a couple ways; here i chose to utilize Ansible's ping module on the localhost.  Of course if you prefer you could always run 
```bash
rpm -qa | grep ansible
```

![Ansible Successful Ping](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jlfd0hhp0tu2u3m0seyu.png)

Everything is good so far with the control node, so now i confirm that port 22 is open on the client instance using a nifty trick i didn't know existed until this project.

```bash
curl -vv telnet://DestinationServer:22
```

![Linux Telnet Example](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q6br9ffs7fknk00ap31o.png)

Fabulous!  We're closer to connecting to the Ansible client instance.  To remote into the client, i need to use the private key from the instance key pair. 

**But i downloaded that in .ppk format for PuTTY, not .pem for OpenSSH! And i can't redownload it!!**

![Friends Chandler No No No](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gix1rs9bw1w1hh5eoj1i.gif)

PuTTYgen to the rescue! The PuTTYgen module is part of the default install of the PuTTY utility and can convert your .ppk to a .pem in no time.  I followed the instructions [here](https://www.simplified.guide/putty/convert-ppk-to-ssh-key).  

Now that i have my private key in OpenSSH format, i just need to drop it on my control node instance.  Since i'm using a Windows client, i'll use [WinSCP](https://winscp.net/eng/download.php) for this.  

> :bulb: **Tip:** If you saved your PuTTY connection configuration earlier, WinSCP will detect and offer to migrate your configuration and private key file.  How easy is that?

[Instructions for transferring files to a Linux host with WinSCP](https://www.alibabacloud.com/blog/how-to-transfer-files-to-or-from-linux-servers-with-winscp_594040)

The private key is on the control instance in the correct format, nothin' left to do but SSH:

```bash
ssh -i dir/keyName user@hostAddress
```

![Remote SSH Successful Login](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n4dq97cl630qmmemxx0a.png)

Here you can see the instance number changed indicating i'm now working in a remote shell from the control node to the client instance.

Configuring Ansible on the control node to communicate with the client instance requires 2 final steps.  

1. Add the hostname/IP of the client host to the Ansible hosts file found in etc/ansible/hosts
2. Add the private key to ~/.ssh

```bash
ssh-agent bash
ssh-add ~/.ssh/keyName
```

Now for the confirmation...

![Successful Ansible Remote Ping](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h9djypggtrv12bba02ca.png)

Ansible is now communicating with the client instance!