zenml-nightly 0.70.0.dev20241122__py3-none-any.whl → 0.70.0.dev20241201__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. zenml/VERSION +1 -1
  2. zenml/artifact_stores/base_artifact_store.py +2 -2
  3. zenml/artifacts/artifact_config.py +7 -1
  4. zenml/artifacts/utils.py +56 -31
  5. zenml/cli/__init__.py +18 -0
  6. zenml/cli/base.py +4 -4
  7. zenml/cli/login.py +26 -0
  8. zenml/cli/pipeline.py +80 -0
  9. zenml/cli/server.py +1 -1
  10. zenml/cli/service_connectors.py +3 -3
  11. zenml/cli/stack.py +0 -3
  12. zenml/cli/stack_components.py +0 -1
  13. zenml/cli/utils.py +0 -5
  14. zenml/client.py +8 -18
  15. zenml/config/compiler.py +12 -3
  16. zenml/config/pipeline_configurations.py +20 -0
  17. zenml/config/pipeline_run_configuration.py +1 -0
  18. zenml/config/step_configurations.py +21 -0
  19. zenml/constants.py +1 -0
  20. zenml/enums.py +1 -0
  21. zenml/image_builders/local_image_builder.py +13 -3
  22. zenml/integrations/__init__.py +1 -0
  23. zenml/integrations/aws/orchestrators/sagemaker_orchestrator.py +14 -6
  24. zenml/integrations/constants.py +1 -0
  25. zenml/integrations/feast/__init__.py +1 -1
  26. zenml/integrations/feast/feature_stores/feast_feature_store.py +13 -9
  27. zenml/integrations/kubernetes/orchestrators/kube_utils.py +54 -9
  28. zenml/integrations/kubernetes/orchestrators/kubernetes_orchestrator.py +65 -3
  29. zenml/integrations/kubernetes/orchestrators/kubernetes_orchestrator_entrypoint.py +14 -2
  30. zenml/integrations/kubernetes/orchestrators/manifest_utils.py +6 -5
  31. zenml/integrations/kubernetes/service_connectors/kubernetes_service_connector.py +2 -1
  32. zenml/integrations/kubernetes/step_operators/kubernetes_step_operator.py +3 -1
  33. zenml/integrations/modal/__init__.py +46 -0
  34. zenml/integrations/modal/flavors/__init__.py +26 -0
  35. zenml/integrations/modal/flavors/modal_step_operator_flavor.py +125 -0
  36. zenml/integrations/modal/step_operators/__init__.py +22 -0
  37. zenml/integrations/modal/step_operators/modal_step_operator.py +242 -0
  38. zenml/io/filesystem.py +2 -2
  39. zenml/io/local_filesystem.py +3 -3
  40. zenml/materializers/built_in_materializer.py +18 -1
  41. zenml/materializers/structured_string_materializer.py +8 -3
  42. zenml/model/model.py +11 -85
  43. zenml/model/utils.py +18 -16
  44. zenml/models/__init__.py +12 -1
  45. zenml/models/v2/core/artifact_version.py +6 -3
  46. zenml/models/v2/core/component.py +0 -22
  47. zenml/models/v2/core/model_version.py +6 -3
  48. zenml/models/v2/core/pipeline_run.py +19 -3
  49. zenml/models/v2/core/run_metadata.py +30 -9
  50. zenml/models/v2/core/service_connector.py +4 -0
  51. zenml/models/v2/core/step_run.py +6 -4
  52. zenml/models/v2/misc/run_metadata.py +38 -0
  53. zenml/models/v2/misc/server_models.py +23 -0
  54. zenml/orchestrators/input_utils.py +19 -6
  55. zenml/orchestrators/publish_utils.py +12 -5
  56. zenml/orchestrators/step_launcher.py +7 -3
  57. zenml/orchestrators/step_run_utils.py +26 -9
  58. zenml/orchestrators/step_runner.py +40 -3
  59. zenml/orchestrators/utils.py +24 -23
  60. zenml/pipelines/pipeline_decorator.py +4 -0
  61. zenml/pipelines/pipeline_definition.py +26 -8
  62. zenml/pipelines/run_utils.py +9 -5
  63. zenml/steps/base_step.py +11 -1
  64. zenml/steps/entrypoint_function_utils.py +7 -3
  65. zenml/steps/step_decorator.py +4 -0
  66. zenml/steps/utils.py +23 -7
  67. zenml/types.py +4 -0
  68. zenml/utils/metadata_utils.py +186 -153
  69. zenml/utils/string_utils.py +41 -16
  70. zenml/utils/visualization_utils.py +4 -1
  71. zenml/zen_server/cloud_utils.py +3 -1
  72. zenml/zen_server/deploy/helm/templates/_environment.tpl +117 -0
  73. zenml/zen_server/deploy/helm/templates/server-db-job.yaml +3 -14
  74. zenml/zen_server/deploy/helm/templates/server-deployment.yaml +16 -4
  75. zenml/zen_server/deploy/helm/templates/server-secret.yaml +2 -17
  76. zenml/zen_server/rbac/endpoint_utils.py +6 -4
  77. zenml/zen_server/rbac/models.py +3 -2
  78. zenml/zen_server/rbac/utils.py +4 -7
  79. zenml/zen_server/routers/server_endpoints.py +47 -0
  80. zenml/zen_server/routers/users_endpoints.py +35 -37
  81. zenml/zen_server/routers/workspaces_endpoints.py +44 -55
  82. zenml/zen_server/template_execution/utils.py +1 -0
  83. zenml/zen_server/zen_server_api.py +45 -6
  84. zenml/zen_stores/migrations/utils.py +40 -24
  85. zenml/zen_stores/migrations/versions/b73bc71f1106_remove_component_spec_path.py +36 -0
  86. zenml/zen_stores/migrations/versions/cc269488e5a9_separate_run_metadata.py +135 -0
  87. zenml/zen_stores/migrations/versions/ec6307720f92_simplify_model_version_links.py +7 -6
  88. zenml/zen_stores/rest_zen_store.py +38 -1
  89. zenml/zen_stores/schemas/__init__.py +5 -1
  90. zenml/zen_stores/schemas/artifact_schemas.py +12 -11
  91. zenml/zen_stores/schemas/component_schemas.py +0 -3
  92. zenml/zen_stores/schemas/model_schemas.py +13 -11
  93. zenml/zen_stores/schemas/pipeline_run_schemas.py +44 -16
  94. zenml/zen_stores/schemas/run_metadata_schemas.py +66 -31
  95. zenml/zen_stores/schemas/step_run_schemas.py +32 -12
  96. zenml/zen_stores/schemas/utils.py +47 -3
  97. zenml/zen_stores/sql_zen_store.py +130 -34
  98. {zenml_nightly-0.70.0.dev20241122.dist-info → zenml_nightly-0.70.0.dev20241201.dist-info}/METADATA +1 -1
  99. {zenml_nightly-0.70.0.dev20241122.dist-info → zenml_nightly-0.70.0.dev20241201.dist-info}/RECORD +102 -95
  100. zenml/utils/cloud_utils.py +0 -40
  101. {zenml_nightly-0.70.0.dev20241122.dist-info → zenml_nightly-0.70.0.dev20241201.dist-info}/LICENSE +0 -0
  102. {zenml_nightly-0.70.0.dev20241122.dist-info → zenml_nightly-0.70.0.dev20241201.dist-info}/WHEEL +0 -0
  103. {zenml_nightly-0.70.0.dev20241122.dist-info → zenml_nightly-0.70.0.dev20241201.dist-info}/entry_points.txt +0 -0
zenml/constants.py CHANGED
@@ -353,6 +353,7 @@ FLAVORS = "/flavors"
353
353
  GET_OR_CREATE = "/get-or-create"
354
354
  HEALTH = "/health"
355
355
  INFO = "/info"
356
+ LOAD_INFO = "/load-info"
356
357
  LOGIN = "/login"
357
358
  LOGOUT = "/logout"
358
359
  LOGS = "/logs"
zenml/enums.py CHANGED
@@ -60,6 +60,7 @@ class VisualizationType(StrEnum):
60
60
  HTML = "html"
61
61
  IMAGE = "image"
62
62
  MARKDOWN = "markdown"
63
+ JSON = "json"
63
64
 
64
65
 
65
66
  class ZenMLServiceType(StrEnum):
@@ -68,15 +68,25 @@ class LocalImageBuilder(BaseImageBuilder):
68
68
  )
69
69
 
70
70
  if not docker_utils.check_docker():
71
+ # For 3., this is not supported by the python docker library
72
+ # https://github.com/docker/docker-py/issues/3146
71
73
  raise RuntimeError(
72
- "Unable to connect to the Docker daemon. There are two "
74
+ "Unable to connect to the Docker daemon. There are three "
73
75
  "common causes for this:\n"
74
76
  "1) The Docker daemon isn't running.\n"
75
77
  "2) The Docker client isn't configured correctly. The client "
76
78
  "loads its configuration from the following file: "
77
79
  "$HOME/.docker/config.json. If your configuration file is in a "
78
- "different location, set it using the `DOCKER_CONFIG` "
79
- "environment variable."
80
+ "different location, set the `DOCKER_CONFIG` environment "
81
+ "variable to the directory that contains your `config.json` "
82
+ "file.\n"
83
+ "3) If your Docker CLI is working fine but you ran into this "
84
+ "issue, you might be using a non-default Docker context which "
85
+ "is not supported by the Docker python library. To verify "
86
+ "this, run `docker context ls` and check which context has a "
87
+ "`*` next to it. If this is not the `default` context, copy "
88
+ "the `DOCKER ENDPOINT` value of that context and set the "
89
+ "`DOCKER_HOST` environment variable to that value."
80
90
  )
