Merge pull request #126806 from carlory/fix-image-volume-mount

Kubelet should honour the VolumeAttributes which are reported by the volume plugin
This commit is contained in:
Kubernetes Prow Robot 2024-11-05 23:21:35 +00:00 committed by GitHub
commit b5e6456795
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 308 additions and 97 deletions

View File

@ -277,18 +277,6 @@ func makeMounts(pod *v1.Pod, podDir string, container *v1.Container, hostName, h
mounts := []kubecontainer.Mount{}
var cleanupAction func()
for i, mount := range container.VolumeMounts {
// Check if the mount is referencing an OCI volume
if imageVolumes != nil && utilfeature.DefaultFeatureGate.Enabled(features.ImageVolume) {
if image, ok := imageVolumes[mount.Name]; ok {
mounts = append(mounts, kubecontainer.Mount{
Name: mount.Name,
ContainerPath: mount.MountPath,
Image: image,
})
continue
}
}
// do not mount /etc/hosts if container is already mounting on the path
mountEtcHostsFile = mountEtcHostsFile && (mount.MountPath != etcHostsPath)
vol, ok := podVolumes[mount.Name]
@ -306,7 +294,19 @@ func makeMounts(pod *v1.Pod, podDir string, container *v1.Container, hostName, h
vol.SELinuxLabeled = true
relabelVolume = true
}
hostPath, err := volumeutil.GetPath(vol.Mounter)
var (
hostPath string
image *runtimeapi.ImageSpec
err error
)
if imageVolumes != nil && utilfeature.DefaultFeatureGate.Enabled(features.ImageVolume) {
image = imageVolumes[mount.Name]
}
if image == nil {
hostPath, err = volumeutil.GetPath(vol.Mounter)
if err != nil {
return nil, cleanupAction, err
}
@ -327,7 +327,7 @@ func makeMounts(pod *v1.Pod, podDir string, container *v1.Container, hostName, h
err = volumevalidation.ValidatePathNoBacksteps(subPath)
if err != nil {
return nil, cleanupAction, fmt.Errorf("unable to provision SubPath `%s`: %v", subPath, err)
return nil, cleanupAction, fmt.Errorf("unable to provision SubPath `%s`: %w", subPath, err)
}
volumePath := hostPath
@ -367,9 +367,10 @@ func makeMounts(pod *v1.Pod, podDir string, container *v1.Container, hostName, h
}
// Docker Volume Mounts fail on Windows if it is not of the form C:/
if volumeutil.IsWindowsLocalPath(runtime.GOOS, hostPath) {
if hostPath != "" && volumeutil.IsWindowsLocalPath(runtime.GOOS, hostPath) {
hostPath = volumeutil.MakeAbsolutePath(runtime.GOOS, hostPath)
}
}
containerPath := mount.MountPath
// IsAbs returns false for UNC path/SMB shares/named pipes in Windows. So check for those specifically and skip MakeAbsolutePath
@ -396,6 +397,7 @@ func makeMounts(pod *v1.Pod, podDir string, container *v1.Container, hostName, h
Name: mount.Name,
ContainerPath: containerPath,
HostPath: hostPath,
Image: image,
ReadOnly: mount.ReadOnly || mustMountRO,
RecursiveReadOnly: rro,
SELinuxRelabel: relabelVolume,

View File

@ -20,16 +20,21 @@ limitations under the License.
package kubelet
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
utilfeature "k8s.io/apiserver/pkg/util/feature"
featuregatetesting "k8s.io/component-base/featuregate/testing"
"k8s.io/utils/ptr"
runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1"
_ "k8s.io/kubernetes/pkg/apis/core/install"
"k8s.io/kubernetes/pkg/features"
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
"k8s.io/kubernetes/pkg/volume"
volumetest "k8s.io/kubernetes/pkg/volume/testing"
"k8s.io/kubernetes/pkg/volume/util/hostutil"
"k8s.io/kubernetes/pkg/volume/util/subpath"
@ -40,16 +45,21 @@ func TestMakeMounts(t *testing.T) {
propagationHostToContainer := v1.MountPropagationHostToContainer
propagationBidirectional := v1.MountPropagationBidirectional
propagationNone := v1.MountPropagationNone
image1 := &runtimeapi.ImageSpec{Image: "image1"}
image2 := &runtimeapi.ImageSpec{Image: "image2"}
testCases := map[string]struct {
container v1.Container
podVolumes kubecontainer.VolumeMap
imageVolumes kubecontainer.ImageVolumes
imageVolumeFeatureEnabled []bool
supportsRRO bool
expectErr bool
expectedErrMsg string
expectedMounts []kubecontainer.Mount
}{
"valid mounts in unprivileged container": {
"valid mounts in unprivileged container": { // TODO: remove it once image volume feature is GA
imageVolumeFeatureEnabled: []bool{true, false},
podVolumes: kubecontainer.VolumeMap{
"disk": kubecontainer.VolumeInfo{Mounter: &stubVolume{path: "/mnt/disk"}},
"disk4": kubecontainer.VolumeInfo{Mounter: &stubVolume{path: "/mnt/host"}},
@ -118,7 +128,8 @@ func TestMakeMounts(t *testing.T) {
},
expectErr: false,
},
"valid mounts in privileged container": {
"valid mounts in privileged container": { // TODO: remove it once image volume feature is GA
imageVolumeFeatureEnabled: []bool{true, false},
podVolumes: kubecontainer.VolumeMap{
"disk": kubecontainer.VolumeInfo{Mounter: &stubVolume{path: "/mnt/disk"}},
"disk4": kubecontainer.VolumeInfo{Mounter: &stubVolume{path: "/mnt/host"}},
@ -177,7 +188,198 @@ func TestMakeMounts(t *testing.T) {
},
expectErr: false,
},
"valid mounts in unprivileged container with image volumes": {
imageVolumeFeatureEnabled: []bool{true},
podVolumes: kubecontainer.VolumeMap{
"disk": kubecontainer.VolumeInfo{Mounter: &stubVolume{path: "/mnt/disk"}},
"disk4": kubecontainer.VolumeInfo{Mounter: &stubVolume{path: "/mnt/host"}},
"disk5": kubecontainer.VolumeInfo{Mounter: &stubVolume{path: "/var/lib/kubelet/podID/volumes/empty/disk5"}},
"image1": kubecontainer.VolumeInfo{Mounter: &stubVolume{attributes: volume.Attributes{ReadOnly: true}}},
"image2": kubecontainer.VolumeInfo{Mounter: &stubVolume{attributes: volume.Attributes{ReadOnly: true}}},
},
imageVolumes: kubecontainer.ImageVolumes{
"image1": image1,
"image2": image2,
},
container: v1.Container{
Name: "container1",
VolumeMounts: []v1.VolumeMount{
{
MountPath: "/etc/hosts",
Name: "disk",
ReadOnly: false,
MountPropagation: &propagationHostToContainer,
},
{
MountPath: "/mnt/path3",
Name: "disk",
ReadOnly: true,
MountPropagation: &propagationNone,
},
{
MountPath: "/mnt/path4",
Name: "disk4",
ReadOnly: false,
},
{
MountPath: "/mnt/path5",
Name: "disk5",
ReadOnly: false,
},
{
MountPath: "/mnt/image1",
Name: "image1",
ReadOnly: false,
},
{
MountPath: "/mnt/image2",
Name: "image2",
ReadOnly: true,
},
},
},
expectedMounts: []kubecontainer.Mount{
{
Name: "disk",
ContainerPath: "/etc/hosts",
HostPath: "/mnt/disk",
ReadOnly: false,
SELinuxRelabel: false,
Propagation: runtimeapi.MountPropagation_PROPAGATION_HOST_TO_CONTAINER,
},
{
Name: "disk",
ContainerPath: "/mnt/path3",
HostPath: "/mnt/disk",
ReadOnly: true,
SELinuxRelabel: false,
Propagation: runtimeapi.MountPropagation_PROPAGATION_PRIVATE,
},
{
Name: "disk4",
ContainerPath: "/mnt/path4",
HostPath: "/mnt/host",
ReadOnly: false,
SELinuxRelabel: false,
Propagation: runtimeapi.MountPropagation_PROPAGATION_PRIVATE,
},
{
Name: "disk5",
ContainerPath: "/mnt/path5",
HostPath: "/var/lib/kubelet/podID/volumes/empty/disk5",
ReadOnly: false,
SELinuxRelabel: false,
Propagation: runtimeapi.MountPropagation_PROPAGATION_PRIVATE,
},
{
Name: "image1",
ContainerPath: "/mnt/image1",
Image: image1,
ReadOnly: true,
SELinuxRelabel: false,
},
{
Name: "image2",
ContainerPath: "/mnt/image2",
Image: image2,
ReadOnly: true,
SELinuxRelabel: false,
},
},
expectErr: false,
},
"valid mounts in privileged container with image volumes": {
imageVolumeFeatureEnabled: []bool{true},
podVolumes: kubecontainer.VolumeMap{
"disk": kubecontainer.VolumeInfo{Mounter: &stubVolume{path: "/mnt/disk"}},
"disk4": kubecontainer.VolumeInfo{Mounter: &stubVolume{path: "/mnt/host"}},
"disk5": kubecontainer.VolumeInfo{Mounter: &stubVolume{path: "/var/lib/kubelet/podID/volumes/empty/disk5"}},
"image1": kubecontainer.VolumeInfo{Mounter: &stubVolume{attributes: volume.Attributes{ReadOnly: true}}},
"image2": kubecontainer.VolumeInfo{Mounter: &stubVolume{attributes: volume.Attributes{ReadOnly: true}}},
},
imageVolumes: kubecontainer.ImageVolumes{
"image1": image1,
"image2": image2,
},
container: v1.Container{
Name: "container1",
VolumeMounts: []v1.VolumeMount{
{
MountPath: "/etc/hosts",
Name: "disk",
ReadOnly: false,
MountPropagation: &propagationBidirectional,
},
{
MountPath: "/mnt/path3",
Name: "disk",
ReadOnly: true,
MountPropagation: &propagationHostToContainer,
},
{
MountPath: "/mnt/path4",
Name: "disk4",
ReadOnly: false,
},
{
MountPath: "/mnt/image1",
Name: "image1",
ReadOnly: false,
},
{
MountPath: "/mnt/image2",
Name: "image2",
ReadOnly: true,
},
},
SecurityContext: &v1.SecurityContext{
Privileged: &bTrue,
},
},
expectedMounts: []kubecontainer.Mount{
{
Name: "disk",
ContainerPath: "/etc/hosts",
HostPath: "/mnt/disk",
ReadOnly: false,
SELinuxRelabel: false,
Propagation: runtimeapi.MountPropagation_PROPAGATION_BIDIRECTIONAL,
},
{
Name: "disk",
ContainerPath: "/mnt/path3",
HostPath: "/mnt/disk",
ReadOnly: true,
SELinuxRelabel: false,
Propagation: runtimeapi.MountPropagation_PROPAGATION_HOST_TO_CONTAINER,
},
{
Name: "disk4",
ContainerPath: "/mnt/path4",
HostPath: "/mnt/host",
ReadOnly: false,
SELinuxRelabel: false,
Propagation: runtimeapi.MountPropagation_PROPAGATION_PRIVATE,
},
{
Name: "image1",
ContainerPath: "/mnt/image1",
Image: image1,
ReadOnly: true,
SELinuxRelabel: false,
},
{
Name: "image2",
ContainerPath: "/mnt/image2",
Image: image2,
ReadOnly: true,
SELinuxRelabel: false,
},
},
expectErr: false,
},
"invalid absolute SubPath": {
imageVolumeFeatureEnabled: []bool{true, false},
podVolumes: kubecontainer.VolumeMap{
"disk": kubecontainer.VolumeInfo{Mounter: &stubVolume{path: "/mnt/disk"}},
},
@ -195,6 +397,7 @@ func TestMakeMounts(t *testing.T) {
expectedErrMsg: "error SubPath `/must/not/be/absolute` must not be an absolute path",
},
"invalid SubPath with backsteps": {
imageVolumeFeatureEnabled: []bool{true, false},
podVolumes: kubecontainer.VolumeMap{
"disk": kubecontainer.VolumeInfo{Mounter: &stubVolume{path: "/mnt/disk"}},
},
@ -212,7 +415,7 @@ func TestMakeMounts(t *testing.T) {
expectedErrMsg: "unable to provision SubPath `no/backsteps/../allowed`: must not contain '..'",
},
"volume doesn't exist": {
podVolumes: kubecontainer.VolumeMap{},
imageVolumeFeatureEnabled: []bool{true, false},
container: v1.Container{
VolumeMounts: []v1.VolumeMount{
{
@ -226,6 +429,7 @@ func TestMakeMounts(t *testing.T) {
expectedErrMsg: "cannot find volume \"disk\" to mount into container \"\"",
},
"volume mounter is nil": {
imageVolumeFeatureEnabled: []bool{true, false},
podVolumes: kubecontainer.VolumeMap{
"disk": kubecontainer.VolumeInfo{},
},
@ -244,7 +448,10 @@ func TestMakeMounts(t *testing.T) {
}
for name, tc := range testCases {
for _, featureEnabled := range tc.imageVolumeFeatureEnabled {
name := fmt.Sprintf("features.ImageVolume is %v, %s", featureEnabled, name)
t.Run(name, func(t *testing.T) {
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ImageVolume, featureEnabled)
fhu := hostutil.NewFakeHostUtil(nil)
fsp := &subpath.FakeSubpath{}
pod := v1.Pod{
@ -253,7 +460,7 @@ func TestMakeMounts(t *testing.T) {
},
}
mounts, _, err := makeMounts(&pod, "/pod", &tc.container, "fakepodname", "", []string{""}, tc.podVolumes, fhu, fsp, nil, tc.supportsRRO, nil)
mounts, _, err := makeMounts(&pod, "/pod", &tc.container, "fakepodname", "", []string{""}, tc.podVolumes, fhu, fsp, nil, tc.supportsRRO, tc.imageVolumes)
// validate only the error if we expect an error
if tc.expectErr {
@ -271,6 +478,7 @@ func TestMakeMounts(t *testing.T) {
assert.Equal(t, tc.expectedMounts, mounts, "mounts of container %+v", tc.container)
})
}
}
}
func TestMakeMountsEtcHostsFile(t *testing.T) {

View File

@ -690,6 +690,7 @@ func TestVolumeUnmountAndDetachControllerEnabled(t *testing.T) {
type stubVolume struct {
path string
attributes volume.Attributes
volume.MetricsNil
}
@ -698,7 +699,7 @@ func (f *stubVolume) GetPath() string {
}
func (f *stubVolume) GetAttributes() volume.Attributes {
return volume.Attributes{}
return f.attributes
}
func (f *stubVolume) SetUp(mounterArgs volume.MounterArgs) error {

View File

@ -67,8 +67,8 @@ func (o *imagePlugin) ConstructVolumeSpec(volumeName, mountPath string) (volume.
func (o *imagePlugin) GetAttributes() volume.Attributes {
return volume.Attributes{
ReadOnly: true,
Managed: true,
SELinuxRelabel: true,
Managed: false,
SELinuxRelabel: false,
}
}