Automating Deployment with AWS Lambda and Systems Manager

Navya ANavya A
6 min read

In modern software development, automation is key to achieving efficiency and reliability in deployment processes. In this blog post, we will walk through a step-by-step guide on how to automate deployments on Amazon EC2 instances using AWS Lambda and AWS Systems Manager (SSM). Additionally, we will explore how to set up a trigger using AWS CodeCommit for seamless integration.

Architecture:

Prerequisites

  1. AWS Account Setup: Ensure you have an active AWS account with the necessary permissions to create Lambda functions, IAM roles, and utilize Systems Manager.

  2. CodeCommit Repository: Set up a CodeCommit repository to store your deployment configuration.

  3. Lambda A and Lambda B: Create two Lambda functions - Lambda A to trigger the deployment and Lambda B to perform specific tasks after successful deployment.

Step 1: Configuring IAM Roles

IAM (Identity and Access Management) roles are crucial for defining the permissions needed by different AWS services.

EC2 Instance Role

Inline Policies

  1. Inline Policy - InlinePolicyName

    • Description: Briefly describe the purpose of this inline policy.

    • Permissions:

      • ssm:SendCommand

      • Other necessary permissions for your specific use case.

    {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Effect": "Allow",
                "Action": "ssm:SendCommand",
                "Resource": "arn:aws:ec2:REGION:ACCOUNT NUMBER:instance/INSTANCE-ID"
            }
        ]
    }
  1. SSM Managed Policy - AmazonSSMManagedInstanceCore

    • Description: This policy grants necessary permissions for Systems Manager to manage EC2 instances.

Lambda Execution Role

Attached Policies

  1. AWS Managed Policy - AmazonEC2ReadOnlyAccess

    • Description: Provides read-only access to EC2 instances.
  2. AWS Managed Policy - AmazonSSMFullAccess

    • Description: Grants full access to AWS Systems Manager.

    • Permissions:

      • ssm:*

      • Full access to all Systems Manager actions.

  3. AWS Managed Policy - AWSCodeCommitFullAccess

    • Description: Provides full access to CodeCommit repositories.

    • Permissions:

      • codecommit:*

      • Full access to all CodeCommit actions.

  4. AWS Managed Policy - AWSLambdaExecute

    • Description: Grants basic permissions for Lambda function execution.

    • Permissions:

      • AWSLambdaExecute policy permissions.
  5. AWS Managed Policy - AWSLambdaRole

    • Description: Grants Lambda functions permissions to create and manage other AWS resources.

    • Permissions:

      • AWSLambdaRole policy permissions.

Inline Policies

  1. Inline Policy - LambdaInvokePolicy

    • Description: Additional inline policy providing specific permissions for Lambda A to invoke other Lambda functions.

    • Permissions:

      • lambda:InvokeFunction

      • Specify ARNs of Lambda functions that Lambda A should be allowed to invoke.

        {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Effect": "Allow",
                    "Action": "lambda:InvokeFunction",
                    "Resource": "arn:aws:lambda:REGION:ACCOUNT NUMBER:function:lambda_b"
                }
            ]
        }

Writing the Deployment Script

Create a deployment script (deploy.sh) that contains the necessary commands to deploy your application. Ensure it has executable permissions (chmod 777 deploy.sh).

Creating a CodeCommit Repository

If you haven't set up a CodeCommit repository yet, follow these steps:

  1. Open the AWS CodeCommit Console in your AWS Management Console.

  2. Click on the Create repository button.

  3. Provide a repository name, like "test", optionally add a description.

  4. Click on Create repository to create your CodeCommit repository.

  5. Upload a "config.yaml" file that contains

     enable: true
    

Lambda A - Triggering Deployments

Write the Lambda A function in Python. This function retrieves the content of the config.yaml file from your CodeCommit repository. If the content contains "enable: true," Lambda A triggers Lambda B.

# -*- coding: utf-8 -*-

import json
import boto3

def lambda_handler(event, context):
    # boto3 clients
    codecommit = boto3.client("codecommit")
    lambda_client = boto3.client("lambda")

    # Retrieve the content of the config.yaml file from CodeCommit
    repo_name = "test"
    commit_specifier = "main"  # This can be a branch name or a commit ID
    file_path = "config.yaml"

    try:
        response = codecommit.get_file(
            repositoryName=repo_name,
            commitSpecifier=commit_specifier,
            filePath=file_path
        )
        config_content = response["fileContent"].decode("utf-8")

        # Check if the content of the config.yaml file contains "enable: true"
        if "enable: true" == config_content.strip():
            print("The 'enable: true' condition is met. Triggering Lambda B.")

            # Trigger Lambda B
            lambda_b_name = "lambda_b"
            lambda_b_payload = {}  # You can pass any additional payload if needed

            lambda_client.invoke(
                FunctionName=lambda_b_name,
                InvocationType='Event',
                Payload=json.dumps(lambda_b_payload)
            )

            return {"statusCode": 200, "body": json.dumps("Lambda B triggered successfully.")}
        else:
            # Log a message indicating that the condition is not met
            print("The 'enable: true' condition is not met. Skipping Lambda B.")

    except Exception as e:
        # Handle exceptions, log errors, and optionally raise or return an error response
        print(f"Error retrieving or processing config.yaml file: {e}")
        return {"statusCode": 500, "body": json.dumps("Error processing config.yaml file.")}

    return {"statusCode": 200, "body": json.dumps("Thanks from Srce Cde!")}

