Skip to content

Commit

Permalink
kubernetes: add support to manage k8s cluster(s)
Browse files Browse the repository at this point in the history
Initial support to create and manage k8s clusters

Signed-off-by: Douglas Schilling Landgraf <[email protected]>
  • Loading branch information
dougsland committed Nov 17, 2024
1 parent 748185c commit b3740c6
Show file tree
Hide file tree
Showing 9 changed files with 332 additions and 2 deletions.
Binary file added examples/kind_cluster/.create_example_pod.swp
Binary file not shown.
96 changes: 96 additions & 0 deletions examples/kind_cluster/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Vehicle Signal Specification (VSS) Middleware to create and manage Kubernetes cluster(s)

## Table of Contents
- [Overview](#overview)
- [Why?](#why)
- [Features](#features)
- [Requirements](#requirements)
- [Kubernetes](#kubernetes)
- [Creating a Kubernetes cluster](#creating-a-kubernetes-cluster)
- [Creating a pod](#creating-a-pod)
- [Deleting a cluster](#deleting-a-cluster)

## Overview

The `KubernetesClusterManager` class is part of VSS Middleware to provide an easy-to-use interface for creating, managing, and interacting with Kubernetes clusters using `kind` (Kubernetes in Docker). It leverages the `kubectl` command-line tool to apply, update, and delete resources, making it simple to manage Pods, Namespaces, and other Kubernetes resources.

## Why?

The purpose of creating this resource in the Middleware is to enable seamless interaction and resource management within Kubernetes clusters (cloud vendors) directly from edge devices, such as vehicles, as more edge vendors are leveraging cloud connectivity to dynamically spawn and manage cloud-based resources from the edge.

## Features

- **Cluster Management**: Create and delete Kubernetes clusters using `kind`.
- **Resource Management**: Apply, update, and delete Kubernetes resources (e.g., Pods, Namespaces) using YAML manifests.
- **Resource Existence Handling**: Automatically check if resources exist before applying or creating them.
- **Customizable**: Supports applying user-defined YAML manifests for flexible resource management.
- **Built-in Error Handling**: Provides robust error handling using exceptions (`RuntimeError`, `FileNotFoundError`) for better reliability and debugging.

## Requirements

- Python 3.6+
- `kind` (Kubernetes in Docker)
- `kubectl` or `oc` (OpenShift CLI)

## Kubernetes

### Creating a kubernetes cluster

```bash
./create_cluster
enabling experimental podman provider
Creating cluster "test-cluster" ...
✓ Ensuring node image (kindest/node:v1.31.2) 🖼
✓ Preparing nodes 📦 📦 📦
✓ Writing configuration 📜
✓ Starting control-plane 🕹️
✓ Installing CNI 🔌
✓ Installing StorageClass 💾
✓ Joining worker nodes 🚜
Set kubectl context to "kind-test-cluster"
You can now use your cluster with:

kubectl cluster-info --context kind-test-cluster

Have a question, bug, or feature request? Let us know! https://kind.sigs.k8s.io/#community 🙂
Cluster 'test-cluster' created successfully.
Listing nodes in the cluster...
NAME STATUS ROLES AGE VERSION
test-cluster-control-plane NotReady control-plane 15s v1.31.2
test-cluster-worker NotReady <none> 2s v1.31.2
test-cluster-worker2 NotReady <none> 2s v1.31.2
```

Code:
```bash
from vss_lib.cloud.k8s import KubernetesClusterManager

PATH_KIND_CONFIG="/usr/local/lib/python3.12/site-packages/vss_lib/cloud/k8s/kind-config.yaml"

if __name__ == "__main__":
manager = KubernetesClusterManager(cluster_name='test-cluster', config_file=f"{PATH_KIND_CONFIG}")
manager.create_cluster()
manager.list_nodes()
```
### Creating a pod
```bash
$ ./create_example_pod
Creating custom namespace...
namespace/custom-namespace created
Creating custom pod in custom namespace...
pod/custom-pod created

$ kubectl get pods -n custom-namespace
NAME READY STATUS RESTARTS AGE
custom-pod 1/1 Running 0 20s
```
### Deleting a cluster
```bash
$ ./delete_cluster
enabling experimental podman provider
Deleting cluster "test-cluster" ...
Deleted nodes: ["test-cluster-worker2" "test-cluster-worker" "test-cluster-control-plane"]
```
24 changes: 24 additions & 0 deletions examples/kind_cluster/create_cluster
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/usr/bin/env python
#
# 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.
#
# Script to create a Kubernetes cluster using KubernetesClusterManager.

from vss_lib.cloud.k8s import KubernetesClusterManager

PATH_KIND_CONFIG = "/usr/local/lib/python3.12/site-packages/vss_lib/cloud/k8s/kind-config.yaml"

if __name__ == "__main__":
manager = KubernetesClusterManager(cluster_name='test-cluster', config_file=f"{PATH_KIND_CONFIG}")
manager.create_cluster()
manager.list_nodes()
53 changes: 53 additions & 0 deletions examples/kind_cluster/create_example_pod
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#!/usr/bin/env python
#
# 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.
#
# Script to create a custom namespace and a pod within it using apply_yaml method in KubernetesClusterManager.
from vss_lib.cloud.k8s import KubernetesClusterManager

if __name__ == "__main__":
manager = KubernetesClusterManager(cluster_name='test-cluster')

# Define a custom YAML manifest for a Namespace
namespace_yaml = """
apiVersion: v1
kind: Namespace
metadata:
name: custom-namespace
"""

# Apply the Namespace YAML manifest with existence check
print("Creating custom namespace...")
manager.apply_yaml(namespace_yaml, resource_type='namespace', resource_name='custom-namespace')

# Define a custom YAML manifest for a Pod within the namespace
custom_pod_yaml = """
apiVersion: v1
kind: Pod
metadata:
name: custom-pod
namespace: custom-namespace
labels:
app: custom-app
spec:
containers:
- name: custom-container
image: busybox
command: ["sleep", "3600"]
ports:
- containerPort: 8080
"""

# Apply the custom Pod YAML manifest with existence check
print("Creating custom pod in custom namespace...")
manager.apply_yaml(custom_pod_yaml, resource_type='pod', resource_name='custom-pod', namespace='custom-namespace')
21 changes: 21 additions & 0 deletions examples/kind_cluster/delete_cluster
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/usr/bin/env python
#
# 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.
#
# Script to delete the entire kind Kubernetes cluster using KubernetesClusterManager.

from vss_lib.cloud.k8s import KubernetesClusterManager

if __name__ == "__main__":
manager = KubernetesClusterManager(cluster_name='test-cluster')
manager.delete_cluster()
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@
'dbus',
'vendor',
'vspec',
'containers'
'containers',
'cloud'
]


Expand Down Expand Up @@ -218,7 +219,6 @@ def run(self):
print(f"Failed to reload D-Bus or start service: {e}")
raise


def check_if_fedora():
"""
Check if the system is running Fedora by inspecting /etc/os-release or using the rpm command.
Expand Down
130 changes: 130 additions & 0 deletions src/cloud/k8s/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
"""
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.
"""

import subprocess
import os


class KubernetesClusterManager:
def __init__(self, cluster_name='kind-cluster', config_file='kind-config.yaml', kind_version='0.25.0', kubectl_version='v1.27.0'):
self.cluster_name = cluster_name
self.config_file = config_file
self.kind_version = kind_version
self.kubectl_version = kubectl_version
self._check_tools_installed()

def _check_tools_installed(self):
"""Check if kind, kubectl, or oc are installed on the system."""
self._check_command('kind', install_instructions=f"""
You can install 'kind' by running the following commands:
curl -Lo ./kind https://kind.sigs.k8s.io/dl/v{self.kind_version}/kind-$(uname)-amd64
chmod +x ./kind
sudo mv ./kind /usr/local/bin/kind
""")

# Check if either kubectl or oc exists
kubectl_or_oc_found = any(self._check_command(cmd, suppress_output=True) for cmd in ['kubectl', 'oc'])
if not kubectl_or_oc_found:
print("Error: Neither 'kubectl' nor 'oc' is installed or found in the system's PATH.")
print(f"You can install 'kubectl' by running the following commands (default version {self.kubectl_version}):")
print(f"""
curl -LO "https://dl.k8s.io/release/{self.kubectl_version}/bin/$(uname -s | tr '[:upper:]' '[:lower:]')/amd64/kubectl"
chmod +x ./kubectl
sudo mv ./kubectl /usr/local/bin/kubectl
""")
print("Alternatively, you can install 'oc' by following instructions at:")
print(" https://mirror.openshift.com/pub/openshift-v4/clients/ocp/latest/")

exit(1)

def _check_command(self, cmd, install_instructions=None, suppress_output=False):
"""Check if a command is available on the system."""
try:
# Check if the command exists in the PATH
subprocess.run(['which', cmd], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return True
except (subprocess.CalledProcessError, FileNotFoundError):
if not suppress_output:
print(f"Error: '{cmd}' is not installed or not found in the system's PATH.")
if install_instructions:
print(install_instructions)
return False

def create_cluster(self):
"""Create a Kubernetes cluster using kind and the provided configuration file."""
if not os.path.exists(self.config_file):
raise FileNotFoundError(f"Configuration file '{self.config_file}' not found.")

try:
subprocess.run(
['kind', 'create', 'cluster', '--name', self.cluster_name, '--config', self.config_file],
check=True
)
except subprocess.CalledProcessError as e:
raise RuntimeError(f"Failed to create cluster: {e}")

def list_nodes(self):
"""List all nodes in the Kubernetes cluster."""
try:
subprocess.run(['kubectl', 'get', 'nodes'], check=True)
except subprocess.CalledProcessError as e:
raise RuntimeError(f"Failed to list nodes: {e}")

def delete_cluster(self):
"""Delete the kind Kubernetes cluster."""
try:
subprocess.run(['kind', 'delete', 'cluster', '--name', self.cluster_name], check=True)
except subprocess.CalledProcessError as e:
raise RuntimeError(f"Failed to delete the cluster: {e}")

def apply_yaml(self, yaml_manifest, resource_type=None, resource_name=None, namespace=None):
"""
Apply a YAML manifest using kubectl, checking if the resource already exists.
If the resource exists, it prints a message and does not re-apply.
Args:
yaml_manifest (str): The YAML content to apply.
resource_type (str, optional): The type of the resource (e.g., pod, namespace).
resource_name (str, optional): The name of the resource.
namespace (str, optional): The namespace of the resource (if applicable).
"""
if resource_type and resource_name:
# Check if the resource already exists
cmd = ['kubectl', 'get', resource_type, resource_name]
if namespace:
cmd.extend(['-n', namespace])

try:
subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return
except subprocess.CalledProcessError:
# Resource does not exist, proceed to apply the manifest
pass

# Apply the manifest
try:
subprocess.run(
['kubectl', 'apply', '-f', '-'],
input=yaml_manifest.encode('utf-8'),
check=True
)
except subprocess.CalledProcessError as e:
raise RuntimeError(f"Failed to apply the YAML manifest: {e}")

def delete_resource(self, resource_type, resource_name):
"""Delete a resource in the Kubernetes cluster by type and name."""
try:
subprocess.run(['kubectl', 'delete', resource_type, resource_name], check=True)
except subprocess.CalledProcessError as e:
raise RuntimeError(f"Failed to delete {resource_type} '{resource_name}': {e}")
6 changes: 6 additions & 0 deletions src/cloud/k8s/kind-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
- role: worker
- role: worker
Empty file removed src/cloud/kind/__init__.py
Empty file.

0 comments on commit b3740c6

Please sign in to comment.