Skip to main content

Automate container and pod deployments with Podman and Ansible

Podman and Ansible are even better together for enabling automation and orchestration of container and pod lifecycles.

Image by falco from Pixabay

Two of the hottest topics in the Enable Sysadmin community are Podman and Ansible. It's no surprise as both bring compelling features and functionality for a variety of needs. With Podman, you have everything you need to manage your containers and pods, while Ansible offers everything you need to automate all the things. So what would be the product of the sum of the benefits of these two tools? Of course: automated and orchestrated management of your container and pod infrastructure!

Take, for instance, my article How to create multidomain web applications with Podman and Nginx. Although it's a relatively simple setup, imagine you need to replicate it in other environments or scale it to a large number of machines. Needless to say, doing all of that by hand is not practical at all, right? That's why automation is great for doing such things, and that's why I'll show how to use Ansible to automate Podman for container and pod deployments, using my previous article's environment as an example.

[ Download now: A system administrator's guide to IT automation. ]

Automate deploying prerequisites

Since I set out to automate everything, I'll automate creating the directories and files I previously created using this Ansible playbook (prereq.yaml):

---
- name: "Playbook to create the prerequisites"
  hosts: localhost
  gather_facts: false
  vars:
    homedir: /home/localuser
    hostip: 192.168.1.30
  tasks:
    - name: "Create the directories"
      ansible.builtin.file:
        path: "{{ homedir }}/{{ item }}"
        state: directory
      loop:
        - syscom
        - sysorg
        - nginx

    - name: "Create the httpd syscom file"
      ansible.builtin.copy:
        dest: "{{ homedir }}/syscom/index.html"
        content: |
          <html>
            <header>
              <title>SysAdmin.com</title>
            </header>
            <body>
              <p>This is the SysAdmin website hosted on the .com domain</p>
            </body>
          </html>

    - name: "Create the httpd sysorg file"
      ansible.builtin.copy:
        dest: "{{ homedir }}/sysorg/index.html"
        content: |
          <html>
            <header>
              <title>SysAdmin.org</title>
            </header>
            <body>
              <p>This is the SysAdmin website hosted on the .org domain</p>
            </body>
          </html>

    - name: "Create the Nginx syscom config"
      ansible.builtin.copy:
        dest: "{{ homedir }}/nginx/syscom.conf"
        content: |
          server {
            listen 80;
            server_name sysadmin.com;
            
            location / {
              proxy_pass http://{{ hostip }}:8080;
            }
          }

    - name: "Create the Nginx sysorg config"
      ansible.builtin.copy:
        dest: "{{ homedir }}/nginx/sysorg.conf"
        content: |
          server {
            listen 80;
            server_name sysadmin.org;
            
            location / {
              proxy_pass http://{{ hostip }}:8081;
            }
          }

    - name: "Create the Nginx default config"
      ansible.builtin.copy:
        dest: "{{ homedir }}/nginx/default.conf"
        content: |
          server {
              listen       80;
              listen  [::]:80;
              server_name  localhost;
              
              location / {
                  root   /usr/share/nginx/html;
                  index  index.html index.htm;
              }
              
              error_page   500 502 503 504  /50x.html;
              location = /50x.html {
                  root   /usr/share/nginx/html;
              }
              
          }

[ Write your first playbook in this hands-on interactive lab. ]

Before running it, update variables homedir and hostip to match your environment. Then, run the playbook to create the directories and files:

$ ansible-playbook prereq.yaml
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'

PLAY [Playbook to create the prerequisites] ***********************************

TASK [Create the directories] *************************************************
changed: [localhost] => (item=syscom)
changed: [localhost] => (item=sysorg)
changed: [localhost] => (item=nginx)

TASK [Create the httpd syscom file] *******************************************
changed: [localhost]

TASK [Create the httpd sysorg file] *******************************************
changed: [localhost]

TASK [Create the Nginx syscom config] *****************************************
changed: [localhost]

TASK [Create the Nginx sysorg config] *****************************************
changed: [localhost]

TASK [Create the Nginx default config] ****************************************
changed: [localhost]

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

$ ls -lR
.:
total 12
drwxr-xr-x. 2 localuser localuser  64 mar 2 13:13 nginx
-rw-r--r--. 1 localuser localuser 2541 mar 2 13:12 prereq.yaml
drwxr-xr-x. 2 localuser localuser  24 mar 2 13:13 syscom
drwxr-xr-x. 2 localuser localuser  24 mar 2 13:13 sysorg

./nginx:
total 12
-rw-r--r--. 1 localuser localuser 311 mar 2 13:13 default.conf
-rw-r--r--. 1 localuser localuser 116 mar 2 13:13 syscom.conf
-rw-r--r--. 1 localuser localuser 116 mar 2 13:13 sysorg.conf

./syscom:
total 4
-rw-r--r--. 1 localuser localuser 163 mar 2 13:13 index.html

./sysorg:
total 4
-rw-r--r--. 1 localuser localuser 163 mar 2 13:13 index.html

To proceed with the next steps, you'll need two distinct Ansible collections: ansible.posix to manipulate kernel parameters with sysctl; and the collection containers.podman to automate Podman with Ansible. Install both collections:

$ for i in ansible.posix containers.podman; \
    do ansible-galaxy collection install $i; \
    done

Check the available modules for each of these collections:

$ ansible-doc -l | grep -e posix -e podman
ansible.posix.acl                                         Set and retrieve ...
ansible.posix.at                                          Schedule the exec...
ansible.posix.authorized_key                              Adds or removes a...
ansible.posix.firewalld                                   Manage arbitrary ...
ansible.posix.firewalld_info                              Gather informatio...
ansible.posix.mount                                       Control active an...
ansible.posix.patch                                       Apply patch files...
ansible.posix.rhel_facts                                  Facts module to s...
ansible.posix.rhel_rpm_ostree                             Ensure packages e...
ansible.posix.rpm_ostree_upgrade                          Manage rpm-ostree...
ansible.posix.seboolean                                   Toggles SELinux b...
ansible.posix.selinux                                     Change policy and...
ansible.posix.synchronize                                 A wrapper around ...
ansible.posix.sysctl                                      Manage entries in...
community.general.udm_user                                Manage posix user...
containers.podman.podman_container                        Manage podman con...
containers.podman.podman_container_info                   Gather facts abou...
containers.podman.podman_containers                       Manage podman con...
containers.podman.podman_export                           Export a podman c...
containers.podman.podman_generate_systemd                 Generate systemd ...
containers.podman.podman_image                            Pull images for u...
containers.podman.podman_image_info                       Gather info about...
containers.podman.podman_import                           Import Podman con...
containers.podman.podman_load                             Load image from a...
containers.podman.podman_login                            Login to a contai...
containers.podman.podman_login_info                       Return the logged...
containers.podman.podman_logout                           Log out of a cont...
containers.podman.podman_network                          Manage podman net...
containers.podman.podman_network_info                     Gather info about...
containers.podman.podman_play                             Play kubernetes Y...
containers.podman.podman_pod                              Manage Podman pod...
containers.podman.podman_pod_info                         Gather info about...
containers.podman.podman_save                             Saves podman imag...
containers.podman.podman_secret                           Manage podman sec...
containers.podman.podman_tag                              Add an additional...
containers.podman.podman_volume                           Manage Podman vol...
containers.podman.podman_volume_info                      Gather info about...

I won't detail what each of these modules do, as I'd need several articles for that. Instead, I'll focus on the containers.podman.podman_container module for creating containers in an automated way.

Automate Podman with Ansible

To automate the creation of Podman containers using Ansible, create a playbook to deploy every single container with its proper parameters (as described in the previous article). Use the specific collections and respective modules for this. You'll also create another playbook to delete all containers when you don't need them anymore. Start with the playbook create_httpd_nginx.yaml:

---
- name: "Playbook to create two httpd containers with one nginx container as a reverse proxy"
  hosts: localhost
  gather_facts: yes
  vars:
    homedir: /home/localuser
    container_registry: docker.io/library
  tasks:
    - name: "Create the syscom container"
      containers.podman.podman_container:
        name: syscom
        hostname: syscom
        image: "{{ container_registry }}/httpd"
        publish: 8080:80
        volume:
          - "{{ homedir }}/syscom:/usr/local/apache2/htdocs:Z"
        state: started

    - name: "Create the sysorg container"
      containers.podman.podman_container:
        name: sysorg
        hostname: sysorg
        image: "{{ container_registry }}/httpd"
        publish: 8081:80
        volume:
          - "{{ homedir }}/sysorg:/usr/local/apache2/htdocs:Z"
        state: started
    
    - name: "Allow the nginx container to run in the 80 port of the host"
      become: true
      become_user: root
      become_method: sudo
      ansible.posix.sysctl:
        name: net.ipv4.ip_unprivileged_port_start
        value: "80"
        sysctl_set: true
        reload: true

    - name: "Create the nginx container"
      containers.podman.podman_container:
        name: nginx
        hostname: nginx
        image: "{{ container_registry }}/nginx"
        publish: 80:80
        volume:
          - "{{ homedir }}/nginx:/etc/nginx/conf.d:Z"
        state: started

    - name: "Disable the use of lower ports for containers again"
      become: true
      become_user: root
      become_method: sudo
      ansible.posix.sysctl:
        name: net.ipv4.ip_unprivileged_port_start
        value: "1024"
        sysctl_set: true
        reload: true

Then write a playbook called stop-remove_httpd_nginx.yaml to stop and remove the containers:

---
- name: "Playbook to delete a container"
  hosts: localhost
  gather_facts: yes
  tasks:
    - name: "Stop and remove all containers"
      containers.podman.podman_container:
        name: "{{ item }}"
        state: absent
      loop:
        - syscom
        - sysorg
        - nginx

Finally, test your playbooks by creating the containers and validating access to the applications. Use option -K with the ansible-playbook command to input the sudo password, as this playbook changes the kernel setting to allow the Nginx container to bind to privileged port 80:

$ ansible-playbook create_httpd_nginx.yaml -K

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

PLAY [Playbook to create two httpd containers with one nginx container as a reverse proxy]
**********************************************************

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

TASK [Create the syscom container] ********************************************
changed: [localhost]

TASK [Create the sysorg container] ********************************************
changed: [localhost]

TASK [Allow the nginx container to run in the 80 port of the host] ************
changed: [localhost]

TASK [Create the nginx container] *********************************************
changed: [localhost]

TASK [Disable the use of lower ports for containers again] ********************
changed: [localhost]

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

$ podman ps
CONTAINER ID IMAGE                          COMMAND              CREATED       STATUS       PORTS                NAMES
2038ad4e5c9e docker.io/library/httpd:latest httpd-foreground     7 seconds ago Up 6 seconds 0.0.0.0:8080->80/tcp syscom
5a01d9bdc5ae docker.io/library/httpd:latest httpd-foreground     5 seconds ago Up 4 seconds 0.0.0.0:8081->80/tcp sysorgp
3c816ebb9bcb docker.io/library/nginx:latest nginx -g daemon o... 3 seconds ago Up 2 seconds 0.0.0.0:80->80/tcp   nginx

$ curl http://sysadmin.com
<html>
  <header>
    <title>SysAdmin.com</title>
  </header>
  <body>
    <p>This is the SysAdmin website hosted on the .com domain</p>
  </body>
</html>

$ curl http://sysadmin.org
<html>
  <header>
    <title>SysAdmin.org</title>
  </header>
  <body>
    <p>This is the SysAdmin website hosted on the .org domain</p>
  </body>
</html>

Nice and done. And to delete all of that, run the other playbook:

$ ansible-playbook stop-remove_httpd_nginx.yaml

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

PLAY [Playbook to create a container] *****************************************

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

TASK [Stop and remove all containers] *****************************************
changed: [localhost] => (item=syscom)
changed: [localhost] => (item=sysorg)
changed: [localhost] => (item=nginx)

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

$ podman ps
CONTAINER ID IMAGE      COMMAND    CREATED    STATUS     PORTS      NAMES

$ curl http://sysadmin.com
curl: (7) Failed to connect to sysadmin.com port 80 after 0 ms: Connection refused

$ curl http://sysadmin.org
curl: (7) Failed to connect to sysadmin.org port 80 after 0 ms: Connection refused

And just like that, you created and deleted the same environment as in my previous article, but faster, by leveraging the power of Ansible for Podman. Explore the other modules available in the containers.podman collection to take advantage of the facilities it provides even more.

Wrap up

Podman and Ansible are very good tools individually for managing containers and automating all things respectively. They are even better together for enabling automation and orchestration of the container and pod lifecycles in simpler scenarios.

For more sophisticated scaling, orchestration, routing, balancing, and automation of many container-based applications and services, you can use a Kubernetes-based enterprise orchestration platform such as Red Hat OpenShift Container Platform.

Topics:   Ansible   Podman   Automation   Containers  
Author’s photo

Alexon Oliveira

Alexon has been working as a Senior Technical Account Manager at Red Hat since 2018, working in the Customer Success organization focusing on Infrastructure and Management, Integration and Automation, Cloud Computing, and Storage Solutions. More about me

Try Red Hat Enterprise Linux

Download it at no charge from the Red Hat Developer program.