In this guide we will discuss how one can create a customized RHEL 8 / CentOS 8 AMI for AWS using Image Builder. For those new to Image Builder, it is a tool used to create customized system images of Red Hat Enterprise Linux, including system images prepared for deployment on cloud platforms.

The image Builder automatically handles server setup details for each image output hence it is faster than manual methods of image creation. A command line tool composer-cli, is available, as well as a graphical user interface in the Cockpit web console.

Image Builder Blocks

  • Blueprint – This define customized system images by listing packages and customizations that will be part of the system. Blueprints are presented to the user as plain text in the Tom’s Obvious, Minimal Language (TOML) format.
  • Compose – Composes are individual builds of a system image, based on a particular version of a particular blueprint.
  • Customization – These are are specifications for the system, which are not packages. This includes users, groups, and SSH keys.

Image Builder output formats

The Image Builder enables you to build images for multiple output formats. See table below.

Description CLI name file extension
QEMU QCOW2 Image qcow2 .qcow2
Ext4 File System Image ext4-filesystem .img
Raw Partitioned Disk Image partitioned-disk .img
Live Bootable ISO live-iso .iso
TAR Archive tar .tar
Amazon Machine Image Disk ami .ami
Azure Disk Image vhd .vhd
VMware Virtual Machine Disk vmdk .vmdk
Openstack openstack .qcow2

Step 1: Install Image Builder packages

Before you can use Image Builder, the following packages need to be installed.

sudo yum -y install vim lorax-composer composer-cli cockpit-composer bash-completion

Enable Image Builder to start after each reboot:

sudo systemctl enable --now lorax-composer.socket

For UI access via Cockpit, enable it:

sudo systemctl enable --now cockpit.socket
sudo firewall-cmd --add-service=cockpit && sudo firewall-cmd --add-service=cockpit --permanent

Load the shell configuration script so that the autocomplete feature for the composer-cli command starts working immediately without reboot:

source  /etc/bash_completion.d/composer-cli

Step 2: Create Blueprint for Image Builder

We’ll use command line interface for this operation. But the same can be done from Cockpit web console. To use the interface, run the composer-cli command with suitable options and arguments.

This is the workflow Image Builder:

  1. Export (save) the blueprint definition to a plain text file
  2. Edit this file in a text editor
  3. Import (push) the blueprint text file back into Image Builder
  4. Run a compose to build an image from the blueprint
  5. Export the image file to download it

Add your $USER to the weldr group.

sudo usermod -aG weldr $USER
newgrp weldr

Creating an Image Builder blueprint:

$ vim rhel8-base.toml

Mine has been modified to look like below:

name = "rhel-8-base"
description = "A RHEL 8 Base Image"
version = "0.0.1"
groups = []

[[modules]]
name = "vim"
version = "*"

[[packages]]
name = "openssh-server"
version = "*"

[[packages]]
name = "rsync"
version = "*"

[[packages]]
name = "tmux"
version = "*"

[[packages]]
name = "git"
version = "*"

[[packages]]
name = "tree"
version = "*"

[[packages]]
name = "bash-completion"
version = "*"

[[packages]]
name = "lvm2"
version = "*"

[[packages]]
name = "wget"
version = "*"

[[packages]]
name = "firewalld"
version = "*"

[[packages]]
name = "python3"
version = "*"

[[packages]]
name = "python3-pip"
version = "*"

[[packages]]
name = "telnet"
version = "*"

[customizations.kernel]
append = "net.ifnames=0"

[[customizations.user]]
name = "rheladmin"
description = " RHEL Admin User"
password = "hashed-user-password"
key = "your-ssh-pub-key"
home = "https://computingforgeeks.com/home/rheladmin/"
shell = "https://computingforgeeks.com/usr/bin/bash"
groups = ["users", "wheel"]

Check documentation page for all entries and customizations.

Push the blueprint back into Image Builder:

$ composer-cli blueprints push rhel8-base.toml

List available Image builders:

$ composer-cli  blueprints list
example-atlas
example-development
example-http-server
rhel-8-base

Step 3: Create a system image with Image Builder

Pass the start option to build the image for your CentOS / RHEL machine.

$ composer-cli compose start BLUEPRINT-NAME IMAGE-TYPE

To see available image types, run:

$ composer-cli compose types
alibaba
ami
ext4-filesystem
google
live-iso
openstack
partitioned-disk
qcow2
tar
vhd
vmdk

So I’ll now start a compose using the blueprint created and output type.

$ composer-cli compose start rhel-8-base ami
Compose 036fb329-0443-48ad-9444-a1c70caa4b36 added to the queue

To check the status of the compose:

$ composer-cli compose status
036fb329-0443-48ad-9444-a1c70caa4b36 RUNNING  Sat Apr  4 15:41:12 2020 rhel-8-base     0.0.1 ami            

$ composer-cli compose status
036fb329-0443-48ad-9444-a1c70caa4b36 FINISHED Sat Apr  4 15:46:52 2020 rhel-8-base     0.0.1 ami              4668260352  

Once the compose is finished, download the resulting image file:

$ composer-cli compose image UUID

-- Example ---
$ composer-cli compose image 036fb329-0443-48ad-9444-a1c70caa4b36
036fb329-0443-48ad-9444-a1c70caa4b36-disk.ami: 4452.00 MB    

Step 4: Upload AMI Image to AWS

