Before Go 1.25, GOMAXPROCS defaulted to the logical CPU count of the machine. A container limited to two CPUs on a host with 32 logical CPUs got 32. That's 32 Ps running goroutines against a quota worth two CPUs of time per period, so the process burns through its quota early and sits throttled for the rest of each period.
Go 1.25 changed the default on Linux. The runtime reads the cgroup CPU bandwidth limit and uses it when it's lower than the CPU count. A fractional limit rounds up. The default never goes below 2 unless the machine itself has fewer CPUs. It also rechecks periodically, so a limit changed while the process runs gets picked up.
Three ways to not get it. GOMAXPROCS in the environment, a call to runtime.GOMAXPROCS with a value, or a go line of 1.24 or lower in go.mod, because containermaxprocs=0 is the default there. And it reads the limit only. A CPU request without a limit changes nothing. In Kubernetes terms that's limits.cpu, and a pod with only requests.cpu keeps the old default.
The probe I use prints two lines.
package main
import (
"fmt"
"runtime"
)
func main() {
fmt.Println("GOMAXPROCS", runtime.GOMAXPROCS(0))
fmt.Println("NumCPU", runtime.NumCPU())
}
Built with this go.mod, so the new default applies.
module probe
go 1.25
NumCPU is the number the old default used. If the two lines match inside a container whose limit is below the CPU count, one of the three things above is in the way.
So the number I care about went from 32 runnable Ps to two.