Kubernetes resource management is moving toward a simpler and more consistent Linux foundation: cgroup v2.

The transition matters because Kubernetes relies on Linux control groups to enforce CPU and memory limits, account for resource usage, and isolate workloads on shared nodes.

For developers, the migration can appear invisible. A Deployment still contains:

Code
resources:
  requests:
    cpu: "250m"
    memory: "256Mi"
  limits:
    cpu: "1"
    memory: "512Mi"

But underneath that configuration, the Linux resource-management model is changing.

That creates an important engineering question:

What should Kubernetes teams test when moving workloads from cgroup v1 to cgroup v2?

The answer is broader than checking whether Pods start.

A production migration should validate the complete path:

Code
Kubernetes
    |
    v
Kubelet
    |
    v
Container Runtime
    |
    v
cgroup v2
    |
    v
Linux Kernel
    |
    v
Application

The biggest risks are usually not Kubernetes manifests themselves. They are older runtimes, monitoring agents, security tools, native software, and custom scripts that make assumptions about the old cgroup interface.

Why cgroup v2 Matters

cgroup v1 uses separate hierarchies for different resource controllers.

cgroup v2 provides a unified hierarchy.

That architectural difference gives Linux and Kubernetes a more consistent way to manage resources.

Conceptually:

Code
cgroup v1

CPU hierarchy
Memory hierarchy
Device hierarchy
Other controllers
      |
      v
Multiple resource trees

versus:

Code
cgroup v2

Unified hierarchy
      |
      +--> CPU
      +--> Memory
      +--> I/O
      +--> Other controllers

Kubernetes has supported cgroup v2 as stable since Kubernetes 1.25. Kubernetes 1.35 deprecates cgroup v1, making the migration increasingly important for cluster operators. (kubernetes.io)

Kubernetes Still Uses the Same Resource Concepts

Moving to cgroup v2 does not eliminate Kubernetes resource requests and limits.

For example:

Code
resources:
  requests:
    cpu: "500m"
    memory: "512Mi"
  limits:
    cpu: "1"
    memory: "1Gi"

The meaning remains familiar:

Code
Request
   |
   v
Scheduling decision

Limit
   |
   v
Runtime resource constraint

The underlying implementation changes.

That distinction is important because application teams generally do not need to rewrite every Deployment just because nodes move to cgroup v2.

The migration is primarily an infrastructure compatibility exercise.

The First Thing to Check: Your Nodes

Do not assume your entire cluster uses one cgroup version.

Clusters can contain node pools created at different times.

A simple Linux check is:

Code
stat -fc %T /sys/fs/cgroup/

A cgroup v2 system reports:

Code
cgroup2fs

You can also inspect mounts:

Code
mount | grep cgroup

The key distinction is:

Code
Cluster version
      !=
Node cgroup version

This is particularly important in managed Kubernetes environments where existing node pools may retain older configuration.

The cgroup Driver Is Not the Same as the cgroup Version

Another common migration mistake is treating these as the same setting.

There are two separate concepts:

Code
cgroup version
    |
    +--> v1
    +--> v2

and:

Code
cgroup driver
    |
    +--> systemd
    +--> cgroupfs

For modern Kubernetes systems using systemd, the recommended configuration is generally the systemd cgroup driver.

A kubelet configuration can look like:

Code
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cgroupDriver: systemd

The container runtime must be configured consistently.

A healthy node therefore has a coherent relationship:

Code
systemd
   |
   +--> kubelet
   |
   +--> container runtime
   |
   v
cgroup v2

Kubernetes recommends the systemd driver when systemd is the init system. (kubernetes.io)

Why Old Tools Can Break

The most important compatibility issue is software that reads the cgroup filesystem directly.

An application may contain code such as:

Code
/sys/fs/cgroup/memory/...

That path reflects the older cgroup v1 model.

cgroup v2 uses a unified hierarchy with different files and semantics.

The result can be:

Code
Application
    |
    v
Assumes cgroup v1
    |
    v
Looks for old path
    |
    v
Path does not exist

The application may crash.

Or worse, it may silently fall back to host-level resource information.

That can cause incorrect memory or CPU decisions.

Container Resource Detection Is Critical

Consider a Kubernetes node with:

Code
Host memory = 64 GB

and a Pod with:

Code
limits:
  memory: "1Gi"

The application should understand that it has approximately a 1 GiB memory limit.

If it incorrectly believes the host has 64 GB available, it can allocate far more memory than Kubernetes intended.

That can produce:

Code
Excessive allocation
      |
      v
Memory pressure
      |
      v
OOM kill
      |
      v
Pod restart

This is why runtime compatibility matters.

The application does not need to know that cgroup v2 exists, but the runtime may need to understand it.

Check .NET Runtime Behavior

For C# and .NET workloads, test container resource behavior explicitly.

A .NET application might run with:

Code
resources:
  requests:
    cpu: "250m"
    memory: "512Mi"
  limits:
    cpu: "1"
    memory: "1Gi"

Then test:

Code
Normal workload
High allocation rate
CPU saturation
Memory pressure
Garbage collection
Pod restart
Horizontal scaling

Do not rely solely on:

Code
kubectl get pods

A Pod being Running only proves that the process is alive.

It does not prove that resource limits are being interpreted correctly.

Java Has Similar Concerns

Java applications have historically required explicit container-awareness improvements.

Modern JDK versions have strong support for cgroup v2, but applications running older Java versions should be checked before migration.

A Java runtime may use container limits to determine:

Code
Heap size
CPU count
Garbage collection configuration
Thread behavior

If those values are calculated incorrectly, the application can behave badly under Kubernetes resource constraints.

This is particularly important for memory-heavy JVM applications.

Node.js Requires Runtime Awareness

Node.js applications can also depend on container resource detection.

For example:

Code
Kubernetes memory limit
        |
        v
cgroup v2
        |
        v
libuv
        |
        v
Node.js runtime
        |
        v
V8 heap configuration

If the runtime does not correctly detect the container limit, V8 may use inappropriate memory assumptions.

This is one reason runtime upgrades should be part of cgroup migration planning.

A cluster upgrade can expose problems that were previously hidden by the older cgroup implementation.

Monitoring Is Often the Biggest Risk

Application developers may never inspect /sys/fs/cgroup.

Monitoring software often does.

A node may contain:

Code
Metrics agent
APM agent
Security agent
Container runtime monitor
Log collector
Custom daemon

These components can use cgroup information to determine:

C#
Which container owns this process?
How much CPU is it consuming?
How much memory is it using?
Which Pod generated this event?

If the agent expects cgroup v1 paths, metrics can disappear or become incorrect after migration.

The dangerous scenario is not:

Code
Monitoring crashes

It is:

Code
Monitoring continues
but reports incorrect data

That can hide production incidents.

Validate Your Observability Stack

Before migration, inventory:

Code
Prometheus collectors
OpenTelemetry agents
APM agents
Node exporters
Cloud monitoring agents
Security monitoring
Custom exporters

Then check whether each component supports cgroup v2.

Test:

Code
CPU metrics
Memory metrics
Container identification
Pod identification
OOM events
Node pressure
Filesystem metrics

Compare the output before and after migration.

For example:

Code
Before:

Pod A memory = 420 MiB

After:

Pod A memory = 420 MiB

If the post-migration value suddenly becomes:

Code
Host memory = 32 GiB

you have found a resource-accounting problem.

Security Agents Need the Same Validation

Security tooling can also depend on cgroups.

Runtime security systems may associate processes with containers using cgroup information.

For example:

Code
Linux process
     |
     v
cgroup identifier
     |
     v
Container
     |
     v
Pod

If that relationship breaks, the security system may:

  • fail to attribute events

  • report incorrect container names

  • lose process visibility

  • generate false alerts

  • miss expected activity

Security validation should therefore be part of the migration checklist.

CPU Behavior Should Be Benchmarked

cgroup v2 changes the underlying CPU-control implementation.

cgroup v1 uses CPU shares.

cgroup v2 uses CPU weight.

Kubernetes has documented work to improve conversion between the two models so that existing Kubernetes resource specifications produce more consistent behavior during migration. (kubernetes.io)

