Kubernetes published v1.37.1 on 23 September 2026 at 19:13 UTC. A DeviceTaintRule that omits spec.deviceSelector no longer matches every dynamic resource allocation device in the cluster. On v1.37.0 that nil selector applied NoSchedule and NoExecute taints cluster wide, so those devices were unschedulable.
The full release notes and downloads are on the GitHub release page. The page itself only points at the 1.37 changelog and the announce list. The notes below are the changelog since v1.37.0. v1.37.1 is a stable patch, not a release candidate, beta, or alpha. The dependency block lists nothing added, changed, or removed.
A nil device selector matches no devices ¶
#141998 fixes the resource slice tracker in staging/src/k8s.io/dynamic-resource-allocation/resourceslice/tracker/tracker.go. driverPoolDeviceIndexPatchKey called ptr.Deref on spec.deviceSelector. A nil pointer became an empty DeviceTaintSelector, and an empty selector matches every driver, pool, and device. The API text says the opposite. Without a selector, no devices match. deviceSelector: {} matches all devices.
The function now returns an empty key when DeviceSelector is nil. applyPatches logs DeviceTaintRule does not apply, no selector at V(7) and continues. The commit says this brings the NoSchedule tracker in line with ruleMatchesDevice, which already returned false for a nil selector. The published note still lists both NoSchedule and NoExecute. The code that changes here is the tracker.
A rule that omitted the selector by mistake stops tainting the whole cluster. A rule that omitted it on purpose, because nil behaved like match all, now matches nothing. taintAllDevicesRule and the benchmark set DeviceSelector to &resourceapi.DeviceTaintSelector{}. The case nil-selector-matches-none expects no patch events. There is no flag to restore the old behavior. The reviewer on #141998 called it an obvious bug in a GA feature, not a security fix and not a regression from an earlier minor. SIG Node owns the patch.
Device counts that would wrap no longer crash the scheduler ¶
#141922 changes Allocate and allocateOne in the stable, incubating, and experimental allocators under staging/src/k8s.io/dynamic-resource-allocation/structured/internal/. Counts that add up past the int range crashed kube-scheduler and skipped the firstAvailable fallback.
Allocate compares before it adds. When minDevicesPerRequest is greater than AllocationResultsMaxSize minus the devices already counted, it returns an error that names the claim, the request, the devices to add, the limit, and the devices already counted. allocateOne compares remaining devices with slots left under the limit. A request over the per claim limit is rejected. An oversized firstAvailable alternative falls through. The scheduler process keeps running. Claims that already fit are unchanged.
Windows proxy panic and kubeadm init ¶
#141681 stops winkernel kube-proxy from exiting in getAllLoadBalancers (pkg/proxy/winkernel/hns.go). The loop read PortMappings[0] on every HNS load balancer. An empty slice, including a load balancer that is only partly created, panicked and ended the process. It now checks len(lb.PortMappings) == 0, logs Skipping load balancer with no port mappings at klog.V(2) with hnsLbID, and continues. LoadBalancerFlagsIPv6 is read only after that guard. One partial object was enough to take kube-proxy down on that Windows node. The skip is a V(2) log, so the crash is no longer the signal.
#141627 changes WriteConfigToDisk in cmd/kubeadm/app/phases/kubelet/config.go. Mutate used to run on the cluster kubelet object, and that same object was marshaled. Mutate applies settings local to the kubeadm init host. On systemd-resolved the field is resolvConf. Init then stored the object in the shared kubelet-config ConfigMap, so other nodes inherited the init host path. The function now calls DeepCopy, runs Mutate on the copy, and marshals the copy to disk. The cluster object is unchanged. A ConfigMap written by a v1.37.0 init stays until a later write replaces it.
Go 1.26.8 and a conformance port ¶
#142179 is the only feature entry. Kubernetes is now built using Go 1.26.8. .go-version moves from 1.26.6 to 1.26.8. build/build-image/cross/VERSION and the registry.k8s.io/kube-cross pin in build/dependencies.yaml move from v1.37.0-go1.26.5-bullseye.0 to v1.37.0-go1.26.8-bullseye.0. In build/common.sh, __default_go_runner_version moves from v2.4.0-go1.26.5-bookworm.0 to v2.4.0-go1.26.8-bookworm.0 (registry.k8s.io/go-runner). __default_distroless_iptables_version moves from v0.9.6 to v0.9.7, and DistrolessIptables in test/utils/image/manifest.go follows, for registry.k8s.io/distroless-iptables.
The dependency block is empty, so go.mod does not move. The note names no CVE. Manifest images are registry.k8s.io/kube-apiserver:v1.37.1, registry.k8s.io/kube-controller-manager:v1.37.1, registry.k8s.io/kube-scheduler:v1.37.1, registry.k8s.io/kube-proxy:v1.37.1, registry.k8s.io/kubectl:v1.37.1, and registry.k8s.io/conformance:v1.37.1, each for amd64, arm64, ppc64le, and s390x.
#141780 changes a test only. The Projected PodCertificate conformance case added in 1.37 runs its mTLS server as non root with capabilities dropped. Binding port 443 fails where net.ipv4.ip_unprivileged_port_start is 1024. The listen port and the Service targetPort move to 8443. The Service port stays 443. kubelet and the API server do not change.
Upgrade notes ¶
A DeviceTaintRule with no spec.deviceSelector matches no devices on v1.37.1. Set deviceSelector: {} to match all devices.
Oversized ResourceClaim counts are rejected by name instead of crashing kube-scheduler. An oversized firstAvailable alternative falls through.
If init ran on v1.37.0 with systemd-resolved, read resolvConf in kubelet-config. This patch does not clear a stored path. Windows winkernel nodes are the only kube-proxy blast radius in #141681.
Where to get it ¶
- Release page: v1.37.1
- Repository: kubernetes/kubernetes
- Changelog:
CHANGELOG-1.37.md - Announce list:
kubernetes-announce - Tag:
v1.37.1