Introduction

What is an Ansible Execution Environnement, by the way ?

As per RedHat definition an Ansible Execution Environment (EE) is a standard way to build and distribute the environment that automation runs in, which reduces complexity and makes it faster and simpler to develop and deploy automation.

More simple, an EE is a container, by default [Podman], which contains everything needed to execute ansible jobs and playbooks, including python, ansible-core, libraries and dependencies, and all stuff that you want add. And of course, It can be distributed as any container.

By default when you install Ansible Automation Platform these execution environments are installed by default, but you can build your owns.

Here what you got by default:

Short NameContainer imageDescription
Minimalee-minimal-rhel8A minimal automation execution environment based on Ansible Core 2.11. only includes the ansible.builtin
Supportedee-supported-rhel8An automation execution environment based on Ansible Core 2.11 that includes Red Hat Ansible Certified Content Collections and their software dependencies.
Compatibilityee-2rhel9An automation execution environment based on Ansible 2.9.

All certified container images are here

The aim of this post is to build an EE from scratch.

The VM configuration

I used a new test VM, with a fresh Ubuntu installation

[ubuntu:~]$  grep DISTRIB_DESCRIPTION /etc/lsb-release                                                                                                                                  
DISTRIB_DESCRIPTION="Ubuntu 22.04.1 LTS"

[ubuntu:~]$ sudo apt-get install podman

Install Ansible Navigator

[ubuntu:~]$ python3 -m pip install ansible-navigator --user

[ubuntu:~]$ python3 -m pip install ansible-builder --user

[ubuntu:~]$ python3 -m pip install ansible-runner --user

[ubuntu:~]$ echo 'export PATH=$HOME/.local/bin:$PATH' >> ~/.profile

[ubuntu:~]$ source ~/.profile

[ubuntu:~]$ ansible-navigator
--------------------------------------------------------------------
Execution environment image and pull policy overview
--------------------------------------------------------------------
Execution environment image name:     quay.io/ansible/creator-ee:v0.9.1
Execution environment image tag:      v0.9.1
Execution environment pull arguments: None
Execution environment pull policy:    tag
Execution environment pull needed:    True
--------------------------------------------------------------------
Updating the execution environment
--------------------------------------------------------------------
Running the command: podman pull quay.io/ansible/creator-ee:v0.9.1
Trying to pull quay.io/ansible/creator-ee:v0.9.1...
Getting image source signatures
Copying blob fee18ea417d6 [======================================] 47.2MiB / 47.2MiB
.....

At first execution ansible-navigator pull the creator-ee image.

Prepare the build process

Create a working directory to prepare the files that you need to build the EE container image. The ansible-builder command searches in this working directory for its execution-environment.yml configuration file, which is used to build the container image.

[ubuntu:~]$ mkdir my-builder-env

[ubuntu:~]$ cd my-builder-env

[ubuntu:~/my-builder-env]$

[ubuntu:~/my-builder-env]$ cat execution-environment.yml

---
version: 1

build_arg_defaults:
  EE_BASE_IMAGE: registry.redhat.io/ansible-automation-platform-20-early-access/ee-minimal-rhel8
  EE_BUILDER_IMAGE: registry.redhat.io/ansible-automation-platform-20-early-access/ansible-builder-rhel8

ansible_config: ansible.cfg 
dependencies:
  galaxy: requirements.yml 
  python: requirements.txt
  system: bindep.txt
additional_build_steps:
	prepend:
		- ADD my_tuf /usr/share/my/stuff
	append:
		- COPY .... 
  • EE_BASE_IMAGE : this is the container image to use as starting point. For public access use quay.io/ansible/ansible-runner:stable-2.11-latest.
  • EE_BUILDER_IMAGE: selects the container to use to build the final image. This is optional.
  • The dependencies sections list all dependencies to be added in the builded container:
    • requirements.yml for additional ansible collections.
    • requirements.txt for additional python modules.
    • bindeb.txt for additional packages.
  • additional_build_steps list all optional steps to be executed at the container creation. These are usual commands used in container creation process.
  • The ansible.cfg, requirements.yml, requirements.txt and bindeb.txt files content:
[ubuntu:~/my-builder-env]$ cat ansible.cfg

[galaxy]
server_list = galaxy

[galaxy_server.galaxy]
url=https://galaxy.ansible.com/


# Add community.aws and community.general to the EE container
[ubuntu:~/my-builder-env]$ cat requirements.yml
---
collections:
  - community.aws
  - community.general


# Add json and requests to the EE container.
[ubuntu:~/my-builder-env]$ cat requirements.txt
json
requests


