Skip to content

Commit

Permalink
Add volumeClaimTemplate as a Workspace volume source
Browse files Browse the repository at this point in the history
An existing PersistentVolumeClaim can currently be used as a Workspace
volume source. There is two ways of using an existing PVC as volume:

 - Reuse an existing PVC
 - Create a new PVC before each PipelineRun.

There is disadvantages by reusing the same PVC for every PipelineRun:

 - You need to clean the PVC at the end of the Pipeline
 - All Tasks using the workspace will be scheduled to the node where
   the PV is bound
 - Concurrent PipelineRuns may interfere, an artifact or file from one
   PipelineRun may slip in to or be used in another PipelineRun, with
   very few audit tracks.

There is also disadvantages by creating a new PVC before each PipelineRun:

 - This can not (easily) be done declaratively
 - This is hard to do programmatically, because it is hard to know when
   to delete the PVC. The PipelineRun can not be set as OwnerReference since
   the PVC must be created first

 This commit adds 'volumeClaimTemplate' as a volume source for workspaces. This
 has several advantages:

 - The syntax is used in k8s StatefulSet and other k8s projects so it is
   familiar in the kubernetes ecosystem
 - It is possible to declaratively declare that a PVC should be created for each
   PipelineRun, e.g. from a TriggerTemplate.
 - The user can choose storageClass (or omit to get the cluster default) to e.g.
   get a faster SSD volume, or to get a volume compatible with e.g. Windows.
 - The user can adapt the size to the job, e.g. use 5Gi for apps that contains
   machine learning models, or 1Gi for microservice apps. It can be changed on
   demand in a configuration that lives in the users namespace e.g. in a
   TriggerTemplate.
 - The size affects the storage quota that is set on the namespace and it may affect
   billing and cost depending on the cluster environment.
 - The PipelineRun or TaskRun with the template is created first, and is used
   as OwnerReference on the PVC. That means that the PVC will have the same lifecycle
   as the PipelineRun.

 Related to #1986

 See also:
  - #2174
  - #2218
  - tektoncd/triggers#476
  - tektoncd/triggers#482
  - kubeflow/kfp-tekton#51
  • Loading branch information
jlpettersson committed Apr 9, 2020
1 parent 0b3a8b1 commit c7ce3da
Show file tree
Hide file tree
Showing 26 changed files with 678 additions and 6 deletions.
12 changes: 9 additions & 3 deletions docs/workspaces.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@

`Workspaces` allow `Tasks` to declare parts of the filesystem that need to be provided
at runtime by `TaskRuns`. A `TaskRun` can make these parts of the filesystem available
in many ways: using a read-only `ConfigMap` or `Secret`, a `PersistentVolumeClaim`
shared with other Tasks, or simply an `emptyDir` that is discarded when the `TaskRun`
in many ways: using a read-only `ConfigMap` or `Secret`, an existing `PersistentVolumeClaim`
shared with other Tasks, create a `PersistentVolumeClaim` from a provided `VolumeClaimTemplate`, or simply an `emptyDir` that is discarded when the `TaskRun`
completes.

