feat: switch to binary runc and containerd install (#333)

* feat: switch to binary runc and containerd install

* fix: always download directly to dst node

* feat: add crictl role

* ci: add tests for binary downloads

* ci: rename scenario and add matrix

* ci: move to using prepare

* ci: stop using  anchors

* chore: refactor to download_artifact

* chore: add jammy to containerd+runc

* chore: bump ansible-lint

* chore: add more platforms for cri

* fix: ensure tar command exists

* chore: drop amznlinux2

---------

Co-authored-by: Mohammed Naser <mnaser@vexxhost.com>
diff --git a/.ansible-lint b/.ansible-lint
index 0462af1..b46745d 100644
--- a/.ansible-lint
+++ b/.ansible-lint
@@ -10,6 +10,3 @@
 warn_list:
   - jinja[invalid]
   - jinja[spacing]
-  - name[casing]
-  - template-instead-of-copy
-  - yaml[line-length]
diff --git a/.github/workflows/containerd.yml b/.github/workflows/containerd.yml
new file mode 100644
index 0000000..69a0ec8
--- /dev/null
+++ b/.github/workflows/containerd.yml
@@ -0,0 +1,69 @@
+# Copyright (c) 2023 VEXXHOST, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+name: containerd
+
+on:
+  pull_request:
+    paths:
+      - molecule/containerd/**
+      - roles/defaults/**
+      - roles/runc/**
+      - roles/binary_download/**
+      - roles/containerd/**
+      - roles/crictl/**
+  push:
+    branches:
+      - main
+    paths:
+      - molecule/containerd/**
+      - roles/defaults/**
+      - roles/runc/**
+      - roles/binary_download/**
+      - roles/containerd/**
+      - roles/crictl/**
+
+jobs:
+  test:
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        distro:
+          - debian10
+          - debian11
+          - fedora36
+          - fedora37
+          - rockylinux8
+          - rockylinux9
+          - ubuntu2004
+          - ubuntu2204
+    steps:
+      - name: Checkout project
+        uses: actions/checkout@v3
+
+      - name: Install Poetry
+        run: pipx install poetry
+
+      - name: Setup Python
+        uses: actions/setup-python@v4
+        with:
+          cache: poetry
+
+      - name: Install dependencies
+        run: poetry install --no-interaction --with dev
+
+      - name: Run Molecule
+        run: poetry run molecule test -s containerd
+        env:
+          MOLECULE_DISTRO: ${{ matrix.distro }}
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 9eff639..b65b5fb 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -15,7 +15,7 @@
           - commit-msg
 
   - repo: https://github.com/ansible/ansible-lint
-    rev: v6.8.6
+    rev: v6.13.1
     hooks:
       - id: ansible-lint
         files: \.(yaml|yml)$
diff --git a/galaxy.yml b/galaxy.yml
index 29d9b5d..7c0b660 100644
--- a/galaxy.yml
+++ b/galaxy.yml
@@ -14,6 +14,14 @@
   community.general: 4.5.0
   kubernetes.core: 2.3.2
   openstack.cloud: 1.7.0
+tags:
+  - application
+  - cloud
+  - infrastructure
+  - linux
+  - monitoring
+  - storage
+  - tools
 repository: https://github.com/vexxhost/atmosphere
 documentation: https://github.com/vexxhost/atmosphere/tree/main/docs
 homepage: https://github.com/vexxhost/atmosphere
diff --git a/meta/runtime.yml b/meta/runtime.yml
index 03c25c9..33f1892 100644
--- a/meta/runtime.yml
+++ b/meta/runtime.yml
@@ -1,2 +1,2 @@
 ---
-requires_ansible: ">=2.11"
+requires_ansible: ">=2.13.4"
diff --git a/molecule/containerd/converge.yml b/molecule/containerd/converge.yml
new file mode 100644
index 0000000..ca8b887
--- /dev/null
+++ b/molecule/containerd/converge.yml
@@ -0,0 +1,20 @@
+# Copyright (c) 2023 VEXXHOST, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+- name: Converge
+  hosts: all
+  become: true
+  roles:
+    - vexxhost.atmosphere.containerd
+    - vexxhost.atmosphere.crictl
diff --git a/molecule/containerd/molecule.yml b/molecule/containerd/molecule.yml
new file mode 100644
index 0000000..bf62b9c
--- /dev/null
+++ b/molecule/containerd/molecule.yml
@@ -0,0 +1,31 @@
+# Copyright (c) 2023 VEXXHOST, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+dependency:
+  name: galaxy
+driver:
+  name: docker
+platforms:
+  - name: instance
+    image: geerlingguy/docker-${MOLECULE_DISTRO:-ubuntu2004}-ansible:latest
+    command: ${MOLECULE_DOCKER_COMMAND:-""}
+    privileged: true
+    cgroupns_mode: host
+    pre_build_image: true
+    volumes:
+      - /sys/fs/cgroup:/sys/fs/cgroup:rw
+provisioner:
+  name: ansible
+verifier:
+  name: ansible
diff --git a/molecule/containerd/prepare.yml b/molecule/containerd/prepare.yml
new file mode 100644
index 0000000..f538ad1
--- /dev/null
+++ b/molecule/containerd/prepare.yml
@@ -0,0 +1,28 @@
+# Copyright (c) 2023 VEXXHOST, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+- name: Prepare
+  hosts: all
+  become: true
+  pre_tasks:
+    - name: Wait for systemd to complete initialization
+      ansible.builtin.command: systemctl is-system-running
+      register: systemctl_status
+      until: >
+        'running' in systemctl_status.stdout or
+        'degraded' in systemctl_status.stdout
+      retries: 30
+      delay: 5
+      changed_when: false
+      failed_when: systemctl_status.rc > 1
diff --git a/molecule/containerd/verify.yml b/molecule/containerd/verify.yml
new file mode 100644
index 0000000..1462252
--- /dev/null
+++ b/molecule/containerd/verify.yml
@@ -0,0 +1,40 @@
+# Copyright (c) 2023 VEXXHOST, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+- name: Verify
+  hosts: all
+  become: true
+
+  pre_tasks:
+    - name: Gather service facts
+      service_facts:
+
+  tasks:
+    - name: Make sure containerd service is running
+      ansible.builtin.assert:
+        that:
+          - ansible_facts.services['containerd.service'].state in ['active', 'running']
+
+    - name: Make sure containerd service is enabled
+      ansible.builtin.assert:
+        that:
+          - ansible_facts.services['containerd.service'].status == 'enabled'
+
+    - name: Pull image from registry
+      ansible.builtin.command: ctr image pull docker.io/library/alpine:latest
+
+    - name: List images
+      ansible.builtin.command: ctr images ls
+      register: images
+      failed_when: not(images.stdout.find('alpine') != -1)
diff --git a/roles/ceph_mgr/tasks/main.yml b/roles/ceph_mgr/tasks/main.yml
index 725c1ad..9ee70ee 100644
--- a/roles/ceph_mgr/tasks/main.yml
+++ b/roles/ceph_mgr/tasks/main.yml
@@ -12,18 +12,18 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
-- name: install packages
+- name: Install packages
   ansible.builtin.apt:
     name: ["ceph-mgr"]
     install_recommends: false
 
-- name: create manager folder
+- name: Create manager folder
   ansible.builtin.file:
     path: "/var/lib/ceph/mgr/ceph-{{ inventory_hostname_short }}"
     state: directory
     owner: ceph
     group: ceph
-    mode: 0700
+    mode: "0700"
 
 - name: Create Ceph manager keyring
   vexxhost.atmosphere.ceph_key:
@@ -36,14 +36,14 @@
     owner: ceph
     group: ceph
 
-- name: ensure permissions are fixed
+- name: Ensure permissions are fixed
   ansible.builtin.file:
     path: "/var/lib/ceph/mon/ceph-{{ inventory_hostname_short }}"
     owner: ceph
     group: ceph
     recurse: true
 
-- name: enable and start service
+- name: Enable and start service
   ansible.builtin.service:
     name: "ceph-mgr@{{ inventory_hostname_short }}"
     state: started
diff --git a/roles/ceph_mon/tasks/bootstrap-ceph.yml b/roles/ceph_mon/tasks/bootstrap-ceph.yml
index 19ab74a..dc19f62 100644
--- a/roles/ceph_mon/tasks/bootstrap-ceph.yml
+++ b/roles/ceph_mon/tasks/bootstrap-ceph.yml
@@ -13,7 +13,7 @@
 # under the License.
 
 # TODO(mnaser): Move to using vexxhost.atmosphere.ceph_key
-- name: create monitor keyring
+- name: Create monitor keyring
   ansible.builtin.command:
     ceph-authtool --gen-key --create-keyring
                   --name mon.
@@ -25,7 +25,7 @@
     - inventory_hostname == groups[ceph_mon_group][0]
 
 # TODO(mnaser): Move to using vexxhost.atmosphere.ceph_key
-- name: create admin keyring
+- name: Create admin keyring
   ansible.builtin.command:
     ceph-authtool --gen-key --create-keyring
                   --name client.admin
@@ -40,7 +40,7 @@
     - inventory_hostname == groups[ceph_mon_group][0]
 
 # TODO(mnaser): Move to using vexxhost.atmosphere.ceph_key
-- name: create bootstrap-osd keyring
+- name: Create bootstrap-osd keyring
   ansible.builtin.command:
     ceph-authtool --gen-key --create-keyring
                   --name client.bootstrap-osd
@@ -53,7 +53,8 @@
     - inventory_hostname == groups[ceph_mon_group][0]
 
 # TODO(mnaser): Move to using vexxhost.atmosphere.ceph_key
-- name: add admin keyring to monitor
+- name: Add admin keyring to monitor
+  changed_when: true
   ansible.builtin.command:
     ceph-authtool --import-keyring /etc/ceph/ceph.client.admin.keyring
                   /tmp/ceph.mon.keyring
@@ -61,14 +62,15 @@
     - inventory_hostname == groups[ceph_mon_group][0]
 
 # TODO(mnaser): Move to using vexxhost.atmosphere.ceph_key
-- name: add bootstrap-osd keyring to monitor
+- name: Add bootstrap-osd keyring to monitor
+  changed_when: true
   ansible.builtin.command:
     ceph-authtool --import-keyring /var/lib/ceph/bootstrap-osd/ceph.keyring
                   /tmp/ceph.mon.keyring
   when:
     - inventory_hostname == groups[ceph_mon_group][0]
 
-- name: create monmap
+- name: Create monmap
   ansible.builtin.command:
     monmaptool --create
                --fsid {{ ceph_mon_fsid }}
@@ -79,17 +81,17 @@
   when:
     - inventory_hostname == groups[ceph_mon_group][0]
 
-- name: create monitor folder
+- name: Create monitor folder
   ansible.builtin.file:
     path: "/var/lib/ceph/mon/ceph-{{ inventory_hostname_short }}"
     state: directory
     owner: ceph
     group: ceph
-    mode: 0700
+    mode: "0700"
   when:
     - inventory_hostname == groups[ceph_mon_group][0]
 
-- name: configure mon initial members
+- name: Configure mon initial members
   community.general.ini_file:
     path: /etc/ceph/ceph.conf
     section: global
@@ -97,13 +99,13 @@
     value: "{{ inventory_hostname_short }}"
     owner: ceph
     group: ceph
-    mode: 0640
+    mode: "0640"
 
-- name: start monitor
+- name: Start monitor
   ansible.builtin.include_tasks: start-monitor.yml
   when:
     - inventory_hostname == groups[ceph_mon_group][0]
 
-- name: set bootstrap node
+- name: Set bootstrap node
   ansible.builtin.set_fact:
     _ceph_mon_bootstrap_node: "{{ groups[ceph_mon_group][0] }}"
diff --git a/roles/ceph_mon/tasks/main.yml b/roles/ceph_mon/tasks/main.yml
index 90cdfc2..5ce89e2 100644
--- a/roles/ceph_mon/tasks/main.yml
+++ b/roles/ceph_mon/tasks/main.yml
@@ -12,16 +12,16 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
-- name: install packages
+- name: Install packages
   ansible.builtin.apt:
     name: ["ceph-mon"]
     install_recommends: false
 
-- name: set ceph monitor ip address
+- name: Set ceph monitor ip address
   ansible.builtin.set_fact:
     ceph_mon_ip_address: "{{ ansible_all_ipv4_addresses | ansible.netcommon.ipaddr(ceph_mon_public_network) | first }}"
 
-- name: generate basic configuration file
+- name: Generate basic configuration file
   community.general.ini_file:
     path: /etc/ceph/ceph.conf
     section: global
@@ -29,7 +29,7 @@
     value: "{{ item.value }}"
     owner: ceph
     group: ceph
-    mode: 0640
+    mode: "0640"
   loop:
     - option: fsid
       value: "{{ ceph_mon_fsid }}"
@@ -40,53 +40,53 @@
     - option: cluster network
       value: "{{ ceph_mon_cluster_network }}"
 
-- name: check if any node is bootstrapped
+- name: Check if any node is bootstrapped
   ansible.builtin.stat:
     path: "/var/lib/ceph/mon/ceph-{{ hostvars[item]['inventory_hostname_short'] }}/store.db"
   register: _ceph_mon_stat
   loop: "{{ groups[ceph_mon_group] }}"
   delegate_to: "{{ item }}"
 
-- name: select pre-existing bootstrap node if exists
+- name: Select pre-existing bootstrap node if exists
   ansible.builtin.set_fact:
     _ceph_mon_bootstrap_node: "{{ _ceph_mon_stat.results | selectattr('stat.exists', 'equalto', true) | map(attribute='item') | first }}"
   when:
     - _ceph_mon_stat.results | selectattr('stat.exists', 'equalto', true) | length > 0
 
-- name: bootstrap cluster
+- name: Bootstrap cluster
   ansible.builtin.include_tasks: bootstrap-ceph.yml
   when:
     - _ceph_mon_stat.results | selectattr('stat.exists', 'equalto', true) | length == 0
 
-- name: grab admin keyring
+- name: Grab admin keyring
   delegate_to: "{{ _ceph_mon_bootstrap_node }}"
   ansible.builtin.slurp:
     src: /etc/ceph/ceph.client.admin.keyring
   register: _ceph_mon_admin_keyring
   when: inventory_hostname != _ceph_mon_bootstrap_node
 
-- name: upload client.admin keyring
+- name: Upload client.admin keyring
   ansible.builtin.copy:
     content: "{{ _ceph_mon_admin_keyring['content'] | b64decode }}"
     dest: /etc/ceph/ceph.client.admin.keyring
-    mode: 0600
+    mode: "0600"
   when: inventory_hostname != _ceph_mon_bootstrap_node
 
-- name: get monitor keyring
+- name: Get monitor keyring
   ansible.builtin.command: ceph auth get mon. -o /tmp/ceph.mon.keyring
   changed_when: false
   when: inventory_hostname != _ceph_mon_bootstrap_node
 
-- name: get monmap keyring
+- name: Get monmap keyring
   ansible.builtin.command: ceph mon getmap -o /tmp/monmap
   changed_when: false
   when: inventory_hostname != _ceph_mon_bootstrap_node
 
-- name: start monitor
+- name: Start monitor
   ansible.builtin.include_tasks: start-monitor.yml
   when: inventory_hostname != _ceph_mon_bootstrap_node
 
-- name: enable msgr2
+- name: Enable "msgr2"
   ansible.builtin.command: ceph mon enable-msgr2
   changed_when: false
   when: inventory_hostname == _ceph_mon_bootstrap_node
diff --git a/roles/ceph_mon/tasks/start-monitor.yml b/roles/ceph_mon/tasks/start-monitor.yml
index d2f46a0..308d44b 100644
--- a/roles/ceph_mon/tasks/start-monitor.yml
+++ b/roles/ceph_mon/tasks/start-monitor.yml
@@ -12,13 +12,13 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
-- name: mkfs monitor
+- name: Create monitor filesystem
   ansible.builtin.shell: |
     ceph-mon --mkfs -i {{ inventory_hostname_short }} --monmap /tmp/monmap --keyring /tmp/ceph.mon.keyring
   args:
     creates: "/var/lib/ceph/mon/ceph-{{ inventory_hostname_short }}/store.db"
 
-- name: ensure permissions are fixed
+- name: Ensure permissions are fixed
   ansible.builtin.file:
     path: "/var/lib/ceph/mon/ceph-{{ inventory_hostname_short }}"
     owner: ceph
@@ -26,7 +26,7 @@
     recurse: true
 
 # NOTE(mnaser): https://bugs.launchpad.net/ubuntu/+source/ceph/+bug/1917414/comments/30
-- name: workaround for aarch64 systems
+- name: Workaround for aarch64 systems
   community.general.ini_file:
     path: /lib/systemd/system/ceph-mon@.service
     section: Service
@@ -34,11 +34,11 @@
     value: false
     owner: ceph
     group: ceph
-    mode: 0644
+    mode: "0644"
   register: _ceph_aarch64_fix
   when: ansible_architecture == 'aarch64'
 
-- name: enable and start service
+- name: Enable and start service
   ansible.builtin.service:
     name: "ceph-mon@{{ inventory_hostname_short }}"
     state: started
diff --git a/roles/ceph_osd/tasks/main.yml b/roles/ceph_osd/tasks/main.yml
index e67a6b6..989424d 100644
--- a/roles/ceph_osd/tasks/main.yml
+++ b/roles/ceph_osd/tasks/main.yml
@@ -12,12 +12,12 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
-- name: install packages
+- name: Install packages
   ansible.builtin.apt:
     name: ["udev", "ceph-osd"]
     install_recommends: false
 
-- name: grab ceph fsid from monitors
+- name: Grab ceph fsid from monitors
   delegate_to: "{{ groups[ceph_osd_mons_group][0] }}"
   register: _ceph_fsid
   changed_when: false
@@ -35,7 +35,7 @@
   ansible.builtin.set_fact:
     ceph_monitors: "{{ _ceph_mon_dump.stdout | from_json | community.general.json_query('mons[*].addr') | map('regex_replace', '(.*):(.*)', '\\1') }}"
 
-- name: generate basic configuration file
+- name: Generate basic configuration file
   community.general.ini_file:
     path: /etc/ceph/ceph.conf
     section: global
@@ -43,39 +43,39 @@
     value: "{{ item.value }}"
     owner: ceph
     group: ceph
-    mode: 0640
+    mode: "0640"
   loop:
     - option: fsid
       value: "{{ _ceph_fsid.stdout | trim }}"
     - option: mon host
       value: "{{ ceph_monitors | join(',') }}"
 
-- name: grab bootstrap-osd from monitors
+- name: Grab bootstrap-osd from monitors
   delegate_to: "{{ groups[ceph_osd_mons_group][0] }}"
   register: _ceph_bootstrap_osd_keyring
   changed_when: false
   ansible.builtin.command: ceph auth get client.bootstrap-osd
 
-- name: install bootstrap-osd keyring
+- name: Install bootstrap-osd keyring
   ansible.builtin.copy:
     content: "{{ _ceph_bootstrap_osd_keyring.stdout }}\n"
     dest: /var/lib/ceph/bootstrap-osd/ceph.keyring
     owner: ceph
     group: ceph
-    mode: 0640
+    mode: "0640"
 
-- name: workaround to allow usage of loop devices
+- name: Workaround to allow usage of loop devices
   ansible.builtin.replace:
     path: /usr/lib/python3/dist-packages/ceph_volume/util/disk.py
     regexp: "'mpath']"
     replace: "'mpath', 'loop']"
     owner: ceph
     group: ceph
-    mode: 0640
+    mode: "0640"
   when: molecule | default(false)
 
 # NOTE(mnaser): https://bugs.launchpad.net/ubuntu/+source/ceph/+bug/1917414/comments/30
-- name: workaround for aarch64 systems
+- name: Workaround for aarch64 systems
   community.general.ini_file:
     path: /lib/systemd/system/ceph-osd@.service
     section: Service
@@ -83,22 +83,22 @@
     value: false
     owner: ceph
     group: ceph
-    mode: 0644
+    mode: "0644"
   register: _ceph_aarch64_fix
   when: ansible_architecture == 'aarch64'
 
-- name: reload systemd
+- name: Reload systemd
   ansible.builtin.service:
     daemon_reload: "{{ _ceph_aarch64_fix.changed }}"
 
-- name: get which devices don't contain osds
+- name: Get which devices don't contain osds
   register: _ceph_osd_check
   failed_when: false
   changed_when: false
   ansible.builtin.command: /usr/sbin/ceph-volume lvm list {{ item }}
   loop: "{{ ceph_osd_devices }}"
 
-- name: create osds for volumes which are not setup
+- name: Create osds for volumes which are not setup
   changed_when: true
   ansible.builtin.command: /usr/sbin/ceph-volume lvm create --data {{ item }}
   loop: "{{ _ceph_osd_check.results | selectattr('rc', 'equalto', 1) | map(attribute='item') }}"
diff --git a/roles/ceph_repository/tasks/main.yml b/roles/ceph_repository/tasks/main.yml
index 72714db..e3d184e 100644
--- a/roles/ceph_repository/tasks/main.yml
+++ b/roles/ceph_repository/tasks/main.yml
@@ -12,23 +12,23 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
-- name: install packages
+- name: Install packages
   ansible.builtin.apt:
     name: ["gnupg"]
     install_recommends: false
 
-- name: add apt key
+- name: Add apt key
   ansible.builtin.apt_key:
     url: "{{ ceph_repository_apt_key }}"
     state: present
 
-- name: configure version pinning
+- name: Configure version pinning
   ansible.builtin.template:
     src: apt-preferences.j2
     dest: /etc/apt/preferences.d/ceph
-    mode: 0644
+    mode: "0644"
 
-- name: add apt repository
+- name: Add apt repository
   ansible.builtin.apt_repository:
     repo: "deb {{ ceph_repository_url }} {{ ansible_distribution_release }} main"
     state: present
diff --git a/roles/cluster_issuer/tasks/main.yml b/roles/cluster_issuer/tasks/main.yml
index 87e3d27..aab9fcb 100644
--- a/roles/cluster_issuer/tasks/main.yml
+++ b/roles/cluster_issuer/tasks/main.yml
@@ -44,7 +44,7 @@
       ansible.builtin.copy:
         content: "{{ _cluster_issuer_ca_secret.resources[0].data['tls.crt'] | b64decode }}"
         dest: /usr/local/share/ca-certificates/atmosphere.crt
-        mode: 0644
+        mode: "0644"
       notify:
         - Update CA certificates on host
 
diff --git a/roles/containerd/defaults/main.yml b/roles/containerd/defaults/main.yml
index 60d4249..1b3525f 100644
--- a/roles/containerd/defaults/main.yml
+++ b/roles/containerd/defaults/main.yml
@@ -1,21 +1,56 @@
----
-# .. vim: foldmarker=[[[,]]]:foldmethod=marker
-
-# .. Copyright (C) 2022 VEXXHOST, Inc.
-# .. SPDX-License-Identifier: Apache-2.0
-
-# Default variables
-# =================
-
-# .. contents:: Sections
-#    :local:
-
-
-# .. envvar:: containerd_pause_image [[[
+# Copyright (c) 2023 VEXXHOST, Inc.
 #
-# Image to use for ``containerd`` pause container
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+containerd_bin_dir: /usr/bin
+
+containerd_version: 1.6.15
+containerd_archive_checksums:
+  arm64:
+    1.6.15: d63e4d27c51e33cd10f8b5621c559f09ece8a65fec66d80551b36cac9e61a07d
+  amd64:
+    1.6.15: 191bb4f6e4afc237efc5c85b5866b6fdfed731bde12cceaa6017a9c7f8aeda02
+
+containerd_download_url: "https://github.com/containerd/containerd/releases/download/v{{ containerd_version }}/containerd-{{ containerd_version }}-{{ ansible_system | lower }}-{{ download_artifact_goarch }}.tar.gz" # noqa: yaml[line-length]
+containerd_download_dest: "{{ containerd_download_unarchive_dest }}.tar.gz"
+containerd_download_unarchive_dest: "{{ download_artifact_work_directory }}/containerd-{{ containerd_version }}-{{ ansible_system | lower }}-{{ download_artifact_goarch }}" # noqa: yaml[line-length]
+containerd_archive_checksum: "{{ containerd_archive_checksums[download_artifact_goarch][containerd_version] }}"
+
 containerd_pause_image: k8s.gcr.io/pause:3.5
-
-                                                                   # ]]]
-
 containerd_insecure_registries: []
+
+# NOTE(mnaser): This is to accomodate for the uninstallation of the old packages
+#               that shipped with the operating system
+containerd_package_name:
+  - containerd
+  - containerd.io
+
+containerd_binaries:
+  - containerd
+  - containerd-shim
+  - containerd-shim-runc-v1
+  - containerd-shim-runc-v2
+  - containerd-stress
+  - ctr
+
+containerd_cfg_dir: /etc/containerd
+containerd_storage_dir: /var/lib/containerd
+containerd_state_dir: /run/containerd
+
+containerd_debug_level: "info"
+containerd_max_container_log_line_size: -1
+
+containerd_limit_proc_num: "infinity"
+containerd_limit_core: "infinity"
+containerd_limit_open_file_num: "infinity"
+containerd_limit_mem_lock: "infinity"
diff --git a/roles/containerd/meta/main.yml b/roles/containerd/meta/main.yml
index a9d5c50..87b064b 100644
--- a/roles/containerd/meta/main.yml
+++ b/roles/containerd/meta/main.yml
@@ -1,4 +1,4 @@
-# Copyright (c) 2022 VEXXHOST, Inc.
+# Copyright (c) 2023 VEXXHOST, Inc.
 #
 # Licensed under the Apache License, Version 2.0 (the "License"); you may
 # not use this file except in compliance with the License. You may obtain
@@ -19,9 +19,33 @@
   min_ansible_version: 5.5.0
   standalone: false
   platforms:
+    - name: Debian
+      versions:
+        - buster
+        - bullseye
+    - name: Fedora
+      versions:
+        - "36"
+        - "37"
+    - name: EL
+      versions:
+        - "8"
+        - "9"
     - name: Ubuntu
       versions:
         - focal
+        - jammy
 
 dependencies:
   - role: defaults
+  - role: runc
+  - role: download_artifact
+    download_artifact_url: "{{ containerd_download_url }}"
+    download_artifact_dest: "{{ containerd_download_dest }}"
+    download_artifact_checksum: "sha256:{{ containerd_archive_checksum }}"
+    download_artifact_owner: root
+    download_artifact_mode: "0755"
+    download_artifact_unarchive: true
+    download_artifact_unarchive_dest: "{{ containerd_download_unarchive_dest }}"
+    download_artifact_unarchive_extra_opts:
+      - --strip-components=1
diff --git a/roles/containerd/tasks/main.yml b/roles/containerd/tasks/main.yml
index 95b38e6..3040bd7 100644
--- a/roles/containerd/tasks/main.yml
+++ b/roles/containerd/tasks/main.yml
@@ -1,4 +1,4 @@
-# Copyright (c) 2022 VEXXHOST, Inc.
+# Copyright (c) 2023 VEXXHOST, Inc.
 #
 # Licensed under the Apache License, Version 2.0 (the "License"); you may
 # not use this file except in compliance with the License. You may obtain
@@ -12,48 +12,60 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
-- name: Add repository
-  ansible.builtin.apt_repository:
-    repo: "deb {{ containerd_repository_url }} {{ ansible_distribution_release }} main"
-    state: present
-  when:
-    - containerd_repository_url is defined
+- name: Remove containerd package
+  ansible.builtin.package:
+    name: "{{ containerd_package_name }}"
+    state: absent
 
-- name: Install packages
-  ansible.builtin.apt:
-    name: containerd
-    state: present
+- name: Install containerd binaries
+  ansible.builtin.copy:
+    src: "{{ containerd_download_unarchive_dest }}/{{ item }}"
+    dest: "{{ containerd_bin_dir }}/{{ item }}"
+    mode: "0755"
+    remote_src: true
+  loop: "{{ containerd_binaries }}"
+  notify: Restart containerd
 
-- name: Create folder for configuration
+- name: Remove containerd orphaned binaries
   ansible.builtin.file:
-    path: /etc/containerd
+    path: "/usr/bin/{{ item }}"
+    state: absent
+  loop: "{{ containerd_binaries }}"
+  when: containerd_bin_dir != "/usr/bin"
+
+- name: Create systemd service file for containerd
+  ansible.builtin.template:
+    src: containerd.service.j2
+    dest: /etc/systemd/system/containerd.service
+    mode: "0644"
+  notify:
+    - Reload systemd
+    - Restart containerd
+
+- name: Create folders for configuration
+  ansible.builtin.file:
+    dest: "{{ item }}"
     state: directory
+    mode: "0755"
     owner: root
     group: root
-    mode: 0755
+  with_items:
+    - "{{ containerd_cfg_dir }}"
+    - "{{ containerd_storage_dir }}"
+    - "{{ containerd_state_dir }}"
   notify:
     - Restart containerd
 
-- name: Update pause image in configuration
+- name: Create containerd config file
   ansible.builtin.template:
     src: config.toml.j2
     dest: /etc/containerd/config.toml
     owner: root
     group: root
-    mode: 0644
+    mode: "0644"
   notify:
     - Restart containerd
 
-- name: Bump DefaultLimitMEMLOCK for system
-  ansible.builtin.lineinfile:
-    path: /etc/systemd/system.conf
-    regexp: '^DefaultLimitMEMLOCK='
-    line: 'DefaultLimitMEMLOCK=infinity'
-    state: present
-  notify:
-    - Reload systemd
-    - Restart containerd
-
 - name: Force any restarts if necessary
   ansible.builtin.meta: flush_handlers
 
diff --git a/roles/containerd/templates/config.toml.j2 b/roles/containerd/templates/config.toml.j2
index 16c0ae1..55b6b41 100644
--- a/roles/containerd/templates/config.toml.j2
+++ b/roles/containerd/templates/config.toml.j2
@@ -1,8 +1,12 @@
 version = 2
 
+[debug]
+  level = "{{ containerd_debug_level | default('info') }}"
+
 [plugins]
   [plugins."io.containerd.grpc.v1.cri"]
     sandbox_image = "{{ containerd_pause_image }}"
+    max_container_log_line_size = {{ containerd_max_container_log_line_size }}
   [plugins."io.containerd.grpc.v1.cri".registry]
     [plugins."io.containerd.grpc.v1.cri".registry.mirrors]
       {% for registry in containerd_insecure_registries %}
diff --git a/roles/containerd/templates/containerd.service.j2 b/roles/containerd/templates/containerd.service.j2
new file mode 100644
index 0000000..adebcf2
--- /dev/null
+++ b/roles/containerd/templates/containerd.service.j2
@@ -0,0 +1,41 @@
+# Copyright The containerd Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+[Unit]
+Description=containerd container runtime
+Documentation=https://containerd.io
+After=network.target local-fs.target
+
+[Service]
+ExecStartPre=-/sbin/modprobe overlay
+ExecStart={{ containerd_bin_dir }}/containerd
+
+Type=notify
+Delegate=yes
+KillMode=process
+Restart=always
+RestartSec=5
+# Having non-zero Limit*s causes performance problems due to accounting overhead
+# in the kernel. We recommend using cgroups to do container-local accounting.
+LimitNPROC={{ containerd_limit_proc_num }}
+LimitCORE={{ containerd_limit_core }}
+LimitNOFILE={{ containerd_limit_open_file_num }}
+LimitMEMLOCK={{ containerd_limit_mem_lock }}
+# Comment TasksMax if your systemd version does not supports it.
+# Only systemd 226 and above support this version.
+TasksMax=infinity
+OOMScoreAdjust=-999
+
+[Install]
+WantedBy=multi-user.target
diff --git a/roles/crictl/README.md b/roles/crictl/README.md
new file mode 100644
index 0000000..f02acf0
--- /dev/null
+++ b/roles/crictl/README.md
@@ -0,0 +1 @@
+# `crictl`
diff --git a/roles/crictl/defaults/main.yml b/roles/crictl/defaults/main.yml
new file mode 100644
index 0000000..a3d2c0c
--- /dev/null
+++ b/roles/crictl/defaults/main.yml
@@ -0,0 +1,33 @@
+# Copyright (c) 2023 VEXXHOST, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+crictl_bin_dir: /usr/bin
+
+crictl_version: v1.25.0
+crictl_checksums:
+  arm64:
+    v1.25.0: 651c939eca010bbf48cc3932516b194028af0893025f9e366127f5b50ad5c4f4
+  amd64:
+    v1.25.0: 86ab210c007f521ac4cdcbcf0ae3fb2e10923e65f16de83e0e1db191a07f0235
+
+crictl_download_url: "https://github.com/kubernetes-sigs/cri-tools/releases/download/{{ crictl_version }}/crictl-{{ crictl_version }}-{{ ansible_system | lower }}-{{ download_artifact_goarch }}.tar.gz" # noqa: yaml[line-length]
+crictl_download_dest: "{{ crictl_download_unarchive_dest }}.tar.gz"
+crictl_download_unarchive_dest: "{{ download_artifact_work_directory }}/crictl-{{ crictl_version }}-{{ ansible_system | lower }}-{{ download_artifact_goarch }}"
+crictl_binary_checksum: "{{ crictl_checksums[download_artifact_goarch][crictl_version] }}"
+
+# NOTE(mnaser): This is to accomodate for the uninstallation of the old packages
+#               that shipped with the operating system
+crictl_package_name: cri-tools
+
+crictl_socket: /run/containerd/containerd.sock
diff --git a/roles/crictl/meta/main.yml b/roles/crictl/meta/main.yml
new file mode 100644
index 0000000..c8c51ab
--- /dev/null
+++ b/roles/crictl/meta/main.yml
@@ -0,0 +1,47 @@
+# Copyright (c) 2023 VEXXHOST, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+galaxy_info:
+  author: VEXXHOST, Inc.
+  description: Ansible role for "crictl"
+  license: Apache-2.0
+  min_ansible_version: 5.5.0
+  standalone: false
+  platforms:
+    - name: Debian
+      versions:
+        - buster
+        - bullseye
+    - name: Fedora
+      versions:
+        - "36"
+        - "37"
+    - name: EL
+      versions:
+        - "8"
+        - "9"
+    - name: Ubuntu
+      versions:
+        - focal
+        - jammy
+
+dependencies:
+  - role: download_artifact
+    download_artifact_url: "{{ crictl_download_url }}"
+    download_artifact_dest: "{{ crictl_download_dest }}"
+    download_artifact_checksum: "sha256:{{ crictl_binary_checksum }}"
+    download_artifact_owner: root
+    download_artifact_mode: "0755"
+    download_artifact_unarchive: true
+    download_artifact_unarchive_dest: "{{ crictl_download_unarchive_dest }}"
diff --git a/roles/crictl/tasks/main.yml b/roles/crictl/tasks/main.yml
new file mode 100644
index 0000000..d4801f3
--- /dev/null
+++ b/roles/crictl/tasks/main.yml
@@ -0,0 +1,38 @@
+# Copyright (c) 2023 VEXXHOST, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+- name: Uninstall crictl package
+  ansible.builtin.package:
+    name: "{{ crictl_package_name }}"
+    state: absent
+
+- name: Copy crictl binary from download dir
+  ansible.builtin.copy:
+    src: "{{ download_artifact_dest }}"
+    dest: "{{ crictl_bin_dir }}/crictl"
+    mode: "0755"
+    remote_src: true
+
+- name: Create crictl config
+  ansible.builtin.template:
+    src: crictl.yaml.j2
+    dest: /etc/crictl.yaml
+    owner: root
+    mode: "0644"
+
+- name: Remove crictl orphaned binary
+  ansible.builtin.file:
+    path: /usr/bin/crictl
+    state: absent
+  when: crictl_bin_dir != "/usr/bin"
diff --git a/roles/crictl/templates/crictl.yaml.j2 b/roles/crictl/templates/crictl.yaml.j2
new file mode 100644
index 0000000..a3355fa
--- /dev/null
+++ b/roles/crictl/templates/crictl.yaml.j2
@@ -0,0 +1,4 @@
+runtime-endpoint: unix://{{ crictl_socket }}
+image-endpoint: unix://{{ crictl_socket }}
+timeout: 30
+debug: false
diff --git a/roles/defaults/defaults/main.yml b/roles/defaults/defaults/main.yml
index 6290727..199d9ee 100644
--- a/roles/defaults/defaults/main.yml
+++ b/roles/defaults/defaults/main.yml
@@ -67,7 +67,7 @@
   horizon: quay.io/vexxhost/horizon:zed
   ingress_nginx_controller: k8s.gcr.io/ingress-nginx/controller:v1.1.1@sha256:0bc88eb15f9e7f84e8e56c14fa5735aaa488b840983f87bd79b1054190e660de
   ingress_nginx_default_backend: k8s.gcr.io/defaultbackend-amd64:1.5
-  ingress_nginx_kube_webhook_certgen: k8s.gcr.io/ingress-nginx/kube-webhook-certgen:v1.1.1@sha256:64d8c73dca984af206adf9d6d7e46aa550362b1d7a01f3a0a91b20cc67868660
+  ingress_nginx_kube_webhook_certgen: k8s.gcr.io/ingress-nginx/kube-webhook-certgen:v1.1.1@sha256:64d8c73dca984af206adf9d6d7e46aa550362b1d7a01f3a0a91b20cc67868660 # noqa: yaml[line-length]
   keepalived: us-docker.pkg.dev/vexxhost-infra/openstack/keepalived:2.0.19
   keystone_api: quay.io/vexxhost/keystone:zed
   keystone_credential_cleanup: quay.io/vexxhost/heat:zed
diff --git a/roles/download_artifact/README.md b/roles/download_artifact/README.md
new file mode 100644
index 0000000..3b14d72
--- /dev/null
+++ b/roles/download_artifact/README.md
@@ -0,0 +1 @@
+# `binary_download`
diff --git a/roles/download_artifact/defaults/main.yml b/roles/download_artifact/defaults/main.yml
new file mode 100644
index 0000000..fa44536
--- /dev/null
+++ b/roles/download_artifact/defaults/main.yml
@@ -0,0 +1,35 @@
+# Copyright (c) 2023 VEXXHOST, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+download_artifact_work_directory: /var/lib/downloads
+
+# download_artifact_url:
+# download_artifact_dest:
+# download_artifact_owner:
+# download_artifact_mode:
+# download_artifact_checksum:
+download_artifact_validate_certs: true
+# download_artifact_force_basic_auth:
+# download_artifact_url_username:
+# download_artifact_url_password:
+
+# NOTE(fitbeard): If this is false, debug information will be displayed but may
+#                 contain some private data, so it is recommended to set it to
+#                 true a production environment.
+download_artifact_no_log: true
+
+# Unarchive the downloaded file
+download_artifact_unarchive: false
+# download_artifact_unarchive_dest:
+# download_artifact_unarchive_extra_opts:
diff --git a/roles/download_artifact/meta/main.yml b/roles/download_artifact/meta/main.yml
new file mode 100644
index 0000000..d17ab0d
--- /dev/null
+++ b/roles/download_artifact/meta/main.yml
@@ -0,0 +1,28 @@
+# Copyright (c) 2023 VEXXHOST, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+galaxy_info:
+  author: VEXXHOST, Inc.
+  description: Ansible role for "binary_download"
+  license: Apache-2.0
+  min_ansible_version: 5.5.0
+  standalone: false
+  platforms:
+    - name: EL
+      versions:
+        - "8"
+        - "9"
+    - name: Ubuntu
+      versions:
+        - focal
diff --git a/roles/download_artifact/tasks/main.yml b/roles/download_artifact/tasks/main.yml
new file mode 100644
index 0000000..a43ec20
--- /dev/null
+++ b/roles/download_artifact/tasks/main.yml
@@ -0,0 +1,73 @@
+# Copyright (c) 2023 VEXXHOST, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+- name: Starting download of file
+  ansible.builtin.debug:
+    msg: "{{ download_artifact_url }}"
+
+- name: Ensure the work directory exists on the target host
+  ansible.builtin.file:
+    path: "{{ download_artifact_work_directory }}"
+    state: directory
+    mode: "0755"
+
+- name: Create destination directory on node
+  ansible.builtin.file:
+    path: "{{ download_artifact_dest | dirname }}"
+    owner: "{{ download_artifact_owner | default(omit) }}"
+    mode: "0755"
+    state: directory
+    recurse: true
+
+- name: Download item
+  ansible.builtin.get_url:
+    url: "{{ download_artifact_url }}"
+    dest: "{{ download_artifact_dest }}"
+    owner: "{{ download_artifact_owner | default(omit) }}"
+    mode: "{{ download_artifact_mode | default(omit) }}"
+    checksum: "{{ download_artifact_checksum | default(omit) }}"
+    validate_certs: "{{ download_artifact_validate_certs | default(omit) }}"
+    force_basic_auth: "{{ download_artifact_force_basic_auth | default(omit) }}"
+    url_username: "{{ download_artifact_url_username | default(omit) }}"
+    url_password: "{{ download_artifact_url_password | default(omit) }}"
+  register: _download_artifact_result
+  until: "'OK' in _download_artifact_result.msg or 'file already exists' in _download_artifact_result.msg"
+  retries: 4
+  # NOTE(fitbeard): This task will avoid logging it's parameters to not leak
+  #                 environment passwords in the log.
+  no_log: "{{ download_artifact_no_log | bool }}"
+
+- name: Extract archive
+  when: download_artifact_unarchive
+  block:
+    - name: Ensure that 'tar' command is available
+      ansible.builtin.package:
+        name: tar
+        state: present
+
+    - name: Create new folder for unarchiving
+      ansible.builtin.file:
+        path: "{{ download_artifact_unarchive_dest }}"
+        state: directory
+        mode: "0755"
+        recurse: true
+
+    - name: Extract archive
+      ansible.builtin.unarchive:
+        src: "{{ download_artifact_dest }}"
+        dest: "{{ download_artifact_unarchive_dest }}"
+        owner: "{{ download_artifact_owner | default(omit) }}"
+        mode: "{{ download_artifact_mode | default(omit) }}"
+        remote_src: true
+        extra_opts: "{{ download_artifact_unarchive_extra_opts | default(omit) }}"
diff --git a/roles/download_artifact/vars/main.yml b/roles/download_artifact/vars/main.yml
new file mode 100644
index 0000000..047e157
--- /dev/null
+++ b/roles/download_artifact/vars/main.yml
@@ -0,0 +1,25 @@
+# Copyright (c) 2023 VEXXHOST, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+_download_artifact_goarch_groups:
+  x86_64: amd64
+  aarch64: arm64
+  armv7l: arm
+
+download_artifact_goarch: >-
+  {%- if ansible_architecture in _download_artifact_goarch_groups -%}
+  {{ _download_artifact_goarch_groups[ansible_architecture] }}
+  {%- else -%}
+  {{ ansible_architecture }}
+  {%- endif -%}
diff --git a/roles/ethtool/tasks/main.yml b/roles/ethtool/tasks/main.yml
index f8293d8..fac1d49 100644
--- a/roles/ethtool/tasks/main.yml
+++ b/roles/ethtool/tasks/main.yml
@@ -2,7 +2,7 @@
   ansible.builtin.file:
     path: /etc/networkd-dispatcher/configuring.d
     state: directory
-    mode: 0755
+    mode: "0755"
     owner: root
     group: root
 
@@ -10,7 +10,7 @@
   ansible.builtin.copy:
     src: ethtool
     dest: /etc/networkd-dispatcher/configuring.d/01-ethtool
-    mode: 0755
+    mode: "0755"
     owner: root
     group: root
 
diff --git a/roles/glance/vars/main.yml b/roles/glance/vars/main.yml
index af5a431..82362f9 100644
--- a/roles/glance/vars/main.yml
+++ b/roles/glance/vars/main.yml
@@ -24,11 +24,11 @@
       glance:
         container:
           glance_api:
-            allowPrivilegeEscalation: "{{ ('cinder' in glance_helm_values.get('conf', {}).get('glance', {}).get('glance_store', {}).get('stores', '')) | bool }}"
-            readOnlyRootFilesystem: "{{ ('cinder' not in glance_helm_values.get('conf', {}).get('glance', {}).get('glance_store', {}).get('stores', '')) | bool }}"
+            allowPrivilegeEscalation: "{{ ('cinder' in glance_helm_values.get('conf', {}).get('glance', {}).get('glance_store', {}).get('stores', '')) | bool }}" # noqa: yaml[line-length]
+            readOnlyRootFilesystem: "{{ ('cinder' not in glance_helm_values.get('conf', {}).get('glance', {}).get('glance_store', {}).get('stores', '')) | bool }}" # noqa: yaml[line-length]
             privileged: "{{ ('cinder' in glance_helm_values.get('conf', {}).get('glance', {}).get('glance_store', {}).get('stores', '')) | bool }}"
             capabilities:
-              add: "{{ ('cinder' in glance_helm_values.get('conf', {}).get('glance', {}).get('glance_store', {}).get('stores', '')) | ternary(['SYS_ADMIN'], []) }}"
+              add: "{{ ('cinder' in glance_helm_values.get('conf', {}).get('glance', {}).get('glance_store', {}).get('stores', '')) | ternary(['SYS_ADMIN'], []) }}" # noqa: yaml[line-length]
     useHostNetwork:
       api: "{{ ('cinder' in glance_helm_values.get('conf', {}).get('glance', {}).get('glance_store', {}).get('stores', '')) | bool }}"
     replicas:
diff --git a/roles/glance_image/tasks/main.yml b/roles/glance_image/tasks/main.yml
index 70aaf0d..a47c37d 100644
--- a/roles/glance_image/tasks/main.yml
+++ b/roles/glance_image/tasks/main.yml
@@ -37,7 +37,7 @@
       ansible.builtin.get_url:
         url: "{{ glance_image_url }}"
         dest: "{{ _workdir.path }}/{{ glance_image_url | basename }}"
-        mode: 0600
+        mode: "0600"
       register: _get_url
 
     - name: Get image format
@@ -51,6 +51,7 @@
 
     - name: Convert file to target disk format
       when: _image_format.stdout != glance_image_disk_format
+      changed_when: true
       ansible.builtin.command:
         qemu-img convert -O {{ glance_image_disk_format }} {{ _get_url.dest }} {{ _get_url.dest }}.converted
 
diff --git a/roles/helm/tasks/debian.yml b/roles/helm/tasks/debian.yml
index 59df4d0..d3da113 100644
--- a/roles/helm/tasks/debian.yml
+++ b/roles/helm/tasks/debian.yml
@@ -12,16 +12,16 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
-- name: add apt key
+- name: Add apt key
   ansible.builtin.apt_key:
     url: "{{ helm_repository_apt_key }}"
     state: present
 
-- name: configure version pinning
+- name: Configure version pinning
   ansible.builtin.template:
     src: apt-preferences.j2
     dest: /etc/apt/preferences.d/helm
-    mode: 0644
+    mode: "0644"
 
 - name: Add package repository
   ansible.builtin.apt_repository:
@@ -33,7 +33,7 @@
     name: ["git", "helm"]
     install_recommends: false
 
-- name: add bash autocomplete for helm
+- name: Add bash autocomplete for helm
   ansible.builtin.lineinfile:
     path: /root/.bashrc
     line: 'source <(helm completion bash)'
diff --git a/roles/helm/tasks/main.yml b/roles/helm/tasks/main.yml
index d8cadc2..40121cc 100644
--- a/roles/helm/tasks/main.yml
+++ b/roles/helm/tasks/main.yml
@@ -20,7 +20,7 @@
 - name: Install packages using operating system specific process
   ansible.builtin.include_tasks: "{{ ansible_os_family | lower }}.yml"
 
-- name: install helm diff
+- name: Install helm diff
   kubernetes.core.helm_plugin:
     plugin_path: https://github.com/databus23/helm-diff
     state: present
diff --git a/roles/image_manifest/tasks/main.yml b/roles/image_manifest/tasks/main.yml
index b07c9b7..d2c3d8f 100644
--- a/roles/image_manifest/tasks/main.yml
+++ b/roles/image_manifest/tasks/main.yml
@@ -27,4 +27,4 @@
   ansible.builtin.copy:
     dest: "{{ image_manifest_path }}"
     content: "{{ image_manifest_data | to_nice_yaml(indent=2) }}"
-    mode: 0644
+    mode: "0644"
diff --git a/roles/keepalived/tasks/main.yml b/roles/keepalived/tasks/main.yml
index 3414049..ec6d8d5 100644
--- a/roles/keepalived/tasks/main.yml
+++ b/roles/keepalived/tasks/main.yml
@@ -175,7 +175,7 @@
                     optional: false
                     secretName: keepalived-etc
                 - configMap:
-                    defaultMode: 0755
+                    defaultMode: 0755 # noqa: yaml[octal-values]
                     name: keepalived-bin
                     optional: false
                   name: keepalived-bin
diff --git a/roles/kubernetes/tasks/bootstrap-cluster.yml b/roles/kubernetes/tasks/bootstrap-cluster.yml
index 4696b42..c3981d9 100644
--- a/roles/kubernetes/tasks/bootstrap-cluster.yml
+++ b/roles/kubernetes/tasks/bootstrap-cluster.yml
@@ -42,7 +42,7 @@
     dest: /etc/kubernetes/kubeadm.yaml
     owner: root
     group: root
-    mode: 0640
+    mode: "0640"
   when: inventory_hostname == _kubernetes_bootstrap_node
 
 - name: Initialize cluster
diff --git a/roles/kubernetes/tasks/control-plane.yml b/roles/kubernetes/tasks/control-plane.yml
index 9d444d7..10f45b0 100644
--- a/roles/kubernetes/tasks/control-plane.yml
+++ b/roles/kubernetes/tasks/control-plane.yml
@@ -21,26 +21,26 @@
         state: directory
         owner: root
         group: root
-        mode: 0755
+        mode: "0755"
     - name: Upload configuration
       ansible.builtin.template:
         src: keepalived.conf.j2
         dest: /etc/keepalived/keepalived.conf
         owner: root
         group: root
-        mode: 0644
+        mode: "0644"
     - name: Upload health check
       ansible.builtin.template:
         src: check_apiserver.sh.j2
         dest: /etc/keepalived/check_apiserver.sh
-        mode: 0755
+        mode: "0755"
     - name: Upload Kubernetes manifest
       ansible.builtin.template:
         src: keepalived.yaml.j2
         dest: /etc/kubernetes/manifests/keepalived.yaml
         owner: root
         group: root
-        mode: 0644
+        mode: "0644"
 
 - name: Upload configuration for HAproxy
   when: kubernetes_keepalived_interface is defined
@@ -51,32 +51,32 @@
         state: directory
         owner: root
         group: root
-        mode: 0755
+        mode: "0755"
     - name: Upload configuration
       ansible.builtin.template:
         src: haproxy.cfg.j2
         dest: /etc/haproxy/haproxy.cfg
         owner: root
         group: root
-        mode: 0644
+        mode: "0644"
     - name: Upload Kubernetes manifest
       ansible.builtin.template:
         src: haproxy.yaml.j2
         dest: /etc/kubernetes/manifests/haproxy.yaml
         owner: root
         group: root
-        mode: 0644
+        mode: "0644"
 
 - name: Bootstrap cluster
   ansible.builtin.include_tasks: bootstrap-cluster.yml
 
-- name: create folder for admin configuration
+- name: Create folder for admin configuration
   ansible.builtin.file:
     path: /root/.kube
     state: directory
     owner: root
     group: root
-    mode: 0750
+    mode: "0750"
 
 - name: Creating a symlink for admin configuration file
   ansible.builtin.file:
@@ -85,18 +85,18 @@
     state: link
     force: true
 
-- name: add bash autocomplete for kubectl
+- name: Add bash autocomplete for kubectl
   ansible.builtin.lineinfile:
     path: /root/.bashrc
     line: 'source <(kubectl completion bash)'
     insertbefore: EOF
 
-- name: install pip
+- name: Install PIP
   ansible.builtin.apt:
     name: python3-pip
     install_recommends: false
 
-- name: install kubernetes python package
+- name: Install Kubernetes python package
   ansible.builtin.pip:
     name: kubernetes
 
diff --git a/roles/kubernetes/tasks/join-cluster.yml b/roles/kubernetes/tasks/join-cluster.yml
index 1b3e6dc..8b29c39 100644
--- a/roles/kubernetes/tasks/join-cluster.yml
+++ b/roles/kubernetes/tasks/join-cluster.yml
@@ -39,6 +39,7 @@
 - name: Generate token for joining cluster
   run_once: true
   delegate_to: "{{ _kubernetes_bootstrap_node | default(groups[kubernetes_control_plane_group][0]) }}"
+  changed_when: true
   ansible.builtin.shell: |
     kubeadm token create
   register: _kubeadm_token_create
@@ -51,7 +52,7 @@
     dest: /etc/kubernetes/kubeadm.yaml
     owner: root
     group: root
-    mode: 0640
+    mode: "0640"
   when:
     - not _stat_etc_kubernetes_kubelet_conf.stat.exists
 
diff --git a/roles/kubernetes/tasks/main.yml b/roles/kubernetes/tasks/main.yml
index 8051d13..b9d55f5 100644
--- a/roles/kubernetes/tasks/main.yml
+++ b/roles/kubernetes/tasks/main.yml
@@ -18,7 +18,7 @@
     dest: /usr/share/keyrings/kubernetes-archive-keyring.gpg
     owner: root
     group: root
-    mode: 0644
+    mode: "0644"
   when:
     - kubernetes_repo_url == _kubernetes_upstream_apt_repository
 
@@ -36,7 +36,7 @@
   ansible.builtin.template:
     src: apt-preferences.j2
     dest: /etc/apt/preferences.d/kubernetes
-    mode: 0644
+    mode: "0644"
 
 - name: Install packages
   ansible.builtin.apt:
@@ -52,7 +52,7 @@
     src: crictl.yaml.j2
     dest: /etc/crictl.yaml
     owner: root
-    mode: 0644
+    mode: "0644"
 
 - name: Enable kernel modules on-boot
   ansible.builtin.template:
@@ -60,7 +60,7 @@
     dest: /etc/modules-load.d/k8s.conf
     owner: root
     group: root
-    mode: 0644
+    mode: "0644"
 
 - name: Enable kernel modules in runtime
   community.general.modprobe:
@@ -82,6 +82,7 @@
 
 - name: Disable swap
   ansible.builtin.command: /sbin/swapoff -a
+  changed_when: true
   ignore_errors: "{{ ansible_check_mode }}"
   when:
     - _swapon.stdout
@@ -101,7 +102,7 @@
     dest: /etc/systemd/system/noswap.service
     owner: root
     group: root
-    mode: 0644
+    mode: "0644"
   notify: Enable noswap service
 
 - name: Configure short hostname
diff --git a/roles/openstack_cli/tasks/main.yml b/roles/openstack_cli/tasks/main.yml
index be5536a..53284e1 100644
--- a/roles/openstack_cli/tasks/main.yml
+++ b/roles/openstack_cli/tasks/main.yml
@@ -42,4 +42,4 @@
     dest: /root/openrc
     owner: root
     group: root
-    mode: 0600
+    mode: "0600"
diff --git a/roles/openstack_helm_endpoints/tasks/main.yml b/roles/openstack_helm_endpoints/tasks/main.yml
index 5ede725..5e23099 100644
--- a/roles/openstack_helm_endpoints/tasks/main.yml
+++ b/roles/openstack_helm_endpoints/tasks/main.yml
@@ -14,6 +14,7 @@
 
 - name: Retrieve list of all the needed endpoints
   ansible.builtin.set_fact:
+    # noqa: yaml[line-length]
     openstack_helm_endpoints_list: |-
       {{ lookup('ansible.builtin.file', '../../../charts/' ~ openstack_helm_endpoints_chart ~ '/values.yaml', split_lines=False) | from_yaml | community.general.json_query('keys(endpoints)') | difference(_openstack_helm_endpoints_ignore) }}
   when:
diff --git a/roles/rook_ceph_cluster/tasks/main.yml b/roles/rook_ceph_cluster/tasks/main.yml
index ee3c06b..a2ba898 100644
--- a/roles/rook_ceph_cluster/tasks/main.yml
+++ b/roles/rook_ceph_cluster/tasks/main.yml
@@ -67,7 +67,7 @@
     _rook_ceph_cluster_admin_auth: "{{ _rook_ceph_cluster_admin_auth_data.stdout | from_json | first }}"
     _rook_ceph_cluster_mon_auth: "{{ _rook_ceph_cluster_mon_auth_data.stdout | from_json | first }}"
     _rook_ceph_cluster_leader_name: "{{ _rook_ceph_cluster_quorum_status.quorum_leader_name }}"
-    _rook_ceph_cluster_leader_mon: "{{ (_rook_ceph_cluster_quorum_status.monmap.mons | selectattr('name', 'equalto', _rook_ceph_cluster_leader_name) | list | first) }}"
+    _rook_ceph_cluster_leader_mon: "{{ (_rook_ceph_cluster_quorum_status.monmap.mons | selectattr('name', 'equalto', _rook_ceph_cluster_leader_name) | list | first) }}" # noqa: yaml[line-length]
     _rook_ceph_cluster_leader_addr: "{{ _rook_ceph_cluster_leader_mon.public_addr.split('/')[0] }}"
 
 - name: Deploy Helm chart
diff --git a/roles/runc/README.md b/roles/runc/README.md
new file mode 100644
index 0000000..0bb4bd9
--- /dev/null
+++ b/roles/runc/README.md
@@ -0,0 +1 @@
+# `runc`
diff --git a/roles/runc/defaults/main.yml b/roles/runc/defaults/main.yml
new file mode 100644
index 0000000..18e3926
--- /dev/null
+++ b/roles/runc/defaults/main.yml
@@ -0,0 +1,30 @@
+# Copyright (c) 2023 VEXXHOST, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+runc_bin_dir: /usr/bin
+
+runc_version: v1.1.4
+runc_checksums:
+  arm64:
+    v1.1.4: dbb71e737eaef454a406ce21fd021bd8f1b35afb7635016745992bbd7c17a223
+  amd64:
+    v1.1.4: db772be63147a4e747b4fe286c7c16a2edc4a8458bd3092ea46aaee77750e8ce
+
+runc_download_url: "https://github.com/opencontainers/runc/releases/download/{{ runc_version }}/runc.{{ download_artifact_goarch }}"
+runc_download_dest: "{{ download_artifact_work_directory }}/runc-{{ runc_version }}-{{ ansible_system | lower }}-{{ download_artifact_goarch }}"
+runc_binary_checksum: "{{ runc_checksums[download_artifact_goarch][runc_version] }}"
+
+# NOTE(mnaser): This is to accomodate for the uninstallation of the old packages
+#               that shipped with the operating system
+runc_package_name: runc
diff --git a/roles/runc/meta/main.yml b/roles/runc/meta/main.yml
new file mode 100644
index 0000000..7a61d82
--- /dev/null
+++ b/roles/runc/meta/main.yml
@@ -0,0 +1,46 @@
+# Copyright (c) 2023 VEXXHOST, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+galaxy_info:
+  author: VEXXHOST, Inc.
+  description: Ansible role for "runc"
+  license: Apache-2.0
+  min_ansible_version: 5.5.0
+  standalone: false
+  platforms:
+    - name: Debian
+      versions:
+        - buster
+        - bullseye
+    - name: Fedora
+      versions:
+        - "36"
+        - "37"
+    - name: EL
+      versions:
+        - "8"
+        - "9"
+    - name: Ubuntu
+      versions:
+        - focal
+        - jammy
+
+dependencies:
+  - role: download_artifact
+    vars:
+      download_artifact_url: "{{ runc_download_url }}"
+      download_artifact_dest: "{{ runc_download_dest }}"
+      download_artifact_checksum: "sha256:{{ runc_binary_checksum }}"
+      download_artifact_owner: root
+      download_artifact_mode: "0755"
diff --git a/roles/runc/tasks/main.yml b/roles/runc/tasks/main.yml
new file mode 100644
index 0000000..32b815d
--- /dev/null
+++ b/roles/runc/tasks/main.yml
@@ -0,0 +1,31 @@
+# Copyright (c) 2023 VEXXHOST, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+- name: Uninstall runc package
+  ansible.builtin.package:
+    name: "{{ runc_package_name }}"
+    state: absent
+
+- name: Copy runc binary from download dir
+  ansible.builtin.copy:
+    src: "{{ runc_download_dest }}"
+    dest: "{{ runc_bin_dir }}/runc"
+    mode: "0755"
+    remote_src: true
+
+- name: Remove runc orphaned binary
+  ansible.builtin.file:
+    path: /usr/bin/runc
+    state: absent
+  when: runc_bin_dir != "/usr/bin"