mirror of
https://github.com/containers/podman.git
synced 2025-05-21 00:56:36 +08:00
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:
@ -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
|
||||||
|
@ -1413,9 +1413,10 @@ func WithNamedVolumes(volumes []*ContainerNamedVolume) CtrCreateOption {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctr.config.NamedVolumes = append(ctr.config.NamedVolumes, &ContainerNamedVolume{
|
ctr.config.NamedVolumes = append(ctr.config.NamedVolumes, &ContainerNamedVolume{
|
||||||
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.
|
||||||
|
@ -387,9 +387,10 @@ func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator, pod *l
|
|||||||
var vols []*libpod.ContainerNamedVolume
|
var vols []*libpod.ContainerNamedVolume
|
||||||
for _, v := range volumes {
|
for _, v := range volumes {
|
||||||
vols = append(vols, &libpod.ContainerNamedVolume{
|
vols = append(vols, &libpod.ContainerNamedVolume{
|
||||||
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"
|
||||||
|
Reference in New Issue
Block a user