For most workloads, this should not require application changes.

But CPU-sensitive systems should still be tested.

Examples include:

Code
High-frequency APIs
CPU-bound workers
Batch processing
Video encoding
Compilation workloads
Latency-sensitive services

Run workloads under controlled contention:

Code
Pod A: CPU request = 1
Pod B: CPU request = 1
Pod C: CPU request = 2

Then measure actual CPU allocation and application latency.

Memory Behavior Is Even More Important

Memory limits should be tested under pressure.

Suppose:

Code
resources:
  limits:
    memory: "512Mi"

A realistic test should deliberately push the application toward that boundary.

Measure:

Code
Working set
RSS
Managed heap
GC activity
Allocation rate
OOM events
Restart count
Request latency

For a .NET service, for example:

Code
Traffic increases
      |
      v
Allocations increase
      |
      v
GC pressure increases
      |
      v
Memory approaches limit
      |
      v
Application remains stable

You want to verify the behavior before production.

cgroup v2 Enables Better Memory Management

One reason Kubernetes is moving toward cgroup v2 is that it provides stronger primitives for resource management.

Kubernetes can use cgroup v2 capabilities for features such as MemoryQoS.

The broader model is:

Code
Pod
 |
 +--> memory.min
 +--> memory.high
 +--> memory.max

These controls provide more nuanced memory behavior than simply allowing a process to consume memory until it is killed.

The exact behavior depends on Kubernetes configuration, kernel capabilities, and the workload.

The important point is that cgroup v2 gives Kubernetes more useful controls over memory pressure.

Pressure Stall Information

Another important capability is Pressure Stall Information, commonly called PSI.

Traditional CPU monitoring might show:

Code
CPU utilization = 80%

PSI asks a different question:

How much time are processes waiting because CPU resources are unavailable?

The distinction matters.

For example:

Code
CPU utilization = 70%
CPU pressure   = high

can be possible when contention causes workloads to spend significant time waiting.

This gives operators another signal for understanding node-level resource pressure.

Autoscaling Can Benefit From Better Signals

Resource management and autoscaling are related.

Suppose an application has:

Code
CPU = 40%
Request latency = high

CPU alone might suggest that no scaling is necessary.

But another metric could show:

Code
CPU pressure = high

or:

Code
Requests waiting = high

This demonstrates why Kubernetes resource utilization should not be viewed in isolation.

A robust architecture can combine:

Code
CPU
Memory
Application metrics
Pressure metrics
Queue depth
Latency

with the appropriate autoscaling mechanisms.

GKE's Migration Is Already Underway

Google Kubernetes Engine has been moving toward cgroup v2 for several releases.

GKE documentation states that new nodes use cgroup v2 by default from GKE 1.26 onward. Existing cgroup v1 nodes do not automatically change simply because the cluster is upgraded to 1.26.

GKE began automatically migrating remaining cgroup v1 clusters starting with GKE 1.33, and cgroup v1 support is scheduled for removal in GKE 1.35. (cloud.google.com)

This creates a practical timeline:

Code
Older node pools
      |
      v
cgroup v1
      |
      v
Migration
      |
      v
cgroup v2
      |
      v
cgroup v1 removal

Teams should therefore identify legacy nodes before the platform forces the migration.

Standard Versus Autopilot

The migration process differs depending on the type of GKE environment.

With Autopilot, Google manages much more of the node infrastructure.

With Standard clusters, teams have more control over:

Code
Node pools
Node configuration
Runtime settings
System software
Scheduling

That also means Standard users have more responsibility for compatibility.

Do not copy a node-level migration procedure from a Standard cluster into an Autopilot environment without checking the platform-specific behavior.

A Safer GKE Migration Pattern

For Standard clusters, a controlled node-pool migration is usually easier to reason about than changing every node simultaneously.

The pattern is:

Code
Existing Node Pool
        |
        v
Create cgroup v2 Node Pool
        |
        v
Validate Nodes
        |
        v
Deploy Test Workloads
        |
        v
Run Integration Tests
        |
        v
Run Load Tests
        |
        v
Move Production Workloads
        |
        v
