Memory problems in Kubernetes can be difficult to troubleshoot because memory does not behave like CPU.

A CPU limit can cause throttling when a workload tries to use more CPU than it is allowed to consume. Memory is different. When the system is under memory pressure, Kubernetes and the Linux kernel have to make decisions about which workloads can continue using memory and which workloads may need to be reclaimed or terminated.

This is where Memory QoS becomes important.

At the same time, Kubernetes has introduced a Pod-level resource model that lets developers describe resource requirements for a Pod rather than configuring everything only at the individual container level.

These two concepts are related, but they solve different problems.

Pod-level resources describe how much resource a Pod should have.

Memory QoS describes how memory pressure and memory protection can be handled.

Understanding the difference is important when running multi-container applications in production.

What Are Pod-Level Resources?

Traditionally, Kubernetes resource requests and limits are configured for individual containers.

For example:

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

If a Pod contains two containers, each container can have its own resource configuration.

For example:

apiVersion: v1
kind: Pod
metadata:
  name: orders-api
spec:
  containers:
    - name: application
      image: orders-api:1.0
      resources:
        requests:
          cpu: "500m"
          memory: "256Mi"
        limits:
          cpu: "1"
          memory: "512Mi"

    - name: logging
      image: log-agent:1.0
      resources:
        requests:
          cpu: "100m"
          memory: "64Mi"
        limits:
          cpu: "200m"
          memory: "128Mi"

This model gives explicit control over each container.

A Pod-level resource model allows the resource configuration to represent the Pod as a whole.

Why Pod-Level Resources Matter

Consider a Pod containing:

orders-api
logging-agent

The application normally uses more memory than the logging agent.

But usage changes throughout the day.

For example:

Normal traffic

Application: 300Mi
Logging:      50Mi
Total:       350Mi

During a period of increased logging:

Application: 350Mi
Logging:     150Mi
Total:       500Mi

The resource requirement belongs to the combined workload, not necessarily to two completely independent applications.

Pod-level resources can provide a way to describe that combined requirement.

This is particularly useful for multi-container workloads where containers are tightly coupled.

What Is Memory QoS?

Memory Quality of Service, or Memory QoS, is about how memory resources are protected and managed when the system experiences memory pressure.

It builds on Linux memory-control mechanisms such as cgroups.

A simplified view looks like this:

Application
     |
     v
Container
     |
     v
Pod
     |
     v
Linux cgroup
     |
     v
Node memory

Kubernetes resource settings can influence how the workload is treated by the underlying system.

Memory QoS is therefore not another way of specifying the application's memory requirement.

It is about how those memory requirements are enforced and protected.

Pod-Level Resources and Memory QoS Solve Different Problems

This distinction is important.

Concept

Main Purpose

Pod-level resources

Describe resource requirements at the Pod level

Container-level resources

Describe resource requirements for individual containers

Memory QoS

Control or protect memory behavior under pressure

Memory request

Helps Kubernetes understand scheduling requirements

Memory limit

Places an upper boundary on memory usage under applicable conditions

Think about it this way:

Pod resources
    =
How much resource does this workload need?

Memory QoS
    =
How should memory be managed when pressure occurs?

They can work together, but they are not interchangeable.

Why Memory Is Different From CPU

Consider a CPU limit:

limits:
  cpu: "1"

If the process wants more CPU than its configured limit allows, CPU can be throttled.

Memory cannot be handled in exactly the same way.

A process cannot simply be given "slower memory."

If memory becomes unavailable, the system has to reclaim memory or take other action.

This is why memory configuration deserves particular attention.

A Simple Memory Example

Suppose a Pod has:

resources:
  requests:
    memory: "512Mi"
  limits:
    memory: "1Gi"

The application may normally use:

400Mi

During a traffic spike:

750Mi

And during an unusually large workload:

950Mi

These values are still below the configured limit.

But if the application keeps allocating memory and reaches:

1Gi

the workload is approaching the configured boundary.

If memory demand continues beyond what the system can provide, the container may eventually be terminated due to an out-of-memory condition.

Memory QoS and Requests

Memory requests are important because they provide information about the workload's expected resource needs.

For example:

requests:
  memory: "512Mi"

This tells Kubernetes that the workload requires that amount of memory for scheduling purposes.

It does not mean the application will always consume exactly 512Mi.

