In this exercise, we’ll be leveraging the play kube functionality of podman to deploy our application using Kubernetes manifests (yaml). This creates alignment between applications running within a full Kubernetes cluster, such as OpenShift, and those that are deployed to locations where Kubernetes isn’t available.
Note
For a quick review of our example application, return to Exercise 1.5
Our sample application is broken up into four individual containers, which we’ll run within a single pod to allow for them to communicate using the same context for localhost.
Let’s consider the following yaml:
---
apiVersion: v1
kind: Pod
metadata:
  name: process-control
spec:
  containers:
    - name: mqtt
      image: quay.io/device-edge-workshops/process-control-mqtt:1.0.0
    - name: simulate
      image: quay.io/device-edge-workshops/process-control-simulate:1.0.0
    - name: control
      image: quay.io/device-edge-workshops/process-control-control:1.0.0
    - name: ui
      image: quay.io/device-edge-workshops/process-control-ui:1.0.0
      ports:
        - containerPort: 1881
          hostPort: 1881
Here, we can see the individual containers, which each contain a component (or service) of the application, grouped into a pod named process-control. Each container has a name and an image, and the UI container also gets an external port mapping.
With our yaml crafted, we can place this file into our code repo and reference it in a playbook in a later step.
Back in our code repo, create a file at playbooks/files/app-definition.yaml, and enter the the yaml from Step 1. Once complete, remember to commit and push the code, if using an IDE or git on the CLI.
Now that our application is defined, we can leverage Ansible to handle a few of the pre-configuration we’ll need to run the app.
First, let’s enable lingering for the user Ansible is using to authenticate to the system:
- name: enable lingering for {{ ansible_user }}
  ansible.builtin.shell:
    cmd: loginctl enable-linger {{ ansible_user }}
  args:
    creates: "/var/lib/systemd/linger/{{ ansible_user }}"
  become: true
The other step we’ll need to take is allowing port 1881 through the device’s firewall:
- name: allow 1881 through the firewall
  ansible.posix.firewalld:
    port: 1881/tcp
    zone: public
    permanent: true
    state: enabled
    immediate: true
  become: true
These two steps could be considered “prerequisities” to running the application. Now, let’s deploy the app itself: first by pushing the yaml to the device, then having podman pick up on that yaml and start up the application.
- name: push out yaml
  ansible.builtin.copy:
    src: files/app-definition.yml
    dest: "/home/{{ ansible_user }}/process-control.yaml"
- name: podman play kube
  containers.podman.podman_play:
    kube_file: "/home/{{ ansible_user }}/process-control.yaml"
    state: started
  notify: _wait_for_startup
In the task to start up the application, we’ve included a scoped notify so our automation doesn’t complete until the application fires up the first time. In Ansible, we call these handlers.
Our handler would look like this:
- name: wait for app startup
  ansible.builtin.wait_for:
    port: 1881
  listen:
    - _wait_for_startup
Notification to handlers is only done when a change is made.
This is included to address the startup time for the application. Since container images will need to be pulled, there will be a slight delay as those images are pulled and stored locally.
With the pieces above, we can put together a playbook to use to deploy our application. Copy the following to a file at playbooks/deploy-app.yml:
---
- name: deploy application to edge device
  hosts:
    - all
  pre_tasks:
    - name: enable lingering for {{ ansible_user }}
      ansible.builtin.shell:
        cmd: loginctl enable-linger {{ ansible_user }}
      args:
        creates: "/var/lib/systemd/linger/{{ ansible_user }}"
      become: true
    - name: allow 1881 through the firewall
      ansible.posix.firewalld:
        port: 1881/tcp
        zone: public
        permanent: true
        state: enabled
        immediate: true
      become: true
  tasks:
    - name: push out yaml
      ansible.builtin.copy:
        src: files/app-definition.yml
        dest: "/home/{{ ansible_user }}/process-control.yaml"
    - name: podman play kube
      containers.podman.podman_play:
        kube_file: "/home/{{ ansible_user }}/process-control.yaml"
        state: started
      notify: _wait_for_startup
  handlers:
    - name: wait for app startup
      ansible.builtin.wait_for:
        port: 1881
      listen:
        - _wait_for_startup
Again, feel free to get this playbook into to your code repo however you’d like: the Gitea web interface, an IDE, git on the CLI, or whatever you’re comfortable with.
Navigation
| Previous Exercise | Next Exercise |