Kubernetes v1.38.0-beta.0: The First Real Look at 1.38 — Post-Quantum Tokens, OCI Seccomp, and DRA's GA Landslide
Sources
- Kubernetes v1.38.0-beta.0 release page
- CHANGELOG-1.38.md (v1.38.0-beta.0 through alpha.1)
- sig-release: v1.38 Release Information (schedule)
- Feature gate registry at the v1.38.0-beta.0 tag
- KEP-6061: OCI Artifact-Based Security Profile Distribution
- KEP-5007: DRA: Device Binding Conditions
- KEP-4671: Gang Scheduling Support in Kubernetes
- KEP-6089: WAS Controller Integration APIs
- go-jose advisory GHSA-mgcc-cww5-fp2q (CVE-2026-116587)
- NIST FIPS 204 — the ML-DSA standard
- v1.38 Release Team roster
- Kubernetes release team calendar
Kubernetes v1.38.0-beta.0 was tagged and published on November 6, 2026 at 04:34 UTC — the week-10 milestone the v1.38 release schedule had planned for Wednesday, November 4 (the same day the feature-blog freeze was slated to begin), slipping two days past its calendar slot. Per the published calendar, code freeze lands November 16–17, the Deprecations and Removals blog publishes November 23, and v1.38.0 GA is planned for Wednesday, December 16, 2026 — comfortably after KubeCon NA (November 9–12), which is where most of the 1.38 conversation will actually happen.
Why should a platform engineer read a beta.0 changelog instead of waiting for the December marketing post? Because a beta.0 cut is the one Kubernetes artifact with zero marketing in it: every line is a merged PR with a number and a human attached. It is the first honest look at what 1.38 actually is — in this case, a release whose three load-bearing stories are post-quantum service-account tokens, OCI-registry-distributed seccomp profiles, and a Dynamic Resource Allocation (DRA) graduation landslide, followed by the largest feature-gate removal wave we can count in a single cycle. We verified everything below against the official CHANGELOG-1.38 (four sections: beta.0, alpha.3, alpha.2, alpha.1), the feature-gate registry at the v1.38.0-beta.0 tag itself, and the underlying PR bodies. We did not stand up a 1.38 beta cluster for this piece — treat it as source-forensics, not hands-on benchmarking.
The 1.38 cycle at a glance
2026
Aug 31 Sep 23 Oct 21 Nov 4-6 Nov 16-17 Dec 16
| | | | | |
v v v v v v
cycle alpha.1 alpha.3 BETA.0 Code freeze v1.38.0
begins | | feature blog + test freeze GA
| | freeze begins |
Enhancements Docs | Burndown from
freeze Sep 30 placeholders YOU ARE Nov 23; deprecations
HERE blog Nov 23
Three release facts frame everything else. First, 1.38 is built on Go 1.27 — the cycle cycled through Go 1.27.1, 1.27.2, and 1.27.3 (#141663, #143116, #143339), which is not trivia: Go 1.27's crypto/mldsa package is the foundation of this release's post-quantum story, and Go 1.27's new HTTP header-count default is the reason the API server grew an explicit 16,384-header-value limit (see the breaking-edges section). Second, the bundled control plane moves to etcd 3.7 (default 3.7.1, SDK bumped to 3.7.3 — etcd project) and CoreDNS 1.15.0 (#143468). Third, this is the first cycle using the new [FutureConformance] test labeling — the first e2e test so marked (Services supporting named targetPorts that resolve to different ports on different endpoints) becomes a conformance requirement in 1.41 (#142561), which is a polite warning to out-of-tree network implementers: the compliance bar now moves on a timer.
The headline: post-quantum authentication (ML-DSA) lands across the stack
The most coherent theme in the 1.38 changelog is the arrival of ML-DSA — Module-Lattice Digital Signature Algorithm, the NIST FIPS 204 post-quantum signature standard — as a first-class citizen of Kubernetes' own PKI. This is not one feature. It is five surfaces, merged by different contributors, riding the crypto/mldsa package that Go 1.27 ships:
| Surface | Maturity in 1.38 | Gate | PR |
|---|---|---|---|
| ServiceAccount tokens signed by an ML-DSA key | Beta — enabled once the API server's compatibility version is 1.38 | ServiceAccountTokenMLDSA | #142397 (@everettraven) |
| Pod certificates requested with ML-DSA (PodCertificateRequest + projected volume) | Beta | — | #142108 (@everettraven) |
| ML-DSA-signed PKCS#10 requests in CertificateSigningRequest | Beta — same compat-version gating | CertificateSigningRequestMLDSA | #142196 (@everettraven) |
| KubeletConfiguration picks ECDSA / RSA / ML-DSA for client & server cert requests | GA (config field), default stays ECDSA-P256 | — | #142046 (@rphillips) |
kubeadm EncryptionAlgorithm accepts ML-DSA-44/65/87 | Config surface (v1beta4) | — | #142037 (@neolit123) |
The gate-registry entry for ServiceAccountTokenMLDSA is the interesting one. At the beta.0 tag it reads as two 1.38 entries: Beta with default false, and Beta with default true but a MinCompatibilityVersion: 1.38 constraint. That is the compatibility-version pattern: freshly created 1.38 clusters get ML-DSA token signing available by default, while API servers still in upgrade skew (compatibility version below 1.38) keep it off. Client-go grew the matching key support from Go 1.27's crypto/mldsa (#142038), and even hack/local-up-cluster.sh can now generate quantum-safe keys and certs for testing (#142401). One quiet sibling change: kubelet's self-signed serving certificate used to always be RSA-2048 regardless of configuration; it now honors serverCertificateKeyAlgorithm and defaults to ECDSA-P256 as documented (#142434) — if you have tooling that pattern-matched on RSA key types for kubelet serving certs, check it before 1.38 reaches your nodes.
Now the skeptical part, because the PR authors themselves flagged it. The kubeadm ML-DSA merge (#142037) carries this admission in its own release note: "while kubeadm is adding this support, support in other components like kube-apiserver and etcd might be added a bit later and until then a kubeadm default setup with ML-DSA might fail." Translation: the config surface arrived before the control plane can necessarily honor it. There is also no dedicated enhancements KEP visible on any of the ML-DSA PRs we checked — search as we might, this landed as PRs, not as a numbered design document, which is unusual for something this foundational. Who should care: regulated shops and anyone with a PQC mandate, who now have a real target to test against from December. Who should skip: everyone running production through a normal OIDC/webhook auth stack — your token verifiers, HSMs, and vendor integrations have no obligation to understand ML-DSA yet, and the defaults above do not change existing RSA/ECDSA behavior. This is a test-in-1.38, adopt-later feature set.
OCI-distributed seccomp profiles: the right answer, half built (KEP-6061)
The second headline is KEP-6061, "OCI Artifact-Based Security Profile Distribution", merged by Sascha Grunert (#142511, API side; #142509, CRI side). The pain it addresses is real and old: today a Localhost seccomp profile is an unversioned file you copy onto every node, outside your GitOps path, with no integrity story — and a pod that references a missing profile simply runs less protected than you thought. Putting profiles in an OCI registry gives you the same distribution, digest-pinning, and pull-credential machinery as your container images. The pod-side API at beta.0, verified against the tagged source:
apiVersion: v1
kind: Pod
metadata:
name: nginx-oci-seccomp
namespace: prod
labels:
app: nginx
spec:
automountServiceAccountToken: false
securityContext:
seccompProfile:
type: RuntimeDefault
containers:
- name: nginx
image: docker.io/library/nginx:1.29
ports:
- containerPort: 80
name: http
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
seccompProfile:
type: OCI
oci:
# Required. Digest-pinned canonical form ONLY:
# registry/repository@sha256:<digest>
# - tags are rejected; sha256 | sha384 | sha512 only
# - registry must be spelled out (docker.io/library/..., not "nginx")
# - max 1024 characters
ref: registry.example.com/seccomp/nginx@sha256:3f1e9a04b2c7d8a5e6f0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2
# Optional base profile the runtime merges with the OCI profile.
# Effective profile permits an operation only if BOTH permit it.
baseProfile:
type: RuntimeDefault # RuntimeDefault | Localhost
The validation rules are strict in the way you want a security primitive to be strict: the reference must be a digest-pinned canonical form — registry/repository@<algorithm>:<digest> with the registry spelled out, tags rejected, digest algorithm limited to SHA-256/384/512, max 1024 characters. Per the KEP design, pull credentials are resolved by the kubelet exactly like image pulls — node credential providers, service-account and pod imagePullSecrets — and handed to the runtime through the CRI, so private profile registries need no new machinery. Two sharp edges from the PR body itself: privileged containers cannot use an OCI profile at all, whether set on the container or inherited from the pod, and PodSecurity keeps rejecting OCI in alpha — so pods in restricted-PSA-labeled namespaces cannot opt in yet. On the CRI side, #142509 adds the PullSecurityProfile, ListSecurityProfiles, and RemoveSecurityProfile RPCs, the seccomp_profile_oci runtime feature, and a well-known SecurityProfileInvalid error.
Pod spec (alpha gate: SecurityProfileOCI on kube-apiserver AND kubelet)
+-----------------------------------------------------------+
| securityContext.seccompProfile.type: OCI |
| oci.ref: registry/repo@sha256:<digest> |
| oci.baseProfile.type: RuntimeDefault |
+----------------------------+------------------------------+
| API-server validation
| (canonical ref, no tags, no privileged)
v
kubelet -----------------> CRI PullSecurityProfile(ref)
(requires runtime feature
seccomp_profile_oci)
| pull secrets assembled like images:
| node creds, SA imagePullSecrets,
| pod spec imagePullSecrets
v
OCI registry artifact (seccomp JSON)
|
v
runtime merges: OCI profile AND base profile
(intersection — permit only if both permit)
Here is the part marketing will not tell you, and the reason this section is titled "half built": as of beta.0, neither containerd nor CRI-O carries the seccomp_profile_oci runtime feature on its main branch — we ran a code search against both repos and got zero hits, and CRI-O's implementation is real but still an open PR (#10382, in review). The in-tree picture is thinner than the API makes it look, too: the KEP's own tracker lists the kubelet-side pull, apply, and garbage-collect logic as unmerged (#142515) — what landed by beta.0 is the API surface, the CRI wire format, and the fail-closed prerequisite, not an end-to-end path. Enabling SecurityProfileOCI today gets you API acceptance, not a working end-to-end deployment. The KEP is refreshingly honest about an alpha credential gap as well: profile pulls bypass the image pull manager, so a private profile pulled by a credentialed pod can be observed and applied by a credential-less pod on the same node that knows the digest — the KEP-2535 verification story is deferred to beta. Add the skew note from the PR body — kubelets before 1.38 fell through to Unconfined on unknown profile types — and you can see why 1.38 also ships the paired fail-closed fix: a pod with an unknown or empty seccompProfile.type now fails with CreateContainerConfigError instead of silently running unprotected (#141958). If you want the current, working version of this discipline on hardened clusters, our Kubernetes security hardening baseline covers the Localhost + GitOps pattern that exists today. Verdict on KEP-6061: architecturally correct, digest-pinned, privilege-aware — file it as "design GA, runtime alpha," and re-check containerd/CRI-O release notes before you write a single pod with type: OCI.
DRA's GA landslide — and the 1.37 double-allocation regression it sits on
If you operate GPU or other accelerator fleets, this is the section that matters. The Dynamic Resource Allocation API, in the works since 1.26, took its graduation landslide this cycle — verified from the gate registry at the beta.0 tag:
| Feature gate | 1.37 state | 1.38 state | Removal |
|---|---|---|---|
DynamicResourceAllocation | GA locked (since 1.35) | Gate deleted — DRA unconditional (#141946) | already gone |
DRADeviceBindingConditions (KEP-5007) | Beta, on | GA, locked true (#142478) | 1.41 |
DRAPartitionableDevices | Beta, on | GA, locked true (#143478) | 1.41 |
DRAConsumableCapacity + DRAFractionalCapacityRange | Beta, on | GA (#143309); consumable capacity locks in 1.39 | 1.42 |
DRADeviceTaints / DRADeviceTaintRules | on | GA, locked true (#140256) | 1.41 |
DRANodeAllocatableResources | — | Alpha (per-container overhead now counts at peak, not full duration — #141375) | — |
Locked GA has a concrete operational meaning: the gate is true and cannot be turned off, so from 1.38 the device-binding-conditions flow (scheduler reserves devices, driver satisfies or denies the binding, conditions time out and free the reservation) is the DRA behavior, not an option. KEP-5007's graduation PR is explicit that tests verifying the old behavior now use historical versions to emulate pre-GA semantics. If you maintain a DRA driver, 1.38 is the release where your ResourceClaim status handling must speak binding conditions fluently — that is what GA-locked means here.
The reason to wait for 1.38 rather than run 1.37 for GPU workloads is a regression the beta.0 notes bury mid-list. #143450 (@troychiu, labeled /kind regression) fixes a v1.37 bug where, if PreBind stored a claim's allocation and then failed — a binding-condition timeout, or a sibling claim failing — Unreserve restored the claim in the assume cache to the informer copy. Result: the scheduler forgot the device was allocated and could hand the same physical device to a second pod. Silent double-allocation of an exclusive accelerator is about the worst failure class a GPU scheduler can have, and it shipped in the 1.37 stable line. If you run 1.37 with DRA drivers today, this fix alone is your 1.38 case study — and it is why our 1.37 pod-resource-managers coverage told you DRA was promising but not yet boring.
Two smaller DRA items deserve operator attention. First, the alpha kubelet_device_plugin_devices metric (#143223) is a gauge of devices by resource_name and health_state — finally distinguishing "device went unhealthy" from "the plugin deregistered," which previously both looked like a silent drop in allocatable. If you have GPU fleet dashboards, this is the metric to backfill alert logic from. Second, a landmine from the alpha.1 notes: a DeviceTaintRule with no spec.deviceSelector used to match every device in the cluster, making all DRA devices unschedulable for NoSchedule/NoExecute taints (#141648); the empty selector now correctly matches nothing, and deviceSelector: {} is the explicit "select all." Same class of fix: a device with a live shared allocation could also be re-allocated exclusively after a driver changed its ResourceSlice (#140798), and the scheduler panicked when a ResourceSlice changed device counts mid-event (#143378). None of these are hypothetical — they are the exact failure shapes device-management code review exists to catch.
Gang scheduling: a GA announcement and a beta reality
The gang-scheduling story in 1.38 is genuinely interesting to read closely, because the documents disagree in public. KEP-4671 ("Gang Scheduling Support in Kubernetes") was updated for GA in v1.38 — the graduation update, enhancements PR #6349, merged on September 22, 2026, before Enhancements Freeze. Yet the gate registry at the beta.0 tag tells a more cautious story: GenericWorkload sits at Beta and off by default (since 1.37), and CompositePodGroup moved to Beta in 1.38 also off by default. Meanwhile the API surface is visibly still moving: CompositePodGroup was promoted to scheduling.k8s.io/v1beta1 (#143088), the WAS building blocks (KEP-6089, "WAS: Controller Integration APIs") were promoted from v1alpha3 types to scheduling.k8s.io/v1 with the old types kept as aliases (#142962), and the Job-controller integration (KEP-5547) landed its name-based Workload/PodGroup discovery: a Job with spec.scheduling now gets its own Workload and PodGroup regardless of owner — including Jobs spawned by CronJobs — with the scheduling.k8s.io/workload-name and scheduling.k8s.io/group-template-name annotations selecting templates in an existing Workload (#143534).
How to read that tension: pieces of gang scheduling are graduating, the umbrella KEP says GA, but at beta.0 the gates that turn the machinery on are still Beta and dark. That is either a code-freeze-window flip still coming, or (more likely, given the volume of validation hardening this cycle — hierarchy depth limits, parent/child scheduling-policy compatibility, uniform priorityClassName across the workload tree, disruption-mode consistency in #143278 and #142719) a feature that is one cycle from GA, with 1.38 as the "everything is typed and validated" release. If you run Kueue or another queueing layer for AI batch, the in-tree Workload/PodGroup APIs are the convergence point to track — the December feature blog is the document that will settle GA-vs-Beta, and we are deliberately not calling it before then.
The other scheduler headline is Topology-Aware Scheduling (TAS), promoted to Beta (#140295) — but note the registry detail: TopologyAwareWorkloadScheduling is Beta and off by default, and it depends on GenericWorkload being on. With TAS enabled, the scheduler now evaluates an adaptive subset of placements by default, with a new percentageOfPlacementsToScore knob to restore the old evaluate-everything behavior (#139859) — a scaling lever aimed at the combinatorial explosion TAS hits on large GPU clusters. The scheduler also grew a queueing hint that immediately re-queues a nominated pod when its nominated node's Allocatable shrinks (#142595), so preemption can proceed on another node without waiting for victims to die first — the kind of latency fix you only appreciate at queue depths of thousands.
The feature-gate graveyard: 19 removals to scrub before GA
1.38 is carrying the largest feature-gate removal wave we have counted in a single Kubernetes cycle — nineteen gates deleted across the four changelog sections. Removal is the healthy end of the feature-gate lifecycle (locked-GA gates that outlive their purpose are config debt), but two of these will hard-fail components that still mention them, so the scrub matters:
| Removed gate | Was | What happens if it's still in your config |
|---|---|---|
SupplementalGroupsPolicy | GA locked (#142093) | Components reject the flag — remove from every --feature-gates before upgrading |
SeparateTaintEvictionController | GA locked (#141789) | Remove the gate; disable taint eviction via --controllers=-taint-eviction-controller instead |
RecoverVolumeExpansionFailure | GA locked since 1.34 | Cosmetic — ignored; scrub anyway (#139079) |
NodeSwap | GA locked since 1.34 | Cosmetic (#143587) |
WinDSR / WinOverlay | GA | Gone; Windows DSR now only via kube-proxy winkernel.enableDSR / --enable-dsr (#142125) |
PodSchedulingReadiness | permanently enabled | Removed from kube-scheduler (#141741) |
CustomResourceFieldSelectors, CRDValidationRatcheting | GA locked | Removed (#141207) |
DynamicResourceAllocation | GA locked since 1.35 | Removed — DRA is unconditional (#141946) |
ServiceAccountTokenPodNodeInfo | GA locked since 1.32 | Removed (#135338) |
KubeletTracing | GA locked since 1.34 | Removed (#141714) |
ExecProbeTimeout, SystemdWatchdog, AllowOverwriteTerminationGracePeriodSeconds | GA locked since 1.35 | Removed (#143335–#143337) |
WindowsHostNetwork | deprecated (KEP-3503 withdrawn) | Removed (#142018) |
MatchLabelKeysInPodAffinity | GA | Removed (#141744) |
PodGroupPreemptionPolicy | Alpha | Removed; preemptionPolicy gated only by GenericWorkload/CompositePodGroup (#143266) |
AggregatedDiscoveryRemoveBetaType + apidiscovery.k8s.io/v2beta1 types | beta API | API and Go package removed (#141208) |
Two API removals ride alongside: the deprecated and unused metrics.k8s.io/v1alpha1 API is gone — migrate any Metrics Server clients to v1 or v1beta1 (#140862) — and Kubemark was deleted from kubernetes/kubernetes entirely (#142700). The KEP-4006 WebSocket streaming gates (TranslateStreamCloseWebsocketRequests, PortForwardWebsockets, AuthorizePodWebsocketUpgradeCreatePermission, ExtendWebSocketsToKubelet) went GA and locked (#142860), and the KUBECTL_REMOTE_COMMAND_WEBSOCKETS/KUBECTL_PORT_FORWARD_WEBSOCKETS environment variables are deleted — kubectl now always uses WebSockets with SPDY fallback for exec, attach, cp, and port-forward (#142861). If you run a firewall, proxy, or service mesh that special-cases SPDY for kubectl traffic, that path just became the fallback instead of the default.
# Grep the entire config surface: Helm values, kustomize patches, Terraform,
# kubeadm configs, node systemd drop-ins, CI scripts.
GATES='RecoverVolumeExpansionFailure|SupplementalGroupsPolicy|NodeSwap|WinDSR|WinOverlay|PodSchedulingReadiness|SeparateTaintEvictionController|CustomResourceFieldSelectors|CRDValidationRatcheting|DynamicResourceAllocation|ServiceAccountTokenPodNodeInfo|KubeletTracing|ExecProbeTimeout|SystemdWatchdog|AllowOverwriteTerminationGracePeriodSeconds|WindowsHostNetwork|MatchLabelKeysInPodAffinity|PodGroupPreemptionPolicy|AggregatedDiscoveryRemoveBetaType'
grep -RInE --include='*.yaml' --include='*.yml' --include='*.json' \
--include='*.tf' --include='*.sh' --include='*.env' \
-e "$GATES" . | grep -v '/vendor/'
# Anything this prints is a 1.38 upgrade blocker or a lint error waiting to happen.
# SupplementalGroupsPolicy and SeparateTaintEvictionController are the two that
# hard-fail components at startup; the rest are hygiene.
The API-server memory diet: sharded watches, dropped managedFields, lazy OpenAPI
The quiet theme with the biggest operational payoff, if you run large controller fleets or big tenants, is the API machinery memory campaign. Five of its pieces shipped on by default in 1.38 — verified against the registry at the beta.0 tag:
| Gate / mechanism | State in 1.38 | What it actually does |
|---|---|---|
ShardedListAndWatch | Beta, on by default (alpha since 1.36) | Informers/reflector sharding for huge object counts; new sharding.NewShardRangeSelector + cache.NewShardedListWatch helpers (#142702), now also for CRDs and dynamic/Unstructured clients (#142975) |
WatchFromStorageWithoutPrevKV | Beta, on by default (new) | etcd watches without prev-kv payloads — less etcd CPU/memory on watch-heavy API servers (#131862) |
OpenAPIV2LazyBuild | Beta, on by default (new) | /openapi/v2 spec built on first request instead of at startup (#140939) |
AllocatorPoolBufferCap | Beta, on by default (new) | Bounds protobuf encode buffers in runtime.AllocatorPool at 256 KiB (#141299) |
ClientsAllowConnectionPool (client-go) | Beta, on by default (new) | Connection pooling across ≥ MinConnectionPoolSize connections, least-loaded first; kubeconfig clusters gain min-connection-pool-size (#142938, #143734); disable with KUBE_FEATURE_ClientsAllowConnectionPool=false |
Read the fine print on OpenAPIV2LazyBuild — it is also a behavior change in disguise: with the gate on, a spec that fails to build no longer fails API-server startup; it is logged and omitted from the aggregated document. If your startup tooling treats "apiserver came up" as "the OpenAPI contract is intact," it no longer is. Meanwhile ManagedFieldsOptOut (alpha, off) gives you three surfaces to strip the notoriously bloated metadata.managedFields: an Accept: application/json;drop=metadata.managedFields header server-side (#139561), rest.Config.DropManagedFields client-side with kube-controller-manager's shared informers opted in (#142695), and kube-scheduler's informers under the ManagedFieldsOptOutClient gate (#143729). Pair that with the UnstructuredList decode rewrite — roughly half the memory, a third faster JSON list decoding for dynamic clients and kubectl get -o json (#142575) — and the shape of the campaign is clear: the API server is being put on a diet so the next ten-thousand-controller cluster fits in RAM.
One bug fix in this area deserves a standing ovation: #143077 (from @nami-ant) fixes a kube-apiserver goroutine and memory leak where a watch-list (sendInitialEvents=true) watcher stayed blocked forever when the client disconnected while the watch cache closed the watcher as too slow. Slow-client leak bugs are how "the API server looks fine until it doesn't" incidents start. Adjacent consistency work: ConsistentGetFromCache (alpha, #143696), reliable Exact-LIST serving from the watch cache past the compaction window (#143325), and the etcd request metrics now split delete-get from update-get with reason labels (#142484) — the last three all from the same author, Marek Siarkowicz, who has been quietly systematizing watch-cache correctness for multiple releases.
The breaking edges nobody's blog post will mention
Beta.0 carries a cluster of changes that will never make a headline but will absolutely page someone in January. The ones we would flag in a platform review:
1. The cel-go module rename — ACTION REQUIRED for Go consumers. Kubernetes moved from github.com/google/cel-go to cel.dev/cel-go v0.33 (#143439; the upstream project now lives at cel-expr/cel-go). If your controllers or admission tooling pass cel-go types (cel.EnvOption, *cel.Env, ref.Val, …) to or from k8s.io/apiserver, your imports break until you switch. Two visible behavior changes ride along: the estimated cost of comparing CEL types (type(a) == type(b)) is now accurate instead of effectively unbounded — some previously-rejected validation rules will now fit the budget — and compilation errors no longer carry the (in container '') suffix, which matters if you string-match error text. CEL field-selection got a significant performance pass (#143639).
2. The apiextensions module split. CRD API types and generated clients moved to a new k8s.io/apiextensions-api module that no longer depends on k8s.io/apiserver; the old packages remain as deprecated aliases and go fix -inline ./... migrates callers (#142746). The protobuf package of the CRD .proto files changed to k8s.io.apiextensions_api.pkg.apis.apiextensions.v1 — wire format unchanged, but any codegen or reflection keyed on the old package name needs updating.
3. Scheduler and API-machinery interface breaks. Out-of-tree scheduler plugins using NominatedPodsForNode must now pass a klog.Logger as the first argument (#142118); PreemptionVictim gained a Type() method and NumPodGroupDisruptions() was replaced by NumDisruptionsByVictimType() (#142955); preemption-capable extenders must handle candidates with empty victim lists (#135486); and direct access to the Raw byte slice in ObjectMeta.ManagedFields[*].FieldsV1 is gone in favor of accessor methods, with -tags=fieldsv1byte as a temporary escape hatch (#143011). The manifest-based mutating-webhook ordering field staticOrder must be removed before downgrading to 1.37 or earlier, which rejects it at startup (#143160).
4. A metric label rename, because dashboards never die. The alpha scheduler metrics scheduler_queued_entities and scheduler_queue_incoming_entities_total renamed their type label to entity — flagged as ACTION REQUIRED in the alpha.2 notes (#143180). If your SLO dashboards or alert rules filter on type="..." against those series, they go blind on upgrade, silently. Same category, less dramatically: endpoint_slice_controller_changes is deprecated in favor of a _total suffix, and the taint-eviction duration metric was discovered to have been recording nanoseconds-scaled-by-1e9 instead of seconds — meaning latencies under ~9.2s all landed in the +Inf bucket (#141742). If your eviction-latency percentiles looked suspiciously flat historically, now you know why.
5. The go-jose CVE and the OIDC key-size landmine. The cycle bumped github.com/go-jose/go-jose/v4 from v4.1.5 to v4.1.9, with #143657 citing CVE-2026-116587 (GHSA-mgcc-cww5-fp2q, high): a crafted JWK with RSA parameters makes go-jose spend excessive CPU in rsa.PrivateKey.Validate — a denial-of-service vector for anything that parses untrusted JWKs, which is precisely what an API server does with an OIDC issuer's published keys. The fix that will actually break someone: JWK parsing now rejects RSA keys larger than 4096 bits. If your external OIDC provider (or an internal CA) publishes an oversized RSA key, authentication for those users stops working on upgrade — no error in Kubernetes, just 401s from the verifier. Check your issuer's jwks_uri key sizes before you upgrade; this is the single most "unexpected 3 a.m. page" candidate in the whole release.
6. Go 1.27 and the 500-group impersonation cliff. Go 1.27 caps HTTP request header values at 500 by default; because Kubernetes serializes group membership into repeated header values, an impersonated or proxied user with roughly 500 or more groups gets HTTP 431. The fix: API servers and anything built on k8s.io/apiserver now explicitly accept up to 16,384 header values (#142607). If you build custom Kubernetes-adjacent HTTP servers on Go 1.27 without that override, this cliff is yours too. The grpc dependency rode two CVE bumps this cycle (v1.82.2 for CVE-2026-84445, v1.84.0 for CVE-2026-84304).
The quiet int64 hardening campaign
Read the alpha.1 section's bug list end to end and one contributor name dominates: @thc1006, landing fix after fix in resource.Quantity and its consumers. The pattern across them: enormous or pathological quantity values — values past 2^63, negative capacities, decimal exponents outside int32 — used to silently wrap, mis-compare, or mis-round, and the API server would accept or reject things based on the wrong arithmetic. The batch includes: Value()/MilliValue()/ScaledValue() now saturate at math.MaxInt64 instead of wrapping (#141305); a kubelet CPU reservation no longer wraps negative; PVC status.capacity no longer accepts a negative quantity (#141169); scheduler per-node resource totals no longer wrap negative and make a full node look empty (#141328); the HPA no longer scales down on amd64 when fed an extreme metric value — instead of up — and extreme Percent policies no longer block scale-up entirely (#141397); and the storage-capacity path in the scheduler compares sizes exactly instead of via overflowing int64 (#142088).
The one with real migration teeth is the LimitRanger exactness fix (#141348): LimitRange comparisons are now exact, and the release notes spell out the operational consequence — requests and limits within one milli-unit of a bound are now rejected where the previous projection rounded them onto the bound, this applies to every PVC create and update, and a stored PVC holding such a value must have its request corrected before it can be updated again; where the stored request sits above a maximum, correcting it would mean shrinking the request, which PVC validation refuses — so those claims need the LimitRange widened instead. That is a genuinely annoying but honest upgrade task: if you have LimitRanges with tight bounds and auto-generated PVC sizes, grep your storage classes and templates for requests within one milli-unit of your bounds before 1.38.
StatefulSet season
If you run databases on Kubernetes, 1.38 quietly had a StatefulSet-themed maintenance cycle: the Recreate update strategy (delete all pods, wait for full termination, then recreate per podManagementPolicy) was promoted to Beta (#142956); rolling updates with maxUnavailable no longer count unavailable pods below the partition against the update budget, which could previously stall Parallel rolls even with maxUnavailable unset (#143114); pod adoption tightened so a stray web-01 can no longer be mistaken for ordinal 1 of StatefulSet web (#141094); scale-down no longer deadlocks with Parallel pod management when ResourceQuota blocks re-creation of failed scale-up pods (#141057); and in-place pod resizing no longer resets existing hugepage limits to zero (#141991) — that last one belongs on a "you had one job" list. The maxUnavailable documentation was also corrected to match behavior: percentages round down to a minimum of 1, not up (#142541), which several operators have discovered empirically in the other direction.
Upgrade notes you will actually hit
From the Urgent Upgrade Notes sections across the cycle, the three with immediate operational teeth:
User namespaces and the 2^32-1 ID range. Kubelet fixed ID-range handling for spec.hostUsers: false pods (#141345): the default range no longer ends at 4294967295 (the kernel refuses to map that ID, so assigned pods failed to start), kubelet now refuses to start if the subordinate range for the kubelet user in /etc/subuid//etc/subgid ends at or past 4294967295 — the notes call out kubelet:65536:4294901760 as the exact config that must be reduced by one idsPerPod block (65536 default) — and if a pod was assigned the invalid range by an older kubelet, the upgraded kubelet refuses to start until you delete the stuck ContainerCreating pod or drain the node. If you run user namespaces, this is a pre-upgrade checklist item, not a post-upgrade surprise you want.
Env-file parsing went strict. Since env files graduated to GA this cycle (gate EnvFiles locked, #139115), kubelet now rejects entries with a hash immediately after a closing single quote — KEY='value'#suffix — instead of silently ignoring the suffix, and a space between = and a quoted value is now a parse error (#143221, #142665). Affected containers fail to start until the env file is corrected. Audit your env files for trailing-comment patterns before upgrading nodes.
kubectl and the OpenAPI v2 purge. kubectl no longer downloads the deprecated /openapi/v2 schema for client-side validation — list documents are validated server-side like single objects, kubectl apply/diff compute patches without the v2 fallback, and kubectl explain no longer falls back to v2 (#143328 and its siblings). The sharp edge: with --validate=strict, resources whose API does not support server-side field validation are now not validated at all. If you rely on strict client-side preflight for exotic CRDs, verify your CRD coverage of OpenAPI v3 before assuming that safety net still exists. On the pleasant side, kuberc graduated to stable as the kubectl preference file (#142619), and kubectl no longer misreads a label like tier-v=8 as a verbosity flag (#143385) — a bug that silently cranked log level on whole scripts.
What beta.0 is for, and what happens next
Do not put v1.38.0-beta.0 near production — it is a pre-release tag with prerelease: true set on the GitHub release, and the conformance image (registry.k8s.io/conformance:v1.38.0-beta.0) exists so you can run the published artifacts in a disposable cluster. What it is genuinely useful for, today: (1) DRA driver authors running binding-condition flows against a GA-locked scheduler; (2) out-of-tree scheduler plugin maintainers compiling against the changed interfaces before code freeze hardens them; (3) admission-policy authors checking the CEL cost-model changes; (4) anyone with an OIDC issuer checking RSA key sizes against the go-jose cutoff. From here the calendar is short: code freeze November 16–17, exceptions only after that, the Deprecations and Removals blog lands November 23 (watch the Kubernetes blog), and v1.38.0 is planned for December 16, 2026. Managed fleets move later — track the 1.38 rollout on the GKE release notes, the EKS version policy pages, and the AKS supported-versions tracker; history says the majors pick 1.38 up in the first half of 2027, so the gate-scrub checklist above has a comfortably long runway — use it.
Credits
The v1.38 release team is led by Sreeram Venkitesh (@sreeram-venkitesh), with ChengHao Yang (@tico88612) running Enhancements and Agustina Barbetta (@aibarbetta) as Branch Manager. On the engineering side, the changelog credits this cycle's shape to a few names worth following: @saschagrunert (OCI seccomp profiles end-to-end, plus the seccomp fail-closed fix), @everettraven (all three ML-DSA authentication surfaces), @thc1006 (the core of the int64/Quantity hardening campaign — this cycle's highest-volume single contributor, with twenty changelog entries), @serathius (watch-cache consistency: ConsistentGetFromCache, exact-LIST serving, the etcd request-metric split), @Jefftree (sharded informers), @yongruilin (managedFields opt-out, the apiextensions module split, and validation-gen), @seans3 (the OpenAPI v2 teardown and WebSocket GA), @mborsz (client-go connection pooling), @pohly (DRA scheduling performance), the gang-scheduling cohort — @jdzikowski, @macsko, @tosi3k, @helayoty, @kannon92, @dom4ha — @danwinship (kube-proxy iptables detection, IP/CIDR validation GA), @troychiu (the DRA double-allocation regression fix), @cwedgwood (the Go 1.27 header fix), @nami-ant (the watch-list leak), @galal-hussein (StatefulSet Recreate), @halaney (the user-namespace ID-range fix), and @everpeace and @wasylkowski-a, who cut large chunks of the feature-gate graveyard.
Verdict: v1.38 is shaping up as a "maturity and hygiene" release rather than a shiny-object release — post-quantum tokens you can safely ignore until your vendors cannot, an OCI seccomp design that is right but waiting on the runtimes, a DRA core that is finally GA-locked and worth planning GPU fleet migrations around (and a 1.37 regression underneath that plan), gang scheduling one careful step from GA, and a genuinely large config-debt cleanup whose two hard-fail gates and one OIDC key-size cutoff deserve a place in your upgrade runbook now. Run the gate-scrub grep this week; stand up a beta.0 cluster if you maintain anything with a scheduler or admission interface; and mark December 16 — with the usual caveat that Kubernetes release dates are plans, not promises, and the release team calendar is the source of truth for when that plan slips.