# Add rsync and git to the EE container.
[ubuntu:~/my-builder-env]$ cat bindep.txt
rsync 
git
zsh

Build the container image

Note: To use Red Hat container registry that hosts the default images, ee-minimal-rhel8 and ansible-builder-rhel8 a Redhat subscription is needed (see EE_BASE_IMAGE field)

For public access use quay.io/ansible/ansible-runner:stable-2.11-latest instead.

[ubuntu:~/my-builder-env]$ podman login registry.redhat.io
Username: [email protected]
Password:
Login Succeeded!

[ubuntu:~/my-builder-env]$ ansible-builder build --tag ee-test:v1.0
Running command:
  podman build -f context/Containerfile -t ee-test:v1.0 context
Complete! The build context can be found at: /home/ubuntu/my-builder-env/context

[ubuntu:~/my-builder-env]$ cd context
[ubuntu:~/my-builder-env/context]$ tree
.
|-- Containerfile
`-- _build
    |-- ansible.cfg
    |-- bindep.txt
    |-- requirements.txt
    `-- requirements.yml

The execution environment builder operates in two steps which can be executed separately:

  1. The command creates the context/ directory in the current directory. In that directory, it creates the Containerfile file, the equivalent of a Dockerfile file, which contains instructions for the podman build command. It also creates an _build/ subdirectory and then copies your requirements.ymlansible.cfgrequirements.txt, and bindep.txt files into it, so that the build process can access them.
  2. The command then runs the podman build command, which constructs the resulting automation execution environment container image.

To execute only the first step use the command ansible-builder create command:

[ubuntu:~ ]$ cd my-builder-env
[ubuntu:~/my-builder-env]$ ls
ansible.cfg bindep.txt execution-environment.yml requirements.txt requirements.yml

[ubuntu:~/my-builder-env]$ ansible-builder create
Complete! The build context can be found at: ubuntu:~/my-builder-env/context]

[ubuntu:~/my-builder-env]$ tree
.
├── ansible.cfg
├── bindep.txt
├── context
│   ├── _build
│   │   ├── ansible.cfg
│   │   ├── bindep.txt
│   │   ├── requirements.txt
│   │   └── requirements.yml
│   └── Containerfile
├── execution-environment.yml
├── requirements.txt
└── requirements.yml


The file Containerfile can be edited to add extra stuff:

[ubuntu:~/my-builder-env]$ cd context

[ubuntu:~/my-builder-env/context ]$ cat Containerfile

ARG EE_BASE_IMAGE=registry.redhat.io/ansible-automation-platform-20-early-access/ee-minimal-rhel8
ARG EE_BUILDER_IMAGE=registry.redhat.io/ansible-automation-platform-20-early-access/ansible-builder-rhel8

FROM $EE_BASE_IMAGE as galaxy
ARG ANSIBLE_GALAXY_CLI_COLLECTION_OPTS=
ARG ANSIBLE_GALAXY_CLI_ROLE_OPTS=
USER root

ADD _build/ansible.cfg ~/.ansible.cfg

ADD _build /build
WORKDIR /build

RUN ansible-galaxy role install $ANSIBLE_GALAXY_CLI_ROLE_OPTS -r requirements.yml --roles-path "/usr/share/ansible/roles"
RUN ANSIBLE_GALAXY_DISABLE_GPG_VERIFY=1 ansible-galaxy collection install $ANSIBLE_GALAXY_CLI_COLLECTION_OPTS -r requirements.yml --collections-path "/usr/share/ansible/collections"

FROM $EE_BUILDER_IMAGE as builder

COPY --from=galaxy /usr/share/ansible /usr/share/ansible

ADD _build/requirements.txt requirements.txt
ADD _build/bindep.txt bindep.txt
RUN ansible-builder introspect --sanitize --user-pip=requirements.txt --user-bindep=bindep.txt --write-bindep=/tmp/src/bindep.txt --write-pip=/tmp/src/requirements.txt
RUN assemble

FROM $EE_BASE_IMAGE
USER root

COPY --from=galaxy /usr/share/ansible /usr/share/ansible

COPY --from=builder /output/ /output/
RUN /output/install-from-bindep && rm -rf /output/wheels
LABEL ansible-execution-environment=true

To execute the build step:

[ubuntu:~/my-builder-env]$ podman build -f context/Containerfile -t ee-test context

Testing the created EE container

The image can be started as any other podman image, installed collections should be under the path /usr/share/ansible/collections/ansible_collections at container level:

[ubuntu:~/my-builder-env/context]$ podman images

REPOSITORY                                                                            TAG         IMAGE ID      CREATED        SIZE
localhost/ee-test                                                                     v1.0        a524ff8039ed  3 minutes ago  678 MB
quay.io/ansible/creator-ee                                                            v0.9.1      d1524b4410d0  2 months ago   1.4 GB
registry.redhat.io/ansible-automation-platform-20-early-access/ee-minimal-rhel8       latest      19186d4e0970  5 months ago   372 MB
registry.redhat.io/ansible-automation-platform-20-early-access/ansible-builder-rhel8  latest      26582aeee896  5 months ago   340 MB

Note: to list the existent EE containers the ansible-navigator command can be used:

[ubuntu:~]$ ansible-navigator images {-m stdout} 

The created image has the IMAGE_ID a524ff8039ed:

[ubuntu:~/my-builder-env]$ podman run -it a524ff8039ed /bin/bash

[[email protected] /]# ls /usr/share/ansible/collections/ansible_collections
amazon	community

[[email protected] /]# python3 --version
Python 3.8.13

[[email protected] /]# git --version
git version 2.31.1

Let’s create a playbook to use, from the added community.general collection (see requirements.yml file), the community.general.python_requirements_info module.

[ubuntu:~ ]$ cd $HOME

[ubuntu:~ ]$ mkdir test

[ubuntu:~/test]$ cat test_playbook.yml
---
- name: Testing a custom Ansible Content Collection
  hosts: localhost

  tasks:
    - name: Show python lib/site paths
      community.general.python_requirements_info:
      register: answer

    - name: print output
      ansible.builtin.debug:
        msg: "{{ answer }}"

Run the test playbook test_playbook.yml:

[ubuntu:~/test]$ ansible-navigator run test_playbook.yml  --eei localhost/ee-test:v1.0 --pp never -b -m stdout

[WARNING]: provided hosts list is empty, only localhost is available. Note that
the implicit localhost does not match 'all'

PLAY [Testing a custom Ansible Content Collection] *****************************

TASK [Gathering Facts] *********************************************************
ok: [localhost]

TASK [Show python lib/site paths] **********************************************
ok: [localhost]

TASK [print output] ************************************************************
ok: [localhost] => {
    "msg": {
        "changed": false,
        "failed": false,
        "mismatched": {},
        "not_found": [],
        "python": "/usr/bin/python3.8",
        "python_system_path": [
            "/tmp/ansible_community.general.python_requirements_info_payload_xand6vtb/ansible_community.general.python_requirements_info_payload.zip",
            "/usr/lib64/python38.zip",
            "/usr/lib64/python3.8",
            "/usr/lib64/python3.8/lib-dynload",
            "/usr/local/lib64/python3.8/site-packages",
            "/usr/local/lib/python3.8/site-packages",
            "/usr/lib64/python3.8/site-packages",
            "/usr/lib/python3.8/site-packages"
        ],
        "python_version": "3.8.13 (default, Jun 14 2022, 17:49:07) \n[GCC 8.5.0 20210514 (Red Hat 8.5.0-13)]",
        "python_version_info": {
            "major": 3,
            "micro": 13,
            "minor": 8,
            "releaselevel": "final",
            "serial": 0
        },
        "valid": {}
    }
}

PLAY RECAP *********************************************************************
localhost                  : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

[ubuntu:~/test]$ 

Publish the container to the hub

Let’s tag our EE image (not mandatory but nice )

[ubuntu:~/test]$ podman tag localhost/ee-test:v1.0 myhub.example.com/mytestnamespace/ee-test:latest

[ubuntu:~/test]$ podman images | grep ee-test
myhub.example.com/mytestnamespace/ee-test                                             latest      a524ff8039ed  8 days ago    678 MB
localhost/ee-test                                                                     v1.0        a524ff8039ed  8 days ago    678 MB

Push the image to the hub:

[ubuntu:~/test]$ podman login myhub.example.com
Username: [email protected]
Password:

[ubuntu:~/test]$ podman push myhub.example.com/mytestnamespace/myhub.example.com/mytestnamespace/ee-test:latest

Conclusion

Using ansible tools from a container give the possibility to standardise even more the environnements deployment. As you can see, there is nothing new under the sun. Build a podman/docker container never need RedHat ansible dedicated tools. What is new, is that using ansible-builder, give you the possibility to start from a standard container which is supposed to follow all the best practice. The opening sentence, that EE “is a standard way to build and distribute the environment that automation runs in, which reduces complexity and makes it faster and simpler to develop and deploy automation.” takes on its full meaning.


Share on