Skip to content

Commit 7cb6f0c

Browse files
committed
feat: Add RKE2 as alternative Kubernetes distribution for service cluster
Add support for deploying RKE2 (Rancher Kubernetes Engine 2) as an alternative to vanilla Kubernetes (kubeadm) for the service cluster. Users can choose the distribution via the new k8s_distro field in omnia_config.yml. Configuration changes: - Add k8s_distro field to service_k8s_cluster in omnia_config.yml (default: kubeadm) - Update omnia_config.json schema to validate k8s_distro enum (kubeadm/rke2) - Expand k8s_cni enum to include canal, cilium (RKE2-supported CNIs) Pipeline integration: - discovery.yml: Enable service_k8s tag when service_rke2 is in software_config.json - include_software_config.yml: Detect service_rke2 and set service_rke2_support fact - validate_software_config_json.yml: Handle service_rke2 arch and version detection - common_validation.py: Accept service_rke2 for cluster validation - image_package_collector.py: Dynamically select service_rke2.json for image builds - k8s_config/main.yml: Branch NFS setup based on k8s_distro - configure_cloud_init_group.yml: Select RKE2-specific templates when k8s_distro=rke2 New files: - 3 RKE2 cloud-init templates (first server, additional server, agent) - create_rke2_config_nfs.yml for RKE2-specific NFS directory setup - service_rke2.json package definitions for RHEL 10.0 x86_64 Key differences from kubeadm path: - RKE2 uses built-in containerd (no CRI-O) - RKE2 manages CNI lifecycle (calico/canal/cilium/flannel) - Token-based cluster join instead of kubeadm certificates - kube-vip deployed as RKE2 static pod manifest - RKE2 registries.yaml for Pulp mirror integration - Port 9345 for RKE2 supervisor API Existing kubeadm deployment is completely unaffected when k8s_distro=kubeadm (default). Signed-off-by: John Lockman <j.lockman@dell.com>
1 parent 9b65ab9 commit 7cb6f0c

16 files changed

Lines changed: 1857 additions & 15 deletions

File tree

