AWS Cloud Operations Blog

Creating Packer images using AWS System Manager Automation

If you run AWS EC2 instances in AWS, then you are probably familiar with the concept of pre-baking Amazon Machine Images (AMIs). That is, preloading all needed software and configuration on an EC2 instance, then creating an image of that. The resulting image can then be used to launch new instances with all software and configuration pre-loaded. This process allows the EC2 instance to come online and be available quickly. It not only simplifies deployment of new instances but is especially useful when an instance is part of an Auto Scaling group and is responding to a spike in load. If the instance takes too long to be ready, it defeats the purpose of dynamic scaling.

A popular tool used by customers to pre-bake AMIs is packer.io. Packer is an open source tool for creating identical machine images for multiple platforms from a single source configuration.

Packer is lightweight and many customers run it as part of a CI/CD pipeline. It is very easy to set up its execution with AWS CodeBuild, and make it part of your deployment process. However, many customers would like to manage the creation of their AMIs using AWS Systems Manager instead. We recently released the ability to execute custom Python or PowerShell scripts with Systems Manager Automation. See that details can be found here. With this new functionality, it is easier than ever to integrate packer with AWS Systems Manager Automation.

In this blog post, we walk through the process of using the new AWS:executeScript Automation action as part of a workflow to create custom AMIs using Packer. Before starting, you may want to get familiar with how the feature works. Here are some links for review:

Walkthrough

For our example, we are using an AWS-provided SSM document that automates the process of running Packer. You can find more information about this SSM document in the documentation.

Set up your environment

The first step to successfully build packer images with SSM is to set up all the requirements. Here is what you need:

  • AWS Account with administrator access so we can set up the different components. After the components are set up, it is possible to set up an IAM account with limited access to only execute the packer automation. More information can be found here.
  • IAM Role to execute the automation and also run the packer build. See the section on IAM credentials below.
  • Packer template file (we provide a sample one below for testing)

IAM credentials

To execute automation workflows, we must create an IAM role that can be used by the SSM service to perform the actions on your behalf. We’ve simplified the process of creating this role by providing a managed IAM Policy called AmazonSSMAutomationRole. This policy has the minimum requirements to execute Automation actions.

There is some good information on this topic here.

For Packer we also must add some additional permissions to be able to launch the temporary instance, tag it, and create the image. The packer features you are using dictates which additional permissions should be added to the role. For our example in this blog post, we use the steps provided here to create the role, and will also add the following inline policy to the role, for packer functions. This is only an sample and depending on how you are customizing your image, you may need to adjust it.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "iam:GetInstanceProfile"
            ],
            "Resource": [
                "arn:aws:iam::*:instance-profile/*"
            ],
            "Effect": "Allow"
        },
        {
            "Action": [
                "logs:CreateLogStream",
                "logs:DescribeLogGroups"
            ],
            "Resource": [
                "arn:aws:logs:*:*:log-group:*"
            ],
            "Effect": "Allow"
        },
        {
            "Action": [
                "s3:ListBucket"
            ],
            "Resource": [
                "arn:aws:s3:::packer-sample-bucket"
            ],
            "Effect": "Allow"
        },
{
            "Action": [
                "s3:GetObject"
            ],
            "Resource": [
                "arn:aws:s3:::packer-sample-bucket/*"
            ],
            "Effect": "Allow"
        },


        {
            "Action": [
                "ec2:DescribeInstances",
                "ec2:CreateKeyPair",
                "ec2:DescribeRegions",
                "ec2:DescribeVolumes",
                "ec2:DescribeSubnets",
                "ec2:DeleteKeyPair",
                "ec2:DescribeSecurityGroups"
            ],
            "Resource": [
                "*"
            ],
            "Effect": "Allow"
        }
    ]
}

You can use the following CloudFormation template to automate the creation of the IAM role required for this example:

AWSTemplateFormatVersion: "2010-09-09"
Description: "Template to create sample IAM role to execute packer automation using SSM"
Parameters: 
  PackerTemplateS3BucketLocation: 
    Type: String
    Description: Enter the name of the bucket where the packer templates will be stored. This is used to add permissions to the policy. For example, my-packer-bucket
