Archived
For k8s, use provisioner-managed volumes when an absolute host path is not specified. (#741)
Lint Checks / Run linter (push) Successful in 45s
Publish / Build and publish (push) Successful in 1m22s
K8s Deploy Test / Run deploy test suite on kind/k8s (push) Failing after 3m20s
Deploy Test / Run deploy test suite (push) Successful in 5m28s
Webapp Test / Run webapp test suite (push) Successful in 4m28s
Smoke Test / Run basic test suite (push) Successful in 4m58s
Lint Checks / Run linter (push) Successful in 45s
Publish / Build and publish (push) Successful in 1m22s
K8s Deploy Test / Run deploy test suite on kind/k8s (push) Failing after 3m20s
Deploy Test / Run deploy test suite (push) Successful in 5m28s
Webapp Test / Run webapp test suite (push) Successful in 4m28s
Smoke Test / Run basic test suite (push) Successful in 4m58s
In kind, when we bind-mount a host directory it is first mounted into the kind container at /mnt, then into the pod at the desired location. We accidentally picked this up for full-blown k8s, and were creating volumes at /mnt. This changes the behavior for both kind and regular k8s so that bind mounts are only allowed if a fully-qualified path is specified. If no path is specified at all, a default storageClass is assumed to be present, and the volume managed by a provisioner. Eg, for kind, the default provisioner is: https://github.com/rancher/local-path-provisioner ``` stack: test deploy-to: k8s-kind config: test-variable-1: test-value-1 network: ports: test: - '80' volumes: # this will be bind-mounted to a host-path test-data-bind: /srv/data # this will be managed by the k8s node test-data-auto: configmaps: test-config: ./configmap/test-config ``` Reviewed-on: #741 Co-authored-by: Thomas E Lackey <telackey@bozemanpass.com> Co-committed-by: Thomas E Lackey <telackey@bozemanpass.com>
This commit was merged in pull request #741.
This commit is contained in:
@@ -21,7 +21,7 @@ from typing import Any, List, Set
|
||||
from stack_orchestrator.opts import opts
|
||||
from stack_orchestrator.util import env_var_map_from_file
|
||||
from stack_orchestrator.deploy.k8s.helpers import named_volumes_from_pod_files, volume_mounts_for_service, volumes_for_pod_files
|
||||
from stack_orchestrator.deploy.k8s.helpers import get_node_pv_mount_path
|
||||
from stack_orchestrator.deploy.k8s.helpers import get_kind_pv_bind_mount_path
|
||||
from stack_orchestrator.deploy.k8s.helpers import envs_from_environment_variables_map, envs_from_compose_file, merge_envs
|
||||
from stack_orchestrator.deploy.deploy_util import parsed_pod_files_map_from_file_names, images_for_deployment
|
||||
from stack_orchestrator.deploy.deploy_types import DeployEnvVars
|
||||
@@ -171,21 +171,33 @@ class ClusterInfo:
|
||||
print(f"Spec Volumes: {spec_volumes}")
|
||||
print(f"Named Volumes: {named_volumes}")
|
||||
print(f"Resources: {resources}")
|
||||
for volume_name in spec_volumes:
|
||||
for volume_name, volume_path in spec_volumes.items():
|
||||
if volume_name not in named_volumes:
|
||||
if opts.o.debug:
|
||||
print(f"{volume_name} not in pod files")
|
||||
continue
|
||||
|
||||
labels = {
|
||||
"app": self.app_name,
|
||||
"volume-label": f"{self.app_name}-{volume_name}"
|
||||
}
|
||||
if volume_path:
|
||||
storage_class_name = "manual"
|
||||
k8s_volume_name = f"{self.app_name}-{volume_name}"
|
||||
else:
|
||||
# These will be auto-assigned.
|
||||
storage_class_name = None
|
||||
k8s_volume_name = None
|
||||
|
||||
spec = client.V1PersistentVolumeClaimSpec(
|
||||
access_modes=["ReadWriteOnce"],
|
||||
storage_class_name="manual",
|
||||
storage_class_name=storage_class_name,
|
||||
resources=to_k8s_resource_requirements(resources),
|
||||
volume_name=f"{self.app_name}-{volume_name}"
|
||||
volume_name=k8s_volume_name
|
||||
)
|
||||
pvc = client.V1PersistentVolumeClaim(
|
||||
metadata=client.V1ObjectMeta(name=f"{self.app_name}-{volume_name}",
|
||||
labels={"volume-label": f"{self.app_name}-{volume_name}"}),
|
||||
spec=spec,
|
||||
metadata=client.V1ObjectMeta(name=f"{self.app_name}-{volume_name}", labels=labels),
|
||||
spec=spec
|
||||
)
|
||||
result.append(pvc)
|
||||
return result
|
||||
@@ -226,16 +238,32 @@ class ClusterInfo:
|
||||
resources = self.spec.get_volume_resources()
|
||||
if not resources:
|
||||
resources = DEFAULT_VOLUME_RESOURCES
|
||||
for volume_name in spec_volumes:
|
||||
for volume_name, volume_path in spec_volumes.items():
|
||||
# We only need to create a volume if it is fully qualified HostPath.
|
||||
# Otherwise, we create the PVC and expect the node to allocate the volume for us.
|
||||
if not volume_path:
|
||||
if opts.o.debug:
|
||||
print(f"{volume_name} does not require an explicit PersistentVolume, since it is not a bind-mount.")
|
||||
continue
|
||||
|
||||
if volume_name not in named_volumes:
|
||||
if opts.o.debug:
|
||||
print(f"{volume_name} not in pod files")
|
||||
continue
|
||||
|
||||
if not os.path.isabs(volume_path):
|
||||
print(f"WARNING: {volume_name}:{volume_path} is not absolute, cannot bind volume.")
|
||||
continue
|
||||
|
||||
if self.spec.is_kind_deployment():
|
||||
host_path = client.V1HostPathVolumeSource(path=get_kind_pv_bind_mount_path(volume_name))
|
||||
else:
|
||||
host_path = client.V1HostPathVolumeSource(path=volume_path)
|
||||
spec = client.V1PersistentVolumeSpec(
|
||||
storage_class_name="manual",
|
||||
access_modes=["ReadWriteOnce"],
|
||||
capacity=to_k8s_resource_requirements(resources).requests,
|
||||
host_path=client.V1HostPathVolumeSource(path=get_node_pv_mount_path(volume_name))
|
||||
host_path=host_path
|
||||
)
|
||||
pv = client.V1PersistentVolume(
|
||||
metadata=client.V1ObjectMeta(name=f"{self.app_name}-{volume_name}",
|
||||
|
||||
@@ -88,6 +88,16 @@ class K8sDeployer(Deployer):
|
||||
if opts.o.debug:
|
||||
print(f"Sending this pv: {pv}")
|
||||
if not opts.o.dry_run:
|
||||
try:
|
||||
pv_resp = self.core_api.read_persistent_volume(name=pv.metadata.name)
|
||||
if pv_resp:
|
||||
if opts.o.debug:
|
||||
print("PVs already present:")
|
||||
print(f"{pv_resp}")
|
||||
continue
|
||||
except: # noqa: E722
|
||||
pass
|
||||
|
||||
pv_resp = self.core_api.create_persistent_volume(body=pv)
|
||||
if opts.o.debug:
|
||||
print("PVs created:")
|
||||
@@ -100,6 +110,17 @@ class K8sDeployer(Deployer):
|
||||
print(f"Sending this pvc: {pvc}")
|
||||
|
||||
if not opts.o.dry_run:
|
||||
try:
|
||||
pvc_resp = self.core_api.read_namespaced_persistent_volume_claim(
|
||||
name=pvc.metadata.name, namespace=self.k8s_namespace)
|
||||
if pvc_resp:
|
||||
if opts.o.debug:
|
||||
print("PVCs already present:")
|
||||
print(f"{pvc_resp}")
|
||||
continue
|
||||
except: # noqa: E722
|
||||
pass
|
||||
|
||||
pvc_resp = self.core_api.create_namespaced_persistent_volume_claim(body=pvc, namespace=self.k8s_namespace)
|
||||
if opts.o.debug:
|
||||
print("PVCs created:")
|
||||
@@ -181,33 +202,35 @@ class K8sDeployer(Deployer):
|
||||
def down(self, timeout, volumes): # noqa: C901
|
||||
self.connect_api()
|
||||
# Delete the k8s objects
|
||||
# Create the host-path-mounted PVs for this deployment
|
||||
pvs = self.cluster_info.get_pvs()
|
||||
for pv in pvs:
|
||||
if opts.o.debug:
|
||||
print(f"Deleting this pv: {pv}")
|
||||
try:
|
||||
pv_resp = self.core_api.delete_persistent_volume(name=pv.metadata.name)
|
||||
if opts.o.debug:
|
||||
print("PV deleted:")
|
||||
print(f"{pv_resp}")
|
||||
except client.exceptions.ApiException as e:
|
||||
_check_delete_exception(e)
|
||||
|
||||
# Figure out the PVCs for this deployment
|
||||
pvcs = self.cluster_info.get_pvcs()
|
||||
for pvc in pvcs:
|
||||
if opts.o.debug:
|
||||
print(f"Deleting this pvc: {pvc}")
|
||||
try:
|
||||
pvc_resp = self.core_api.delete_namespaced_persistent_volume_claim(
|
||||
name=pvc.metadata.name, namespace=self.k8s_namespace
|
||||
)
|
||||
if volumes:
|
||||
# Create the host-path-mounted PVs for this deployment
|
||||
pvs = self.cluster_info.get_pvs()
|
||||
for pv in pvs:
|
||||
if opts.o.debug:
|
||||
print("PVCs deleted:")
|
||||
print(f"{pvc_resp}")
|
||||
except client.exceptions.ApiException as e:
|
||||
_check_delete_exception(e)
|
||||
print(f"Deleting this pv: {pv}")
|
||||
try:
|
||||
pv_resp = self.core_api.delete_persistent_volume(name=pv.metadata.name)
|
||||
if opts.o.debug:
|
||||
print("PV deleted:")
|
||||
print(f"{pv_resp}")
|
||||
except client.exceptions.ApiException as e:
|
||||
_check_delete_exception(e)
|
||||
|
||||
# Figure out the PVCs for this deployment
|
||||
pvcs = self.cluster_info.get_pvcs()
|
||||
for pvc in pvcs:
|
||||
if opts.o.debug:
|
||||
print(f"Deleting this pvc: {pvc}")
|
||||
try:
|
||||
pvc_resp = self.core_api.delete_namespaced_persistent_volume_claim(
|
||||
name=pvc.metadata.name, namespace=self.k8s_namespace
|
||||
)
|
||||
if opts.o.debug:
|
||||
print("PVCs deleted:")
|
||||
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()
|
||||
|
||||
@@ -81,7 +81,7 @@ def named_volumes_from_pod_files(parsed_pod_files):
|
||||
return named_volumes
|
||||
|
||||
|
||||
def get_node_pv_mount_path(volume_name: str):
|
||||
def get_kind_pv_bind_mount_path(volume_name: str):
|
||||
return f"/mnt/{volume_name}"
|
||||
|
||||
|
||||
@@ -106,11 +106,14 @@ def volume_mounts_for_service(parsed_pod_files, service):
|
||||
mount_path = mount_split[1]
|
||||
mount_options = mount_split[2] if len(mount_split) == 3 else None
|
||||
if opts.o.debug:
|
||||
print(f"volumne_name: {volume_name}")
|
||||
print(f"volume_name: {volume_name}")
|
||||
print(f"mount path: {mount_path}")
|
||||
print(f"mount options: {mount_options}")
|
||||
volume_device = client.V1VolumeMount(
|
||||
mount_path=mount_path, name=volume_name, read_only="ro" == mount_options)
|
||||
mount_path=mount_path,
|
||||
name=volume_name,
|
||||
read_only="ro" == mount_options
|
||||
)
|
||||
result.append(volume_device)
|
||||
return result
|
||||
|
||||
@@ -133,18 +136,8 @@ def volumes_for_pod_files(parsed_pod_files, spec, app_name):
|
||||
return result
|
||||
|
||||
|
||||
def _get_host_paths_for_volumes(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():
|
||||
volume_definition = volumes[volume_name]
|
||||
if volume_definition and "driver_opts" in volume_definition:
|
||||
host_path = volume_definition["driver_opts"]["device"]
|
||||
result[volume_name] = host_path
|
||||
return result
|
||||
def _get_host_paths_for_volumes(deployment_context):
|
||||
return deployment_context.spec.get_volumes()
|
||||
|
||||
|
||||
def _make_absolute_host_path(data_mount_path: Path, deployment_dir: Path) -> Path:
|
||||
@@ -152,12 +145,12 @@ def _make_absolute_host_path(data_mount_path: Path, deployment_dir: Path) -> Pat
|
||||
return data_mount_path
|
||||
else:
|
||||
# Python Path voodo that looks pretty odd:
|
||||
return Path.cwd().joinpath(deployment_dir.joinpath("compose").joinpath(data_mount_path)).resolve()
|
||||
return Path.cwd().joinpath(deployment_dir.joinpath(data_mount_path)).resolve()
|
||||
|
||||
|
||||
def _generate_kind_mounts(parsed_pod_files, deployment_dir, deployment_context):
|
||||
volume_definitions = []
|
||||
volume_host_path_map = _get_host_paths_for_volumes(parsed_pod_files)
|
||||
volume_host_path_map = _get_host_paths_for_volumes(deployment_context)
|
||||
# Note these paths are relative to the location of the pod files (at present)
|
||||
# So we need to fix up to make them correct and absolute because kind assumes
|
||||
# relative to the cwd.
|
||||
@@ -177,14 +170,15 @@ def _generate_kind_mounts(parsed_pod_files, deployment_dir, deployment_context):
|
||||
volume_name = mount_split[0]
|
||||
mount_path = mount_split[1]
|
||||
if opts.o.debug:
|
||||
print(f"volumne_name: {volume_name}")
|
||||
print(f"volume_name: {volume_name}")
|
||||
print(f"map: {volume_host_path_map}")
|
||||
print(f"mount path: {mount_path}")
|
||||
if volume_name not in deployment_context.spec.get_configmaps():
|
||||
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)}\n"
|
||||
)
|
||||
if volume_host_path_map[volume_name]:
|
||||
volume_definitions.append(
|
||||
f" - hostPath: {_make_absolute_host_path(volume_host_path_map[volume_name], deployment_dir)}\n"
|
||||
f" containerPath: {get_kind_pv_bind_mount_path(volume_name)}\n"
|
||||
)
|
||||
return (
|
||||
"" if len(volume_definitions) == 0 else (
|
||||
" extraMounts:\n"
|
||||
|
||||
Reference in New Issue
Block a user