From 90640ec14a7612baba12b658e962a9436b583ce4 Mon Sep 17 00:00:00 2001 From: Kubernetes Release Robot Date: Tue, 12 May 2026 10:02:44 +0000 Subject: [PATCH 01/15] Update CHANGELOG/CHANGELOG-1.35.md for v1.35.5 --- CHANGELOG/CHANGELOG-1.35.md | 236 ++++++++++++++++++++++++++---------- 1 file changed, 171 insertions(+), 65 deletions(-) diff --git a/CHANGELOG/CHANGELOG-1.35.md b/CHANGELOG/CHANGELOG-1.35.md index 6b38da9b9bc4a..0c39070f26ada 100644 --- a/CHANGELOG/CHANGELOG-1.35.md +++ b/CHANGELOG/CHANGELOG-1.35.md @@ -1,192 +1,298 @@ -- [v1.35.4](#v1354) - - [Downloads for v1.35.4](#downloads-for-v1354) +- [v1.35.5](#v1355) + - [Downloads for v1.35.5](#downloads-for-v1355) - [Source Code](#source-code) - [Client Binaries](#client-binaries) - [Server Binaries](#server-binaries) - [Node Binaries](#node-binaries) - [Container Images](#container-images) - - [Changelog since v1.35.3](#changelog-since-v1353) + - [Changelog since v1.35.4](#changelog-since-v1354) - [Changes by Kind](#changes-by-kind) - - [Feature](#feature) - [Bug or Regression](#bug-or-regression) - [Dependencies](#dependencies) - [Added](#added) - [Changed](#changed) - [Removed](#removed) -- [v1.35.3](#v1353) - - [Downloads for v1.35.3](#downloads-for-v1353) +- [v1.35.4](#v1354) + - [Downloads for v1.35.4](#downloads-for-v1354) - [Source Code](#source-code-1) - [Client Binaries](#client-binaries-1) - [Server Binaries](#server-binaries-1) - [Node Binaries](#node-binaries-1) - [Container Images](#container-images-1) - - [Changelog since v1.35.2](#changelog-since-v1352) + - [Changelog since v1.35.3](#changelog-since-v1353) - [Changes by Kind](#changes-by-kind-1) + - [Feature](#feature) - [Bug or Regression](#bug-or-regression-1) - - [Other (Cleanup or Flake)](#other-cleanup-or-flake) - [Dependencies](#dependencies-1) - [Added](#added-1) - [Changed](#changed-1) - [Removed](#removed-1) -- [v1.35.2](#v1352) - - [Downloads for v1.35.2](#downloads-for-v1352) +- [v1.35.3](#v1353) + - [Downloads for v1.35.3](#downloads-for-v1353) - [Source Code](#source-code-2) - [Client Binaries](#client-binaries-2) - [Server Binaries](#server-binaries-2) - [Node Binaries](#node-binaries-2) - [Container Images](#container-images-2) - - [Changelog since v1.35.1](#changelog-since-v1351) + - [Changelog since v1.35.2](#changelog-since-v1352) - [Changes by Kind](#changes-by-kind-2) - - [Feature](#feature-1) + - [Bug or Regression](#bug-or-regression-2) + - [Other (Cleanup or Flake)](#other-cleanup-or-flake) - [Dependencies](#dependencies-2) - [Added](#added-2) - [Changed](#changed-2) - [Removed](#removed-2) -- [v1.35.1](#v1351) - - [Downloads for v1.35.1](#downloads-for-v1351) +- [v1.35.2](#v1352) + - [Downloads for v1.35.2](#downloads-for-v1352) - [Source Code](#source-code-3) - [Client Binaries](#client-binaries-3) - [Server Binaries](#server-binaries-3) - [Node Binaries](#node-binaries-3) - [Container Images](#container-images-3) - - [Changelog since v1.35.0](#changelog-since-v1350) + - [Changelog since v1.35.1](#changelog-since-v1351) - [Changes by Kind](#changes-by-kind-3) - - [Feature](#feature-2) - - [Bug or Regression](#bug-or-regression-2) - - [Other (Cleanup or Flake)](#other-cleanup-or-flake-1) + - [Feature](#feature-1) - [Dependencies](#dependencies-3) - [Added](#added-3) - [Changed](#changed-3) - [Removed](#removed-3) -- [v1.35.0](#v1350) - - [Downloads for v1.35.0](#downloads-for-v1350) +- [v1.35.1](#v1351) + - [Downloads for v1.35.1](#downloads-for-v1351) - [Source Code](#source-code-4) - [Client Binaries](#client-binaries-4) - [Server Binaries](#server-binaries-4) - [Node Binaries](#node-binaries-4) - [Container Images](#container-images-4) - - [Changelog since v1.34.0](#changelog-since-v1340) - - [Urgent Upgrade Notes](#urgent-upgrade-notes) - - [(No, really, you MUST read this before you upgrade)](#no-really-you-must-read-this-before-you-upgrade) + - [Changelog since v1.35.0](#changelog-since-v1350) - [Changes by Kind](#changes-by-kind-4) - - [Deprecation](#deprecation) - - [API Change](#api-change) - - [Feature](#feature-3) - - [Documentation](#documentation) + - [Feature](#feature-2) - [Bug or Regression](#bug-or-regression-3) - - [Other (Cleanup or Flake)](#other-cleanup-or-flake-2) + - [Other (Cleanup or Flake)](#other-cleanup-or-flake-1) - [Dependencies](#dependencies-4) - [Added](#added-4) - [Changed](#changed-4) - [Removed](#removed-4) -- [v1.35.0-rc.1](#v1350-rc1) - - [Downloads for v1.35.0-rc.1](#downloads-for-v1350-rc1) +- [v1.35.0](#v1350) + - [Downloads for v1.35.0](#downloads-for-v1350) - [Source Code](#source-code-5) - [Client Binaries](#client-binaries-5) - [Server Binaries](#server-binaries-5) - [Node Binaries](#node-binaries-5) - [Container Images](#container-images-5) - - [Changelog since v1.35.0-rc.0](#changelog-since-v1350-rc0) + - [Changelog since v1.34.0](#changelog-since-v1340) + - [Urgent Upgrade Notes](#urgent-upgrade-notes) + - [(No, really, you MUST read this before you upgrade)](#no-really-you-must-read-this-before-you-upgrade) - [Changes by Kind](#changes-by-kind-5) - - [Feature](#feature-4) + - [Deprecation](#deprecation) + - [API Change](#api-change) + - [Feature](#feature-3) + - [Documentation](#documentation) - [Bug or Regression](#bug-or-regression-4) - - [Other (Cleanup or Flake)](#other-cleanup-or-flake-3) + - [Other (Cleanup or Flake)](#other-cleanup-or-flake-2) - [Dependencies](#dependencies-5) - [Added](#added-5) - [Changed](#changed-5) - [Removed](#removed-5) -- [v1.35.0-rc.0](#v1350-rc0) - - [Downloads for v1.35.0-rc.0](#downloads-for-v1350-rc0) +- [v1.35.0-rc.1](#v1350-rc1) + - [Downloads for v1.35.0-rc.1](#downloads-for-v1350-rc1) - [Source Code](#source-code-6) - [Client Binaries](#client-binaries-6) - [Server Binaries](#server-binaries-6) - [Node Binaries](#node-binaries-6) - [Container Images](#container-images-6) - - [Changelog since v1.35.0-beta.0](#changelog-since-v1350-beta0) + - [Changelog since v1.35.0-rc.0](#changelog-since-v1350-rc0) - [Changes by Kind](#changes-by-kind-6) - - [Feature](#feature-5) + - [Feature](#feature-4) - [Bug or Regression](#bug-or-regression-5) + - [Other (Cleanup or Flake)](#other-cleanup-or-flake-3) - [Dependencies](#dependencies-6) - [Added](#added-6) - [Changed](#changed-6) - [Removed](#removed-6) -- [v1.35.0-beta.0](#v1350-beta0) - - [Downloads for v1.35.0-beta.0](#downloads-for-v1350-beta0) +- [v1.35.0-rc.0](#v1350-rc0) + - [Downloads for v1.35.0-rc.0](#downloads-for-v1350-rc0) - [Source Code](#source-code-7) - [Client Binaries](#client-binaries-7) - [Server Binaries](#server-binaries-7) - [Node Binaries](#node-binaries-7) - [Container Images](#container-images-7) - - [Changelog since v1.35.0-alpha.3](#changelog-since-v1350-alpha3) + - [Changelog since v1.35.0-beta.0](#changelog-since-v1350-beta0) - [Changes by Kind](#changes-by-kind-7) - - [API Change](#api-change-1) - - [Feature](#feature-6) + - [Feature](#feature-5) - [Bug or Regression](#bug-or-regression-6) - - [Other (Cleanup or Flake)](#other-cleanup-or-flake-4) - [Dependencies](#dependencies-7) - [Added](#added-7) - [Changed](#changed-7) - [Removed](#removed-7) -- [v1.35.0-alpha.3](#v1350-alpha3) - - [Downloads for v1.35.0-alpha.3](#downloads-for-v1350-alpha3) +- [v1.35.0-beta.0](#v1350-beta0) + - [Downloads for v1.35.0-beta.0](#downloads-for-v1350-beta0) - [Source Code](#source-code-8) - [Client Binaries](#client-binaries-8) - [Server Binaries](#server-binaries-8) - [Node Binaries](#node-binaries-8) - [Container Images](#container-images-8) - - [Changelog since v1.35.0-alpha.2](#changelog-since-v1350-alpha2) - - [Urgent Upgrade Notes](#urgent-upgrade-notes-1) - - [(No, really, you MUST read this before you upgrade)](#no-really-you-must-read-this-before-you-upgrade-1) + - [Changelog since v1.35.0-alpha.3](#changelog-since-v1350-alpha3) - [Changes by Kind](#changes-by-kind-8) - - [API Change](#api-change-2) - - [Feature](#feature-7) + - [API Change](#api-change-1) + - [Feature](#feature-6) - [Bug or Regression](#bug-or-regression-7) - - [Other (Cleanup or Flake)](#other-cleanup-or-flake-5) + - [Other (Cleanup or Flake)](#other-cleanup-or-flake-4) - [Dependencies](#dependencies-8) - [Added](#added-8) - [Changed](#changed-8) - [Removed](#removed-8) -- [v1.35.0-alpha.2](#v1350-alpha2) - - [Downloads for v1.35.0-alpha.2](#downloads-for-v1350-alpha2) +- [v1.35.0-alpha.3](#v1350-alpha3) + - [Downloads for v1.35.0-alpha.3](#downloads-for-v1350-alpha3) - [Source Code](#source-code-9) - [Client Binaries](#client-binaries-9) - [Server Binaries](#server-binaries-9) - [Node Binaries](#node-binaries-9) - [Container Images](#container-images-9) - - [Changelog since v1.35.0-alpha.1](#changelog-since-v1350-alpha1) + - [Changelog since v1.35.0-alpha.2](#changelog-since-v1350-alpha2) + - [Urgent Upgrade Notes](#urgent-upgrade-notes-1) + - [(No, really, you MUST read this before you upgrade)](#no-really-you-must-read-this-before-you-upgrade-1) - [Changes by Kind](#changes-by-kind-9) - - [Deprecation](#deprecation-1) - - [API Change](#api-change-3) - - [Feature](#feature-8) - - [Documentation](#documentation-1) + - [API Change](#api-change-2) + - [Feature](#feature-7) - [Bug or Regression](#bug-or-regression-8) - - [Other (Cleanup or Flake)](#other-cleanup-or-flake-6) + - [Other (Cleanup or Flake)](#other-cleanup-or-flake-5) - [Dependencies](#dependencies-9) - [Added](#added-9) - [Changed](#changed-9) - [Removed](#removed-9) -- [v1.35.0-alpha.1](#v1350-alpha1) - - [Downloads for v1.35.0-alpha.1](#downloads-for-v1350-alpha1) +- [v1.35.0-alpha.2](#v1350-alpha2) + - [Downloads for v1.35.0-alpha.2](#downloads-for-v1350-alpha2) - [Source Code](#source-code-10) - [Client Binaries](#client-binaries-10) - [Server Binaries](#server-binaries-10) - [Node Binaries](#node-binaries-10) - [Container Images](#container-images-10) - - [Changelog since v1.34.0](#changelog-since-v1340-1) + - [Changelog since v1.35.0-alpha.1](#changelog-since-v1350-alpha1) - [Changes by Kind](#changes-by-kind-10) - - [API Change](#api-change-4) - - [Feature](#feature-9) + - [Deprecation](#deprecation-1) + - [API Change](#api-change-3) + - [Feature](#feature-8) + - [Documentation](#documentation-1) - [Bug or Regression](#bug-or-regression-9) - - [Other (Cleanup or Flake)](#other-cleanup-or-flake-7) + - [Other (Cleanup or Flake)](#other-cleanup-or-flake-6) - [Dependencies](#dependencies-10) - [Added](#added-10) - [Changed](#changed-10) - [Removed](#removed-10) +- [v1.35.0-alpha.1](#v1350-alpha1) + - [Downloads for v1.35.0-alpha.1](#downloads-for-v1350-alpha1) + - [Source Code](#source-code-11) + - [Client Binaries](#client-binaries-11) + - [Server Binaries](#server-binaries-11) + - [Node Binaries](#node-binaries-11) + - [Container Images](#container-images-11) + - [Changelog since v1.34.0](#changelog-since-v1340-1) + - [Changes by Kind](#changes-by-kind-11) + - [API Change](#api-change-4) + - [Feature](#feature-9) + - [Bug or Regression](#bug-or-regression-10) + - [Other (Cleanup or Flake)](#other-cleanup-or-flake-7) + - [Dependencies](#dependencies-11) + - [Added](#added-11) + - [Changed](#changed-11) + - [Removed](#removed-11) +# v1.35.5 + + +## Downloads for v1.35.5 + + + +### Source Code + +filename | sha512 hash +-------- | ----------- +[kubernetes.tar.gz](https://dl.k8s.io/v1.35.5/kubernetes.tar.gz) | 9fae066bbdbcf68be964271a00be186c6fd8dbfa9d29ab66d060e11a23d2e472cff701014dfbcca4e5bd690a6767a32c4cc7d4f994975173a5f9ae84e4fe5b2c +[kubernetes-src.tar.gz](https://dl.k8s.io/v1.35.5/kubernetes-src.tar.gz) | 7d375a8c59ba80288db208c12b8dd016f9c2e1b1d5dafc8de5c6716c101279e048a0d4ff867eff044c4a6be5e6cda028a842def73ff99372fed973d9a78f875f + +### Client Binaries + +filename | sha512 hash +-------- | ----------- +[kubernetes-client-darwin-amd64.tar.gz](https://dl.k8s.io/v1.35.5/kubernetes-client-darwin-amd64.tar.gz) | ddddb5c6aa59ad9dfbf16f98091cb62ac76680ff30d557d8b4aaf1be2ce26acd41f48025aad3cb9fe5a67cdbe9c3fedeed817c9dcacc72f31a4a886bd1038cbd +[kubernetes-client-darwin-arm64.tar.gz](https://dl.k8s.io/v1.35.5/kubernetes-client-darwin-arm64.tar.gz) | a2595425c1de84f60c20cf381c57e81878b67ba459c5d425c224fabaff2f975ceb8bdcc6c37711e6f432e4c960801d1efec996da98ffe826afcb4901b4c4e6e5 +[kubernetes-client-linux-386.tar.gz](https://dl.k8s.io/v1.35.5/kubernetes-client-linux-386.tar.gz) | 909775fd9d3de94a1ff3e63db77061f6586532ba940a5bbc353c26b0a09cbd83c8066f87c5d2fe817882b6019957ad4542682939ae3dad39345c4c140f720cb1 +[kubernetes-client-linux-amd64.tar.gz](https://dl.k8s.io/v1.35.5/kubernetes-client-linux-amd64.tar.gz) | 5fc5b3e63884f18a5ac8856a4cb87a505826ff706a555baa30c1ce7eee1e7123c4f4a3a87f09a48924c15b50de82cf0bac37b859a92024eb9106a948f1ea9ab5 +[kubernetes-client-linux-arm.tar.gz](https://dl.k8s.io/v1.35.5/kubernetes-client-linux-arm.tar.gz) | 503d58934d62d55e099c9c236557b3d3fe295af4741984e6e2b68ab800e4b0bbe8ccc372172781dddae268260fc5eae337f30b8610adffdd53de3a8a0dbaaa9f +[kubernetes-client-linux-arm64.tar.gz](https://dl.k8s.io/v1.35.5/kubernetes-client-linux-arm64.tar.gz) | d981d71c41eb351e977ec39e5b7d1832c6cfed7d5fd951d4cff14f5e4b371ebf7941a27fe73024e503a7662c824a60b71f6a9db036f48c77dbb1c326695e74cf +[kubernetes-client-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.35.5/kubernetes-client-linux-ppc64le.tar.gz) | 5060414b36f3363789e165c7ed51f3ae42ee0fa31d0a47151e6e27da7eabc4e33ce005dfe96d7c1db8f64ab21df612e9c654241f32ad5a79f03dbd9080f5ca89 +[kubernetes-client-linux-s390x.tar.gz](https://dl.k8s.io/v1.35.5/kubernetes-client-linux-s390x.tar.gz) | 515e8ebda9d7152fc1c37c97a3629a477c5edb78b9cd8e752d4766e7c3c4d5dd2f1cca046aa8fd21290b1fe3aa5055df1659c12b12655c886f68573b3443b6ba +[kubernetes-client-windows-386.tar.gz](https://dl.k8s.io/v1.35.5/kubernetes-client-windows-386.tar.gz) | 6fc51b19b2975c119f4f2ed85fd5e951d0197df8583a972aa5e1c7122fe0ec97f367d22c216d7db4e04ddc16e8769e5ccdb8d96efa581bb1bb7c756dec369109 +[kubernetes-client-windows-amd64.tar.gz](https://dl.k8s.io/v1.35.5/kubernetes-client-windows-amd64.tar.gz) | 14fb6490b22582f68c280b38023798f25d26d74d43f81f9913612643b3ec0d64db0f124a136e166a68ed926f8452bfd243cd450c2604c772a8be2d327a7843ea +[kubernetes-client-windows-arm64.tar.gz](https://dl.k8s.io/v1.35.5/kubernetes-client-windows-arm64.tar.gz) | cc999fc58d65e37df49b0d5512a465299af91267e3dfb3a510cffd85afc58ab6f98593e0dac939e803f408345242bf31a6c268a62048f4575bb32e082acba7c4 + +### Server Binaries + +filename | sha512 hash +-------- | ----------- +[kubernetes-server-linux-amd64.tar.gz](https://dl.k8s.io/v1.35.5/kubernetes-server-linux-amd64.tar.gz) | e8c85142436316eb790ff7ca077f09051d6203b49601ddd56dc93770d32c6304f423d227469d7bdf603f56eb11e88e3d2d1ef6db2e443e10406ba768d0fffa74 +[kubernetes-server-linux-arm64.tar.gz](https://dl.k8s.io/v1.35.5/kubernetes-server-linux-arm64.tar.gz) | 8024efc9285c116d28924b80957783da9214d821bfe15a4d73c5be3764a6836f470847062f9ac1ae939379e590dd8f62d0f7b531034e478aca94182d954ac18c +[kubernetes-server-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.35.5/kubernetes-server-linux-ppc64le.tar.gz) | e2e29fcee38dc7dca1ff205ea723240da65149ac2c0bf8bf60f60f8c91c63384a6b145d59464a3b3509f9b0d9e5c840c6bd8a56cfddfbe1a9e02e2f171883738 +[kubernetes-server-linux-s390x.tar.gz](https://dl.k8s.io/v1.35.5/kubernetes-server-linux-s390x.tar.gz) | 9e07b5fd04bf43e4a9ac1b44d17a0959841ffe9827d8984c0a9b997cc60f8860445ff628404eb3519165f233de9586dd3855dfb715935f35b7e4a0c26855c264 + +### Node Binaries + +filename | sha512 hash +-------- | ----------- +[kubernetes-node-linux-amd64.tar.gz](https://dl.k8s.io/v1.35.5/kubernetes-node-linux-amd64.tar.gz) | b9bd503bb0ece05c4f34cb6611ceb8d1a9716f5cf4a5663898112e2e5a6c1e4e4a52206f62f900dfb24a8f9f799f7a7c20891ece8cdae521e7cd755b6fdbc844 +[kubernetes-node-linux-arm64.tar.gz](https://dl.k8s.io/v1.35.5/kubernetes-node-linux-arm64.tar.gz) | 7d804f2ef7fa5c5efcce7776ff784a22f09a6cdd1bd662c9b65f2feb793617e1693c478f0d2e3bb9764ce6c8f0eed1071eb596bd9f15eba0e7e43d99aa5f8b34 +[kubernetes-node-linux-ppc64le.tar.gz](https://dl.k8s.io/v1.35.5/kubernetes-node-linux-ppc64le.tar.gz) | f1b4a9174229ef68336de4eae91f13ac7bf5b9e59b62564a6f3c98cbb167ea61ad636b9be3800b3edccc05989570c5c0235363395a94244836c1e3308faa1e4e +[kubernetes-node-linux-s390x.tar.gz](https://dl.k8s.io/v1.35.5/kubernetes-node-linux-s390x.tar.gz) | c0f54407576ebec53aaba95a48da4bb3c8d46bb89fac00276f979ed8055b7a1ca838ff88efc85cbf9f5bdd2e6464b74db13a2bfc8deca0cc02935dd91a4b280c +[kubernetes-node-windows-amd64.tar.gz](https://dl.k8s.io/v1.35.5/kubernetes-node-windows-amd64.tar.gz) | 92e45a91666c7e13d79bb3dae7ad889c1f9b1d809c73b24fd2db493fd7887605f1d3d6d1fb243ebc42b99dc262eb9ac9a842ab792183807f5b97c490364ca0c6 + +### Container Images + +All container images are available as manifest lists and support the described +architectures. It is also possible to pull a specific architecture directly by +adding the "-$ARCH" suffix to the container image name. + +name | architectures +---- | ------------- +[registry.k8s.io/conformance:v1.35.5](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/conformance-s390x) +[registry.k8s.io/kube-apiserver:v1.35.5](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-apiserver-s390x) +[registry.k8s.io/kube-controller-manager:v1.35.5](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-controller-manager-s390x) +[registry.k8s.io/kube-proxy:v1.35.5](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-proxy-s390x) +[registry.k8s.io/kube-scheduler:v1.35.5](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kube-scheduler-s390x) +[registry.k8s.io/kubectl:v1.35.5](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl) | [amd64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-amd64), [arm64](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-arm64), [ppc64le](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-ppc64le), [s390x](https://console.cloud.google.com/artifacts/docker/k8s-artifacts-prod/southamerica-east1/images/kubectl-s390x) + +## Changelog since v1.35.4 + +## Changes by Kind + +### Bug or Regression + +- Fixed a scheduler bug where replacing a Pod with the same name during a failed scheduling attempt could leave stale in-flight queue state and unbounded growth of in-flight event tracking. The scheduler now clears in-flight state using the UID from the scheduling attempt, not only the UID on the refreshed Pod object. ([#138434](https://github.com/kubernetes/kubernetes/pull/138434), [@Argh4k](https://github.com/Argh4k)) [SIG Scheduling] +- Fixed stale remote HNS endpoint cleanup on Windows when a pod IP is reused across nodes in L2Bridge networks, preventing DNS timeouts caused by traffic being routed to the wrong node. ([#138602](https://github.com/kubernetes/kubernetes/pull/138602), [@princepereira](https://github.com/princepereira)) [SIG Network and Windows] +- Kube-proxy does not perform full-sync operations when operation in large cluster mode (more than 1000 endpoints) ([#138636](https://github.com/kubernetes/kubernetes/pull/138636), [@aojea](https://github.com/aojea)) [SIG Network] +- Kubeadm: during 'kubeadm init', if the default 'admin.conf' and 'super-admin.conf' paths are used, load the files, but construct in memory kubeconfigs that point to the InitConfiguration.localAPIEndpoint instead of the ClusterConfiguration.controlPlaneEndpoint. This would resolve issues with delayed load balancers which are provisioned only after the first kube-apiserver instance starts. ([#138683](https://github.com/kubernetes/kubernetes/pull/138683), [@neolit123](https://github.com/neolit123)) [SIG Cluster Lifecycle] +- Kubeadm: skip LocalAPIEndpoint defaulting on 'kubeadm join' for worker nodes. ([#138803](https://github.com/kubernetes/kubernetes/pull/138803), [@neolit123](https://github.com/neolit123)) [SIG Cluster Lifecycle] +- Kubeadm: use a dedicated ClusterRole 'system:kubelet-api-admin' for the kube-apiserver kubelet client. ([#138959](https://github.com/kubernetes/kubernetes/pull/138959), [@neolit123](https://github.com/neolit123)) [SIG Cluster Lifecycle] +- Kubeadm: when checking the etcd cluster status use a quorum approach, instead of considering the health of all members. This would allow the check to not fail if there are sufficient healthy voting members. ([#138539](https://github.com/kubernetes/kubernetes/pull/138539), [@ahrtr](https://github.com/ahrtr)) [SIG Cluster Lifecycle] +- Kubelet_pod_start_sli_duration_seconds_bucket metric now matches pod startup latency SLI/SLO documentation. ([#138153](https://github.com/kubernetes/kubernetes/pull/138153), [@alimaazamat](https://github.com/alimaazamat)) [SIG Node] + +## Dependencies + +### Added +_Nothing has changed._ + +### Changed +_Nothing has changed._ + +### Removed +_Nothing has changed._ + + + # v1.35.4 From 350f793a9c0ae0528ea669e028261f4ec8665ead Mon Sep 17 00:00:00 2001 From: takonomura Date: Fri, 8 May 2026 17:18:00 +0900 Subject: [PATCH 02/15] DRA: fix AllocationModeAll with consumed counters When collecting all matching devices for AllocationModeAll, the allocator did not record the source pool on the candidate device. Devices with consumed counters use that pool when checking shared counter availability, which caused kube-scheduler to panic. Set the pool on all-devices candidates in the stable, incubating, and experimental allocators, and add a shared regression test for AllocationModeAll with consumed counters. --- .../allocatortesting/allocator_testing.go | 37 +++++++++++++++++++ .../experimental/allocator_experimental.go | 1 + .../incubating/allocator_incubating.go | 1 + 3 files changed, 39 insertions(+) diff --git a/staging/src/k8s.io/dynamic-resource-allocation/structured/internal/allocatortesting/allocator_testing.go b/staging/src/k8s.io/dynamic-resource-allocation/structured/internal/allocatortesting/allocator_testing.go index 9a62ae4b98403..46ba0c6cd4efb 100644 --- a/staging/src/k8s.io/dynamic-resource-allocation/structured/internal/allocatortesting/allocator_testing.go +++ b/staging/src/k8s.io/dynamic-resource-allocation/structured/internal/allocatortesting/allocator_testing.go @@ -1223,6 +1223,43 @@ func TestAllocator(t *testing.T, deviceAllocationResult(req0, driverA, pool1, device1, false), )}, }, + "all-devices-with-consumed-counters": { + features: Features{ + PartitionableDevices: true, + }, + claimsToAllocate: objects(claimWithRequests(claim0, nil, resourceapi.DeviceRequest{ + Name: req0, + Exactly: &resourceapi.ExactDeviceRequest{ + AllocationMode: resourceapi.DeviceAllocationModeAll, + DeviceClassName: classA, + }, + })), + classes: objects(class(classA, driverA)), + slices: unwrapResourceSlices( + sliceWithDevices(slice1, node1, resourcePool(pool1, 2), driverA, + device(device1, nil, nil).withDeviceCounterConsumption( + deviceCounterConsumption(counterSet1, + map[string]resource.Quantity{ + "memory": resource.MustParse("4Gi"), + }, + ), + ), + ), + sliceWithCounterSets(slice2, node1, resourcePool(pool1, 2), driverA, + counterSet(counterSet1, + map[string]resource.Quantity{ + "memory": resource.MustParse("8Gi"), + }, + ), + ), + ), + node: node(node1, region1), + + expectResults: []any{allocationResult( + localNodeSelector(node1), + deviceAllocationResult(req0, driverA, pool1, device1, false), + )}, + }, "all-devices-many": { claimsToAllocate: objects(claimWithRequests(claim0, nil, resourceapi.DeviceRequest{ Name: req0, diff --git a/staging/src/k8s.io/dynamic-resource-allocation/structured/internal/experimental/allocator_experimental.go b/staging/src/k8s.io/dynamic-resource-allocation/structured/internal/experimental/allocator_experimental.go index fd63fff1e8e3f..8b4a077485e34 100644 --- a/staging/src/k8s.io/dynamic-resource-allocation/structured/internal/experimental/allocator_experimental.go +++ b/staging/src/k8s.io/dynamic-resource-allocation/structured/internal/experimental/allocator_experimental.go @@ -552,6 +552,7 @@ func (alloc *allocator) validateDeviceRequest(request requestAccessor, parentReq id: DeviceID{Driver: slice.Spec.Driver, Pool: slice.Spec.Pool.Name, Device: slice.Spec.Devices[deviceIndex].Name}, Device: &slice.Spec.Devices[deviceIndex], slice: slice, + pool: pool, } if alloc.features.ConsumableCapacity { // Next validate whether resource request over capacity diff --git a/staging/src/k8s.io/dynamic-resource-allocation/structured/internal/incubating/allocator_incubating.go b/staging/src/k8s.io/dynamic-resource-allocation/structured/internal/incubating/allocator_incubating.go index a051dd9640ca2..ef6c113596b0c 100644 --- a/staging/src/k8s.io/dynamic-resource-allocation/structured/internal/incubating/allocator_incubating.go +++ b/staging/src/k8s.io/dynamic-resource-allocation/structured/internal/incubating/allocator_incubating.go @@ -471,6 +471,7 @@ func (alloc *allocator) validateDeviceRequest(request requestAccessor, parentReq id: DeviceID{Driver: slice.Spec.Driver, Pool: slice.Spec.Pool.Name, Device: slice.Spec.Devices[deviceIndex].Name}, Device: &slice.Spec.Devices[deviceIndex], slice: slice, + pool: pool, } requestData.allDevices = append(requestData.allDevices, device) } From 9f52f4d26ccf8db47587b30eee7a0487ece9c164 Mon Sep 17 00:00:00 2001 From: Ondra Kupka Date: Tue, 24 Feb 2026 11:53:18 +0100 Subject: [PATCH 03/15] controller/selinuxwarning: Pre-parse SELinux label When calling ControllerSELinuxTranslator.Conflicts(), the SELinux label is repeatedly split into []string to detect conflicts. This causes a huge number of allocations when there are many comparisons. This is now made more efficient by pre-parsing the SELinux label and storing it in podInfo as [4]string for fast comparison when needed. --- .../selinuxwarning/cache/volumecache.go | 7 +- .../selinuxwarning/cache/volumecache_test.go | 2 + .../internal/parse/selinux_label.go | 32 ++++++ .../internal/parse/selinux_label_test.go | 106 ++++++++++++++++++ .../translator/selinux_translator.go | 21 ++-- 5 files changed, 156 insertions(+), 12 deletions(-) create mode 100644 pkg/controller/volume/selinuxwarning/internal/parse/selinux_label.go create mode 100644 pkg/controller/volume/selinuxwarning/internal/parse/selinux_label_test.go diff --git a/pkg/controller/volume/selinuxwarning/cache/volumecache.go b/pkg/controller/volume/selinuxwarning/cache/volumecache.go index 4b19c985c866e..dba6c5b93de74 100644 --- a/pkg/controller/volume/selinuxwarning/cache/volumecache.go +++ b/pkg/controller/volume/selinuxwarning/cache/volumecache.go @@ -23,6 +23,7 @@ import ( v1 "k8s.io/api/core/v1" "k8s.io/client-go/tools/cache" "k8s.io/klog/v2" + "k8s.io/kubernetes/pkg/controller/volume/selinuxwarning/internal/parse" "k8s.io/kubernetes/pkg/controller/volume/selinuxwarning/translator" ) @@ -81,6 +82,8 @@ type podInfo struct { // SELinux seLinuxLabel to be applied to the volume in the Pod. // Either as mount option or recursively by the container runtime. seLinuxLabel string + // Pre-parsed SELinux label parts for fast conflict detection. + seLinuxParts [4]string // SELinuxChangePolicy of the Pod. changePolicy v1.PodSELinuxChangePolicy } @@ -89,6 +92,7 @@ func newPodInfoListForPod(podKey cache.ObjectName, seLinuxLabel string, changePo return map[cache.ObjectName]podInfo{ podKey: { seLinuxLabel: seLinuxLabel, + seLinuxParts: parse.ParseSELinuxLabel(seLinuxLabel), changePolicy: changePolicy, }, } @@ -116,6 +120,7 @@ func (c *volumeCache) AddVolume(logger klog.Logger, volumeName v1.UniqueVolumeNa // The volume is already known podInfo := podInfo{ seLinuxLabel: label, + seLinuxParts: parse.ParseSELinuxLabel(label), changePolicy: changePolicy, } oldPodInfo, found := volume.pods[podKey] @@ -148,7 +153,7 @@ func (c *volumeCache) AddVolume(logger klog.Logger, volumeName v1.UniqueVolumeNa OtherPropertyValue: string(changePolicy), }) } - if c.seLinuxTranslator.Conflicts(otherPodInfo.seLinuxLabel, label) { + if c.seLinuxTranslator.ConflictsParsed(otherPodInfo.seLinuxParts, podInfo.seLinuxParts) { // Send conflict to both pods conflicts = append(conflicts, Conflict{ PropertyName: "SELinuxLabel", diff --git a/pkg/controller/volume/selinuxwarning/cache/volumecache_test.go b/pkg/controller/volume/selinuxwarning/cache/volumecache_test.go index 5bba301b6920a..71f916647b2cc 100644 --- a/pkg/controller/volume/selinuxwarning/cache/volumecache_test.go +++ b/pkg/controller/volume/selinuxwarning/cache/volumecache_test.go @@ -25,6 +25,7 @@ import ( "k8s.io/client-go/tools/cache" "k8s.io/klog/v2" "k8s.io/klog/v2/ktesting" + "k8s.io/kubernetes/pkg/controller/volume/selinuxwarning/internal/parse" "k8s.io/kubernetes/pkg/controller/volume/selinuxwarning/translator" ) @@ -436,6 +437,7 @@ func TestVolumeCache_AddVolumeSendConflicts(t *testing.T) { } expectedPodInfo := podInfo{ seLinuxLabel: tt.podToAdd.label, + seLinuxParts: parse.ParseSELinuxLabel(tt.podToAdd.label), changePolicy: tt.podToAdd.changePolicy, } if !reflect.DeepEqual(existingInfo, expectedPodInfo) { diff --git a/pkg/controller/volume/selinuxwarning/internal/parse/selinux_label.go b/pkg/controller/volume/selinuxwarning/internal/parse/selinux_label.go new file mode 100644 index 0000000000000..0fd48ed8b6783 --- /dev/null +++ b/pkg/controller/volume/selinuxwarning/internal/parse/selinux_label.go @@ -0,0 +1,32 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package parse + +import "strings" + +// ParseSELinuxLabel parses a SELinux label string into its components. +// Format: "user:role:type:level" -> [user, role, type, level] +// Missing components are represented as empty strings. +func ParseSELinuxLabel(label string) [4]string { + var parts [4]string + if label == "" { + return parts + } + split := strings.SplitN(label, ":", 4) + copy(parts[:], split) + return parts +} diff --git a/pkg/controller/volume/selinuxwarning/internal/parse/selinux_label_test.go b/pkg/controller/volume/selinuxwarning/internal/parse/selinux_label_test.go new file mode 100644 index 0000000000000..e82feed748f2c --- /dev/null +++ b/pkg/controller/volume/selinuxwarning/internal/parse/selinux_label_test.go @@ -0,0 +1,106 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package parse + +import ( + "reflect" + "testing" +) + +func TestParseSELinuxLabel(t *testing.T) { + tests := []struct { + name string + label string + expectedParts []string + }{ + { + name: "empty label", + label: "", + expectedParts: []string{"", "", "", ""}, + }, + { + name: "complete label with all components", + label: "system_u:system_r:container_t:s0:c0,c1", + expectedParts: []string{"system_u", "system_r", "container_t", "s0:c0,c1"}, + }, + { + name: "label with user, role, and type only", + label: "system_u:system_r:container_t", + expectedParts: []string{"system_u", "system_r", "container_t", ""}, + }, + { + name: "label with user and role only", + label: "system_u:system_r", + expectedParts: []string{"system_u", "system_r", "", ""}, + }, + { + name: "label with user only", + label: "system_u", + expectedParts: []string{"system_u", "", "", ""}, + }, + { + name: "label missing user but with role and type", + label: ":system_r:container_t", + expectedParts: []string{"", "system_r", "container_t", ""}, + }, + { + name: "label missing user and role but with type", + label: "::container_t", + expectedParts: []string{"", "", "container_t", ""}, + }, + { + name: "label missing user and role but with type and level", + label: "::container_t:s0", + expectedParts: []string{"", "", "container_t", "s0"}, + }, + { + name: "label with all empty components except level", + label: ":::s0:c0,c1", + expectedParts: []string{"", "", "", "s0:c0,c1"}, + }, + { + name: "label with special characters in components", + label: "user_with_underscore:role-with-dash:type.with.dots:s0:c0.c1", + expectedParts: []string{"user_with_underscore", "role-with-dash", "type.with.dots", "s0:c0.c1"}, + }, + { + name: "label with extra colons in level component", + label: "user:role:type:s0:c0,c1:extra", + expectedParts: []string{"user", "role", "type", "s0:c0,c1:extra"}, + }, + { + name: "multiple colons only", + label: ":::", + expectedParts: []string{"", "", "", ""}, + }, + { + name: "five colons", + label: ":::::", + expectedParts: []string{"", "", "", "::"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + parts := ParseSELinuxLabel(tt.label) + partsSlice := parts[:] + if !reflect.DeepEqual(partsSlice, tt.expectedParts) { + t.Errorf("ParseSELinuxLabel(%q) = %v, expected parts = %v", tt.label, partsSlice, tt.expectedParts) + } + }) + } +} diff --git a/pkg/controller/volume/selinuxwarning/translator/selinux_translator.go b/pkg/controller/volume/selinuxwarning/translator/selinux_translator.go index 99ce3e97dd7ca..db599c98cd7e1 100644 --- a/pkg/controller/volume/selinuxwarning/translator/selinux_translator.go +++ b/pkg/controller/volume/selinuxwarning/translator/selinux_translator.go @@ -20,6 +20,7 @@ import ( "strings" v1 "k8s.io/api/core/v1" + "k8s.io/kubernetes/pkg/controller/volume/selinuxwarning/internal/parse" "k8s.io/kubernetes/pkg/volume/util" ) @@ -70,18 +71,16 @@ func (c *ControllerSELinuxTranslator) SELinuxOptionsToFileLabel(opts *v1.SELinux // However: "system_u:system_r:container_t:s0:c1,c2" *does* conflict with ":::s0:c98,c99". // And ":::s0:c1,c2" *does* conflict with "" or ":::", because it's never defaulted by the OS. func (c *ControllerSELinuxTranslator) Conflicts(labelA, labelB string) bool { - partsA := strings.SplitN(labelA, ":", 4) - partsB := strings.SplitN(labelB, ":", 4) - - // Reorder, so partsA is always longer than partsB - if len(partsA) < len(partsB) { - partsB, partsA = partsA, partsB - } + return c.ConflictsParsed(parse.ParseSELinuxLabel(labelA), parse.ParseSELinuxLabel(labelB)) +} - for len(partsB) < len(partsA) { - partsB = append(partsB, "") - } - for i := range partsA { +// ConflictsParsed returns true if two pre-parsed SELinux labels conflict. +// This is an optimized version of Conflicts() that operates on pre-split labels +// to avoid repeated string allocations in hot paths (e.g., metrics collection). +// partsA and partsB must be 4-element arrays in the format: [user, role, type, level] +func (c *ControllerSELinuxTranslator) ConflictsParsed(partsA, partsB [4]string) bool { + // Compare each component + for i := range 4 { if partsA[i] == partsB[i] { continue } From fd088212052ffc4b94db03c76e8e10780e06ccda Mon Sep 17 00:00:00 2001 From: Ondra Kupka Date: Tue, 24 Feb 2026 11:36:31 +0100 Subject: [PATCH 04/15] controller/selinuxwarning/cache: Add reverse index Added podToVolumes reverse index to optimize DeletePod. Currently we simply iterate through all the volumes and remove the pod being deleted from there. This is inefficient and takes longer the longer the volume list becomes. Keeping a map pod -> volumes makes removing a pod fast. We can just jump to the relevant volumes directly and remove the pod from there. --- .../selinuxwarning/cache/volumecache.go | 46 ++++++++++++++++++- .../selinuxwarning/cache/volumecache_test.go | 44 ++++++++++++++++++ 2 files changed, 89 insertions(+), 1 deletion(-) diff --git a/pkg/controller/volume/selinuxwarning/cache/volumecache.go b/pkg/controller/volume/selinuxwarning/cache/volumecache.go index dba6c5b93de74..521476b1c446b 100644 --- a/pkg/controller/volume/selinuxwarning/cache/volumecache.go +++ b/pkg/controller/volume/selinuxwarning/cache/volumecache.go @@ -17,10 +17,12 @@ limitations under the License. package cache import ( + "slices" "sort" "sync" v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/util/sets" "k8s.io/client-go/tools/cache" "k8s.io/klog/v2" "k8s.io/kubernetes/pkg/controller/volume/selinuxwarning/internal/parse" @@ -57,6 +59,9 @@ type volumeCache struct { seLinuxTranslator *translator.ControllerSELinuxTranslator // All volumes of all existing Pods. volumes map[v1.UniqueVolumeName]usedVolume + // Reverse index: maps each pod to the list of volumes it uses. + // The index is used during pod deletion. + podToVolumes map[cache.ObjectName]sets.Set[v1.UniqueVolumeName] } var _ VolumeCache = &volumeCache{} @@ -66,6 +71,7 @@ func NewVolumeLabelCache(seLinuxTranslator *translator.ControllerSELinuxTranslat return &volumeCache{ seLinuxTranslator: seLinuxTranslator, volumes: make(map[v1.UniqueVolumeName]usedVolume), + podToVolumes: make(map[cache.ObjectName]sets.Set[v1.UniqueVolumeName]), } } @@ -114,6 +120,9 @@ func (c *volumeCache) AddVolume(logger klog.Logger, volumeName v1.UniqueVolumeNa pods: newPodInfoListForPod(podKey, label, changePolicy), } c.volumes[volumeName] = volume + + // Add to reverse index + c.registerPodVolume(podKey, volumeName) return conflicts } @@ -133,6 +142,9 @@ func (c *volumeCache) AddVolume(logger klog.Logger, volumeName v1.UniqueVolumeNa // Add the updated pod info to the cache volume.pods[podKey] = podInfo + // Add to reverse index + c.registerPodVolume(podKey, volumeName) + // Emit conflicts for the pod for otherPodKey, otherPodInfo := range volume.pods { if otherPodInfo.changePolicy != changePolicy { @@ -181,12 +193,28 @@ func (c *volumeCache) DeletePod(logger klog.Logger, podKey cache.ObjectName) { defer c.mutex.Unlock() defer c.dump(logger) - for volumeName, volume := range c.volumes { + // Use reverse index to only iterate through volumes this pod actually uses. + for volumeName := range c.podToVolumes[podKey] { + volume, found := c.volumes[volumeName] + if !found { + continue + } delete(volume.pods, podKey) if len(volume.pods) == 0 { delete(c.volumes, volumeName) } } + delete(c.podToVolumes, podKey) +} + +// registerPodVolume adds volumeName to the pod volume index. +// Make sure to hold c.mutex when calling this function. +func (c *volumeCache) registerPodVolume(podKey cache.ObjectName, volumeName v1.UniqueVolumeName) { + if podVolumes, ok := c.podToVolumes[podKey]; ok { + podVolumes.Insert(volumeName) + } else { + c.podToVolumes[podKey] = sets.New(volumeName) + } } func (c *volumeCache) dump(logger klog.Logger) { @@ -220,6 +248,22 @@ func (c *volumeCache) dump(logger klog.Logger) { logger.Info(" pod", "pod", podKey, "seLinuxLabel", podInfo.seLinuxLabel, "changePolicy", podInfo.changePolicy) } } + + // Collect all pods, sort them and print the associated volumes. + podKeys := make([]cache.ObjectName, 0, len(c.podToVolumes)) + for podKey := range c.podToVolumes { + podKeys = append(podKeys, podKey) + } + sort.Slice(podKeys, func(i, j int) bool { + return podKeys[i].String() < podKeys[j].String() + }) + + logger.Info("VolumeCache reverse index dump:") + for _, podKey := range podKeys { + podVolumes := sets.List(c.podToVolumes[podKey]) + slices.Sort(podVolumes) + logger.Info(" pod", "pod", podKey, "volumes", podVolumes) + } } // GetPodsForCSIDriver returns all pods that use volumes with the given CSI driver. diff --git a/pkg/controller/volume/selinuxwarning/cache/volumecache_test.go b/pkg/controller/volume/selinuxwarning/cache/volumecache_test.go index 71f916647b2cc..b1121cd28343a 100644 --- a/pkg/controller/volume/selinuxwarning/cache/volumecache_test.go +++ b/pkg/controller/volume/selinuxwarning/cache/volumecache_test.go @@ -46,6 +46,39 @@ func sortConflicts(conflicts []Conflict) { }) } +// verifyReverseIndexConsistency checks that forward and reverse indexes are symmetric +func verifyReverseIndexConsistency(t *testing.T, c *volumeCache) { + t.Helper() + + // For every (pod, volume) in reverse index, verify it exists in forward index. + for podKey, volumes := range c.podToVolumes { + for volumeName := range volumes { + volume, found := c.volumes[volumeName] + if !found { + t.Errorf("Reverse index has pod %s -> volume %s, but volume not in forward index", podKey, volumeName) + continue + } + if _, found := volume.pods[podKey]; !found { + t.Errorf("Reverse index has pod %s -> volume %s, but pod not in volume's pod list", podKey, volumeName) + } + } + } + + // For every (volume, pod) in forward index, verify it exists in reverse index. + for volumeName, volume := range c.volumes { + for podKey := range volume.pods { + podVolumes, found := c.podToVolumes[podKey] + if !found { + t.Errorf("Forward index has volume %s -> pod %s, but pod not in reverse index", volumeName, podKey) + continue + } + if _, found := podVolumes[volumeName]; !found { + t.Errorf("Forward index has volume %s -> pod %s, but volume not in pod's volume list", volumeName, podKey) + } + } + } +} + // Delete all items in a bigger cache and check it's empty func TestVolumeCache_DeleteAll(t *testing.T) { var podsToDelete []cache.ObjectName @@ -70,6 +103,8 @@ func TestVolumeCache_DeleteAll(t *testing.T) { t.Log("Before deleting all pods:") c.dump(dumpLogger) + verifyReverseIndexConsistency(t, c) + // Act: delete all pods for _, podKey := range podsToDelete { c.DeletePod(logger, podKey) @@ -80,6 +115,12 @@ func TestVolumeCache_DeleteAll(t *testing.T) { t.Errorf("Expected cache to be empty, got %d volumes", len(c.volumes)) c.dump(dumpLogger) } + + // Assert: the reverse index is also empty + if len(c.podToVolumes) != 0 { + t.Errorf("Expected reverse index to be empty, got %d pods", len(c.podToVolumes)) + } + verifyReverseIndexConsistency(t, c) } type podWithVolume struct { @@ -444,6 +485,9 @@ func TestVolumeCache_AddVolumeSendConflicts(t *testing.T) { t.Errorf("pod %s has unexpected info: %+v", podKey, existingInfo) } + // Verify reverse index consistency + verifyReverseIndexConsistency(t, c) + // Act again: get the conflicts via SendConflicts ch := make(chan Conflict) go func() { From 4a0532be209e6a56c6369a2f988fc9f0ae9e5f76 Mon Sep 17 00:00:00 2001 From: Hemant Kumar Date: Mon, 11 May 2026 17:34:32 -0400 Subject: [PATCH 05/15] Cache selinux conflicts Also prevent duplicate metric emissions --- .../selinuxwarning/cache/volumecache.go | 80 ++++--- .../selinuxwarning/cache/volumecache_test.go | 223 ++++++++++++++++-- .../volume/selinuxwarning/metrics.go | 8 +- .../selinux_warning_controller_test.go | 8 +- 4 files changed, 260 insertions(+), 59 deletions(-) diff --git a/pkg/controller/volume/selinuxwarning/cache/volumecache.go b/pkg/controller/volume/selinuxwarning/cache/volumecache.go index 521476b1c446b..ebd756584c0e6 100644 --- a/pkg/controller/volume/selinuxwarning/cache/volumecache.go +++ b/pkg/controller/volume/selinuxwarning/cache/volumecache.go @@ -48,8 +48,8 @@ type VolumeCache interface { // change their SELinux support dynamically. GetPodsForCSIDriver(driverName string) []cache.ObjectName - // SendConflicts sends all current conflicts to the given channel. - SendConflicts(logger klog.Logger, ch chan<- Conflict) + // GetConflicts returns the current set of active conflicts (both directions). + GetConflicts(logger klog.Logger) []Conflict } // VolumeCache stores all volumes used by Pods and their properties that the controller needs to track, @@ -62,6 +62,8 @@ type volumeCache struct { // Reverse index: maps each pod to the list of volumes it uses. // The index is used during pod deletion. podToVolumes map[cache.ObjectName]sets.Set[v1.UniqueVolumeName] + // Currently active conflicts per volume (both directions, symmetric pairs). + conflicts map[v1.UniqueVolumeName][]Conflict } var _ VolumeCache = &volumeCache{} @@ -72,6 +74,7 @@ func NewVolumeLabelCache(seLinuxTranslator *translator.ControllerSELinuxTranslat seLinuxTranslator: seLinuxTranslator, volumes: make(map[v1.UniqueVolumeName]usedVolume), podToVolumes: make(map[cache.ObjectName]sets.Set[v1.UniqueVolumeName]), + conflicts: make(map[v1.UniqueVolumeName][]Conflict), } } @@ -164,6 +167,7 @@ func (c *volumeCache) AddVolume(logger klog.Logger, volumeName v1.UniqueVolumeNa OtherPod: podKey, OtherPropertyValue: string(changePolicy), }) + } if c.seLinuxTranslator.ConflictsParsed(otherPodInfo.seLinuxParts, podInfo.seLinuxParts) { // Send conflict to both pods @@ -184,6 +188,21 @@ func (c *volumeCache) AddVolume(logger klog.Logger, volumeName v1.UniqueVolumeNa }) } } + // Update the conflict cache for this volume: remove stale conflicts for this pod, then add new ones + volumeConflicts := c.conflicts[volumeName] + updated := make([]Conflict, 0, len(volumeConflicts)) + for _, existing := range volumeConflicts { + if existing.Pod != podKey && existing.OtherPod != podKey { + updated = append(updated, existing) + } + } + updated = append(updated, conflicts...) + if len(updated) == 0 { + delete(c.conflicts, volumeName) + } else { + c.conflicts[volumeName] = updated + } + return conflicts } @@ -193,6 +212,25 @@ func (c *volumeCache) DeletePod(logger klog.Logger, podKey cache.ObjectName) { defer c.mutex.Unlock() defer c.dump(logger) + for volumeName := range c.podToVolumes[podKey] { + conflicts, found := c.conflicts[volumeName] + if !found { + continue + } + updated := make([]Conflict, 0, len(conflicts)) + for _, existing := range conflicts { + // preserve other conflicts belonging to volume + if existing.Pod != podKey && existing.OtherPod != podKey { + updated = append(updated, existing) + } + } + if len(updated) == 0 { + delete(c.conflicts, volumeName) + } else { + c.conflicts[volumeName] = updated + } + } + // Use reverse index to only iterate through volumes this pod actually uses. for volumeName := range c.podToVolumes[podKey] { volume, found := c.volumes[volumeName] @@ -283,42 +321,16 @@ func (c *volumeCache) GetPodsForCSIDriver(driverName string) []cache.ObjectName return pods } -// SendConflicts sends all current conflicts to the given channel. -func (c *volumeCache) SendConflicts(logger klog.Logger, ch chan<- Conflict) { +// GetConflicts returns the current set of active conflicts (both directions, symmetric pairs). +func (c *volumeCache) GetConflicts(logger klog.Logger) []Conflict { c.mutex.RLock() defer c.mutex.RUnlock() logger.V(4).Info("Scraping conflicts") c.dump(logger) - for _, volume := range c.volumes { - // compare pods that use the same volume with each other - for podKey, podInfo := range volume.pods { - for otherPodKey, otherPodInfo := range volume.pods { - if podKey == otherPodKey { - continue - } - // create conflict only for the first pod. The other pod will get the same conflict in its own iteration of `volume.pods` loop. - if podInfo.changePolicy != otherPodInfo.changePolicy { - ch <- Conflict{ - PropertyName: "SELinuxChangePolicy", - EventReason: "SELinuxChangePolicyConflict", - Pod: podKey, - PropertyValue: string(podInfo.changePolicy), - OtherPod: otherPodKey, - OtherPropertyValue: string(otherPodInfo.changePolicy), - } - } - if c.seLinuxTranslator.Conflicts(podInfo.seLinuxLabel, otherPodInfo.seLinuxLabel) { - ch <- Conflict{ - PropertyName: "SELinuxLabel", - EventReason: "SELinuxLabelConflict", - Pod: podKey, - PropertyValue: podInfo.seLinuxLabel, - OtherPod: otherPodKey, - OtherPropertyValue: otherPodInfo.seLinuxLabel, - } - } - } - } + result := sets.New[Conflict]() + for _, volConflicts := range c.conflicts { + result.Insert(volConflicts...) } + return result.UnsortedList() } diff --git a/pkg/controller/volume/selinuxwarning/cache/volumecache_test.go b/pkg/controller/volume/selinuxwarning/cache/volumecache_test.go index b1121cd28343a..82fd27e815fe8 100644 --- a/pkg/controller/volume/selinuxwarning/cache/volumecache_test.go +++ b/pkg/controller/volume/selinuxwarning/cache/volumecache_test.go @@ -147,8 +147,8 @@ func addReverseConflict(conflicts []Conflict) []Conflict { return newConflicts } -// Test AddVolume and SendConflicts together, they both provide []conflict with the same data -func TestVolumeCache_AddVolumeSendConflicts(t *testing.T) { +// Test that AddVolume and GetConflicts return the same []conflict data +func TestVolumeCache_AddVolumeGetConflicts(t *testing.T) { existingPods := []podWithVolume{ { podNamespace: "ns1", @@ -488,18 +488,8 @@ func TestVolumeCache_AddVolumeSendConflicts(t *testing.T) { // Verify reverse index consistency verifyReverseIndexConsistency(t, c) - // Act again: get the conflicts via SendConflicts - ch := make(chan Conflict) - go func() { - c.SendConflicts(logger, ch) - close(ch) - }() - - // Assert - receivedConflicts := []Conflict{} - for c := range ch { - receivedConflicts = append(receivedConflicts, c) - } + // Verify that GetConflicts returns the same conflicts + receivedConflicts := c.GetConflicts(logger) sortConflicts(receivedConflicts) if !reflect.DeepEqual(receivedConflicts, expectedConflicts) { t.Errorf("SendConflicts returned unexpected conflicts: %+v", receivedConflicts) @@ -509,6 +499,211 @@ func TestVolumeCache_AddVolumeSendConflicts(t *testing.T) { } } +// Test that conflicts are tracked per-volume: a pod with conflicts on +// multiple volumes retains all of them after successive AddVolume calls. +func TestVolumeCache_MultiVolumeConflicts(t *testing.T) { + logger, _ := getTestLoggers(t) + seLinuxTranslator := &translator.ControllerSELinuxTranslator{} + c := NewVolumeLabelCache(seLinuxTranslator).(*volumeCache) + + podA := cache.ObjectName{Namespace: "ns", Name: "podA"} + podB := cache.ObjectName{Namespace: "ns", Name: "podB"} + podC := cache.ObjectName{Namespace: "ns", Name: "podC"} + + // podB uses vol1 with label1 + c.AddVolume(logger, "vol1", podB, "system_u:system_r:labelB", v1.SELinuxChangePolicyMountOption, "driver1") + // podC uses vol2 with label2 + c.AddVolume(logger, "vol2", podC, "system_u:system_r:labelC", v1.SELinuxChangePolicyMountOption, "driver1") + + // podA uses vol1 with a different label (conflict with podB) + conflicts1 := c.AddVolume(logger, "vol1", podA, "system_u:system_r:labelA", v1.SELinuxChangePolicyMountOption, "driver1") + if len(conflicts1) == 0 { + t.Fatal("Expected conflicts on vol1 between podA and podB") + } + + // podA also uses vol2 with a different label (conflict with podC) + conflicts2 := c.AddVolume(logger, "vol2", podA, "system_u:system_r:labelA", v1.SELinuxChangePolicyMountOption, "driver1") + if len(conflicts2) == 0 { + t.Fatal("Expected conflicts on vol2 between podA and podC") + } + + // GetConflicts must return conflicts from BOTH volumes + allConflicts := c.GetConflicts(logger) + expectedCount := len(conflicts1) + len(conflicts2) + if len(allConflicts) != expectedCount { + t.Errorf("GetConflicts returned %d conflicts, expected %d (vol1: %d + vol2: %d)", + len(allConflicts), expectedCount, len(conflicts1), len(conflicts2)) + } + + // After deleting podA, all conflicts should be gone + c.DeletePod(logger, podA) + remaining := c.GetConflicts(logger) + if len(remaining) != 0 { + t.Errorf("Expected no conflicts after deleting podA, got %d: %+v", len(remaining), remaining) + } + + // Verify deduplication: podD and podE conflict on two volumes with the same labels. + // Identical Conflict entries from different volumes must be deduplicated by GetConflicts. + podD := cache.ObjectName{Namespace: "ns", Name: "podD"} + podE := cache.ObjectName{Namespace: "ns", Name: "podE"} + + c.AddVolume(logger, "vol3", podD, "system_u:system_r:labelD", v1.SELinuxChangePolicyMountOption, "driver1") + c.AddVolume(logger, "vol4", podD, "system_u:system_r:labelD", v1.SELinuxChangePolicyMountOption, "driver1") + + conflictsVol3 := c.AddVolume(logger, "vol3", podE, "system_u:system_r:labelE", v1.SELinuxChangePolicyMountOption, "driver1") + conflictsVol4 := c.AddVolume(logger, "vol4", podE, "system_u:system_r:labelE", v1.SELinuxChangePolicyMountOption, "driver1") + + if len(conflictsVol3) != len(conflictsVol4) { + t.Fatalf("Expected same number of conflicts from vol3 and vol4 (%d vs %d)", len(conflictsVol3), len(conflictsVol4)) + } + if len(conflictsVol3) == 0 { + t.Fatal("Expected conflicts between podD and podE") + } + + allConflicts = c.GetConflicts(logger) + deCount := 0 + for _, conflict := range allConflicts { + if conflict.Pod == podD || conflict.Pod == podE || conflict.OtherPod == podD || conflict.OtherPod == podE { + deCount++ + } + } + if deCount != len(conflictsVol3) { + t.Errorf("Expected %d deduplicated conflicts for podD/podE (from 2 volumes), got %d", len(conflictsVol3), deCount) + } +} + +func TestVolumeCache_DeletePodConflicts(t *testing.T) { + podA := cache.ObjectName{Namespace: "ns", Name: "podA"} + podB := cache.ObjectName{Namespace: "ns", Name: "podB"} + podC := cache.ObjectName{Namespace: "ns", Name: "podC"} + podD := cache.ObjectName{Namespace: "ns", Name: "podD"} + + tests := []struct { + name string + // Pods to add before deletion. + initialPods []podWithVolume + // Pod to delete. + podToDelete cache.ObjectName + // If true, delete the pod a second time to verify idempotency. + deleteTwice bool + // Pod pairs that must still have symmetric conflicts after deletion. + // Each pair [2]cache.ObjectName expects both (A→B) and (B→A) to be present. + expectedSurvivingPairs [][2]cache.ObjectName + }{ + { + name: "delete one of two conflicting pods clears all conflicts", + initialPods: []podWithVolume{ + {podNamespace: "ns", podName: "podA", volumeName: "vol1", label: "system_u:system_r:labelA", changePolicy: v1.SELinuxChangePolicyMountOption}, + {podNamespace: "ns", podName: "podB", volumeName: "vol1", label: "system_u:system_r:labelB", changePolicy: v1.SELinuxChangePolicyMountOption}, + }, + podToDelete: podA, + expectedSurvivingPairs: nil, + }, + { + name: "delete non-conflicting pod preserves existing conflicts", + initialPods: []podWithVolume{ + {podNamespace: "ns", podName: "podA", volumeName: "vol1", label: "system_u:system_r:labelA", changePolicy: v1.SELinuxChangePolicyMountOption}, + {podNamespace: "ns", podName: "podB", volumeName: "vol1", label: "system_u:system_r:labelB", changePolicy: v1.SELinuxChangePolicyMountOption}, + {podNamespace: "ns", podName: "podC", volumeName: "vol2", label: "system_u:system_r:labelC", changePolicy: v1.SELinuxChangePolicyMountOption}, + }, + podToDelete: podC, + expectedSurvivingPairs: [][2]cache.ObjectName{{podA, podB}}, + }, + { + name: "three pods on same volume delete one leaves remaining pair conflict", + initialPods: []podWithVolume{ + {podNamespace: "ns", podName: "podA", volumeName: "vol1", label: "system_u:system_r:labelA", changePolicy: v1.SELinuxChangePolicyMountOption}, + {podNamespace: "ns", podName: "podB", volumeName: "vol1", label: "system_u:system_r:labelB", changePolicy: v1.SELinuxChangePolicyMountOption}, + {podNamespace: "ns", podName: "podC", volumeName: "vol1", label: "system_u:system_r:labelC", changePolicy: v1.SELinuxChangePolicyMountOption}, + }, + podToDelete: podA, + expectedSurvivingPairs: [][2]cache.ObjectName{{podB, podC}}, + }, + { + name: "delete pod with conflicts on multiple volumes", + initialPods: []podWithVolume{ + {podNamespace: "ns", podName: "podB", volumeName: "vol1", label: "system_u:system_r:labelB", changePolicy: v1.SELinuxChangePolicyMountOption}, + {podNamespace: "ns", podName: "podC", volumeName: "vol2", label: "system_u:system_r:labelC", changePolicy: v1.SELinuxChangePolicyMountOption}, + {podNamespace: "ns", podName: "podA", volumeName: "vol1", label: "system_u:system_r:labelA", changePolicy: v1.SELinuxChangePolicyMountOption}, + {podNamespace: "ns", podName: "podA", volumeName: "vol2", label: "system_u:system_r:labelA", changePolicy: v1.SELinuxChangePolicyMountOption}, + }, + podToDelete: podA, + expectedSurvivingPairs: nil, + }, + { + name: "delete pod preserves conflicts on unrelated volumes", + initialPods: []podWithVolume{ + {podNamespace: "ns", podName: "podA", volumeName: "vol1", label: "system_u:system_r:labelA", changePolicy: v1.SELinuxChangePolicyMountOption}, + {podNamespace: "ns", podName: "podB", volumeName: "vol1", label: "system_u:system_r:labelB", changePolicy: v1.SELinuxChangePolicyMountOption}, + {podNamespace: "ns", podName: "podC", volumeName: "vol2", label: "system_u:system_r:labelC", changePolicy: v1.SELinuxChangePolicyMountOption}, + {podNamespace: "ns", podName: "podD", volumeName: "vol2", label: "system_u:system_r:labelD", changePolicy: v1.SELinuxChangePolicyMountOption}, + }, + podToDelete: podA, + expectedSurvivingPairs: [][2]cache.ObjectName{{podC, podD}}, + }, + { + name: "delete pod that was already deleted is a no-op", + initialPods: []podWithVolume{ + {podNamespace: "ns", podName: "podA", volumeName: "vol1", label: "system_u:system_r:labelA", changePolicy: v1.SELinuxChangePolicyMountOption}, + {podNamespace: "ns", podName: "podB", volumeName: "vol1", label: "system_u:system_r:labelB", changePolicy: v1.SELinuxChangePolicyMountOption}, + }, + podToDelete: podA, + deleteTwice: true, + expectedSurvivingPairs: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + logger, _ := getTestLoggers(t) + seLinuxTranslator := &translator.ControllerSELinuxTranslator{} + c := NewVolumeLabelCache(seLinuxTranslator).(*volumeCache) + + for _, pod := range tt.initialPods { + c.AddVolume(logger, pod.volumeName, cache.ObjectName{Namespace: pod.podNamespace, Name: pod.podName}, pod.label, pod.changePolicy, "driver1") + } + + c.DeletePod(logger, tt.podToDelete) + if tt.deleteTwice { + c.DeletePod(logger, tt.podToDelete) + } + + remaining := c.GetConflicts(logger) + + // Deleted pod must not appear in any conflict + for _, conflict := range remaining { + if conflict.Pod == tt.podToDelete || conflict.OtherPod == tt.podToDelete { + t.Errorf("found conflict involving deleted pod %s: %+v", tt.podToDelete, conflict) + } + } + + // Verify each expected surviving pair exists in both directions + for _, pair := range tt.expectedSurvivingPairs { + hasForward := false + hasReverse := false + for _, conflict := range remaining { + if conflict.Pod == pair[0] && conflict.OtherPod == pair[1] { + hasForward = true + } + if conflict.Pod == pair[1] && conflict.OtherPod == pair[0] { + hasReverse = true + } + } + if !hasForward || !hasReverse { + t.Errorf("expected symmetric conflict between %s and %s, got %+v", pair[0], pair[1], remaining) + } + } + + // If no pairs are expected, there should be no conflicts at all + if len(tt.expectedSurvivingPairs) == 0 && len(remaining) != 0 { + t.Errorf("expected no conflicts, got %+v", remaining) + } + + verifyReverseIndexConsistency(t, c) + }) + } +} + func TestVolumeCache_GetPodsForCSIDriver(t *testing.T) { seLinuxTranslator := &translator.ControllerSELinuxTranslator{} c := NewVolumeLabelCache(seLinuxTranslator).(*volumeCache) diff --git a/pkg/controller/volume/selinuxwarning/metrics.go b/pkg/controller/volume/selinuxwarning/metrics.go index d95665c916248..c285bd78db4a8 100644 --- a/pkg/controller/volume/selinuxwarning/metrics.go +++ b/pkg/controller/volume/selinuxwarning/metrics.go @@ -59,13 +59,7 @@ func (c *collector) DescribeWithStability(ch chan<- *metrics.Desc) { } func (c *collector) CollectWithStability(ch chan<- metrics.Metric) { - conflictCh := make(chan cache.Conflict) - go func() { - c.cache.SendConflicts(c.logger, conflictCh) - close(conflictCh) - }() - - for conflict := range conflictCh { + for _, conflict := range c.cache.GetConflicts(c.logger) { ch <- metrics.NewLazyConstMetric(seLinuxConflictDesc, metrics.GaugeValue, 1.0, diff --git a/pkg/controller/volume/selinuxwarning/selinux_warning_controller_test.go b/pkg/controller/volume/selinuxwarning/selinux_warning_controller_test.go index 9d9998bc62a9f..1d2e66f4e40e9 100644 --- a/pkg/controller/volume/selinuxwarning/selinux_warning_controller_test.go +++ b/pkg/controller/volume/selinuxwarning/selinux_warning_controller_test.go @@ -783,12 +783,12 @@ func (f *fakeVolumeCache) GetPodsForCSIDriver(driverName string) []cache.ObjectN return pods } -func (f *fakeVolumeCache) SendConflicts(logger klog.Logger, ch chan<- volumecache.Conflict) { +func (f *fakeVolumeCache) GetConflicts(logger klog.Logger) []volumecache.Conflict { + result := make([]volumecache.Conflict, 0) for _, conflicts := range f.conflictsToSend { - for _, conflict := range conflicts { - ch <- conflict - } + result = append(result, conflicts...) } + return result } func collectEvents(source <-chan string) []string { From b596e7bcff25b60d524934d90b9b24f6b2795b60 Mon Sep 17 00:00:00 2001 From: Jordan Liggitt Date: Tue, 19 May 2026 16:29:03 -0400 Subject: [PATCH 06/15] Restore ability to plumb binary data through envvar values --- pkg/kubelet/container/runtime.go | 2 +- pkg/kubelet/kubelet_pods.go | 5 ++++- pkg/kubelet/kuberuntime/kuberuntime_container.go | 3 ++- staging/src/k8s.io/cri-api/pkg/apis/runtime/v1/api.pb.go | 8 ++++---- staging/src/k8s.io/cri-api/pkg/apis/runtime/v1/api.proto | 2 +- 5 files changed, 12 insertions(+), 8 deletions(-) diff --git a/pkg/kubelet/container/runtime.go b/pkg/kubelet/container/runtime.go index 46deb9da07b80..8823f866f5240 100644 --- a/pkg/kubelet/container/runtime.go +++ b/pkg/kubelet/container/runtime.go @@ -462,7 +462,7 @@ type Image struct { // EnvVar represents the environment variable. type EnvVar struct { Name string - Value string + Value string // TODO: switch to []byte } // Annotation represents an annotation. diff --git a/pkg/kubelet/kubelet_pods.go b/pkg/kubelet/kubelet_pods.go index 4df460a08805e..2c8d0c07f0d6f 100644 --- a/pkg/kubelet/kubelet_pods.go +++ b/pkg/kubelet/kubelet_pods.go @@ -766,7 +766,7 @@ func (kl *Kubelet) makeEnvironmentVariables(pod *v1.Pod, container *v1.Container var ( configMaps = make(map[string]*v1.ConfigMap) secrets = make(map[string]*v1.Secret) - tmpEnv = make(map[string]string) + tmpEnv = make(map[string]string) // TODO: switch to map[string][]byte ) // Env will override EnvFrom variables. @@ -798,6 +798,7 @@ func (kl *Kubelet) makeEnvironmentVariables(pod *v1.Pod, container *v1.Container k = envFrom.Prefix + k } + // TODO: validate no NUL bytes tmpEnv[k] = v } case envFrom.SecretRef != nil: @@ -825,6 +826,7 @@ func (kl *Kubelet) makeEnvironmentVariables(pod *v1.Pod, container *v1.Container k = envFrom.Prefix + k } + // TODO: validate no NUL bytes tmpEnv[k] = string(v) } } @@ -918,6 +920,7 @@ func (kl *Kubelet) makeEnvironmentVariables(pod *v1.Pod, container *v1.Container } return result, fmt.Errorf("couldn't find key %v in Secret %v/%v", key, pod.Namespace, name) } + // TODO: validate no NUL bytes runtimeVal = string(runtimeValBytes) case utilfeature.DefaultFeatureGate.Enabled(features.EnvFiles) && envVar.ValueFrom.FileKeyRef != nil: f := envVar.ValueFrom.FileKeyRef diff --git a/pkg/kubelet/kuberuntime/kuberuntime_container.go b/pkg/kubelet/kuberuntime/kuberuntime_container.go index 8ef687b530939..50aefa005546f 100644 --- a/pkg/kubelet/kuberuntime/kuberuntime_container.go +++ b/pkg/kubelet/kuberuntime/kuberuntime_container.go @@ -34,6 +34,7 @@ import ( "time" codes "google.golang.org/grpc/codes" + crierror "k8s.io/cri-api/pkg/errors" "github.com/opencontainers/selinux/go-selinux" @@ -399,7 +400,7 @@ func (m *kubeGenericRuntimeManager) generateContainerConfig(ctx context.Context, e := opts.Envs[idx] envs[idx] = &runtimeapi.KeyValue{ Key: e.Name, - Value: e.Value, + Value: []byte(e.Value), } } config.Envs = envs diff --git a/staging/src/k8s.io/cri-api/pkg/apis/runtime/v1/api.pb.go b/staging/src/k8s.io/cri-api/pkg/apis/runtime/v1/api.pb.go index ab5bda03a6a57..98e3c33e1de4c 100644 --- a/staging/src/k8s.io/cri-api/pkg/apis/runtime/v1/api.pb.go +++ b/staging/src/k8s.io/cri-api/pkg/apis/runtime/v1/api.pb.go @@ -4078,7 +4078,7 @@ func (x *ImageSpec) GetRuntimeHandler() string { type KeyValue struct { state protoimpl.MessageState `protogen:"open.v1"` Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` - Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` + Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -4120,11 +4120,11 @@ func (x *KeyValue) GetKey() string { return "" } -func (x *KeyValue) GetValue() string { +func (x *KeyValue) GetValue() []byte { if x != nil { return x.Value } - return "" + return nil } // LinuxContainerResources specifies Linux specific configuration for @@ -11571,7 +11571,7 @@ var file_staging_src_k8s_io_cri_api_pkg_apis_runtime_v1_api_proto_rawDesc = stri 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x32, 0x0a, 0x08, 0x4b, 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, + 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x95, 0x04, 0x0a, 0x17, 0x4c, 0x69, 0x6e, 0x75, 0x78, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x70, 0x75, 0x5f, 0x70, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, diff --git a/staging/src/k8s.io/cri-api/pkg/apis/runtime/v1/api.proto b/staging/src/k8s.io/cri-api/pkg/apis/runtime/v1/api.proto index 9e7b26aa72e41..3649609bfc1e3 100644 --- a/staging/src/k8s.io/cri-api/pkg/apis/runtime/v1/api.proto +++ b/staging/src/k8s.io/cri-api/pkg/apis/runtime/v1/api.proto @@ -839,7 +839,7 @@ message ImageSpec { message KeyValue { string key = 1; - string value = 2; + bytes value = 2; } // LinuxContainerResources specifies Linux specific configuration for From f1ce76d7a152fa4eec2d2531a2052d743936716e Mon Sep 17 00:00:00 2001 From: Anish Ramasekar Date: Wed, 13 May 2026 11:49:54 -0700 Subject: [PATCH 07/15] feat(volume): add IsRemount to MounterArgs Thread the reconciler's existing isRemount signal into MounterArgs so volume plugins can distinguish an initial publish from a republish (e.g. CSIDriver.spec.requiresRepublish=true). No behavior change. --- .../util/operationexecutor/operation_generator.go | 1 + pkg/volume/volume.go | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/pkg/volume/util/operationexecutor/operation_generator.go b/pkg/volume/util/operationexecutor/operation_generator.go index 9d5c937d95372..790ff5e119dbb 100644 --- a/pkg/volume/util/operationexecutor/operation_generator.go +++ b/pkg/volume/util/operationexecutor/operation_generator.go @@ -586,6 +586,7 @@ func (og *operationGenerator) GenerateMountVolumeFunc( FSGroupChangePolicy: fsGroupChangePolicy, Recorder: og.recorder, SELinuxLabel: volumeToMount.SELinuxLabel, + IsRemount: isRemount, }) // Update actual state of world markOpts := MarkVolumeOpts{ diff --git a/pkg/volume/volume.go b/pkg/volume/volume.go index 4a6fc55e29bb7..fa5c7c6025dd7 100644 --- a/pkg/volume/volume.go +++ b/pkg/volume/volume.go @@ -138,6 +138,16 @@ type MounterArgs struct { // Optional interface that will be used to change the ownership of the volume, if specified. // mainly used by unit tests VolumeOwnershipApplicator VolumeOwnershipChanger + + // IsRemount is true when SetUp is being invoked on a volume that the + // reconciler considers already mounted to the pod, e.g. a periodic + // republish triggered by CSIDriver.spec.requiresRepublish=true. Volume + // plugins should use this to avoid destroying state (e.g. mount + // directories, volume metadata files) that the pod is currently + // observing through an existing bind mount, since teardown on a + // failed remount cannot be repaired by a subsequent successful + // remount and would leave the pod with stale contents. + IsRemount bool } type VolumeOwnershipChanger interface { From df562ae381276d25c0ef3c9c9cabb53bb83610f9 Mon Sep 17 00:00:00 2001 From: Anish Ramasekar Date: Wed, 13 May 2026 11:55:18 -0700 Subject: [PATCH 08/15] fix(csi): preserve mount dir on NodePublish error during remount On a remount (e.g. CSIDriver.spec.requiresRepublish=true), the volume is already published and the pod is observing the existing bind mount. Removing the mount dir on a NodePublish error left the pod with stale contents that subsequent successful republishes could not repair. --- pkg/volume/csi/csi_mounter.go | 6 ++- pkg/volume/csi/csi_mounter_test.go | 82 ++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 1 deletion(-) diff --git a/pkg/volume/csi/csi_mounter.go b/pkg/volume/csi/csi_mounter.go index f8760280dcb0e..2674a9afac6ec 100644 --- a/pkg/volume/csi/csi_mounter.go +++ b/pkg/volume/csi/csi_mounter.go @@ -314,7 +314,11 @@ func (c *csiMountMgr) SetUpAt(dir string, mounterArgs volume.MounterArgs) error if csiRPCError != nil { // If operation finished with error then we can remove the mount directory. - if volumetypes.IsOperationFinishedError(csiRPCError) { + // Skip on remount (e.g. CSIDriver.spec.requiresRepublish=true): the volume + // was already published and the pod is observing the existing bind mount, + // so removing the mount dir here would leave the pod with stale contents + // that a subsequent successful republish cannot repair (#121271). + if volumetypes.IsOperationFinishedError(csiRPCError) && !mounterArgs.IsRemount { if removeMountDirErr := removeMountDir(c.plugin, dir); removeMountDirErr != nil { klog.Error(log("mounter.SetupAt failed to remove mount dir after a NodePublish() error [%s]: %v", dir, removeMountDirErr)) } diff --git a/pkg/volume/csi/csi_mounter_test.go b/pkg/volume/csi/csi_mounter_test.go index 62aecb809f0b1..e192eec60265b 100644 --- a/pkg/volume/csi/csi_mounter_test.go +++ b/pkg/volume/csi/csi_mounter_test.go @@ -28,6 +28,8 @@ import ( "time" "github.com/google/go-cmp/cmp" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" authenticationv1 "k8s.io/api/authentication/v1" corev1 "k8s.io/api/core/v1" storage "k8s.io/api/storage/v1" @@ -1169,6 +1171,86 @@ func TestMounterSetUpWithFSGroup(t *testing.T) { } } +func TestMounterSetUpFWithNodePublishFinalError(t *testing.T) { + testCases := []struct { + name string + podUID types.UID + options []string + spec func(string, []string) *volume.Spec + isRemount bool + expectDataFileExists bool + }{ + { + // Regression test for https://github.com/kubernetes/kubernetes/issues/121271: + // when NodePublishVolume fails on a remount (e.g. a republish + // triggered by CSIDriver.spec.requiresRepublish=true), the + // mount directory and vol_data.json must be preserved so the + // pod continues to see the previously-published contents and + // a subsequent successful republish can refresh them in place. + name: "setup with remount preserves mount dir on final error", + podUID: types.UID(fmt.Sprintf("%08X", rand.Uint64())), + spec: func(fsType string, options []string) *volume.Spec { + pvSrc := makeTestPV("pv1", 20, testDriver, "vol1") + pvSrc.Spec.CSI.FSType = fsType + pvSrc.Spec.MountOptions = options + return volume.NewSpecFromPersistentVolume(pvSrc, false) + }, + isRemount: true, + expectDataFileExists: true, + }, + } + + for _, tc := range testCases { + volumeLifecycleModes := []storage.VolumeLifecycleMode{ + storage.VolumeLifecyclePersistent, + } + driver := getTestCSIDriver(testDriver, nil, nil, volumeLifecycleModes) + fakeClient := fakeclient.NewClientset(driver) + plug, tmpDir := newTestPlugin(t, fakeClient) + defer func() { + _ = os.RemoveAll(tmpDir) + }() + registerFakePlugin(testDriver, "endpoint", []string{"1.0.0"}, t) + t.Run(tc.name, func(t *testing.T) { + mounter, err := plug.NewMounter( + tc.spec("zfs", tc.options), + &corev1.Pod{ObjectMeta: meta.ObjectMeta{UID: tc.podUID, Namespace: testns}}, + ) + if mounter == nil || err != nil { + t.Fatal("failed to create CSI mounter") + } + + csiMounter := mounter.(*csiMountMgr) + csiMounter.csiClient = setupClient(t, true) + + attachID := getAttachmentName(csiMounter.volumeID, string(csiMounter.driverName), string(plug.host.GetNodeName())) + attachment := makeTestAttachment(attachID, "test-node", csiMounter.spec.Name()) + _, err = csiMounter.k8s.StorageV1().VolumeAttachments().Create(context.TODO(), attachment, meta.CreateOptions{}) + if err != nil { + t.Fatalf("failed to setup VolumeAttachment: %v", err) + } + + csiMounter.csiClient.(*fakeCsiDriverClient).nodeClient.SetNextError(status.Errorf(codes.InvalidArgument, "mount failed")) + + // Mounter.SetUp() + if err := csiMounter.SetUp(volume.MounterArgs{ + IsRemount: tc.isRemount, + }); err == nil { + t.Fatalf("mounter.Setup expected err but succeed") + } + + mountPath := csiMounter.GetPath() + volPath := filepath.Dir(mountPath) + dataFile := filepath.Join(volPath, volDataFileName) + _, statErr := os.Stat(dataFile) + exists := statErr == nil + if exists != tc.expectDataFileExists { + t.Errorf("volume file [%s]: exists=%v, want=%v (statErr=%v)", dataFile, exists, tc.expectDataFileExists, statErr) + } + }) + } +} + func TestUnmounterTeardown(t *testing.T) { plug, tmpDir := newTestPlugin(t, nil) defer os.RemoveAll(tmpDir) From 77292b8b08af2fce8787bc4f622c17cab247fe01 Mon Sep 17 00:00:00 2001 From: Rahul Date: Wed, 22 Apr 2026 13:48:04 -0700 Subject: [PATCH 09/15] fix(endpoint): avoid panic on services with empty IPFamilies Accessing svc.Spec.IPFamilies[0] without a bounds check panics when a service reaches the controller with an empty IPFamilies field. This can happen via watch events: the apiserver's defaultOnRead decorator populates IPFamilies on GET/LIST but not on watch (cachingObject wrapping bypasses the type assertion). Restore the inference logic removed in #130101: fall back to ClusterIP for headful services and pod IP for headless services. Signed-off-by: Rahul --- .../endpoint/endpoints_controller.go | 18 ++++- .../endpoint/endpoints_controller_test.go | 80 +++++++++++++++++++ 2 files changed, 96 insertions(+), 2 deletions(-) diff --git a/pkg/controller/endpoint/endpoints_controller.go b/pkg/controller/endpoint/endpoints_controller.go index fe1c5f40832c7..6b52f860a2f7a 100644 --- a/pkg/controller/endpoint/endpoints_controller.go +++ b/pkg/controller/endpoint/endpoints_controller.go @@ -220,8 +220,22 @@ func (e *Controller) Run(ctx context.Context, workers int) { func podToEndpointAddressForService(svc *v1.Service, pod *v1.Pod) (*v1.EndpointAddress, error) { var endpointIP string - - wantIPv6 := svc.Spec.IPFamilies[0] == v1.IPv6Protocol + ipFamily := v1.IPv4Protocol + + // IPFamilies is expected to be populated by apiserver defaulting, but + // some services may reach this controller with an empty IPFamilies via + // watch events. Infer the family from ClusterIP or + // pod IP so we never panic on IPFamilies[0]. + if len(svc.Spec.IPFamilies) > 0 { + ipFamily = svc.Spec.IPFamilies[0] + } else if len(svc.Spec.ClusterIP) > 0 && svc.Spec.ClusterIP != v1.ClusterIPNone { + if utilnet.IsIPv6String(svc.Spec.ClusterIP) { + ipFamily = v1.IPv6Protocol + } + } else if utilnet.IsIPv6String(pod.Status.PodIP) { + ipFamily = v1.IPv6Protocol + } + wantIPv6 := ipFamily == v1.IPv6Protocol // Find an IP that matches the family. We parse and restringify the IP in case the // value on the Pod is in an irregular format. diff --git a/pkg/controller/endpoint/endpoints_controller_test.go b/pkg/controller/endpoint/endpoints_controller_test.go index bead9c2f6494a..c12fb83a9fa10 100644 --- a/pkg/controller/endpoint/endpoints_controller_test.go +++ b/pkg/controller/endpoint/endpoints_controller_test.go @@ -3224,3 +3224,83 @@ func TestSyncEndpointsAddDeletePorts(t *testing.T) { t.Fatalf("incorrect endpoints after deleting first port:\n%s", diff) } } + +func TestPodToEndpointAddressForServiceEmptyIPFamilies(t *testing.T) { + testCases := []struct { + name string + clusterIP string + podIPs []v1.PodIP + podIP string + wantErr bool + wantFamily v1.IPFamily + }{ + { + name: "headful IPv4, IPv4 pod", + clusterIP: "10.0.0.1", + podIPs: []v1.PodIP{{IP: "10.244.0.1"}}, + wantFamily: v1.IPv4Protocol, + }, + { + name: "headful IPv6, IPv6 pod", + clusterIP: "fd00::1", + podIPs: []v1.PodIP{{IP: "fd00::10"}}, + wantFamily: v1.IPv6Protocol, + }, + { + name: "headful IPv4, no matching pod IP", + clusterIP: "10.0.0.1", + podIPs: []v1.PodIP{{IP: "fd00::10"}}, + wantErr: true, + }, + { + name: "headless, IPv4 pod", + clusterIP: v1.ClusterIPNone, + podIPs: []v1.PodIP{{IP: "10.244.0.1"}}, + podIP: "10.244.0.1", + wantFamily: v1.IPv4Protocol, + }, + { + name: "headless, IPv6 pod", + clusterIP: v1.ClusterIPNone, + podIPs: []v1.PodIP{{IP: "fd00::10"}}, + podIP: "fd00::10", + wantFamily: v1.IPv6Protocol, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + svc := &v1.Service{ + ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"}, + Spec: v1.ServiceSpec{ + // Intentionally leave IPFamilies empty. + ClusterIP: tc.clusterIP, + }, + } + pod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "foo-pod", Namespace: "bar", UID: "uid-1"}, + Spec: v1.PodSpec{NodeName: "node-1"}, + Status: v1.PodStatus{PodIP: tc.podIP, PodIPs: tc.podIPs}, + } + + addr, err := podToEndpointAddressForService(svc, pod) + if tc.wantErr { + if err == nil { + t.Fatalf("expected error but got addr=%v", addr) + } + return + } + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if addr == nil { + t.Fatal("expected an address but got nil") + } + isV6 := utilnet.IsIPv6String(addr.IP) + wantV6 := tc.wantFamily == v1.IPv6Protocol + if isV6 != wantV6 { + t.Errorf("got IP %q (IPv6=%v), want family %v", addr.IP, isV6, tc.wantFamily) + } + }) + } +} From a74c5bff9abdc6f170d807ce6b90073e94c8d089 Mon Sep 17 00:00:00 2001 From: John Belamaric Date: Tue, 12 May 2026 21:51:11 +0000 Subject: [PATCH 10/15] Fix DRA scoring bug with mixed allocated and unallocated claims --- .../dynamicresources/dynamicresources.go | 26 +++++++++++++++---- .../dynamicresources/dynamicresources_test.go | 13 ++++++++++ 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/pkg/scheduler/framework/plugins/dynamicresources/dynamicresources.go b/pkg/scheduler/framework/plugins/dynamicresources/dynamicresources.go index 75551dc98055d..c737882fa0e91 100644 --- a/pkg/scheduler/framework/plugins/dynamicresources/dynamicresources.go +++ b/pkg/scheduler/framework/plugins/dynamicresources/dynamicresources.go @@ -830,7 +830,7 @@ func (pl *DynamicResources) PostFilter(ctx context.Context, cs fwk.CycleState, p } func (pl *DynamicResources) Score(ctx context.Context, cs fwk.CycleState, pod *v1.Pod, nodeInfo fwk.NodeInfo) (int64, *fwk.Status) { - if !pl.enabled { + if !pl.enabled || !pl.fts.EnableDRAPrioritizedList { return 0, nil } logger := klog.FromContext(ctx) @@ -859,13 +859,29 @@ func (pl *DynamicResources) Score(ctx context.Context, cs fwk.CycleState, pod *v func computeScore(iterator iter.Seq2[int, *resourceapi.ResourceClaim], allocations nodeAllocation) (int64, error) { var score int64 - for i, claim := range iterator { + unallocatedIndex := 0 + for _, claim := range iterator { // Collect the names for all allocated subrequests. allocatedSubRequests := sets.New[string]() - if i >= len(allocations.allocationResults) { - return 0, fmt.Errorf("number of allocations %d is smaller than number of claims", len(allocations.allocationResults)) + + var allocation *resourceapi.AllocationResult + // The allocation for a claim can be in two places: + // 1. For claims allocated in a previous cycle (e.g. PodGroup claims), the allocation + // is already in claim.Status.Allocation. + // 2. For claims allocated in this cycle (in Filter), the allocation is in + // allocations.allocationResults. + // Since we iterate over all claims, we must check both and maintain a separate index + // for claims that needed allocation in this cycle. + if claim.Status.Allocation != nil { + allocation = claim.Status.Allocation + } else { + if unallocatedIndex >= len(allocations.allocationResults) { + return 0, fmt.Errorf("number of allocations %d is smaller than number of claims needing allocation", len(allocations.allocationResults)) + } + allocation = &allocations.allocationResults[unallocatedIndex] + unallocatedIndex++ } - allocation := allocations.allocationResults[i] + for _, res := range allocation.Devices.Results { request := res.Request if resourceclaim.IsSubRequestRef(request) { diff --git a/pkg/scheduler/framework/plugins/dynamicresources/dynamicresources_test.go b/pkg/scheduler/framework/plugins/dynamicresources/dynamicresources_test.go index fc089251eab1c..03d63da6cfe3e 100644 --- a/pkg/scheduler/framework/plugins/dynamicresources/dynamicresources_test.go +++ b/pkg/scheduler/framework/plugins/dynamicresources/dynamicresources_test.go @@ -3310,6 +3310,19 @@ func Test_computesScore(t *testing.T) { allocations: nodeAllocation{}, expectErr: true, }, + "mix-of-allocated-and-unallocated-claims": { + claims: []*resourceapi.ResourceClaim{ + allocatedClaim, + pendingClaim2, + }, + allocations: nodeAllocation{ + allocationResults: []resourceapi.AllocationResult{ + *allocationResult2, + }, + }, + expectedScore: 0, + expectErr: false, + }, "single-request-only-subrequest-allocated": { claims: []*resourceapi.ResourceClaim{ st.MakeResourceClaim(). From d2212b840d95a355a557648ddeca2f219b99fa85 Mon Sep 17 00:00:00 2001 From: "bo.jiang" Date: Thu, 28 May 2026 12:43:17 +0800 Subject: [PATCH 11/15] kubeadm: fix dry-run CA copy paths in init certs Signed-off-by: bo.jiang --- cmd/kubeadm/app/cmd/phases/init/certs.go | 14 +++-- cmd/kubeadm/app/cmd/phases/init/certs_test.go | 55 +++++++++++++++++++ 2 files changed, 65 insertions(+), 4 deletions(-) diff --git a/cmd/kubeadm/app/cmd/phases/init/certs.go b/cmd/kubeadm/app/cmd/phases/init/certs.go index 5d1df7453f09d..84280e52d9e83 100644 --- a/cmd/kubeadm/app/cmd/phases/init/certs.go +++ b/cmd/kubeadm/app/cmd/phases/init/certs.go @@ -18,6 +18,7 @@ package phases import ( "fmt" + "os" "path/filepath" "strings" @@ -216,20 +217,25 @@ func runCAPhase(ca *certsphase.KubeadmCert) func(c workflow.RunData) error { if cert, err := pkiutil.TryLoadCertFromDisk(data.CertificateDir(), ca.BaseName); err == nil { certsphase.CheckCertificatePeriodValidity(ca.BaseName, cert) + srcCertPath, srcKeyPath := pkiutil.PathsForCertAndKey(data.CertificateDir(), ca.BaseName) + dryRunCertPath, dryRunKeyPath := pkiutil.PathsForCertAndKey(data.CertificateWriteDir(), ca.BaseName) // If CA Cert existed while dryrun, copy CA Cert to dryrun dir for later use if data.DryRun() { - err := kubeadmutil.CopyFile(filepath.Join(data.CertificateDir(), kubeadmconstants.CACertName), filepath.Join(data.CertificateWriteDir(), kubeadmconstants.CACertName)) + if err := os.MkdirAll(filepath.Dir(dryRunCertPath), os.FileMode(0700)); err != nil { + return errors.Wrapf(err, "failed to create directory %s", filepath.Dir(dryRunCertPath)) + } + err := kubeadmutil.CopyFile(srcCertPath, dryRunCertPath) if err != nil { - return errors.Wrapf(err, "could not copy %s to dry run directory %s", kubeadmconstants.CACertName, data.CertificateWriteDir()) + return errors.Wrapf(err, "could not copy %s to dry run directory %s", fmt.Sprintf("%s.crt", ca.BaseName), data.CertificateWriteDir()) } } if _, err := pkiutil.TryLoadKeyFromDisk(data.CertificateDir(), ca.BaseName); err == nil { // If CA Key existed while dryrun, copy CA Key to dryrun dir for later use if data.DryRun() { - err := kubeadmutil.CopyFile(filepath.Join(data.CertificateDir(), kubeadmconstants.CAKeyName), filepath.Join(data.CertificateWriteDir(), kubeadmconstants.CAKeyName)) + err := kubeadmutil.CopyFile(srcKeyPath, dryRunKeyPath) if err != nil { - return errors.Wrapf(err, "could not copy %s to dry run directory %s", kubeadmconstants.CAKeyName, data.CertificateWriteDir()) + return errors.Wrapf(err, "could not copy %s to dry run directory %s", fmt.Sprintf("%s.key", ca.BaseName), data.CertificateWriteDir()) } } fmt.Printf("[certs] Using existing %s certificate authority\n", ca.BaseName) diff --git a/cmd/kubeadm/app/cmd/phases/init/certs_test.go b/cmd/kubeadm/app/cmd/phases/init/certs_test.go index 7ee76b6459c1a..fb8d3a97dc7ed 100644 --- a/cmd/kubeadm/app/cmd/phases/init/certs_test.go +++ b/cmd/kubeadm/app/cmd/phases/init/certs_test.go @@ -17,13 +17,17 @@ limitations under the License. package phases import ( + "os" + "path/filepath" "testing" "github.com/spf13/cobra" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" "k8s.io/kubernetes/cmd/kubeadm/app/cmd/phases/workflow" + certsphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs" certstestutil "k8s.io/kubernetes/cmd/kubeadm/app/util/certs" + "k8s.io/kubernetes/cmd/kubeadm/app/util/pkiutil" pkiutiltesting "k8s.io/kubernetes/cmd/kubeadm/app/util/pkiutil/testing" testutil "k8s.io/kubernetes/cmd/kubeadm/test" ) @@ -33,10 +37,19 @@ type testCertsData struct { cfg *kubeadmapi.InitConfiguration } +type testDryRunCertsData struct { + testCertsData + certificateDir string + certificateWriteDir string +} + func (t *testCertsData) Cfg() *kubeadmapi.InitConfiguration { return t.cfg } func (t *testCertsData) ExternalCA() bool { return false } func (t *testCertsData) CertificateDir() string { return t.cfg.CertificatesDir } func (t *testCertsData) CertificateWriteDir() string { return t.cfg.CertificatesDir } +func (t *testDryRunCertsData) DryRun() bool { return true } +func (t *testDryRunCertsData) CertificateDir() string { return t.certificateDir } +func (t *testDryRunCertsData) CertificateWriteDir() string { return t.certificateWriteDir } func TestCreateSparseCerts(t *testing.T) { for _, test := range certstestutil.GetSparseCertTestCases(t) { @@ -63,3 +76,45 @@ func TestCreateSparseCerts(t *testing.T) { }) } } + +func TestRunCAPhaseCopiesExistingCAFilesToDryRunDir(t *testing.T) { + for _, ca := range []*certsphase.KubeadmCert{ + certsphase.KubeadmCertRootCA(), + certsphase.KubeadmCertFrontProxyCA(), + certsphase.KubeadmCertEtcdCA(), + } { + t.Run(ca.Name, func(t *testing.T) { + pkiutiltesting.Reset() + + sourceDir := t.TempDir() + writeDir := t.TempDir() + caCert, caKey := certstestutil.SetupCertificateAuthority(t) + certPath, _ := pkiutil.PathsForCertAndKey(sourceDir, ca.BaseName) + if err := os.MkdirAll(filepath.Dir(certPath), os.FileMode(0700)); err != nil { + t.Fatalf("failed to create source directory for %s: %v", ca.BaseName, err) + } + if err := pkiutil.WriteCertAndKey(sourceDir, ca.BaseName, caCert, caKey); err != nil { + t.Fatalf("failed to write source CA files for %s: %v", ca.BaseName, err) + } + + cfg := testutil.GetDefaultInternalConfig(t) + cfg.CertificatesDir = sourceDir + data := &testDryRunCertsData{ + testCertsData: testCertsData{cfg: cfg}, + certificateDir: sourceDir, + certificateWriteDir: writeDir, + } + + if err := runCAPhase(ca)(data); err != nil { + t.Fatalf("runCAPhase(%s) returned error: %v", ca.Name, err) + } + + if _, err := pkiutil.TryLoadCertFromDisk(writeDir, ca.BaseName); err != nil { + t.Fatalf("expected copied cert for %s in dry-run dir: %v", ca.BaseName, err) + } + if _, err := pkiutil.TryLoadKeyFromDisk(writeDir, ca.BaseName); err != nil { + t.Fatalf("expected copied key for %s in dry-run dir: %v", ca.BaseName, err) + } + }) + } +} From d44082b4bbd307d4a30b6ecdf7bde9b9e6ebf3bd Mon Sep 17 00:00:00 2001 From: Lalit Chauhan Date: Wed, 3 Jun 2026 19:33:53 +0000 Subject: [PATCH 12/15] Fix wrong marking of errors --- pkg/apis/core/validation/validation.go | 6 +++--- pkg/apis/resource/validation/validation.go | 4 ++-- pkg/apis/storage/validation/validation.go | 2 +- .../k8s.io/apimachinery/pkg/api/validation/objectmeta.go | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pkg/apis/core/validation/validation.go b/pkg/apis/core/validation/validation.go index 213f4d7b7d4cc..ee4fbf440e98c 100644 --- a/pkg/apis/core/validation/validation.go +++ b/pkg/apis/core/validation/validation.go @@ -176,7 +176,7 @@ func ValidateQualifiedName(value string, fldPath *field.Path) field.ErrorList { func ValidateDNS1123SubdomainWithUnderScore(value string, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} for _, msg := range validation.IsDNS1123SubdomainWithUnderscore(value) { - allErrs = append(allErrs, field.Invalid(fldPath, value, msg)).WithOrigin("format=k8s-dns-subdomain-with-underscore") + allErrs = append(allErrs, field.Invalid(fldPath, value, msg).WithOrigin("format=k8s-dns-subdomain-with-underscore")) } return allErrs } @@ -185,7 +185,7 @@ func ValidateDNS1123SubdomainWithUnderScore(value string, fldPath *field.Path) f func ValidateDNS1123Subdomain(value string, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} for _, msg := range validation.IsDNS1123Subdomain(value) { - allErrs = append(allErrs, field.Invalid(fldPath, value, msg)).WithOrigin("format=k8s-long-name") + allErrs = append(allErrs, field.Invalid(fldPath, value, msg).WithOrigin("format=k8s-long-name")) } return allErrs } @@ -4977,7 +4977,7 @@ func ValidateNodeSelectorRequirement(rq core.NodeSelectorRequirement, allowInval path := fldPath.Child("values") for valueIndex, value := range rq.Values { for _, msg := range validation.IsValidLabelValue(value) { - allErrs = append(allErrs, field.Invalid(path.Index(valueIndex), value, msg)).WithOrigin("format=k8s-label-value") + allErrs = append(allErrs, field.Invalid(path.Index(valueIndex), value, msg).WithOrigin("format=k8s-label-value")) } } } diff --git a/pkg/apis/resource/validation/validation.go b/pkg/apis/resource/validation/validation.go index ef2b4cfe82603..a3ec8f899ec29 100644 --- a/pkg/apis/resource/validation/validation.go +++ b/pkg/apis/resource/validation/validation.go @@ -773,7 +773,7 @@ func validateCounterSet(counterSet resource.CounterSet, fldPath *field.Path) fie if counterSet.Name == "" { allErrs = append(allErrs, field.Required(fldPath.Child("name"), "").MarkCoveredByDeclarative()) } else { - allErrs = append(allErrs, validateCounterName(counterSet.Name, fldPath.Child("name"))...).MarkCoveredByDeclarative() + allErrs = append(allErrs, validateCounterName(counterSet.Name, fldPath.Child("name")).MarkCoveredByDeclarative()...) } if len(counterSet.Counters) == 0 { allErrs = append(allErrs, field.Required(fldPath.Child("counters"), "")) @@ -874,7 +874,7 @@ func validateDeviceCounterConsumption(deviceCounterConsumption resource.DeviceCo if len(deviceCounterConsumption.CounterSet) == 0 { allErrs = append(allErrs, field.Required(fldPath.Child("counterSet"), "").MarkCoveredByDeclarative()) } else { - allErrs = append(allErrs, validateCounterName(deviceCounterConsumption.CounterSet, fldPath.Child("counterSet"))...).MarkCoveredByDeclarative() + allErrs = append(allErrs, validateCounterName(deviceCounterConsumption.CounterSet, fldPath.Child("counterSet")).MarkCoveredByDeclarative()...) } if len(deviceCounterConsumption.Counters) == 0 { allErrs = append(allErrs, field.Required(fldPath.Child("counters"), "")) diff --git a/pkg/apis/storage/validation/validation.go b/pkg/apis/storage/validation/validation.go index e103b613aae58..6b0d8c4ea54d7 100644 --- a/pkg/apis/storage/validation/validation.go +++ b/pkg/apis/storage/validation/validation.go @@ -88,7 +88,7 @@ func ValidateStorageClassUpdate(storageClass, oldStorageClass *storage.StorageCl func validateProvisioner(provisioner string, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} if len(provisioner) == 0 { - allErrs = append(allErrs, field.Required(fldPath, provisioner)).MarkCoveredByDeclarative() + allErrs = append(allErrs, field.Required(fldPath, provisioner).MarkCoveredByDeclarative()) } if len(provisioner) > 0 { allErrs = append(allErrs, apivalidation.ValidateQualifiedName(strings.ToLower(provisioner), fldPath)...) diff --git a/staging/src/k8s.io/apimachinery/pkg/api/validation/objectmeta.go b/staging/src/k8s.io/apimachinery/pkg/api/validation/objectmeta.go index 839fcbc2c17b0..9a4f378483a6a 100644 --- a/staging/src/k8s.io/apimachinery/pkg/api/validation/objectmeta.go +++ b/staging/src/k8s.io/apimachinery/pkg/api/validation/objectmeta.go @@ -46,7 +46,7 @@ func ValidateAnnotations(annotations map[string]string, fldPath *field.Path) fie for k := range annotations { // The rule is QualifiedName except that case doesn't matter, so convert to lowercase before checking. for _, msg := range validation.IsQualifiedName(strings.ToLower(k)) { - allErrs = append(allErrs, field.Invalid(fldPath, k, msg)).WithOrigin("format=k8s-label-key") + allErrs = append(allErrs, field.Invalid(fldPath, k, msg).WithOrigin("format=k8s-label-key")) } } if err := ValidateAnnotationsSize(annotations); err != nil { From 5ccf8f4ed515eeaf8a2a4a248385272e19437688 Mon Sep 17 00:00:00 2001 From: Carlos Panato Date: Tue, 9 Jun 2026 15:13:36 +0200 Subject: [PATCH 13/15] Bump images and versions to go 1.25.11 and distroless iptables Signed-off-by: Carlos Panato --- .go-version | 2 +- build/build-image/cross/VERSION | 2 +- build/common.sh | 4 +-- build/dependencies.yaml | 6 ++-- staging/publishing/rules.yaml | 64 ++++++++++++++++----------------- test/utils/image/manifest.go | 2 +- 6 files changed, 40 insertions(+), 40 deletions(-) diff --git a/.go-version b/.go-version index 0e0c284d88ab0..4fd1625308000 100644 --- a/.go-version +++ b/.go-version @@ -1 +1 @@ -1.25.9 +1.25.11 diff --git a/build/build-image/cross/VERSION b/build/build-image/cross/VERSION index 53dd3d6c1b53a..5338637c768f8 100644 --- a/build/build-image/cross/VERSION +++ b/build/build-image/cross/VERSION @@ -1 +1 @@ -v1.35.0-go1.25.9-bullseye.0 \ No newline at end of file +v1.35.0-go1.25.11-bullseye.0 \ No newline at end of file diff --git a/build/common.sh b/build/common.sh index e9c0268a37990..d8342d7319291 100755 --- a/build/common.sh +++ b/build/common.sh @@ -80,8 +80,8 @@ readonly REMOTE_OUTPUT_BINPATH="${REMOTE_OUTPUT_SUBPATH}/bin" readonly REMOTE_OUTPUT_GOPATH="${REMOTE_OUTPUT_SUBPATH}/go" # These are the default versions (image tags) for their respective base images. -readonly __default_distroless_iptables_version=v0.8.9 -readonly __default_go_runner_version=v2.4.0-go1.25.9-bookworm.0 +readonly __default_distroless_iptables_version=v0.8.11 +readonly __default_go_runner_version=v2.4.0-go1.25.11-bookworm.0 readonly __default_setcap_version=bookworm-v1.0.6 # The default image for all binaries which are dynamically linked. diff --git a/build/dependencies.yaml b/build/dependencies.yaml index dd422524fc298..14820fb091222 100644 --- a/build/dependencies.yaml +++ b/build/dependencies.yaml @@ -122,7 +122,7 @@ dependencies: # should also be updated, but go-runner is much harder to exploit and has # far less relevancy to go updates for Kubernetes more generally. - name: "registry.k8s.io/kube-cross: dependents" - version: v1.35.0-go1.25.9-bullseye.0 + version: v1.35.0-go1.25.11-bullseye.0 refPaths: - path: build/build-image/cross/VERSION @@ -170,7 +170,7 @@ dependencies: match: registry\.k8s\.io\/build-image\/debian-base:[a-zA-Z]+\-v((([0-9]+)\.([0-9]+)\.([0-9]+)(?:-([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?)(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?) - name: "registry.k8s.io/distroless-iptables: dependents" - version: v0.8.9 + version: v0.8.11 refPaths: - path: build/common.sh match: __default_distroless_iptables_version= @@ -178,7 +178,7 @@ dependencies: match: configs\[DistrolessIptables\] = Config{list\.BuildImageRegistry, "distroless-iptables", "v([0-9]+)\.([0-9]+)\.([0-9]+)"} - name: "registry.k8s.io/go-runner: dependents" - version: v2.4.0-go1.25.9-bookworm.0 + version: v2.4.0-go1.25.11-bookworm.0 refPaths: - path: build/common.sh match: __default_go_runner_version= diff --git a/staging/publishing/rules.yaml b/staging/publishing/rules.yaml index beb93c49fed75..681def6c81651 100644 --- a/staging/publishing/rules.yaml +++ b/staging/publishing/rules.yaml @@ -25,7 +25,7 @@ rules: dirs: - staging/src/k8s.io/apimachinery - name: release-1.35 - go: 1.25.9 + go: 1.25.11 source: branch: release-1.35 dirs: @@ -69,7 +69,7 @@ rules: dirs: - staging/src/k8s.io/api - name: release-1.35 - go: 1.25.9 + go: 1.25.11 dependencies: - repository: apimachinery branch: release-1.35 @@ -140,7 +140,7 @@ rules: go build -mod=mod ./... go test -mod=mod ./... - name: release-1.35 - go: 1.25.9 + go: 1.25.11 dependencies: - repository: apimachinery branch: release-1.35 @@ -193,7 +193,7 @@ rules: dirs: - staging/src/k8s.io/code-generator - name: release-1.35 - go: 1.25.9 + go: 1.25.11 dependencies: - repository: apimachinery branch: release-1.35 @@ -255,7 +255,7 @@ rules: dirs: - staging/src/k8s.io/component-base - name: release-1.35 - go: 1.25.9 + go: 1.25.11 dependencies: - repository: apimachinery branch: release-1.35 @@ -322,7 +322,7 @@ rules: dirs: - staging/src/k8s.io/component-helpers - name: release-1.35 - go: 1.25.9 + go: 1.25.11 dependencies: - repository: apimachinery branch: release-1.35 @@ -373,7 +373,7 @@ rules: dirs: - staging/src/k8s.io/kms - name: release-1.35 - go: 1.25.9 + go: 1.25.11 dependencies: - repository: apimachinery branch: release-1.35 @@ -452,7 +452,7 @@ rules: dirs: - staging/src/k8s.io/apiserver - name: release-1.35 - go: 1.25.9 + go: 1.25.11 dependencies: - repository: apimachinery branch: release-1.35 @@ -555,7 +555,7 @@ rules: dirs: - staging/src/k8s.io/kube-aggregator - name: release-1.35 - go: 1.25.9 + go: 1.25.11 dependencies: - repository: apimachinery branch: release-1.35 @@ -681,7 +681,7 @@ rules: # assumes GO111MODULE=on go build -mod=mod . - name: release-1.35 - go: 1.25.9 + go: 1.25.11 dependencies: - repository: apimachinery branch: release-1.35 @@ -788,7 +788,7 @@ rules: # assumes GO111MODULE=on go build -mod=mod . - name: release-1.35 - go: 1.25.9 + go: 1.25.11 dependencies: - repository: apimachinery branch: release-1.35 @@ -901,7 +901,7 @@ rules: required-packages: - k8s.io/code-generator - name: release-1.35 - go: 1.25.9 + go: 1.25.11 dependencies: - repository: apimachinery branch: release-1.35 @@ -985,7 +985,7 @@ rules: dirs: - staging/src/k8s.io/metrics - name: release-1.35 - go: 1.25.9 + go: 1.25.11 dependencies: - repository: apimachinery branch: release-1.35 @@ -1054,7 +1054,7 @@ rules: dirs: - staging/src/k8s.io/cli-runtime - name: release-1.35 - go: 1.25.9 + go: 1.25.11 dependencies: - repository: api branch: release-1.35 @@ -1129,7 +1129,7 @@ rules: dirs: - staging/src/k8s.io/sample-cli-plugin - name: release-1.35 - go: 1.25.9 + go: 1.25.11 dependencies: - repository: api branch: release-1.35 @@ -1205,7 +1205,7 @@ rules: dirs: - staging/src/k8s.io/kube-proxy - name: release-1.35 - go: 1.25.9 + go: 1.25.11 dependencies: - repository: apimachinery branch: release-1.35 @@ -1246,7 +1246,7 @@ rules: dirs: - staging/src/k8s.io/cri-api - name: release-1.35 - go: 1.25.9 + go: 1.25.11 source: branch: release-1.35 dirs: @@ -1322,7 +1322,7 @@ rules: dirs: - staging/src/k8s.io/cri-client - name: release-1.35 - go: 1.25.9 + go: 1.25.11 dependencies: - repository: api branch: release-1.35 @@ -1425,7 +1425,7 @@ rules: dirs: - staging/src/k8s.io/kubelet - name: release-1.35 - go: 1.25.9 + go: 1.25.11 dependencies: - repository: apimachinery branch: release-1.35 @@ -1524,7 +1524,7 @@ rules: dirs: - staging/src/k8s.io/controller-manager - name: release-1.35 - go: 1.25.9 + go: 1.25.11 dependencies: - repository: api branch: release-1.35 @@ -1637,7 +1637,7 @@ rules: dirs: - staging/src/k8s.io/cloud-provider - name: release-1.35 - go: 1.25.9 + go: 1.25.11 dependencies: - repository: api branch: release-1.35 @@ -1762,7 +1762,7 @@ rules: dirs: - staging/src/k8s.io/kube-controller-manager - name: release-1.35 - go: 1.25.9 + go: 1.25.11 dependencies: - repository: apimachinery branch: release-1.35 @@ -1833,7 +1833,7 @@ rules: dirs: - staging/src/k8s.io/cluster-bootstrap - name: release-1.35 - go: 1.25.9 + go: 1.25.11 dependencies: - repository: apimachinery branch: release-1.35 @@ -1890,7 +1890,7 @@ rules: dirs: - staging/src/k8s.io/csi-translation-lib - name: release-1.35 - go: 1.25.9 + go: 1.25.11 dependencies: - repository: api branch: release-1.35 @@ -1927,7 +1927,7 @@ rules: dirs: - staging/src/k8s.io/mount-utils - name: release-1.35 - go: 1.25.9 + go: 1.25.11 source: branch: release-1.35 dirs: @@ -2027,7 +2027,7 @@ rules: dirs: - staging/src/k8s.io/kubectl - name: release-1.35 - go: 1.25.9 + go: 1.25.11 dependencies: - repository: api branch: release-1.35 @@ -2128,7 +2128,7 @@ rules: dirs: - staging/src/k8s.io/pod-security-admission - name: release-1.35 - go: 1.25.9 + go: 1.25.11 dependencies: - repository: api branch: release-1.35 @@ -2249,7 +2249,7 @@ rules: dirs: - staging/src/k8s.io/dynamic-resource-allocation - name: release-1.35 - go: 1.25.9 + go: 1.25.11 dependencies: - repository: apimachinery branch: release-1.35 @@ -2355,7 +2355,7 @@ rules: dirs: - staging/src/k8s.io/kube-scheduler - name: release-1.35 - go: 1.25.9 + go: 1.25.11 dependencies: - repository: apimachinery branch: release-1.35 @@ -2442,7 +2442,7 @@ rules: dirs: - staging/src/k8s.io/endpointslice - name: release-1.35 - go: 1.25.9 + go: 1.25.11 dependencies: - repository: api branch: release-1.35 @@ -2482,11 +2482,11 @@ rules: dirs: - staging/src/k8s.io/externaljwt - name: release-1.35 - go: 1.25.9 + go: 1.25.11 source: branch: release-1.35 dirs: - staging/src/k8s.io/externaljwt recursive-delete-patterns: - '*/.gitattributes' -default-go-version: 1.25.9 +default-go-version: 1.25.11 diff --git a/test/utils/image/manifest.go b/test/utils/image/manifest.go index c0c6fd35a6b86..0844af178f43a 100644 --- a/test/utils/image/manifest.go +++ b/test/utils/image/manifest.go @@ -214,7 +214,7 @@ func initImageConfigs(list RegistryList) (map[ImageID]Config, map[ImageID]Config configs[APIServer] = Config{list.PromoterE2eRegistry, "sample-apiserver", "1.29.2"} configs[AppArmorLoader] = Config{list.PromoterE2eRegistry, "apparmor-loader", "1.4"} configs[BusyBox] = Config{list.PromoterE2eRegistry, "busybox", "1.37.0-1"} - configs[DistrolessIptables] = Config{list.BuildImageRegistry, "distroless-iptables", "v0.8.9"} + configs[DistrolessIptables] = Config{list.BuildImageRegistry, "distroless-iptables", "v0.8.11"} configs[Etcd] = Config{list.GcEtcdRegistry, "etcd", "3.6.6-0"} configs[InvalidRegistryImage] = Config{list.InvalidRegistry, "alpine", "3.1"} configs[IpcUtils] = Config{list.PromoterE2eRegistry, "ipc-utils", "1.3"} From fc0e7a6ca50f7ce368f9a5516e1716b473ed3a26 Mon Sep 17 00:00:00 2001 From: Kubernetes Release Robot Date: Thu, 11 Jun 2026 18:05:07 +0000 Subject: [PATCH 14/15] Release commit for Kubernetes v1.35.6 From 7694c3fe1f610941bae304104ff0b48a661c77d5 Mon Sep 17 00:00:00 2001 From: Chai Bot Date: Mon, 29 Jun 2026 18:24:13 +0000 Subject: [PATCH 15/15] UPSTREAM: : hack/update-vendor.sh, make update and update image --- openshift-hack/images/hyperkube/Dockerfile.rhel | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openshift-hack/images/hyperkube/Dockerfile.rhel b/openshift-hack/images/hyperkube/Dockerfile.rhel index 5c8de9a5feecd..a3817ddbb1ab2 100644 --- a/openshift-hack/images/hyperkube/Dockerfile.rhel +++ b/openshift-hack/images/hyperkube/Dockerfile.rhel @@ -15,4 +15,4 @@ COPY --from=builder /tmp/build/* /usr/bin/ LABEL io.k8s.display-name="OpenShift Kubernetes Server Commands" \ io.k8s.description="OpenShift is a platform for developing, building, and deploying containerized applications." \ io.openshift.tags="openshift,hyperkube" \ - io.openshift.build.versions="kubernetes=1.35.5" + io.openshift.build.versions="kubernetes=1.35.6"