Skip to content

Commit ff3e5e0

Browse files
committed
GenericEphemeralVolume: initial implementation
The implementation consists of - identifying all places where VolumeSource.PersistentVolumeClaim has a special meaning and then ensuring that the same code path is taken for an ephemeral volume, with the ownership check - adding a controller that produces the PVCs for each embedded VolumeSource.EphemeralVolume - relaxing the PVC protection controller such that it removes the finalizer already before the pod is deleted (only if the GenericEphemeralVolume feature is enabled): this is needed to break a cycle where foreground deletion of the pod blocks on removing the PVC, which waits for deletion of the pod The controller was derived from the endpointslices controller.
1 parent af91e76 commit ff3e5e0

File tree

25 files changed

+861
-72
lines changed

25 files changed

+861
-72
lines changed

cmd/kube-controller-manager/app/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ go_library(
7474
"//pkg/controller/ttl:go_default_library",
7575
"//pkg/controller/ttlafterfinished:go_default_library",
7676
"//pkg/controller/volume/attachdetach:go_default_library",
77+
"//pkg/controller/volume/ephemeral:go_default_library",
7778
"//pkg/controller/volume/expand:go_default_library",
7879
"//pkg/controller/volume/persistentvolume:go_default_library",
7980
"//pkg/controller/volume/persistentvolume/config:go_default_library",

cmd/kube-controller-manager/app/controllermanager.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,7 @@ func NewControllerInitializers(loopMode ControllerLoopMode) map[string]InitFunc
423423
controllers["pv-protection"] = startPVProtectionController
424424
controllers["ttl-after-finished"] = startTTLAfterFinishedController
425425
controllers["root-ca-cert-publisher"] = startRootCACertPublisher
426+
controllers["ephemeral-volume"] = startEphemeralVolumeController
426427

427428
return controllers
428429
}

