Chapter 21. Managing containers by using RHEL system roles With the podman RHEL system role, you can manage Podman configuration, containers, and systemd services that run Podman containers. 21.1. Creating a rootless container with bind mount by using the podman RHEL system role You can use the podman RHEL system role to create rootless containers with bind mount by running an Ansible playbook and with that, manage your application configuration. The example Ansible playbook starts two Kubernetes pods: one for a database and another for a web application. The database pod configuration is specified in the playbook, while the web application pod is defined in an external YAML file. Prerequisites You have prepared the control node and the managed nodes . You are logged in to the control node as a user who can run playbooks on the managed nodes. The account you use to connect to the managed nodes has sudo permissions on them. The user and group webapp exist, and must be listed in the /etc/subuid and /etc/subgid files on the host. Procedure Create a playbook file, for example ~/playbook.yml , with the following content: - name: Configure Podman hosts: managed-node-01.example.com tasks: - name: Create a web application and a database ansible.builtin.include_role: name: rhel-system-roles.podman vars: podman_create_host_directories: true podman_firewall: - port: 8080-8081/tcp state: enabled - port: 12340/tcp state: enabled podman_selinux_ports: - ports: 8080-8081 setype: http_port_t podman_kube_specs: - state: started run_as_user: dbuser run_as_group: dbgroup kube_file_content: apiVersion: v1 kind: Pod metadata: name: db spec: containers: - name: db image: quay.io/linux-system-roles/mysql:5.6 ports: - containerPort: 1234 hostPort: 12340 volumeMounts: - mountPath: /var/lib/db:Z name: db volumes: - name: db hostPath: path: /var/lib/db - state: started run_as_user: webapp run_as_group: webapp kube_file_src: /path/to/webapp.yml The settings specified in the example playbook include the following: run_as_user and run_as_group Specify that containers are rootless. kube_file_content Contains a Kubernetes YAML file defining the first container named db . You can generate the Kubernetes YAML file by using the podman kube generate command. The db container is based on the quay.io/db/db:stable container image. The db bind mount maps the /var/lib/db directory on the host to the /var/lib/db directory in the container. The Z flag labels the content with a private unshared label, therefore, only the db container can access the content. kube_file_src: <path> Defines the second container. The content of the /path/to/webapp.yml file on the controller node will be copied to the kube_file field on the managed node. volumes: <list> A YAML list to define the source of the data to provide in one or more containers. For example, a local disk on the host ( hostPath ) or other disk device. volumeMounts: <list> A YAML list to define the destination where the individual container will mount a given volume. podman_create_host_directories: true Creates the directory on the host. This instructs the role to check the kube specification for hostPath volumes and create those directories on the host. If you need more control over the ownership and permissions, use podman_host_directories . For details about all variables used in the playbook, see the /usr/share/ansible/roles/rhel-system-roles.podman/README.md file on the control node. Validate the playbook syntax: Note that this command only validates the syntax and does not protect against a wrong but valid configuration. Run the playbook: Additional resources /usr/share/ansible/roles/rhel-system-roles.podman/README.md file /usr/share/doc/rhel-system-roles/podman/ directory 21.2. Creating a rootful container with Podman volume by using the podman RHEL system role You can use the podman RHEL system role to create a rootful container with a Podman volume by running an Ansible playbook and with that, manage your application configuration. The example Ansible playbook deploys a Kubernetes pod named ubi8-httpd running an HTTP server container from the registry.access.redhat.com/ubi8/httpd-24 image. The container's web content is mounted from a persistent volume named ubi8-html-volume . By default, the podman role creates rootful containers. Prerequisites You have prepared the control node and the managed nodes . You are logged in to the control node as a user who can run playbooks on the managed nodes. The account you use to connect to the managed nodes has sudo permissions on them. Procedure Create a playbook file, for example ~/playbook.yml , with the following content: - name: Configure Podman hosts: managed-node-01.example.com tasks: - name: Start Apache server on port 8080 ansible.builtin.include_role: name: rhel-system-roles.podman vars: podman_firewall: - port: 8080/tcp state: enabled podman_kube_specs: - state: started kube_file_content: apiVersion: v1 kind: Pod metadata: name: ubi8-httpd spec: containers: - name: ubi8-httpd image: registry.access.redhat.com/ubi8/httpd-24 ports: - containerPort: 8080 hostPort: 8080 volumeMounts: - mountPath: /var/www/html:Z name: ubi8-html volumes: - name: ubi8-html persistentVolumeClaim: claimName: ubi8-html-volume The settings specified in the example playbook include the following: kube_file_content Contains a Kubernetes YAML file defining the first container named db . You can generate the Kubernetes YAML file by using the podman kube generate command. The ubi8-httpd container is based on the registry.access.redhat.com/ubi8/httpd-24 container image. The ubi8-html-volume maps the /var/www/html directory on the host to the container. The Z flag labels the content with a private unshared label, therefore, only the ubi8-httpd container can access the content. The pod mounts the existing persistent volume named ubi8-html-volume with the mount path /var/www/html . For details about all variables used in the playbook, see the /usr/share/ansible/roles/rhel-system-roles.podman/README.md file on the control node. Validate the playbook syntax: Note that this command only validates the syntax and does not protect against a wrong but valid configuration. Run the playbook: Additional resources /usr/share/ansible/roles/rhel-system-roles.podman/README.md file /usr/share/doc/rhel-system-roles/podman/ directory 21.3. Creating a Quadlet application with secrets by using the podman RHEL system role You can use the podman RHEL system role to create a Quadlet application with secrets by running an Ansible playbook. Prerequisites You have prepared the control node and the managed nodes . You are logged in to the control node as a user who can run playbooks on the managed nodes. The account you use to connect to the managed nodes has sudo permissions on them. The certificate and the corresponding private key that the web server in the container should use are stored in the ~/certificate.pem and ~/key.pem files. Procedure Display the contents of the certificate and private key files: You require this information in a later step. Store your sensitive variables in an encrypted file: Create the vault: After the ansible-vault create command opens an editor, enter the sensitive data in the <key> : <value> format: root_password: <root_password> certificate: |- -----BEGIN CERTIFICATE----- ... -----END CERTIFICATE----- key: |- -----BEGIN PRIVATE KEY----- ... -----END PRIVATE KEY----- Ensure that all lines in the certificate and key variables start with two spaces. Save the changes, and close the editor. Ansible encrypts the data in the vault. Create a playbook file, for example ~/playbook.yml , with the following content: - name: Deploy a wordpress CMS with MySQL database hosts: managed-node-01.example.com vars_files: - vault.yml tasks: - name: Create and run the container ansible.builtin.include_role: name: rhel-system-roles.podman vars: podman_create_host_directories: true podman_activate_systemd_unit: false podman_quadlet_specs: - name: quadlet-demo type: network file_content: | [Network] Subnet=192.168.30.0/24 Gateway=192.168.30.1 Label=app=wordpress - file_src: quadlet-demo-mysql.volume - template_src: quadlet-demo-mysql.container.j2 - file_src: envoy-proxy-configmap.yml - file_src: quadlet-demo.yml - file_src: quadlet-demo.kube activate_systemd_unit: true podman_firewall: - port: 8000/tcp state: enabled - port: 9000/tcp state: enabled podman_secrets: - name: mysql-root-password-container state: present skip_existing: true data: "{{ root_password }}" - name: mysql-root-password-kube state: present skip_existing: true data: | apiVersion: v1 data: password: "{{ root_password | b64encode }}" kind: Secret metadata: name: mysql-root-password-kube - name: envoy-certificates state: present skip_existing: true data: | apiVersion: v1 data: certificate.key: {{ key | b64encode }} certificate.pem: {{ certificate | b64encode }} kind: Secret metadata: name: envoy-certificates The procedure creates a WordPress content management system paired with a MySQL database. The podman_quadlet_specs role variable defines a set of configurations for the Quadlet, which refers to a group of containers or services that work together in a certain way. It includes the following specifications: The Wordpress network is defined by the quadlet-demo network unit. The volume configuration for MySQL container is defined by the file_src: quadlet-demo-mysql.volume field. The template_src: quadlet-demo-mysql.container.j2 field is used to generate a configuration for the MySQL container. Two YAML files follow: file_src: envoy-proxy-configmap.yml and file_src: quadlet-demo.yml . Note that .yml is not a valid Quadlet unit type, therefore these files will just be copied and not processed as a Quadlet specification. The Wordpress and envoy proxy containers and configuration are defined by the file_src: quadlet-demo.kube field. The kube unit refers to the YAML files in the [Kube] section as Yaml=quadlet-demo.yml and ConfigMap=envoy-proxy-configmap.yml . Validate the playbook syntax: Note that this command only validates the syntax and does not protect against a wrong but valid configuration. Run the playbook: Additional resources /usr/share/ansible/roles/rhel-system-roles.podman/README.md file /usr/share/doc/rhel-system-roles/podman/ directory

