Add emptyDir volume support to kube play

When a kube yaml has a volume set as empty dir, podman
will create an anonymous volume with the empty dir name and
attach it to the containers running in the pod. When the pod
is removed, the empy dir volume created is also removed.

Add tests and docs for this as well.

Signed-off-by: Urvashi Mohnani <umohnani@redhat.com>
This commit is contained in:
Urvashi Mohnani
2022-07-24 19:44:41 -04:00
parent 57441b4c71
commit 98169c20dd
11 changed files with 98 additions and 15 deletions

View File

@ -21,7 +21,7 @@ Currently, the supported Kubernetes kinds are:
`Kubernetes Pods or Deployments`
Only two volume types are supported by kube play, the *hostPath* and *persistentVolumeClaim* volume types. For the *hostPath* volume type, only the *default (empty)*, *DirectoryOrCreate*, *Directory*, *FileOrCreate*, *File*, *Socket*, *CharDevice* and *BlockDevice* subtypes are supported. Podman interprets the value of *hostPath* *path* as a file path when it contains at least one forward slash, otherwise Podman treats the value as the name of a named volume. When using a *persistentVolumeClaim*, the value for *claimName* is the name for the Podman named volume.
Only three volume types are supported by kube play, the *hostPath*, *emptyDir*, and *persistentVolumeClaim* volume types. For the *hostPath* volume type, only the *default (empty)*, *DirectoryOrCreate*, *Directory*, *FileOrCreate*, *File*, *Socket*, *CharDevice* and *BlockDevice* subtypes are supported. Podman interprets the value of *hostPath* *path* as a file path when it contains at least one forward slash, otherwise Podman treats the value as the name of a named volume. When using a *persistentVolumeClaim*, the value for *claimName* is the name for the Podman named volume. When using an *emptyDir* volume, podman creates an anonymous volume that is attached the containers running inside the pod and is deleted once the pod is removed.
Note: When playing a kube YAML with init containers, the init container will be created with init type value `once`. To change the default type, use the `io.podman.annotations.init.container.type` annotation to set the type to `always`.

View File

@ -237,6 +237,9 @@ type ContainerNamedVolume struct {
Dest string `json:"dest"`
// Options are fstab style mount options
Options []string `json:"options,omitempty"`
// IsAnonymous sets the named volume as anonymous even if it has a name
// This is used for emptyDir volumes from a kube yaml
IsAnonymous bool `json:"setAnonymous,omitempty"`
}
// ContainerOverlayVolume is a overlay volume that will be mounted into the

View File

@ -1413,9 +1413,10 @@ func WithNamedVolumes(volumes []*ContainerNamedVolume) CtrCreateOption {
}
ctr.config.NamedVolumes = append(ctr.config.NamedVolumes, &ContainerNamedVolume{
Name: vol.Name,
Dest: vol.Dest,
Options: mountOpts,
Name: vol.Name,
Dest: vol.Dest,
Options: mountOpts,
IsAnonymous: vol.IsAnonymous,
})
}

View File

@ -474,6 +474,11 @@ func (r *Runtime) setupContainer(ctx context.Context, ctr *Container) (_ *Contai
return nil, fmt.Errorf("error retrieving named volume %s for new container: %w", vol.Name, err)
}
}
if vol.IsAnonymous {
// If SetAnonymous is true, make this an anonymous volume
// this is needed for emptyDir volumes from kube yamls
isAnonymous = true
}
logrus.Debugf("Creating new volume %s for container", vol.Name)
@ -814,11 +819,11 @@ func (r *Runtime) removeContainer(ctx context.Context, c *Container, force, remo
// Ignore error, since podman will report original error
volumesFrom, _ := c.volumesFrom()
if len(volumesFrom) > 0 {
logrus.Debugf("Cleaning up volume not possible since volume is in use (%s)", v)
logrus.Debugf("Cleaning up volume not possible since volume is in use (%s)", v.Name)
continue
}
}
logrus.Errorf("Cleaning up volume (%s): %v", v, err)
logrus.Errorf("Cleaning up volume (%s): %v", v.Name, err)
}
}
}
@ -968,7 +973,7 @@ func (r *Runtime) evictContainer(ctx context.Context, idOrName string, removeVol
continue
}
if err := r.removeVolume(ctx, volume, false, timeout, false); err != nil && err != define.ErrNoSuchVolume && err != define.ErrVolumeBeingUsed {
logrus.Errorf("Cleaning up volume (%s): %v", v, err)
logrus.Errorf("Cleaning up volume (%s): %v", v.Name, err)
}
}
}