cmd/kube-controller-manager/app/core.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ import (
5757
ttlcontroller "k8s.io/kubernetes/pkg/controller/ttl"
5858
"k8s.io/kubernetes/pkg/controller/ttlafterfinished"
5959
"k8s.io/kubernetes/pkg/controller/volume/attachdetach"
60+
"k8s.io/kubernetes/pkg/controller/volume/ephemeral"
6061
"k8s.io/kubernetes/pkg/controller/volume/expand"
6162
persistentvolumecontroller "k8s.io/kubernetes/pkg/controller/volume/persistentvolume"
6263
"k8s.io/kubernetes/pkg/controller/volume/pvcprotection"
@@ -373,6 +374,22 @@ func startVolumeExpandController(ctx ControllerContext) (http.Handler, bool, err
373374
return nil, false, nil
374375
}
375376

377+
func startEphemeralVolumeController(ctx ControllerContext) (http.Handler, bool, error) {
378+
if utilfeature.DefaultFeatureGate.Enabled(features.GenericEphemeralVolume) {
379+
ephemeralController, err := ephemeral.NewController(
380+
ctx.ClientBuilder.ClientOrDie("ephemeral-volume-controller"),
381+
ctx.InformerFactory.Core().V1().Pods(),
382+
ctx.InformerFactory.Core().V1().PersistentVolumeClaims())
383+
if err != nil {
384+
return nil, true, fmt.Errorf("failed to start ephemeral volume controller: %v", err)
385+
}
386+
// TODO (before beta at the latest): make this configurable similar to the EndpointController
387+
go ephemeralController.Run(1 /* int(ctx.ComponentConfig.EphemeralController.ConcurrentEphemeralVolumeSyncs) */, ctx.Stop)
388+
return nil, true, nil
389+
}
390+
return nil, false, nil
391+
}
392+
376393
func startEndpointController(ctx ControllerContext) (http.Handler, bool, error) {
377394
go endpointcontroller.NewEndpointController(
378395
ctx.InformerFactory.Core().V1().Pods(),
@@ -539,6 +556,7 @@ func startPVCProtectionController(ctx ControllerContext) (http.Handler, bool, er
539556
ctx.InformerFactory.Core().V1().Pods(),
540557
ctx.ClientBuilder.ClientOrDie("pvc-protection-controller"),
541558
utilfeature.DefaultFeatureGate.Enabled(features.StorageObjectInUseProtection),
559+
utilfeature.DefaultFeatureGate.Enabled(features.StorageObjectInUseProtection),
542560
)
543561
if err != nil {
544562
return nil, true, fmt.Errorf("failed to start the pvc protection controller: %v", err)

pkg/controller/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ filegroup(
136136
"//pkg/controller/util/node:all-srcs",
137137
"//pkg/controller/volume/attachdetach:all-srcs",
138138
"//pkg/controller/volume/common:all-srcs",
139+
"//pkg/controller/volume/ephemeral:all-srcs",
139140
"//pkg/controller/volume/events:all-srcs",
140141
"//pkg/controller/volume/expand:all-srcs",
141142
"//pkg/controller/volume/persistentvolume:all-srcs",

pkg/controller/volume/attachdetach/attach_detach_controller.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ func NewAttachDetachController(
203203

204204
// This custom indexer will index pods by its PVC keys. Then we don't need
205205
// to iterate all pods every time to find pods which reference given PVC.
206-
if err := common.AddIndexerIfNotPresent(adc.podIndexer, common.PodPVCIndex, common.PodPVCIndexFunc); err != nil {
206+
if err := common.AddPodPVCIndexerIfNotPresent(adc.podIndexer); err != nil {
207207
return nil, fmt.Errorf("Could not initialize attach detach controller: %v", err)
208208
}
209209

@@ -425,7 +425,7 @@ func (adc *attachDetachController) populateDesiredStateOfWorld() error {
425425
// The volume specs present in the ActualStateOfWorld are nil, let's replace those
426426
// with the correct ones found on pods. The present in the ASW with no corresponding
427427
// pod will be detached and the spec is irrelevant.
428-
volumeSpec, err := util.CreateVolumeSpec(podVolume, podToAdd.Namespace, nodeName, &adc.volumePluginMgr, adc.pvcLister, adc.pvLister, adc.csiMigratedPluginManager, adc.intreeToCSITranslator)
428+
volumeSpec, err := util.CreateVolumeSpec(podVolume, podToAdd, nodeName, &adc.volumePluginMgr, adc.pvcLister, adc.pvLister, adc.csiMigratedPluginManager, adc.intreeToCSITranslator)
429429
if err != nil {
430430
klog.Errorf(
431431
"Error creating spec for volume %q, pod %q/%q: %v",

pkg/controller/volume/attachdetach/metrics/metrics.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ func (collector *attachDetachStateCollector) getVolumeInUseCount() volumeCount {
168168
continue
169169
}
170170
for _, podVolume := range pod.Spec.Volumes {
171-
volumeSpec, err := util.CreateVolumeSpec(podVolume, pod.Namespace, types.NodeName(pod.Spec.NodeName), collector.volumePluginMgr, collector.pvcLister, collector.pvLister, collector.csiMigratedPluginManager, collector.intreeToCSITranslator)
171+
volumeSpec, err := util.CreateVolumeSpec(podVolume, pod, types.NodeName(pod.Spec.NodeName), collector.volumePluginMgr, collector.pvcLister, collector.pvLister, collector.csiMigratedPluginManager, collector.intreeToCSITranslator)
172172
if err != nil {
173173
continue
174174
}

pkg/controller/volume/attachdetach/util/util.go

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -39,57 +39,67 @@ import (
3939
// A volume.Spec that refers to an in-tree plugin spec is translated to refer
4040
// to a migrated CSI plugin spec if all conditions for CSI migration on a node
4141
// for the in-tree plugin is satisfied.
42-
func CreateVolumeSpec(podVolume v1.Volume, podNamespace string, nodeName types.NodeName, vpm *volume.VolumePluginMgr, pvcLister corelisters.PersistentVolumeClaimLister, pvLister corelisters.PersistentVolumeLister, csiMigratedPluginManager csimigration.PluginManager, csiTranslator csimigration.InTreeToCSITranslator) (*volume.Spec, error) {
42+
func CreateVolumeSpec(podVolume v1.Volume, pod *v1.Pod, nodeName types.NodeName, vpm *volume.VolumePluginMgr, pvcLister corelisters.PersistentVolumeClaimLister, pvLister corelisters.PersistentVolumeLister, csiMigratedPluginManager csimigration.PluginManager, csiTranslator csimigration.InTreeToCSITranslator) (*volume.Spec, error) {
43+
claimName := ""
44+
readOnly := false
4345
if pvcSource := podVolume.VolumeSource.PersistentVolumeClaim; pvcSource != nil {
46+
claimName = pvcSource.ClaimName
47+
readOnly = pvcSource.ReadOnly
48+
}
49+
if ephemeralSource := podVolume.VolumeSource.Ephemeral; ephemeralSource != nil && utilfeature.DefaultFeatureGate.Enabled(features.GenericEphemeralVolume) {
50+
claimName = pod.Name + "-" + podVolume.Name
51+
readOnly = ephemeralSource.ReadOnly
52+
}
53+
if claimName != "" {
4454
klog.V(10).Infof(
4555
"Found PVC, ClaimName: %q/%q",
46-
podNamespace,
47-
pvcSource.ClaimName)
56+
pod.Namespace,
57+
claimName)
4858

4959
// If podVolume is a PVC, fetch the real PV behind the claim
5060
pvName, pvcUID, err := getPVCFromCacheExtractPV(
51-
podNamespace, pvcSource.ClaimName, pvcLister)
61+
pod.Namespace, claimName, pvcLister)
5262
if err != nil {
5363
return nil, fmt.Errorf(
5464
"error processing PVC %q/%q: %v",
55-
podNamespace,
56-
pvcSource.ClaimName,
65+
pod.Namespace,
66+
claimName,
5767
err)
5868
}
5969

6070
klog.V(10).Infof(
6171
"Found bound PV for PVC (ClaimName %q/%q pvcUID %v): pvName=%q",
62-
podNamespace,
63-
pvcSource.ClaimName,
72+
pod.Namespace,
73+
claimName,
6474
pvcUID,
6575
pvName)
6676

6777
// Fetch actual PV object
6878
volumeSpec, err := getPVSpecFromCache(
69-
pvName, pvcSource.ReadOnly, pvcUID, pvLister)
79+
pvName, readOnly, pvcUID, pvLister)
7080
if err != nil {
7181
return nil, fmt.Errorf(
7282
"error processing PVC %q/%q: %v",
73-
podNamespace,
74-
pvcSource.ClaimName,
83+
pod.Namespace,
84+
claimName,
7585
err)
7686
}
7787

7888
volumeSpec, err = translateInTreeSpecToCSIIfNeeded(volumeSpec, nodeName, vpm, csiMigratedPluginManager, csiTranslator)
7989
if err != nil {
8090
return nil, fmt.Errorf(
8191
"error performing CSI migration checks and translation for PVC %q/%q: %v",
82-
podNamespace,
83-
pvcSource.ClaimName,
92+
pod.Namespace,
93+
claimName,
8494
err)
8595
}
8696

8797
klog.V(10).Infof(
8898
"Extracted volumeSpec (%v) from bound PV (pvName %q) and PVC (ClaimName %q/%q pvcUID %v)",
8999
volumeSpec.Name(),
90100
pvName,
91-
podNamespace,
92-
pvcSource.ClaimName,
101+
pod.Namespace,
102+
claimName,
93103
pvcUID)
94104

95105
return volumeSpec, nil
@@ -219,7 +229,7 @@ func ProcessPodVolumes(pod *v1.Pod, addVolumes bool, desiredStateOfWorld cache.D
219229

220230
// Process volume spec for each volume defined in pod
221231
for _, podVolume := range pod.Spec.Volumes {
222-
volumeSpec, err := CreateVolumeSpec(podVolume, pod.Namespace, nodeName, volumePluginMgr, pvcLister, pvLister, csiMigratedPluginManager, csiTranslator)
232+
volumeSpec, err := CreateVolumeSpec(podVolume, pod, nodeName, volumePluginMgr, pvcLister, pvLister, csiMigratedPluginManager, csiTranslator)
223233
if err != nil {
224234
klog.V(10).Infof(
225235
"Error processing volume %q for pod %q/%q: %v",

pkg/controller/volume/common/BUILD

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ go_library(
66
importpath = "k8s.io/kubernetes/pkg/controller/volume/common",
77
visibility = ["//visibility:public"],
88
deps = [
9+
"//pkg/features:go_default_library",
910
"//staging/src/k8s.io/api/core/v1:go_default_library",
11+
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
1012
"//staging/src/k8s.io/client-go/tools/cache:go_default_library",
1113
],
1214
)

pkg/controller/volume/common/common.go

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,27 +20,46 @@ import (
2020
"fmt"
2121

2222
v1 "k8s.io/api/core/v1"
23+
utilfeature "k8s.io/apiserver/pkg/util/feature"
2324
"k8s.io/client-go/tools/cache"
25+
"k8s.io/kubernetes/pkg/features"
2426
)
2527

2628
const (
2729
// PodPVCIndex is the lookup name for the index function, which is to index by pod pvcs.
2830
PodPVCIndex = "pod-pvc-index"
2931
)
3032

31-
// PodPVCIndexFunc returns PVC keys for given pod
32-
func PodPVCIndexFunc(obj interface{}) ([]string, error) {
33-
pod, ok := obj.(*v1.Pod)
34-
if !ok {
35-
return []string{}, nil
36-
}
37-
keys := []string{}
38-
for _, podVolume := range pod.Spec.Volumes {
39-
if pvcSource := podVolume.VolumeSource.PersistentVolumeClaim; pvcSource != nil {
40-
keys = append(keys, fmt.Sprintf("%s/%s", pod.Namespace, pvcSource.ClaimName))
33+
// PodPVCIndexFunc creates an index function that returns PVC keys (=
34+
// namespace/name) for given pod. If enabled, this includes the PVCs
35+
// that might be created for generic ephemeral volumes.
36+
func PodPVCIndexFunc(genericEphemeralVolumeFeatureEnabled bool) func(obj interface{}) ([]string, error) {
37+
return func(obj interface{}) ([]string, error) {
38+
pod, ok := obj.(*v1.Pod)
39+
if !ok {
40+
return []string{}, nil
41+
}
42+
keys := []string{}
43+
for _, podVolume := range pod.Spec.Volumes {
44+
claimName := ""
45+
if pvcSource := podVolume.VolumeSource.PersistentVolumeClaim; pvcSource != nil {
46+
claimName = pvcSource.ClaimName
47+
}
48+
if ephemeralSource := podVolume.VolumeSource.Ephemeral; genericEphemeralVolumeFeatureEnabled && ephemeralSource != nil {
49+
claimName = pod.Name + "-" + podVolume.Name
50+
}
51+
if claimName != "" {
52+
keys = append(keys, fmt.Sprintf("%s/%s", pod.Namespace, claimName))
53+
}
4154
}
55+
return keys, nil
4256
}
43-
return keys, nil
57+
}
58+
59+
// AddPodPVCIndexerIfNotPresent adds the PodPVCIndexFunc with the current global setting for GenericEphemeralVolume.
60+
func AddPodPVCIndexerIfNotPresent(indexer cache.Indexer) error {
61+
return AddIndexerIfNotPresent(indexer, PodPVCIndex,
62+
PodPVCIndexFunc(utilfeature.DefaultFeatureGate.Enabled(features.GenericEphemeralVolume)))
4463
}
4564

4665
// AddIndexerIfNotPresent adds the index function with the name into the cache indexer if not present
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
2+
3+
go_library(
4+
name = "go_default_library",
5+
srcs = [
6+
"controller.go",
7+
"doc.go",
8+
],
9+
importpath = "k8s.io/kubernetes/pkg/controller/volume/ephemeral",
10+
visibility = ["//visibility:public"],
11+
deps = [
12+
"//pkg/controller/volume/common:go_default_library",
13+
"//pkg/controller/volume/events:go_default_library",
14+
"//pkg/volume/util:go_default_library",
15+
"//staging/src/k8s.io/api/core/v1:go_default_library",
16+
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
17+
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
18+
"//staging/src/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
19+
"//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library",
20+
"//staging/src/k8s.io/client-go/informers/core/v1:go_default_library",
21+
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
22+
"//staging/src/k8s.io/client-go/kubernetes/scheme:go_default_library",
23+
"//staging/src/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library",
24+
"//staging/src/k8s.io/client-go/listers/core/v1:go_default_library",
25+
"//staging/src/k8s.io/client-go/tools/cache:go_default_library",
26+
"//staging/src/k8s.io/client-go/tools/record:go_default_library",
27+
"//staging/src/k8s.io/client-go/util/workqueue:go_default_library",
28+
"//vendor/k8s.io/klog/v2:go_default_library",
29+
],
30+
)
31+
32+
go_test(
33+
name = "go_default_test",
34+
srcs = ["controller_test.go"],
35+
embed = [":go_default_library"],
36+
deps = [
37+
"//pkg/controller:go_default_library",
38+
"//staging/src/k8s.io/api/core/v1:go_default_library",
39+
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
40+
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
41+
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
42+
"//staging/src/k8s.io/client-go/informers:go_default_library",
43+
"//staging/src/k8s.io/client-go/kubernetes/fake:go_default_library",
44+
"//staging/src/k8s.io/client-go/tools/cache:go_default_library",
45+
"//vendor/github.com/stretchr/testify/assert:go_default_library",
46+
"//vendor/k8s.io/klog/v2:go_default_library",
47+
],
48+
)
49+
50+
filegroup(
51+
name = "package-srcs",
52+
srcs = glob(["**"]),
53+
tags = ["automanaged"],
54+
visibility = ["//visibility:private"],
55+
)
56+
57+
filegroup(
58+
name = "all-srcs",
59+
srcs = [":package-srcs"],
60+
tags = ["automanaged"],
61+
visibility = ["//visibility:public"],
62+
)

0 commit comments

Comments
 (0)