By implementing Test-driven development and Continuous Integration for infrastructure code with Ansible and Molecule, we’ve done a huge step towards the right direction. But we should not exclude our cloud infrastructure! Here’s a guide on how to use Molecule with AWS EC2, naturally triggered by TravisCI.
Ansible & Molecule – blog series
Part 1: Test-driven infrastructure development with Ansible & Molecule
Part 2: Continuous Infrastructure with Ansible, Molecule & TravisCI
Part 3: Continuous cloud infrastructure with Ansible, Molecule & TravisCI on AWS
What about the cloud?
In the course of this blog post series, we have come quite far. We’re now able to write tests for our infrastructure code using Molecule & Testinfra . And these tests are executed by our CI platform every time we commit code or the cron job automatically pulls the trigger. So we gained a lot more trust in our code. And at the same time, our Ansible role can be executed by Molecule on multiple infrastructure providers like Docker and Vagrant. We just need to leverage the power of Molecule’s multi-scenario approach.
But wait! Didn’t they say Molecule was also able to use cloud infrastructure as well? Isn’t Molecule built on top of Ansible to be able to use possibly every infrastructure? It’s pretty cool to use local infrastructure – be it on our laptop or on TravisCI. But what about using a cloud infrastructure like Amazon Web Services (AWS) ? It would be great to have our Ansible roles automatically tested against that also. And it would also enable us to leave the ‘hello world’ examples and test the Ansible roles that need to invoke multiple instances at the same time, as required in projects like kubernetes-the-ansible-way . So we should have a look on how to use Molecule with AWS and continuously run every test with a CI platform like TravisCI.
AWS EC2 as Molecule’s infrastructure provider
The list of Molecule’s supported cloud infrastructure providers is quite long. And it should be, since Molecule is built right on top of Ansible. If you want to get a feeling of how comprehensive Ansible’s cloud modules are, just take a look at the docs. I am impressed every time I look into them, since this list is growing rapidly with every Ansible release.
logo sources: Molecule logo, Vagrant logo , VirtualBox logo , Ansible logo , Docker logo , Testinfra logo , pytest logo , Ubuntu logo
This blog series’ example project on GitHub will be enhanced here again so every step is fully comprehensible. As we already introduced Molecule’s multi-scenario approach in the second article , it should be no big deal to integrate Molecule’s AWS EC2 driver into a new scenario inside the existing example project. Assuming that you followed all the other steps mentioned in the previous posts, the only prerequisite to using Molecule’s AWS EC2 driver is to install the boto Python packages . They will provide interfaces to Amazon Web Services for Molecule, which itself uses Ansible’s ec2 module to interact with AWS. Simply install the boto packages via the Python package manager pip:
1pip install boto boto3
Configure Molecule to use AWS EC2
As the example project already consists of two scenarios – default
for Docker and vagrant-ubuntu
for the Vagrant infrastructure provider – we simply need to leverage Molecule’s molecule init scenario
command, which doesn’t initialize a full-blown new Ansible role like molecule init role
. It just integrates a new scenario inside a multi-scenario project. Let’s call our new scenario aws-ec2-ubuntu
. Simply put cd
into the Ansible role’s root directory molecule-ansible-docker-aws/docker
and run:
1molecule init scenario --driver-name ec2 --role-name docker --scenario-name aws-ec2-ubuntu
This should create a new directory aws-ec2-ubuntu
inside the docker/molecule
folder. We’ll integrate the results into our multi-scenario project in a second. First let’s have a look at what the project structure now looks like:
Compared to the Docker or Vagrant scenarios, Molecule generated a few files more this time. Next to the already well-known molecule.yml
there are also create.yml
and destroy.yml
playbooks. The prepare.yml
playbook and the documentation file INSTALL.rst
are again nothing new to us. So let’s dig into the generated molecule.yml first:
1scenario: 2 name: ec2 3 4driver: 5 name: ec2 6platforms: 7 - name: instance 8 image: ami-a5b196c0 9 instance_type: t2.micro 10 vpc_subnet_id: subnet-6456fd1f 11 12provisioner: 13 name: ansible 14 lint: 15 name: ansible-lint 16 enabled: false 17 playbooks: 18 converge: ../playbook.yml 19 20lint: 21 name: yamllint 22 enabled: false 23 24verifier: 25 name: testinfra 26 directory: ../tests/ 27 env: 28 # get rid of the DeprecationWarning messages of third-party libs, 29 # see https://docs.pytest.org/en/latest/warnings.html#deprecationwarning-and-pendingdeprecationwarning 30 PYTHONWARNINGS: "ignore:.*U.*mode is deprecated:DeprecationWarning" 31 lint: 32 name: flake8 33 options: 34 # show which tests where executed in test output 35 v: 1
Compared to the other scenarios, there’s only one main difference here: the driver.name
configuration uses ec2
and the platforms
key contains some AWS EC2-specific parameters. The platform is already pre-configured with a concrete Amazon Machine Image (AMI) and instance_type
alongside a specific vpc_subnet_id
. For now I just tuned the instance name to aws-ec2-ubuntu
, like we already did in our Docker and Vagrant scenarios. To keep the Molecule scenario name and the infrastructure instance names in sync is just a an assistance for us to be able to find out what part we are looking at right now.
As already mentioned, we have to tune some parts inside the molecule.yml
to fully match our project’s multi-scenario setup. First of all, the configuration parameter provisioner.playbook.converge
needs to be configured in a way that Molecule is able to find the single playbook.yml
. Also the verifier
section has to be enhanced to gain all the described advantages like supressed deprecation warnings and the better test result overview.
Having a glance at the create.yml
The command molecule init scenario
also generated the create.yml
. As this is a generated file, we don’t have to maintain it. But as a curious person who already loves Ansible, I couldn’t resist taking a look. At first it looks rather stunning – especially to the ones new to AWS. Let’s have a look at the create.yml (I’ve shortened it a bit):
1- name: Create 2 hosts: localhost 3 connection: local 4 gather_facts: false 5 no_log: "{{ not (lookup('env', 'MOLECULE_DEBUG') | bool or molecule_yml.provisioner.log|default(false) | bool) }}" 6 vars: 7 ssh_user: ubuntu 8 ssh_port: 22 9 10 security_group_name: molecule 11 security_group_description: Security group for testing Molecule 12 security_group_rules: 13 - proto: tcp 14 from_port: "{{ ssh_port }}" 15 to_port: "{{ ssh_port }}" 16 cidr_ip: '0.0.0.0/0' 17 - proto: icmp 18 from_port: 8 19 to_port: -1 20 cidr_ip: '0.0.0.0/0' 21 security_group_rules_egress: 22 - proto: -1 23 from_port: 0 24 to_port: 0 25 cidr_ip: '0.0.0.0/0' 26 27 keypair_name: molecule_key 28 keypair_path: "{{ lookup('env', 'MOLECULE_EPHEMERAL_DIRECTORY') }}/ssh_key" 29 tasks: 30 - name: Create security group 31 ec2_group: 32 name: "{{ security_group_name }}" 33 description: "{{ security_group_name }}" 34 rules: "{{ security_group_rules }}" 35 rules_egress: "{{ security_group_rules_egress }}" 36 37 ... 38 39 - name: Create keypair 40 ec2_key: 41 name: "{{ keypair_name }}" 42 register: keypair 43 44 - name: Persist the keypair 45 copy: 46 dest: "{{ keypair_path }}" 47 content: "{{ keypair.key.private_key }}" 48 mode: 0600 49 when: keypair.changed 50 51 - name: Create molecule instance(s) 52 ec2: 53 key_name: "{{ keypair_name }}" 54 image: "{{ item.image }}" 55 instance_type: "{{ item.instance_type }}" 56 vpc_subnet_id: "{{ item.vpc_subnet_id }}" 57 group: "{{ security_group_name }}" 58 instance_tags: 59 instance: "{{ item.name }}" 60 wait: true 61 assign_public_ip: true 62 exact_count: 1 63 count_tag: 64 instance: "{{ item.name }}" 65 register: server 66 with_items: "{{ molecule_yml.platforms }}" 67 async: 7200 68 poll: 0 69 70 - name: Wait for instance(s) creation to complete 71 async_status: 72 jid: "{{ item.ansible_job_id }}" 73 register: ec2_jobs 74 until: ec2_jobs.finished 75 retries: 300 76 with_items: "{{ server.results }}"
Skimming the code, we notice the usage of Ansible’s ec2_group module . It is used to maintain AWS EC2 security groups. Using the parameters rules
and rules_egress
, it configures appropriate firewall inbound and outbound rules for our Molecule EC2 instance.
After that, Ansible’s ec2_key module is used to create a new EC2 key pair and store it locally if none exists on your local machine already. Now the ec2 module takes over, which is able to create or terminate AWS EC2 instances without any hassle with the AWS GUI. Waiting for the EC2 instance to come up, the create.yml
playbook uses the async_status module on the pre-registered variable server.results
from the ec2
module.
The following Ansible modules are solely used to create an inline Ansible inventory, which is finally used to connect into the EC2 instance via SSH. The also generated destroy.yml
playbook is just the opposite to the create.yml
playbook and tears the created EC2 instance down.
Install and configure AWS CLI
Now let’s focus on our target again: using AWS EC2 as an infrastructure provider for Molecule. We want to be able to run our tests again! So what’s left to do? In order to be able to successfully run our Molecule tests with AWS EC2, we need to have the AWS CLI installed . Like Molecule or Ansible, we can do this via Python package manager pip :
1pip install awscli
Having done that, we should check if the AWS CLI was successfully installed. The aws --version
command should print out something like the following:
1$ aws --version 2aws-cli/1.16.80 Python/3.7.2 Darwin/18.2.0 botocore/1.12.70
In order to configure the AWS CLI to use the correct credentials and AWS region, we need to take a look at the AWS docs . According to them, the fastest way to accomplish a correct configuration is to run aws configure
:
1$ aws configure 2AWS Access Key ID [None]: AKIAIOSFODNN7EXAMPLE 3AWS Secret Access Key [None]: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY 4Default region name [None]: eu-central-1 5Default output format [None]: json
Insert your AWS access key and secret alongside your desired AWS region. For me, this is eu-central-1
.
Configure region & VPC subnet id
Using Molecule’s AWS EC2 has two configuration inconsistencies right now: the configuration of the AWS region and the VPC subnet id.
Every configuration parameter used for Molecule’s AWS connectivity is generated correctly by the command molecule init --driver-name ec2
. The credentials are picked up correctly from the file ~/.aws/credentials
, where AWS CLI stores them. Except the region
configuration, which can be obtained from ~/.aws/config
. Running a Molecule test without the additional setting of the AWS region as an environment variable currently results in this documented error :
1"msg": "Either region or ec2_url must be specified",
To fix this, we need to set the region as environment variable before running our Molecule tests against EC2:
1export EC2_REGION=eu-central-1
And there’s another thing left to do: we need to configure the correct vpc_subnet_id
inside our molecule.yml
. If we forget to do so, we’ll get errors like this:
1" raise self.ResponseError(response.status, response.reason, body)", 2 "boto.exception.EC2ResponseError: EC2ResponseError: 400 Bad Request", 3 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>", 4 "<Response><Errors><Error><Code>InvalidSubnetID.NotFound</Code><Message>The subnet ID 'subnet-6456fd1f' does not exist</Message></Error></Errors><RequestID>1c971ba2-0d86-4335-9445-d989e988afce</RequestID></Response>" 5]
Therefore we have to figure out the correct VPC subnet ID of our region. For this, we can simply use AWS CLI with aws ec2 describe-subnets
to find the correct ID inside the field Subnets.SubnetId
:
1$ aws ec2 describe-subnets 2 3{ 4 "Subnets": [ 5 { 6 "AvailabilityZone": "eu-central-1b", 7 "AvailabilityZoneId": "euc1-az3", 8 ... 9 "SubnetId": "subnet-a2efa1d9", 10 ... 11 }, 12 { 13 "AvailabilityZone": "eu-central-1c", 14 ... 15 }, 16 { 17 "AvailabilityZone": "eu-central-1a", 18 ... 19 } 20 ] 21}
Having the correct ID, we can head over to our molecule.yml and edit the vpc_subnet_id
so it contains the right value:
1scenario: 2 name: aws-ec2-ubuntu 3 4driver: 5 name: ec2 6platforms: 7 - name: aws-ec2-ubuntu 8 image: ami-a5b196c0 9 instance_type: t2.micro 10 vpc_subnet_id: subnet-a2efa1d9 11...
Choosing an AMI with Ubuntu 18.04
As stated on askubuntu.com , Amazon maintains an “official” Ubuntu 18.04 Amazon Machine Image (AMI). But this one isn’t available under the free tier right now. As this official image is just the same as the one from Canonical, we can easily switch to this alternative – since this is allowed to be used with the free tier.
Every publicly available Canonical AMI could be found with the ubuntu Amazon EC2 AMI Locator . Just fill in your region like eu-central-1
together with the desired Ubuntu version and the site should filter all available AMI images in this region:
As you may notice, there are several possible AMIs left. In our case, we need to choose the AMI ID with the Instance Type hvm:ebs-ssd
. This means our EC2 instance will use Amazon Elastic Block Storage memory (EBS) instead of instance storage. Only the EBS instance type is eligible for free tier usage right now. In my AWS region eu-central-1
the correct AMI ID for Ubuntu 18.04 is ami-080d06f90eb293a27
. Pick the AMI ID that corresponds to your region and then configure it inside the platforms.image
paramter in the molecule.yml:
1scenario: 2 name: aws-ec2-ubuntu 3 4driver: 5 name: ec2 6platforms: 7 - name: aws-ec2-ubuntu 8 image: ami-080d06f90eb293a27 9 instance_type: t2.micro 10 vpc_subnet_id: subnet-a2efa1d9 11...
Creating a EC2 instance with Molecule
Now we should have everything prepared to start running Molecule tests on AWS EC2. To create the infrastructure needed to run our tests on, all we have to do is invoke Molecule’s create
step. Therefore, we run the following command (including the --debug
flag so that we are able to see what’s going on):
1molecule --debug create --scenario-name aws-ec2-ubuntu
In the meantime we can look at our Ansible EC2 management console at eu-central-1.console.aws.amazon.com/ec2/v2/ . After giving the molecule create
command a go, we should see our first Molecule EC2 instance starting up:
If everything went fine in Molecule’s create
phase, the EC2 management console should show the running EC2 instance:
Run a first test on EC2 with Molecule
If the AWS EC2 instance was created successfully, we can move on to Molecule’s converge
phase. This should run our implemented Ansible role against the initialized EC2 instance. Therefore we need to execute:
1molecule converge --scenario-name aws-ec2-ubuntu
If that worked out fine, we’re finally where we wanted to be in the first place! We’re now able to run our Molecule tests in the verify
phase. In order to invoke this last step, we have to run:
1molecule verify --scenario-name aws-ec2-ubuntu
This should successfully execute our Testinfra test suite described in docker/molecule/tests/test_docker.py onto our EC2 instance. If everything went fine, it should output something like:
Cleaning up
Finally it’s time to terminate our EC2 instance. In order to achieve this, all we need to do is run a molecule destroy
command that will tear down our EC2 instance:
1molecule --debug destroy --scenario-name aws-ec2-ubuntu
Again we can take a look at our AWS EC2 management console:
Our EC2 instance should have reached the state terminated
. This state is similar to the stopped
state. The only difference is the additional step of also removing the attached EBS storage (see the AWS EC2 Instance Lifecycle chart for more info).
Here’s also a full asciinema cast containing all mentioned steps:
Continuous cloud infrastructure with Molecule, AWS EC2, and TravisCI
For me, the ultimate goal of this whole journey with Molecule was to be able to have TravisCI create cloud environments to execute and test Ansible roles on them. This is needed for “beyond-hello-world” projects like kubernetes-the-ansible-way .
To achieve this, we need to bring all our latest learnings together:
We’ll use Molecule to develop and test Ansible roles, leveraging cloud infrastructure providers like AWS and executing everything automatically with TravisCI – triggered by commits or cron jobs.
logo sources: Molecule logo , Vagrant logo , VirtualBox logo , Ansible logo , Docker logo , Testinfra logo , pytest logo , Ubuntu logo , TravisCI logo , GitHub logo
So let’s do it! We need to configure TravisCI by enhancing its configuration file .travis.yml :
1sudo: required 2language: python 3 4env: 5- EC2_REGION=eu-central-1 6 7services: 8- docker 9 10install: 11- pip install molecule 12- pip install docker-py 13 14# install AWS related packages 15- pip install boto boto3 16- pip install --upgrade awscli 17 18# configure AWS CLI 19- aws configure set aws_access_key_id $AWS_ACCESS_KEY 20- aws configure set aws_secret_access_key $AWS_SECRET_KEY 21- aws configure set default.region $DEPLOYMENT_REGION 22# show AWS CLI config 23- aws configure list 24 25script: 26- cd docker 27 28# Molecule Testing Travis-locally with Docker 29- molecule test 30 31# Molecule Testing on AWS EC2 32- molecule create --scenario-name aws-ec2-ubuntu 33- molecule converge --scenario-name aws-ec2-ubuntu 34- molecule verify --scenario-name aws-ec2-ubuntu 35- molecule destroy --scenario-name aws-ec2-ubuntu
First we need to set the region as an appropriate environment variable inside the env
parameter. Inside the install
section, we need to make sure that the same Python package additions are present that we already installed locally. This includes boto/boto3
and awscli
.
After that, we need to configure the AWS CLI to use the correct credentials and AWS region. This can be achieved by using the aws configure set
command. Heading over to the settings tab of our TravisCI project, we need to correctly insert the three environment variables AWS_ACCESS_KEY
, AWS_SECRET_KEY
, and DEPLOYMENT_REGION
:
The last part is to add the molecule commands to the script
section of the .travis.yml
.
Fixing current problems with boto on Travis
Sadly there’s a flaw while using the AWS connectivity library boto on TravisCI right now . This results in errors like:
1... 2 " File \"/tmp/ansible_ec2_payload_y3lfWH/__main__.py\", line 552, in <module>", 3 " File \"/home/travis/virtualenv/python2.7.14/lib/python2.7/site-packages/boto/__init__.py\", line 1216, in <module>", 4 " boto.plugin.load_plugins(config)", 5 "AttributeError: 'module' object has no attribute 'plugin'" 6 ] 7 } 8 9 PLAY RECAP ********************************************************************* 10 localhost : ok=6 changed=4 unreachable=0 failed=1 11 12ERROR: 13The command "molecule --debug create --scenario-name aws-ec2-ubuntu" exited with 2.
To fix this, there are two changes needed inside our .travis.yml . We need to set sudo to false
and use the environment variable BOTO_CONFIG=/dev/null
:
1sudo: false 2language: python 3 4env: 5- EC2_REGION=eu-central-1 BOTO_CONFIG="/dev/null" 6...
Remember to add the
BOTO_CONFIG
environment variable into the exact same line as the already existing variableEC2_REGION
. Otherwise Travis starts multiple builds with only one variable set to each build.
Now head over to Travis and check out the log. If everything went fine, the output should look green and somehow like this:
Continuous cloud infrastructure rocks!
Throughout the post we saw how easy it is to use a cloud infrastructure provider like AWS EC2 together with Ansible & Molecule. The boilerplate playbooks are generated by Molecule for us, enabling us at the same time to learn how to use Ansible to manage our cloud environments thanks to the enormous cloud module list. Now we’re able to develop and test our Ansible roles against multiple (cloud) infrastructures at the same time. This gives us much more trust in our infrastructure code. We can verify any time if our code is able to run on every infrastructure we can think of. Darn cool. 🙂
And doing the Molecule tests on our CI platform to verify our code on multiple infrastructures automatically takes the cake! This post outlined comprehensibly how to configure TravisCI to execute Molecule tests on AWS EC2 instances – starting, initializing and terminating the infrastructure as it is needed throughout the verification. The safety net has become thicker again. All this should enable us to finally test much more complex Ansible roles automatically with Molecule!
As always, there are things left to do. We can take a look at other cloud providers as well. Or we can dive into the zoo of tools of a certain provider. Just pick one of those tools and you’ll be able to write the next post! We also have some Molecule verifiers left and maybe we’ll finally see how to test a full Kubernetes setup . Stay tuned!
More articles
fromJonas Hecht
Your job at codecentric?
Jobs
Agile Developer und Consultant (w/d/m)
Alle Standorte
More articles in this subject area
Discover exciting further topics and let the codecentric world inspire you.
Gemeinsam bessere Projekte umsetzen.
Wir helfen deinem Unternehmen.
Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.
Hilf uns, noch besser zu werden.
Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.
Blog author
Jonas Hecht
Senior Solution Architect
Do you still have questions? Just send me a message.
Do you still have questions? Just send me a message.