Commands:
- name: Configure Podman hosts: managed-node-01.example.com tasks: - name: Create a web application and a database ansible.builtin.include_role: name: rhel-system-roles.podman vars: podman_create_host_directories: true podman_firewall: - port: 8080-8081/tcp state: enabled - port: 12340/tcp state: enabled podman_selinux_ports: - ports: 8080-8081 setype: http_port_t podman_kube_specs: - state: started run_as_user: dbuser run_as_group: dbgroup kube_file_content: apiVersion: v1 kind: Pod metadata: name: db spec: containers: - name: db image: quay.io/linux-system-roles/mysql:5.6 ports: - containerPort: 1234 hostPort: 12340 volumeMounts: - mountPath: /var/lib/db:Z name: db volumes: - name: db hostPath: path: /var/lib/db - state: started run_as_user: webapp run_as_group: webapp kube_file_src: /path/to/webapp.yml
ansible-playbook --syntax-check --ask-vault-pass ~/playbook.yml
ansible-playbook --ask-vault-pass ~/playbook.yml
- name: Configure Podman hosts: managed-node-01.example.com tasks: - name: Start Apache server on port 8080 ansible.builtin.include_role: name: rhel-system-roles.podman vars: podman_firewall: - port: 8080/tcp state: enabled podman_kube_specs: - state: started kube_file_content: apiVersion: v1 kind: Pod metadata: name: ubi8-httpd spec: containers: - name: ubi8-httpd image: registry.access.redhat.com/ubi8/httpd-24 ports: - containerPort: 8080 hostPort: 8080 volumeMounts: - mountPath: /var/www/html:Z name: ubi8-html volumes: - name: ubi8-html persistentVolumeClaim: claimName: ubi8-html-volume
ansible-playbook --syntax-check ~/playbook.yml
ansible-playbook ~/playbook.yml
cat ~/certificate.pem -----BEGIN CERTIFICATE----- -----END CERTIFICATE----- cat ~/key.pem -----BEGIN PRIVATE KEY----- -----END PRIVATE KEY-----
ansible-vault create vault.yml New Vault password: <vault_password> Confirm New Vault password: <vault_password>
root_password: <root_password> certificate: |- -----BEGIN CERTIFICATE----- -----END CERTIFICATE----- key: |- -----BEGIN PRIVATE KEY----- -----END PRIVATE KEY-----
- name: Deploy a wordpress CMS with MySQL database hosts: managed-node-01.example.com vars_files: - vault.yml tasks: - name: Create and run the container ansible.builtin.include_role: name: rhel-system-roles.podman vars: podman_create_host_directories: true podman_activate_systemd_unit: false podman_quadlet_specs: - name: quadlet-demo type: network file_content: | [Network] Subnet=192.168.30.0/24 Gateway=192.168.30.1 Label=app=wordpress - file_src: quadlet-demo-mysql.volume - template_src: quadlet-demo-mysql.container.j2 - file_src: envoy-proxy-configmap.yml - file_src: quadlet-demo.yml - file_src: quadlet-demo.kube activate_systemd_unit: true podman_firewall: - port: 8000/tcp state: enabled - port: 9000/tcp state: enabled podman_secrets: - name: mysql-root-password-container state: present skip_existing: true data: "{{ root_password }}" - name: mysql-root-password-kube state: present skip_existing: true data: | apiVersion: v1 data: password: "{{ root_password | b64encode }}" kind: Secret metadata: name: mysql-root-password-kube - name: envoy-certificates state: present skip_existing: true data: | apiVersion: v1 data: certificate.key: {{ key | b64encode }} certificate.pem: {{ certificate | b64encode }} kind: Secret metadata: name: envoy-certificates
ansible-playbook --syntax-check --ask-vault-pass ~/playbook.yml
ansible-playbook --ask-vault-pass ~/playbook.yml