`Workspaces` are similar to `Volumes` except that they allow a `Task` author
Expand Down Expand Up @@ -294,9 +294,15 @@ However, they work well for single `TaskRuns` where the data stored in the `empt

#### `persistentVolumeClaim`

The `persistentVolumeClaim` field references a [`persistentVolumeClaim` volume](https://kubernetes.io/docs/concepts/storage/volumes/#persistentvolumeclaim).
The `persistentVolumeClaim` field references an existing [`persistentVolumeClaim` volume](https://kubernetes.io/docs/concepts/storage/volumes/#persistentvolumeclaim).
`PersistentVolumeClaim` volumes are a good choice for sharing data among `Tasks` within a `Pipeline`.

#### `volumeClaimTemplate`

The `volumeClaimTemplate` is a template of a [`persistentVolumeClaim` volume](https://kubernetes.io/docs/concepts/storage/volumes/#persistentvolumeclaim), created for each `PipelineRun` or `TaskRun`.
When the volume is created from a template in a `PipelineRun` or `TaskRun` it will be deleted when the `PipelineRun` or `TaskRun` is deleted.
`volumeClaimTemplate` volumes are a good choice for sharing data among `Tasks` within a `Pipeline` when the volume is only used during a `PipelineRun` or `TaskRun`.

#### `configMap`

The `configMap` field references a [`configMap` volume](https://kubernetes.io/docs/concepts/storage/volumes/#configmap).
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
name: volume-from-template
spec:
tasks:
- name: writer
taskSpec:
steps:
- name: write
image: ubuntu
script: echo bar > $(workspaces.task-ws.path)/foo
workspaces:
- name: task-ws
workspaces:
- name: task-ws
workspace: ws
- name: reader
runAfter:
- writer
taskSpec:
steps:
- name: read
image: ubuntu
script: cat $(workspaces.myws.path)/foo | grep bar
workspaces:
- name: myws
workspaces:
- name: myws
workspace: ws
workspaces:
- name: ws
---
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
generateName: run-with-template-
spec:
pipelineRef:
name: volume-from-template
workspaces:
- name: ws
volumeClaimTemplate:
metadata:
name: mypvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
11 changes: 11 additions & 0 deletions pkg/apis/pipeline/v1alpha1/pipelinerun_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,3 +208,14 @@ func (pr *PipelineRun) GetServiceAccountName(pipelineTaskName string) string {
}
return serviceAccountName
}

// HasVolumeClaimTemplate returns true if PipelineRun contains volumeClaimTemplates that is
// used for creating PersistentVolumeClaims with an OwnerReference for each run
func (pr *PipelineRun) HasVolumeClaimTemplate() bool {
for _, ws := range pr.Spec.Workspaces {
if ws.VolumeClaimTemplate != nil {
return true
}
}
return false
}
19 changes: 19 additions & 0 deletions pkg/apis/pipeline/v1alpha1/pipelinerun_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,25 @@ func TestPipelineRunIsCancelled(t *testing.T) {
}
}

func TestPipelineRunHasVolumeClaimTemplate(t *testing.T) {
pr := &v1alpha1.PipelineRun{
Spec: v1alpha1.PipelineRunSpec{
Workspaces: []v1alpha1.WorkspaceBinding{{
Name: "my-workspace",
VolumeClaimTemplate: &corev1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "pvc",
},
Spec: corev1.PersistentVolumeClaimSpec{},
},
}},
},
}
if !pr.HasVolumeClaimTemplate() {
t.Fatal("Expected pipelinerun to have a volumeClaimTemplate workspace")
}
}

func TestPipelineRunKey(t *testing.T) {
pr := tb.PipelineRun("prunname", "testns")
expectedKey := fmt.Sprintf("PipelineRun/%p", pr)
Expand Down
25 changes: 25 additions & 0 deletions pkg/apis/pipeline/v1alpha1/taskrun_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,18 @@ import (
"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"knative.dev/pkg/apis"
)

var (
taskRunGroupVersionKind = schema.GroupVersionKind{
Group: SchemeGroupVersion.Group,
Version: SchemeGroupVersion.Version,
Kind: pipeline.TaskRunControllerName,
}
)

// TaskRunSpec defines the desired state of TaskRun
type TaskRunSpec struct {
// +optional
Expand Down Expand Up @@ -165,6 +174,11 @@ func (tr *TaskRun) GetBuildPodRef() corev1.ObjectReference {
}
}

// GetOwnerReference gets the task run as owner reference for any related objects
func (tr *TaskRun) GetOwnerReference() metav1.OwnerReference {
return *metav1.NewControllerRef(tr, taskRunGroupVersionKind)
}

// GetPipelineRunPVCName for taskrun gets pipelinerun
func (tr *TaskRun) GetPipelineRunPVCName() string {
if tr == nil {
Expand Down Expand Up @@ -228,3 +242,14 @@ func (tr *TaskRun) IsPartOfPipeline() (bool, string, string) {

return false, "", ""
}

// HasVolumeClaimTemplate returns true if TaskRun contains volumeClaimTemplates that is
// used for creating PersistentVolumeClaims with an OwnerReference for each run
func (tr *TaskRun) HasVolumeClaimTemplate() bool {
for _, ws := range tr.Spec.Workspaces {
if ws.VolumeClaimTemplate != nil {
return true
}
}
return false
}
19 changes: 19 additions & 0 deletions pkg/apis/pipeline/v1alpha1/taskrun_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,25 @@ func TestTaskRunIsCancelled(t *testing.T) {
}
}

func TestTaskRunHasVolumeClaimTemplate(t *testing.T) {
tr := &v1alpha1.TaskRun{
Spec: v1alpha1.TaskRunSpec{
Workspaces: []v1alpha1.WorkspaceBinding{{
Name: "my-workspace",
VolumeClaimTemplate: &corev1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "pvc",
},
Spec: corev1.PersistentVolumeClaimSpec{},
},
}},
},
}
if !tr.HasVolumeClaimTemplate() {
t.Fatal("Expected taskrun to have a volumeClaimTemplate workspace")
}
}

func TestTaskRunKey(t *testing.T) {
tr := tb.TaskRun("taskrunname", "")
expectedKey := fmt.Sprintf("TaskRun/%p", tr)
Expand Down
11 changes: 11 additions & 0 deletions pkg/apis/pipeline/v1beta1/pipelinerun_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,17 @@ func (pr *PipelineRun) GetServiceAccountName(pipelineTaskName string) string {
return serviceAccountName
}

// HasVolumeClaimTemplate returns true if PipelineRun contains volumeClaimTemplates that is
// used for creating PersistentVolumeClaims with an OwnerReference for each run
func (pr *PipelineRun) HasVolumeClaimTemplate() bool {
for _, ws := range pr.Spec.Workspaces {
if ws.VolumeClaimTemplate != nil {
return true
}
}
return false
}

// PipelineRunSpec defines the desired state of PipelineRun
type PipelineRunSpec struct {
// +optional
Expand Down
19 changes: 19 additions & 0 deletions pkg/apis/pipeline/v1beta1/pipelinerun_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,25 @@ func TestPipelineRunIsCancelled(t *testing.T) {
}
}

func TestPipelineRunHasVolumeClaimTemplate(t *testing.T) {
pr := &v1beta1.PipelineRun{
Spec: v1beta1.PipelineRunSpec{
Workspaces: []v1beta1.WorkspaceBinding{{
Name: "my-workspace",
VolumeClaimTemplate: &corev1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "pvc",
},
Spec: corev1.PersistentVolumeClaimSpec{},
},
}},
},
}
if !pr.HasVolumeClaimTemplate() {
t.Fatal("Expected pipelinerun to have a volumeClaimTemplate workspace")
}
}

func TestPipelineRunKey(t *testing.T) {
pr := tb.PipelineRun("prunname", "testns")
expectedKey := fmt.Sprintf("PipelineRun/%p", pr)
Expand Down
25 changes: 25 additions & 0 deletions pkg/apis/pipeline/v1beta1/taskrun_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,19 @@ import (
"github.com/tektoncd/pipeline/pkg/apis/pipeline"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"knative.dev/pkg/apis"
duckv1beta1 "knative.dev/pkg/apis/duck/v1beta1"
)

var (
taskRunGroupVersionKind = schema.GroupVersionKind{
Group: SchemeGroupVersion.Group,
Version: SchemeGroupVersion.Version,
Kind: pipeline.TaskRunControllerName,
}
)

// TaskRunSpec defines the desired state of TaskRun
type TaskRunSpec struct {
// +optional
Expand Down Expand Up @@ -163,6 +172,11 @@ type TaskRunResult struct {
Value string `json:"value"`
}

// GetOwnerReference gets the task run as owner reference for any related objects
func (tr *TaskRun) GetOwnerReference() metav1.OwnerReference {
return *metav1.NewControllerRef(tr, taskRunGroupVersionKind)
}

// GetCondition returns the Condition matching the given type.
func (trs *TaskRunStatus) GetCondition(t apis.ConditionType) *apis.Condition {
return taskRunCondSet.Manage(trs).GetCondition(t)
Expand Down Expand Up @@ -338,3 +352,14 @@ func (tr *TaskRun) IsPartOfPipeline() (bool, string, string) {

return false, "", ""
}

// HasVolumeClaimTemplate returns true if TaskRun contains volumeClaimTemplates that is
// used for creating PersistentVolumeClaims with an OwnerReference for each run
func (tr *TaskRun) HasVolumeClaimTemplate() bool {
for _, ws := range tr.Spec.Workspaces {
if ws.VolumeClaimTemplate != nil {
return true
}
}
return false
}
19 changes: 19 additions & 0 deletions pkg/apis/pipeline/v1beta1/taskrun_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,25 @@ func TestTaskRunIsCancelled(t *testing.T) {
}
}

func TestTaskRunHasVolumeClaimTemplate(t *testing.T) {
tr := &v1beta1.TaskRun{
Spec: v1beta1.TaskRunSpec{
Workspaces: []v1beta1.WorkspaceBinding{{
Name: "my-workspace",
VolumeClaimTemplate: &corev1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "pvc",
},
Spec: corev1.PersistentVolumeClaimSpec{},
},
}},
},
}
if !tr.HasVolumeClaimTemplate() {
t.Fatal("Expected taskrun to have a volumeClaimTemplate workspace")
}
}

func TestTaskRunKey(t *testing.T) {
tr := &v1beta1.TaskRun{
ObjectMeta: metav1.ObjectMeta{
Expand Down
4 changes: 4 additions & 0 deletions pkg/apis/pipeline/v1beta1/workspace_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ type WorkspaceBinding struct {
// for this binding (i.e. the volume will be mounted at this sub directory).
// +optional
SubPath string `json:"subPath,omitempty"`
// VolumeClaimTemplate is a template for a claim that will be created in the same namespace.
// The PipelineRun controller is responsible for creating a unique claim for each instance of PipelineRun.
// +optional
VolumeClaimTemplate *corev1.PersistentVolumeClaim `json:"volumeClaimTemplate,omitempty"`
// PersistentVolumeClaimVolumeSource represents a reference to a
// PersistentVolumeClaim in the same namespace. Either this OR EmptyDir can be used.
// +optional
Expand Down
4 changes: 4 additions & 0 deletions pkg/apis/pipeline/v1beta1/workspace_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
// WorkspaceBinding may include.
var allVolumeSourceFields []string = []string{
"workspace.persistentvolumeclaim",
"workspace.volumeclaimtemplate",
"workspace.emptydir",
"workspace.configmap",
"workspace.secret",
Expand Down Expand Up @@ -72,6 +73,9 @@ func (b *WorkspaceBinding) Validate(ctx context.Context) *apis.FieldError {
// has been configured with.
func (b *WorkspaceBinding) numSources() int {
n := 0
if b.VolumeClaimTemplate != nil {
n++
}
if b.PersistentVolumeClaim != nil {
n++
}
Expand Down
20 changes: 20 additions & 0 deletions pkg/apis/pipeline/v1beta1/workspace_validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import (
"testing"

corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func TestWorkspaceBindingValidateValid(t *testing.T) {
Expand All @@ -35,6 +37,24 @@ func TestWorkspaceBindingValidateValid(t *testing.T) {
ClaimName: "pool-party",
},
},
}, {
name: "Valid volumeClaimTemplate",
binding: &WorkspaceBinding{
Name: "beth",
VolumeClaimTemplate: &corev1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "mypvc",
},
Spec: corev1.PersistentVolumeClaimSpec{
AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce},
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
"storage": resource.MustParse("1Gi"),
},
},
},
},
},
}, {
name: "Valid emptyDir",
binding: &WorkspaceBinding{
Expand Down
5 changes: 5 additions & 0 deletions pkg/apis/pipeline/v1beta1/zz_generated.deepcopy.go

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

Loading

0 comments on commit c7ce3da

Please sign in to comment.