-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Extend the DSL with support for Persistent Volumes and Snapshots #801
Comments
Also fixed a few typos in the description and the code example for uniformity. |
@vkoukis Did you consider extending the DSL to just make K8s resources first class? Then a resource like K8s Job or TFJob or any resource which then supports volumes and PVs would automatically work? The DSL then becomes a way to build those objects using idiomatic python. One way to achieve the above would be to use the DSL as it exists today and have the ContainerOp create the desired K8s resource. So we add a layer of indirection. Instead of directly creating a K8s job we launch a container which will create it. This can be done today using pipelines support for lightweight containers. Did the DSL intentionally choose ContainerOp as the primitive or is this the reflection of the underlying implementation being Argo and the DSL being designed to match it? /cc @Ark-kun @hongye-sun |
Hello @jlewi , thanks for taking the time to read the design doc!
I think this design doc actually does target making these two specific K8s resources, If I understand your argument correctly, it is "why not extend the DSL so it can also manage generic K8s resources, as pipeline steps?" Yes, I did consider extending the DSL to make (generic) K8s resources first class, but I bumped into two main problems:
First: Yes, the user could orchestrate the creation of generic K8s resources, including volumes as lightweight containers, e.g., using This is also the problem that #783 tries to address.
This exposes a lot of the lower-level interaction with K8s to the end user.
The whole point of having a DSL is to make the most common tasks super-simple.
and then
in the arguments to the ContainerOp constructor. Second: A more important advantage of using DSL-specific objects, instead of K8s objects directly, is that these objects, For example
means "referencing
lead to implicit scheduling of the snapshot only when This makes volumes and snapshots accessible from the DSL in a very idiomatic Pythonic way, and allows expressing rather complex dependencies on the use of volumes by steps. |
/cc @hongye-sun Hi @vkoukis, I have a couple comments:
Currently, the DSL exposes a subset of Argo features. We are thinking of changing this so that All Argo features are available (including the POD and container level specific features that Argo exposes). We are looking for a way to expose these features in the DSL without having to manually write the Python code to make them accessible. (Maybe we could do this by leveraging the Argo openAPI spec, but it seems a bit tricky: https://github.com/argoproj/argo/blob/master/api/openapi-spec/swagger.json). What is your opinion on this? Would it affect your implementation? Higher level utilities could still be implemented on top of the lower level API to match what you propose here.
Could you please clarify how the volume related resources will be created and reliably released in your implementation? Will you need to modify the backend (it only supports Argo workflows today)? Will you create a container from a pipeline step to create the resource? Will you leverage some Argo feature? (https://github.com/argoproj/argo/blob/master/examples/volumes-pvc.yaml#L11) The later is I think what you mean when you say "Note the PV is created dynamically, by the workflow engine". |
/cc @vicaire |
I would like to express my opinion on this from an end-user perspective. In our organization we have been testing Pipelines and tried to integrate it into our workflows. The issues and limitations with mounting volumes and managing PVCs restrict us from adopting Pipelines extensively and in production settings. I agree with @vkoukis on this:
One of the major obstacles in adopting Pipelines is that our users need both to learn the specific Pipelines DSL and to have some knowledge about the inners workings of Pipelines, to compile and run their workflows. We would like to be able to completely abstract away the K8S specific concepts (VolumeClaims, VolumeMount, PersistentVolumeClaims, Secrets, ...) to let the users just focus on building the actual workflow. This proposal aligns perfectly with this and reflects our needs for a higher level DSL and easier volumes management. Specifically:
We feel like this proposal could set Pipelines' storage management in the right direction. We would be very happy to take part to the discussion, provide support and end-to-end use cases to design the best possible API and user experience. |
Thank you for the detailed write-up and proposal ! These capture many pain-points that we faced as we were trying out Pipelines. I like the idea to manage the lifecycle of volumes and snapshots without touching the k8s python client.
|
Thanks for the proposal. I like the design to abstract k8s volume and snapshot implementation in DSL. I have a few questions:
|
@vicaire Hello Pascal, thanks for taking the time to read the design doc and provide feedback!
We definitely don't want to modify the DSL compiler backend, this would be a major change. We explicitly targeted the existing Argo workflows framework when designing this. I understand that Argo currently supports We explicitly opted against Instead, our current implementation bases Going this way enables this kind of code with rich dependencies:
This will create one task in the DAG for creating the PVC that backs Note we do not depend on how Argo itself implements the
After we have this merged, and have gathered more experience, we would definitely like to tackle what you are proposing, and if I understand correctly also what @jlewi alluded to:
Yes, we are leveraging a feature that Argo provides, the |
Hello Stefano! Thank you for the encouraging words! We're definitely excited about the real-world use cases of this, as well. We'll be following up with a PR shortly. We would love to get your feedback on the implementation, and adjust accordingly. |
Hello Adhita! We had read your #783 as part of trying to understand the current approaches, and we had it in mind as we iterated on the proposed extensions to the DSL, thank you for this.
Thanks :)
Dependency resolution comes with using
Since we have creation of PVCs and VolumeSnapshots as distinct steps in a DAG, we can show them independently, and they can fail independently. If a We haven't looked deeply into into how we can communicate this failure condition all the way up to the UI, so we would definitely welcome any suggestions you may have! |
Thank you for taking the time to read the design doc and give feedback!
We explicitly do not want it to be a generic "K8s Volume", because this may mean a lot of different things, which behave in a completely different way. We explicitly target We limit what a Also note that although
A
We treat
I think @StefanoFioravanzo's comment summarizes it nicely:
Taking a snapshot can be a very efficient operation with modern storage, even when multi-GB data is involved. So, we want to enable Pipelines to orchestrate the external storage to take snapshots, at user-defined steps. The user specifies this explicitly, as part of defining the pipeline, see the code example above. So, for example, they can snapshot every individual steps output, before it is passed to a next step, very efficiently. This way, using snapshots allows you to reconstruct the exact input and output that a step had, by mounting these snapshots directly, without having to copy multiple GBs of data in and out of the container, from/to an object store, as you would have to do with the current artifact stores.
A |
@vkoukis Great design for supporting PipelineVolume easily! One question here, did you consider the case that the PVC name is specified from Pipelines UI during creating a run after pipeline definited? Seems that's not supported by old way (using k8s_client.V1VolumeMount) due to #521. From the design we allow user to specify an existing PVC, but user may change that during creating the run on Pipelines UI. Thanks. /cc @gyliu513 @hougangliu |
Thanks for reading the design doc and giving feedback! That's a good question. Similarly to how you can specify the data source to use as a I'm not sure #521 is related; the discussion there is whether a So, to summarize, I think it makes sense to be able to specify an existing PVC as a |
Sounds great. Yes, based on a |
Update: the KFP team is looking into this closely. We realize the importance of good support for volume, and a good/intuitive abstraction in the DSL. |
@vicaire Great, thanks for the update! We've proposed a demo at the upcoming community meeting of Tue, Feb 26, so we can also show the latest iteration of our code live, and solicit feedback. |
@hongye-sun @vkoukis How would things change if the fundamental primitive in the DSL was a K8s job and not a container? e.g. if it was
A container is not a top level K8s resource; Deployments, Jobs, Pods, Custom resources these are top level resources. Is our efforts to simplify K8s resources by eliminating certain fields just creating more problems? Are we just going to end up exposing all the fields in the underlying K8s resource just with a different API? What if we broke this into separate problems
The second problem seems to tap into a larger problem within K8s community of using programming languages to define resources.
Regarding #3, it seems like one of the problems we are facing is that in python its easy to define a lambda as a python function and have well defined inputs and outputs. Right now we are trying to shoe horn that into what Argo provides. Maybe there's a better primitive out there e.g. from OpenFaas or Knative? And if not should we consider creating a suitable custom resource that the DSL could compile to? |
Jeremy, We can make DSL just focusing on the orchestration by generalizing k8s resource spec like: dsl.ResourceOp(k8s_resource_spec) The problem I see here is how to pass the data between steps which is the core problem for orchestration part. The data I am describing here have two types: small parameter data and large artifact data. For small ones, we solved it in ContainerOp by chaining output_files between steps. This logic here is very specific to container resource in k8s. I am not confident that we can come up with generic solution to all k8s resources. The argo's way to use json path query to return output from k8s resource is not enough as there is no way to let user to control the output in their code. E.g. user cannot output some data from a tf-job to pipeline easily. The same problem applies to large data. I think the proposal here is trying to solve large data passing problem by using volumes. I have a few ideas here:
Does it make sense? |
Hey Antons
A wrapper around the k8s python client for using volumes already exists (see: PR #783)
Adding a EmptyDir is still left though.
… On Mar 2, 2019, at 17:49, Antons Kranga ***@***.***> wrote:
Let me put my 5 cents. I believe existing functionality of a ContainerOp is good enough. There are number of variations what for to use volumes. So instead of trying to replicate PodSpec it is better (IMHO) to stick with python (client)[https://github.com/kubernetes-client/python]
Current implementation of ContainerOp allows easy escape to the python client. Also (IMHO) KFP should be generic enough and avoid put code that user (possibly data scientist) doesn't want to see.
Created a gist is a gist how to use it: https://gist.github.com/akranga/bd92b48da913582e82b91ca13a4733ab
Please comment on it!
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or mute the thread.
|
Following the review in #926 [thanks @hongye-sun , @vicaire ! ], I am amending this design doc to include a step of implementation steps. Overall our goals are:
More specifically, here is the proposed implementation, in distinct steps: Commit 1: Minor changes in the DSL and the CompilerTo introduce Commit 2: ResourceOpDefine Input: It accepts any object that describes a K8s resource in the K8s Python client. Output: The operator queries the object it created during the This would be an easy way to create tasks which create a certain kind of K8s resource and output some of its attributes as task outputs. It also covers @jlewi's proposal for making K8s resources first class. If I understand correctly, it also closes #415, #429. Similarly, it can be used to orchestrate TFJob instances or secrets, see #973, #1027 Example:
Commit 3: Enable
|
Hi everyone! I am really glad #879 is merged, it is a great job. Kudos to @eterna2! Since we will be implementing We will move anything common for all the leaf template types from Finally, |
* Rename BaseOp.volumes to k8s_volumes * Add cops attributes to Pipeline. This is a dict having all the ContainerOps of the pipeline. * Set some processing in _op_to_template as ContainerOp specific Signed-off-by: Ilias Katsakioris <elikatsis@arrikto.com>
vkoukis@, elikatsis@, thanks for the fantastic proposal and detailed implementation plan. This is great! We are looking forward to the PRs! |
@vicaire Thank you for the kind words! We have pushed a new version of the PR following the revised implementation plan, please see here: #926 (comment) Looking forward to your review! |
* Rename BaseOp.volumes to k8s_volumes * Add cops attributes to Pipeline. This is a dict having all the ContainerOps of the pipeline. * Set some processing in _op_to_template as ContainerOp specific Signed-off-by: Ilias Katsakioris <elikatsis@arrikto.com>
* Rename BaseOp.volumes to k8s_volumes * Add cops attributes to Pipeline. This is a dict having all the ContainerOps of the pipeline. * Set some processing in _op_to_template as ContainerOp specific Signed-off-by: Ilias Katsakioris <elikatsis@arrikto.com>
* Add cops attributes to Pipeline. This is a dict having all the ContainerOps of the pipeline. * Set some processing in _op_to_template as ContainerOp specific Signed-off-by: Ilias Katsakioris <elikatsis@arrikto.com>
* SDK: Create BaseOp class * BaseOp class is the base class for any Argo Template type * ContainerOp derives from BaseOp * Rename dependent_names to deps Signed-off-by: Ilias Katsakioris <elikatsis@arrikto.com> * SDK: In preparation for the new feature ResourceOps (#801) * Add cops attributes to Pipeline. This is a dict having all the ContainerOps of the pipeline. * Set some processing in _op_to_template as ContainerOp specific Signed-off-by: Ilias Katsakioris <elikatsis@arrikto.com> * SDK: Simplify the consumption of Volumes by ContainerOps Add `pvolumes` argument and attribute to ContainerOp. It is a dict having mount paths as keys and V1Volumes as values. These are added to the pipeline and mounted by the container of the ContainerOp. Signed-off-by: Ilias Katsakioris <elikatsis@arrikto.com> * SDK: Add ResourceOp * ResourceOp is the SDK's equivalent for Argo's resource template * Add rops attribute to Pipeline: Dictionary containing ResourceOps * Extend _op_to_template to produce the template for ResourceOps * Use processed_op instead of op everywhere in _op_to_template() * Add samples/resourceop/resourceop_basic.py * Add tests/dsl/resource_op_tests.py * Extend tests/compiler/compiler_tests.py Signed-off-by: Ilias Katsakioris <elikatsis@arrikto.com> * SDK: Simplify the creation of PersistentVolumeClaim instances * Add VolumeOp: A specified ResourceOp for PVC creation * Add samples/resourceops/volumeop_basic.py * Add tests/dsl/volume_op_tests.py * Extend tests/compiler/compiler_tests.py Signed-off-by: Ilias Katsakioris <elikatsis@arrikto.com> * SDK: Emit a V1Volume as `.volume` from dsl.VolumeOp * Extend VolumeOp so it outputs a `.volume` attribute ready to be consumed by the `pvolumes` argument to ContainerOp's constructor * Update samples/resourceop/volumeop_basic.py * Extend tests/dsl/volume_op_tests.py * Update tests/compiler/compiler_tests.py Signed-off-by: Ilias Katsakioris <elikatsis@arrikto.com> * SDK: Add PipelineVolume * PipelineVolume inherits from V1Volume and it comes with its own set of KFP-specific dependencies. It is aligned with how PipelineParam instances are used. I.e. consuming a PipelineVolume leads to implicit dependencies without the user having to call the `.after()` method on a ContainerOp. * PipelineVolume comes with its own `.after()` method, which can be used to append extra dependencies to the instance. * Extend ContainerOp to handle PipelineVolume deps * Set `.volume` attribute of VolumeOp to be a PipelineVolume instead * Add samples/resourceops/volumeop_{parallel,dag,sequential}.py * Fix tests/dsl/volume_op_tests.py * Add tests/dsl/pipeline_volume_tests.py * Extend tests/compiler/compiler_tests.py Signed-off-by: Ilias Katsakioris <elikatsis@arrikto.com> * SDK: Simplify the creation of VolumeSnapshot instances * VolumeSnapshotOp: A specified ResourceOp for VolumeSnapshot creation * Add samples/resourceops/volume_snapshotop_{sequential,rokurl}.py * Add tests/dsl/volume_snapshotop_tests.py * Extend tests/compiler/compiler_tests.py NOTE: VolumeSnapshots is an Alpha feature at the time of this commit. Signed-off-by: Ilias Katsakioris <elikatsis@arrikto.com> * Extend UI for the ResourceOp and Volumes feature of the Compiler * Add VolumeMounts tab/entry (Run/Pipeline view) * Add Manifest tab/entry (Run/Pipeline view) * Add & Extend tests * Update tests snapshot files Signed-off-by: Ilias Katsakioris <elikatsis@arrikto.com> * Cleaning up the diff (before moving things back) * Renamed op.deps back to op.dependent_names * Moved Container, Sidecar and BaseOp classed back to _container_op.py This way the diff is much smaller and more understandable. We can always split or refactor the file later. Refactorings should not be mixed with genuine changes.
Extend the DSL with support for Persistent Volumes and Snapshots
Overview - Rationale
This document describes proposed additions to the DSL of Kubeflow Pipelines to seamlessly support the use of Persistent Volumes and Volume Snapshots as distinct resources in pipelines.
This means steps can exchange multi-GB data doing standard file I/O on mounted Persistent Volumes, without having to upload/download this data to/from external object stores. To manipulate storage volumes, we use standard, vendor-neutral Kubernetes primitives [namely
PersistentVolumeClaim
, andVolumeSnapshot
API objects], but without the user having to manipulate these K8s objects manually.Instead, the goal is for the user to declare how steps use volumes as pipeline resources for data exchange, with an intuitive DSL syntax, similarly to what they do for the rest of their pipeline resources.
Extending the DSL to support the use of PVs for data exchange will make the use of Kubeflow Pipelines on-prem much easier, and will also enable the use of advanced storage functionality offered by modern storage, i.e., snapshots, as a way to gain insight on how a multi-GB pipeline executes at every step. A few issues related to this, which this design also targets, are #783, #721, #275.
This is work done jointly with @elikatsis, @ioandr, @klolos, and @iliastsi.
@elikatsis has already completed the changes to the compiler necessary to support the functionality being described in this design doc, and will be submitting a PR with the proposed changes incorporating community feedback from this discussion.
We have already completed related work, to introduce support for mounting arbitrary PVs in notebooks, for kubeflow/kubeflow#34, kubeflow/kubeflow#1918, being replaced by kubeflow/kubeflow#1995.
Looking forward to your comments!
Design
Design goals
In the following, we describe the design considerations behind our proposed approach, and discuss alternatives.
ContainerOp
s.Working examples
Here are a few use cases that we used as representative examples when iterating on the proposed DSL extensions:
PersistentVolumeClaim
(PVC) object that they have already created.VolumeSnapshot
resources] in a vendor-neutral way.Basic Primitives
A Pipeline Volume: Instances of class
dsl.PipelineVolume
represent individual Persistent Volumes. They can be mounted byContainerOp
instances as volumes, at specific mount points, and used for data exchange among them. Users may ask Pipelines to create PVCs dynamically, or they can refer to data they had created in the past by mentioningPersistentVolumeClaims
that already exist on the cluster, or by specifying an existingVolumeSnapshot
K8s object as the data source for a new PVC to be created dynamically. Examples:vol_new = dsl.PipelineVolume(size="150G")
vol_existing = dsl.PipelineVolume(pvc="my-existing-data")
vol_from_snap = dsl.PipelineVolume(data_source="snapshot1")
Attributes:
pvc
: An existing PVC already filled with data, to use as a Persistent Volume for this pipeline.size
: The size of a new PVC to be created dynamically, as part of the pipeline.C.storage_class
: The storage class to use for the dynamically created PVC.data_source
: The name of an existingVolumeSnapshot
K8s object from which to clone data for a new dynamically created PVC, or a reference to adsl.PipelineVolumeSnapshot
instance.A Pipeline Volume Snapshot: Instances of class
dsl.PipelineVolumeSnapshot
represent individual Volume Snapshots, created by snapshotting instances ofdsl.PipelineVolume
. They can be used to create new instances ofdsl.PipelineVolume
(a clone operation), or directly as input volumes forContainerOp
instances, at which point an implicit clone operation takes place.Examples:
snap1 = dsl.PipelineVolumeSnapshot(vol1)
snap2 = vol2.snapshot()
Code - Iterations on proposed syntax
Looking forward to your feedback, and we will be following up with a PR shortly.
The text was updated successfully, but these errors were encountered: