Try it free

Effective pod resources

  • Latest Dynatrace
  • Reference
  • 4-min read
  • Published Jul 28, 2026

Resource requests and limits for CPU and memory can be set at the container level, the init container level, and—since Kubernetes 1.34—at the pod level. Because different container types run at different times, computing the effective pod-level limit is non-linear: a simple sum of all container limits gives the wrong answer in many real-world pods.

Dynatrace calculates the correct effective value at ingest time and adjusts the metric so that aggregated views across pods, workloads, namespaces, and clusters always reflect what Kubernetes actually enforces.

Applies to Latest Dynatrace (Grail) monitoring only. Requires Kubernetes monitoring via ActiveGate.

If a service mesh like Istio is configured to use native sidecar containers, those sidecars are ingested and summed correctly in ActiveGate version 1.339+. Pods using earlier Istio versions (before 1.24, or from 1.24 through 1.26 without explicit configuration) used app containers for the proxy, which were already accounted for. Upgrading Istio to version 1.27+ (where native sidecars are the default) without also upgrading to ActiveGate version 1.339+ will cause sidecar resources to be missing from the metric.

ScenarioActiveGate version 1.339+ActiveGate version 1.343+ActiveGate version 1.345+

App containers

✓

✓

✓

Native sidecar containers (restartPolicy: Always)

✓

✓

✓

Init containers (compensating delta added)

—

✓

✓

Pod-level resources (spec.resources)—Kubernetes 1.34+ (compensating delta added)

—

—

✓

Pod overhead (overhead in RuntimeClass) (compensating delta added)

—

—

✓

How Kubernetes calculates effective pod limits

Kubernetes defines three container types with distinct lifecycle windows. Because they don't all run simultaneously, their resource contributions cannot simply be summed.

The following example shows a pod with two init containers and one app container. kubectl describe pod reports each container's limits individually; it does not compute the effective pod-level limit.

Init Containers:
init-step-1:
Limits: cpu: 50m memory: 32Mi
init-step-2:
Limits: cpu: 100m memory: 60Mi
Containers:
app:
Limits: cpu: 4m memory: 32Mi

The effective limit must be derived manually:

  • CPU: MAX(MAX(50m, 100m), 4m) = 100m
  • Memory: MAX(MAX(32Mi, 60Mi), 32Mi) = 60Mi
TypeWhen it runsContribution to effective pod limit

App container

Entire pod lifetime

Added to running-phase SUM

Native sidecar (restartPolicy: Always)

Starts during init phase, runs for entire pod lifetime

Added to running-phase SUM

Init container

Sequentially before app containers start, then exits

Contributes to init-phase MAX

Pod-level spec.resources

N/A—a budget applied to the whole pod

Can cap the effective limit; can raise the effective request

Native sidecars are init containers with restartPolicy: Always, available since Kubernetes 1.29. Unlike classic init containers, they are not terminated before the app starts—they persist for the full pod lifetime and run concurrently with app containers.

The effective pod limit for a resource is:

effective limit = MAX(
SUM(app containers + sidecar containers), // running phase
MAX(each init container + sidecars running before it) // init phase
)

For the full specification, see Resource sharing within containers in the Kubernetes documentation.

If a pod-level limit is set and is lower than the running-phase sum, Kubernetes enforces the pod-level cap.

Pod-level resource specification (spec.resources, feature gate PodLevelResources) is a beta feature enabled by default since Kubernetes version 1.34. Some web consoles and kubectl describe node do not yet account for pod-level resources in their resource display; values shown there may not reflect what Kubernetes enforces.

How Dynatrace ensures accuracy

Dynatrace stores resource metrics at container level (dt.kubernetes.container.limits_cpu, dt.kubernetes.container.requests_memory, and so on) to preserve per-container granularity. For most pods, aggregating these metrics across containers gives the correct pod total.

When simple aggregation would produce an incorrect effective limit, a compensating delta data point is added to the same metric at ingest time. This delta makes the aggregated total correct without changing any individual container data points.

Using the same example pod, querying dt.kubernetes.container.limits_cpu by container shows:

k8s.container.namek8s.container.typecpu_millicoremem_mib

app

app

4

32

(compensating delta)

—

96

28

Total (sum)

100

60

The compensating delta row has no container name or type. Summing across all rows gives the correct effective pod limit that Kubernetes enforces.

The delta value is:

  • Positive when the init phase peak (init containers + sidecars already running) exceeds the running-phase sum, or when a pod-level request is higher than the container sum.
  • Negative when a pod-level CPU or memory limit caps the total below the container sum. This applies to limits only; Kubernetes does not allow pod-level requests to be lower than the container sum.

The compensating delta data point has no dt.smartscape.container dimension. Its dt.smartscape_source.type is K8S_POD, even when the delta originates from an init container. This lets you distinguish it in queries if needed.

Native sidecar containers do not require a compensating delta. Because they run for the full pod lifetime, their contribution is linear and is summed directly with app container values.

Verify the calculation

To check whether compensating delta data points are being persisted, query by container-related dimensions. k8s.cluster.name and similar attributes are entity properties, not metric dimensions; use dt.smartscape.* fields to group and identify the deltas. Rows where dt.smartscape.container is absent are the compensating delta data points.

timeseries sum(dt.kubernetes.container.limits_memory),
by: { dt.smartscape.k8s_pod, dt.smartscape.container, k8s.container.type }

To scope to a specific workload, start from the metric and join entity context from Smartscape. Alias the k8s.* fields inside the lookup subquery.

timeseries sum(dt.kubernetes.container.limits_memory),
by: { dt.smartscape.k8s_pod, dt.smartscape.container, k8s.container.type }
| lookup [
smartscapeNodes "*"
| filter type == "K8S_POD"
| fields id, cluster = k8s.cluster.name, ns = k8s.namespace.name, wl = k8s.workload.name
], sourceField: dt.smartscape.k8s_pod, lookupField: id
| fieldsAdd cluster = lookup.cluster, ns = lookup.ns, wl = lookup.wl
| filter cluster == "<cluster-name>" and ns == "<namespace>" and wl == "<workload>"
| fields dt.smartscape.k8s_pod, dt.smartscape.container, k8s.container.type, cluster, ns, wl

If the compensating delta is missing, check the ActiveGate version and property values.

Related tags
Infrastructure ObservabilityKubernetes (new)Kubernetes