Skip to content

Instantly share code, notes, and snippets.

@dims
Created April 23, 2026 21:31
Show Gist options
  • Select an option

  • Save dims/b300dab0f65fddd66416b69818a21753 to your computer and use it in GitHub Desktop.

Select an option

Save dims/b300dab0f65fddd66416b69818a21753 to your computer and use it in GitHub Desktop.
What k8s.io/constants enables — prioritized impact analysis (PR #135896)

What k8s.io/constants enables — prioritized impact analysis

PR: kubernetes/kubernetes#135896 Branch: add-constants-module at /Users/dsrinivas/go/src/k8s.io/kubernetes-pr135896 Cross-checked: all factual claims below verified against the actual branch.


The structural shift in one sentence

For the first time, there is a k8s.io/* module that sits below k8s.io/apimachinery and k8s.io/api in the dependency graph — and both of those modules now depend on it. The old order was "everything flows from apimachinery"; the new order is "everything flows from constants."


Priority 1 — Eliminates "import 50 MB to get one integer" for external consumers

The problem: external tools that need a single constant — DNS1123SubdomainMaxLength, LabelTopologyZone, NodeNotReady — must today import k8s.io/apimachinery or k8s.io/api/core/v1. This pulls in klog, kube-openapi, protobuf, CBOR, json-iterator, structured-merge-diff, and 26+ other modules.

Measured impact (from binary-size test harness):

To get one constant, import... Transitive pkgs go.sum entries Binary delta
k8s.io/constants/{labels,rfc,...} 64 0 +496 KB
k8s.io/apimachinery/pkg/util/validation 107 6 +1.04 MB
k8s.io/api/core/v1 272 41 +7.26 MB

That's a 14.6× binary size reduction and zero supply-chain surface (go.sum is empty because k8s.io/constants has no dependencies at all).

Who benefits immediately: admission webhooks, kubectl plugins, CLI tools, Karpenter/cluster-autoscaler/cluster-api node selectors, schema validators, policy engines (Kyverno, Gatekeeper, OPA helpers), documentation generators.

Verified: cat staging/src/k8s.io/constants/go.mod — no require block at all.


Priority 2 — Fixes api/resource/v* pulling all of util/validation for one integer (issue #127889)

The problem (master today):

// staging/src/k8s.io/api/resource/v1/types.go:25
import "k8s.io/apimachinery/pkg/util/validation"
const PoolNameMaxLength = validation.DNS1123SubdomainMaxLength

Same in v1beta1 and v1beta2. One integer forces k8s.io/api/resource to depend on util/validation, which pulls in the regex engine, unicode, apimachinery/pkg/util/validation/field, pkg/api/validate/content, klog, and more.

After the PR:

import "k8s.io/constants/rfc"
const PoolNameMaxLength = rfc.DNS1123SubdomainMaxLength

k8s.io/api/resource no longer imports util/validation at all — confirmed (grep returns empty on the PR branch).

Impact on consumers of the DRA API: any project using k8s.io/api/resource/v1 gets a narrower transitive graph for free — no code changes needed.


Priority 3 — Single source of truth for DNS/size constants (eliminates duplication risk)

The problem (master today): DNS1123SubdomainMaxLength = 253 and DNS1123LabelMaxLength = 63 are each declared independently in two places:

  • apimachinery/pkg/util/validation/validation.go:191,161
  • apimachinery/pkg/api/validate/content/dns.go:57,28

Two independent integer literals that must stay identical. Nobody enforces this.

After the PR: both locations become:

import "k8s.io/constants/rfc"
const DNS1123SubdomainMaxLength int = rfc.DNS1123SubdomainMaxLength  // re-export

There is now one definition (rfc/dns.go:42) and everything else is a compile-time alias. Drift is structurally impossible.

Also fixed: FieldManagerMaxLength on master is a var (mutable at runtime), not a const. The PR pins it to limits.FieldManagerMaxLength (a proper const).

Also surfaced: labelKeyMaxLength in content/kube.go:28 was unexported — callers had no way to reference it and hard-coded 63. The PR exports it as limits.LabelKeyMaxLength.


Priority 4 — Backwards compatibility: zero code changes required from existing consumers

Every legacy name is preserved as a compile-time alias:

// api/core/v1/well_known_labels.go (PR branch)
import "k8s.io/constants/labels"

const (
    LabelHostname       = labels.Hostname        // was "kubernetes.io/hostname"
    LabelTopologyZone   = labels.TopologyZone    // was "topology.kubernetes.io/zone"
    LabelTopologyRegion = labels.TopologyRegion
    ...
)

Same for well_known_taints.go (TaintNodeNotReady = taints.NodeNotReady) and apimachinery/pkg/util/validation/validation.go (all three DNS constants). Existing code compiles without changes. Migration is opt-in, one import at a time.

Verified: annotation_key_constants.go is byte-identical between master and PR branch — deliberately not touched. The compat surface is preserved.


Priority 5 — Explicit, uniform deprecation and alpha contract

Before: deprecation of a label/annotation was expressed inconsistently — sometimes a // deprecated line comment, sometimes a godoc Deprecated: line, sometimes nothing.

After: the constants module enforces two conventions throughout:

  1. Naming: deprecated constants have a Deprecated prefix (DeprecatedFailureDomainBetaZone, DeprecatedSeccompPod, DeprecatedAppArmorBetaContainerPrefix). IDEs and staticcheck surface this automatically.
  2. Godoc: every deprecated entry has // Deprecated: use X instead. pointing at its replacement.
  3. Alpha: alpha constants document their feature gate: "This is an alpha annotation and requires enabling the PodDeletionCost feature gate."

Impact: one authoritative place to audit "what labels/annotations are deprecated in this Kubernetes version." Today the answer is distributed across 12 files.


Priority 6 — Single searchable home for all Kubernetes vocabulary strings

Today, a new contributor (or an AI assistant) looking for "the Go constant for the topology zone label" must know to look in k8s.io/api/core/v1/well_known_labels.go. There is no canonical index.

After this PR: k8s.io/constants/{labels,annotations,taints} is the answer. When published as github.com/kubernetes/constants, it gets its own pkg.go.dev page organized by vocabulary category — the first hit for "Kubernetes well-known labels" in any Go package search.

The magic-string problem in-tree: there are currently ~217 literal occurrences of "kubernetes.io/hostname" in the tree even though v1.LabelHostname exists — the weight of importing k8s.io/api/core/v1 made using the constant feel expensive in many contexts (tests, kubeadm, integration). A zero-dep import removes that excuse entirely and makes lint rules enforceable.


Priority 7 — Unlocks a natural series of follow-on cleanups

These become straightforward after this lands:

Follow-up What it does Blocker today
Convert annotation_key_constants.go (both internal + external copies) to re-exports Eliminates ~300 lines of near-duplicated code + hand-maintained consistency comment No destination to point at
Migrate apimachinery/pkg/util/managedfields/internal/lastapplied.go:31 Third private copy of LastAppliedConfigAnnotation Same
Migrate 9 remaining well_known_*.go files (discovery, networking, cloud-provider, kubelet) Consolidates all 12 well-known files into one module No destination
Lint rule: forbid literal strings that appear in k8s.io/constants Makes magic strings a CI failure No import path light enough to mandate everywhere
Simplify util/validation to pure functions Strip the constant declarations; the file becomes validation logic only Two sets of compat re-exports are now established by the PR
k8s.io/types module (for UID, IPFamily, ResourceName) Uses same pattern and same zero-dep principle No established template

Priority 8 — Foundation for declarative validation / CEL alignment

Kubernetes is migrating from imperative Go validation to CEL expressions generated by validation-gen. CEL expressions that check length (e.g., self.size() <= 253) need to reference the same integer as Go code.

The constants/rfc module is already consumed by apimachinery/pkg/api/validate/content/dns.go (the CEL-validation-gen building block). As CEL generators template length constants into expressions, having one rfc.DNS1123SubdomainMaxLength means both the Go validator and the generated CEL expression track the same definition — drift is impossible.

The zero-dep property matters here: CEL environments embedded in CRD validation and admission webhooks are sensitive to import bloat. A pure-constant import is the cleanest integration.


Honest limitations

  • Scope is narrow today: only core/v1 labels/annotations/taints + three DNS integers. The other 9 well_known_*.go files (discovery, networking, cloud-provider, kubelet) are untouched.
  • annotation_key_constants.go not migrated: 14 annotation constants in core/v1 still have literal string definitions, not re-exports. Second-phase work.
  • No explicit stability policy document: the module exists; the written per-constant stability contract is not yet in README.md or STABILITY.md.
  • Standalone repo question open: liggitt and thockin raised whether a staging module risks k/k → external → k/k-staging dependency loops. A standalone github.com/kubernetes/constants repo would avoid this but adds release overhead. Still under discussion.
  • rfc/ naming and DNS1035LabelMaxLength: jpbetz noted kube-openapi#384 uses k8s-short-name/k8s-long-name — identifier alignment pending sign-off. thockin wants rfc/ dropped from scope pending DV maturity. These may narrow the module's initial footprint.

Summary table

# Benefit Audience Evidence
1 14.6× binary size reduction; zero transitive deps External consumers Binary test harness; go.mod has no require block
2 api/resource/v* drops util/validation entirely DRA API consumers grep returns empty on PR branch
3 DNS/size constant duplication eliminated Maintainers Was 2 independent = 253; now both are = rfc.*
4 Zero breaking changes; all legacy names preserved Everyone annotation_key_constants.go unchanged; LHS aliases intact
5 Uniform deprecation/alpha contract Maintainers, IDEs, linters Deprecated* naming + godoc throughout
6 One searchable canonical home for K8s vocabulary Contributors, tooling, AI 217 magic-string occurrences of "kubernetes.io/hostname" in-tree
7 Unlocks 8+ follow-on cleanups Maintainers annotation_key_constants, well_known networking/cloud-provider, lint rules
8 CEL/DV validation stays consistent with Go constants Maintainers, API machinery content/dns.go already consumes constants/rfc
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment