Merge pull request #15473 from umohnani8/empty-dir
Add emptyDir volume support to kube play
This commit is contained in:
commit
8266dbe7a9
|
@ -21,7 +21,7 @@ Currently, the supported Kubernetes kinds are:
|
||||||
|
|
||||||
`Kubernetes Pods or Deployments`
|
`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`.
|
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`.
|
||||||
|
|
||||||
|
|
|
@ -237,6 +237,9 @@ type ContainerNamedVolume struct {
|
||||||
Dest string `json:"dest"`
|
Dest string `json:"dest"`
|
||||||
// Options are fstab style mount options
|
// Options are fstab style mount options
|
||||||
Options []string `json:"options,omitempty"`
|
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
|
// ContainerOverlayVolume is a overlay volume that will be mounted into the
|
||||||
|
|
|
@ -1416,6 +1416,7 @@ func WithNamedVolumes(volumes []*ContainerNamedVolume) CtrCreateOption {
|
||||||
Name: vol.Name,
|
Name: vol.Name,
|
||||||
Dest: vol.Dest,
|
Dest: vol.Dest,
|
||||||
Options: mountOpts,
|
Options: mountOpts,
|
||||||
|
IsAnonymous: vol.IsAnonymous,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
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)
|
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
|
// Ignore error, since podman will report original error
|
||||||
volumesFrom, _ := c.volumesFrom()
|
volumesFrom, _ := c.volumesFrom()
|
||||||
if len(volumesFrom) > 0 {
|
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
|
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
|
continue
|
||||||
}
|
}
|
||||||
if err := r.removeVolume(ctx, volume, false, timeout, false); err != nil && err != define.ErrNoSuchVolume && err != define.ErrVolumeBeingUsed {
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
// 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 {
|
for _, v := range volumes {
|
||||||
if (v.Type == kube.KubeVolumeTypeConfigMap || v.Type == kube.KubeVolumeTypeSecret) && !v.Optional {
|
if (v.Type == kube.KubeVolumeTypeConfigMap || v.Type == kube.KubeVolumeTypeSecret) && !v.Optional {
|
||||||
vol, err := ic.Libpod.NewVolume(ctx, libpod.WithVolumeName(v.Source))
|
vol, err := ic.Libpod.NewVolume(ctx, libpod.WithVolumeName(v.Source))
|
||||||
|
|
|
@ -58,6 +58,10 @@ type VolumeSource struct {
|
||||||
ConfigMap *ConfigMapVolumeSource `json:"configMap,omitempty"`
|
ConfigMap *ConfigMapVolumeSource `json:"configMap,omitempty"`
|
||||||
// Secret represents a secret that should be mounted as a volume
|
// Secret represents a secret that should be mounted as a volume
|
||||||
Secret *SecretVolumeSource `json:"secret,omitempty"`
|
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.
|
// PersistentVolumeClaimVolumeSource references the user's PVC in the same namespace.
|
||||||
|
|
|
@ -390,6 +390,7 @@ func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator, pod *l
|
||||||
Name: v.Name,
|
Name: v.Name,
|
||||||
Dest: v.Dest,
|
Dest: v.Dest,
|
||||||
Options: v.Options,
|
Options: v.Options,
|
||||||
|
IsAnonymous: v.IsAnonymous,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
options = append(options, libpod.WithNamedVolumes(vols))
|
options = append(options, libpod.WithNamedVolumes(vols))
|
||||||
|
|
|
@ -406,8 +406,15 @@ func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGener
|
||||||
Name: volumeSource.Source,
|
Name: volumeSource.Source,
|
||||||
Options: options,
|
Options: options,
|
||||||
}
|
}
|
||||||
|
|
||||||
s.Volumes = append(s.Volumes, &secretVolume)
|
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:
|
default:
|
||||||
return nil, errors.New("unsupported volume source type")
|
return nil, errors.New("unsupported volume source type")
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ const (
|
||||||
KubeVolumeTypeBlockDevice
|
KubeVolumeTypeBlockDevice
|
||||||
KubeVolumeTypeCharDevice
|
KubeVolumeTypeCharDevice
|
||||||
KubeVolumeTypeSecret
|
KubeVolumeTypeSecret
|
||||||
|
KubeVolumeTypeEmptyDir
|
||||||
)
|
)
|
||||||
|
|
||||||
//nolint:revive
|
//nolint:revive
|
||||||
|
@ -219,8 +220,13 @@ func VolumeFromConfigMap(configMapVolumeSource *v1.ConfigMapVolumeSource, config
|
||||||
return kv, nil
|
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
|
// 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 {
|
switch {
|
||||||
case volumeSource.HostPath != nil:
|
case volumeSource.HostPath != nil:
|
||||||
return VolumeFromHostPath(volumeSource.HostPath)
|
return VolumeFromHostPath(volumeSource.HostPath)
|
||||||
|
@ -230,8 +236,10 @@ func VolumeFromSource(volumeSource v1.VolumeSource, configMaps []v1.ConfigMap, s
|
||||||
return VolumeFromConfigMap(volumeSource.ConfigMap, configMaps)
|
return VolumeFromConfigMap(volumeSource.ConfigMap, configMaps)
|
||||||
case volumeSource.Secret != nil:
|
case volumeSource.Secret != nil:
|
||||||
return VolumeFromSecret(volumeSource.Secret, secretsManager)
|
return VolumeFromSecret(volumeSource.Secret, secretsManager)
|
||||||
|
case volumeSource.EmptyDir != nil:
|
||||||
|
return VolumeFromEmptyDir(volumeSource.EmptyDir, volName)
|
||||||
default:
|
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)
|
volumes := make(map[string]*KubeVolume)
|
||||||
|
|
||||||
for _, specVolume := range specVolumes {
|
for _, specVolume := range specVolumes {
|
||||||
volume, err := VolumeFromSource(specVolume.VolumeSource, configMaps, secretsManager)
|
volume, err := VolumeFromSource(specVolume.VolumeSource, configMaps, secretsManager, specVolume.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create volume %q: %w", specVolume.Name, err)
|
return nil, fmt.Errorf("failed to create volume %q: %w", specVolume.Name, err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,9 @@ type NamedVolume struct {
|
||||||
Dest string
|
Dest string
|
||||||
// Options are options that the named volume will be mounted with.
|
// Options are options that the named volume will be mounted with.
|
||||||
Options []string
|
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
|
// OverlayVolume holds information about a overlay volume that will be mounted into
|
||||||
|
|
|
@ -509,6 +509,9 @@ spec:
|
||||||
volumes:
|
volumes:
|
||||||
{{ range . }}
|
{{ range . }}
|
||||||
- name: {{ .Name }}
|
- name: {{ .Name }}
|
||||||
|
{{- if (eq .VolumeType "EmptyDir") }}
|
||||||
|
emptyDir: {}
|
||||||
|
{{- end }}
|
||||||
{{- if (eq .VolumeType "HostPath") }}
|
{{- if (eq .VolumeType "HostPath") }}
|
||||||
hostPath:
|
hostPath:
|
||||||
path: {{ .HostPath.Path }}
|
path: {{ .HostPath.Path }}
|
||||||
|
@ -1242,12 +1245,15 @@ type ConfigMap struct {
|
||||||
Optional bool
|
Optional bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type EmptyDir struct{}
|
||||||
|
|
||||||
type Volume struct {
|
type Volume struct {
|
||||||
VolumeType string
|
VolumeType string
|
||||||
Name string
|
Name string
|
||||||
HostPath
|
HostPath
|
||||||
PersistentVolumeClaim
|
PersistentVolumeClaim
|
||||||
ConfigMap
|
ConfigMap
|
||||||
|
EmptyDir
|
||||||
}
|
}
|
||||||
|
|
||||||
// getHostPathVolume takes a type and a location for a HostPath
|
// 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 {
|
type Env struct {
|
||||||
Name string
|
Name string
|
||||||
Value string
|
Value string
|
||||||
|
@ -2762,6 +2776,43 @@ VOLUME %s`, ALPINE, hostPathDir+"/")
|
||||||
Expect(kube).Should(Exit(0))
|
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() {
|
It("podman play kube applies labels to pods", func() {
|
||||||
var numReplicas int32 = 5
|
var numReplicas int32 = 5
|
||||||
expectedLabelKey := "key1"
|
expectedLabelKey := "key1"
|
||||||
|
|
Loading…
Reference in New Issue