81
91
 
82
92
  def build(
@@ -48,6 +48,7 @@ from zenml.integrations.lightgbm import LightGBMIntegration # noqa
48
48
 
49
49
  # from zenml.integrations.llama_index import LlamaIndexIntegration # noqa
50
50
  from zenml.integrations.mlflow import MlflowIntegration # noqa
51
+ from zenml.integrations.modal import ModalIntegration # noqa
51
52
  from zenml.integrations.neptune import NeptuneIntegration # noqa
52
53
  from zenml.integrations.neural_prophet import NeuralProphetIntegration # noqa
53
54
  from zenml.integrations.numpy import NumpyIntegration # noqa
@@ -305,8 +305,21 @@ class SagemakerOrchestrator(ContainerizedOrchestrator):
305
305
  # Retrieve Executor arguments provided in the Step settings.
306
306
  if use_training_step:
307
307
  args_for_step_executor = step_settings.estimator_args or {}
308
+ args_for_step_executor.setdefault(
309
+ "volume_size", step_settings.volume_size_in_gb
310
+ )
311
+ args_for_step_executor.setdefault(
312
+ "max_run", step_settings.max_runtime_in_seconds
313
+ )
308
314
  else:
309
315
  args_for_step_executor = step_settings.processor_args or {}
316
+ args_for_step_executor.setdefault(
317
+ "volume_size_in_gb", step_settings.volume_size_in_gb
318
+ )
319
+ args_for_step_executor.setdefault(
320
+ "max_runtime_in_seconds",
321
+ step_settings.max_runtime_in_seconds,
322
+ )
310
323
 
311
324
  # Set default values from configured orchestrator Component to
312
325
  # arguments to be used when they are not present in processor_args.
@@ -314,12 +327,7 @@ class SagemakerOrchestrator(ContainerizedOrchestrator):
314
327
  "role",
315
328
  step_settings.execution_role or self.config.execution_role,
316
329
  )
317
- args_for_step_executor.setdefault(
318
- "volume_size_in_gb", step_settings.volume_size_in_gb
319
- )
320
- args_for_step_executor.setdefault(
321
- "max_runtime_in_seconds", step_settings.max_runtime_in_seconds
322
- )
330
+
323
331
  tags = step_settings.tags
