Skip to content

Commit

Permalink
feat(Backend + SDK): Update kfp backend and kubernetes sdk to support…
Browse files Browse the repository at this point in the history
… ConfigMaps as volumes and as env variables (#10483)

* Update kfp backend and kubernetes sdk to support ConfigMaps as volumes and as env

Signed-off-by: Alexey Roytman <roytman@il.ibm.com>

* update go.mod, apiserver.csv and driver.csv

Signed-off-by: Alexey Roytman <roytman@il.ibm.com>

* add test/snapshot/data files

Signed-off-by: Alexey Roytman <roytman@il.ibm.com>

* fix tests

Signed-off-by: Alexey Roytman <roytman@il.ibm.com>

* go mod tidy

Signed-off-by: Alexey Roytman <roytman@il.ibm.com>

* update backend/third_party_licenses/apiserver.csv

Signed-off-by: Alexey Roytman <roytman@il.ibm.com>

* update backend/third_party_licenses/apiserver.csv

Signed-off-by: Alexey Roytman <roytman@il.ibm.com>

* fix comments

Signed-off-by: Alexey Roytman <roytman@il.ibm.com>

* fix comments

Signed-off-by: Alexey Roytman <roytman@il.ibm.com>

* update go.mod, apiserver.csv and driver.csv

Signed-off-by: Alexey Roytman <roytman@il.ibm.com>

---------

Signed-off-by: Alexey Roytman <roytman@il.ibm.com>
  • Loading branch information
roytman committed Feb 24, 2024
1 parent 3dbf3cf commit 1edd85f
Show file tree
Hide file tree
Showing 15 changed files with 818 additions and 9 deletions.
33 changes: 33 additions & 0 deletions backend/src/v2/driver/driver.go
Expand Up @@ -534,6 +534,39 @@ func extendPodSpecPatch(
}
}

// Get config map mount information
for _, configMapAsVolume := range kubernetesExecutorConfig.GetConfigMapAsVolume() {
configMapVolume := k8score.Volume{
Name: configMapAsVolume.GetConfigMapName(),
VolumeSource: k8score.VolumeSource{
ConfigMap: &k8score.ConfigMapVolumeSource{
LocalObjectReference: k8score.LocalObjectReference{Name: configMapAsVolume.GetConfigMapName()}},
},
}
configMapVolumeMount := k8score.VolumeMount{
Name: configMapAsVolume.GetConfigMapName(),
MountPath: configMapAsVolume.GetMountPath(),
}
podSpec.Volumes = append(podSpec.Volumes, configMapVolume)
podSpec.Containers[0].VolumeMounts = append(podSpec.Containers[0].VolumeMounts, configMapVolumeMount)
}

// Get config map env information
for _, configMapAsEnv := range kubernetesExecutorConfig.GetConfigMapAsEnv() {
for _, keyToEnv := range configMapAsEnv.GetKeyToEnv() {
configMapEnvVar := k8score.EnvVar{
Name: keyToEnv.GetEnvVar(),
ValueFrom: &k8score.EnvVarSource{
ConfigMapKeyRef: &k8score.ConfigMapKeySelector{
Key: keyToEnv.GetConfigMapKey(),
},
},
}
configMapEnvVar.ValueFrom.ConfigMapKeyRef.LocalObjectReference.Name = configMapAsEnv.GetConfigMapName()
podSpec.Containers[0].Env = append(podSpec.Containers[0].Env, configMapEnvVar)
}
}

// Get image pull secret information
for _, imagePullSecret := range kubernetesExecutorConfig.GetImagePullSecret() {
podSpec.ImagePullSecrets = append(podSpec.ImagePullSecrets, k8score.LocalObjectReference{Name: imagePullSecret.GetSecretName()})
Expand Down
117 changes: 117 additions & 0 deletions backend/src/v2/driver/driver_test.go
Expand Up @@ -606,6 +606,123 @@ func Test_extendPodSpecPatch_Secret(t *testing.T) {
}
}