View File

@ -436,7 +436,7 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY
}
// Go through the volumes and create a podman volume for all volumes that have been
// defined by a configmap
// defined by a configmap or secret
for _, v := range volumes {
if (v.Type == kube.KubeVolumeTypeConfigMap || v.Type == kube.KubeVolumeTypeSecret) && !v.Optional {
vol, err := ic.Libpod.NewVolume(ctx, libpod.WithVolumeName(v.Source))

View File

@ -58,6 +58,10 @@ type VolumeSource struct {
ConfigMap *ConfigMapVolumeSource `json:"configMap,omitempty"`
// Secret represents a secret that should be mounted as a volume
Secret *SecretVolumeSource `json:"secret,omitempty"`
// emptyDir represents a temporary directory that shares a pod's lifetime.
// More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir
// +optional
EmptyDir *EmptyDirVolumeSource `json:"emptyDir,omitempty"`
}
// PersistentVolumeClaimVolumeSource references the user's PVC in the same namespace.

View File

@ -387,9 +387,10 @@ func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator, pod *l
var vols []*libpod.ContainerNamedVolume
for _, v := range volumes {
vols = append(vols, &libpod.ContainerNamedVolume{
Name: v.Name,
Dest: v.Dest,
Options: v.Options,
Name: v.Name,
Dest: v.Dest,
Options: v.Options,
IsAnonymous: v.IsAnonymous,
})
}
options = append(options, libpod.WithNamedVolumes(vols))

View File

@ -406,8 +406,15 @@ func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGener
Name: volumeSource.Source,
Options: options,
}
s.Volumes = append(s.Volumes, &secretVolume)
case KubeVolumeTypeEmptyDir:
emptyDirVolume := specgen.NamedVolume{
Dest: volume.MountPath,
Name: volumeSource.Source,
Options: options,
IsAnonymous: true,
}
s.Volumes = append(s.Volumes, &emptyDirVolume)
default:
return nil, errors.New("unsupported volume source type")
}

View File