The actual usage can be lower or higher.

This is why resource requests should be based on measured application behavior.

Memory QoS and Limits

A memory limit is different:

limits:
  memory: "1Gi"

The limit establishes an upper memory boundary for the container or applicable Pod configuration.

Memory QoS can use the relationship between requests and limits when configuring the underlying memory controls.

The exact behavior depends on the Kubernetes version, feature configuration, container runtime, and operating system.

This is one reason teams should verify Memory QoS behavior in their own cluster rather than assuming that every cluster behaves identically.

A Multi-Container Example

Consider this Pod:

apiVersion: v1
kind: Pod
metadata:
  name: payment-service
spec:
  resources:
    requests:
      cpu: "1"
      memory: "768Mi"
    limits:
      cpu: "2"
      memory: "1.5Gi"

  containers:
    - name: application
      image: payment-service:1.0

    - name: telemetry
      image: telemetry-agent:1.0

The Pod has a combined resource configuration.

The application and telemetry containers are part of the same workload.

Now imagine their memory usage changes:

Scenario A

Application: 500Mi
Telemetry:    80Mi
Total:       580Mi

Later:

Scenario B

Application: 350Mi
Telemetry:   250Mi
Total:       600Mi

The resource requirement has shifted between containers while the combined usage remains within the Pod's budget.

This is one of the situations where Pod-level resource configuration can make sense.

Why Memory QoS Still Matters

Even with a Pod-level resource budget, the node can experience memory pressure.

For example:

Node memory
|
+-- Pod A
+-- Pod B
+-- Pod C
+-- System processes

If multiple workloads consume memory at the same time, the node may have less available memory than expected.

Kubernetes needs to manage these workloads according to their resource configuration and QoS characteristics.

This is why resource settings should always be considered at two levels:

Pod level
    +
Node level

A correctly configured Pod can still run on a node that is under severe memory pressure.

Checking Memory Usage

A quick way to check current Pod memory usage is:

kubectl top pod

For a namespace:

kubectl top pod -n production

For individual containers:

kubectl top pod <pod-name> --containers

Example:

POD             NAME          CPU(cores)   MEMORY(bytes)
payment-api     application   420m         620Mi
payment-api     telemetry      80m         120Mi

The total memory usage is roughly:

620Mi + 120Mi = 740Mi

If the Pod's configured memory limit is:

1.5Gi

the current usage is below that limit.

But a single snapshot does not tell you whether memory usage is stable.

Monitor Memory Over Time

Suppose monitoring shows:

09:00 -> 420Mi
10:00 -> 470Mi
11:00 -> 550Mi
12:00 -> 680Mi
13:00 -> 820Mi

That trend is more useful than one measurement.

It may indicate:

The next step should be application-level investigation rather than immediately increasing the memory limit.

Memory Pressure on the Node

You can inspect a node using:

kubectl describe node <node-name>

Look at the node conditions.

Memory pressure is particularly important.

If the node is under memory pressure, Kubernetes may need to evict Pods depending on the circumstances and their priority and QoS characteristics.

This means a Pod's resource configuration cannot be considered in isolation.

Quality of Service Classes

Kubernetes also has Pod QoS classes.

The commonly discussed classes are:

These are determined by the resource configuration of the containers in the Pod.

For example, a Pod where every container has matching CPU and memory requests and limits can qualify for the Guaranteed class under the applicable rules.

A Pod with some resource requests or limits but not matching requirements may be Burstable.

A Pod without resource requests and limits can fall into BestEffort.

You can check the QoS class with:

kubectl get pod <pod-name> -o jsonpath='{.status.qosClass}'

Example:

Burstable

This is useful when investigating memory pressure and eviction behavior.

Pod-Level Resources Do Not Automatically Mean Guaranteed QoS

This is an important distinction.

Adding a Pod-level resource configuration does not mean the Pod automatically receives the highest QoS classification.

QoS classification has its own rules.

If QoS behavior matters for your workload, inspect the actual Pod status:

kubectl get pod <pod-name> -o jsonpath='{.status.qosClass}'

Do not infer the QoS class just by looking at one resource field.

Memory QoS and Application Design

Kubernetes configuration cannot fix an application that continuously consumes memory without releasing it.

For example:

public class Cache
{
    private readonly Dictionary<string, byte[]> data = new();

