GOMAXPROCS, cgroups, and Containers

Updated

September 8, 2026

GOMAXPROCS, cgroups, and Containers

Overview

In Kubernetes and cgroup-limited environments, CPU quota ≠ host core count. Older Go versions defaulted GOMAXPROCS to machine CPUs, causing thrashing inside small pods. Modern Go improves cgroup awareness — still verify in your version and platform.

Diagram: Quota mismatch

flow:
  [Pod]
       |
       v
  [Bad]

  [Pod]
       |
       v
  [Good]

Failure Mode

Pod limit: 500m CPU (0.5 core)
GOMAXPROCS=16 (node size)
  -> 16 threads fight for 0.5 CPU
  -> latency noise, steal time, GC assists hurt more

Practices

// log at boot
slog.Info("sched", "gomaxprocs", runtime.GOMAXPROCS(0), "numCPU", runtime.NumCPU())
  • Prefer runtime auto-detect on current Go
  • Or set explicitly from downward API / cgroup read
  • Libraries like automaxprocs existed historically for this gap

Memory Side

Pair CPU limits with GOMEMLIMIT near container memory limit (leave headroom for non-Go RSS, caches).

GOMEMLIMIT=400MiB ./app

Experiment

go run .
package main

import (
    "fmt"
    "runtime"
)

func main() {
    fmt.Println("NumCPU", runtime.NumCPU())
    fmt.Println("GOMAXPROCS", runtime.GOMAXPROCS(0))
}

Run inside and outside a constrained container if you have Docker:

docker run --rm --cpus=0.5 -v "$PWD":/app -w /app golang:1.27 go run .

What to notice: Compare printed values to --cpus quota on your platform/Go version.

Try next: Load test a CPU-bound endpoint with GOMAXPROCS 1 vs 4 under a 1 CPU quota.