Merge pull request #22410 from mheon/automount_images_k8s

Add the ability to automount images as volumes via play
This commit is contained in:
openshift-merge-bot[bot] 2024-04-25 19:26:47 +00:00 committed by GitHub
commit 15cc8862c1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 175 additions and 2 deletions

View File

@ -41,6 +41,8 @@ Options specific to type=**image**:
- *rw*, *readwrite*: *true* or *false* (default if unspecified: *false*).
- *subpath*: Mount only a specific path within the image, instead of the whole image.
Options specific to **bind** and **glob**:
- *ro*, *readonly*: *true* or *false* (default if unspecified: *false*).

View File

@ -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

View File

@ -275,6 +275,8 @@ type ContainerImageVolume struct {
Dest string `json:"dest"`
// ReadWrite sets the volume writable.
ReadWrite bool `json:"rw"`
// SubPath determines which part of the image will be mounted into the container.
SubPath string `json:"subPath,omitempty"`
}
// ContainerSecret is a secret that is mounted in a container

View File

@ -459,11 +459,23 @@ func (c *Container) generateSpec(ctx context.Context) (s *spec.Spec, cleanupFunc
return nil, nil, fmt.Errorf("failed to create TempDir in the %s directory: %w", c.config.StaticDir, err)
}
imagePath := mountPoint
if volume.SubPath != "" {
safeMount, err := c.safeMountSubPath(mountPoint, volume.SubPath)
if err != nil {
return nil, nil, err
}
safeMounts = append(safeMounts, safeMount)
imagePath = safeMount.mountPoint
}
var overlayMount spec.Mount
if volume.ReadWrite {
overlayMount, err = overlay.Mount(contentDir, mountPoint, volume.Dest, c.RootUID(), c.RootGID(), c.runtime.store.GraphOptions())
overlayMount, err = overlay.Mount(contentDir, imagePath, volume.Dest, c.RootUID(), c.RootGID(), c.runtime.store.GraphOptions())
} else {
overlayMount, err = overlay.MountReadOnly(contentDir, mountPoint, volume.Dest, c.RootUID(), c.RootGID(), c.runtime.store.GraphOptions())
overlayMount, err = overlay.MountReadOnly(contentDir, imagePath, volume.Dest, c.RootUID(), c.RootGID(), c.runtime.store.GraphOptions())
}
if err != nil {
return nil, nil, fmt.Errorf("creating overlay mount for image %q failed: %w", volume.Source, err)

View File

@ -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
)

View File

@ -1474,6 +1474,7 @@ func WithImageVolumes(volumes []*ContainerImageVolume) CtrCreateOption {
Dest: vol.Dest,
Source: vol.Source,
ReadWrite: vol.ReadWrite,
SubPath: vol.SubPath,
})
}

View File

@ -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(),
}

View File

@ -501,6 +501,7 @@ func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator, pod *l
Dest: v.Destination,
Source: v.Source,
ReadWrite: v.ReadWrite,
SubPath: v.SubPath,
})
}
options = append(options, libpod.WithImageVolumes(vols))

View File

@ -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, "=")

View File

@ -53,6 +53,9 @@ type ImageVolume struct {
Destination string
// ReadWrite sets the volume writable.
ReadWrite bool
// SubPath mounts a particular path within the image.
// If empty, the whole image is mounted.
SubPath string `json:"subPath,omitempty"`
}
// GenVolumeMounts parses user input into mounts, volumes and overlay volumes

View File

@ -611,6 +611,14 @@ func getImageVolume(args []string) (*specgen.ImageVolume, error) {
default:
return nil, fmt.Errorf("invalid rw value %q: %w", value, util.ErrBadMntOption)
}
case "subpath":
if !hasValue {
return nil, fmt.Errorf("%v: %w", name, errOptionArg)
}
if !filepath.IsAbs(value) {
return nil, fmt.Errorf("volume subpath %q must be an absolute path", value)
}
newVolume.SubPath = value
case "consistency":
// Often used on MACs and mistakenly on Linux platforms.
// Since Docker ignores this option so shall we.

View File

@ -934,4 +934,25 @@ USER testuser`, CITEST_IMAGE)
Expect(run).Should(ExitCleanly())
Expect(run.OutputToString()).Should(ContainSubstring(strings.TrimLeft("/vol/", f.Name())))
})
It("podman run --mount type=image with subpath", func() {
podmanTest.AddImageToRWStore(ALPINE)
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, pathInCtr)
run2 := podmanTest.Podman(run2Cmd)
run2.WaitWithDefaultTimeout()
Expect(run2).Should(ExitCleanly())
Expect(run1.OutputToString()).Should(Equal(run2.OutputToString()))
})
})

View File

@ -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
}