mirror of
https://github.com/kubernetes/kubernetes.git
synced 2026-06-09 00:34:10 -04:00
216 lines
8.9 KiB
Go
216 lines
8.9 KiB
Go
// +build linux
|
|
|
|
/*
|
|
Copyright 2021 The Kubernetes Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package subpath
|
|
|
|
import (
|
|
"fmt"
|
|
"os/exec"
|
|
"strings"
|
|
|
|
"k8s.io/klog/v2"
|
|
mountutils "k8s.io/utils/mount"
|
|
)
|
|
|
|
const (
|
|
// Default mount command if mounter path is not specified.
|
|
defaultMountCommand = "mount"
|
|
// Log message where sensitive mount options were removed
|
|
sensitiveOptionsRemoved = "<masked>"
|
|
)
|
|
|
|
// Mounter provides the subpath implementation of mount.Interface
|
|
// for the linux platform. This implementation assumes that the
|
|
// kubelet is running in the host's root mount namespace.
|
|
type Mounter struct {
|
|
mountutils.Interface
|
|
mounterPath string
|
|
withSystemd bool
|
|
}
|
|
|
|
// NewMounter returns a MountInterface for the current system.
|
|
// It provides options to override the default mounter behavior.
|
|
// mounterPath allows using an alternative to `/bin/mount` for mounting.
|
|
func NewMounter(mounter mountutils.Interface, mounterPath string) MountInterface {
|
|
return &Mounter{
|
|
Interface: mounter,
|
|
mounterPath: mounterPath,
|
|
withSystemd: detectSystemd(),
|
|
}
|
|
}
|
|
|
|
// MountSensitiveWithFlags is the same as MountSensitive() with additional mount flags
|
|
func (mounter *Mounter) MountSensitiveWithFlags(source string, target string, fstype string, options []string, sensitiveOptions []string, mountFlags []string) error {
|
|
// Path to mounter binary if containerized mounter is needed. Otherwise, it is set to empty.
|
|
// All Linux distros are expected to be shipped with a mount utility that a support bind mounts.
|
|
mounterPath := ""
|
|
bind, bindOpts, bindRemountOpts, bindRemountOptsSensitive := mountutils.MakeBindOptsSensitive(options, sensitiveOptions)
|
|
if bind {
|
|
err := mounter.doMount(mounterPath, defaultMountCommand, source, target, fstype, bindOpts, bindRemountOptsSensitive, mountFlags)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return mounter.doMount(mounterPath, defaultMountCommand, source, target, fstype, bindRemountOpts, bindRemountOptsSensitive, mountFlags)
|
|
}
|
|
// The list of filesystems that require containerized mounter on GCI image cluster
|
|
fsTypesNeedMounter := map[string]struct{}{
|
|
"nfs": {},
|
|
"glusterfs": {},
|
|
"ceph": {},
|
|
"cifs": {},
|
|
}
|
|
if _, ok := fsTypesNeedMounter[fstype]; ok {
|
|
mounterPath = mounter.mounterPath
|
|
}
|
|
return mounter.doMount(mounterPath, defaultMountCommand, source, target, fstype, options, sensitiveOptions, mountFlags)
|
|
}
|
|
|
|
// doMount runs the mount command. mounterPath is the path to mounter binary if containerized mounter is used.
|
|
// sensitiveOptions is an extension of options except they will not be logged (because they may contain sensitive material)
|
|
// mountFlags are additional flags used in the mount command that are not related with fstype and mount options
|
|
func (mounter *Mounter) doMount(mounterPath string, mountCmd string, source string, target string, fstype string, options []string, sensitiveOptions []string, mountFlags []string) error {
|
|
mountArgs, mountArgsLogStr := makeMountArgsSensitiveWithMountFlags(source, target, fstype, options, sensitiveOptions, mountFlags)
|
|
if len(mounterPath) > 0 {
|
|
mountArgs = append([]string{mountCmd}, mountArgs...)
|
|
mountArgsLogStr = mountCmd + " " + mountArgsLogStr
|
|
mountCmd = mounterPath
|
|
}
|
|
|
|
if mounter.withSystemd {
|
|
// Try to run mount via systemd-run --scope. This will escape the
|
|
// service where kubelet runs and any fuse daemons will be started in a
|
|
// specific scope. kubelet service than can be restarted without killing
|
|
// these fuse daemons.
|
|
//
|
|
// Complete command line (when mounterPath is not used):
|
|
// systemd-run --description=... --scope -- mount -t <type> <what> <where>
|
|
//
|
|
// Expected flow:
|
|
// * systemd-run creates a transient scope (=~ cgroup) and executes its
|
|
// argument (/bin/mount) there.
|
|
// * mount does its job, forks a fuse daemon if necessary and finishes.
|
|
// (systemd-run --scope finishes at this point, returning mount's exit
|
|
// code and stdout/stderr - thats one of --scope benefits).
|
|
// * systemd keeps the fuse daemon running in the scope (i.e. in its own
|
|
// cgroup) until the fuse daemon dies (another --scope benefit).
|
|
// Kubelet service can be restarted and the fuse daemon survives.
|
|
// * When the fuse daemon dies (e.g. during unmount) systemd removes the
|
|
// scope automatically.
|
|
//
|
|
// systemd-mount is not used because it's too new for older distros
|
|
// (CentOS 7, Debian Jessie).
|
|
mountCmd, mountArgs, mountArgsLogStr = mountutils.AddSystemdScopeSensitive("systemd-run", target, mountCmd, mountArgs, mountArgsLogStr)
|
|
// } else {
|
|
// No systemd-run on the host (or we failed to check it), assume kubelet
|
|
// does not run as a systemd service.
|
|
// No code here, mountCmd and mountArgs are already populated.
|
|
}
|
|
|
|
// Logging with sensitive mount options removed.
|
|
klog.V(4).Infof("Mounting cmd (%s) with arguments (%s)", mountCmd, mountArgsLogStr)
|
|
command := exec.Command(mountCmd, mountArgs...)
|
|
output, err := command.CombinedOutput()
|
|
if err != nil {
|
|
klog.Errorf("Mount failed: %v\nMounting command: %s\nMounting arguments: %s\nOutput: %s\n", err, mountCmd, mountArgsLogStr, string(output))
|
|
return fmt.Errorf("mount failed: %v\nMounting command: %s\nMounting arguments: %s\nOutput: %s",
|
|
err, mountCmd, mountArgsLogStr, string(output))
|
|
}
|
|
return err
|
|
}
|
|
|
|
// detectSystemd returns true if OS runs with systemd as init. When not sure
|
|
// (permission errors, ...), it returns false.
|
|
// There may be different ways how to detect systemd, this one makes sure that
|
|
// systemd-runs (needed by Mount()) works.
|
|
func detectSystemd() bool {
|
|
if _, err := exec.LookPath("systemd-run"); err != nil {
|
|
klog.V(2).Infof("Detected OS without systemd")
|
|
return false
|
|
}
|
|
// Try to run systemd-run --scope /bin/true, that should be enough
|
|
// to make sure that systemd is really running and not just installed,
|
|
// which happens when running in a container with a systemd-based image
|
|
// but with different pid 1.
|
|
cmd := exec.Command("systemd-run", "--description=Kubernetes systemd probe", "--scope", "true")
|
|
output, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
klog.V(2).Infof("Cannot run systemd-run, assuming non-systemd OS")
|
|
klog.V(4).Infof("systemd-run failed with: %v", err)
|
|
klog.V(4).Infof("systemd-run output: %s", string(output))
|
|
return false
|
|
}
|
|
klog.V(2).Infof("Detected OS with systemd")
|
|
return true
|
|
}
|
|
|
|
// makeMountArgsSensitiveWithMountFlags makes the arguments to the mount(8) command.
|
|
// sensitiveOptions is an extension of options except they will not be logged (because they may contain sensitive material)
|
|
// mountFlags are additional mount flags that are not related with the fstype and mount options
|
|
func makeMountArgsSensitiveWithMountFlags(source, target, fstype string, options []string, sensitiveOptions []string, mountFlags []string) (mountArgs []string, mountArgsLogStr string) {
|
|
// Build mount command as follows:
|
|
// mount [$mountFlags] [-t $fstype] [-o $options] [$source] $target
|
|
mountArgs = []string{}
|
|
mountArgsLogStr = ""
|
|
|
|
mountArgs = append(mountArgs, mountFlags...)
|
|
mountArgsLogStr += strings.Join(mountFlags, " ")
|
|
|
|
if len(fstype) > 0 {
|
|
mountArgs = append(mountArgs, "-t", fstype)
|
|
mountArgsLogStr += strings.Join(mountArgs, " ")
|
|
}
|
|
if len(options) > 0 || len(sensitiveOptions) > 0 {
|
|
combinedOptions := []string{}
|
|
combinedOptions = append(combinedOptions, options...)
|
|
combinedOptions = append(combinedOptions, sensitiveOptions...)
|
|
mountArgs = append(mountArgs, "-o", strings.Join(combinedOptions, ","))
|
|
// exclude sensitiveOptions from log string
|
|
mountArgsLogStr += " -o " + sanitizedOptionsForLogging(options, sensitiveOptions)
|
|
}
|
|
if len(source) > 0 {
|
|
mountArgs = append(mountArgs, source)
|
|
mountArgsLogStr += " " + source
|
|
}
|
|
mountArgs = append(mountArgs, target)
|
|
mountArgsLogStr += " " + target
|
|
|
|
return mountArgs, mountArgsLogStr
|
|
}
|
|
|
|
// sanitizedOptionsForLogging will return a comma separated string containing
|
|
// options and sensitiveOptions. Each entry in sensitiveOptions will be
|
|
// replaced with the string sensitiveOptionsRemoved
|
|
// e.g. o1,o2,<masked>,<masked>
|
|
func sanitizedOptionsForLogging(options []string, sensitiveOptions []string) string {
|
|
separator := ""
|
|
if len(options) > 0 && len(sensitiveOptions) > 0 {
|
|
separator = ","
|
|
}
|
|
|
|
sensitiveOptionsStart := ""
|
|
sensitiveOptionsEnd := ""
|
|
if len(sensitiveOptions) > 0 {
|
|
sensitiveOptionsStart = strings.Repeat(sensitiveOptionsRemoved+",", len(sensitiveOptions)-1)
|
|
sensitiveOptionsEnd = sensitiveOptionsRemoved
|
|
}
|
|
|
|
return strings.Join(options, ",") +
|
|
separator +
|
|
sensitiveOptionsStart +
|
|
sensitiveOptionsEnd
|
|
}
|