github.com/adityamillind98/moby@v23.0.0-rc.4+incompatible/pkg/sysinfo/sysinfo_linux.go (about)

     1  package sysinfo // import "github.com/docker/docker/pkg/sysinfo"
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path"
     7  	"strings"
     8  	"sync"
     9  
    10  	"github.com/containerd/cgroups"
    11  	"github.com/containerd/containerd/pkg/seccomp"
    12  	"github.com/moby/sys/mountinfo"
    13  	"github.com/sirupsen/logrus"
    14  )
    15  
    16  var (
    17  	readMountinfoOnce sync.Once
    18  	readMountinfoErr  error
    19  	cgroupMountinfo   []*mountinfo.Info
    20  )
    21  
    22  // readCgroupMountinfo returns a list of cgroup v1 mounts (i.e. the ones
    23  // with fstype of "cgroup") for the current running process.
    24  //
    25  // The results are cached (to avoid re-reading mountinfo which is relatively
    26  // expensive), so it is assumed that cgroup mounts are not being changed.
    27  func readCgroupMountinfo() ([]*mountinfo.Info, error) {
    28  	readMountinfoOnce.Do(func() {
    29  		cgroupMountinfo, readMountinfoErr = mountinfo.GetMounts(
    30  			mountinfo.FSTypeFilter("cgroup"),
    31  		)
    32  	})
    33  
    34  	return cgroupMountinfo, readMountinfoErr
    35  }
    36  
    37  func findCgroupV1Mountpoints() (map[string]string, error) {
    38  	mounts, err := readCgroupMountinfo()
    39  	if err != nil {
    40  		return nil, err
    41  	}
    42  
    43  	allSubsystems, err := cgroups.ParseCgroupFile("/proc/self/cgroup")
    44  	if err != nil {
    45  		return nil, fmt.Errorf("Failed to parse cgroup information: %v", err)
    46  	}
    47  
    48  	allMap := make(map[string]bool)
    49  	for s := range allSubsystems {
    50  		allMap[s] = false
    51  	}
    52  
    53  	mps := make(map[string]string)
    54  	for _, mi := range mounts {
    55  		for _, opt := range strings.Split(mi.VFSOptions, ",") {
    56  			seen, known := allMap[opt]
    57  			if known && !seen {
    58  				allMap[opt] = true
    59  				mps[strings.TrimPrefix(opt, "name=")] = mi.Mountpoint
    60  			}
    61  		}
    62  		if len(mps) >= len(allMap) {
    63  			break
    64  		}
    65  	}
    66  	return mps, nil
    67  }
    68  
    69  type infoCollector func(info *SysInfo)
    70  
    71  // WithCgroup2GroupPath specifies the cgroup v2 group path to inspect availability
    72  // of the controllers.
    73  //
    74  // WithCgroup2GroupPath is expected to be used for rootless mode with systemd driver.
    75  //
    76  // e.g. g = "/user.slice/user-1000.slice/user@1000.service"
    77  func WithCgroup2GroupPath(g string) Opt {
    78  	return func(o *SysInfo) {
    79  		if p := path.Clean(g); p != "" {
    80  			o.cg2GroupPath = p
    81  		}
    82  	}
    83  }
    84  
    85  // New returns a new SysInfo, using the filesystem to detect which features
    86  // the kernel supports.
    87  func New(options ...Opt) *SysInfo {
    88  	if cgroups.Mode() == cgroups.Unified {
    89  		return newV2(options...)
    90  	}
    91  	return newV1()
    92  }
    93  
    94  func newV1() *SysInfo {
    95  	var (
    96  		err     error
    97  		sysInfo = &SysInfo{}
    98  	)
    99  
   100  	ops := []infoCollector{
   101  		applyNetworkingInfo,
   102  		applyAppArmorInfo,
   103  		applySeccompInfo,
   104  		applyCgroupNsInfo,
   105  	}
   106  
   107  	sysInfo.cgMounts, err = findCgroupV1Mountpoints()
   108  	if err != nil {
   109  		logrus.Warn(err)
   110  	} else {
   111  		ops = append(ops,
   112  			applyMemoryCgroupInfo,
   113  			applyCPUCgroupInfo,
   114  			applyBlkioCgroupInfo,
   115  			applyCPUSetCgroupInfo,
   116  			applyPIDSCgroupInfo,
   117  			applyDevicesCgroupInfo,
   118  		)
   119  	}
   120  
   121  	for _, o := range ops {
   122  		o(sysInfo)
   123  	}
   124  	return sysInfo
   125  }
   126  
   127  // applyMemoryCgroupInfo adds the memory cgroup controller information to the info.
   128  func applyMemoryCgroupInfo(info *SysInfo) {
   129  	mountPoint, ok := info.cgMounts["memory"]
   130  	if !ok {
   131  		info.Warnings = append(info.Warnings, "Your kernel does not support cgroup memory limit")
   132  		return
   133  	}
   134  	info.MemoryLimit = ok
   135  
   136  	info.SwapLimit = cgroupEnabled(mountPoint, "memory.memsw.limit_in_bytes")
   137  	if !info.SwapLimit {
   138  		info.Warnings = append(info.Warnings, "Your kernel does not support swap memory limit")
   139  	}
   140  	info.MemoryReservation = cgroupEnabled(mountPoint, "memory.soft_limit_in_bytes")
   141  	if !info.MemoryReservation {
   142  		info.Warnings = append(info.Warnings, "Your kernel does not support memory reservation")
   143  	}
   144  	info.OomKillDisable = cgroupEnabled(mountPoint, "memory.oom_control")
   145  	if !info.OomKillDisable {
   146  		info.Warnings = append(info.Warnings, "Your kernel does not support oom control")
   147  	}
   148  	info.MemorySwappiness = cgroupEnabled(mountPoint, "memory.swappiness")
   149  	if !info.MemorySwappiness {
   150  		info.Warnings = append(info.Warnings, "Your kernel does not support memory swappiness")
   151  	}
   152  
   153  	// Option is deprecated, but still accepted on API < v1.42 with cgroups v1,
   154  	// so setting the field to allow feature detection.
   155  	info.KernelMemory = cgroupEnabled(mountPoint, "memory.kmem.limit_in_bytes")
   156  
   157  	// Option is deprecated in runc, but still accepted in our API, so setting
   158  	// the field to allow feature detection, but don't warn if it's missing, to
   159  	// make the daemon logs a bit less noisy.
   160  	info.KernelMemoryTCP = cgroupEnabled(mountPoint, "memory.kmem.tcp.limit_in_bytes")
   161  }
   162  
   163  // applyCPUCgroupInfo adds the cpu cgroup controller information to the info.
   164  func applyCPUCgroupInfo(info *SysInfo) {
   165  	mountPoint, ok := info.cgMounts["cpu"]
   166  	if !ok {
   167  		info.Warnings = append(info.Warnings, "Unable to find cpu cgroup in mounts")
   168  		return
   169  	}
   170  
   171  	info.CPUShares = cgroupEnabled(mountPoint, "cpu.shares")
   172  	if !info.CPUShares {
   173  		info.Warnings = append(info.Warnings, "Your kernel does not support CPU shares")
   174  	}
   175  
   176  	info.CPUCfs = cgroupEnabled(mountPoint, "cpu.cfs_quota_us")
   177  	if !info.CPUCfs {
   178  		info.Warnings = append(info.Warnings, "Your kernel does not support CPU CFS scheduler")
   179  	}
   180  
   181  	info.CPURealtime = cgroupEnabled(mountPoint, "cpu.rt_period_us")
   182  	if !info.CPURealtime {
   183  		info.Warnings = append(info.Warnings, "Your kernel does not support CPU realtime scheduler")
   184  	}
   185  }
   186  
   187  // applyBlkioCgroupInfo adds the blkio cgroup controller information to the info.
   188  func applyBlkioCgroupInfo(info *SysInfo) {
   189  	mountPoint, ok := info.cgMounts["blkio"]
   190  	if !ok {
   191  		info.Warnings = append(info.Warnings, "Unable to find blkio cgroup in mounts")
   192  		return
   193  	}
   194  
   195  	info.BlkioWeight = cgroupEnabled(mountPoint, "blkio.weight")
   196  	if !info.BlkioWeight {
   197  		info.Warnings = append(info.Warnings, "Your kernel does not support cgroup blkio weight")
   198  	}
   199  
   200  	info.BlkioWeightDevice = cgroupEnabled(mountPoint, "blkio.weight_device")
   201  	if !info.BlkioWeightDevice {
   202  		info.Warnings = append(info.Warnings, "Your kernel does not support cgroup blkio weight_device")
   203  	}
   204  
   205  	info.BlkioReadBpsDevice = cgroupEnabled(mountPoint, "blkio.throttle.read_bps_device")
   206  	if !info.BlkioReadBpsDevice {
   207  		info.Warnings = append(info.Warnings, "Your kernel does not support cgroup blkio throttle.read_bps_device")
   208  	}
   209  
   210  	info.BlkioWriteBpsDevice = cgroupEnabled(mountPoint, "blkio.throttle.write_bps_device")
   211  	if !info.BlkioWriteBpsDevice {
   212  		info.Warnings = append(info.Warnings, "Your kernel does not support cgroup blkio throttle.write_bps_device")
   213  	}
   214  	info.BlkioReadIOpsDevice = cgroupEnabled(mountPoint, "blkio.throttle.read_iops_device")
   215  	if !info.BlkioReadIOpsDevice {
   216  		info.Warnings = append(info.Warnings, "Your kernel does not support cgroup blkio throttle.read_iops_device")
   217  	}
   218  
   219  	info.BlkioWriteIOpsDevice = cgroupEnabled(mountPoint, "blkio.throttle.write_iops_device")
   220  	if !info.BlkioWriteIOpsDevice {
   221  		info.Warnings = append(info.Warnings, "Your kernel does not support cgroup blkio throttle.write_iops_device")
   222  	}
   223  }
   224  
   225  // applyCPUSetCgroupInfo adds the cpuset cgroup controller information to the info.
   226  func applyCPUSetCgroupInfo(info *SysInfo) {
   227  	mountPoint, ok := info.cgMounts["cpuset"]
   228  	if !ok {
   229  		info.Warnings = append(info.Warnings, "Unable to find cpuset cgroup in mounts")
   230  		return
   231  	}
   232  	info.Cpuset = ok
   233  
   234  	var err error
   235  
   236  	cpus, err := os.ReadFile(path.Join(mountPoint, "cpuset.cpus"))
   237  	if err != nil {
   238  		return
   239  	}
   240  	info.Cpus = strings.TrimSpace(string(cpus))
   241  
   242  	mems, err := os.ReadFile(path.Join(mountPoint, "cpuset.mems"))
   243  	if err != nil {
   244  		return
   245  	}
   246  	info.Mems = strings.TrimSpace(string(mems))
   247  }
   248  
   249  // applyPIDSCgroupInfo adds whether the pids cgroup controller is available to the info.
   250  func applyPIDSCgroupInfo(info *SysInfo) {
   251  	_, ok := info.cgMounts["pids"]
   252  	if !ok {
   253  		info.Warnings = append(info.Warnings, "Unable to find pids cgroup in mounts")
   254  		return
   255  	}
   256  	info.PidsLimit = true
   257  }
   258  
   259  // applyDevicesCgroupInfo adds whether the devices cgroup controller is available to the info.
   260  func applyDevicesCgroupInfo(info *SysInfo) {
   261  	_, ok := info.cgMounts["devices"]
   262  	info.CgroupDevicesEnabled = ok
   263  }
   264  
   265  // applyNetworkingInfo adds networking information to the info.
   266  func applyNetworkingInfo(info *SysInfo) {
   267  	info.IPv4ForwardingDisabled = !readProcBool("/proc/sys/net/ipv4/ip_forward")
   268  	info.BridgeNFCallIPTablesDisabled = !readProcBool("/proc/sys/net/bridge/bridge-nf-call-iptables")
   269  	info.BridgeNFCallIP6TablesDisabled = !readProcBool("/proc/sys/net/bridge/bridge-nf-call-ip6tables")
   270  }
   271  
   272  // applyAppArmorInfo adds whether AppArmor is enabled to the info.
   273  func applyAppArmorInfo(info *SysInfo) {
   274  	if _, err := os.Stat("/sys/kernel/security/apparmor"); !os.IsNotExist(err) {
   275  		if _, err := os.ReadFile("/sys/kernel/security/apparmor/profiles"); err == nil {
   276  			info.AppArmor = true
   277  		}
   278  	}
   279  }
   280  
   281  // applyCgroupNsInfo adds whether cgroupns is enabled to the info.
   282  func applyCgroupNsInfo(info *SysInfo) {
   283  	if _, err := os.Stat("/proc/self/ns/cgroup"); !os.IsNotExist(err) {
   284  		info.CgroupNamespaces = true
   285  	}
   286  }
   287  
   288  // applySeccompInfo checks if Seccomp is supported, via CONFIG_SECCOMP.
   289  func applySeccompInfo(info *SysInfo) {
   290  	info.Seccomp = seccomp.IsEnabled()
   291  }
   292  
   293  func cgroupEnabled(mountPoint, name string) bool {
   294  	_, err := os.Stat(path.Join(mountPoint, name))
   295  	return err == nil
   296  }
   297  
   298  func readProcBool(path string) bool {
   299  	val, err := os.ReadFile(path)
   300  	if err != nil {
   301  		return false
   302  	}
   303  	return strings.TrimSpace(string(val)) == "1"
   304  }