github.com/containers/podman/v4@v4.9.4/pkg/specgen/generate/validate.go (about)

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