mirror of https://github.com/k3s-io/k3s
Delete nsenter and containerized flag
parent
90434b3273
commit
995b7d3a49
|
@ -123,8 +123,6 @@ type KubeletFlags struct {
|
|||
// Whitelist of unsafe sysctls or sysctl patterns (ending in *).
|
||||
// +optional
|
||||
AllowedUnsafeSysctls []string
|
||||
// containerized should be set to true if kubelet is running in a container.
|
||||
Containerized bool
|
||||
// remoteRuntimeEndpoint is the endpoint of remote runtime service
|
||||
RemoteRuntimeEndpoint string
|
||||
// remoteImageEndpoint is the endpoint of remote image service
|
||||
|
@ -393,7 +391,6 @@ func (f *KubeletFlags) AddFlags(mainfs *pflag.FlagSet) {
|
|||
|
||||
fs.BoolVar(&f.RegisterNode, "register-node", f.RegisterNode, "Register the node with the apiserver. If --kubeconfig is not provided, this flag is irrelevant, as the Kubelet won't have an apiserver to register with.")
|
||||
fs.Var(utiltaints.NewTaintsVar(&f.RegisterWithTaints), "register-with-taints", "Register the node with the given list of taints (comma separated \"<key>=<value>:<effect>\"). No-op if register-node is false.")
|
||||
fs.BoolVar(&f.Containerized, "containerized", f.Containerized, "Running kubelet in a container.")
|
||||
|
||||
// EXPERIMENTAL FLAGS
|
||||
fs.StringVar(&f.ExperimentalMounterPath, "experimental-mounter-path", f.ExperimentalMounterPath, "[Experimental] Path of mounter binary. Leave empty to use the default mount.")
|
||||
|
|
|
@ -88,7 +88,6 @@ import (
|
|||
"k8s.io/kubernetes/pkg/util/flock"
|
||||
"k8s.io/kubernetes/pkg/util/mount"
|
||||
nodeutil "k8s.io/kubernetes/pkg/util/node"
|
||||
"k8s.io/kubernetes/pkg/util/nsenter"
|
||||
"k8s.io/kubernetes/pkg/util/oom"
|
||||
"k8s.io/kubernetes/pkg/util/rlimit"
|
||||
"k8s.io/kubernetes/pkg/version"
|
||||
|
@ -358,16 +357,6 @@ func UnsecuredDependencies(s *options.KubeletServer) (*kubelet.Dependencies, err
|
|||
|
||||
mounter := mount.New(s.ExperimentalMounterPath)
|
||||
var pluginRunner = exec.New()
|
||||
if s.Containerized {
|
||||
klog.V(2).Info("Running kubelet in containerized mode")
|
||||
ne, err := nsenter.NewNsenter(nsenter.DefaultHostRootFsPath, exec.New())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mounter = mount.NewNsenterMounter(s.RootDirectory, ne)
|
||||
// an exec interface which can use nsenter for flex plugin calls
|
||||
pluginRunner = nsenter.NewNsenterExecutor(nsenter.DefaultHostRootFsPath, exec.New())
|
||||
}
|
||||
|
||||
var dockerClientConfig *dockershim.ClientConfig
|
||||
if s.ContainerRuntime == kubetypes.DockerContainerRuntime {
|
||||
|
|
|
@ -1,470 +0,0 @@
|
|||
// +build linux
|
||||
|
||||
/*
|
||||
Copyright 2014 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 mount
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
"k8s.io/klog"
|
||||
utilfile "k8s.io/kubernetes/pkg/util/file"
|
||||
"k8s.io/kubernetes/pkg/util/nsenter"
|
||||
)
|
||||
|
||||
const (
|
||||
// hostProcMountsPath is the default mount path for rootfs
|
||||
hostProcMountsPath = "/rootfs/proc/1/mounts"
|
||||
// hostProcMountinfoPath is the default mount info path for rootfs
|
||||
hostProcMountinfoPath = "/rootfs/proc/1/mountinfo"
|
||||
)
|
||||
|
||||
// Currently, all docker containers receive their own mount namespaces.
|
||||
// NsenterMounter works by executing nsenter to run commands in
|
||||
// the host's mount namespace.
|
||||
type NsenterMounter struct {
|
||||
ne *nsenter.Nsenter
|
||||
// rootDir is location of /var/lib/kubelet directory.
|
||||
rootDir string
|
||||
}
|
||||
|
||||
// NewNsenterMounter creates a new mounter for kubelet that runs as a container.
|
||||
func NewNsenterMounter(rootDir string, ne *nsenter.Nsenter) *NsenterMounter {
|
||||
return &NsenterMounter{
|
||||
rootDir: rootDir,
|
||||
ne: ne,
|
||||
}
|
||||
}
|
||||
|
||||
// NsenterMounter implements mount.Interface
|
||||
var _ = Interface(&NsenterMounter{})
|
||||
|
||||
// Mount runs mount(8) in the host's root mount namespace. Aside from this
|
||||
// aspect, Mount has the same semantics as the mounter returned by mount.New()
|
||||
func (n *NsenterMounter) Mount(source string, target string, fstype string, options []string) error {
|
||||
bind, bindOpts, bindRemountOpts := isBind(options)
|
||||
|
||||
if bind {
|
||||
err := n.doNsenterMount(source, target, fstype, bindOpts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return n.doNsenterMount(source, target, fstype, bindRemountOpts)
|
||||
}
|
||||
|
||||
return n.doNsenterMount(source, target, fstype, options)
|
||||
}
|
||||
|
||||
// doNsenterMount nsenters the host's mount namespace and performs the
|
||||
// requested mount.
|
||||
func (n *NsenterMounter) doNsenterMount(source, target, fstype string, options []string) error {
|
||||
klog.V(5).Infof("nsenter mount %s %s %s %v", source, target, fstype, options)
|
||||
cmd, args := n.makeNsenterArgs(source, target, fstype, options)
|
||||
outputBytes, err := n.ne.Exec(cmd, args).CombinedOutput()
|
||||
if len(outputBytes) != 0 {
|
||||
klog.V(5).Infof("Output of mounting %s to %s: %v", source, target, string(outputBytes))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// makeNsenterArgs makes a list of argument to nsenter in order to do the
|
||||
// requested mount.
|
||||
func (n *NsenterMounter) makeNsenterArgs(source, target, fstype string, options []string) (string, []string) {
|
||||
mountCmd := n.ne.AbsHostPath("mount")
|
||||
mountArgs := makeMountArgs(source, target, fstype, options)
|
||||
|
||||
if systemdRunPath, hasSystemd := n.ne.SupportsSystemd(); hasSystemd {
|
||||
// Complete command line:
|
||||
// nsenter --mount=/rootfs/proc/1/ns/mnt -- /bin/systemd-run --description=... --scope -- /bin/mount -t <type> <what> <where>
|
||||
// Expected flow is:
|
||||
// * nsenter breaks out of container's mount namespace and executes
|
||||
// host's systemd-run.
|
||||
// * 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 container can be restarted and the fuse daemon survives.
|
||||
// * When the daemon dies (e.g. during unmount) systemd removes the
|
||||
// scope automatically.
|
||||
mountCmd, mountArgs = addSystemdScope(systemdRunPath, target, mountCmd, mountArgs)
|
||||
} else {
|
||||
// Fall back to simple mount when the host has no systemd.
|
||||
// Complete command line:
|
||||
// nsenter --mount=/rootfs/proc/1/ns/mnt -- /bin/mount -t <type> <what> <where>
|
||||
// Expected flow is:
|
||||
// * nsenter breaks out of container's mount namespace and executes host's /bin/mount.
|
||||
// * mount does its job, forks a fuse daemon if necessary and finishes.
|
||||
// * Any fuse daemon runs in cgroup of kubelet docker container,
|
||||
// restart of kubelet container will kill it!
|
||||
|
||||
// No code here, mountCmd and mountArgs use /bin/mount
|
||||
}
|
||||
|
||||
return mountCmd, mountArgs
|
||||
}
|
||||
|
||||
// Unmount runs umount(8) in the host's mount namespace.
|
||||
func (n *NsenterMounter) Unmount(target string) error {
|
||||
args := []string{target}
|
||||
// No need to execute systemd-run here, it's enough that unmount is executed
|
||||
// in the host's mount namespace. It will finish appropriate fuse daemon(s)
|
||||
// running in any scope.
|
||||
klog.V(5).Infof("nsenter unmount args: %v", args)
|
||||
outputBytes, err := n.ne.Exec("umount", args).CombinedOutput()
|
||||
if len(outputBytes) != 0 {
|
||||
klog.V(5).Infof("Output of unmounting %s: %v", target, string(outputBytes))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// List returns a list of all mounted filesystems in the host's mount namespace.
|
||||
func (*NsenterMounter) List() ([]MountPoint, error) {
|
||||
return listProcMounts(hostProcMountsPath)
|
||||
}
|
||||
|
||||
func (m *NsenterMounter) IsNotMountPoint(dir string) (bool, error) {
|
||||
return IsNotMountPoint(m, dir)
|
||||
}
|
||||
|
||||
func (*NsenterMounter) IsMountPointMatch(mp MountPoint, dir string) bool {
|
||||
deletedDir := fmt.Sprintf("%s\\040(deleted)", dir)
|
||||
return (mp.Path == dir) || (mp.Path == deletedDir)
|
||||
}
|
||||
|
||||
// IsLikelyNotMountPoint determines whether a path is a mountpoint by calling findmnt
|
||||
// in the host's root mount namespace.
|
||||
func (n *NsenterMounter) IsLikelyNotMountPoint(file string) (bool, error) {
|
||||
file, err := filepath.Abs(file)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
|
||||
// Check the directory exists
|
||||
if _, err = os.Stat(file); os.IsNotExist(err) {
|
||||
klog.V(5).Infof("findmnt: directory %s does not exist", file)
|
||||
return true, err
|
||||
}
|
||||
|
||||
// Resolve any symlinks in file, kernel would do the same and use the resolved path in /proc/mounts
|
||||
resolvedFile, err := n.EvalHostSymlinks(file)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
|
||||
// Add --first-only option: since we are testing for the absence of a mountpoint, it is sufficient to get only
|
||||
// the first of multiple possible mountpoints using --first-only.
|
||||
// Also add fstype output to make sure that the output of target file will give the full path
|
||||
// TODO: Need more refactoring for this function. Track the solution with issue #26996
|
||||
args := []string{"-o", "target,fstype", "--noheadings", "--first-only", "--target", resolvedFile}
|
||||
klog.V(5).Infof("nsenter findmnt args: %v", args)
|
||||
out, err := n.ne.Exec("findmnt", args).CombinedOutput()
|
||||
if err != nil {
|
||||
klog.V(2).Infof("Failed findmnt command for path %s: %s %v", resolvedFile, out, err)
|
||||
// Different operating systems behave differently for paths which are not mount points.
|
||||
// On older versions (e.g. 2.20.1) we'd get error, on newer ones (e.g. 2.26.2) we'd get "/".
|
||||
// It's safer to assume that it's not a mount point.
|
||||
return true, nil
|
||||
}
|
||||
mountTarget, err := parseFindMnt(string(out))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
klog.V(5).Infof("IsLikelyNotMountPoint findmnt output for path %s: %v:", resolvedFile, mountTarget)
|
||||
|
||||
if mountTarget == resolvedFile {
|
||||
klog.V(5).Infof("IsLikelyNotMountPoint: %s is a mount point", resolvedFile)
|
||||
return false, nil
|
||||
}
|
||||
klog.V(5).Infof("IsLikelyNotMountPoint: %s is not a mount point", resolvedFile)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// parse output of "findmnt -o target,fstype" and return just the target
|
||||
func parseFindMnt(out string) (string, error) {
|
||||
// cut trailing newline
|
||||
out = strings.TrimSuffix(out, "\n")
|
||||
// cut everything after the last space - it's the filesystem type
|
||||
i := strings.LastIndex(out, " ")
|
||||
if i == -1 {
|
||||
return "", fmt.Errorf("error parsing findmnt output, expected at least one space: %q", out)
|
||||
}
|
||||
return out[:i], nil
|
||||
}
|
||||
|
||||
// DeviceOpened checks if block device in use by calling Open with O_EXCL flag.
|
||||
// Returns true if open returns errno EBUSY, and false if errno is nil.
|
||||
// Returns an error if errno is any error other than EBUSY.
|
||||
// Returns with error if pathname is not a device.
|
||||
func (n *NsenterMounter) DeviceOpened(pathname string) (bool, error) {
|
||||
return exclusiveOpenFailsOnDevice(pathname)
|
||||
}
|
||||
|
||||
// PathIsDevice uses FileInfo returned from os.Stat to check if path refers
|
||||
// to a device.
|
||||
func (n *NsenterMounter) PathIsDevice(pathname string) (bool, error) {
|
||||
pathType, err := n.GetFileType(pathname)
|
||||
isDevice := pathType == FileTypeCharDev || pathType == FileTypeBlockDev
|
||||
return isDevice, err
|
||||
}
|
||||
|
||||
//GetDeviceNameFromMount given a mount point, find the volume id from checking /proc/mounts
|
||||
func (n *NsenterMounter) GetDeviceNameFromMount(mountPath, pluginDir string) (string, error) {
|
||||
return getDeviceNameFromMount(n, mountPath, pluginDir)
|
||||
}
|
||||
|
||||
func (n *NsenterMounter) MakeRShared(path string) error {
|
||||
return doMakeRShared(path, hostProcMountinfoPath)
|
||||
}
|
||||
|
||||
func (mounter *NsenterMounter) GetFileType(pathname string) (FileType, error) {
|
||||
var pathType FileType
|
||||
outputBytes, err := mounter.ne.Exec("stat", []string{"-L", "--printf=%F", pathname}).CombinedOutput()
|
||||
if err != nil {
|
||||
if strings.Contains(string(outputBytes), "No such file") {
|
||||
err = fmt.Errorf("%s does not exist", pathname)
|
||||
} else {
|
||||
err = fmt.Errorf("stat %s error: %v", pathname, string(outputBytes))
|
||||
}
|
||||
return pathType, err
|
||||
}
|
||||
|
||||
switch string(outputBytes) {
|
||||
case "socket":
|
||||
return FileTypeSocket, nil
|
||||
case "character special file":
|
||||
return FileTypeCharDev, nil
|
||||
case "block special file":
|
||||
return FileTypeBlockDev, nil
|
||||
case "directory":
|
||||
return FileTypeDirectory, nil
|
||||
case "regular file":
|
||||
return FileTypeFile, nil
|
||||
}
|
||||
|
||||
return pathType, fmt.Errorf("only recognise file, directory, socket, block device and character device")
|
||||
}
|
||||
|
||||
func (mounter *NsenterMounter) MakeDir(pathname string) error {
|
||||
args := []string{"-p", pathname}
|
||||
if _, err := mounter.ne.Exec("mkdir", args).CombinedOutput(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mounter *NsenterMounter) MakeFile(pathname string) error {
|
||||
args := []string{pathname}
|
||||
if _, err := mounter.ne.Exec("touch", args).CombinedOutput(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mounter *NsenterMounter) ExistsPath(pathname string) (bool, error) {
|
||||
// Resolve the symlinks but allow the target not to exist. EvalSymlinks
|
||||
// would return an generic error when the target does not exist.
|
||||
hostPath, err := mounter.ne.EvalSymlinks(pathname, false /* mustExist */)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
kubeletpath := mounter.ne.KubeletPath(hostPath)
|
||||
return utilfile.FileExists(kubeletpath)
|
||||
}
|
||||
|
||||
func (mounter *NsenterMounter) EvalHostSymlinks(pathname string) (string, error) {
|
||||
return mounter.ne.EvalSymlinks(pathname, true)
|
||||
}
|
||||
|
||||
func (mounter *NsenterMounter) CleanSubPaths(podDir string, volumeName string) error {
|
||||
return doCleanSubPaths(mounter, podDir, volumeName)
|
||||
}
|
||||
|
||||
func (mounter *NsenterMounter) PrepareSafeSubpath(subPath Subpath) (newHostPath string, cleanupAction func(), err error) {
|
||||
// Bind-mount the subpath to avoid using symlinks in subpaths.
|
||||
newHostPath, err = doNsEnterBindSubPath(mounter, subPath)
|
||||
|
||||
// There is no action when the container starts. Bind-mount will be cleaned
|
||||
// when container stops by CleanSubPaths.
|
||||
cleanupAction = nil
|
||||
return newHostPath, cleanupAction, err
|
||||
}
|
||||
|
||||
func (mounter *NsenterMounter) SafeMakeDir(subdir string, base string, perm os.FileMode) error {
|
||||
fullSubdirPath := filepath.Join(base, subdir)
|
||||
evaluatedSubdirPath, err := mounter.ne.EvalSymlinks(fullSubdirPath, false /* mustExist */)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error resolving symlinks in %s: %s", fullSubdirPath, err)
|
||||
}
|
||||
evaluatedSubdirPath = filepath.Clean(evaluatedSubdirPath)
|
||||
|
||||
evaluatedBase, err := mounter.ne.EvalSymlinks(base, true /* mustExist */)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error resolving symlinks in %s: %s", base, err)
|
||||
}
|
||||
evaluatedBase = filepath.Clean(evaluatedBase)
|
||||
|
||||
rootDir := filepath.Clean(mounter.rootDir)
|
||||
if PathWithinBase(evaluatedBase, rootDir) {
|
||||
// Base is in /var/lib/kubelet. This directory is shared between the
|
||||
// container with kubelet and the host. We don't need to add '/rootfs'.
|
||||
// This is useful when /rootfs is mounted as read-only - we can still
|
||||
// create subpaths for paths in /var/lib/kubelet.
|
||||
return doSafeMakeDir(evaluatedSubdirPath, evaluatedBase, perm)
|
||||
}
|
||||
|
||||
// Base is somewhere on the host's filesystem. Add /rootfs and try to make
|
||||
// the directory there.
|
||||
// This requires /rootfs to be writable.
|
||||
kubeletSubdirPath := mounter.ne.KubeletPath(evaluatedSubdirPath)
|
||||
kubeletBase := mounter.ne.KubeletPath(evaluatedBase)
|
||||
return doSafeMakeDir(kubeletSubdirPath, kubeletBase, perm)
|
||||
}
|
||||
|
||||
func (mounter *NsenterMounter) GetMountRefs(pathname string) ([]string, error) {
|
||||
exists, err := mounter.ExistsPath(pathname)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !exists {
|
||||
return []string{}, nil
|
||||
}
|
||||
hostpath, err := mounter.ne.EvalSymlinks(pathname, true /* mustExist */)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return searchMountPoints(hostpath, hostProcMountinfoPath)
|
||||
}
|
||||
|
||||
func doNsEnterBindSubPath(mounter *NsenterMounter, subpath Subpath) (hostPath string, err error) {
|
||||
// Linux, kubelet runs in a container:
|
||||
// - safely open the subpath
|
||||
// - bind-mount the subpath to target (this can be unsafe)
|
||||
// - check that we mounted the right thing by comparing device ID and inode
|
||||
// of the subpath (via safely opened fd) and the target (that's under our
|
||||
// control)
|
||||
|
||||
// Evaluate all symlinks here once for all subsequent functions.
|
||||
evaluatedHostVolumePath, err := mounter.ne.EvalSymlinks(subpath.VolumePath, true /*mustExist*/)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error resolving symlinks in %q: %v", subpath.VolumePath, err)
|
||||
}
|
||||
evaluatedHostSubpath, err := mounter.ne.EvalSymlinks(subpath.Path, true /*mustExist*/)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error resolving symlinks in %q: %v", subpath.Path, err)
|
||||
}
|
||||
klog.V(5).Infof("doBindSubPath %q (%q) for volumepath %q", subpath.Path, evaluatedHostSubpath, subpath.VolumePath)
|
||||
subpath.VolumePath = mounter.ne.KubeletPath(evaluatedHostVolumePath)
|
||||
subpath.Path = mounter.ne.KubeletPath(evaluatedHostSubpath)
|
||||
|
||||
// Check the subpath is correct and open it
|
||||
fd, err := safeOpenSubPath(mounter, subpath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer syscall.Close(fd)
|
||||
|
||||
alreadyMounted, bindPathTarget, err := prepareSubpathTarget(mounter, subpath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if alreadyMounted {
|
||||
return bindPathTarget, nil
|
||||
}
|
||||
|
||||
success := false
|
||||
defer func() {
|
||||
// Cleanup subpath on error
|
||||
if !success {
|
||||
klog.V(4).Infof("doNsEnterBindSubPath() failed for %q, cleaning up subpath", bindPathTarget)
|
||||
if cleanErr := cleanSubPath(mounter, subpath); cleanErr != nil {
|
||||
klog.Errorf("Failed to clean subpath %q: %v", bindPathTarget, cleanErr)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Leap of faith: optimistically expect that nobody has modified previously
|
||||
// expanded evalSubPath with evil symlinks and bind-mount it.
|
||||
// Mount is done on the host! don't use kubelet path!
|
||||
klog.V(5).Infof("bind mounting %q at %q", evaluatedHostSubpath, bindPathTarget)
|
||||
if err = mounter.Mount(evaluatedHostSubpath, bindPathTarget, "" /*fstype*/, []string{"bind"}); err != nil {
|
||||
return "", fmt.Errorf("error mounting %s: %s", evaluatedHostSubpath, err)
|
||||
}
|
||||
|
||||
// Check that the bind-mount target is the same inode and device as the
|
||||
// source that we keept open, i.e. we mounted the right thing.
|
||||
err = checkDeviceInode(fd, bindPathTarget)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error checking bind mount for subpath %s: %s", subpath.VolumePath, err)
|
||||
}
|
||||
|
||||
success = true
|
||||
klog.V(3).Infof("Bound SubPath %s into %s", subpath.Path, bindPathTarget)
|
||||
return bindPathTarget, nil
|
||||
}
|
||||
|
||||
// checkDeviceInode checks that opened file and path represent the same file.
|
||||
func checkDeviceInode(fd int, path string) error {
|
||||
var srcStat, dstStat unix.Stat_t
|
||||
err := unix.Fstat(fd, &srcStat)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error running fstat on subpath FD: %v", err)
|
||||
}
|
||||
|
||||
err = unix.Stat(path, &dstStat)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error running fstat on %s: %v", path, err)
|
||||
}
|
||||
|
||||
if srcStat.Dev != dstStat.Dev {
|
||||
return fmt.Errorf("different device number")
|
||||
}
|
||||
if srcStat.Ino != dstStat.Ino {
|
||||
return fmt.Errorf("different inode")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mounter *NsenterMounter) GetFSGroup(pathname string) (int64, error) {
|
||||
hostPath, err := mounter.ne.EvalSymlinks(pathname, true /* mustExist */)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
kubeletpath := mounter.ne.KubeletPath(hostPath)
|
||||
return getFSGroup(kubeletpath)
|
||||
}
|
||||
|
||||
func (mounter *NsenterMounter) GetSELinuxSupport(pathname string) (bool, error) {
|
||||
return getSELinuxSupport(pathname, hostProcMountsPath)
|
||||
}
|
||||
|
||||
func (mounter *NsenterMounter) GetMode(pathname string) (os.FileMode, error) {
|
||||
hostPath, err := mounter.ne.EvalSymlinks(pathname, true /* mustExist */)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
kubeletpath := mounter.ne.KubeletPath(hostPath)
|
||||
return getMode(kubeletpath)
|
||||
}
|
|
@ -1,122 +0,0 @@
|
|||
// +build !linux
|
||||
|
||||
/*
|
||||
Copyright 2014 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 mount
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
|
||||
"k8s.io/kubernetes/pkg/util/nsenter"
|
||||
)
|
||||
|
||||
type NsenterMounter struct{}
|
||||
|
||||
func NewNsenterMounter(rootDir string, ne *nsenter.Nsenter) *NsenterMounter {
|
||||
return &NsenterMounter{}
|
||||
}
|
||||
|
||||
var _ = Interface(&NsenterMounter{})
|
||||
|
||||
func (*NsenterMounter) Mount(source string, target string, fstype string, options []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*NsenterMounter) Unmount(target string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*NsenterMounter) List() ([]MountPoint, error) {
|
||||
return []MountPoint{}, nil
|
||||
}
|
||||
|
||||
func (m *NsenterMounter) IsNotMountPoint(dir string) (bool, error) {
|
||||
return IsNotMountPoint(m, dir)
|
||||
}
|
||||
|
||||
func (*NsenterMounter) IsMountPointMatch(mp MountPoint, dir string) bool {
|
||||
return (mp.Path == dir)
|
||||
}
|
||||
|
||||
func (*NsenterMounter) IsLikelyNotMountPoint(file string) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (*NsenterMounter) DeviceOpened(pathname string) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (*NsenterMounter) PathIsDevice(pathname string) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (*NsenterMounter) GetDeviceNameFromMount(mountPath, pluginDir string) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (*NsenterMounter) MakeRShared(path string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*NsenterMounter) GetFileType(_ string) (FileType, error) {
|
||||
return FileType("fake"), errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (*NsenterMounter) MakeDir(pathname string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*NsenterMounter) MakeFile(pathname string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*NsenterMounter) ExistsPath(pathname string) (bool, error) {
|
||||
return true, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (*NsenterMounter) EvalHostSymlinks(pathname string) (string, error) {
|
||||
return "", errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (*NsenterMounter) SafeMakeDir(pathname string, base string, perm os.FileMode) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*NsenterMounter) PrepareSafeSubpath(subPath Subpath) (newHostPath string, cleanupAction func(), err error) {
|
||||
return subPath.Path, nil, nil
|
||||
}
|
||||
|
||||
func (*NsenterMounter) CleanSubPaths(podDir string, volumeName string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*NsenterMounter) GetMountRefs(pathname string) ([]string, error) {
|
||||
return nil, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (*NsenterMounter) GetFSGroup(pathname string) (int64, error) {
|
||||
return -1, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (*NsenterMounter) GetSELinuxSupport(pathname string) (bool, error) {
|
||||
return false, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (*NsenterMounter) GetMode(pathname string) (os.FileMode, error) {
|
||||
return 0, errors.New("not implemented")
|
||||
}
|
|
@ -1,76 +0,0 @@
|
|||
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"exec.go",
|
||||
"exec_unsupported.go",
|
||||
"nsenter.go",
|
||||
"nsenter_unsupported.go",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/pkg/util/nsenter",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = select({
|
||||
"@io_bazel_rules_go//go/platform:android": [
|
||||
"//vendor/k8s.io/utils/exec:go_default_library",
|
||||
],
|
||||
"@io_bazel_rules_go//go/platform:darwin": [
|
||||
"//vendor/k8s.io/utils/exec:go_default_library",
|
||||
],
|
||||
"@io_bazel_rules_go//go/platform:dragonfly": [
|
||||
"//vendor/k8s.io/utils/exec:go_default_library",
|
||||
],
|
||||
"@io_bazel_rules_go//go/platform:freebsd": [
|
||||
"//vendor/k8s.io/utils/exec:go_default_library",
|
||||
],
|
||||
"@io_bazel_rules_go//go/platform:linux": [
|
||||
"//vendor/k8s.io/klog:go_default_library",
|
||||
"//vendor/k8s.io/utils/exec:go_default_library",
|
||||
],
|
||||
"@io_bazel_rules_go//go/platform:nacl": [
|
||||
"//vendor/k8s.io/utils/exec:go_default_library",
|
||||
],
|
||||
"@io_bazel_rules_go//go/platform:netbsd": [
|
||||
"//vendor/k8s.io/utils/exec:go_default_library",
|
||||
],
|
||||
"@io_bazel_rules_go//go/platform:openbsd": [
|
||||
"//vendor/k8s.io/utils/exec:go_default_library",
|
||||
],
|
||||
"@io_bazel_rules_go//go/platform:plan9": [
|
||||
"//vendor/k8s.io/utils/exec:go_default_library",
|
||||
],
|
||||
"@io_bazel_rules_go//go/platform:solaris": [
|
||||
"//vendor/k8s.io/utils/exec:go_default_library",
|
||||
],
|
||||
"@io_bazel_rules_go//go/platform:windows": [
|
||||
"//vendor/k8s.io/utils/exec:go_default_library",
|
||||
],
|
||||
"//conditions:default": [],
|
||||
}),
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["nsenter_test.go"],
|
||||
embed = [":go_default_library"],
|
||||
deps = select({
|
||||
"@io_bazel_rules_go//go/platform:linux": [
|
||||
"//vendor/k8s.io/utils/exec:go_default_library",
|
||||
],
|
||||
"//conditions:default": [],
|
||||
}),
|
||||
)
|
|
@ -1,8 +0,0 @@
|
|||
reviewers:
|
||||
- jsafrane
|
||||
- msau42
|
||||
- cofyc
|
||||
approvers:
|
||||
- jsafrane
|
||||
- msau42
|
||||
- cofyc
|
|
@ -1,67 +0,0 @@
|
|||
// +build linux
|
||||
|
||||
/*
|
||||
Copyright 2018 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 nsenter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"k8s.io/klog"
|
||||
"k8s.io/utils/exec"
|
||||
)
|
||||
|
||||
// Executor wraps executor interface to be executed via nsenter
|
||||
type Executor struct {
|
||||
// Exec implementation
|
||||
executor exec.Interface
|
||||
// Path to the host's root proc path
|
||||
hostProcMountNsPath string
|
||||
}
|
||||
|
||||
// NewNsenterExecutor returns new nsenter based executor
|
||||
func NewNsenterExecutor(hostRootFsPath string, executor exec.Interface) *Executor {
|
||||
hostProcMountNsPath := filepath.Join(hostRootFsPath, mountNsPath)
|
||||
nsExecutor := &Executor{
|
||||
hostProcMountNsPath: hostProcMountNsPath,
|
||||
executor: executor,
|
||||
}
|
||||
return nsExecutor
|
||||
}
|
||||
|
||||
// Command returns a command wrapped with nenter
|
||||
func (nsExecutor *Executor) Command(cmd string, args ...string) exec.Cmd {
|
||||
fullArgs := append([]string{fmt.Sprintf("--mount=%s", nsExecutor.hostProcMountNsPath), "--"},
|
||||
append([]string{cmd}, args...)...)
|
||||
klog.V(5).Infof("Running nsenter command: %v %v", nsenterPath, fullArgs)
|
||||
return nsExecutor.executor.Command(nsenterPath, fullArgs...)
|
||||
}
|
||||
|
||||
// CommandContext returns a CommandContext wrapped with nsenter
|
||||
func (nsExecutor *Executor) CommandContext(ctx context.Context, cmd string, args ...string) exec.Cmd {
|
||||
fullArgs := append([]string{fmt.Sprintf("--mount=%s", nsExecutor.hostProcMountNsPath), "--"},
|
||||
append([]string{cmd}, args...)...)
|
||||
klog.V(5).Infof("Running nsenter command: %v %v", nsenterPath, fullArgs)
|
||||
return nsExecutor.executor.CommandContext(ctx, nsenterPath, fullArgs...)
|
||||
}
|
||||
|
||||
// LookPath returns a LookPath wrapped with nsenter
|
||||
func (nsExecutor *Executor) LookPath(file string) (string, error) {
|
||||
return "", fmt.Errorf("not implemented, error looking up : %s", file)
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
// +build !linux
|
||||
|
||||
/*
|
||||
Copyright 2017 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 nsenter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/utils/exec"
|
||||
)
|
||||
|
||||
// Executor wraps executor interface to be executed via nsenter
|
||||
type Executor struct {
|
||||
// Exec implementation
|
||||
executor exec.Interface
|
||||
// Path to the host's root proc path
|
||||
hostProcMountNsPath string
|
||||
}
|
||||
|
||||
// NewNsenterExecutor returns new nsenter based executor
|
||||
func NewNsenterExecutor(hostRootFsPath string, executor exec.Interface) *Executor {
|
||||
nsExecutor := &Executor{
|
||||
hostProcMountNsPath: hostRootFsPath,
|
||||
executor: executor,
|
||||
}
|
||||
return nsExecutor
|
||||
}
|
||||
|
||||
// Command returns a command wrapped with nenter
|
||||
func (nsExecutor *Executor) Command(cmd string, args ...string) exec.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
// CommandContext returns a CommandContext wrapped with nsenter
|
||||
func (nsExecutor *Executor) CommandContext(ctx context.Context, cmd string, args ...string) exec.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
// LookPath returns a LookPath wrapped with nsenter
|
||||
func (nsExecutor *Executor) LookPath(file string) (string, error) {
|
||||
return "", fmt.Errorf("not implemented, error looking up : %s", file)
|
||||
}
|
|
@ -1,236 +0,0 @@
|
|||
// +build linux
|
||||
|
||||
/*
|
||||
Copyright 2017 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 nsenter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"k8s.io/utils/exec"
|
||||
|
||||
"k8s.io/klog"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultHostRootFsPath is path to host's filesystem mounted into container
|
||||
// with kubelet.
|
||||
DefaultHostRootFsPath = "/rootfs"
|
||||
// mountNsPath is the default mount namespace of the host
|
||||
mountNsPath = "/proc/1/ns/mnt"
|
||||
// nsenterPath is the default nsenter command
|
||||
nsenterPath = "nsenter"
|
||||
)
|
||||
|
||||
// Nsenter is part of experimental support for running the kubelet
|
||||
// in a container.
|
||||
//
|
||||
// Nsenter requires:
|
||||
//
|
||||
// 1. Docker >= 1.6 due to the dependency on the slave propagation mode
|
||||
// of the bind-mount of the kubelet root directory in the container.
|
||||
// Docker 1.5 used a private propagation mode for bind-mounts, so mounts
|
||||
// performed in the host's mount namespace do not propagate out to the
|
||||
// bind-mount in this docker version.
|
||||
// 2. The host's root filesystem must be available at /rootfs
|
||||
// 3. The nsenter binary must be on the Kubelet process' PATH in the container's
|
||||
// filesystem.
|
||||
// 4. The Kubelet process must have CAP_SYS_ADMIN (required by nsenter); at
|
||||
// the present, this effectively means that the kubelet is running in a
|
||||
// privileged container.
|
||||
// 5. The volume path used by the Kubelet must be the same inside and outside
|
||||
// the container and be writable by the container (to initialize volume)
|
||||
// contents. TODO: remove this requirement.
|
||||
// 6. The host image must have "mount", "findmnt", "umount", "stat", "touch",
|
||||
// "mkdir", "ls", "sh" and "chmod" binaries in /bin, /usr/sbin, or /usr/bin
|
||||
// 7. The host image should have systemd-run in /bin, /usr/sbin, or /usr/bin if
|
||||
// systemd is installed/enabled in the operating system.
|
||||
// For more information about mount propagation modes, see:
|
||||
// https://www.kernel.org/doc/Documentation/filesystems/sharedsubtree.txt
|
||||
type Nsenter struct {
|
||||
// a map of commands to their paths on the host filesystem
|
||||
paths map[string]string
|
||||
|
||||
// Path to the host filesystem, typically "/rootfs". Used only for testing.
|
||||
hostRootFsPath string
|
||||
|
||||
// Exec implementation, used only for testing
|
||||
executor exec.Interface
|
||||
}
|
||||
|
||||
// NewNsenter constructs a new instance of Nsenter
|
||||
func NewNsenter(hostRootFsPath string, executor exec.Interface) (*Nsenter, error) {
|
||||
ne := &Nsenter{
|
||||
hostRootFsPath: hostRootFsPath,
|
||||
executor: executor,
|
||||
}
|
||||
if err := ne.initPaths(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ne, nil
|
||||
}
|
||||
|
||||
func (ne *Nsenter) initPaths() error {
|
||||
ne.paths = map[string]string{}
|
||||
binaries := []string{
|
||||
"mount",
|
||||
"findmnt",
|
||||
"umount",
|
||||
"systemd-run",
|
||||
"stat",
|
||||
"touch",
|
||||
"mkdir",
|
||||
"sh",
|
||||
"chmod",
|
||||
"realpath",
|
||||
}
|
||||
// search for the required commands in other locations besides /usr/bin
|
||||
for _, binary := range binaries {
|
||||
// check for binary under the following directories
|
||||
for _, path := range []string{"/", "/bin", "/usr/sbin", "/usr/bin"} {
|
||||
binPath := filepath.Join(path, binary)
|
||||
if _, err := os.Stat(filepath.Join(ne.hostRootFsPath, binPath)); err != nil {
|
||||
continue
|
||||
}
|
||||
ne.paths[binary] = binPath
|
||||
break
|
||||
}
|
||||
// systemd-run is optional, bailout if we don't find any of the other binaries
|
||||
if ne.paths[binary] == "" && binary != "systemd-run" {
|
||||
return fmt.Errorf("unable to find %v", binary)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Exec executes nsenter commands in hostProcMountNsPath mount namespace
|
||||
func (ne *Nsenter) Exec(cmd string, args []string) exec.Cmd {
|
||||
hostProcMountNsPath := filepath.Join(ne.hostRootFsPath, mountNsPath)
|
||||
fullArgs := append([]string{fmt.Sprintf("--mount=%s", hostProcMountNsPath), "--"},
|
||||
append([]string{ne.AbsHostPath(cmd)}, args...)...)
|
||||
klog.V(5).Infof("Running nsenter command: %v %v", nsenterPath, fullArgs)
|
||||
return ne.executor.Command(nsenterPath, fullArgs...)
|
||||
}
|
||||
|
||||
// AbsHostPath returns the absolute runnable path for a specified command
|
||||
func (ne *Nsenter) AbsHostPath(command string) string {
|
||||
path, ok := ne.paths[command]
|
||||
if !ok {
|
||||
return command
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
// SupportsSystemd checks whether command systemd-run exists
|
||||
func (ne *Nsenter) SupportsSystemd() (string, bool) {
|
||||
systemdRunPath, ok := ne.paths["systemd-run"]
|
||||
return systemdRunPath, ok && systemdRunPath != ""
|
||||
}
|
||||
|
||||
// EvalSymlinks returns the path name on the host after evaluating symlinks on the
|
||||
// host.
|
||||
// mustExist makes EvalSymlinks to return error when the path does not
|
||||
// exist. When it's false, it evaluates symlinks of the existing part and
|
||||
// blindly adds the non-existing part:
|
||||
// pathname: /mnt/volume/non/existing/directory
|
||||
// /mnt/volume exists
|
||||
// non/existing/directory does not exist
|
||||
// -> It resolves symlinks in /mnt/volume to say /mnt/foo and returns
|
||||
// /mnt/foo/non/existing/directory.
|
||||
//
|
||||
// BEWARE! EvalSymlinks is not able to detect symlink looks with mustExist=false!
|
||||
// If /tmp/link is symlink to /tmp/link, EvalSymlinks(/tmp/link/foo) returns /tmp/link/foo.
|
||||
func (ne *Nsenter) EvalSymlinks(pathname string, mustExist bool) (string, error) {
|
||||
var args []string
|
||||
if mustExist {
|
||||
// "realpath -e: all components of the path must exist"
|
||||
args = []string{"-e", pathname}
|
||||
} else {
|
||||
// "realpath -m: no path components need exist or be a directory"
|
||||
args = []string{"-m", pathname}
|
||||
}
|
||||
outBytes, err := ne.Exec("realpath", args).CombinedOutput()
|
||||
if err != nil {
|
||||
klog.Infof("failed to resolve symbolic links on %s: %v", pathname, err)
|
||||
return "", err
|
||||
}
|
||||
return strings.TrimSpace(string(outBytes)), nil
|
||||
}
|
||||
|
||||
// KubeletPath returns the path name that can be accessed by containerized
|
||||
// kubelet. It is recommended to resolve symlinks on the host by EvalSymlinks
|
||||
// before calling this function
|
||||
func (ne *Nsenter) KubeletPath(pathname string) string {
|
||||
return filepath.Join(ne.hostRootFsPath, pathname)
|
||||
}
|
||||
|
||||
// NewFakeNsenter returns a Nsenter that does not run "nsenter --mount=... --",
|
||||
// but runs everything in the same mount namespace as the unit test binary.
|
||||
// rootfsPath is supposed to be a symlink, e.g. /tmp/xyz/rootfs -> /.
|
||||
// This fake Nsenter is enough for most operations, e.g. to resolve symlinks,
|
||||
// but it's not enough to call /bin/mount - unit tests don't run as root.
|
||||
func NewFakeNsenter(rootfsPath string) (*Nsenter, error) {
|
||||
executor := &fakeExec{
|
||||
rootfsPath: rootfsPath,
|
||||
}
|
||||
// prepare /rootfs/bin, usr/bin and usr/sbin
|
||||
bin := filepath.Join(rootfsPath, "bin")
|
||||
if err := os.Symlink("/bin", bin); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
usr := filepath.Join(rootfsPath, "usr")
|
||||
if err := os.Mkdir(usr, 0755); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
usrbin := filepath.Join(usr, "bin")
|
||||
if err := os.Symlink("/usr/bin", usrbin); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
usrsbin := filepath.Join(usr, "sbin")
|
||||
if err := os.Symlink("/usr/sbin", usrsbin); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewNsenter(rootfsPath, executor)
|
||||
}
|
||||
|
||||
type fakeExec struct {
|
||||
rootfsPath string
|
||||
}
|
||||
|
||||
func (f fakeExec) Command(cmd string, args ...string) exec.Cmd {
|
||||
// This will intentionaly panic if Nsenter does not provide enough arguments.
|
||||
realCmd := args[2]
|
||||
realArgs := args[3:]
|
||||
return exec.New().Command(realCmd, realArgs...)
|
||||
}
|
||||
|
||||
func (fakeExec) LookPath(file string) (string, error) {
|
||||
return "", errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (fakeExec) CommandContext(ctx context.Context, cmd string, args ...string) exec.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ exec.Interface = fakeExec{}
|
|
@ -1,311 +0,0 @@
|
|||
// +build linux
|
||||
|
||||
/*
|
||||
Copyright 2018 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 nsenter
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"k8s.io/utils/exec"
|
||||
)
|
||||
|
||||
func TestExec(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
command string
|
||||
args []string
|
||||
expectedOutput string
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "simple command",
|
||||
command: "echo",
|
||||
args: []string{"hello", "world"},
|
||||
expectedOutput: "hello world\n",
|
||||
},
|
||||
{
|
||||
name: "nozero exit code",
|
||||
command: "false",
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
executor := fakeExec{
|
||||
rootfsPath: "/rootfs",
|
||||
}
|
||||
for _, test := range tests {
|
||||
ns := Nsenter{
|
||||
hostRootFsPath: "/rootfs",
|
||||
executor: executor,
|
||||
}
|
||||
cmd := ns.Exec(test.command, test.args)
|
||||
outBytes, err := cmd.CombinedOutput()
|
||||
out := string(outBytes)
|
||||
if err != nil && !test.expectError {
|
||||
t.Errorf("Test %q: unexpected error: %s", test.name, err)
|
||||
}
|
||||
if err == nil && test.expectError {
|
||||
t.Errorf("Test %q: expected error, got none", test.name)
|
||||
}
|
||||
if test.expectedOutput != out {
|
||||
t.Errorf("test %q: expected output %q, got %q", test.name, test.expectedOutput, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKubeletPath(t *testing.T) {
|
||||
tests := []struct {
|
||||
rootfs string
|
||||
hostpath string
|
||||
expectedKubeletPath string
|
||||
}{
|
||||
{
|
||||
// simple join
|
||||
"/rootfs",
|
||||
"/some/path",
|
||||
"/rootfs/some/path",
|
||||
},
|
||||
{
|
||||
// squash slashes
|
||||
"/rootfs/",
|
||||
"//some/path",
|
||||
"/rootfs/some/path",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
ns := Nsenter{
|
||||
hostRootFsPath: test.rootfs,
|
||||
}
|
||||
out := ns.KubeletPath(test.hostpath)
|
||||
if out != test.expectedKubeletPath {
|
||||
t.Errorf("Expected path %q, got %q", test.expectedKubeletPath, out)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvalSymlinks(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
mustExist bool
|
||||
prepare func(tmpdir string) (src string, expectedDst string, err error)
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "simple file /src",
|
||||
mustExist: true,
|
||||
prepare: func(tmpdir string) (src string, expectedDst string, err error) {
|
||||
src = filepath.Join(tmpdir, "src")
|
||||
err = ioutil.WriteFile(src, []byte{}, 0644)
|
||||
return src, src, err
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "non-existing file /src",
|
||||
mustExist: true,
|
||||
prepare: func(tmpdir string) (src string, expectedDst string, err error) {
|
||||
src = filepath.Join(tmpdir, "src")
|
||||
return src, "", nil
|
||||
},
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "non-existing file /src/ with mustExist=false",
|
||||
mustExist: false,
|
||||
prepare: func(tmpdir string) (src string, expectedDst string, err error) {
|
||||
src = filepath.Join(tmpdir, "src")
|
||||
return src, src, nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "non-existing file /existing/path/src with mustExist=false with existing directories",
|
||||
mustExist: false,
|
||||
prepare: func(tmpdir string) (src string, expectedDst string, err error) {
|
||||
src = filepath.Join(tmpdir, "existing/path")
|
||||
if err := os.MkdirAll(src, 0755); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
src = filepath.Join(src, "src")
|
||||
return src, src, nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "simple symlink /src -> /dst",
|
||||
mustExist: false,
|
||||
prepare: func(tmpdir string) (src string, expectedDst string, err error) {
|
||||
dst := filepath.Join(tmpdir, "dst")
|
||||
if err = ioutil.WriteFile(dst, []byte{}, 0644); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
src = filepath.Join(tmpdir, "src")
|
||||
err = os.Symlink(dst, src)
|
||||
return src, dst, err
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "dangling symlink /src -> /non-existing-path",
|
||||
mustExist: true,
|
||||
prepare: func(tmpdir string) (src string, expectedDst string, err error) {
|
||||
dst := filepath.Join(tmpdir, "non-existing-path")
|
||||
src = filepath.Join(tmpdir, "src")
|
||||
err = os.Symlink(dst, src)
|
||||
return src, "", err
|
||||
},
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "dangling symlink /src -> /non-existing-path with mustExist=false",
|
||||
mustExist: false,
|
||||
prepare: func(tmpdir string) (src string, expectedDst string, err error) {
|
||||
dst := filepath.Join(tmpdir, "non-existing-path")
|
||||
src = filepath.Join(tmpdir, "src")
|
||||
err = os.Symlink(dst, src)
|
||||
return src, dst, err
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "symlink to directory /src/file, where /src is link to /dst",
|
||||
mustExist: true,
|
||||
prepare: func(tmpdir string) (src string, expectedDst string, err error) {
|
||||
dst := filepath.Join(tmpdir, "dst")
|
||||
if err = os.Mkdir(dst, 0755); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
dstFile := filepath.Join(dst, "file")
|
||||
if err = ioutil.WriteFile(dstFile, []byte{}, 0644); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
src = filepath.Join(tmpdir, "src")
|
||||
if err = os.Symlink(dst, src); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
srcFile := filepath.Join(src, "file")
|
||||
return srcFile, dstFile, nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "symlink to non-existing directory: /src/file, where /src is link to /dst and dst does not exist",
|
||||
mustExist: true,
|
||||
prepare: func(tmpdir string) (src string, expectedDst string, err error) {
|
||||
dst := filepath.Join(tmpdir, "dst")
|
||||
|
||||
src = filepath.Join(tmpdir, "src")
|
||||
if err = os.Symlink(dst, src); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
srcFile := filepath.Join(src, "file")
|
||||
return srcFile, "", nil
|
||||
},
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "symlink to non-existing directory: /src/file, where /src is link to /dst and dst does not exist with mustExist=false",
|
||||
mustExist: false,
|
||||
prepare: func(tmpdir string) (src string, expectedDst string, err error) {
|
||||
dst := filepath.Join(tmpdir, "dst")
|
||||
dstFile := filepath.Join(dst, "file")
|
||||
|
||||
src = filepath.Join(tmpdir, "src")
|
||||
if err = os.Symlink(dst, src); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
srcFile := filepath.Join(src, "file")
|
||||
return srcFile, dstFile, nil
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
ns := Nsenter{
|
||||
hostRootFsPath: "/rootfs",
|
||||
executor: fakeExec{
|
||||
rootfsPath: "/rootfs",
|
||||
},
|
||||
}
|
||||
|
||||
tmpdir, err := ioutil.TempDir("", "nsenter-hostpath-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
src, expectedDst, err := test.prepare(tmpdir)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
dst, err := ns.EvalSymlinks(src, test.mustExist)
|
||||
if err != nil && !test.expectError {
|
||||
t.Errorf("Test %q: unexpected error: %s", test.name, err)
|
||||
}
|
||||
if err == nil && test.expectError {
|
||||
t.Errorf("Test %q: expected error, got none", test.name)
|
||||
}
|
||||
if dst != expectedDst {
|
||||
t.Errorf("Test %q: expected destination %q, got %q", test.name, expectedDst, dst)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewNsenter(t *testing.T) {
|
||||
// Create a symlink /tmp/xyz/rootfs -> / and use it as rootfs path
|
||||
// It should resolve all binaries correctly, the test runs on Linux
|
||||
|
||||
tmpdir, err := ioutil.TempDir("", "nsenter-hostpath-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
rootfs := filepath.Join(tmpdir, "rootfs")
|
||||
if err = os.Symlink("/", rootfs); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = NewNsenter(rootfs, exec.New())
|
||||
if err != nil {
|
||||
t.Errorf("Error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewNsenterError(t *testing.T) {
|
||||
// Create empty dir /tmp/xyz/rootfs and use it as rootfs path
|
||||
// It should resolve all binaries correctly, the test runs on Linux
|
||||
|
||||
tmpdir, err := ioutil.TempDir("", "nsenter-hostpath-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
rootfs := filepath.Join(tmpdir, "rootfs")
|
||||
if err = os.MkdirAll(rootfs, 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = NewNsenter(rootfs, exec.New())
|
||||
if err == nil {
|
||||
t.Errorf("Expected error, got none")
|
||||
}
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
// +build !linux
|
||||
|
||||
/*
|
||||
Copyright 2017 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 nsenter
|
||||
|
||||
import (
|
||||
"k8s.io/utils/exec"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultHostRootFsPath is path to host's filesystem mounted into container
|
||||
// with kubelet.
|
||||
DefaultHostRootFsPath = "/rootfs"
|
||||
)
|
||||
|
||||
// Nsenter is part of experimental support for running the kubelet
|
||||
// in a container.
|
||||
type Nsenter struct {
|
||||
// a map of commands to their paths on the host filesystem
|
||||
Paths map[string]string
|
||||
}
|
||||
|
||||
// NewNsenter constructs a new instance of Nsenter
|
||||
func NewNsenter(hostRootFsPath string, executor exec.Interface) (*Nsenter, error) {
|
||||
return &Nsenter{}, nil
|
||||
}
|
||||
|
||||
// Exec executes nsenter commands in hostProcMountNsPath mount namespace
|
||||
func (ne *Nsenter) Exec(cmd string, args []string) exec.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
// AbsHostPath returns the absolute runnable path for a specified command
|
||||
func (ne *Nsenter) AbsHostPath(command string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// SupportsSystemd checks whether command systemd-run exists
|
||||
func (ne *Nsenter) SupportsSystemd() (string, bool) {
|
||||
return "", false
|
||||
}
|
Loading…
Reference in New Issue