Ansible in AWS Lambda

As member of the new PaaS generation, AWS Lambda provides a “serverless” computing platform: the developer no longer has to worry about capacity planning, code deploys, server updates, etc., basically a step closer to the “NoOps” controversial Nirvana.

When python support was added to AWS Lambda, I wondered if it would be possible to use it replicate some of the Ansible Tower functionality: features like running on-demand playbooks or call ansible from cloud-init.

According to AWS documentation, a deployment package is required. The first step to create it is installing ansible into a target directory: lambda, as seen below.

pip install ansible==1.9.4 -t lambda

Now let’s call ansible using the python API:

Zip the contents, so as to create the deployment package:

pushd lambda; \
zip -r ../lambda.zip ../ansible_lambda.py ../test.yml * ; \
popd

Configure a AWS Lambda function:

However, when testing, the following message appears:

So what happened? What did we do wrong? According to the AWS Lambda FAQ:

Q: What if I need scratch space on disk for my AWS Lambda function?A: Each Lambda function receives 500MB of non-persistent disk space in its own /tmp directory.

In light of these instructions, we’ll have to take a step back and perform a tweak in the code:

After packaging and uploading the new code:

{
"localhost": {
"changed": 1,
"failures": 0,
"ok": 2,
"skipped": 0,
"unreachable": 0
}
}

Amazon API Gateway

To leverage the lambda capabilities, API Gateway will work as an entry point with a simple POST http endpoint:

Use the previously created lambda function:

Deploy it:

Test it using curl:

$ curl -X POST -d'{}' https://xxxxxx.execute-api.us-west-2.amazonaws.com/test
{
"localhost": {
"unreachable": 0,
"skipped": 0,
"ok": 2,
"changed": 1,
"failures": 0
}
}

Before going any further, be sure to check API Gateway limits:

10-second timeout for both AWS Lambda and HTTP back-end integrations. This limit cannot be changed currently.

A possible workaround to this limitation is Amazon SNS. A high level architecture diagram for this approach:

First create a new SNS topic:

Write a new lambda function that will push new messages from API Gateway into SNS:

The IAM Role for this function:

Change ansible python script to support SNS messages:

Modify ansible lambda function to trigger based on SNS messages:

Update the API Gateway configuration and point to the SNS message pusher function:

Deploy the API and test:

$ curl -X POST -d'{"playbook": "test.yml", "inventory": ["localhost"]}' https://xxxxxx.execute-api.us-west-2.amazonaws.com/test
{
"ResponseMetadata": {
"HTTPStatusCode": 200,
"RequestId": “xxx"
},
"MessageId": “xxx"
}

To debug we need to check cloudwatch log:

PLAY [all]**********************************************************
GATHERING FACTS ****************************************************
ok: [localhost]
TASK: [shell echo test] ********************************************
changed: [localhost]

Everything is now working as expected!

Next Steps

I have shown how to run ansible in lambda, however some extra steps will have to be made in order for it to be useful:

  • Use paramiko for ssh connections;
  • Download playbooks and roles from a S3 bucket;
  • Download/Decrypt secrets using KMS;
  • Notify, for example, a slack channel for results;
  • Workaround the 300 seconds execution limit for lambda functions.