Basic volume support for k8s #622

Merged
telackey merged 1 commits from dboreham/k8s-volumes into main 2023-11-03 23:02:13 +00:00
3 changed files with 92 additions and 8 deletions

View File

@ -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={

View File

@ -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

View File

@ -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