func Test_extendPodSpecPatch_ConfigMap(t *testing.T) {
tests := []struct {
name string
k8sExecCfg *kubernetesplatform.KubernetesExecutorConfig
podSpec *k8score.PodSpec
expected *k8score.PodSpec
}{
{
"Valid - config map as volume",
&kubernetesplatform.KubernetesExecutorConfig{
ConfigMapAsVolume: []*kubernetesplatform.ConfigMapAsVolume{
{
ConfigMapName: "cm1",
MountPath: "/data/path",
},
},
},
&k8score.PodSpec{
Containers: []k8score.Container{
{
Name: "main",
},
},
},
&k8score.PodSpec{
Containers: []k8score.Container{
{
Name: "main",
VolumeMounts: []k8score.VolumeMount{
{
Name: "cm1",
MountPath: "/data/path",
},
},
},
},
Volumes: []k8score.Volume{
{
Name: "cm1",
VolumeSource: k8score.VolumeSource{
ConfigMap: &k8score.ConfigMapVolumeSource{
LocalObjectReference: k8score.LocalObjectReference{Name: "cm1"}},
},
},
},
},
},
{
"Valid - config map not specified",
&kubernetesplatform.KubernetesExecutorConfig{},
&k8score.PodSpec{
Containers: []k8score.Container{
{
Name: "main",
},
},
},
&k8score.PodSpec{
Containers: []k8score.Container{
{
Name: "main",
},
},
},
},
{
"Valid - config map as env",
&kubernetesplatform.KubernetesExecutorConfig{
ConfigMapAsEnv: []*kubernetesplatform.ConfigMapAsEnv{
{
ConfigMapName: "my-cm",
KeyToEnv: []*kubernetesplatform.ConfigMapAsEnv_ConfigMapKeyToEnvMap{
{
ConfigMapKey: "foo",
EnvVar: "CONFIG_MAP_VAR",
},
},
},
},
},
&k8score.PodSpec{
Containers: []k8score.Container{
{
Name: "main",
},
},
},
&k8score.PodSpec{
Containers: []k8score.Container{
{
Name: "main",
Env: []k8score.EnvVar{
{
Name: "CONFIG_MAP_VAR",
ValueFrom: &k8score.EnvVarSource{
ConfigMapKeyRef: &k8score.ConfigMapKeySelector{
k8score.LocalObjectReference{Name: "my-cm"},
"foo",
nil,
},
},
},
},
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := extendPodSpecPatch(tt.podSpec, tt.k8sExecCfg, nil, nil)
assert.Nil(t, err)
assert.Equal(t, tt.expected, tt.podSpec)
})
}
}

func Test_extendPodSpecPatch_ImagePullSecrets(t *testing.T) {
tests := []struct {
name string
Expand Down
2 changes: 1 addition & 1 deletion backend/third_party_licenses/apiserver.csv
Expand Up @@ -61,7 +61,7 @@ github.com/klauspost/cpuid,https://github.com/klauspost/cpuid/blob/v1.3.1/LICENS
github.com/klauspost/pgzip,https://github.com/klauspost/pgzip/blob/v1.2.5/LICENSE,MIT
github.com/kubeflow/pipelines/api/v2alpha1/go,https://github.com/kubeflow/pipelines/blob/758c91f76784/api/LICENSE,Apache-2.0
github.com/kubeflow/pipelines/backend,https://github.com/kubeflow/pipelines/blob/HEAD/LICENSE,Apache-2.0
github.com/kubeflow/pipelines/kubernetes_platform/go/kubernetesplatform,https://github.com/kubeflow/pipelines/blob/e129b0501379/kubernetes_platform/LICENSE,Apache-2.0
github.com/kubeflow/pipelines/kubernetes_platform/go/kubernetesplatform,https://github.com/kubeflow/pipelines/blob/2983a7d49078/kubernetes_platform/LICENSE,Apache-2.0
github.com/kubeflow/pipelines/third_party/ml-metadata/go/ml_metadata,https://github.com/kubeflow/pipelines/blob/e1f0c010f800/third_party/ml-metadata/LICENSE,Apache-2.0
github.com/lann/builder,https://github.com/lann/builder/blob/47ae307949d0/LICENSE,MIT
github.com/lann/ps,https://github.com/lann/ps/blob/62de8c46ede0/LICENSE,MIT
Expand Down
2 changes: 1 addition & 1 deletion backend/third_party_licenses/driver.csv
Expand Up @@ -31,7 +31,7 @@ github.com/josharian/intern,https://github.com/josharian/intern/blob/v1.0.0/lice
github.com/json-iterator/go,https://github.com/json-iterator/go/blob/v1.1.12/LICENSE,MIT
github.com/kubeflow/pipelines/api/v2alpha1/go,https://github.com/kubeflow/pipelines/blob/758c91f76784/api/LICENSE,Apache-2.0
github.com/kubeflow/pipelines/backend,https://github.com/kubeflow/pipelines/blob/HEAD/LICENSE,Apache-2.0
github.com/kubeflow/pipelines/kubernetes_platform/go/kubernetesplatform,https://github.com/kubeflow/pipelines/blob/e129b0501379/kubernetes_platform/LICENSE,Apache-2.0
github.com/kubeflow/pipelines/kubernetes_platform/go/kubernetesplatform,https://github.com/kubeflow/pipelines/blob/2983a7d49078/kubernetes_platform/LICENSE,Apache-2.0
github.com/kubeflow/pipelines/third_party/ml-metadata/go/ml_metadata,https://github.com/kubeflow/pipelines/blob/e1f0c010f800/third_party/ml-metadata/LICENSE,Apache-2.0
github.com/mailru/easyjson,https://github.com/mailru/easyjson/blob/v0.7.7/LICENSE,MIT
github.com/modern-go/concurrent,https://github.com/modern-go/concurrent/blob/bacd9c7ef1dd/LICENSE,Apache-2.0
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Expand Up @@ -31,7 +31,7 @@ require (
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.4 // indirect
github.com/kubeflow/pipelines/api v0.0.0-20230331215358-758c91f76784
github.com/kubeflow/pipelines/kubernetes_platform v0.0.0-20240216222951-e129b0501379
github.com/kubeflow/pipelines/kubernetes_platform v0.0.0-20240222213131-2983a7d49078
github.com/kubeflow/pipelines/third_party/ml-metadata v0.0.0-20230810215105-e1f0c010f800
github.com/lestrrat-go/strftime v1.0.4
github.com/mattn/go-sqlite3 v1.14.16
Expand Down
4 changes: 2 additions & 2 deletions go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

40 changes: 39 additions & 1 deletion kubernetes_platform/python/README.md
Expand Up @@ -57,6 +57,44 @@ def pipeline():
mount_path='/mnt/my_vol')
```

### ConfigMap: As environment variable
```python
from kfp import dsl
from kfp import kubernetes

@dsl.component
def print_config_map():
import os
print(os.environ['my-cm'])

@dsl.pipeline
def pipeline():
task = print_config_map()
kubernetes.use_config_map_as_env(task,
config_map_name='my-cm',
secret_key_to_env={'foo': 'CM_VAR'})
```

### ConfigMap: As mounted volume
```python
from kfp import dsl
from kfp import kubernetes

@dsl.component
def print_config_map():
with open('/mnt/my_vol') as f:
print(f.read())

@dsl.pipeline
def pipeline():
task = print_config_map()
kubernetes.use_secret_as_volume(task,
config_map_name='my-cm',
mount_path='/mnt/my_vol')
```



### PersistentVolumeClaim: Dynamically create PVC, mount, then delete
```python
from kfp import dsl
Expand Down Expand Up @@ -127,4 +165,4 @@ def my_pipeline():
annotation_key='run_id',
annotation_value='123456',
)
```
```
4 changes: 4 additions & 0 deletions kubernetes_platform/python/kfp/kubernetes/__init__.py
Expand Up @@ -23,11 +23,15 @@
'DeletePVC',
'mount_pvc',
'set_image_pull_secrets',
'use_config_map_as_env',
'use_config_map_as_volume',
'use_secret_as_env',
'use_secret_as_volume',
]

from kfp.kubernetes.image import set_image_pull_secrets
from kfp.kubernetes.config_map import use_config_map_as_volume
from kfp.kubernetes.config_map import use_config_map_as_env
from kfp.kubernetes.node_selector import add_node_selector
from kfp.kubernetes.pod_metadata import add_pod_annotation
from kfp.kubernetes.pod_metadata import add_pod_label
Expand Down
87 changes: 87 additions & 0 deletions kubernetes_platform/python/kfp/kubernetes/config_map.py
@@ -0,0 +1,87 @@
# Copyright 2024 The Kubeflow Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import Dict

from google.protobuf import json_format
from kfp.dsl import PipelineTask
from kfp.kubernetes import common
from kfp.kubernetes import kubernetes_executor_config_pb2 as pb


def use_config_map_as_env(
task: PipelineTask,
config_map_name: str,
config_map_key_to_env: Dict[str, str],
) -> PipelineTask:
"""Use a Kubernetes ConfigMap as an environment variable as described by the `Kubernetes documentation
https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/#define-container-environment-variables-using-configmap-data` _.
Args:
task: Pipeline task.
config_map_name: Name of the ConfigMap.
config_map_key_to_env: Dictionary of ConfigMap key to environment variable name. For example, ``{'foo': 'FOO'}`` sets the value of the ConfigMap's foo field to the environment variable ``FOO``.
Returns:
Task object with updated ConfigMap configuration.
"""

msg = common.get_existing_kubernetes_config_as_message(task)

key_to_env = [
pb.ConfigMapAsEnv.ConfigMapKeyToEnvMap(
config_map_key=config_map_key,
env_var=env_var,
) for config_map_key, env_var in config_map_key_to_env.items()
]
config_map_as_env = pb.ConfigMapAsEnv(
config_map_name=config_map_name,
key_to_env=key_to_env,
)

msg.config_map_as_env.append(config_map_as_env)

task.platform_config['kubernetes'] = json_format.MessageToDict(msg)

return task


def use_config_map_as_volume(
task: PipelineTask,
config_map_name: str,
mount_path: str,
) -> PipelineTask:
"""Use a Kubernetes ConfigMap by mounting its data to the task's container as
described by the `Kubernetes documentation <https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/#add-configmap-data-to-a-volume>`_.
Args:
task: Pipeline task.
config_map_name: Name of the ConfigMap.
mount_path: Path to which to mount the ConfigMap data.
Returns:
Task object with updated ConfigMap configuration.
"""

msg = common.get_existing_kubernetes_config_as_message(task)

config_map_as_vol = pb.ConfigMapAsVolume(
config_map_name=config_map_name,
mount_path=mount_path,
)
msg.config_map_as_volume.append(config_map_as_vol)

task.platform_config['kubernetes'] = json_format.MessageToDict(msg)

return task
5 changes: 2 additions & 3 deletions kubernetes_platform/python/kfp/kubernetes/secret.py
Expand Up @@ -25,9 +25,8 @@ def use_secret_as_env(
secret_name: str,
secret_key_to_env: Dict[str, str],
) -> PipelineTask:
"""Use a Kubernetes Secret as an environment variable as described in
https://kubernetes.io/docs/concepts/configuration/secret/#using-secrets-as-
environment-variables.
"""Use a Kubernetes Secret as an environment variable as described by the `Kubernetes documentation
https://kubernetes.io/docs/concepts/configuration/secret/#using-secrets-as-environment-variables `_.
Args:
task: Pipeline task.
Expand Down

0 comments on commit 1edd85f

Please sign in to comment.