diff --git a/docs/source/compute_config/kubernetes.md b/docs/source/compute_config/kubernetes.md index 671ba56f..047ee1c8 100644 --- a/docs/source/compute_config/kubernetes.md +++ b/docs/source/compute_config/kubernetes.md @@ -78,6 +78,7 @@ k8s: |k8s | master_timeout | 600 |no | Master pod timeout in seconds. Default 600 seconds | |k8s | container_security_context | PSS Baseline (drop ALL caps, no privilege escalation, RuntimeDefault seccomp) | no | Mapping injected as the container `securityContext` on every Lithops pod. Set to `null` to disable. | |k8s | pod_security_context | | no | Mapping injected as the pod-level `securityContext`. Required for clusters enforcing Pod Security Standards Restricted (e.g. EGI Rancher, GKE Autopilot, OpenShift). Requires a non-root runtime image. | +|k8s | runtime_arch | auto-detected from cluster nodes; falls back to `amd64` if mixed or unknown | no | Architecture passed to `docker build --platform=linux/`. Set explicitly when targeting a specific architecture on a mixed-arch cluster. Allowed values: `amd64`, `arm64`. | ## Running on Pod Security Standards Restricted clusters diff --git a/lithops/serverless/backends/k8s/config.py b/lithops/serverless/backends/k8s/config.py index 6e8f4636..ecb71a5a 100644 --- a/lithops/serverless/backends/k8s/config.py +++ b/lithops/serverless/backends/k8s/config.py @@ -34,6 +34,11 @@ 'seccompProfile': {'type': 'RuntimeDefault'}, } +# Architectures supported by `docker build --platform=linux/`; matches the +# values emitted by `v1.NodeStatus.NodeInfo.architecture`. +SUPPORTED_RUNTIME_ARCHS = {'amd64', 'arm64'} +DEFAULT_RUNTIME_ARCH = 'amd64' + DEFAULT_GROUP = "batch" DEFAULT_VERSION = "v1" MASTER_NAME = "lithops-master" @@ -159,6 +164,12 @@ def load_config(config_data): if value is not None and not isinstance(value, dict): raise Exception(f"'{key}' under 'k8s' must be a mapping or null, got {type(value).__name__}") + arch = config_data['k8s'].get('runtime_arch') + if arch is not None and arch not in SUPPORTED_RUNTIME_ARCHS: + raise Exception( + f"'runtime_arch' under 'k8s' must be one of {sorted(SUPPORTED_RUNTIME_ARCHS)} or null, got '{arch}'" + ) + if 'runtime' in config_data['k8s']: runtime = config_data['k8s']['runtime'] registry = config_data['k8s']['docker_server'] diff --git a/lithops/serverless/backends/k8s/k8s.py b/lithops/serverless/backends/k8s/k8s.py index a880676b..7aeb38cc 100644 --- a/lithops/serverless/backends/k8s/k8s.py +++ b/lithops/serverless/backends/k8s/k8s.py @@ -135,6 +135,43 @@ def _apply_security_context(self, job_res): if container_sc: pod_spec['containers'][0]['securityContext'] = container_sc + def _detect_cluster_arch(self): + """Return the dominant node architecture, or None if mixed/unknown.""" + try: + nodes = self.core_api.list_node() + except ApiException as e: + logger.warning(f"Could not list cluster nodes for arch detection: {e}") + return None + archs = { + n.status.node_info.architecture for n in nodes.items + if n.status and n.status.node_info + } + if len(archs) == 1: + return archs.pop() + if len(archs) > 1: + logger.warning( + f"Cluster has mixed node architectures {sorted(archs)}; " + "set 'runtime_arch' in the k8s config to pick one explicitly." + ) + return None + + def _resolve_runtime_arch(self): + """Resolve the platform arch for `docker build --platform=linux/`.""" + configured = self.k8s_config.get('runtime_arch') + if configured: + return configured + detected = self._detect_cluster_arch() + if detected in config.SUPPORTED_RUNTIME_ARCHS: + logger.debug(f"Auto-detected cluster arch: {detected}") + return detected + if detected is not None: + logger.warning( + f"Auto-detected cluster arch '{detected}' is not supported by Lithops " + f"(expected one of {sorted(config.SUPPORTED_RUNTIME_ARCHS)}); " + f"falling back to '{config.DEFAULT_RUNTIME_ARCH}'." + ) + return config.DEFAULT_RUNTIME_ARCH + def build_runtime(self, docker_image_name, dockerfile, extra_args=[]): """ Builds a new runtime from a Docker file and pushes it to the registry @@ -143,11 +180,13 @@ def build_runtime(self, docker_image_name, dockerfile, extra_args=[]): docker_path = utils.get_docker_path() + arch = self._resolve_runtime_arch() + platform = f'linux/{arch}' if dockerfile: assert os.path.isfile(dockerfile), f'Cannot locate "{dockerfile}"' - cmd = f'{docker_path} build --platform=linux/amd64 -t {docker_image_name} -f {dockerfile} . ' + cmd = f'{docker_path} build --platform={platform} -t {docker_image_name} -f {dockerfile} . ' else: - cmd = f'{docker_path} build --platform=linux/amd64 -t {docker_image_name} . ' + cmd = f'{docker_path} build --platform={platform} -t {docker_image_name} . ' cmd = cmd + ' '.join(extra_args) try: