zenml-nightly 0.61.0.dev20240712__py3-none-any.whl → 0.62.0.dev20240727__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.
- README.md +2 -2
- RELEASE_NOTES.md +40 -0
- zenml/VERSION +1 -1
- zenml/__init__.py +2 -0
- zenml/cli/stack.py +114 -248
- zenml/cli/stack_components.py +5 -3
- zenml/config/pipeline_spec.py +2 -2
- zenml/config/step_configurations.py +3 -3
- zenml/constants.py +3 -0
- zenml/enums.py +16 -0
- zenml/integrations/__init__.py +1 -0
- zenml/integrations/azure/__init__.py +2 -2
- zenml/integrations/constants.py +1 -0
- zenml/integrations/databricks/__init__.py +52 -0
- zenml/integrations/databricks/flavors/__init__.py +30 -0
- zenml/integrations/databricks/flavors/databricks_model_deployer_flavor.py +118 -0
- zenml/integrations/databricks/flavors/databricks_orchestrator_flavor.py +147 -0
- zenml/integrations/databricks/model_deployers/__init__.py +20 -0
- zenml/integrations/databricks/model_deployers/databricks_model_deployer.py +249 -0
- zenml/integrations/databricks/orchestrators/__init__.py +20 -0
- zenml/integrations/databricks/orchestrators/databricks_orchestrator.py +497 -0
- zenml/integrations/databricks/orchestrators/databricks_orchestrator_entrypoint_config.py +97 -0
- zenml/integrations/databricks/services/__init__.py +19 -0
- zenml/integrations/databricks/services/databricks_deployment.py +407 -0
- zenml/integrations/databricks/utils/__init__.py +14 -0
- zenml/integrations/databricks/utils/databricks_utils.py +87 -0
- zenml/integrations/gcp/orchestrators/vertex_orchestrator.py +44 -28
- zenml/integrations/great_expectations/data_validators/ge_data_validator.py +12 -8
- zenml/integrations/huggingface/materializers/huggingface_datasets_materializer.py +88 -3
- zenml/integrations/huggingface/steps/accelerate_runner.py +1 -7
- zenml/integrations/kubernetes/__init__.py +3 -2
- zenml/integrations/kubernetes/flavors/__init__.py +8 -0
- zenml/integrations/kubernetes/flavors/kubernetes_step_operator_flavor.py +166 -0
- zenml/integrations/kubernetes/orchestrators/kubernetes_orchestrator.py +1 -13
- zenml/integrations/kubernetes/orchestrators/manifest_utils.py +22 -4
- zenml/integrations/kubernetes/pod_settings.py +4 -0
- zenml/integrations/kubernetes/step_operators/__init__.py +22 -0
- zenml/integrations/kubernetes/step_operators/kubernetes_step_operator.py +235 -0
- zenml/integrations/lightgbm/__init__.py +1 -0
- zenml/integrations/mlflow/__init__.py +1 -1
- zenml/integrations/mlflow/model_registries/mlflow_model_registry.py +6 -2
- zenml/integrations/mlflow/services/mlflow_deployment.py +1 -1
- zenml/integrations/skypilot_azure/__init__.py +1 -3
- zenml/integrations/skypilot_lambda/__init__.py +1 -1
- zenml/logging/step_logging.py +34 -35
- zenml/materializers/built_in_materializer.py +1 -1
- zenml/materializers/cloudpickle_materializer.py +1 -1
- zenml/model/model.py +1 -1
- zenml/models/v2/core/code_repository.py +2 -2
- zenml/models/v2/core/component.py +29 -0
- zenml/models/v2/core/server_settings.py +0 -20
- zenml/models/v2/misc/full_stack.py +32 -0
- zenml/models/v2/misc/stack_deployment.py +5 -0
- zenml/new/pipelines/run_utils.py +1 -1
- zenml/orchestrators/__init__.py +4 -0
- zenml/orchestrators/step_launcher.py +1 -0
- zenml/orchestrators/wheeled_orchestrator.py +147 -0
- zenml/service_connectors/service_connector_utils.py +408 -0
- zenml/stack_deployments/azure_stack_deployment.py +179 -0
- zenml/stack_deployments/gcp_stack_deployment.py +13 -4
- zenml/stack_deployments/stack_deployment.py +10 -0
- zenml/stack_deployments/utils.py +4 -0
- zenml/steps/base_step.py +7 -5
- zenml/utils/function_utils.py +1 -1
- zenml/utils/pipeline_docker_image_builder.py +8 -0
- zenml/utils/source_utils.py +4 -1
- zenml/zen_server/dashboard/assets/{404-DpJaNHKF.js → 404-B_YdvmwS.js} +1 -1
- zenml/zen_server/dashboard/assets/{@reactflow-DJfzkHO1.js → @reactflow-l_1hUr1S.js} +1 -1
- zenml/zen_server/dashboard/assets/{AwarenessChannel-BYDLT2xC.js → AwarenessChannel-CFg5iX4Z.js} +1 -1
- zenml/zen_server/dashboard/assets/{CodeSnippet-BkOuRmyq.js → CodeSnippet-Dvkx_82E.js} +1 -1
- zenml/zen_server/dashboard/assets/CollapsibleCard-opiuBHHc.js +1 -0
- zenml/zen_server/dashboard/assets/{Commands-ZvWR1BRs.js → Commands-DoN1xrEq.js} +1 -1
- zenml/zen_server/dashboard/assets/{CopyButton-DVwLkafa.js → CopyButton-Cr7xYEPb.js} +1 -1
- zenml/zen_server/dashboard/assets/{CsvVizualization-C2IiqX4I.js → CsvVizualization-Ck-nZ43m.js} +3 -3
- zenml/zen_server/dashboard/assets/{Error-CqX0VqW_.js → Error-kLtljEOM.js} +1 -1
- zenml/zen_server/dashboard/assets/{ExecutionStatus-BoLUXR9t.js → ExecutionStatus-DguLLgTK.js} +1 -1
- zenml/zen_server/dashboard/assets/{Helpbox-LFydyVwh.js → Helpbox-BXUMP21n.js} +1 -1
- zenml/zen_server/dashboard/assets/{Infobox-DnENC0sh.js → Infobox-DSt0O-dm.js} +1 -1
- zenml/zen_server/dashboard/assets/{InlineAvatar-CbJtYr0t.js → InlineAvatar-xsrsIGE-.js} +1 -1
- zenml/zen_server/dashboard/assets/Pagination-C6X-mifw.js +1 -0
- zenml/zen_server/dashboard/assets/{SetPassword-BYBdbQDo.js → SetPassword-BXGTWiwj.js} +1 -1
- zenml/zen_server/dashboard/assets/{SuccessStep-Nx743hll.js → SuccessStep-DZC60t0x.js} +1 -1
- zenml/zen_server/dashboard/assets/{UpdatePasswordSchemas-DF9gSzE0.js → UpdatePasswordSchemas-DGvwFWO1.js} +1 -1
- zenml/zen_server/dashboard/assets/{chevron-right-double-BiEMg7rd.js → chevron-right-double-CZBOf6JM.js} +1 -1
- zenml/zen_server/dashboard/assets/cloud-only-C_yFCAkP.js +1 -0
- zenml/zen_server/dashboard/assets/index-BczVOqUf.js +55 -0
- zenml/zen_server/dashboard/assets/index-EpMIKgrI.css +1 -0
- zenml/zen_server/dashboard/assets/{login-mutation-BUnVASxp.js → login-mutation-CrHrndTI.js} +1 -1
- zenml/zen_server/dashboard/assets/logs-D8k8BVFf.js +1 -0
- zenml/zen_server/dashboard/assets/{not-found-B4VnX8gK.js → not-found-DYa4pC-C.js} +1 -1
- zenml/zen_server/dashboard/assets/{package-CsUhPmou.js → package-B3fWP-Dh.js} +1 -1
- zenml/zen_server/dashboard/assets/page-1h_sD1jz.js +1 -0
- zenml/zen_server/dashboard/assets/{page-Sxn82W-5.js → page-1iL8aMqs.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-DMOYZppS.js → page-2grKx_MY.js} +1 -1
- zenml/zen_server/dashboard/assets/page-5NCOHOsy.js +1 -0
- zenml/zen_server/dashboard/assets/{page-JyfeDUfu.js → page-8a4UMKXZ.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-Bx6o0ARS.js → page-B6h3iaHJ.js} +1 -1
- zenml/zen_server/dashboard/assets/page-BDns21Iz.js +1 -0
- zenml/zen_server/dashboard/assets/{page-3efNCDeb.js → page-BhgCDInH.js} +2 -2
- zenml/zen_server/dashboard/assets/{page-DKlIdAe5.js → page-Bi-wtWiO.js} +2 -2
- zenml/zen_server/dashboard/assets/{page-7zTHbhhI.js → page-BkeAAYwp.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-CRTJ0UuR.js → page-BkuQDIf-.js} +1 -1
- zenml/zen_server/dashboard/assets/page-BnaevhnB.js +1 -0
- zenml/zen_server/dashboard/assets/{page-BEs6jK71.js → page-Bq0YxkLV.js} +1 -1
- zenml/zen_server/dashboard/assets/page-Bs2F4eoD.js +2 -0
- zenml/zen_server/dashboard/assets/{page-CUZIGO-3.js → page-C6-UGEbH.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-Xu8JEjSU.js → page-CCNRIt_f.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-DvCvroOM.js → page-CHNxpz3n.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-BpSqIf4B.js → page-DgorQFqi.js} +1 -1
- zenml/zen_server/dashboard/assets/page-K8ebxVIs.js +1 -0
- zenml/zen_server/dashboard/assets/{page-Cx67M0QT.js → page-MFQyIJd3.js} +1 -1
- zenml/zen_server/dashboard/assets/page-TgCF0P_U.js +1 -0
- zenml/zen_server/dashboard/assets/page-ZnCEe-eK.js +9 -0
- zenml/zen_server/dashboard/assets/{page-Dc_7KMQE.js → page-uA5prJGY.js} +1 -1
- zenml/zen_server/dashboard/assets/persist-D7HJNBWx.js +1 -0
- zenml/zen_server/dashboard/assets/plus-C8WOyCzt.js +1 -0
- zenml/zen_server/dashboard/assets/stack-detail-query-Cficsl6d.js +1 -0
- zenml/zen_server/dashboard/assets/update-server-settings-mutation-7d8xi1tS.js +1 -0
- zenml/zen_server/dashboard/assets/{url-DuQMeqYA.js → url-D7mAQGUM.js} +1 -1
- zenml/zen_server/dashboard/index.html +4 -4
- zenml/zen_server/dashboard_legacy/asset-manifest.json +4 -4
- zenml/zen_server/dashboard_legacy/index.html +1 -1
- zenml/zen_server/dashboard_legacy/{precache-manifest.c8c57fb0d2132b1d3c2119e776b7dfb3.js → precache-manifest.12246c7548e71e2c4438e496360de80c.js} +4 -4
- zenml/zen_server/dashboard_legacy/service-worker.js +1 -1
- zenml/zen_server/dashboard_legacy/static/js/main.3b27024b.chunk.js +2 -0
- zenml/zen_server/dashboard_legacy/static/js/{main.382439a7.chunk.js.map → main.3b27024b.chunk.js.map} +1 -1
- zenml/zen_server/deploy/helm/Chart.yaml +1 -1
- zenml/zen_server/deploy/helm/README.md +2 -2
- zenml/zen_server/rbac/utils.py +10 -2
- zenml/zen_server/routers/devices_endpoints.py +4 -1
- zenml/zen_server/routers/server_endpoints.py +29 -2
- zenml/zen_server/routers/service_connectors_endpoints.py +57 -0
- zenml/zen_server/routers/steps_endpoints.py +2 -1
- zenml/zen_stores/migrations/versions/0.62.0_release.py +23 -0
- zenml/zen_stores/migrations/versions/b4fca5241eea_migrate_onboarding_state.py +167 -0
- zenml/zen_stores/rest_zen_store.py +4 -0
- zenml/zen_stores/schemas/component_schemas.py +14 -0
- zenml/zen_stores/schemas/server_settings_schemas.py +23 -11
- zenml/zen_stores/sql_zen_store.py +151 -1
- {zenml_nightly-0.61.0.dev20240712.dist-info → zenml_nightly-0.62.0.dev20240727.dist-info}/METADATA +5 -5
- {zenml_nightly-0.61.0.dev20240712.dist-info → zenml_nightly-0.62.0.dev20240727.dist-info}/RECORD +144 -121
- zenml/zen_server/dashboard/assets/Pagination-DEbVUupy.js +0 -1
- zenml/zen_server/dashboard/assets/chevron-down-D_ZlKMqH.js +0 -1
- zenml/zen_server/dashboard/assets/cloud-only-DVbIeckv.js +0 -1
- zenml/zen_server/dashboard/assets/index-C_CrU4vI.js +0 -1
- zenml/zen_server/dashboard/assets/index-DK1ynKjA.js +0 -55
- zenml/zen_server/dashboard/assets/index-inApY3KQ.css +0 -1
- zenml/zen_server/dashboard/assets/page-C43QGHTt.js +0 -9
- zenml/zen_server/dashboard/assets/page-CR0OG7ss.js +0 -1
- zenml/zen_server/dashboard/assets/page-CaopxiU1.js +0 -1
- zenml/zen_server/dashboard/assets/page-D7Z399xy.js +0 -1
- zenml/zen_server/dashboard/assets/page-D93kd7Xj.js +0 -1
- zenml/zen_server/dashboard/assets/page-DMsSn3dv.js +0 -2
- zenml/zen_server/dashboard/assets/page-Hus2pr9T.js +0 -1
- zenml/zen_server/dashboard/assets/page-TKXERe16.js +0 -1
- zenml/zen_server/dashboard/assets/plus-DOeLmm7C.js +0 -1
- zenml/zen_server/dashboard/assets/update-server-settings-mutation-CR8e3Sir.js +0 -1
- zenml/zen_server/dashboard_legacy/static/js/main.382439a7.chunk.js +0 -2
- {zenml_nightly-0.61.0.dev20240712.dist-info → zenml_nightly-0.62.0.dev20240727.dist-info}/LICENSE +0 -0
- {zenml_nightly-0.61.0.dev20240712.dist-info → zenml_nightly-0.62.0.dev20240727.dist-info}/WHEEL +0 -0
- {zenml_nightly-0.61.0.dev20240712.dist-info → zenml_nightly-0.62.0.dev20240727.dist-info}/entry_points.txt +0 -0
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (c) ZenML GmbH
|
1
|
+
# Copyright (c) ZenML GmbH 2024. All Rights Reserved.
|
2
2
|
#
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
4
|
# you may not use this file except in compliance with the License.
|
@@ -16,12 +16,21 @@
|
|
16
16
|
import os
|
17
17
|
from collections import defaultdict
|
18
18
|
from tempfile import TemporaryDirectory, mkdtemp
|
19
|
-
from typing import
|
19
|
+
from typing import (
|
20
|
+
TYPE_CHECKING,
|
21
|
+
Any,
|
22
|
+
ClassVar,
|
23
|
+
Dict,
|
24
|
+
Optional,
|
25
|
+
Tuple,
|
26
|
+
Type,
|
27
|
+
Union,
|
28
|
+
)
|
20
29
|
|
21
30
|
from datasets import Dataset, load_from_disk
|
22
31
|
from datasets.dataset_dict import DatasetDict
|
23
32
|
|
24
|
-
from zenml.enums import ArtifactType
|
33
|
+
from zenml.enums import ArtifactType, VisualizationType
|
25
34
|
from zenml.io import fileio
|
26
35
|
from zenml.materializers.base_materializer import BaseMaterializer
|
27
36
|
from zenml.materializers.pandas_materializer import PandasMaterializer
|
@@ -33,6 +42,31 @@ if TYPE_CHECKING:
|
|
33
42
|
DEFAULT_DATASET_DIR = "hf_datasets"
|
34
43
|
|
35
44
|
|
45
|
+
def extract_repo_name(checksum_str: str) -> Optional[str]:
|
46
|
+
"""Extracts the repo name from the checksum string.
|
47
|
+
|
48
|
+
An example of a checksum_str is:
|
49
|
+
"hf://datasets/nyu-mll/glue@bcdcba79d07bc864c1c254ccfcedcce55bcc9a8c/mrpc/train-00000-of-00001.parquet"
|
50
|
+
and the expected output is "nyu-mll/glue".
|
51
|
+
|
52
|
+
Args:
|
53
|
+
checksum_str: The checksum_str to extract the repo name from.
|
54
|
+
|
55
|
+
Returns:
|
56
|
+
str: The extracted repo name.
|
57
|
+
"""
|
58
|
+
dataset = None
|
59
|
+
try:
|
60
|
+
parts = checksum_str.split("/")
|
61
|
+
if len(parts) >= 4:
|
62
|
+
# Case: nyu-mll/glue
|
63
|
+
dataset = f"{parts[3]}/{parts[4].split('@')[0]}"
|
64
|
+
except Exception: # pylint: disable=broad-except
|
65
|
+
pass
|
66
|
+
|
67
|
+
return dataset
|
68
|
+
|
69
|
+
|
36
70
|
class HFDatasetMaterializer(BaseMaterializer):
|
37
71
|
"""Materializer to read data to and from huggingface datasets."""
|
38
72
|
|
@@ -103,3 +137,54 @@ class HFDatasetMaterializer(BaseMaterializer):
|
|
103
137
|
metadata[key][dataset_name] = value
|
104
138
|
return dict(metadata)
|
105
139
|
raise ValueError(f"Unsupported type {type(ds)}")
|
140
|
+
|
141
|
+
def save_visualizations(
|
142
|
+
self, ds: Union[Dataset, DatasetDict]
|
143
|
+
) -> Dict[str, VisualizationType]:
|
144
|
+
"""Save visualizations for the dataset.
|
145
|
+
|
146
|
+
Args:
|
147
|
+
ds: The Dataset or DatasetDict to visualize.
|
148
|
+
|
149
|
+
Returns:
|
150
|
+
A dictionary mapping visualization paths to their types.
|
151
|
+
|
152
|
+
Raises:
|
153
|
+
ValueError: If the given object is not a `Dataset` or `DatasetDict`.
|
154
|
+
"""
|
155
|
+
visualizations = {}
|
156
|
+
|
157
|
+
if isinstance(ds, Dataset):
|
158
|
+
datasets = {"default": ds}
|
159
|
+
elif isinstance(ds, DatasetDict):
|
160
|
+
datasets = ds
|
161
|
+
else:
|
162
|
+
raise ValueError(f"Unsupported type {type(ds)}")
|
163
|
+
|
164
|
+
for name, dataset in datasets.items():
|
165
|
+
# Generate a unique identifier for the dataset
|
166
|
+
if dataset.info.download_checksums:
|
167
|
+
dataset_id = extract_repo_name(
|
168
|
+
[x for x in dataset.info.download_checksums.keys()][0]
|
169
|
+
)
|
170
|
+
if dataset_id:
|
171
|
+
# Create the iframe HTML
|
172
|
+
html = f"""
|
173
|
+
<iframe
|
174
|
+
src="https://huggingface.co/datasets/{dataset_id}/embed/viewer"
|
175
|
+
frameborder="0"
|
176
|
+
width="100%"
|
177
|
+
height="560px"
|
178
|
+
></iframe>
|
179
|
+
"""
|
180
|
+
|
181
|
+
# Save the HTML to a file
|
182
|
+
visualization_path = os.path.join(
|
183
|
+
self.uri, f"{name}_viewer.html"
|
184
|
+
)
|
185
|
+
with fileio.open(visualization_path, "w") as f:
|
186
|
+
f.write(html)
|
187
|
+
|
188
|
+
visualizations[visualization_path] = VisualizationType.HTML
|
189
|
+
|
190
|
+
return visualizations
|
@@ -111,15 +111,9 @@ def run_with_accelerate(
|
|
111
111
|
if isinstance(v, bool):
|
112
112
|
if v:
|
113
113
|
commands.append(f"--{k}")
|
114
|
-
elif isinstance(v, str):
|
115
|
-
commands += [f"--{k}", '"{v}"']
|
116
114
|
elif type(v) in (list, tuple, set):
|
117
115
|
for each in v:
|
118
|
-
commands
|
119
|
-
if isinstance(each, str):
|
120
|
-
commands.append(f'"{each}"')
|
121
|
-
else:
|
122
|
-
commands.append(f"{each}")
|
116
|
+
commands += [f"--{k}", f"{each}"]
|
123
117
|
else:
|
124
118
|
commands += [f"--{k}", f"{v}"]
|
125
119
|
|
@@ -24,6 +24,7 @@ from zenml.integrations.integration import Integration
|
|
24
24
|
from zenml.stack import Flavor
|
25
25
|
|
26
26
|
KUBERNETES_ORCHESTRATOR_FLAVOR = "kubernetes"
|
27
|
+
KUBERNETES_STEP_OPERATOR_FLAVOR = "kubernetes"
|
27
28
|
|
28
29
|
|
29
30
|
class KubernetesIntegration(Integration):
|
@@ -42,10 +43,10 @@ class KubernetesIntegration(Integration):
|
|
42
43
|
List of new stack component flavors.
|
43
44
|
"""
|
44
45
|
from zenml.integrations.kubernetes.flavors import (
|
45
|
-
KubernetesOrchestratorFlavor,
|
46
|
+
KubernetesOrchestratorFlavor, KubernetesStepOperatorFlavor
|
46
47
|
)
|
47
48
|
|
48
|
-
return [KubernetesOrchestratorFlavor]
|
49
|
+
return [KubernetesOrchestratorFlavor, KubernetesStepOperatorFlavor]
|
49
50
|
|
50
51
|
|
51
52
|
KubernetesIntegration.check_installation()
|
@@ -18,9 +18,17 @@ from zenml.integrations.kubernetes.flavors.kubernetes_orchestrator_flavor import
|
|
18
18
|
KubernetesOrchestratorFlavor,
|
19
19
|
KubernetesOrchestratorSettings,
|
20
20
|
)
|
21
|
+
from zenml.integrations.kubernetes.flavors.kubernetes_step_operator_flavor import (
|
22
|
+
KubernetesStepOperatorConfig,
|
23
|
+
KubernetesStepOperatorFlavor,
|
24
|
+
KubernetesStepOperatorSettings,
|
25
|
+
)
|
21
26
|
|
22
27
|
__all__ = [
|
23
28
|
"KubernetesOrchestratorFlavor",
|
24
29
|
"KubernetesOrchestratorConfig",
|
25
30
|
"KubernetesOrchestratorSettings",
|
31
|
+
"KubernetesStepOperatorConfig",
|
32
|
+
"KubernetesStepOperatorFlavor",
|
33
|
+
"KubernetesStepOperatorSettings",
|
26
34
|
]
|
@@ -0,0 +1,166 @@
|
|
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
|
+
"""Kubernetes step operator flavor."""
|
15
|
+
|
16
|
+
from typing import TYPE_CHECKING, Optional, Type
|
17
|
+
|
18
|
+
from zenml.config.base_settings import BaseSettings
|
19
|
+
from zenml.constants import KUBERNETES_CLUSTER_RESOURCE_TYPE
|
20
|
+
from zenml.integrations.kubernetes import KUBERNETES_STEP_OPERATOR_FLAVOR
|
21
|
+
from zenml.integrations.kubernetes.pod_settings import KubernetesPodSettings
|
22
|
+
from zenml.models import ServiceConnectorRequirements
|
23
|
+
from zenml.step_operators import BaseStepOperatorConfig, BaseStepOperatorFlavor
|
24
|
+
|
25
|
+
if TYPE_CHECKING:
|
26
|
+
from zenml.integrations.kubernetes.step_operators import (
|
27
|
+
KubernetesStepOperator,
|
28
|
+
)
|
29
|
+
|
30
|
+
|
31
|
+
class KubernetesStepOperatorSettings(BaseSettings):
|
32
|
+
"""Settings for the Kubernetes step operator.
|
33
|
+
|
34
|
+
Attributes:
|
35
|
+
pod_settings: Pod settings to apply to pods executing the steps.
|
36
|
+
service_account_name: Name of the service account to use for the pod.
|
37
|
+
privileged: If the container should be run in privileged mode.
|
38
|
+
"""
|
39
|
+
|
40
|
+
pod_settings: Optional[KubernetesPodSettings] = None
|
41
|
+
service_account_name: Optional[str] = None
|
42
|
+
privileged: bool = False
|
43
|
+
|
44
|
+
|
45
|
+
class KubernetesStepOperatorConfig(
|
46
|
+
BaseStepOperatorConfig, KubernetesStepOperatorSettings
|
47
|
+
):
|
48
|
+
"""Configuration for the Kubernetes step operator.
|
49
|
+
|
50
|
+
Attributes:
|
51
|
+
kubernetes_namespace: Name of the Kubernetes namespace to be used.
|
52
|
+
incluster: If `True`, the step operator will run the pipeline inside the
|
53
|
+
same cluster in which the orchestrator is running. For this to work,
|
54
|
+
the pod running the orchestrator needs permissions to create new
|
55
|
+
pods. If set, the `kubernetes_context` config option is ignored. If
|
56
|
+
the stack component is linked to a Kubernetes service connector,
|
57
|
+
this field is ignored.
|
58
|
+
kubernetes_context: Name of a Kubernetes context to run pipelines in.
|
59
|
+
If the stack component is linked to a Kubernetes service connector,
|
60
|
+
this field is ignored. Otherwise, it is mandatory.
|
61
|
+
"""
|
62
|
+
|
63
|
+
kubernetes_namespace: str = "zenml"
|
64
|
+
incluster: bool = False
|
65
|
+
kubernetes_context: Optional[str] = None
|
66
|
+
|
67
|
+
@property
|
68
|
+
def is_remote(self) -> bool:
|
69
|
+
"""Checks if this stack component is running remotely.
|
70
|
+
|
71
|
+
This designation is used to determine if the stack component can be
|
72
|
+
used with a local ZenML database or if it requires a remote ZenML
|
73
|
+
server.
|
74
|
+
|
75
|
+
Returns:
|
76
|
+
True if this config is for a remote component, False otherwise.
|
77
|
+
"""
|
78
|
+
return True
|
79
|
+
|
80
|
+
@property
|
81
|
+
def is_local(self) -> bool:
|
82
|
+
"""Checks if this stack component is running locally.
|
83
|
+
|
84
|
+
Returns:
|
85
|
+
True if this config is for a local component, False otherwise.
|
86
|
+
"""
|
87
|
+
return False
|
88
|
+
|
89
|
+
|
90
|
+
class KubernetesStepOperatorFlavor(BaseStepOperatorFlavor):
|
91
|
+
"""Kubernetes step operator flavor."""
|
92
|
+
|
93
|
+
@property
|
94
|
+
def name(self) -> str:
|
95
|
+
"""Name of the flavor.
|
96
|
+
|
97
|
+
Returns:
|
98
|
+
The name of the flavor.
|
99
|
+
"""
|
100
|
+
return KUBERNETES_STEP_OPERATOR_FLAVOR
|
101
|
+
|
102
|
+
@property
|
103
|
+
def service_connector_requirements(
|
104
|
+
self,
|
105
|
+
) -> Optional[ServiceConnectorRequirements]:
|
106
|
+
"""Service connector resource requirements for service connectors.
|
107
|
+
|
108
|
+
Specifies resource requirements that are used to filter the available
|
109
|
+
service connector types that are compatible with this flavor.
|
110
|
+
|
111
|
+
Returns:
|
112
|
+
Requirements for compatible service connectors, if a service
|
113
|
+
connector is required for this flavor.
|
114
|
+
"""
|
115
|
+
return ServiceConnectorRequirements(
|
116
|
+
resource_type=KUBERNETES_CLUSTER_RESOURCE_TYPE,
|
117
|
+
)
|
118
|
+
|
119
|
+
@property
|
120
|
+
def docs_url(self) -> Optional[str]:
|
121
|
+
"""A url to point at docs explaining this flavor.
|
122
|
+
|
123
|
+
Returns:
|
124
|
+
A flavor docs url.
|
125
|
+
"""
|
126
|
+
return self.generate_default_docs_url()
|
127
|
+
|
128
|
+
@property
|
129
|
+
def sdk_docs_url(self) -> Optional[str]:
|
130
|
+
"""A url to point at SDK docs explaining this flavor.
|
131
|
+
|
132
|
+
Returns:
|
133
|
+
A flavor SDK docs url.
|
134
|
+
"""
|
135
|
+
return self.generate_default_sdk_docs_url()
|
136
|
+
|
137
|
+
@property
|
138
|
+
def logo_url(self) -> str:
|
139
|
+
"""A url to represent the flavor in the dashboard.
|
140
|
+
|
141
|
+
Returns:
|
142
|
+
The flavor logo.
|
143
|
+
"""
|
144
|
+
return "https://public-flavor-logos.s3.eu-central-1.amazonaws.com/step_operator/kubernetes.png"
|
145
|
+
|
146
|
+
@property
|
147
|
+
def config_class(self) -> Type[KubernetesStepOperatorConfig]:
|
148
|
+
"""Returns `KubernetesStepOperatorConfig` config class.
|
149
|
+
|
150
|
+
Returns:
|
151
|
+
The config class.
|
152
|
+
"""
|
153
|
+
return KubernetesStepOperatorConfig
|
154
|
+
|
155
|
+
@property
|
156
|
+
def implementation_class(self) -> Type["KubernetesStepOperator"]:
|
157
|
+
"""Implementation class for this flavor.
|
158
|
+
|
159
|
+
Returns:
|
160
|
+
The implementation class.
|
161
|
+
"""
|
162
|
+
from zenml.integrations.kubernetes.step_operators import (
|
163
|
+
KubernetesStepOperator,
|
164
|
+
)
|
165
|
+
|
166
|
+
return KubernetesStepOperator
|
@@ -38,7 +38,6 @@ from kubernetes import config as k8s_config
|
|
38
38
|
|
39
39
|
from zenml.config.base_settings import BaseSettings
|
40
40
|
from zenml.enums import StackComponentType
|
41
|
-
from zenml.environment import Environment
|
42
41
|
from zenml.integrations.kubernetes.flavors.kubernetes_orchestrator_flavor import (
|
43
42
|
KubernetesOrchestratorConfig,
|
44
43
|
KubernetesOrchestratorSettings,
|
@@ -335,19 +334,8 @@ class KubernetesOrchestrator(ContainerizedOrchestrator):
|
|
335
334
|
environment.
|
336
335
|
|
337
336
|
Raises:
|
338
|
-
RuntimeError: If
|
337
|
+
RuntimeError: If the Kubernetes orchestrator is not configured.
|
339
338
|
"""
|
340
|
-
# First check whether the code is running in a notebook.
|
341
|
-
if Environment.in_notebook():
|
342
|
-
raise RuntimeError(
|
343
|
-
"The Kubernetes orchestrator cannot run pipelines in a notebook "
|
344
|
-
"environment. The reason is that it is non-trivial to create "
|
345
|
-
"a Docker image of a notebook. Please consider refactoring "
|
346
|
-
"your notebook cells into separate scripts in a Python module "
|
347
|
-
"and run the code outside of a notebook when using this "
|
348
|
-
"orchestrator."
|
349
|
-
)
|
350
|
-
|
351
339
|
for step_name, step in deployment.step_configurations.items():
|
352
340
|
if self.requires_resources_in_orchestration_environment(step):
|
353
341
|
logger.warning(
|
@@ -139,24 +139,42 @@ def build_pod_manifest(
|
|
139
139
|
],
|
140
140
|
security_context=security_context,
|
141
141
|
)
|
142
|
+
image_pull_secrets = []
|
143
|
+
if pod_settings:
|
144
|
+
image_pull_secrets = [
|
145
|
+
k8s_client.V1LocalObjectReference(name=name)
|
146
|
+
for name in pod_settings.image_pull_secrets
|
147
|
+
]
|
142
148
|
|
143
149
|
pod_spec = k8s_client.V1PodSpec(
|
144
150
|
containers=[container_spec],
|
145
151
|
restart_policy="Never",
|
152
|
+
image_pull_secrets=image_pull_secrets,
|
146
153
|
)
|
147
154
|
|
148
155
|
if service_account_name is not None:
|
149
156
|
pod_spec.service_account_name = service_account_name
|
150
157
|
|
158
|
+
labels = {}
|
159
|
+
|
151
160
|
if pod_settings:
|
152
161
|
add_pod_settings(pod_spec, pod_settings)
|
153
162
|
|
154
|
-
|
155
|
-
|
156
|
-
|
163
|
+
# Add pod_settings.labels to the labels
|
164
|
+
if pod_settings.labels:
|
165
|
+
labels.update(pod_settings.labels)
|
166
|
+
|
167
|
+
# Add run_name and pipeline_name to the labels
|
168
|
+
labels.update(
|
169
|
+
{
|
157
170
|
"run": run_name,
|
158
171
|
"pipeline": pipeline_name,
|
159
|
-
}
|
172
|
+
}
|
173
|
+
)
|
174
|
+
|
175
|
+
pod_metadata = k8s_client.V1ObjectMeta(
|
176
|
+
name=pod_name,
|
177
|
+
labels=labels,
|
160
178
|
)
|
161
179
|
|
162
180
|
if pod_settings and pod_settings.annotations:
|
@@ -33,6 +33,8 @@ class KubernetesPodSettings(BaseSettings):
|
|
33
33
|
volumes: Volumes to mount in the pod.
|
34
34
|
volume_mounts: Volume mounts to apply to the pod containers.
|
35
35
|
host_ipc: Whether to enable host IPC for the pod.
|
36
|
+
image_pull_secrets: Image pull secrets to use for the pod.
|
37
|
+
labels: Labels to apply to the pod.
|
36
38
|
"""
|
37
39
|
|
38
40
|
node_selectors: Dict[str, str] = {}
|
@@ -43,6 +45,8 @@ class KubernetesPodSettings(BaseSettings):
|
|
43
45
|
volumes: List[Dict[str, Any]] = []
|
44
46
|
volume_mounts: List[Dict[str, Any]] = []
|
45
47
|
host_ipc: bool = False
|
48
|
+
image_pull_secrets: List[str] = []
|
49
|
+
labels: Dict[str, str] = {}
|
46
50
|
|
47
51
|
@field_validator("volumes", mode="before")
|
48
52
|
@classmethod
|
@@ -0,0 +1,22 @@
|
|
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
|
+
"""Kubernetes step operator."""
|
15
|
+
|
16
|
+
from zenml.integrations.kubernetes.step_operators.kubernetes_step_operator import (
|
17
|
+
KubernetesStepOperator,
|
18
|
+
)
|
19
|
+
|
20
|
+
__all__ = [
|
21
|
+
"KubernetesStepOperator",
|
22
|
+
]
|
@@ -0,0 +1,235 @@
|
|
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
|
+
"""Kubernetes step operator implementation."""
|
15
|
+
|
16
|
+
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Type, cast
|
17
|
+
|
18
|
+
from kubernetes import client as k8s_client
|
19
|
+
|
20
|
+
from zenml.config.base_settings import BaseSettings
|
21
|
+
from zenml.config.build_configuration import BuildConfiguration
|
22
|
+
from zenml.enums import StackComponentType
|
23
|
+
from zenml.integrations.kubernetes.flavors import (
|
24
|
+
KubernetesStepOperatorConfig,
|
25
|
+
KubernetesStepOperatorSettings,
|
26
|
+
)
|
27
|
+
from zenml.integrations.kubernetes.orchestrators import kube_utils
|
28
|
+
from zenml.integrations.kubernetes.orchestrators.manifest_utils import (
|
29
|
+
build_pod_manifest,
|
30
|
+
)
|
31
|
+
from zenml.logger import get_logger
|
32
|
+
from zenml.stack import Stack, StackValidator
|
33
|
+
from zenml.step_operators import BaseStepOperator
|
34
|
+
|
35
|
+
if TYPE_CHECKING:
|
36
|
+
from zenml.config.base_settings import BaseSettings
|
37
|
+
from zenml.config.step_run_info import StepRunInfo
|
38
|
+
from zenml.models import PipelineDeploymentBase
|
39
|
+
|
40
|
+
logger = get_logger(__name__)
|
41
|
+
|
42
|
+
KUBERNETES_STEP_OPERATOR_DOCKER_IMAGE_KEY = "kubernetes_step_operator"
|
43
|
+
|
44
|
+
|
45
|
+
class KubernetesStepOperator(BaseStepOperator):
|
46
|
+
"""Step operator to run on Kubernetes."""
|
47
|
+
|
48
|
+
_k8s_client: Optional[k8s_client.ApiClient] = None
|
49
|
+
|
50
|
+
@property
|
51
|
+
def config(self) -> KubernetesStepOperatorConfig:
|
52
|
+
"""Returns the `KubernetesStepOperatorConfig` config.
|
53
|
+
|
54
|
+
Returns:
|
55
|
+
The configuration.
|
56
|
+
"""
|
57
|
+
return cast(KubernetesStepOperatorConfig, self._config)
|
58
|
+
|
59
|
+
@property
|
60
|
+
def settings_class(self) -> Optional[Type["BaseSettings"]]:
|
61
|
+
"""Settings class for the Kubernetes step operator.
|
62
|
+
|
63
|
+
Returns:
|
64
|
+
The settings class.
|
65
|
+
"""
|
66
|
+
return KubernetesStepOperatorSettings
|
67
|
+
|
68
|
+
@property
|
69
|
+
def validator(self) -> Optional[StackValidator]:
|
70
|
+
"""Validates the stack.
|
71
|
+
|
72
|
+
Returns:
|
73
|
+
A validator that checks that the stack contains a remote container
|
74
|
+
registry and a remote artifact store.
|
75
|
+
"""
|
76
|
+
|
77
|
+
def _validate_remote_components(stack: "Stack") -> Tuple[bool, str]:
|
78
|
+
if stack.artifact_store.config.is_local:
|
79
|
+
return False, (
|
80
|
+
"The Kubernetes step operator runs code remotely and "
|
81
|
+
"needs to write files into the artifact store, but the "
|
82
|
+
f"artifact store `{stack.artifact_store.name}` of the "
|
83
|
+
"active stack is local. Please ensure that your stack "
|
84
|
+
"contains a remote artifact store when using the Vertex "
|
85
|
+
"step operator."
|
86
|
+
)
|
87
|
+
|
88
|
+
container_registry = stack.container_registry
|
89
|
+
assert container_registry is not None
|
90
|
+
|
91
|
+
if container_registry.config.is_local:
|
92
|
+
return False, (
|
93
|
+
"The Kubernetes step operator runs code remotely and "
|
94
|
+
"needs to push/pull Docker images, but the "
|
95
|
+
f"container registry `{container_registry.name}` of the "
|
96
|
+
"active stack is local. Please ensure that your stack "
|
97
|
+
"contains a remote container registry when using the "
|
98
|
+
"Kubernetes step operator."
|
99
|
+
)
|
100
|
+
|
101
|
+
return True, ""
|
102
|
+
|
103
|
+
return StackValidator(
|
104
|
+
required_components={
|
105
|
+
StackComponentType.CONTAINER_REGISTRY,
|
106
|
+
StackComponentType.IMAGE_BUILDER,
|
107
|
+
},
|
108
|
+
custom_validation_function=_validate_remote_components,
|
109
|
+
)
|
110
|
+
|
111
|
+
def get_docker_builds(
|
112
|
+
self, deployment: "PipelineDeploymentBase"
|
113
|
+
) -> List["BuildConfiguration"]:
|
114
|
+
"""Gets the Docker builds required for the component.
|
115
|
+
|
116
|
+
Args:
|
117
|
+
deployment: The pipeline deployment for which to get the builds.
|
118
|
+
|
119
|
+
Returns:
|
120
|
+
The required Docker builds.
|
121
|
+
"""
|
122
|
+
builds = []
|
123
|
+
for step_name, step in deployment.step_configurations.items():
|
124
|
+
if step.config.step_operator == self.name:
|
125
|
+
build = BuildConfiguration(
|
126
|
+
key=KUBERNETES_STEP_OPERATOR_DOCKER_IMAGE_KEY,
|
127
|
+
settings=step.config.docker_settings,
|
128
|
+
step_name=step_name,
|
129
|
+
)
|
130
|
+
builds.append(build)
|
131
|
+
|
132
|
+
return builds
|
133
|
+
|
134
|
+
def get_kube_client(self) -> k8s_client.ApiClient:
|
135
|
+
"""Get the Kubernetes API client.
|
136
|
+
|
137
|
+
Returns:
|
138
|
+
The Kubernetes API client.
|
139
|
+
|
140
|
+
Raises:
|
141
|
+
RuntimeError: If the service connector returns an unexpected client.
|
142
|
+
"""
|
143
|
+
if self.config.incluster:
|
144
|
+
kube_utils.load_kube_config(incluster=True)
|
145
|
+
self._k8s_client = k8s_client.ApiClient()
|
146
|
+
return self._k8s_client
|
147
|
+
|
148
|
+
# Refresh the client also if the connector has expired
|
149
|
+
if self._k8s_client and not self.connector_has_expired():
|
150
|
+
return self._k8s_client
|
151
|
+
|
152
|
+
connector = self.get_connector()
|
153
|
+
if connector:
|
154
|
+
client = connector.connect()
|
155
|
+
if not isinstance(client, k8s_client.ApiClient):
|
156
|
+
raise RuntimeError(
|
157
|
+
f"Expected a k8s_client.ApiClient while trying to use the "
|
158
|
+
f"linked connector, but got {type(client)}."
|
159
|
+
)
|
160
|
+
self._k8s_client = client
|
161
|
+
else:
|
162
|
+
kube_utils.load_kube_config(
|
163
|
+
context=self.config.kubernetes_context,
|
164
|
+
)
|
165
|
+
self._k8s_client = k8s_client.ApiClient()
|
166
|
+
|
167
|
+
return self._k8s_client
|
168
|
+
|
169
|
+
@property
|
170
|
+
def _k8s_core_api(self) -> k8s_client.CoreV1Api:
|
171
|
+
"""Getter for the Kubernetes Core API client.
|
172
|
+
|
173
|
+
Returns:
|
174
|
+
The Kubernetes Core API client.
|
175
|
+
"""
|
176
|
+
return k8s_client.CoreV1Api(self.get_kube_client())
|
177
|
+
|
178
|
+
def launch(
|
179
|
+
self,
|
180
|
+
info: "StepRunInfo",
|
181
|
+
entrypoint_command: List[str],
|
182
|
+
environment: Dict[str, str],
|
183
|
+
) -> None:
|
184
|
+
"""Launches a step on Kubernetes.
|
185
|
+
|
186
|
+
Args:
|
187
|
+
info: Information about the step run.
|
188
|
+
entrypoint_command: Command that executes the step.
|
189
|
+
environment: Environment variables to set in the step operator
|
190
|
+
environment.
|
191
|
+
"""
|
192
|
+
settings = cast(
|
193
|
+
KubernetesStepOperatorSettings, self.get_settings(info)
|
194
|
+
)
|
195
|
+
image_name = info.get_image(
|
196
|
+
key=KUBERNETES_STEP_OPERATOR_DOCKER_IMAGE_KEY
|
197
|
+
)
|
198
|
+
|
199
|
+
pod_name = f"{info.run_name}_{info.pipeline_step_name}"
|
200
|
+
pod_name = kube_utils.sanitize_pod_name(pod_name)
|
201
|
+
|
202
|
+
command = entrypoint_command[:3]
|
203
|
+
args = entrypoint_command[3:]
|
204
|
+
|
205
|
+
# Create and run the orchestrator pod.
|
206
|
+
pod_manifest = build_pod_manifest(
|
207
|
+
run_name=info.run_name,
|
208
|
+
pod_name=pod_name,
|
209
|
+
pipeline_name=info.pipeline.name,
|
210
|
+
image_name=image_name,
|
211
|
+
command=command,
|
212
|
+
args=args,
|
213
|
+
privileged=settings.privileged,
|
214
|
+
service_account_name=settings.service_account_name,
|
215
|
+
pod_settings=settings.pod_settings,
|
216
|
+
env=environment,
|
217
|
+
mount_local_stores=False,
|
218
|
+
)
|
219
|
+
|
220
|
+
self._k8s_core_api.create_namespaced_pod(
|
221
|
+
namespace=self.config.kubernetes_namespace,
|
222
|
+
body=pod_manifest,
|
223
|
+
)
|
224
|
+
|
225
|
+
logger.info(
|
226
|
+
"Waiting for pod of step `%s` to start...", info.pipeline_step_name
|
227
|
+
)
|
228
|
+
kube_utils.wait_pod(
|
229
|
+
kube_client_fn=self.get_kube_client,
|
230
|
+
pod_name=pod_name,
|
231
|
+
namespace=self.config.kubernetes_namespace,
|
232
|
+
exit_condition_lambda=kube_utils.pod_is_done,
|
233
|
+
stream_logs=True,
|
234
|
+
)
|
235
|
+
logger.info("Pod of step `%s` completed.", info.pipeline_step_name)
|