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 }