Skip to content

Commit d477d27

Browse files
authored
Merge branch 'main' into support-k8s-1-35
2 parents b11a9b2 + d1567d4 commit d477d27

8 files changed

Lines changed: 162 additions & 36 deletions

File tree

.github/workflows/release-integration-sidecar.yml

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ jobs:
5555

5656
- name: Set image tag
5757
run: |
58-
VERSION=${{ github.ref_name || inputs.tag }}
58+
VERSION=$(echo "${{ github.ref_name || inputs.tag }}" | sed 's/^v//')
5959
echo "DOCKER_IMAGE_TAG=${VERSION}-sidecar" >> $GITHUB_ENV
6060
6161
- name: Set up QEMU
@@ -81,15 +81,6 @@ jobs:
8181
-f Dockerfile.sidecar \
8282
.
8383
84-
- name: Run Trivy vulnerability scanner
85-
uses: aquasecurity/trivy-action@v0.36.0
86-
with:
87-
image-ref: '${{ env.DOCKER_IMAGE_NAME }}:ci-scan'
88-
format: 'table'
89-
exit-code: '1'
90-
ignore-unfixed: true
91-
severity: 'HIGH,CRITICAL'
92-
9384
- name: Push sidecar image
9485
run: |
9586
docker buildx build --push --platform=$DOCKER_PLATFORMS \