@ -32,6 +32,7 @@ const (
KubeVolumeTypeBlockDevice
KubeVolumeTypeCharDevice
KubeVolumeTypeSecret
KubeVolumeTypeEmptyDir
)
//nolint:revive
@ -219,8 +220,13 @@ func VolumeFromConfigMap(configMapVolumeSource *v1.ConfigMapVolumeSource, config
return kv, nil
}
// Create a kubeVolume for an emptyDir volume
func VolumeFromEmptyDir(emptyDirVolumeSource *v1.EmptyDirVolumeSource, name string) (*KubeVolume, error) {
return &KubeVolume{Type: KubeVolumeTypeEmptyDir, Source: name}, nil
}
// Create a KubeVolume from one of the supported VolumeSource
func VolumeFromSource(volumeSource v1.VolumeSource, configMaps []v1.ConfigMap, secretsManager *secrets.SecretsManager) (*KubeVolume, error) {
func VolumeFromSource(volumeSource v1.VolumeSource, configMaps []v1.ConfigMap, secretsManager *secrets.SecretsManager, volName string) (*KubeVolume, error) {
switch {
case volumeSource.HostPath != nil:
return VolumeFromHostPath(volumeSource.HostPath)
@ -230,8 +236,10 @@ func VolumeFromSource(volumeSource v1.VolumeSource, configMaps []v1.ConfigMap, s
return VolumeFromConfigMap(volumeSource.ConfigMap, configMaps)
case volumeSource.Secret != nil:
return VolumeFromSecret(volumeSource.Secret, secretsManager)
case volumeSource.EmptyDir != nil:
return VolumeFromEmptyDir(volumeSource.EmptyDir, volName)
default:
return nil, errors.New("HostPath, ConfigMap, and PersistentVolumeClaim are currently the only supported VolumeSource")
return nil, errors.New("HostPath, ConfigMap, EmptyDir, and PersistentVolumeClaim are currently the only supported VolumeSource")
}
}
@ -240,7 +248,7 @@ func InitializeVolumes(specVolumes []v1.Volume, configMaps []v1.ConfigMap, secre
volumes := make(map[string]*KubeVolume)
for _, specVolume := range specVolumes {
volume, err := VolumeFromSource(specVolume.VolumeSource, configMaps, secretsManager)
volume, err := VolumeFromSource(specVolume.VolumeSource, configMaps, secretsManager, specVolume.Name)
if err != nil {
return nil, fmt.Errorf("failed to create volume %q: %w", specVolume.Name, err)
}

View File

@ -23,6 +23,9 @@ type NamedVolume struct {
Dest string
// Options are options that the named volume will be mounted with.
Options []string
// IsAnonymous sets the named volume as anonymous even if it has a name
// This is used for emptyDir volumes from a kube yaml
IsAnonymous bool
}
// OverlayVolume holds information about a overlay volume that will be mounted into

View File

@ -509,6 +509,9 @@ spec:
volumes:
{{ range . }}
- name: {{ .Name }}
{{- if (eq .VolumeType "EmptyDir") }}
emptyDir: {}
{{- end }}
{{- if (eq .VolumeType "HostPath") }}
hostPath:
path: {{ .HostPath.Path }}
@ -1242,12 +1245,15 @@ type ConfigMap struct {
Optional bool
}
type EmptyDir struct{}
type Volume struct {
VolumeType string
Name string
HostPath
PersistentVolumeClaim
ConfigMap
EmptyDir
}
// getHostPathVolume takes a type and a location for a HostPath
@ -1289,6 +1295,14 @@ func getConfigMapVolume(vName string, items []map[string]string, optional bool)
}
}
func getEmptyDirVolume() *Volume {
return &Volume{
VolumeType: "EmptyDir",
Name: defaultVolName,
EmptyDir: EmptyDir{},
}
}
type Env struct {
Name string
Value string
@ -2762,6 +2776,43 @@ VOLUME %s`, ALPINE, hostPathDir+"/")
Expect(kube).Should(Exit(0))
})
It("podman play kube with emptyDir volume", func() {
podName := "test-pod"
ctrName1 := "vol-test-ctr"
ctrName2 := "vol-test-ctr-2"
ctr1 := getCtr(withVolumeMount("/test-emptydir", false), withImage(BB), withName(ctrName1))
ctr2 := getCtr(withVolumeMount("/test-emptydir-2", false), withImage(BB), withName(ctrName2))
pod := getPod(withPodName(podName), withVolume(getEmptyDirVolume()), withCtr(ctr1), withCtr(ctr2))
err = generateKubeYaml("pod", pod, kubeYaml)
Expect(err).To(BeNil())
kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
kube.WaitWithDefaultTimeout()
Expect(kube).Should(Exit(0))
emptyDirCheck1 := podmanTest.Podman([]string{"exec", podName + "-" + ctrName1, "ls", "/test-emptydir"})
emptyDirCheck1.WaitWithDefaultTimeout()
Expect(emptyDirCheck1).Should(Exit(0))
emptyDirCheck2 := podmanTest.Podman([]string{"exec", podName + "-" + ctrName2, "ls", "/test-emptydir-2"})
emptyDirCheck2.WaitWithDefaultTimeout()
Expect(emptyDirCheck2).Should(Exit(0))
volList1 := podmanTest.Podman([]string{"volume", "ls", "-q"})
volList1.WaitWithDefaultTimeout()
Expect(volList1).Should(Exit(0))
Expect(volList1.OutputToString()).To(Equal(defaultVolName))
remove := podmanTest.Podman([]string{"pod", "rm", "-f", podName})
remove.WaitWithDefaultTimeout()
Expect(remove).Should(Exit(0))
volList2 := podmanTest.Podman([]string{"volume", "ls", "-q"})
volList2.WaitWithDefaultTimeout()
Expect(volList2).Should(Exit(0))
Expect(volList2.OutputToString()).To(Equal(""))
})
It("podman play kube applies labels to pods", func() {
var numReplicas int32 = 5
expectedLabelKey := "key1"