Resources:
    SSMAutomationPackerRole:
        Type: "AWS::IAM::Role"
        Properties:
            RoleName: "SSMAutomationPackerCF"
            ManagedPolicyArns: [
              'arn:aws:iam::aws:policy/service-role/AmazonSSMAutomationRole'
            ]
            AssumeRolePolicyDocument: 
                Version: "2012-10-17"
                Statement: 
                  - 
                    Effect: "Allow"
                    Action: 
                      - "sts:AssumeRole"
                    Principal: 
                        Service: 
                          - "ec2.amazonaws.com"
                          - "ssm.amazonaws.com"

    SSMAutomationPackerInstanceProfile:
        Type: "AWS::IAM::InstanceProfile"
        Properties:
            InstanceProfileName: "SSMAutomationPackerCF"
            Roles:
              - !Ref SSMAutomationPackerRole

    SSMAutomationPackerInlinePolicy:
        Type: "AWS::IAM::Policy"
        Properties:
            PolicyName: "SSMAutomationPackerInline"
            PolicyDocument: 
                Version: "2012-10-17"
                Statement: 
                  - 
                    Effect: "Allow"
                    Action: 
                      - "iam:GetInstanceProfile"
                    Resource: 
                      - "arn:aws:iam::*:instance-profile/*"
                  - 
                    Effect: "Allow"
                    Action: 
                      - "logs:CreateLogStream"
                      - "logs:DescribeLogGroups"
                    Resource: 
                      - "arn:aws:logs:*:*:log-group:*"
                  - 
                    Effect: "Allow"
                    Action: 
                      - "s3:ListBucket"
                    Resource: 
                      - !Sub 'arn:aws:s3:::${PackerTemplateS3BucketLocation}'
                  - 
                    Effect: "Allow"
                    Action: 
                      - "s3:GetObject"
                    Resource: 
                      - !Sub 'arn:aws:s3:::${PackerTemplateS3BucketLocation}/*'
                  - 
                    Effect: "Allow"
                    Action: 
                      - "ec2:DescribeInstances"
                      - "ec2:CreateKeyPair"
                      - "ec2:DescribeRegions"
                      - "ec2:DescribeVolumes"
                      - "ec2:DescribeSubnets"
                      - "ec2:DeleteKeyPair"
                      - "ec2:DescribeSecurityGroups"
                    Resource: 
                      - "*"
            Roles: 
              - !Ref SSMAutomationPackerRole

    SSMAutomationPackerPassrolePolicy:
        Type: "AWS::IAM::Policy"
        Properties:
            PolicyName: "SSMAutomationPackerPassrole"
            PolicyDocument: 
                Version: "2012-10-17"
                Statement: 
                  - 
                    Sid: "SSMAutomationPackerPassrolePolicy"
                    Effect: "Allow"
                    Action: "iam:PassRole"
                    Resource: !GetAtt SSMAutomationPackerRole.Arn
            Roles: 
              - !Ref SSMAutomationPackerRole

Running a Packer automation workflow

Packer automates all the steps needed to configure an instance. For the purposes of this example, let’s say we need to configure an EC2 instance to be a LAMP (Linux, Apache, MariaDB, and PHP) server. This would require a number of steps and configuration changes. We have a good tutorial on how to do this manually here.

Let’s use packer to automate the process provided. If you walk through that example, you will notice that all the steps in that tutorial could be summarized with the following script. This script installs all the required software, then creates needed files.

sudo yum update -y 
sudo amazon-linux-extras install -y lamp-mariadb10.2-php7.2 php7.2 
sudo yum install -y httpd mariadb-server 
sudo systemctl start httpd 
sudo systemctl enable httpd 
sudo usermod -a -G apache ec2-user 
sudo chown -R ec2-user:apache /var/www 
sudo chmod 2775 /var/www && find /var/www -type d -exec sudo chmod 2775 {} \\; 
find /var/www -type f -exec sudo chmod 0664 {} \\; 
echo \"<?php phpinfo(); ?>\" > /var/www/html/phpinfo.php

Let’s use packer to perform all those steps and get our LAMP instance ready.

Packer uses a template file to define all the elements of the build process. We won’t go into all the details of how packer works, but you can find good information on that topic here.

Here is the packer template file we use:

{
    "builders": [
      {
        "type": "amazon-ebs",
        "region": "us-east-1",
        "source_ami": "ami-00068cd7555f543d5",
        "instance_type": "m5.large",
        "ssh_username": "ec2-user",
        "ami_name": "packer-testing-ebs-{{isotime | clean_resource_name}}",
        "ssh_timeout": "5m",
        "iam_instance_profile": "SSMAutomationPackerCF",
        "vpc_id": "vpc-4a5b512f",
        "subnet_id": "subnet-6285e349",
        "security_group_id": "sg-7c3a7c1b",
        "associate_public_ip_address": true,
        "run_tags": {
          "Name": "web-server-packer"
        },
        "tags": {
          "Name": "webserver"
        }
      }
    ],
    "provisioners": [
      {
        "type": "shell",
        "inline": [
            "sudo yum update -y",
            "sudo amazon-linux-extras install -y lamp-mariadb10.2-php7.2 php7.2",
            "sudo yum install -y httpd mariadb-server",
            "sudo systemctl start httpd",
            "sudo systemctl enable httpd",
            "sudo usermod -a -G apache ec2-user",
            "sudo chown -R ec2-user:apache /var/www",
            "sudo chmod 2775 /var/www && find /var/www -type d -exec sudo chmod 2775 {} \\;",
            "find /var/www -type f -exec sudo chmod 0664 {} \\;",
            "echo \"<?php phpinfo(); ?>\" > /var/www/html/phpinfo.php"
        ]
      }
    ]
  }
  