    public void Add(string key, byte[] value)
    {
        data[key] = value;
    }
}

If this cache grows indefinitely, increasing the Pod memory limit only allows the application to consume more memory before hitting the problem.

The application may need:

Resource configuration is part of capacity management, not a replacement for application optimization.

Common Mistakes

Treating Memory Like CPU

Memory cannot simply be throttled when usage reaches a limit.

Setting Requests Too Low

If requests are much lower than actual requirements, scheduling and memory-pressure behavior may not match the workload's needs.

Setting Limits Too High

A very high memory limit can allow a workload to consume resources that other Pods need.

Ignoring the Node

A Pod may have available memory according to its own configuration while the node itself is under pressure.

Assuming QoS From One Setting

QoS classification depends on the overall resource configuration and Kubernetes rules.

Increasing Memory Without Finding the Cause

If memory usage is continuously increasing, investigate the application.

Best Practices

Measure Actual Memory Usage

Use metrics from realistic workloads.

Set Requests Based on Normal Requirements

The request should represent the workload's expected resource requirement rather than an arbitrary number.

Set Limits Based on Safe Maximum Usage

The limit should provide enough room for expected peaks while protecting the cluster.

Monitor Memory Trends

Look at historical data, not only current usage.

Check Container-Level Usage

A Pod can contain several containers with very different memory behavior.

Test Under Pressure

Load testing can reveal memory problems that are invisible during normal development workloads.

Troubleshooting OOMKilled Containers

If a container is repeatedly restarting, inspect the Pod:

kubectl describe pod <pod-name>

Check the container's state and last termination reason.

You can also use:

kubectl get pod <pod-name> -o jsonpath='{.status.containerStatuses[*].lastState}'

If you find an out-of-memory termination, investigate:

Application memory usage
Configured memory limit
Request size
Cache growth
Object allocation
Traffic pattern
Node memory pressure

Do not immediately increase the limit.

First determine whether the application is behaving normally.

A Practical Investigation

Suppose you have:

Memory request: 512Mi
Memory limit:   1Gi
Current usage:  850Mi

Start by checking historical usage.

If the application normally uses:

300–400Mi

and suddenly reaches:

850Mi

investigate what changed.

If the application normally uses:

800–900Mi

then the resource configuration may simply be too small for the actual workload.

The numbers need context.

Pod-Level Resources vs Memory QoS in Practice

It helps to keep the concepts separate:

Pod-Level Resources
        |
        v
Defines the workload's resource budget
        |
        v
Kubernetes scheduling and resource management

Memory QoS
        |
        v
Controls memory-related behavior
        |
        v
Linux memory management under pressure

They are complementary rather than competing features.

A team may configure Pod-level resources and still need to understand memory QoS, QoS classification, node pressure, and application memory behavior.

When Pod-Level Resources Are Useful

Consider Pod-level resources when:

When Container-Level Resources Are Better

Use explicit container-level resources when:

There is no requirement to use Pod-level resources simply because the Kubernetes version supports them.

Advantages and Disadvantages

Advantages

Disadvantages

Provides a resource view of the whole Pod

Can make per-container ownership less obvious

Useful for tightly coupled containers

Not required for every workload

Can support flexible resource sharing

Teams need to understand the interaction with container resources

Works alongside Kubernetes memory management

Does not solve application memory leaks

Useful for multi-container workloads

Node-level memory pressure still matters

Summary

Pod-level resources and Memory QoS address different parts of Kubernetes resource management.

Pod-level resources describe the CPU and memory requirements of the Pod as a combined workload. This can be useful when several containers work closely together and their resource usage changes over time.

Memory QoS is concerned with how memory is managed and protected when workloads and nodes experience memory pressure. It works with Kubernetes resource configuration and underlying Linux memory controls rather than replacing resource requests and limits.

For production workloads, the important thing is to look at the complete picture. Check Pod-level configuration, individual container usage, QoS classification, node memory pressure, and application memory behavior.

If a Pod is using too much memory, increasing the limit may be appropriate in some cases, but it should follow measurement and investigation. A growing cache, memory leak, unusually large request, or inefficient buffering may require an application-level fix instead.

The most reliable approach is to size resources from real workload data, monitor memory over time, and understand how Pod-level configuration and memory management work together.