Drain Old Nodes
        |
        v
Remove Old Pool

This gives you a rollback option.

If something fails:

Code
cgroup v2 node
      |
      v
Unexpected behavior
      |
      v
Move workload back
      |
      v
Investigate

That is significantly safer than making a cluster-wide change without a fallback.

Pod Disruption Budgets Matter

Node migration normally involves draining nodes.

That means Kubernetes may need to evict Pods.

If an application has:

Code
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: api-pdb
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app: api

the migration must respect the application's availability requirements.

A poorly configured PodDisruptionBudget can also make node migration extremely slow.

The migration plan should therefore consider:

Code
Replicas
PodDisruptionBudget
Topology spread
Anti-affinity
Node capacity
Startup time
Readiness probes

Readiness Probes Become Important

During node replacement, Pods restart or move.

A good readiness probe ensures traffic reaches the new Pod only after the application is actually ready.

For example:

Code
readinessProbe:
  httpGet:
    path: /health/ready
    port: 8080
  initialDelaySeconds: 5
  periodSeconds: 5

The exact values depend on the application.

The migration should test:

Code
Old Pod terminating
       |
       v
New Pod starting
       |
       v
Readiness check
       |
       v
Traffic enabled

This prevents infrastructure migration from becoming an application outage.

Check Horizontal Pod Autoscaler Behavior

Autoscaling should also be tested.

A workload may use:

Code
metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70

Run load against the application before and after migration.

Verify:

Code
Desired replicas
Current replicas
CPU utilization
Scale-up time
Scale-down time
Metric availability

The cgroup migration should not unexpectedly change the application's perceived CPU behavior enough to destabilize HPA.

Test Cluster Autoscaler Too

If HPA increases replicas, the cluster may need more nodes.

That creates another control loop:

Code
Traffic increases
      |
      v
HPA increases Pods
      |
      v
Insufficient node capacity
      |
      v
Cluster Autoscaler adds nodes
      |
      v
Pods become schedulable

The migration should test this entire chain.

A node-level resource-management change can otherwise expose capacity assumptions that were not visible under normal traffic.

Do Not Forget DaemonSets

Node-level agents commonly run as DaemonSets.

Examples:

Code
Monitoring
Logging
Security
Networking
Storage

These are exactly the workloads most likely to interact with node-level Linux features.

Before migration, identify every DaemonSet:

Code
kubectl get daemonsets -A

Then classify them:

Code
Application-independent
Node monitoring
Security
Networking
Storage
Custom infrastructure

Pay special attention to anything that mounts:

Code
/sys
/proc
/dev
/sys/fs/cgroup

A Pod mounting the cgroup filesystem is a strong signal that its compatibility should be checked.

Search Your Own Code for cgroup Assumptions

Do not rely only on vendor documentation.

Search your repositories for:

Code
/sys/fs/cgroup

and:

Code
cgroup

For example:

Code
grep -R "/sys/fs/cgroup" .

For PowerShell:

Code
Get-ChildItem -Recurse | Select-String "/sys/fs/cgroup"

Also inspect shell scripts used by:

Code
Node initialization
Health checks
Monitoring
Deployment automation
Diagnostics

A ten-line shell script written years ago can be the most fragile part of a cgroup migration.

Container Image Testing

Build your application image using the same runtime and operating-system base used in production.

For example:

Code
FROM mcr.microsoft.com/dotnet/aspnet:10.0

WORKDIR /app

COPY ./publish .

ENTRYPOINT ["dotnet", "Orders.Api.dll"]

The specific runtime version should match your application's supported configuration.

Then run the image under realistic Kubernetes limits.

Do not conclude that the image is compatible simply because:

Code
docker run ...

works on a developer laptop.

The important environment is:

Code
Linux node
+
container runtime
+
cgroup v2
+
Kubernetes resource limits

Common Mistakes

Assuming cgroup v2 requires application rewrites

Most applications do not need code changes.

The main issue is compatibility with software that interacts directly with cgroups.

Checking only Pod health

A Running Pod does not prove that CPU and memory accounting are correct.

Ignoring node-level agents

Monitoring and security tools can be more sensitive than the application itself.