Install Python 3 and the pip tool:

sudo yum -y install python3 python3-pip

Install the AWS command-line tools with pip:

sudo pip3 install awscli

Configure the AWS command-line client according to your AWS access details:

$ aws configure
AWS Access Key ID [None]:
AWS Secret Access Key [None]:
Default region name [None]:
Default output format [None]:

Configure the AWS command-line client to use your bucket:

$ BUCKET=ami-image-bucket
$ aws s3 mb s3://$BUCKET

Confirm bucket creation:

$ aws s3 ls 
2020-04-04 15:49:47 ami-image-bucket

Create a vmimport S3 Role in IAM and grant it permissions to access S3:

printf '{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "vmie.amazonaws.com" }, "Action": "sts:AssumeRole", "Condition": { "StringEquals":{ "sts:Externalid": "vmimport" } } } ] }' > trust-policy.json

printf '{ "Version":"2012-10-17", "Statement":[ { "Effect":"Allow", "Action":[ "s3:GetBucketLocation", "s3:GetObject", "s3:ListBucket" ], "Resource":[ "arn:aws:s3:::%s", "arn:aws:s3:::%s/*" ] }, { "Effect":"Allow", "Action":[ "ec2:ModifySnapshotAttribute", "ec2:CopySnapshot", "ec2:RegisterImage", "ec2:Describe*" ], "Resource":"*" } ] }' $BUCKET $BUCKET > role-policy.json

aws iam create-role --role-name vmimport --assume-role-policy-document file://trust-policy.json

aws iam put-role-policy --role-name vmimport --policy-name vmimport --policy-document file://role-policy.json

Uploading an AMI image to AWS:

$ BUCKET=ami-image-bucket
$ AMI=036fb329-0443-48ad-9444-a1c70caa4b36-disk.ami
$ aws s3 cp $AMI s3://$BUCKET
upload: ./036fb329-0443-48ad-9444-a1c70caa4b36-disk.ami to s3://ami-image-bucket/036fb329-0443-48ad-9444-a1c70caa4b36-disk.ami

After the upload to S3 ends, import the image as a snapshot into EC2:

printf '{ "Description": "my-image", "Format": "raw", "UserBucket": { "S3Bucket": "%s", "S3Key": "%s" } }' $BUCKET $AMI > containers.json

aws ec2 import-snapshot --disk-container file://containers.json

Sample output:

{
    "ImportTaskId": "import-snap-0617ccf6944d82089",
    "SnapshotTaskDetail": {
        "DiskImageSize": 0.0,
        "Format": "RAW",
        "Progress": "3",
        "Status": "active",
        "StatusMessage": "pending",
        "UserBucket": {
            "S3Bucket": "ami-image-bucket",
            "S3Key": "036fb329-0443-48ad-9444-a1c70caa4b36-disk.ami"
        }
    }
}

Confirm import process:

$ aws ec2 describe-import-snapshot-tasks --filters Name=task-state,Values=active
{
    "ImportSnapshotTasks": [
        {
            "ImportTaskId": "import-snap-0617ccf6944d82089",
            "SnapshotTaskDetail": {
                "DiskImageSize": 4668260352.0,
                "Format": "RAW",
                "Progress": "94",
                "SnapshotId": "snap-0fd61ffa2f2cd4ad0",
                "Status": "active",
                "StatusMessage": "Preparing snapshot",
                "UserBucket": {
                    "S3Bucket": "ami-image-bucket",
                    "S3Key": "036fb329-0443-48ad-9444-a1c70caa4b36-disk.ami"
                }
            },
            "Tags": []
        }
    ]
}

Login to AWS and confirm existence of Snapshot.

<img alt="" data-ezsrc="https://kirelos.com/wp-content/uploads/2020/04/echo/Build-Custome-CentOS-RHEL-AWS-AMI-Using-Image-Bulder-01-1024×226.png" data-ez ezimgfmt="rs rscb8 src ng ngcb8 srcset" src="data:image/svg xml,”>

Create an image from the uploaded snapshot right clicking Snapshot on EC2 and selecting Create Image:

<img alt="" data-ezsrc="https://kirelos.com/wp-content/uploads/2020/04/echo/Build-Custome-CentOS-RHEL-AWS-AMI-Using-Image-Bulder-02-1024×326.png" data-ez ezimgfmt="rs rscb8 src ng ngcb8 srcset" src="data:image/svg xml,”>

Give Image a name and set virtualization type, disk size, description e.t.c.

<img alt="" data-ezsrc="https://kirelos.com/wp-content/uploads/2020/04/echo/Build-Custome-CentOS-RHEL-AWS-AMI-Using-Image-Bulder-03-1024×475.png" data-ez ezimgfmt="rs rscb8 src ng ngcb8 srcset" src="data:image/svg xml,”>

After creation, the image will be available on the AMI section.

<img alt="" data-ezsrc="https://kirelos.com/wp-content/uploads/2020/04/echo/Build-Custome-CentOS-RHEL-AWS-AMI-Using-Image-Bulder-05-1024×329.png" data-ez ezimgfmt="rs rscb8 src ng ngcb8 srcset" src="data:image/svg xml,”>

Reference:

Similar guides:

Automate RHEL and CentOS Installation on KVM with Kickstart

Build AWS EC2 Machine Images (AMI) With Packer and Ansible