common/library/module_utils/input_validation/schema/omnia_config.json

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,77 @@
176176
}
177177
]
178178
}
179+
},
180+
"service_rke2_k8s_cluster": {
181+
"type": "array",
182+
"description": "List of RKE2 Kubernetes cluster configurations.",
183+
"items": {
184+
"type": "object",
185+
"properties": {
186+
"cluster_name": {
187+
"type": "string",
188+
"minLength": 1,
189+
"description": "Unique name for the RKE2 service cluster."
190+
},
191+
"deployment": {
192+
"type": "boolean"
193+
},
194+
"k8s_cni": {
195+
"enum": ["calico", "canal", "cilium", "flannel"],
196+
"description": "K8s CNI plugin for the RKE2 cluster."
197+
},
198+
"pod_external_ip_range": {
199+
"description": "IP range for LoadBalancer external IPs.",
200+
"type": "string",
201+
"pattern": "^(?:(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)-(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))|(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)/[0-9]{1,2}$|^$"
202+
},
203+
"k8s_service_addresses": {
204+
"type": "string",
205+
"pattern": "^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)/[0-9]{1,2}$",
206+
"description": "CIDR for K8s service IPs."
207+
},
208+
"k8s_pod_network_cidr": {
209+
"type": "string",
210+
"pattern": "^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)/[0-9]{1,2}$",
211+
"description": "CIDR for K8s pod network."
212+
},
213+
"nfs_storage_name": {
214+
"type": "string",
215+
"description": "The NFS client server name mentioned in storage_config.yml"
216+
},
217+
"csi_powerscale_driver_secret_file_path": {
218+
"description": "Absolute file path for the secret.yaml file.",
219+
"type": "string",
220+
"pattern": "^(|/?([a-zA-Z0-9._-]+/)*[a-zA-Z0-9._-]+\\.yaml)$"
221+
},
222+
"csi_powerscale_driver_values_file_path": {
223+
"description": "File path for the values.yaml file.",
224+
"type": "string",
225+
"pattern": "^(|/?([a-zA-Z0-9._-]+/)*[a-zA-Z0-9._-]+\\.yaml)$"
226+
}
227+
},
228+
"required": [
229+
"cluster_name",
230+
"k8s_cni",
231+
"k8s_service_addresses"
232+
],
233+
"allOf": [
234+
{
235+
"if": {
236+
"properties": {
237+
"csi_powerscale_driver_secret_file_path": {
238+
"type": "string",
239+
"minLength": 1
240+
}
241+
},
242+
"required": ["csi_powerscale_driver_secret_file_path"]
243+
},
244+
"then": {
245+
"required": ["csi_powerscale_driver_values_file_path"]
246+
}
247+
}
248+
]
249+
}
179250
}
180251
},
181252
"required": [

common/library/module_utils/input_validation/validation_flows/common_validation.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1463,6 +1463,10 @@ def validate_k8s(data, admin_networks, softwares, ha_config, tag_names, errors,
14631463
cluster_set["service_k8s_cluster"] = data.get(
14641464
"service_k8s_cluster", [])
14651465

1466+
if "service_rke2" in softwares and "service_k8s" in tag_names:
1467+
cluster_set["service_rke2_k8s_cluster"] = data.get(
1468+
"service_rke2_k8s_cluster", [])
1469+
14661470
for k8s_cluster_type, k8s_clusters in cluster_set.items():
14671471
deployments_list = [k.get('deployment', False) for k in k8s_clusters]
14681472
true_count = deployments_list.count(True)

common/library/modules/image_package_collector.py

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,11 @@ def collect_packages_from_json(sw_data, fg_name=None,
109109
elif service_k8s_defined:
110110
fg_name = fg_name.replace("_aarch64", "").replace("_x86_64", "")
111111

112-
if "service_k8s" in sw_data and "cluster" in sw_data["service_k8s"]:
113-
for entry in sw_data["service_k8s"]["cluster"]:
112+
# Determine the top-level key (service_k8s or service_rke2)
113+
k8s_top_key = "service_rke2" if "service_rke2" in sw_data else "service_k8s"
114+
115+
if k8s_top_key in sw_data and "cluster" in sw_data[k8s_top_key]:
116+
for entry in sw_data[k8s_top_key]["cluster"]:
114117
if entry.get("type") == "rpm" and "package" in entry:
115118
packages.append(entry["package"])
116119

@@ -175,8 +178,8 @@ def process_functional_group(fg_name, arch, os_version, input_project_dir,
175178
sw_data, fg_name=fg_name, slurm_defined=True
176179
)
177180
)
178-
elif json_file.startswith("service_k8s_v"):
179-
# Handle versioned service_k8s_v<version>.json files
181+
elif json_file.startswith("service_k8s_v") or json_file == "service_rke2.json":
182+
# Handle versioned service_k8s_v<version>.json or service_rke2.json
180183
packages.extend(
181184
collect_packages_from_json(
182185
sw_data, fg_name=fg_name, service_k8s_defined=True
@@ -256,12 +259,20 @@ def run_module():
256259

257260
# Only validate service_k8s version if k8s functional groups are present
258261
service_k8s_json = None
259-
if needs_service_k8s:
262+
if needs_service_k8s and "service_rke2" not in allowed_softwares:
260263
if not service_k8s_version:
261264
module.fail_json(msg="service_k8s version not found in software_config.json")
262265
service_k8s_json = f"service_k8s_v{service_k8s_version}.json"
263266

264267
# pylint: disable=line-too-long
268+
# Determine which K8s package JSON to use based on software_config.json
269+
if "service_rke2" in allowed_softwares:
270+
k8s_json = "service_rke2.json"
271+
elif service_k8s_json:
272+
k8s_json = service_k8s_json
273+
else:
274+
k8s_json = None
275+
265276
# Functional group → json files mapping
266277
software_map = {
267278
"os_x86_64": ["default_packages.json", "ldms.json"],
@@ -280,12 +291,12 @@ def run_module():
280291
],
281292
}
282293

283-
# Add k8s functional groups to software_map only if service_k8s_json is available
284-
if service_k8s_json:
294+
# Add k8s functional groups to software_map if k8s json is available
295+
if k8s_json:
285296
software_map.update({
286-
"service_kube_node_x86_64": [service_k8s_json],
287-
"service_kube_control_plane_first_x86_64": [service_k8s_json],
288-
"service_kube_control_plane_x86_64": [service_k8s_json],
297+
"service_kube_node_x86_64": [k8s_json],
298+
"service_kube_control_plane_first_x86_64": [k8s_json],
299+
"service_kube_control_plane_x86_64": [k8s_json],
289300
})
290301

291302
compute_images_dict = {}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
{
2+
"service_rke2": {
3+
"cluster": [
4+
{ "package": "docker.io/library/busybox", "type": "image", "tag": "1.36" },
5+
{ "package": "firewalld", "type": "rpm", "repo_name": "x86_64_baseos" },
6+
{ "package": "python3-firewall", "type": "rpm", "repo_name": "x86_64_baseos" },
7+
{ "package": "git", "type": "rpm", "repo_name": "x86_64_appstream"},
8+
{ "package": "vim", "type": "rpm", "repo_name": "x86_64_appstream"},
9+
{ "package": "rke2-server", "type": "rpm", "repo_name": "rke2" },
10+
{ "package": "rke2-agent", "type": "rpm", "repo_name": "rke2" },
11+
{ "package": "rke2-common", "type": "rpm", "repo_name": "rke2" },
12+
{ "package": "rke2-selinux", "type": "rpm", "repo_name": "rke2" },
13+
{ "package": "docker.io/victoriametrics/victoria-metrics", "type": "image", "tag": "v1.128.0" },
14+
{ "package": "docker.io/victoriametrics/vmagent", "type": "image", "tag": "v1.128.0" },
15+
{ "package": "docker.io/victoriametrics/vmstorage", "type": "image", "tag": "v1.128.0-cluster" },
16+
{ "package": "docker.io/victoriametrics/vminsert", "type": "image", "tag": "v1.128.0-cluster" },
17+
{ "package": "docker.io/victoriametrics/vmselect", "type": "image", "tag": "v1.128.0-cluster" },
18+
{ "package": "docker.io/curlimages/curl", "type": "image", "tag": "8.17.0" },
19+
{ "package": "docker.io/rmohr/activemq", "type": "image", "tag": "5.15.9" },
20+
{ "package": "docker.io/library/mysql", "type": "image", "tag": "9.3.0" },
21+
{ "package": "docker.io/dellhpcomniaaisolution/idrac_telemetry_receiver", "type": "image", "tag": "1.0" },
22+
{ "package": "docker.io/dellhpcomniaaisolution/kafkapump", "type": "image", "tag": "1.0" },
23+
{ "package": "docker.io/dellhpcomniaaisolution/victoriapump", "type": "image", "tag": "1.0" },
24+
{ "package": "cryptography==45.0.7", "type": "pip_module" },
25+
{ "package": "omsdk==1.2.518", "type": "pip_module" },
26+
{ "package": "cffi==1.17.1", "type": "pip_module" },
27+
{ "package": "quay.io/strimzi/operator", "tag": "0.48.0", "type": "image" },
28+
{ "package": "quay.io/strimzi/kafka", "tag": "0.48.0-kafka-4.1.0", "type": "image" },
29+
{ "package": "docker.io/dellhpcomniaaisolution/ubuntu-ldms", "tag": "1.0", "type": "image" },
30+
{ "package": "strimzi-kafka-operator-helm-3-chart-0.48.0", "type": "tarball", "url": "https://github.com/strimzi/strimzi-kafka-operator/releases/download/0.48.0/strimzi-kafka-operator-helm-3-chart-0.48.0.tgz" },
31+
{ "package": "quay.io/strimzi/kafka-bridge", "tag": "0.33.1", "type": "image" },
32+
{ "package": "apptainer", "type": "rpm", "repo_name": "epel" },
33+
{ "package": "doca-ofed", "type": "iso", "url": "https://www.mellanox.com/downloads/DOCA/DOCA_v3.2.1/host/doca-host-3.2.1-044000_25.10_rhel10.x86_64.rpm"}
34+
]
35+
},
36+
"service_kube_control_plane": {
37+
"cluster": [
38+
{ "package": "ghcr.io/kube-vip/kube-vip", "tag": "v0.8.9", "type": "image" },
39+
{ "package": "quay.io/metallb/speaker", "tag": "v0.15.2", "type": "image" },
40+
{ "package": "prettytable==3.14.0", "type": "pip_module" },
41+
{ "package": "python3.12", "type": "rpm", "repo_name": "x86_64_appstream" },
42+
{ "package": "git", "type": "rpm", "repo_name": "x86_64_appstream"},
43+
{ "package": "kubernetes==33.1.0", "type": "pip_module" },
44+
{ "package": "PyMySQL==1.1.2", "type": "pip_module" }
45+
]
46+
},
47+
"service_kube_control_plane_first": {
48+
"cluster": [
49+
{ "package": "ghcr.io/kube-vip/kube-vip", "tag": "v0.8.9", "type": "image" },
50+
{ "package": "quay.io/metallb/speaker", "tag": "v0.15.2", "type": "image" },
51+
{
52+
"package": "metallb-native-v0.15.2",
53+
"type": "manifest",
54+
"url": "https://raw.githubusercontent.com/metallb/metallb/v0.15.2/config/manifests/metallb-native.yaml"
55+
},
56+
{ "package": "helm-v3.19.0-amd64", "type": "tarball", "url": "https://get.helm.sh/helm-v3.19.0-linux-amd64.tar.gz" },
57+
{ "package": "nfs-subdir-external-provisioner-4.0.18", "type": "tarball", "url": "https://github.com/kubernetes-sigs/nfs-subdir-external-provisioner/releases/download/nfs-subdir-external-provisioner-4.0.18/nfs-subdir-external-provisioner-4.0.18.tgz" },
58+
{ "package": "prettytable==3.14.0", "type": "pip_module" },
59+
{ "package": "python3.12", "type": "rpm", "repo_name": "x86_64_appstream" },
60+
{ "package": "git", "type": "rpm", "repo_name": "x86_64_appstream"},
61+
{ "package": "kubernetes==33.1.0", "type": "pip_module" },
62+
{ "package": "PyMySQL==1.1.2", "type": "pip_module" }
63+
]
64+
},
65+
66+
"service_kube_node": {
67+
"cluster": [
68+
{ "package": "registry.k8s.io/sig-storage/nfs-subdir-external-provisioner", "tag": "v4.0.2", "type": "image" },
69+
{ "package": "quay.io/metallb/speaker", "tag": "v0.15.2", "type": "image" },
70+
{ "package": "quay.io/metallb/controller", "tag": "v0.15.2", "type": "image" }
71+
]
72+
}
73+
}

input/omnia_config.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,3 +201,22 @@ service_k8s_cluster:
201201
k8s_crio_storage_size: "20G"
202202
csi_powerscale_driver_secret_file_path: ""
203203
csi_powerscale_driver_values_file_path: ""
204+
205+
# ----------------------------SERVICE RKE2 K8S--------------------------------------------------
206+
# To deploy RKE2 instead of vanilla Kubernetes (kubeadm), configure this block
207+
# and add "service_rke2" to software_config.json softwares list instead of "service_k8s".
208+
# RKE2 bundles its own container runtime (containerd) and manages the CNI lifecycle.
209+
# k8s_cni supports "calico", "canal", "cilium", and "flannel".
210+
# Only one of service_k8s_cluster or service_rke2_k8s_cluster should have deployment: true.
211+
# Please ensure corresponding cluster entry is added to high_availability_config.yml.
212+
213+
# service_rke2_k8s_cluster:
214+
# - cluster_name: service_cluster
215+
# deployment: false
216+
# k8s_cni: "calico"
217+
# pod_external_ip_range: "172.16.107.170-172.16.107.200"
218+
# k8s_service_addresses: "10.233.0.0/18"
219+
# k8s_pod_network_cidr: "10.233.64.0/18"
220+
# nfs_storage_name: "nfs_k8s"
221+
# csi_powerscale_driver_secret_file_path: ""
222+
# csi_powerscale_driver_values_file_path: ""

local_repo/roles/validation/tasks/validate_software_config_json.yml

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,24 @@
3131
- name: Check if service k8s support is true
3232
ansible.builtin.set_fact:
3333
service_k8s_support: "{{ software_config.softwares | selectattr('name', 'equalto', 'service_k8s') | list | length > 0 }}"
34+
service_rke2_support: "{{ software_config.softwares | selectattr('name', 'equalto', 'service_rke2') | list | length > 0 }}"
3435
software_names: "{{ software_config.softwares | map(attribute='name') | select('defined') | list }}"
3536
software_json_list: "{{ software_names | map('regex_replace', '$', '.json') | list }}"
3637

38+
- name: Set service_k8s_support true when rke2 is selected
39+
ansible.builtin.set_fact:
40+
service_k8s_support: true
41+
when: service_rke2_support | bool
42+
3743
- name: Get k8s archs
3844
ansible.builtin.set_fact:
3945
service_k8s_arch: "{{ (software_config.softwares | selectattr('name', 'equalto', 'service_k8s') | first).get('arch', default_archs) }}"
40-
when: service_k8s_support
46+
when: service_k8s_support and not (service_rke2_support | bool)
47+
48+
- name: Get rke2 archs
49+
ansible.builtin.set_fact:
50+
service_k8s_arch: "{{ (software_config.softwares | selectattr('name', 'equalto', 'service_rke2') | first).get('arch', default_archs) }}"
51+
when: service_rke2_support | bool
4152

4253
- name: Get k8s archs
4354
ansible.builtin.set_fact:
@@ -91,4 +102,5 @@
91102
msg: "{{ fail_msg }}"
92103
when:
93104
- service_k8s_support
105+
- not (service_rke2_support | default(false) | bool)
94106
- service_k8s_version not in supported_k8s_versions

provision/provision.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@
6767
(
6868
['service_k8s']
6969
if ( 'service_k8s' in (
70+
lookup('file', hostvars['localhost']['input_project_dir'] ~ '/software_config.json')
71+
| from_json).softwares | map(attribute='name') | list
72+
or 'service_rke2' in (
7073
lookup('file', hostvars['localhost']['input_project_dir'] ~ '/software_config.json')
7174
| from_json).softwares | map(attribute='name') | list )
7275
else []

provision/roles/configure_ochami/tasks/configure_cloud_init_group.yml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,21 @@
2626
failed_when: false
2727
when: not (hostvars['localhost']['upgrade_mode'] | default(false) | bool)
2828

29+
- name: Determine cloud-init template path based on k8s_distro # noqa: yaml[line-length]
30+
ansible.builtin.set_fact:
31+
ci_group_template_src: >-
32+
{%- if k8s_distro | default('kubeadm') == 'rke2' and
33+
'service_kube' in functional_group_name -%}
34+
cloud_init/rke2/ci-group-{{ functional_group_name }}.yaml.j2
35+
{%- else -%}
36+
cloud_init/ci-group-{{ functional_group_name }}.yaml.j2
37+
{%- endif -%}
38+
2939
- name: Load ci group template
3040
block:
3141
- name: Load ci group template - {{ functional_group_name }}
3242
ansible.builtin.template:
33-
src: "cloud_init/ci-group-{{ functional_group_name }}.yaml.j2"
43+
src: "{{ ci_group_template_src | trim }}"
3444
dest: "{{ cloud_init_dir }}/ci-group-{{ functional_group_name }}.yaml"
3545
mode: "{{ hostvars['localhost']['file_permissions_644'] }}"
3646
rescue:

0 commit comments

Comments
 (0)