diff options
author | Matt Heon <mheon@redhat.com> | 2024-04-17 08:23:38 -0400 |
---|---|---|
committer | Matt Heon <mheon@redhat.com> | 2024-04-25 14:12:27 -0400 |
commit | 30e2c923d6f0b7e9fc46e353288804e3fbbbad67 (patch) | |
tree | 4b3d6ea9c1bccbac156a185a293c2b41739244b6 | |
parent | 693ae0ebc60d648b870eb25c6d2fb155c280da61 (diff) |
Add the ability to automount images as volumes via play
Effectively, this is an ability to take an image already pulled
to the system, and automatically mount it into one or more
containers defined in Kubernetes YAML accepted by `podman play`.
Requirements:
- The image must already exist in storage.
- The image must have at least 1 volume directive.
- The path given by the volume directive will be mounted from the
image into the container. For example, an image with a volume
at `/test/test_dir` will have `/test/test_dir` in the image
mounted to `/test/test_dir` in the container.
- Multiple images can be specified. If multiple images have a
volume at a specific path, the last image specified trumps.
- The images are always mounted read-only.
- Images to mount are defined in the annotation
"io.podman.annotations.kube.image.automount/$ctrname" as a
semicolon-separated list. They are mounted into a single
container in the pod, not the whole pod.
As we're using a nonstandard annotation, this is Podman only, any
Kubernetes install will just ignore this.
Underneath, this compiles down to an image volume
(`podman run --mount type=image,...`) with subpaths to specify
what bits we want to mount into the container.
Signed-off-by: Matt Heon <mheon@redhat.com>
-rw-r--r-- | docs/source/markdown/podman-kube-play.1.md.in | 11 | ||||
-rw-r--r-- | libpod/define/annotations.go | 3 | ||||
-rw-r--r-- | pkg/domain/infra/abi/play.go | 60 | ||||
-rw-r--r-- | pkg/specgen/generate/kube/kube.go | 4 | ||||
-rw-r--r-- | test/e2e/run_volume_test.go | 11 | ||||
-rw-r--r-- | test/system/700-play.bats | 45 |
6 files changed, 131 insertions, 3 deletions
diff --git a/docs/source/markdown/podman-kube-play.1.md.in b/docs/source/markdown/podman-kube-play.1.md.in index ea2ebf4a0..21a929a13 100644 --- a/docs/source/markdown/podman-kube-play.1.md.in +++ b/docs/source/markdown/podman-kube-play.1.md.in @@ -158,6 +158,17 @@ spec: and as a result environment variable `FOO` is set to `bar` for container `container-1`. +`Automounting Volumes` + +An image can be automatically mounted into a container if the annotation `io.podman.annotations.kube.image.automount/$ctrname` is given. The following rules apply: + +- The image must already exist locally. +- The image must have at least 1 volume directive. +- The path given by the volume directive will be mounted from the image into the container. For example, an image with a volume at `/test/test_dir` will have `/test/test_dir` in the image mounted to `/test/test_dir` in the container. +- Multiple images can be specified. If multiple images have a volume at a specific path, the last image specified trumps. +- The images are always mounted read-only. +- Images to mount are defined in the annotation "io.podman.annotations.kube.image.automount/$ctrname" as a semicolon-separated list. They are mounted into a single container in the pod, not the whole pod. The annotation can be specified for additional containers if additional mounts are required. + ## OPTIONS @@option annotation.container diff --git a/libpod/define/annotations.go b/libpod/define/annotations.go index a9d4031ae..ac1956f56 100644 --- a/libpod/define/annotations.go +++ b/libpod/define/annotations.go @@ -160,6 +160,9 @@ const ( // the k8s behavior of waiting for the intialDelaySeconds to be over before updating the status KubeHealthCheckAnnotation = "io.podman.annotations.kube.health.check" + // KubeImageAutomountAnnotation + KubeImageAutomountAnnotation = "io.podman.annotations.kube.image.volumes.mount" + // TotalAnnotationSizeLimitB is the max length of annotations allowed by Kubernetes. TotalAnnotationSizeLimitB int = 256 * (1 << 10) // 256 kB ) diff --git a/pkg/domain/infra/abi/play.go b/pkg/domain/infra/abi/play.go index f1202dfce..fa7ffb8ea 100644 --- a/pkg/domain/infra/abi/play.go +++ b/pkg/domain/infra/abi/play.go @@ -126,6 +126,54 @@ func (ic *ContainerEngine) createServiceContainer(ctx context.Context, name stri return ctr, nil } +func (ic *ContainerEngine) prepareAutomountImages(ctx context.Context, forContainer string, annotations map[string]string) ([]*specgen.ImageVolume, error) { + volMap := make(map[string]*specgen.ImageVolume) + + ctrAnnotation := define.KubeImageAutomountAnnotation + "/" + forContainer + + automount, ok := annotations[ctrAnnotation] + if !ok || automount == "" { + return nil, nil + } + + for _, imageName := range strings.Split(automount, ";") { + img, fullName, err := ic.Libpod.LibimageRuntime().LookupImage(imageName, nil) + if err != nil { + return nil, fmt.Errorf("image %s from container %s does not exist in local storage, cannot automount: %w", imageName, forContainer, err) + } + + logrus.Infof("Resolved image name %s to %s for automount into container %s", imageName, fullName, forContainer) + + inspect, err := img.Inspect(ctx, nil) + if err != nil { + return nil, fmt.Errorf("cannot inspect image %s to automount into container %s: %w", fullName, forContainer, err) + } + + volumes := inspect.Config.Volumes + + for path := range volumes { + if oldPath, ok := volMap[path]; ok && oldPath != nil { + logrus.Warnf("Multiple volume mounts to %q requested, overriding image %q with image %s", path, oldPath.Source, fullName) + } + + imgVol := new(specgen.ImageVolume) + imgVol.Source = fullName + imgVol.Destination = path + imgVol.ReadWrite = false + imgVol.SubPath = path + + volMap[path] = imgVol + } + } + + toReturn := make([]*specgen.ImageVolume, 0, len(volMap)) + for _, vol := range volMap { + toReturn = append(toReturn, vol) + } + + return toReturn, nil +} + func prepareVolumesFrom(forContainer, podName string, ctrNames, annotations map[string]string) ([]string, error) { annotationVolsFrom := define.VolumesFromAnnotation + "/" + forContainer @@ -829,6 +877,11 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY initCtrType = define.OneShotInitContainer } + automountImages, err := ic.prepareAutomountImages(ctx, initCtr.Name, annotations) + if err != nil { + return nil, nil, err + } + var volumesFrom []string if list, err := prepareVolumesFrom(initCtr.Name, podName, ctrNames, annotations); err != nil { return nil, nil, err @@ -857,6 +910,7 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY UserNSIsHost: p.Userns.IsHost(), Volumes: volumes, VolumesFrom: volumesFrom, + ImageVolumes: automountImages, UtsNSIsHost: p.UtsNs.IsHost(), } specGen, err := kube.ToSpecGen(ctx, &specgenOpts) @@ -913,6 +967,11 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY labels[k] = v } + automountImages, err := ic.prepareAutomountImages(ctx, container.Name, annotations) + if err != nil { + return nil, nil, err + } + var volumesFrom []string if list, err := prepareVolumesFrom(container.Name, podName, ctrNames, annotations); err != nil { return nil, nil, err @@ -942,6 +1001,7 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY UserNSIsHost: p.Userns.IsHost(), Volumes: volumes, VolumesFrom: volumesFrom, + ImageVolumes: automountImages, UtsNSIsHost: p.UtsNs.IsHost(), } diff --git a/pkg/specgen/generate/kube/kube.go b/pkg/specgen/generate/kube/kube.go index ec5cc1042..1328323ce 100644 --- a/pkg/specgen/generate/kube/kube.go +++ b/pkg/specgen/generate/kube/kube.go @@ -142,6 +142,8 @@ type CtrSpecGenOptions struct { Volumes map[string]*KubeVolume // VolumesFrom for all containers VolumesFrom []string + // Image Volumes for this container + ImageVolumes []*specgen.ImageVolume // PodID of the parent pod PodID string // PodName of the parent pod @@ -223,6 +225,8 @@ func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGener Driver: opts.LogDriver, } + s.ImageVolumes = opts.ImageVolumes + s.LogConfiguration.Options = make(map[string]string) for _, o := range opts.LogOptions { opt, val, hasVal := strings.Cut(o, "=") diff --git a/test/e2e/run_volume_test.go b/test/e2e/run_volume_test.go index 07cfca1fb..4dda259cd 100644 --- a/test/e2e/run_volume_test.go +++ b/test/e2e/run_volume_test.go @@ -936,14 +936,19 @@ USER testuser`, CITEST_IMAGE) }) It("podman run --mount type=image with subpath", func() { - ctrCommand := []string{"run", "--mount", fmt.Sprintf("type=image,source=%s,dest=/mnt,subpath=/etc", ALPINE), ALPINE, "ls"} + podmanTest.AddImageToRWStore(ALPINE) - run1Cmd := append(ctrCommand, "/etc") + pathToCheck := "/sbin" + pathInCtr := "/mnt" + + ctrCommand := []string{"run", "--mount", fmt.Sprintf("type=image,source=%s,dst=%s,subpath=%s", ALPINE, pathInCtr, pathToCheck), ALPINE, "ls"} + + run1Cmd := append(ctrCommand, pathToCheck) run1 := podmanTest.Podman(run1Cmd) run1.WaitWithDefaultTimeout() Expect(run1).Should(ExitCleanly()) - run2Cmd := append(ctrCommand, "/mnt") + run2Cmd := append(ctrCommand, pathInCtr) run2 := podmanTest.Podman(run2Cmd) run2.WaitWithDefaultTimeout() Expect(run2).Should(ExitCleanly()) diff --git a/test/system/700-play.bats b/test/system/700-play.bats index 174db0514..144177574 100644 --- a/test/system/700-play.bats +++ b/test/system/700-play.bats @@ -981,3 +981,48 @@ _EOF run_podman pod rm -t 0 -f test_pod run_podman rmi -f userimage:latest $from_image } + +@test "podman play with automount volume" { + cat >$PODMAN_TMPDIR/Containerfile <<EOF +FROM $IMAGE +RUN mkdir /test1 /test2 +RUN touch /test1/a /test1/b /test1/c +RUN touch /test2/asdf /test2/ejgre /test2/lteghe +VOLUME /test1 +VOLUME /test2 +EOF + + run_podman build -t automount_test -f $PODMAN_TMPDIR/Containerfile + + fname="/tmp/play_kube_wait_$(random_string 6).yaml" + echo " +apiVersion: v1 +kind: Pod +metadata: + labels: + app: test + name: test_pod +spec: + restartPolicy: Never + containers: + - name: testctr + image: $IMAGE + command: + - top +" > $fname + + run_podman kube play --annotation "io.podman.annotations.kube.image.volumes.mount/testctr=automount_test" $fname + + run_podman run --rm automount_test ls /test1 + run_out_test1="$output" + run_podman exec test_pod-testctr ls /test1 + assert "$output" = "$run_out_test1" "matching ls run/exec volume path test1" + + run_podman run --rm automount_test ls /test2 + run_out_test2="$output" + run_podman exec test_pod-testctr ls /test2 + assert "$output" = "$run_out_test2" "matching ls run/exec volume path test2" + + run_podman rm -f -t 0 -a + run_podman rmi automount_test +} |