CHANGELOG.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,26 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
1111
- Support Kubernetes 1.35 @jamescripter [#1493](https://github.com/newrelic/nri-kubernetes/pull/1493)
1212

1313
### bugfix
14+
- Use actual applied container resources (`Pod.Status.ContainerStatuses[i].Resources`) over desired spec resources (`Pod.Spec.Containers[i].Resources`) where present to correctly report `cpuRequestedCores`, `memoryRequestedBytes`, and related metrics during in-place pod vertical scaling (K8s 1.33+ beta, 1.35+ GA) @kondracek-nr [#1433](https://github.com/newrelic/nri-kubernetes/pull/1433)
15+
16+
## v4.3.6 - 2026-07-01
17+
18+
### 🐞 Bug fixes
19+
- fix sidecar image tag, strip "v" prefix. @danielstokes [#1500](https://github.com/newrelic/nri-kubernetes/pull/1500)
20+
21+
## v4.3.5 - 2026-07-01
22+
23+
### 🐞 Bug fixes
24+
- Fix sidecar release GHA @dbudziwojski [#1497](https://github.com/newrelic/nri-kubernetes/pull/1497)
25+
26+
## v4.3.4 - 2026-06-30
27+
28+
### 🐞 Bug fixes
1429
- Fix fillContainerStatuses panics @changliu-wk [#1488](https://github.com/newrelic/nri-kubernetes/pull/1488)
1530

31+
### ⛓️ Dependencies
32+
- Updated k8s.io/utils digest to be93311
33+
1634
## v4.3.3 - 2026-06-29
1735

1836
### ⛓️ Dependencies

Dockerfile.sidecar

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# This creates a single-container image suitable for Fargate sidecar injection
33

44
ARG MODE=normal
5-
ARG BASE_IMAGE=newrelic/infrastructure-bundle:3.3.32
5+
ARG BASE_IMAGE=newrelic/infrastructure-bundle:3.3.33
66

77
FROM $BASE_IMAGE AS base
88

charts/newrelic-infrastructure/Chart.yaml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ sources:
77
- https://github.com/newrelic/nri-kubernetes/
88
- https://github.com/newrelic/nri-kubernetes/tree/main/charts/newrelic-infrastructure
99
- https://github.com/newrelic/infrastructure-agent/
10-
version: 4.3.2
11-
appVersion: 4.3.2
10+
version: 4.3.6
11+
appVersion: 4.3.6
1212
dependencies:
1313
- name: common-library
1414
version: 2.3.3
@@ -18,8 +18,10 @@ maintainers:
1818
url: https://github.com/danielstokes
1919
- name: dbudziwojskiNR
2020
url: https://github.com/dbudziwojskiNR
21-
- name: kondracek-nr
22-
url: https://github.com/kondracek-nr
21+
- name: jamescripter
22+
url: https://github.com/jamescripter
23+
- name: NRhzhao
24+
url: https://github.com/NRhzhao
2325
- name: Philip-R-Beckwith
2426
url: https://github.com/Philip-R-Beckwith
2527
- name: TmNguyen12

charts/newrelic-infrastructure/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,8 @@ Infrastructure Agent integrations (Kafka, Cassandra, Redis, etc.) are not suppor
281281

282282
* [danielstokes](https://github.com/danielstokes)
283283
* [dbudziwojskiNR](https://github.com/dbudziwojskiNR)
284-
* [kondracek-nr](https://github.com/kondracek-nr)
284+
* [jamescripter](https://github.com/jamescripter)
285+
* [NRhzhao](https://github.com/NRhzhao)
285286
* [Philip-R-Beckwith](https://github.com/Philip-R-Beckwith)
286287
* [TmNguyen12](https://github.com/TmNguyen12)
287288

charts/newrelic-infrastructure/values.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ images:
4141
agent:
4242
registry: ""
4343
repository: newrelic/infrastructure-bundle
44-
tag: 3.3.32
44+
tag: 3.3.33
4545
pullPolicy: IfNotPresent
4646
# -- Image for the New Relic Kubernetes integration.
4747
# @default -- See `values.yaml`
@@ -62,7 +62,7 @@ images:
6262
windowsAgent:
6363
registry: ""
6464
repository: newrelic/infrastructure-bundle
65-
tag: 3.3.32
65+
tag: 3.3.33
6666
pullPolicy: IfNotPresent
6767

6868
# -- Enable Windows node monitoring.

src/kubelet/metric/pods.go

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,11 @@ import (
2222
)
2323

2424
// KubeletPodsPath is the path where kubelet serves information about pods.
25-
const KubeletPodsPath = "/pods"
26-
const KubeServiceKubeletPodsPath = "/api/v1/pods"
27-
const nodeSelectorQuery = "fieldSelector=spec.nodeName=%s"
25+
const (
26+
KubeletPodsPath = "/pods"
27+
KubeServiceKubeletPodsPath = "/api/v1/pods"
28+
nodeSelectorQuery = "fieldSelector=spec.nodeName=%s"
29+
)
2830

2931
// maxPodsResponseBytes caps the kubelet /pods response read at 100MB to prevent OOM.
3032
// Normal responses are typically 1-10MB even on large nodes.
@@ -192,12 +194,13 @@ func getKubeServiceHost() string {
192194

193195
func (podsFetcher *PodsFetcher) fetchContainersData(pod *v1.Pod) map[string]definition.RawMetrics {
194196
statuses := make(map[string]definition.RawMetrics)
195-
fillContainerStatuses(pod, statuses)
197+
containerStatusByName := getContainerStatusesByName(pod)
198+
fillContainerStatuses(pod, containerStatusByName, statuses)
196199

197200
metrics := make(map[string]definition.RawMetrics)
198201
containers := pod.Spec.Containers
199202

200-
// Add sidecar containers
203+
// Add sidecar containers (init containers with RestartPolicy Always)
201204
for _, initContainer := range pod.Spec.InitContainers {
202205
if initContainer.RestartPolicy != nil && *initContainer.RestartPolicy == v1.ContainerRestartPolicyAlways {
203206
containers = append(containers, initContainer)
@@ -218,19 +221,29 @@ func (podsFetcher *PodsFetcher) fetchContainersData(pod *v1.Pod) map[string]defi
218221
metrics[id]["nodeIP"] = v
219222
}
220223

221-
if v, ok := c.Resources.Requests[v1.ResourceCPU]; ok {
224+
// As of K8s 1.33 (beta, on by default) / 1.35 (GA), in-place pod vertical scaling means
225+
// Pod.Spec.Containers[i].Resources is the desired state, not the actual state.
226+
// Pod.Status.ContainerStatuses[i].Resources holds the actual applied resources.
227+
228+
// Prefer status resources (actual applied) over spec resources (desired state).
229+
resources := c.Resources
230+
if cs, ok := containerStatusByName[c.Name]; ok && cs.Resources != nil {
231+
resources = *cs.Resources
232+
}
233+
234+
if v, ok := resources.Requests[v1.ResourceCPU]; ok {
222235
metrics[id]["cpuRequestedCores"] = v.MilliValue()
223236
}
224237

225-
if v, ok := c.Resources.Limits[v1.ResourceCPU]; ok {
238+
if v, ok := resources.Limits[v1.ResourceCPU]; ok {
226239
metrics[id]["cpuLimitCores"] = v.MilliValue()
227240
}
228241

229-
if v, ok := c.Resources.Requests[v1.ResourceMemory]; ok {
242+
if v, ok := resources.Requests[v1.ResourceMemory]; ok {
230243
metrics[id]["memoryRequestedBytes"] = v.Value()
231244
}
232245

233-
if v, ok := c.Resources.Limits[v1.ResourceMemory]; ok {
246+
if v, ok := resources.Limits[v1.ResourceMemory]; ok {
234247
metrics[id]["memoryLimitBytes"] = v.Value()
235248
}
236249

@@ -254,23 +267,30 @@ func (podsFetcher *PodsFetcher) fetchContainersData(pod *v1.Pod) map[string]defi
254267
return metrics
255268
}
256269

257-
func fillContainerStatuses(pod *v1.Pod, dest map[string]definition.RawMetrics) {
258-
containerStatuses := pod.Status.ContainerStatuses
270+
func getContainerStatusesByName(pod *v1.Pod) map[string]*v1.ContainerStatus {
271+
containerStatusByName := make(map[string]*v1.ContainerStatus)
272+
for i := range pod.Status.ContainerStatuses {
273+
containerStatus := &pod.Status.ContainerStatuses[i]
274+
containerStatusByName[containerStatus.Name] = containerStatus
275+
}
259276

260277
// Add sidecar containers (init containers with RestartPolicy Always)
261278
for _, initContainer := range pod.Spec.InitContainers {
262279
if initContainer.RestartPolicy != nil && *initContainer.RestartPolicy == v1.ContainerRestartPolicyAlways {
263280
for _, initContainerStatus := range pod.Status.InitContainerStatuses {
264281
if initContainerStatus.Name == initContainer.Name {
265-
containerStatuses = append(containerStatuses, initContainerStatus)
282+
containerStatusByName[initContainerStatus.Name] = &initContainerStatus
266283
break
267284
}
268285
}
269286
}
270287
}
271288

272-
for _, c := range containerStatuses {
273-
name := c.Name
289+
return containerStatusByName
290+
}
291+
292+
func fillContainerStatuses(pod *v1.Pod, containerStatuses map[string]*v1.ContainerStatus, dest map[string]definition.RawMetrics) {
293+
for name, c := range containerStatuses {
274294
id := containerID(pod, name)
275295

276296
// Set the ExitCode. Zero if no terminated Exit Code.

src/kubelet/metric/pods_test.go

Lines changed: 98 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/google/go-cmp/cmp"
1515
"github.com/stretchr/testify/assert"
1616
corev1 "k8s.io/api/core/v1"
17+
"k8s.io/apimachinery/pkg/api/resource"
1718
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1819

1920
"github.com/newrelic/nri-kubernetes/v3/internal/config"
@@ -573,7 +574,8 @@ func TestFillContainerStatuses_IsReadyForAllStates(t *testing.T) {
573574
{Name: testContainerName, Ready: true, State: corev1.ContainerState{Running: &corev1.ContainerStateRunning{}}},
574575
}
575576
dest := make(map[string]definition.RawMetrics)
576-
fillContainerStatuses(pod, dest)
577+
containerStatusByName := getContainerStatusesByName(pod)
578+
fillContainerStatuses(pod, containerStatusByName, dest)
577579
id := fmt.Sprintf("%s_%s_%s", namespace, podName, testContainerName)
578580
assert.Equal(t, true, dest[id]["isReady"])
579581
})
@@ -585,7 +587,8 @@ func TestFillContainerStatuses_IsReadyForAllStates(t *testing.T) {
585587
{Name: testContainerName, Ready: false, State: corev1.ContainerState{Waiting: &corev1.ContainerStateWaiting{Reason: "CrashLoopBackOff"}}},
586588
}
587589
dest := make(map[string]definition.RawMetrics)
588-
fillContainerStatuses(pod, dest)
590+
containerStatusByName := getContainerStatusesByName(pod)
591+
fillContainerStatuses(pod, containerStatusByName, dest)
589592
id := fmt.Sprintf("%s_%s_%s", namespace, podName, testContainerName)
590593
assert.Equal(t, false, dest[id]["isReady"])
591594
})
@@ -597,7 +600,8 @@ func TestFillContainerStatuses_IsReadyForAllStates(t *testing.T) {
597600
{Name: testContainerName, Ready: false, State: corev1.ContainerState{Terminated: &corev1.ContainerStateTerminated{ExitCode: 0}}},
598601
}
599602
dest := make(map[string]definition.RawMetrics)
600-
fillContainerStatuses(pod, dest)
603+
containerStatusByName := getContainerStatusesByName(pod)
604+
fillContainerStatuses(pod, containerStatusByName, dest)
601605
id := fmt.Sprintf("%s_%s_%s", namespace, podName, testContainerName)
602606
assert.Equal(t, false, dest[id]["isReady"])
603607
})
@@ -609,7 +613,8 @@ func TestFillContainerStatuses_IsReadyForAllStates(t *testing.T) {
609613
{Name: testContainerName, Ready: false, State: corev1.ContainerState{}}, // all nil → Unknown
610614
}
611615
dest := make(map[string]definition.RawMetrics)
612-
fillContainerStatuses(pod, dest)
616+
containerStatusByName := getContainerStatusesByName(pod)
617+
fillContainerStatuses(pod, containerStatusByName, dest)
613618
id := fmt.Sprintf("%s_%s_%s", namespace, podName, testContainerName)
614619
assert.Equal(t, false, dest[id]["isReady"])
615620
})
@@ -706,3 +711,92 @@ func TestFetchPodData_WithPriorityClassNameOnly(t *testing.T) {
706711
assert.False(t, hasPriority, "priority should not be present when nil")
707712
assert.Equal(t, "system-cluster-critical", result["priorityClassName"])
708713
}
714+
715+
// TestFetchContainersData_InPlaceVerticalScaling verifies that when
716+
// ContainerStatus.Resources is populated (K8s 1.33+ in-place pod vertical scaling),
717+
// the actual applied resources are used instead of Spec resources (desired state).
718+
func TestFetchContainersData_InPlaceVerticalScaling(t *testing.T) {
719+
t.Parallel()
720+
721+
specCPU := resource.MustParse("100m")
722+
specMemory := resource.MustParse("128Mi")
723+
statusCPU := resource.MustParse("200m") // resized up
724+
statusMemory := resource.MustParse("256Mi")
725+
726+
pod := &corev1.Pod{
727+
ObjectMeta: metav1.ObjectMeta{
728+
Name: "test-pod",
729+
Namespace: "default",
730+
},
731+
Spec: corev1.PodSpec{
732+
NodeName: "test-node",
733+
Containers: []corev1.Container{
734+
{
735+
Name: "app",
736+
Image: "app:latest",
737+
Resources: corev1.ResourceRequirements{
738+
Requests: corev1.ResourceList{
739+
corev1.ResourceCPU: specCPU,
740+
corev1.ResourceMemory: specMemory,
741+
},
742+
Limits: corev1.ResourceList{
743+
corev1.ResourceCPU: specCPU,
744+
corev1.ResourceMemory: specMemory,
745+
},
746+
},
747+
},
748+
{
749+
Name: "sidecar",
750+
Image: "sidecar:latest",
751+
Resources: corev1.ResourceRequirements{
752+
Requests: corev1.ResourceList{
753+
corev1.ResourceCPU: specCPU,
754+
corev1.ResourceMemory: specMemory,
755+
},
756+
},
757+
},
758+
},
759+
},
760+
Status: corev1.PodStatus{
761+
HostIP: "192.168.0.1",
762+
ContainerStatuses: []corev1.ContainerStatus{
763+
{
764+
Name: "app",
765+
// Resources populated: actual applied state after resize
766+
Resources: &corev1.ResourceRequirements{
767+
Requests: corev1.ResourceList{
768+
corev1.ResourceCPU: statusCPU,
769+
corev1.ResourceMemory: statusMemory,
770+
},
771+
Limits: corev1.ResourceList{
772+
corev1.ResourceCPU: statusCPU,
773+
corev1.ResourceMemory: statusMemory,
774+
},
775+
},
776+
},
777+
{
778+
Name: "sidecar",
779+
Resources: nil, // no status resources; fall back to spec
780+
},
781+
},
782+
},
783+
}
784+
785+
podFetcher := &PodsFetcher{}
786+
result := podFetcher.fetchContainersData(pod)
787+
788+
appID := "default_test-pod_app"
789+
sidecarID := "default_test-pod_sidecar"
790+
791+
// "app" container: status resources should take precedence
792+
assert.Equal(t, statusCPU.MilliValue(), result[appID]["cpuRequestedCores"], "app: should use status CPU request")
793+
assert.Equal(t, statusCPU.MilliValue(), result[appID]["cpuLimitCores"], "app: should use status CPU limit")
794+
assert.Equal(t, statusMemory.Value(), result[appID]["memoryRequestedBytes"], "app: should use status memory request")
795+
assert.Equal(t, statusMemory.Value(), result[appID]["memoryLimitBytes"], "app: should use status memory limit")
796+
797+
// "sidecar" container: status resources nil, should fall back to spec
798+
assert.Equal(t, specCPU.MilliValue(), result[sidecarID]["cpuRequestedCores"], "sidecar: should fall back to spec CPU request")
799+
assert.Equal(t, specMemory.Value(), result[sidecarID]["memoryRequestedBytes"], "sidecar: should fall back to spec memory request")
800+
_, hasLimit := result[sidecarID]["cpuLimitCores"]
801+
assert.False(t, hasLimit, "sidecar: no CPU limit in spec, should not be set")
802+
}

0 commit comments

Comments
 (0)