github.com/hanks177/podman/v4@v4.1.3-0.20220613032544-16d90015bc83/pkg/specgen/generate/validate.go (about)

     1  package generate
     2  
     3  import (
     4  	"io/ioutil"
     5  	"os"
     6  	"path/filepath"
     7  
     8  	"github.com/containers/common/pkg/cgroups"
     9  	"github.com/containers/common/pkg/sysinfo"
    10  	"github.com/hanks177/podman/v4/pkg/specgen"
    11  	"github.com/hanks177/podman/v4/utils"
    12  	"github.com/pkg/errors"
    13  )
    14  
    15  // Verify resource limits are sanely set when running on cgroup v1.
    16  func verifyContainerResourcesCgroupV1(s *specgen.SpecGenerator) ([]string, error) {
    17  	warnings := []string{}
    18  
    19  	sysInfo := sysinfo.New(true)
    20  
    21  	if s.ResourceLimits == nil {
    22  		return warnings, nil
    23  	}
    24  
    25  	if s.ResourceLimits.Unified != nil {
    26  		return nil, errors.New("Cannot use --cgroup-conf without cgroup v2")
    27  	}
    28  
    29  	// Memory checks
    30  	if s.ResourceLimits.Memory != nil {
    31  		memory := s.ResourceLimits.Memory
    32  		if memory.Limit != nil && !sysInfo.MemoryLimit {
    33  			warnings = append(warnings, "Your kernel does not support memory limit capabilities or the cgroup is not mounted. Limitation discarded.")
    34  			memory.Limit = nil
    35  			memory.Swap = nil
    36  		}
    37  		if memory.Limit != nil && memory.Swap != nil && !sysInfo.SwapLimit {
    38  			warnings = append(warnings, "Your kernel does not support swap limit capabilities or the cgroup is not mounted. Memory limited without swap.")
    39  			memory.Swap = nil
    40  		}
    41  		if memory.Limit != nil && memory.Swap != nil && *memory.Swap < *memory.Limit {
    42  			return warnings, errors.New("minimum memoryswap limit should be larger than memory limit, see usage")
    43  		}
    44  		if memory.Limit == nil && memory.Swap != nil {
    45  			return warnings, errors.New("you should always set a memory limit when using a memoryswap limit, see usage")
    46  		}
    47  		if memory.Swappiness != nil {
    48  			if !sysInfo.MemorySwappiness {
    49  				warnings = append(warnings, "Your kernel does not support memory swappiness capabilities, or the cgroup is not mounted. Memory swappiness discarded.")
    50  				memory.Swappiness = nil
    51  			} else if *memory.Swappiness > 100 {
    52  				return warnings, errors.Errorf("invalid value: %v, valid memory swappiness range is 0-100", *memory.Swappiness)
    53  			}
    54  		}
    55  		if memory.Reservation != nil && !sysInfo.MemoryReservation {
    56  			warnings = append(warnings, "Your kernel does not support memory soft limit capabilities or the cgroup is not mounted. Limitation discarded.")
    57  			memory.Reservation = nil
    58  		}
    59  		if memory.Limit != nil && memory.Reservation != nil && *memory.Limit < *memory.Reservation {
    60  			return warnings, errors.New("minimum memory limit cannot be less than memory reservation limit, see usage")
    61  		}
    62  		if memory.DisableOOMKiller != nil && *memory.DisableOOMKiller && !sysInfo.OomKillDisable {
    63  			warnings = append(warnings, "Your kernel does not support OomKillDisable. OomKillDisable discarded.")
    64  			memory.DisableOOMKiller = nil
    65  		}
    66  	}
    67  
    68  	// Pids checks
    69  	if s.ResourceLimits.Pids != nil {
    70  		// TODO: Should this be 0, or checking that ResourceLimits.Pids
    71  		// is set at all?
    72  		if s.ResourceLimits.Pids.Limit >= 0 && !sysInfo.PidsLimit {
    73  			warnings = append(warnings, "Your kernel does not support pids limit capabilities or the cgroup is not mounted. PIDs limit discarded.")
    74  			s.ResourceLimits.Pids = nil
    75  		}
    76  	}
    77  
    78  	// CPU Checks
    79  	if s.ResourceLimits.CPU != nil {
    80  		cpu := s.ResourceLimits.CPU
    81  		if cpu.Shares != nil && !sysInfo.CPUShares {
    82  			warnings = append(warnings, "Your kernel does not support CPU shares or the cgroup is not mounted. Shares discarded.")
    83  			cpu.Shares = nil
    84  		}
    85  		if cpu.Period != nil && !sysInfo.CPUCfsPeriod {
    86  			warnings = append(warnings, "Your kernel does not support CPU cfs period or the cgroup is not mounted. Period discarded.")
    87  			cpu.Period = nil
    88  		}
    89  		if cpu.Period != nil && (*cpu.Period < 1000 || *cpu.Period > 1000000) {
    90  			return warnings, errors.New("CPU cfs period cannot be less than 1ms (i.e. 1000) or larger than 1s (i.e. 1000000)")
    91  		}
    92  		if cpu.Quota != nil && !sysInfo.CPUCfsQuota {
    93  			warnings = append(warnings, "Your kernel does not support CPU cfs quota or the cgroup is not mounted. Quota discarded.")
    94  			cpu.Quota = nil
    95  		}
    96  		if cpu.Quota != nil && *cpu.Quota < 1000 {
    97  			return warnings, errors.New("CPU cfs quota cannot be less than 1ms (i.e. 1000)")
    98  		}
    99  		if (cpu.Cpus != "" || cpu.Mems != "") && !sysInfo.Cpuset {
   100  			warnings = append(warnings, "Your kernel does not support cpuset or the cgroup is not mounted. CPUset discarded.")
   101  			cpu.Cpus = ""
   102  			cpu.Mems = ""
   103  		}
   104  
   105  		cpusAvailable, err := sysInfo.IsCpusetCpusAvailable(cpu.Cpus)
   106  		if err != nil {
   107  			return warnings, errors.Errorf("invalid value %s for cpuset cpus", cpu.Cpus)
   108  		}
   109  		if !cpusAvailable {
   110  			return warnings, errors.Errorf("requested CPUs are not available - requested %s, available: %s", cpu.Cpus, sysInfo.Cpus)
   111  		}
   112  
   113  		memsAvailable, err := sysInfo.IsCpusetMemsAvailable(cpu.Mems)
   114  		if err != nil {
   115  			return warnings, errors.Errorf("invalid value %s for cpuset mems", cpu.Mems)
   116  		}
   117  		if !memsAvailable {
   118  			return warnings, errors.Errorf("requested memory nodes are not available - requested %s, available: %s", cpu.Mems, sysInfo.Mems)
   119  		}
   120  	}
   121  
   122  	// Blkio checks
   123  	if s.ResourceLimits.BlockIO != nil {
   124  		blkio := s.ResourceLimits.BlockIO
   125  		if blkio.Weight != nil && !sysInfo.BlkioWeight {
   126  			warnings = append(warnings, "Your kernel does not support Block I/O weight or the cgroup is not mounted. Weight discarded.")
   127  			blkio.Weight = nil
   128  		}
   129  		if blkio.Weight != nil && (*blkio.Weight > 1000 || *blkio.Weight < 10) {
   130  			return warnings, errors.New("range of blkio weight is from 10 to 1000")
   131  		}
   132  		if len(blkio.WeightDevice) > 0 && !sysInfo.BlkioWeightDevice {
   133  			warnings = append(warnings, "Your kernel does not support Block I/O weight_device or the cgroup is not mounted. Weight-device discarded.")
   134  			blkio.WeightDevice = nil
   135  		}
   136  		if len(blkio.ThrottleReadBpsDevice) > 0 && !sysInfo.BlkioReadBpsDevice {
   137  			warnings = append(warnings, "Your kernel does not support BPS Block I/O read limit or the cgroup is not mounted. Block I/O BPS read limit discarded")
   138  			blkio.ThrottleReadBpsDevice = nil
   139  		}
   140  		if len(blkio.ThrottleWriteBpsDevice) > 0 && !sysInfo.BlkioWriteBpsDevice {
   141  			warnings = append(warnings, "Your kernel does not support BPS Block I/O write limit or the cgroup is not mounted. Block I/O BPS write limit discarded.")
   142  			blkio.ThrottleWriteBpsDevice = nil
   143  		}
   144  		if len(blkio.ThrottleReadIOPSDevice) > 0 && !sysInfo.BlkioReadIOpsDevice {
   145  			warnings = append(warnings, "Your kernel does not support IOPS Block read limit or the cgroup is not mounted. Block I/O IOPS read limit discarded.")
   146  			blkio.ThrottleReadIOPSDevice = nil
   147  		}
   148  		if len(blkio.ThrottleWriteIOPSDevice) > 0 && !sysInfo.BlkioWriteIOpsDevice {
   149  			warnings = append(warnings, "Your kernel does not support IOPS Block I/O write limit or the cgroup is not mounted. Block I/O IOPS write limit discarded.")
   150  			blkio.ThrottleWriteIOPSDevice = nil
   151  		}
   152  	}
   153  
   154  	return warnings, nil
   155  }
   156  
   157  // Verify resource limits are sanely set when running on cgroup v2.
   158  func verifyContainerResourcesCgroupV2(s *specgen.SpecGenerator) ([]string, error) {
   159  	warnings := []string{}
   160  
   161  	if s.ResourceLimits == nil {
   162  		return warnings, nil
   163  	}
   164  
   165  	if s.ResourceLimits.Memory != nil && s.ResourceLimits.Memory.Swap != nil {
   166  		own, err := utils.GetOwnCgroup()
   167  		if err != nil {
   168  			return warnings, err
   169  		}
   170  
   171  		if own == "/" {
   172  			// If running under the root cgroup try to create or reuse a "probe" cgroup to read memory values
   173  			own = "podman_probe"
   174  			_ = os.MkdirAll(filepath.Join("/sys/fs/cgroup", own), 0o755)
   175  			_ = ioutil.WriteFile("/sys/fs/cgroup/cgroup.subtree_control", []byte("+memory"), 0o644)
   176  		}
   177  
   178  		memoryMax := filepath.Join("/sys/fs/cgroup", own, "memory.max")
   179  		memorySwapMax := filepath.Join("/sys/fs/cgroup", own, "memory.swap.max")
   180  		_, errMemoryMax := os.Stat(memoryMax)
   181  		_, errMemorySwapMax := os.Stat(memorySwapMax)
   182  		// Differently than cgroup v1, the memory.*max files are not present in the
   183  		// root directory, so we cannot query directly that, so as best effort use
   184  		// the current cgroup.
   185  		// Check whether memory.max exists in the current cgroup and memory.swap.max
   186  		//  does not.  In this case we can be sure memory swap is not enabled.
   187  		// If both files don't exist, the memory controller might not be enabled
   188  		// for the current cgroup.
   189  		if errMemoryMax == nil && errMemorySwapMax != nil {
   190  			warnings = append(warnings, "Your kernel does not support swap limit capabilities or the cgroup is not mounted. Memory limited without swap.")
   191  			s.ResourceLimits.Memory.Swap = nil
   192  		}
   193  	}
   194  	return warnings, nil
   195  }
   196  
   197  // Verify resource limits are sanely set, removing any limits that are not
   198  // possible with the current cgroups config.
   199  func verifyContainerResources(s *specgen.SpecGenerator) ([]string, error) {
   200  	cgroup2, err := cgroups.IsCgroup2UnifiedMode()
   201  	if err != nil {
   202  		return []string{}, err
   203  	}
   204  	if cgroup2 {
   205  		return verifyContainerResourcesCgroupV2(s)
   206  	}
   207  	return verifyContainerResourcesCgroupV1(s)
   208  }