mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-27 21:47:07 +00:00
Merge pull request #64351 from msau42/fix-readonly
Automatic merge from submit-queue (batch tested with PRs 62266, 64351, 64366, 64235, 64560). If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>. Bind mount subpath with same read/write settings as underlying volume **What this PR does / why we need it**: https://github.com/kubernetes/kubernetes/pull/63045 broke two scenarios: * If volumeMount path already exists in container image, container runtime will try to chown the volume * In SELinux system, we will try to set SELinux labels when starting the container This fix makes it so that the subpath bind mount will inherit the read/write settings of the underlying volume mount. It does this by using the "bind,remount" mount options when doing the bind mount. The underlying volume mount is ro when the volumeSource.readOnly flag is set. This is for persistent volume types like PVC, GCE PD, NFS, etc. When this is set, we won't try to configure SELinux labels. Also in this mode, subpaths have to already exist in the volume, we cannot make new directories on a read only volume. When volumeMount.readOnly is set, the container runtime is in charge of making the volume in the container readOnly, but the underlying volume mount on the host can be writable. This can be set for any volume type, and is permanently set for atomic volume types like configmaps, secrets. In this case, SELinux labels will be applied before the container runtime makes the volume readOnly. And subpaths don't have to exist. **Which issue(s) this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close the issue(s) when PR gets merged)*: Fixes #64120 **Special notes for your reviewer**: **Release note**: ```release-note Fixes issue for readOnly subpath mounts for SELinux systems and when the volume mountPath already existed in the container image. ```
This commit is contained in:
commit
2cb5c47b12
@ -206,7 +206,6 @@ func makeMounts(pod *v1.Pod, podDir string, container *v1.Container, hostName, h
|
|||||||
VolumePath: volumePath,
|
VolumePath: volumePath,
|
||||||
PodDir: podDir,
|
PodDir: podDir,
|
||||||
ContainerName: container.Name,
|
ContainerName: container.Name,
|
||||||
ReadOnly: mount.ReadOnly || vol.Mounter.GetAttributes().ReadOnly,
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Don't pass detailed error back to the user because it could give information about host filesystem
|
// Don't pass detailed error back to the user because it could give information about host filesystem
|
||||||
|
@ -136,8 +136,6 @@ type Subpath struct {
|
|||||||
PodDir string
|
PodDir string
|
||||||
// Name of the container
|
// Name of the container
|
||||||
ContainerName string
|
ContainerName string
|
||||||
// True if the mount needs to be readonly
|
|
||||||
ReadOnly bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exec executes command where mount utilities are. This can be either the host,
|
// Exec executes command where mount utilities are. This can be either the host,
|
||||||
|
@ -884,10 +884,6 @@ func doBindSubPath(mounter Interface, subpath Subpath) (hostPath string, err err
|
|||||||
|
|
||||||
// Do the bind mount
|
// Do the bind mount
|
||||||
options := []string{"bind"}
|
options := []string{"bind"}
|
||||||
if subpath.ReadOnly {
|
|
||||||
options = append(options, "ro")
|
|
||||||
}
|
|
||||||
|
|
||||||
glog.V(5).Infof("bind mounting %q at %q", mountSource, bindPathTarget)
|
glog.V(5).Infof("bind mounting %q at %q", mountSource, bindPathTarget)
|
||||||
if err = mounter.Mount(mountSource, bindPathTarget, "" /*fstype*/, options); err != nil {
|
if err = mounter.Mount(mountSource, bindPathTarget, "" /*fstype*/, options); err != nil {
|
||||||
return "", fmt.Errorf("error mounting %s: %s", subpath.Path, err)
|
return "", fmt.Errorf("error mounting %s: %s", subpath.Path, err)
|
||||||
|
@ -1009,7 +1009,6 @@ func getTestPaths(base string) (string, string) {
|
|||||||
|
|
||||||
func TestBindSubPath(t *testing.T) {
|
func TestBindSubPath(t *testing.T) {
|
||||||
defaultPerm := os.FileMode(0750)
|
defaultPerm := os.FileMode(0750)
|
||||||
readOnlyPerm := os.FileMode(0444)
|
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@ -1017,7 +1016,6 @@ func TestBindSubPath(t *testing.T) {
|
|||||||
// base.
|
// base.
|
||||||
prepare func(base string) ([]string, string, string, error)
|
prepare func(base string) ([]string, string, string, error)
|
||||||
expectError bool
|
expectError bool
|
||||||
readOnly bool
|
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "subpath-dir",
|
name: "subpath-dir",
|
||||||
@ -1214,55 +1212,6 @@ func TestBindSubPath(t *testing.T) {
|
|||||||
},
|
},
|
||||||
expectError: false,
|
expectError: false,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "subpath-dir-readonly",
|
|
||||||
prepare: func(base string) ([]string, string, string, error) {
|
|
||||||
volpath, _ := getTestPaths(base)
|
|
||||||
subpath := filepath.Join(volpath, "dir0")
|
|
||||||
return nil, volpath, subpath, os.MkdirAll(subpath, defaultPerm)
|
|
||||||
},
|
|
||||||
expectError: false,
|
|
||||||
readOnly: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "subpath-file-readonly",
|
|
||||||
prepare: func(base string) ([]string, string, string, error) {
|
|
||||||
volpath, _ := getTestPaths(base)
|
|
||||||
subpath := filepath.Join(volpath, "file0")
|
|
||||||
if err := os.MkdirAll(volpath, defaultPerm); err != nil {
|
|
||||||
return nil, "", "", err
|
|
||||||
}
|
|
||||||
return nil, volpath, subpath, ioutil.WriteFile(subpath, []byte{}, defaultPerm)
|
|
||||||
},
|
|
||||||
expectError: false,
|
|
||||||
readOnly: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "subpath-dir-and-volume-readonly",
|
|
||||||
prepare: func(base string) ([]string, string, string, error) {
|
|
||||||
volpath, _ := getTestPaths(base)
|
|
||||||
subpath := filepath.Join(volpath, "dir0")
|
|
||||||
if err := os.MkdirAll(subpath, defaultPerm); err != nil {
|
|
||||||
return nil, "", "", err
|
|
||||||
}
|
|
||||||
return nil, volpath, subpath, os.Chmod(subpath, readOnlyPerm)
|
|
||||||
},
|
|
||||||
expectError: false,
|
|
||||||
readOnly: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "subpath-file-and-vol-readonly",
|
|
||||||
prepare: func(base string) ([]string, string, string, error) {
|
|
||||||
volpath, _ := getTestPaths(base)
|
|
||||||
subpath := filepath.Join(volpath, "file0")
|
|
||||||
if err := os.MkdirAll(volpath, defaultPerm); err != nil {
|
|
||||||
return nil, "", "", err
|
|
||||||
}
|
|
||||||
return nil, volpath, subpath, ioutil.WriteFile(subpath, []byte{}, readOnlyPerm)
|
|
||||||
},
|
|
||||||
expectError: false,
|
|
||||||
readOnly: true,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
@ -1287,7 +1236,6 @@ func TestBindSubPath(t *testing.T) {
|
|||||||
VolumePath: volPath,
|
VolumePath: volPath,
|
||||||
PodDir: filepath.Join(base, "pod0"),
|
PodDir: filepath.Join(base, "pod0"),
|
||||||
ContainerName: testContainer,
|
ContainerName: testContainer,
|
||||||
ReadOnly: test.readOnly,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_, subpathMount := getTestPaths(base)
|
_, subpathMount := getTestPaths(base)
|
||||||
@ -1313,39 +1261,12 @@ func TestBindSubPath(t *testing.T) {
|
|||||||
if err = validateFileExists(subpathMount); err != nil {
|
if err = validateFileExists(subpathMount); err != nil {
|
||||||
t.Errorf("test %q failed: %v", test.name, err)
|
t.Errorf("test %q failed: %v", test.name, err)
|
||||||
}
|
}
|
||||||
if err = validateReadOnlyMount(test.readOnly, bindPathTarget, fm); err != nil {
|
|
||||||
t.Errorf("test %q failed: %v", test.name, err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
os.RemoveAll(base)
|
os.RemoveAll(base)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateReadOnlyMount(expectedReadOnly bool, bindPathTarget string, mounter *FakeMounter) error {
|
|
||||||
mps, err := mounter.List()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("fakeMounter.List() returned error: %v", err)
|
|
||||||
}
|
|
||||||
for _, mp := range mps {
|
|
||||||
if mp.Path == bindPathTarget {
|
|
||||||
foundReadOnly := false
|
|
||||||
for _, opts := range mp.Opts {
|
|
||||||
if opts == "ro" {
|
|
||||||
foundReadOnly = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if expectedReadOnly != foundReadOnly {
|
|
||||||
return fmt.Errorf("expected readOnly %v, got %v for mount point %v", expectedReadOnly, foundReadOnly, bindPathTarget)
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fmt.Errorf("failed to find mountPoint %v", bindPathTarget)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseMountInfo(t *testing.T) {
|
func TestParseMountInfo(t *testing.T) {
|
||||||
info :=
|
info :=
|
||||||
`62 0 253:0 / / rw,relatime shared:1 - ext4 /dev/mapper/ssd-root rw,seclabel,data=ordered
|
`62 0 253:0 / / rw,relatime shared:1 - ext4 /dev/mapper/ssd-root rw,seclabel,data=ordered
|
||||||
|
@ -110,6 +110,13 @@ var _ = utils.SIGDescribe("Subpath", func() {
|
|||||||
testBasicSubpath(f, "configmap-value", pod)
|
testBasicSubpath(f, "configmap-value", pod)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
It("should support subpaths with configmap pod with mountPath of existing file", func() {
|
||||||
|
pod := testPodSubpath(f, "configmap-key", "configmap", &v1.VolumeSource{ConfigMap: &v1.ConfigMapVolumeSource{LocalObjectReference: v1.LocalObjectReference{Name: "my-configmap"}}})
|
||||||
|
file := "/etc/resolv.conf"
|
||||||
|
pod.Spec.Containers[0].VolumeMounts[0].MountPath = file
|
||||||
|
testBasicSubpathFile(f, "configmap-value", pod, file)
|
||||||
|
})
|
||||||
|
|
||||||
It("should support subpaths with downward pod", func() {
|
It("should support subpaths with downward pod", func() {
|
||||||
pod := testPodSubpath(f, "downward/podname", "downwardAPI", &v1.VolumeSource{
|
pod := testPodSubpath(f, "downward/podname", "downwardAPI", &v1.VolumeSource{
|
||||||
DownwardAPI: &v1.DownwardAPIVolumeSource{
|
DownwardAPI: &v1.DownwardAPIVolumeSource{
|
||||||
@ -199,7 +206,7 @@ var _ = utils.SIGDescribe("Subpath", func() {
|
|||||||
setInitCommand(pod, fmt.Sprintf("ln -s /bin %s", subPathDir))
|
setInitCommand(pod, fmt.Sprintf("ln -s /bin %s", subPathDir))
|
||||||
|
|
||||||
// Pod should fail
|
// Pod should fail
|
||||||
testPodFailSupath(f, pod)
|
testPodFailSubpath(f, pod)
|
||||||
})
|
})
|
||||||
|
|
||||||
It("should fail if subpath file is outside the volume [Slow]", func() {
|
It("should fail if subpath file is outside the volume [Slow]", func() {
|
||||||
@ -207,7 +214,7 @@ var _ = utils.SIGDescribe("Subpath", func() {
|
|||||||
setInitCommand(pod, fmt.Sprintf("ln -s /bin/sh %s", subPathDir))
|
setInitCommand(pod, fmt.Sprintf("ln -s /bin/sh %s", subPathDir))
|
||||||
|
|
||||||
// Pod should fail
|
// Pod should fail
|
||||||
testPodFailSupath(f, pod)
|
testPodFailSubpath(f, pod)
|
||||||
})
|
})
|
||||||
|
|
||||||
It("should fail if non-existent subpath is outside the volume [Slow]", func() {
|
It("should fail if non-existent subpath is outside the volume [Slow]", func() {
|
||||||
@ -215,7 +222,7 @@ var _ = utils.SIGDescribe("Subpath", func() {
|
|||||||
setInitCommand(pod, fmt.Sprintf("ln -s /bin/notanexistingpath %s", subPathDir))
|
setInitCommand(pod, fmt.Sprintf("ln -s /bin/notanexistingpath %s", subPathDir))
|
||||||
|
|
||||||
// Pod should fail
|
// Pod should fail
|
||||||
testPodFailSupath(f, pod)
|
testPodFailSubpath(f, pod)
|
||||||
})
|
})
|
||||||
|
|
||||||
It("should fail if subpath with backstepping is outside the volume [Slow]", func() {
|
It("should fail if subpath with backstepping is outside the volume [Slow]", func() {
|
||||||
@ -223,7 +230,7 @@ var _ = utils.SIGDescribe("Subpath", func() {
|
|||||||
setInitCommand(pod, fmt.Sprintf("ln -s ../ %s", subPathDir))
|
setInitCommand(pod, fmt.Sprintf("ln -s ../ %s", subPathDir))
|
||||||
|
|
||||||
// Pod should fail
|
// Pod should fail
|
||||||
testPodFailSupath(f, pod)
|
testPodFailSubpath(f, pod)
|
||||||
})
|
})
|
||||||
|
|
||||||
It("should support creating multiple subpath from same volumes [Slow]", func() {
|
It("should support creating multiple subpath from same volumes [Slow]", func() {
|
||||||
@ -297,7 +304,7 @@ var _ = utils.SIGDescribe("Subpath", func() {
|
|||||||
testReadFile(f, volumePath, pod, 0)
|
testReadFile(f, volumePath, pod, 0)
|
||||||
})
|
})
|
||||||
|
|
||||||
It("should support readOnly directory specified in the volumeSource", func() {
|
It("should support existing directories when readOnly specified in the volumeSource", func() {
|
||||||
roVol := vol.getReadOnlyVolumeSpec()
|
roVol := vol.getReadOnlyVolumeSpec()
|
||||||
if roVol == nil {
|
if roVol == nil {
|
||||||
framework.Skipf("Volume type %v doesn't support readOnly source", curVolType)
|
framework.Skipf("Volume type %v doesn't support readOnly source", curVolType)
|
||||||
@ -312,6 +319,19 @@ var _ = utils.SIGDescribe("Subpath", func() {
|
|||||||
// Read it from inside the subPath from container 0
|
// Read it from inside the subPath from container 0
|
||||||
testReadFile(f, filePathInSubpath, pod, 0)
|
testReadFile(f, filePathInSubpath, pod, 0)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
It("should fail for new directories when readOnly specified in the volumeSource", func() {
|
||||||
|
roVol := vol.getReadOnlyVolumeSpec()
|
||||||
|
if roVol == nil {
|
||||||
|
framework.Skipf("Volume type %v doesn't support readOnly source", curVolType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set volume source to read only
|
||||||
|
pod.Spec.Volumes[0].VolumeSource = *roVol
|
||||||
|
|
||||||
|
// Pod should fail
|
||||||
|
testPodFailSubpathError(f, pod, "")
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -319,7 +339,11 @@ var _ = utils.SIGDescribe("Subpath", func() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
func testBasicSubpath(f *framework.Framework, contents string, pod *v1.Pod) {
|
func testBasicSubpath(f *framework.Framework, contents string, pod *v1.Pod) {
|
||||||
setReadCommand(volumePath, &pod.Spec.Containers[0])
|
testBasicSubpathFile(f, contents, pod, volumePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testBasicSubpathFile(f *framework.Framework, contents string, pod *v1.Pod, filepath string) {
|
||||||
|
setReadCommand(filepath, &pod.Spec.Containers[0])
|
||||||
|
|
||||||
By(fmt.Sprintf("Creating pod %s", pod.Name))
|
By(fmt.Sprintf("Creating pod %s", pod.Name))
|
||||||
f.TestContainerOutput("atomic-volume-subpath", pod, 0, []string{contents})
|
f.TestContainerOutput("atomic-volume-subpath", pod, 0, []string{contents})
|
||||||
@ -412,6 +436,11 @@ func testPodSubpath(f *framework.Framework, subpath, volumeType string, source *
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
SecurityContext: &v1.PodSecurityContext{
|
||||||
|
SELinuxOptions: &v1.SELinuxOptions{
|
||||||
|
Level: "s0:c0,c1",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -473,7 +502,11 @@ func testReadFile(f *framework.Framework, file string, pod *v1.Pod, containerInd
|
|||||||
Expect(err).NotTo(HaveOccurred(), "while deleting pod")
|
Expect(err).NotTo(HaveOccurred(), "while deleting pod")
|
||||||
}
|
}
|
||||||
|
|
||||||
func testPodFailSupath(f *framework.Framework, pod *v1.Pod) {
|
func testPodFailSubpath(f *framework.Framework, pod *v1.Pod) {
|
||||||
|
testPodFailSubpathError(f, pod, "subPath")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testPodFailSubpathError(f *framework.Framework, pod *v1.Pod, errorMsg string) {
|
||||||
By(fmt.Sprintf("Creating pod %s", pod.Name))
|
By(fmt.Sprintf("Creating pod %s", pod.Name))
|
||||||
pod, err := f.ClientSet.CoreV1().Pods(f.Namespace.Name).Create(pod)
|
pod, err := f.ClientSet.CoreV1().Pods(f.Namespace.Name).Create(pod)
|
||||||
Expect(err).ToNot(HaveOccurred(), "while creating pod")
|
Expect(err).ToNot(HaveOccurred(), "while creating pod")
|
||||||
@ -494,7 +527,7 @@ func testPodFailSupath(f *framework.Framework, pod *v1.Pod) {
|
|||||||
events, err := f.ClientSet.CoreV1().Events(f.Namespace.Name).List(options)
|
events, err := f.ClientSet.CoreV1().Events(f.Namespace.Name).List(options)
|
||||||
Expect(err).NotTo(HaveOccurred(), "while getting pod events")
|
Expect(err).NotTo(HaveOccurred(), "while getting pod events")
|
||||||
Expect(len(events.Items)).NotTo(Equal(0), "no events found")
|
Expect(len(events.Items)).NotTo(Equal(0), "no events found")
|
||||||
Expect(events.Items[0].Message).To(ContainSubstring("subPath"), "subpath error not found")
|
Expect(events.Items[0].Message).To(ContainSubstring(errorMsg), fmt.Sprintf("%q error not found", errorMsg))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests that the existing subpath mount is detected when a container restarts
|
// Tests that the existing subpath mount is detected when a container restarts
|
||||||
@ -785,6 +818,47 @@ func (s *gcepdPVCSource) createVolume(f *framework.Framework) volInfo {
|
|||||||
s.pvc, err = framework.CreatePVC(f.ClientSet, f.Namespace.Name, pvc)
|
s.pvc, err = framework.CreatePVC(f.ClientSet, f.Namespace.Name, pvc)
|
||||||
framework.ExpectNoError(err, "Error creating PVC")
|
framework.ExpectNoError(err, "Error creating PVC")
|
||||||
|
|
||||||
|
// Launch pod to format the PD first
|
||||||
|
pod := &v1.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: fmt.Sprintf("gcepd-prep-%s", f.Namespace.Name),
|
||||||
|
},
|
||||||
|
Spec: v1.PodSpec{
|
||||||
|
Containers: []v1.Container{
|
||||||
|
{
|
||||||
|
Name: fmt.Sprintf("init-volume-%s", f.Namespace.Name),
|
||||||
|
Image: "busybox",
|
||||||
|
Command: []string{"/bin/sh", "-ec", "echo nothing"},
|
||||||
|
VolumeMounts: []v1.VolumeMount{
|
||||||
|
{
|
||||||
|
Name: volumeName,
|
||||||
|
MountPath: "/vol",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
RestartPolicy: v1.RestartPolicyNever,
|
||||||
|
Volumes: []v1.Volume{
|
||||||
|
{
|
||||||
|
Name: volumeName,
|
||||||
|
VolumeSource: v1.VolumeSource{
|
||||||
|
PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
|
||||||
|
ClaimName: s.pvc.Name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
pod, err = f.ClientSet.CoreV1().Pods(f.Namespace.Name).Create(pod)
|
||||||
|
Expect(err).ToNot(HaveOccurred(), "while creating gce pd init pod")
|
||||||
|
|
||||||
|
err = framework.WaitForPodSuccessInNamespace(f.ClientSet, pod.Name, pod.Namespace)
|
||||||
|
Expect(err).ToNot(HaveOccurred(), "while waiting for gce pd init pod to succeed")
|
||||||
|
|
||||||
|
err = framework.DeletePodWithWait(f, f.ClientSet, pod)
|
||||||
|
Expect(err).ToNot(HaveOccurred(), "while deleting gce pd init pod")
|
||||||
|
|
||||||
return volInfo{
|
return volInfo{
|
||||||
source: &v1.VolumeSource{
|
source: &v1.VolumeSource{
|
||||||
PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
|
PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
|
||||||
|
Loading…
Reference in New Issue
Block a user