mirror of
https://github.com/containers/podman.git
synced 2025-10-18 03:33:32 +08:00
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>
This commit is contained 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
|
||||
|
@ -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
|
||||
)
|
||||
|
@ -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(),
|
||||
}
|
||||
|
||||
|
@ -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, "=")
|
||||
|
@ -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())
|
||||
|
@ -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
|
||||
}
|
||||
|
Reference in New Issue
Block a user