Mixing cgroup version and cgroup driver

They are different concepts.

Migrating all nodes simultaneously

A progressive node-pool migration provides better control.

Forgetting runtime versions

Old runtimes may not correctly detect container resource limits.

Ignoring memory pressure

Normal traffic is not enough to validate resource behavior.

Testing only application containers

DaemonSets and system components need validation too.

Changing the runtime and application at the same time

Keep the migration isolated so failures are easier to diagnose.

Advantages and Disadvantages

Advantages

Better resource model: cgroup v2 provides a unified Linux hierarchy.

Improved memory control: Kubernetes can use newer memory-management primitives.

Better diagnostics: PSI can provide additional visibility into resource contention.

Long-term Kubernetes direction: cgroup v2 is the platform's strategic path as cgroup v1 is deprecated.

More consistent runtime behavior: Modern runtimes can use container limits more accurately.

Disadvantages

Migration effort: Existing clusters and node pools may require controlled replacement.

Tool compatibility: Older monitoring and security software may depend on cgroup v1.

Runtime dependencies: Old language runtimes may have incorrect resource detection.

Operational complexity: Node replacement affects scheduling, availability, and capacity.

Debugging differences: Operators familiar with cgroup v1 must learn the new hierarchy and semantics.

A Practical Migration Checklist

Before migration:

CSS
[ ] Identify Kubernetes version
[ ] Identify node cgroup version
[ ] Identify cgroup driver
[ ] Identify container runtime
[ ] Inventory DaemonSets
[ ] Inventory monitoring agents
[ ] Inventory security agents
[ ] Check language runtime versions
[ ] Search application code for cgroup access
[ ] Check container images

During migration:

CSS
[ ] Create cgroup v2 node pool
[ ] Verify node configuration
[ ] Run representative workloads
[ ] Test CPU limits
[ ] Test memory limits
[ ] Test OOM behavior
[ ] Verify monitoring
[ ] Verify security tooling
[ ] Test HPA
[ ] Test Cluster Autoscaler
[ ] Validate PodDisruptionBudgets

After migration:

CSS
[ ] Compare resource metrics
[ ] Compare application latency
[ ] Compare restart rates
[ ] Review node pressure
[ ] Review security events
[ ] Monitor for runtime warnings
[ ] Remove legacy cgroup v1 nodes

What C# Developers Should Actually Do

For most .NET teams, the migration should be treated as a platform compatibility test, not a code migration.

Focus on these areas:

SQL
.NET runtime
    |
    +--> Memory detection
    +--> GC behavior
    +--> CPU behavior
    +--> Thread scheduling
    +--> Container limits
    +--> OOM behavior

Run the same workload on:

Code
cgroup v1 node
       vs.
cgroup v2 node

Compare:

Code
Memory usage
CPU usage
P95 latency
P99 latency
GC pauses
Throughput
Restart behavior

If the results are equivalent or better, you have strong evidence that the application is compatible.

If they differ significantly, investigate the runtime and resource-management behavior before migrating the entire fleet.

Summary

The move from cgroup v1 to cgroup v2 is fundamentally an infrastructure change, but it can affect application behavior indirectly through runtimes, monitoring systems, security tools, and resource accounting.

Kubernetes has already established cgroup v2 as the long-term direction, and cgroup v1 is being deprecated. GKE has likewise been moving new nodes to cgroup v2 and has a defined migration path for older environments. (kubernetes.io)

The safest approach is progressive migration:

Code
Inventory
   |
   v
Compatibility testing
   |
   v
New cgroup v2 nodes
   |
   v
Representative workloads
   |
   v
Production migration
   |
   v
Retire cgroup v1

Most application code should remain unchanged.

The areas that deserve the most attention are software that reads /sys/fs/cgroup, runtimes that detect container resources, monitoring and security agents, and workloads that operate close to CPU or memory limits.

For production Kubernetes environments, cgroup v2 should therefore be treated neither as a purely invisible platform change nor as a reason to rewrite applications.

It is a node-level migration that requires application-level validation.

The earlier you identify cgroup v1 assumptions, the easier the eventual migration becomes.