Add ConfigMap support for k8s. (#714)

* Minor fixes for deploying with k8s and podman.

* ConfigMap support
This commit is contained in:
2024-01-30 23:09:48 -06:00
committed by GitHub
parent 62af03077f
commit 12ec1bec43
7 changed files with 208 additions and 54 deletions
+62 -18
View File
@@ -13,6 +13,8 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http:#www.gnu.org/licenses/>.
import os
from kubernetes import client
from typing import Any, List, Set
@@ -112,9 +114,10 @@ class ClusterInfo:
services = pod["services"]
for service_name in services:
service_info = services[service_name]
port = int(service_info["ports"][0])
if opts.o.debug:
print(f"service port: {port}")
if "ports" in service_info:
port = int(service_info["ports"][0])
if opts.o.debug:
print(f"service port: {port}")
service = client.V1Service(
metadata=client.V1ObjectMeta(name=f"{self.app_name}-service"),
spec=client.V1ServiceSpec(
@@ -130,30 +133,70 @@ class ClusterInfo:
def get_pvcs(self):
result = []
volumes = named_volumes_from_pod_files(self.parsed_pod_yaml_map)
spec_volumes = self.spec.get_volumes()
named_volumes = named_volumes_from_pod_files(self.parsed_pod_yaml_map)
if opts.o.debug:
print(f"Volumes: {volumes}")
for volume_name in volumes:
print(f"Spec Volumes: {spec_volumes}")
print(f"Named Volumes: {named_volumes}")
for volume_name in spec_volumes:
if volume_name not in named_volumes:
if opts.o.debug:
print(f"{volume_name} not in pod files")
continue
spec = client.V1PersistentVolumeClaimSpec(
access_modes=["ReadWriteOnce"],
storage_class_name="manual",
resources=client.V1ResourceRequirements(
requests={"storage": "2Gi"}
),
volume_name=volume_name
volume_name=f"{self.app_name}-{volume_name}"
)
pvc = client.V1PersistentVolumeClaim(
metadata=client.V1ObjectMeta(name=volume_name,
labels={"volume-label": volume_name}),
metadata=client.V1ObjectMeta(name=f"{self.app_name}-{volume_name}",
labels={"volume-label": f"{self.app_name}-{volume_name}"}),
spec=spec,
)
result.append(pvc)
return result
def get_configmaps(self):
result = []
spec_configmaps = self.spec.get_configmaps()
named_volumes = named_volumes_from_pod_files(self.parsed_pod_yaml_map)
for cfg_map_name, cfg_map_path in spec_configmaps.items():
if cfg_map_name not in named_volumes:
if opts.o.debug:
print(f"{cfg_map_name} not in pod files")
continue
if not cfg_map_path.startswith("/"):
cfg_map_path = os.path.join(os.path.dirname(self.spec.file_path), cfg_map_path)
# Read in all the files at a single-level of the directory. This mimics the behavior
# of `kubectl create configmap foo --from-file=/path/to/dir`
data = {}
for f in os.listdir(cfg_map_path):
full_path = os.path.join(cfg_map_path, f)
if os.path.isfile(full_path):
data[f] = open(full_path, 'rt').read()
spec = client.V1ConfigMap(
metadata=client.V1ObjectMeta(name=f"{self.app_name}-{cfg_map_name}",
labels={"configmap-label": cfg_map_name}),
data=data
)
result.append(spec)
return result
def get_pvs(self):
result = []
volumes = named_volumes_from_pod_files(self.parsed_pod_yaml_map)
for volume_name in volumes:
spec_volumes = self.spec.get_volumes()
named_volumes = named_volumes_from_pod_files(self.parsed_pod_yaml_map)
for volume_name in spec_volumes:
if volume_name not in named_volumes:
if opts.o.debug:
print(f"{volume_name} not in pod files")
continue
spec = client.V1PersistentVolumeSpec(
storage_class_name="manual",
access_modes=["ReadWriteOnce"],
@@ -161,8 +204,8 @@ class ClusterInfo:
host_path=client.V1HostPathVolumeSource(path=get_node_pv_mount_path(volume_name))
)
pv = client.V1PersistentVolume(
metadata=client.V1ObjectMeta(name=volume_name,
labels={"volume-label": volume_name}),
metadata=client.V1ObjectMeta(name=f"{self.app_name}-{volume_name}",
labels={"volume-label": f"{self.app_name}-{volume_name}"}),
spec=spec,
)
result.append(pv)
@@ -178,10 +221,11 @@ class ClusterInfo:
container_name = service_name
service_info = services[service_name]
image = service_info["image"]
port = int(service_info["ports"][0])
if opts.o.debug:
print(f"image: {image}")
print(f"service port: {port}")
if "ports" in service_info:
port = int(service_info["ports"][0])
if opts.o.debug:
print(f"image: {image}")
print(f"service port: {port}")
# Re-write the image tag for remote deployment
image_to_use = remote_tag_for_image(
image, self.spec.get_image_registry()) if self.spec.get_image_registry() is not None else image
@@ -199,7 +243,7 @@ class ClusterInfo:
),
)
containers.append(container)
volumes = volumes_for_pod_files(self.parsed_pod_yaml_map)
volumes = volumes_for_pod_files(self.parsed_pod_yaml_map, self.spec)
image_pull_secrets = [client.V1LocalObjectReference(name="laconic-registry")]
template = client.V1PodTemplateSpec(
metadata=client.V1ObjectMeta(labels={"app": self.app_name}),
+56 -19
View File
@@ -1,5 +1,4 @@
# Copyright © 2023 Vulcanize
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
@@ -110,6 +109,20 @@ class K8sDeployer(Deployer):
if opts.o.debug:
print("PVCs created:")
print(f"{pvc_resp}")
# Figure out the ConfigMaps for this deployment
config_maps = self.cluster_info.get_configmaps()
for cfg_map in config_maps:
if opts.o.debug:
print(f"Sending this ConfigMap: {cfg_map}")
cfg_rsp = self.core_api.create_namespaced_config_map(
body=cfg_map,
namespace=self.k8s_namespace
)
if opts.o.debug:
print("ConfigMap created:")
print(f"{cfg_rsp}")
# Process compose files into a Deployment
deployment = self.cluster_info.get_deployment(image_pull_policy=None if self.is_kind() else "Always")
# Create the k8s objects
@@ -135,17 +148,21 @@ class K8sDeployer(Deployer):
if not self.is_kind():
ingress: client.V1Ingress = self.cluster_info.get_ingress()
if opts.o.debug:
print(f"Sending this ingress: {ingress}")
ingress_resp = self.networking_api.create_namespaced_ingress(
namespace=self.k8s_namespace,
body=ingress
)
if opts.o.debug:
print("Ingress created:")
print(f"{ingress_resp}")
if ingress:
if opts.o.debug:
print(f"Sending this ingress: {ingress}")
ingress_resp = self.networking_api.create_namespaced_ingress(
namespace=self.k8s_namespace,
body=ingress
)
if opts.o.debug:
print("Ingress created:")
print(f"{ingress_resp}")
else:
if opts.o.debug:
print("No ingress configured")
def down(self, timeout, volumes):
def down(self, timeout, volumes): # noqa: C901
self.connect_api()
# Delete the k8s objects
# Create the host-path-mounted PVs for this deployment
@@ -175,6 +192,22 @@ class K8sDeployer(Deployer):
print(f"{pvc_resp}")
except client.exceptions.ApiException as e:
_check_delete_exception(e)
# Figure out the ConfigMaps for this deployment
cfg_maps = self.cluster_info.get_configmaps()
for cfg_map in cfg_maps:
if opts.o.debug:
print(f"Deleting this ConfigMap: {cfg_map}")
try:
cfg_map_resp = self.core_api.delete_namespaced_config_map(
name=cfg_map.metadata.name, namespace=self.k8s_namespace
)
if opts.o.debug:
print("ConfigMap deleted:")
print(f"{cfg_map_resp}")
except client.exceptions.ApiException as e:
_check_delete_exception(e)
deployment = self.cluster_info.get_deployment()
if opts.o.debug:
print(f"Deleting this deployment: {deployment}")
@@ -198,14 +231,18 @@ class K8sDeployer(Deployer):
if not self.is_kind():
ingress: client.V1Ingress = self.cluster_info.get_ingress()
if opts.o.debug:
print(f"Deleting this ingress: {ingress}")
try:
self.networking_api.delete_namespaced_ingress(
name=ingress.metadata.name, namespace=self.k8s_namespace
)
except client.exceptions.ApiException as e:
_check_delete_exception(e)
if ingress:
if opts.o.debug:
print(f"Deleting this ingress: {ingress}")
try:
self.networking_api.delete_namespaced_ingress(
name=ingress.metadata.name, namespace=self.k8s_namespace
)
except client.exceptions.ApiException as e:
_check_delete_exception(e)
else:
if opts.o.debug:
print("No ingress to delete")
if self.is_kind():
# Destroy the kind cluster
+17 -8
View File
@@ -73,7 +73,7 @@ def named_volumes_from_pod_files(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():
for volume, value in volumes.items():
# Volume definition looks like:
# 'laconicd-data': None
named_volumes.append(volume)
@@ -98,22 +98,31 @@ def volume_mounts_for_service(parsed_pod_files, service):
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)
parts = mount_string.split(":")
volume_name = parts[0]
mount_path = parts[1]
mount_options = parts[2] if len(parts) == 3 else None
volume_device = client.V1VolumeMount(
mount_path=mount_path, name=volume_name, read_only="ro" == mount_options)
result.append(volume_device)
return result
def volumes_for_pod_files(parsed_pod_files):
def volumes_for_pod_files(parsed_pod_files, spec):
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)
if volume_name in spec.get_configmaps():
config_map = client.V1ConfigMapVolumeSource(name=volume_name)
volume = client.V1Volume(name=volume_name, config_map=config_map)
result.append(volume)
else:
claim = client.V1PersistentVolumeClaimVolumeSource(claim_name=volume_name)
volume = client.V1Volume(name=volume_name, persistent_volume_claim=claim)
result.append(volume)
return result
@@ -158,7 +167,7 @@ def _generate_kind_mounts(parsed_pod_files, deployment_dir):
volume_definitions.append(
f" - hostPath: {_make_absolute_host_path(volume_host_path_map[volume_name], deployment_dir)}\n"
f" containerPath: {get_node_pv_mount_path(volume_name)}"
)
)
return (
"" if len(volume_definitions) == 0 else (
" extraMounts:\n"