Let’s walk through some of the details of this template.

The template is divided in 2 main sections: builder and provisioners. A builder is the target platform and its configuration, that packer will use to build the image. In this case, we will be using amazon-ebs as the builder. The provisioner is the mechanism used to install and configure software on the instance. Packer provides a multitude of provisioners, including popular configuration management tools like Ansible, Puppet, and Chef. For our example, we will be using the shell provisioner which allows us to execute shell commands on the instance.

On the builder section for the shell provisioner you will find the following directives:

  • region: The Region where the packer instance is launched. This should match the Region of the specified VPC and Subnet.
  • source_ami: The AMI we are starting from. In this case, we are using the latest Amazon Linux 2 provided AMI.
  • instance_type: The instance type that is launched by Packer. If you are performing complex installations or compiling software as part of the packer run, it may be a good idea to make this a bigger instance. That allows the process to complete faster and could result in cost savings.
  • ssh_username: The default ssh username defined on the source_ami. In this case we are using the standard ec2_user for Amazon Linux 2
  • ami_name: The name of the resulting AMI. The name should be descriptive and follow a standard nomenclature for tracking purposes. In this case, we are leveraging a couple of packer functions to make the AMI name unique. These functions will append the date and time in a clean format at the end of the AMI name.
  • ssh_timeout: How long packer will wait for SSH to be ready on the instance.
  • vpc_id and subnet_id: The VPC and subnet where the instance will launch. Make sure this matches the settings in your account.
  • associate_public_ip_address: Launch an EC2 instance with a public IP address associated. This is important since the instance will must communicate with the Systems Manager APIs and will need access to the public internet. You can optionally configure Systems Manager to use an interface VPC endpoint in Amazon Virtual Private Cloud. But you must set this flag to true for packer to work properly. There is more information on that here and here.

Modify the provided sample file with the correct values for your environment. Then save the file to an S3 bucket for later use. Please note this should be the same bucket specified when creating the IAM Role that will be used to execute the automation. See the section on IAM Credentials on this blog post. Now that we have all the pieces to run the packer automation, let’s put it all together and walk through an example of how you do it on the console.

Step by step process

  1. Log in to the AWS Management Console with Administrator privileges.
  2. Click on Services, then go to the Systems Manager option.
  3. On the left pane under Actions and Change click on “Automation”
  4. Click on the “Execute Automation” button.
  5. On the Automation Document search field, enter “AWS-RunPacker”. This displays the AWS-RunPacker document in the results, click on it.
  6. Now, from within the detail of the AWS-RunPacker document, click on “Execute Automation”.
  7. Select Simple Execution.
  8. On the input parameters section for the TemplateS3BucketName field, enter the name of the bucket where you saved the packer template file. Ensure the IAM Role has access to the bucket.
  9. On the TemplateFileName field, enter the name of the template file you created. For example, image.json
  10. On the mode drop-down, select Build. Note, you can also at this point select validate or fix. Validate will check if your template file is valid, and fix will find backwards incompatible parts of the template. It will also bring it up to date, so it can be used with the latest version of Packer.
  11. On the “Force” field select True. This forces the build to run even if it finds an image with the same name.
  12. On the “AutomationAssumeRole” field, enter the ARN of the IAM Role that was created on the section IAM Credentials of this blog post. This is the IAM Role that is used to execute the automation workflow and build the packer image.
  13. Click on “Execute”. This starts the workflow and take you to the Execution Detail screen. Here you can monitor the progress of the execution.

Once the execution completes successfully, you have a brand-new AMI with all the components of a LAMP server. You can modify and customize this workflow to suit your needs, and simplify your deployments using Systems Manager and Packer.

Please note that there is a limit of 600 seconds for the execution of the aws:executeScript component that we are using to launch Packer. Because of that, if you add many provisioner steps you must ensure that the execution will not exceed this limit. One option to work around this is to use a larger instance type that will perform the steps faster.

Conclusion

In this blog post, we learned how to use the AWS-RunPacker SSM Document to create an automation workflow to build Packer images. Using SSM as part of your image pre-baking process improves your security posture, simplifies the process of building images and also provides auditability and centralized management.  For a complete solution to handle image creation pipelines, please reference the EC2 Image Builder service.  To learn more visit this blog.

About the Author

Andres Silva is a Principal Technical Account Manager for AWS Enterprise Support. He has been working with AWS technology for more than 9 years. Andres works with Enterprise customers to design, implement and support complex cloud infrastructures. When he is not building cloud automation he enjoys skateboarding with his 2 kids.