Lambda B - Post-Deployment Tasks

Write the Lambda B function that performs tasks after a successful deployment. In this example, Lambda B invokes specific actions, but you can customize it based on your requirements.

# -*- coding: utf-8 -*-

import time
import json
import boto3

def lambda_handler(event, context):
    # boto3 client
    client = boto3.client("ec2")
    ssm = boto3.client("ssm")

    # getting instance information
    describeInstance = client.describe_instances()

    InstanceId = []
    # fetching instance id of the running instances
    for i in describeInstance["Reservations"]:
        for instance in i["Instances"]:
            if instance["State"]["Name"] == "running":
                InstanceId.append(instance["InstanceId"])

    # looping through instance ids
    for instanceid in InstanceId:
        # command to run deployment.sh on instance
        response = ssm.send_command(
            InstanceIds=[instanceid],
            DocumentName="AWS-RunShellScript",
            Parameters={
                "commands": ["chmod +x /deploy.sh", "/deploy.sh"]
            }
        )

        # fetching command id for the output
        command_id = response["Command"]["CommandId"]

        time.sleep(3)

        # fetching command output
        output = ssm.get_command_invocation(CommandId=command_id, InstanceId=instanceid)
        print(output)

    return {"statusCode": 200, "body": json.dumps("Thanks from Srce Cde!")}

Configuring Lambda A Trigger

Now, let's configure the trigger for Lambda A:

  1. Open the AWS Lambda Console.

  2. Click on your Lambda A function (lambda_a) to open its configuration.

  3. In the designer section, click on Add trigger.

  4. From the trigger configuration options, select CodeCommit.

  5. In the "Repository" dropdown, choose the CodeCommit repository you created earlier.

  6. In the "Branch" dropdown, select main. This means Lambda A will be triggered whenever changes are pushed to the main branch.

  7. Optionally, configure the trigger to filter by file path or file name if needed.

  8. Click on Add to add the CodeCommit trigger.

Update Lambda A Configuration

  1. Open the AWS Lambda Console.

  2. Find and click on the lam``bda_a function to open its configuration.

  3. In the "Configuration" tab, scroll down to the "General configuration" section.

  4. Locate the "Timeout" setting.

  5. Change the value from the current setting to 1 minute (60 seconds).

  6. Save the changes.

Update Lambda B Configuration

  1. Open the AWS Lambda Console.

  2. Find and click on the lam``bda_b function to open its configuration.

  3. In the "Configuration" tab, scroll down to the "General configuration" section.

  4. Locate the "Timeout" setting.

  5. Change the value from the current setting to 1 minute (60 seconds).

  6. Save the changes.

By updating the timeout setting to 1 minute for both Lambda functions, you ensure that each function has a maximum execution time of 1 minute before it's automatically terminated. This setting is useful to prevent functions from running indefinitely and incurring unnecessary costs.

Testing the Trigger

Now, whenever changes are pushed to the main branch of your CodeCommit repository, Lambda A will be triggered. It will then check

Conclusion:

In conclusion, automating deployment with AWS Lambda and Systems Manager offers a scalable and efficient solution. IAM roles, Lambda functions, and CodeCommit integration combine for a responsive and reliable deployment pipeline. By adjusting scripts, configurations, and testing thoroughly, developers can achieve streamlined and high-quality software deployments. Happy deploying!

1
Subscribe to my newsletter

Read articles from Navya A directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Navya A
Navya A

๐Ÿ‘‹ Welcome to my Hashnode profile! I'm a passionate technologist with expertise in AWS, DevOps, Kubernetes, Terraform, Datree, and various cloud technologies. Here's a glimpse into what I bring to the table: ๐ŸŒŸ Cloud Aficionado: I thrive in the world of cloud technologies, particularly AWS. From architecting scalable infrastructure to optimizing cost efficiency, I love diving deep into the AWS ecosystem and crafting robust solutions. ๐Ÿš€ DevOps Champion: As a DevOps enthusiast, I embrace the culture of collaboration and continuous improvement. I specialize in streamlining development workflows, implementing CI/CD pipelines, and automating infrastructure deployment using modern tools like Kubernetes. โ›ต Kubernetes Navigator: Navigating the seas of containerization is my forte. With a solid grasp on Kubernetes, I orchestrate containerized applications, manage deployments, and ensure seamless scalability while maximizing resource utilization. ๐Ÿ—๏ธ Terraform Magician: Building infrastructure as code is where I excel. With Terraform, I conjure up infrastructure blueprints, define infrastructure-as-code, and provision resources across multiple cloud platforms, ensuring consistent and reproducible deployments. ๐ŸŒณ Datree Guardian: In my quest for secure and compliant code, I leverage Datree to enforce best practices and prevent misconfigurations. I'm passionate about maintaining code quality, security, and reliability in every project I undertake. ๐ŸŒ Cloud Explorer: The ever-evolving cloud landscape fascinates me, and I'm constantly exploring new technologies and trends. From serverless architectures to big data analytics, I'm eager to stay ahead of the curve and help you harness the full potential of the cloud. Whether you need assistance in designing scalable architectures, optimizing your infrastructure, or enhancing your DevOps practices, I'm here to collaborate and share my knowledge. Let's embark on a journey together, where we leverage cutting-edge technologies to build robust and efficient solutions in the cloud! ๐Ÿš€๐Ÿ’ป