324
332
  args_for_step_executor.setdefault(
325
333
  "tags",
@@ -42,6 +42,7 @@ LANGCHAIN = "langchain"
42
42
  LIGHTGBM = "lightgbm"
43
43
  # LLAMA_INDEX = "llama_index"
44
44
  MLFLOW = "mlflow"
45
+ MODAL = "modal"
45
46
  NEPTUNE = "neptune"
46
47
  NEURAL_PROPHET = "neural_prophet"
47
48
  NUMPY = "numpy"
@@ -31,7 +31,7 @@ class FeastIntegration(Integration):
31
31
 
32
32
  NAME = FEAST
33
33
  # click is added to keep the feast click version in sync with ZenML's click
34
- REQUIREMENTS = ["feast", "click>=8.0.1,<8.1.4"]
34
+ REQUIREMENTS = ["feast>=0.12.0", "click>=8.0.1,<8.1.4"]
35
35
  REQUIREMENTS_IGNORED_ON_UNINSTALL = ["click", "pandas"]
36
36
 
37
37
  @classmethod
@@ -16,7 +16,7 @@
16
16
  from typing import Any, Dict, List, Union, cast
17
17
 
18
18
  import pandas as pd
19
- from feast import FeatureStore # type: ignore
19
+ from feast import FeatureService, FeatureStore # type: ignore
20
20
  from feast.infra.registry.base_registry import BaseRegistry # type: ignore
21
21
 
22
22
  from zenml.feature_stores.base_feature_store import BaseFeatureStore
@@ -43,14 +43,14 @@ class FeastFeatureStore(BaseFeatureStore):
43
43
  def get_historical_features(
44
44
  self,
45
45
  entity_df: Union[pd.DataFrame, str],
46
- features: List[str],
46
+ features: Union[List[str], FeatureService],
47
47
  full_feature_names: bool = False,
48
48
  ) -> pd.DataFrame:
49
49
  """Returns the historical features for training or batch scoring.
50
50
 
51
51
  Args:
52
52
  entity_df: The entity DataFrame or entity name.
53
- features: The features to retrieve.
53
+ features: The features to retrieve or a FeatureService.
54
54
  full_feature_names: Whether to return the full feature names.
55
55
 
56
56
  Raise:
@@ -70,14 +70,14 @@ class FeastFeatureStore(BaseFeatureStore):
70
70
  def get_online_features(
71
71
  self,
72
72
  entity_rows: List[Dict[str, Any]],
73
- features: List[str],
73
+ features: Union[List[str], FeatureService],
74
74
  full_feature_names: bool = False,
75
75
  ) -> Dict[str, Any]:
76
76
  """Returns the latest online feature data.
77
77
 
78
78
  Args:
79
79
  entity_rows: The entity rows to retrieve.
80
- features: The features to retrieve.
80
+ features: The features to retrieve or a FeatureService.
81
81
  full_feature_names: Whether to return the full feature names.
82
82
 
83
83
  Raise:
@@ -118,17 +118,21 @@ class FeastFeatureStore(BaseFeatureStore):
118
118
  fs = FeatureStore(repo_path=self.config.feast_repo)
119
119
  return [ds.name for ds in fs.list_entities()]
120
120
 
121
- def get_feature_services(self) -> List[str]:
122
- """Returns the feature service names.
121
+ def get_feature_services(self) -> List[FeatureService]:
122
+ """Returns the feature services.
123
123
 
124
124
  Raise:
125
125
  ConnectionError: If the online component (Redis) is not available.
126
126
 
127
127
  Returns:
128
- The feature service names.
128
+ The feature services.
129
129
  """
130
130
  fs = FeatureStore(repo_path=self.config.feast_repo)
131
- return [ds.name for ds in fs.list_feature_services()]
131
+ feature_services: List[FeatureService] = list(
132
+ fs.list_feature_services()
133
+ )
134
+
135
+ return feature_services
132
136
 
133
137
  def get_feature_views(self) -> List[str]:
134
138
  """Returns the feature view names.
@@ -42,8 +42,8 @@ from kubernetes import config as k8s_config
42
42
  from kubernetes.client.rest import ApiException
43
43
 
44
44
  from zenml.integrations.kubernetes.orchestrators.manifest_utils import (
45
- build_cluster_role_binding_manifest_for_service_account,
46
45
  build_namespace_manifest,
46
+ build_role_binding_manifest_for_service_account,
47
47
  build_service_account_manifest,
48
48
  )
49
49
  from zenml.logger import get_logger
@@ -94,18 +94,62 @@ def load_kube_config(
94
94
  k8s_config.load_kube_config(context=context)
95
95
 
96
96
 
97
- def sanitize_pod_name(pod_name: str) -> str:
97
+ def calculate_max_pod_name_length_for_namespace(namespace: str) -> int:
98
+ """Calculate the max pod length for a certain namespace.
99
+
100
+ Args:
101
+ namespace: The namespace in which the pod will be created.
102
+
103
+ Returns:
104
+ The maximum pod name length.
105
+ """
106
+ # Kubernetes allows Pod names to have 253 characters. However, when
107
+ # creating a pod they try to create a log file which is called
108
+ # <NAMESPACE>_<POD_NAME>_<UUID>, which adds additional characters and
109
+ # runs into filesystem limitations for filename lengths (255). We therefore
110
+ # subtract the length of a UUID (36), the two underscores and the
111
+ # namespace length from the max filename length.
112
+ return 255 - 38 - len(namespace)
113
+
114
+
115
+ def sanitize_pod_name(pod_name: str, namespace: str) -> str:
98
116
  """Sanitize pod names so they conform to Kubernetes pod naming convention.
99
117
 
100
118
  Args:
101
119
  pod_name: Arbitrary input pod name.
120
+ namespace: Namespace in which the Pod will be created.
102
121
 
103
122
  Returns:
104
123
  Sanitized pod name.
105
124
  """
125
+ # https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-subdomain-names
106
126
  pod_name = re.sub(r"[^a-z0-9-]", "-", pod_name.lower())
107
127
  pod_name = re.sub(r"^[-]+", "", pod_name)
108
- return re.sub(r"[-]+", "-", pod_name)
128
+ pod_name = re.sub(r"[-]+$", "", pod_name)
129
+ pod_name = re.sub(r"[-]+", "-", pod_name)
130
+
131
+ allowed_length = calculate_max_pod_name_length_for_namespace(
132
+ namespace=namespace
133
+ )
134
+ return pod_name[:allowed_length]
135
+
136
+
137
+ def sanitize_label(label: str) -> str:
138
+ """Sanitize a label for a Kubernetes resource.
139
+
140
+ Args:
141
+ label: The label to sanitize.
142
+
143
+ Returns:
144
+ The sanitized label.
145
+ """
146
+ # https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#rfc-1035-label-names
147
+ label = re.sub(r"[^a-z0-9-]", "-", label.lower())
148
+ label = re.sub(r"^[-]+", "", label)
149
+ label = re.sub(r"[-]+$", "", label)
150
+ label = re.sub(r"[-]+", "-", label)
151
+
152
+ return label[:63]
109
153
 
110
154
 
111
155
  def pod_is_not_pending(pod: k8s_client.V1Pod) -> bool:
@@ -288,7 +332,7 @@ def create_edit_service_account(
288
332
  rbac_api: k8s_client.RbacAuthorizationV1Api,
289
333
  service_account_name: str,
290
334
  namespace: str,
291
- cluster_role_binding_name: str = "zenml-edit",
335
+ role_binding_name: str = "zenml-edit",
292
336
  ) -> None:
293
337
  """Create a new Kubernetes service account with "edit" rights.
294
338
 
@@ -297,16 +341,17 @@ def create_edit_service_account(
297
341
  rbac_api: Client of Rbac Authorization V1 API of Kubernetes API.
298
342
  service_account_name: Name of the service account.
299
343
  namespace: Kubernetes namespace. Defaults to "default".
300
- cluster_role_binding_name: Name of the cluster role binding.
301
- Defaults to "zenml-edit".
344
+ role_binding_name: Name of the role binding. Defaults to "zenml-edit".
302
345
  """
303
- crb_manifest = build_cluster_role_binding_manifest_for_service_account(
304
- name=cluster_role_binding_name,
346
+ rb_manifest = build_role_binding_manifest_for_service_account(
347
+ name=role_binding_name,
305
348
  role_name="edit",
306
349
  service_account_name=service_account_name,
307
350
  namespace=namespace,
308
351
  )
309
- _if_not_exists(rbac_api.create_cluster_role_binding)(body=crb_manifest)
352
+ _if_not_exists(rbac_api.create_namespaced_role_binding)(
353
+ namespace=namespace, body=rb_manifest
354
+ )
310
355
 
311
356
  sa_manifest = build_service_account_manifest(
312
357
  name=service_account_name, namespace=namespace
@@ -59,6 +59,7 @@ from zenml.integrations.kubernetes.orchestrators.manifest_utils import (
59
59
  build_cron_job_manifest,
60
60
  build_pod_manifest,
61
61
  )
62
+ from zenml.integrations.kubernetes.pod_settings import KubernetesPodSettings
62
63
  from zenml.logger import get_logger
63
64
  from zenml.orchestrators import ContainerizedOrchestrator
64
65
  from zenml.orchestrators.utils import get_orchestrator_run_name
@@ -328,6 +329,45 @@ class KubernetesOrchestrator(ContainerizedOrchestrator):
328
329
  custom_validation_function=_validate_local_requirements,
329
330
  )
330
331
 
332
+ @classmethod
333
+ def apply_default_resource_requests(
334
+ cls,
335
+ memory: str,
336
+ cpu: Optional[str] = None,
337
+ pod_settings: Optional[KubernetesPodSettings] = None,
338
+ ) -> KubernetesPodSettings:
339
+ """Applies default resource requests to a pod settings object.
340
+
341
+ Args:
342
+ memory: The memory resource request.
343
+ cpu: The CPU resource request.
344
+ pod_settings: The pod settings to update. A new one will be created
345
+ if not provided.
346
+
347
+ Returns:
348
+ The new or updated pod settings.
349
+ """
350
+ resources = {
351
+ "requests": {"memory": memory},
352
+ }
353
+ if cpu:
354
+ resources["requests"]["cpu"] = cpu
355
+ if not pod_settings:
356
+ pod_settings = KubernetesPodSettings(resources=resources)
357
+ elif not pod_settings.resources:
358
+ # We can't update the pod settings in place (because it's a frozen
359
+ # pydantic model), so we have to create a new one.
360
+ pod_settings = KubernetesPodSettings(
361
+ **pod_settings.model_dump(exclude_unset=True),
362
+ resources=resources,
363
+ )
364
+ else:
365
+ set_requests = pod_settings.resources.get("requests", {})
366
+ resources["requests"].update(set_requests)
367
+ pod_settings.resources["requests"] = resources["requests"]
368
+
369
+ return pod_settings
370
+
331
371
  def prepare_or_run_pipeline(
332
372
  self,
333
373
  deployment: "PipelineDeploymentResponse",
@@ -355,8 +395,19 @@ class KubernetesOrchestrator(ContainerizedOrchestrator):
355
395
  )
356
396
 
357
397
  pipeline_name = deployment.pipeline_configuration.name
358
- orchestrator_run_name = get_orchestrator_run_name(pipeline_name)
359
- pod_name = kube_utils.sanitize_pod_name(orchestrator_run_name)
398
+
399
+ # We already make sure the orchestrator run name has the correct length
400
+ # to make sure we don't cut off the randomized suffix later when
401
+ # sanitizing the pod name. This avoids any pod naming collisions.
402
+ max_length = kube_utils.calculate_max_pod_name_length_for_namespace(
403
+ namespace=self.config.kubernetes_namespace
404
+ )
405
+ orchestrator_run_name = get_orchestrator_run_name(
406
+ pipeline_name, max_length=max_length
407
+ )
408
+ pod_name = kube_utils.sanitize_pod_name(
409
+ orchestrator_run_name, namespace=self.config.kubernetes_namespace
410
+ )
360
411
 
361
412
  assert stack.container_registry
362
413
 
@@ -422,6 +473,17 @@ class KubernetesOrchestrator(ContainerizedOrchestrator):
422
473
  )
423
474
  return
424
475
 
476
+ # We set some default minimum resource requests for the orchestrator pod
477
+ # here if the user has not specified any, because the orchestrator pod
478
+ # takes up some memory resources itself and, if not specified, the pod
479
+ # will be scheduled on any node regardless of available memory and risk
480
+ # negatively impacting or even crashing the node due to memory pressure.
481
+ orchestrator_pod_settings = self.apply_default_resource_requests(
482
+ memory="400Mi",
483
+ cpu="100m",
484
+ pod_settings=settings.orchestrator_pod_settings,
485
+ )
486
+
425
487
  # Create and run the orchestrator pod.
426
488
  pod_manifest = build_pod_manifest(
427
489
  run_name=orchestrator_run_name,
@@ -431,7 +493,7 @@ class KubernetesOrchestrator(ContainerizedOrchestrator):
431
493
  command=command,
432
494
  args=args,
433
495
  privileged=False,
434
- pod_settings=settings.orchestrator_pod_settings,
496
+ pod_settings=orchestrator_pod_settings,
435
497
  service_account_name=service_account_name,
436
498
  env=environment,
437
499
  mount_local_stores=self.config.is_local,
@@ -90,7 +90,9 @@ def main() -> None:
90
90
  """
91
91
  # Define Kubernetes pod name.
92
92
  pod_name = f"{orchestrator_run_id}-{step_name}"
93
- pod_name = kube_utils.sanitize_pod_name(pod_name)
93
+ pod_name = kube_utils.sanitize_pod_name(
94
+ pod_name, namespace=args.kubernetes_namespace
95
+ )
94
96
 
95
97
  image = KubernetesOrchestrator.get_image(
96
98
  deployment=deployment_config, step_name=step_name
@@ -116,6 +118,16 @@ def main() -> None:
116
118
  env = get_config_environment_vars()
117
119
  env[ENV_ZENML_KUBERNETES_RUN_ID] = orchestrator_run_id
118
120
 
121
+ # We set some default minimum memory resource requests for the step pod
122
+ # here if the user has not specified any, because the step pod takes up
123
+ # some memory resources itself and, if not specified, the pod will be
124
+ # scheduled on any node regardless of available memory and risk
125
+ # negatively impacting or even crashing the node due to memory pressure.
126
+ pod_settings = KubernetesOrchestrator.apply_default_resource_requests(
127
+ memory="400Mi",
128
+ pod_settings=settings.pod_settings,
129
+ )
130
+
119
131
  # Define Kubernetes pod manifest.
120
132
  pod_manifest = build_pod_manifest(
121
133
  pod_name=pod_name,
@@ -126,7 +138,7 @@ def main() -> None:
126
138
  args=step_args,
127
139
  env=env,
128
140
  privileged=settings.privileged,
129
- pod_settings=settings.pod_settings,
141
+ pod_settings=pod_settings,
130
142
  service_account_name=settings.step_pod_service_account_name
131
143
  or settings.service_account_name,
132
144
  mount_local_stores=mount_local_stores,
@@ -25,6 +25,7 @@ from zenml.constants import ENV_ZENML_ENABLE_REPO_INIT_WARNINGS
25
25
  from zenml.integrations.airflow.orchestrators.dag_generator import (
26
26
  ENV_ZENML_LOCAL_STORES_PATH,
27
27
  )
28
+ from zenml.integrations.kubernetes.orchestrators import kube_utils
28
29
  from zenml.integrations.kubernetes.pod_settings import KubernetesPodSettings
29
30
 
30
31
 
@@ -167,8 +168,8 @@ def build_pod_manifest(
167
168
  # Add run_name and pipeline_name to the labels
168
169
  labels.update(
169
170
  {
170
- "run": run_name,
171
- "pipeline": pipeline_name,
171
+ "run": kube_utils.sanitize_label(run_name),
172
+ "pipeline": kube_utils.sanitize_label(pipeline_name),
172
173
  }
173
174
  )
174
175
 
@@ -304,13 +305,13 @@ def build_cron_job_manifest(
304
305
  return job_manifest
305
306
 
306
307
 
307
- def build_cluster_role_binding_manifest_for_service_account(
308
+ def build_role_binding_manifest_for_service_account(
308
309
  name: str,
309
310
  role_name: str,
310
311
  service_account_name: str,
311
312
  namespace: str = "default",
312
313
  ) -> Dict[str, Any]:
313
- """Build a manifest for a cluster role binding of a service account.
314
+ """Build a manifest for a role binding of a service account.
314
315
 
315
316
  Args:
316
317
  name: Name of the cluster role binding.
@@ -323,7 +324,7 @@ def build_cluster_role_binding_manifest_for_service_account(
323
324
  """
324
325
  return {
325
326
  "apiVersion": "rbac.authorization.k8s.io/v1",
326
- "kind": "ClusterRoleBinding",
327
+ "kind": "RoleBinding",
327
328
  "metadata": {"name": name},
328
329
  "subjects": [
329
330
  {
@@ -26,6 +26,7 @@ from typing import Any, List, Optional
26
26
  from kubernetes import client as k8s_client
27
27
  from kubernetes import config as k8s_config
28
28
  from pydantic import Field
29
+ from urllib3.exceptions import HTTPError
29
30
 
30
31
  from zenml.constants import KUBERNETES_CLUSTER_RESOURCE_TYPE
31
32
  from zenml.exceptions import AuthorizationException
@@ -572,7 +573,7 @@ class KubernetesServiceConnector(ServiceConnector):
572
573
  auth_settings=["BearerToken"],
573
574
  response_type="VersionInfo",
574
575
  )
575
- except k8s_client.ApiException as err:
576
+ except (k8s_client.ApiException, HTTPError) as err:
576
577
  raise AuthorizationException(
577
578
  f"failed to verify Kubernetes cluster access: {err}"
578
579
  ) from err
@@ -197,7 +197,9 @@ class KubernetesStepOperator(BaseStepOperator):
197
197
  )
198
198
 
199
199
  pod_name = f"{info.run_name}_{info.pipeline_step_name}"
200
- pod_name = kube_utils.sanitize_pod_name(pod_name)
200
+ pod_name = kube_utils.sanitize_pod_name(
201
+ pod_name, namespace=self.config.kubernetes_namespace
202
+ )
201
203
 
202
204
  command = entrypoint_command[:3]
203
205
  args = entrypoint_command[3:]
@@ -0,0 +1,46 @@
1
+ # Copyright (c) ZenML GmbH 2024. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at:
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12
+ # or implied. See the License for the specific language governing
13
+ # permissions and limitations under the License.
14
+ """Modal integration for cloud-native step execution.
15
+
16
+ The Modal integration sub-module provides a step operator flavor that allows
17
+ executing steps on Modal's cloud infrastructure.
18
+ """
19
+ from typing import List, Type
20
+
21
+ from zenml.integrations.constants import MODAL
22
+ from zenml.integrations.integration import Integration
23
+ from zenml.stack import Flavor
24
+
25
+ MODAL_STEP_OPERATOR_FLAVOR = "modal"
26
+
27
+
28
+ class ModalIntegration(Integration):
29
+ """Definition of Modal integration for ZenML."""
30
+
31
+ NAME = MODAL
32
+ REQUIREMENTS = ["modal>=0.64.49,<1"]
33
+
34
+ @classmethod
35
+ def flavors(cls) -> List[Type[Flavor]]:
36
+ """Declare the stack component flavors for the Modal integration.
37
+
38
+ Returns:
39
+ List of new stack component flavors.
40
+ """
41
+ from zenml.integrations.modal.flavors import ModalStepOperatorFlavor
42
+
43
+ return [ModalStepOperatorFlavor]
44
+
45
+
46
+ ModalIntegration.check_installation()
@@ -0,0 +1,26 @@
1
+ # Copyright (c) ZenML GmbH 2024. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at:
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12
+ # or implied. See the License for the specific language governing
13
+ # permissions and limitations under the License.
14
+ """Modal integration flavors."""
15
+
16
+ from zenml.integrations.modal.flavors.modal_step_operator_flavor import (
17
+ ModalStepOperatorConfig,
18
+ ModalStepOperatorFlavor,
19
+ ModalStepOperatorSettings,
20
+ )
21
+
22
+ __all__ = [
23
+ "ModalStepOperatorConfig",
24
+ "ModalStepOperatorFlavor",
25
+ "ModalStepOperatorSettings",
26
+ ]