mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-31 15:25:57 +00:00
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:
commit
b5e6456795
@ -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,
|
||||
|
@ -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) {
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user