From 0f93d30d54f8b41d317e1df6303816fde3d0dd04 Mon Sep 17 00:00:00 2001 From: David Boreham Date: Fri, 3 Nov 2023 17:02:13 -0600 Subject: [PATCH] Basic volume support (#622) --- app/deploy/k8s/cluster_info.py | 28 +++++++++++++++++++- app/deploy/k8s/deploy_k8s.py | 25 +++++++++++++----- app/deploy/k8s/helpers.py | 47 ++++++++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+), 8 deletions(-) diff --git a/app/deploy/k8s/cluster_info.py b/app/deploy/k8s/cluster_info.py index 540f5f8c..dd6df456 100644 --- a/app/deploy/k8s/cluster_info.py +++ b/app/deploy/k8s/cluster_info.py @@ -18,6 +18,7 @@ from typing import Any, List, Set from app.opts import opts from app.util import get_yaml +from app.deploy.k8s.helpers import named_volumes_from_pod_files, volume_mounts_for_service, volumes_for_pod_files class ClusterInfo: @@ -47,6 +48,28 @@ class ClusterInfo: if opts.o.debug: print(f"image_set: {self.image_set}") + def get_pvcs(self): + result = [] + volumes = named_volumes_from_pod_files(self.parsed_pod_yaml_map) + if opts.o.debug: + print(f"Volumes: {volumes}") + for volume_name in volumes: + spec = client.V1PersistentVolumeClaimSpec( + storage_class_name="standard", + access_modes=["ReadWriteOnce"], + resources=client.V1ResourceRequirements( + requests={"storage": "2Gi"} + ) + ) + pvc = client.V1PersistentVolumeClaim( + metadata=client.V1ObjectMeta(name=volume_name, + labels={"volume-label": volume_name}), + spec=spec, + ) + result.append(pvc) + return result + + # to suit the deployment, and also annotate the container specs to point at said volumes def get_deployment(self): containers = [] for pod_name in self.parsed_pod_yaml_map: @@ -56,19 +79,22 @@ class ClusterInfo: container_name = service_name service_info = services[service_name] image = service_info["image"] + volume_mounts = volume_mounts_for_service(self.parsed_pod_yaml_map, service_name) container = client.V1Container( name=container_name, image=image, ports=[client.V1ContainerPort(container_port=80)], + volume_mounts=volume_mounts, resources=client.V1ResourceRequirements( requests={"cpu": "100m", "memory": "200Mi"}, limits={"cpu": "500m", "memory": "500Mi"}, ), ) containers.append(container) + volumes = volumes_for_pod_files(self.parsed_pod_yaml_map) template = client.V1PodTemplateSpec( metadata=client.V1ObjectMeta(labels={"app": self.app_name}), - spec=client.V1PodSpec(containers=containers), + spec=client.V1PodSpec(containers=containers, volumes=volumes), ) spec = client.V1DeploymentSpec( replicas=1, template=template, selector={ diff --git a/app/deploy/k8s/deploy_k8s.py b/app/deploy/k8s/deploy_k8s.py index bb1fcd87..25e0f485 100644 --- a/app/deploy/k8s/deploy_k8s.py +++ b/app/deploy/k8s/deploy_k8s.py @@ -26,6 +26,7 @@ class K8sDeployer(Deployer): name: str = "k8s" core_api: client.CoreV1Api apps_api: client.AppsV1Api + k8s_namespace: str = "default" kind_cluster_name: str cluster_info : ClusterInfo @@ -49,17 +50,27 @@ class K8sDeployer(Deployer): self.connect_api() # Ensure the referenced containers are copied into kind load_images_into_kind(self.kind_cluster_name, self.cluster_info.image_set) + # Figure out the PVCs for this deployment + pvcs = self.cluster_info.get_pvcs() + for pvc in pvcs: + if opts.o.debug: + print(f"Sending this: {pvc}") + pvc_resp = self.core_api.create_namespaced_persistent_volume_claim(body=pvc, namespace=self.k8s_namespace) + if opts.o.debug: + print("PVCs created:") + print(f"{pvc_resp}") # Process compose files into a Deployment deployment = self.cluster_info.get_deployment() # Create the k8s objects - resp = self.apps_api.create_namespaced_deployment( - body=deployment, namespace="default" - ) - if opts.o.debug: - print("Deployment created.\n") - print(f"{resp.metadata.namespace} {resp.metadata.name} \ - {resp.metadata.generation} {resp.spec.template.spec.containers[0].image}") + print(f"Sending this: {deployment}") + deployment_resp = self.apps_api.create_namespaced_deployment( + body=deployment, namespace=self.k8s_namespace + ) + if opts.o.debug: + print("Deployment created:") + print(f"{deployment_resp.metadata.namespace} {deployment_resp.metadata.name} \ + {deployment_resp.metadata.generation} {deployment_resp.spec.template.spec.containers[0].image}") def down(self, timeout, volumes): # Delete the k8s objects diff --git a/app/deploy/k8s/helpers.py b/app/deploy/k8s/helpers.py index 731d667d..3ff5e2b7 100644 --- a/app/deploy/k8s/helpers.py +++ b/app/deploy/k8s/helpers.py @@ -55,3 +55,50 @@ def pods_in_deployment(core_api: client.CoreV1Api, deployment_name: str): def log_stream_from_string(s: str): # Note response has to be UTF-8 encoded because the caller expects to decode it yield ("ignore", s.encode()) + + +def named_volumes_from_pod_files(parsed_pod_files): + # Parse the compose files looking for named volumes + named_volumes = [] + for pod in parsed_pod_files: + parsed_pod_file = parsed_pod_files[pod] + if "volumes" in parsed_pod_file: + volumes = parsed_pod_file["volumes"] + for volume in volumes.keys(): + # Volume definition looks like: + # 'laconicd-data': None + named_volumes.append(volume) + return named_volumes + + +def volume_mounts_for_service(parsed_pod_files, service): + result = [] + # Find the service + for pod in parsed_pod_files: + parsed_pod_file = parsed_pod_files[pod] + if "services" in parsed_pod_file: + services = parsed_pod_file["services"] + for service_name in services: + if service_name == service: + service_obj = services[service_name] + if "volumes" in service_obj: + volumes = service_obj["volumes"] + for mount_string in volumes: + # Looks like: test-data:/data + (volume_name, mount_path) = mount_string.split(":") + volume_device = client.V1VolumeMount(mount_path=mount_path, name=volume_name) + result.append(volume_device) + return result + + +def volumes_for_pod_files(parsed_pod_files): + result = [] + for pod in parsed_pod_files: + parsed_pod_file = parsed_pod_files[pod] + if "volumes" in parsed_pod_file: + volumes = parsed_pod_file["volumes"] + for volume_name in volumes.keys(): + claim = client.V1PersistentVolumeClaimVolumeSource(claim_name=volume_name) + volume = client.V1Volume(name=volume_name, persistent_volume_claim=claim) + result.append(volume) + return result