Date: 2025-12-22
Scope: All staging modules in staging/src/k8s.io/
Commit: c255acea91f541a6f57a57fb6a6a400506ca2456
This report provides a comprehensive analysis of cross-dependencies between Kubernetes staging modules to identify opportunities for reducing coupling and module footprint.
Key Findings:
- apimachinery is properly isolated - Only depends on klog, kube-openapi, and k8s.io/utils
- Utility packages are heavily shared - util/sets (200 imports), util/runtime (208 imports), util/wait (109 imports)
- Constant imports are a minor issue - Only a few places import packages just for constants
- Generated code adds complexity - Apply configurations and validation code pull in extra dependencies
- component-base ↔ client-go has bidirectional coupling - Metrics adapters create coupling
- No circular dependencies found - The layering is mostly clean
| Module | apimachinery imports | api imports | client-go imports | Other k8s.io |
|---|---|---|---|---|
| apimachinery | - (self) | 0 | 0 | klog, kube-openapi, utils |
| api | 478 | - (self) | 0 | klog only |
| client-go | 2101 | 1312 | - (self) | klog, kube-openapi, utils |
| apiserver | 1169 | 133 | 180 | component-base, kms, klog |
| component-base | 54 | 0 | 4 | klog, utils |
| kubectl | 547 | 199 | 200 | cli-runtime, metrics, component-base |
Level 0 (Foundation):
┌─────────────────────────────────────────────────────────────────┐
│ apimachinery │
│ (depends only on: klog, kube-openapi, k8s.io/utils) │
└─────────────────────────────────────────────────────────────────┘
│
Level 1: │
┌───────────┬───────────────────┼───────────────────┬─────────────┐
│ api │ kms │ cri-api │ mount-utils │
│ │ │ │ externaljwt │
└─────┬─────┴─────────┬─────────┴─────────┬─────────┴─────────────┘
│ │ │
Level 2: │ │
┌─────┴───────────────┴───────────────────┴───────────────────────┐
│ client-go │
│ code-generator, cluster-bootstrap │
└─────────────────────────────┬───────────────────────────────────┘
│
Level 3: │
┌─────────────────────────────┴───────────────────────────────────┐
│ component-base, component-helpers, cli-runtime │
│ cri-client, metrics │
└─────────────────────────────┬───────────────────────────────────┘
│
Level 4+: │
┌─────────────────────────────┴───────────────────────────────────┐
│ apiserver, kubectl, kubelet, kube-scheduler, cloud-provider │
│ controller-manager, kube-aggregator, apiextensions-apiserver │
└─────────────────────────────────────────────────────────────────┘
These packages from k8s.io/apimachinery/pkg/util/* are imported across staging:
| Package | Import Count | Description | Reduction Potential |
|---|---|---|---|
util/runtime |
208 | Error handling, panics, finalizers | Low - fundamental |
util/sets |
200 | Generic set implementations | Low - fundamental |
util/validation/field |
192 | Field-level validation errors | Low - validation infra |
util/managedfields |
170 | Server-side apply logic | Low - core feature |
util/wait |
109 | Polling, backoff, conditions | Low - fundamental |
util/errors |
71 | Error aggregation | Low - fundamental |
util/intstr |
59 | IntOrString type | None - API type |
util/version |
38 | Semantic versioning | Medium - could use stdlib |
util/net |
39 | Network utilities | Low - specialized |
util/validation |
32 | Validation functions + constants | Medium - constants could be extracted |
| Module | Files | Primary Usage |
|---|---|---|
| kubectl | 13 | Validation functions + constants |
| apiserver | 7 | Validation functions + constants |
| apimachinery | 4 | Defines the package |
| apiextensions-apiserver | 3 | CRD validation |
| api | 3 | Constants only |
| kube-aggregator | 1 | Validation functions |
| client-go | 1 | Validation functions |
Assessment: Only k8s.io/api uses this package purely for constants - this is the Issue #127889 pattern.
Severity: Low Effort: Low
Packages imported only to access simple constants that could be hardcoded:
| Location | Constant | Value | Source |
|---|---|---|---|
api/resource/v1/types.go |
DNS1123SubdomainMaxLength |
253 | RFC 1123 |
api/resource/v1beta1/types.go |
DNS1123SubdomainMaxLength |
253 | RFC 1123 |
api/resource/v1beta2/types.go |
DNS1123SubdomainMaxLength |
253 | RFC 1123 |
kubectl/pkg/cmd/taint/taint.go |
DNS1123SubdomainMaxLength, LabelValueMaxLength |
253, 63 | Help text |
kubectl/pkg/cmd/expose/expose.go |
DNS1035LabelMaxLength |
63 | Truncation |
kubectl/pkg/cmd/set/set_selector.go |
LabelValueMaxLength |
63 | Help text |
apiserver/.../handlers/create.go |
FieldManagerMaxLength |
128 | Length check |
Severity: Medium Effort: High
component-base ↔ client-go coupling:
component-base/metrics/prometheus/clientgo/leaderelection/metrics.go
→ imports k8s.io/client-go/tools/leaderelection
component-base/metrics/prometheus/workqueue/metrics.go
→ imports k8s.io/client-go/util/workqueue
component-base/metrics/prometheus/restclient/metrics.go
→ imports k8s.io/client-go/tools/metrics
component-base/tracing/utils.go
→ imports k8s.io/client-go/transport
Why this exists: component-base provides Prometheus metrics adapters that register with client-go's metrics interfaces.
Why it matters: Creates a subtle coupling where component-base (Level 3) imports from client-go (Level 2), but client-go also depends on component-base for metrics.
Potential solutions:
- Move metrics interfaces to a lower-level package
- Accept the coupling as necessary for metrics registration
- Create a dedicated
k8s.io/client-go-metricsinterface package
Severity: Low Effort: Medium
Apply Configurations (client-go):
The client-go/applyconfigurations/ directory contains generated apply configuration types. These files are generated but add to the overall dependency surface.
Generated Validations (api):
api/extensions/v1beta1/zz_generated.validations.go
→ imports validation/field, api/validate, api/safe, api/operation
Questions for discussion:
- Should validation code be generated into
k8s.io/api? - Could validation live in a separate module?
Current imports from apimachinery:
| Package | Count | Classification |
|---|---|---|
pkg/apis/meta/v1 |
~190 | Essential - ObjectMeta, TypeMeta |
pkg/runtime |
~130 | Essential - Object interface |
pkg/runtime/schema |
~80 | Essential - GVK |
pkg/api/resource |
~30 | Essential - Quantity |
pkg/util/intstr |
~25 | Essential - IntOrString |
pkg/types |
~25 | Essential - UID, types |
pkg/util/validation |
3 | Reducible - constants only |
pkg/api/validate/* |
~4 | Questionable - generated code |
Reduction opportunity: Remove util/validation import by hardcoding constants.
Import profile (top packages):
- 2101 imports from apimachinery/pkg
- 1312 imports from api/*
- Heavy but expected for a client library
Notable patterns:
- Generated listers/informers for every API type
- Apply configurations for every API type
- Typed clients for every API group
No significant reduction opportunities - this is the expected role of client-go.
Import profile:
- 1169 from apimachinery/pkg
- 133 from api/*
- 180 from client-go
- 134 from component-base
Notable dependencies:
- Heavy use of client-go informers
- Metrics and tracing from component-base
- kube-openapi for OpenAPI generation
Minimal footprint:
- 54 from apimachinery/pkg
- 4 from client-go (metrics adapters)
- ~20 from klog
The client-go imports are the only questionable area (see Section 3.2).
| # | Action | Files | Impact |
|---|---|---|---|
| 1 | Hardcode DNS1123SubdomainMaxLength=253 in api/resource |
3 | Removes util/validation from api |
| 2 | Hardcode DNS/Label constants in kubectl commands | 4 | Minor cleanup |
| 3 | Hardcode FieldManagerMaxLength=128 in apiserver |
1 | Minor cleanup |
| # | Action | Complexity | Benefit |
|---|---|---|---|
| 1 | Create RFC constants package in k8s.io/api | Medium | Single source of truth |
| 2 | Review generated validation code placement | Medium | Cleaner api module |
| 3 | Inline validation constant usage | Low | Remove content import |
| Topic | Question | Stakeholders |
|---|---|---|
| Metrics interface location | Should metrics interfaces be in a lower-level package? | sig-instrumentation |
| Validation code generation | Should validation be generated into api? | sig-api-machinery |
| Apply configuration size | Can generated code be reduced? | sig-api-machinery |
- apimachinery has zero dependencies on api or client-go
- api has zero dependencies on client-go
- No circular dependencies between staging modules
- Lower-level modules (apimachinery, api) are properly minimal
- Higher-level modules (kubectl, apiserver) have expected larger dependency sets
- Generated code is isolated in predictable locations
- The core types in meta/v1, runtime, and types are stable and well-defined
- Import patterns are consistent across modules
21 k8s.io/klog/v2
11 k8s.io/kube-openapi/pkg/*
4 k8s.io/utils/clock
3 k8s.io/utils/ptr
3 k8s.io/utils/net
1 k8s.io/klog/v2
// pkg/util/validation/validation.go
const DNS1123LabelMaxLength int = 63
const DNS1123SubdomainMaxLength int = 253
const DNS1035LabelMaxLength int = 63
// pkg/api/validate/content/kube.go
const LabelValueMaxLength int = 63
// pkg/apis/meta/v1/validation/validation.go
var FieldManagerMaxLength = 128Constants that define Kubernetes API behavior are currently scattered across multiple modules:
| Constant Type | Currently Defined In |
|---|---|
| RFC DNS limits (253, 63) | apimachinery/pkg/util/validation, apimachinery/pkg/api/validate/content |
| Well-known label keys | api/core/v1, api/networking/v1, api/discovery/v1, kubelet/pkg/apis |
| Well-known annotation keys | api/core/v1, cloud-provider/api |
| Well-known taints | api/core/v1, cloud-provider/api |
| Resource/field limits | api/resource/v1, apimachinery/pkg/apis/meta/v1/validation |
Existing well-known constant files (12 files scattered across modules):
api/core/v1/well_known_labels.go (74 lines, ~28 constants)
api/core/v1/well_known_taints.go (52 lines)
api/core/v1/annotation_key_constants.go (159 lines)
api/networking/v1/well_known_labels.go
api/networking/v1/well_known_annotations.go
api/networking/v1beta1/well_known_labels.go
api/networking/v1beta1/well_known_annotations.go
api/discovery/v1/well_known_labels.go
api/discovery/v1beta1/well_known_labels.go
cloud-provider/api/well_known_annotations.go
cloud-provider/api/well_known_taints.go
kubelet/pkg/apis/well_known_labels.go
Create a new foundational module at Level -1 (below apimachinery):
k8s.io/constants/
├── rfc/
│ ├── dns.go # DNS1123SubdomainMaxLength=253, DNS1123LabelMaxLength=63
│ └── doc.go # RFC references and documentation
├── limits/
│ └── limits.go # LabelValueMaxLength=63, FieldManagerMaxLength=128
├── labels/
│ └── labels.go # LabelHostname, LabelTopologyZone, LabelOSStable, etc.
├── annotations/
│ └── annotations.go # LastAppliedConfigAnnotation, MirrorPodAnnotationKey, etc.
├── taints/
│ └── taints.go # Well-known taint keys and effects
├── finalizers/
│ └── finalizers.go # Well-known finalizer strings
└── doc.go
New Dependency Graph:
Level -1 (NEW):
┌─────────────────────────────────────────────────────────────────┐
│ k8s.io/constants │
│ (ZERO dependencies - pure const declarations) │
└─────────────────────────────────────────────────────────────────┘
│
Level 0: │
┌───────────────────────────────┴─────────────────────────────────┐
│ apimachinery │
│ (imports constants for validation functions) │
└─────────────────────────────────────────────────────────────────┘
│
Level 1: │
┌───────────────────────────────┴─────────────────────────────────┐
│ api │
│ (imports constants for type definitions & defaults) │
└─────────────────────────────────────────────────────────────────┘
│
(...)
Pros:
- Eliminates Issue #127889 pattern entirely
- Single source of truth for all Kubernetes "magic strings"
- Makes "API constants" explicit - if it's in this module, it's part of the API contract
- Better discoverability - one place to find all kubernetes.io/* strings
- Clear versioning and deprecation path for constants
Cons:
- Yet another module to publish and maintain
- Breaking change requiring import updates across codebase
- Where to draw the line on what's a "constant"?
- Adds complexity to the publishing bot configuration
Create k8s.io/constants as the source of truth, but keep existing constants as re-exports:
// k8s.io/constants/rfc/dns.go (NEW - source of truth)
package rfc
// DNS1123SubdomainMaxLength is the maximum length per RFC 1123.
const DNS1123SubdomainMaxLength = 253
const DNS1123LabelMaxLength = 63
const DNS1035LabelMaxLength = 63// k8s.io/apimachinery/pkg/util/validation/validation.go (UPDATED - re-export)
package validation
import "k8s.io/constants/rfc"
// DNS1123SubdomainMaxLength is re-exported for backwards compatibility.
// Deprecated: Import directly from k8s.io/constants/rfc instead.
const DNS1123SubdomainMaxLength = rfc.DNS1123SubdomainMaxLengthMigration timeline:
| Phase | Action | Breaking? |
|---|---|---|
| 1 | Create k8s.io/constants module |
No |
| 2 | Update apimachinery to import & re-export | No |
| 3 | Update api to import & re-export | No |
| 4 | Add deprecation notices to re-exports | No |
| 5 | Update docs to recommend direct imports | No |
| 6 | (Future) Remove re-exports in v2 | Yes (major version) |
| Task | Files Changed | Complexity |
|---|---|---|
| Create k8s.io/constants module structure | ~10 new files | Low |
| Update rules.yaml for publishing | 1 file | Low |
| Update import-restrictions.yaml | 1 file | Low |
| Update apimachinery to re-export | ~5 files | Low |
| Update api/core/v1 to re-export | ~3 files | Low |
| Update api/networking/v1 to re-export | ~2 files | Low |
| Update api/discovery/v1 to re-export | ~1 file | Low |
| Update cloud-provider/api to re-export | ~2 files | Low |
| Update kubelet/pkg/apis to re-export | ~1 file | Low |
| Fix Issue #127889 (api/resource uses constants) | 3 files | Low |
| Update kubectl to use constants | 4 files | Low |
| Update apiserver to use constants | 1 file | Low |
| Total | ~35 files | Low-Medium |
| Module | Current Imports for Constants | After Change | Reduction |
|---|---|---|---|
| k8s.io/api | apimachinery/pkg/util/validation (2000+ LOC) |
constants/rfc (~20 LOC) |
99% |
| kubectl | apimachinery/pkg/util/validation (2000+ LOC) |
constants/rfc + constants/limits (~40 LOC) |
98% |
| apiserver | apimachinery/pkg/apis/meta/v1/validation (500+ LOC) |
constants/limits (~20 LOC) |
96% |
BEFORE: External package wants DNS1123SubdomainMaxLength
└── imports k8s.io/apimachinery/pkg/util/validation
└── imports k8s.io/apimachinery/pkg/util/validation/field
└── imports k8s.io/apimachinery/pkg/util/errors
└── imports regexp, net, strings, unicode, ...
└── Total: ~15 transitive imports, ~3000 LOC
AFTER: External package wants DNS1123SubdomainMaxLength
└── imports k8s.io/constants/rfc
└── (no dependencies)
└── Total: 0 transitive imports, ~20 LOC
| Metric | Before | After | Improvement |
|---|---|---|---|
| Transitive imports for constants | 15+ packages | 0 packages | 100% reduction |
| LOC pulled in for constants | ~3000 | ~20 | 99.3% reduction |
| Compile time impact | Pulls in regex engine | Pure constants | Significant |
| Binary size contribution | Validation code included | Constants only | Reduced |
| Current Location | Constant Count | After Consolidation |
|---|---|---|
api/core/v1/well_known_labels.go |
~28 | → constants/labels/ |
api/core/v1/well_known_taints.go |
~10 | → constants/taints/ |
api/core/v1/annotation_key_constants.go |
~15 | → constants/annotations/ |
api/networking/v1/well_known_labels.go |
~5 | → constants/labels/ |
api/networking/v1/well_known_annotations.go |
~3 | → constants/annotations/ |
api/discovery/v1/well_known_labels.go |
~5 | → constants/labels/ |
cloud-provider/api/well_known_*.go |
~12 | → constants/ |
kubelet/pkg/apis/well_known_labels.go |
~15 | → constants/labels/ |
apimachinery/pkg/util/validation/ |
~5 | → constants/rfc/ |
apimachinery/pkg/api/validate/content/ |
~3 | → constants/limits/ |
| Total | ~100 constants | 1 module |
| Module | Approximate Size | What You Get |
|---|---|---|
k8s.io/apimachinery |
~2.5 MB | Everything: types, validation, serialization, utilities |
k8s.io/api |
~15 MB | All Kubernetes API types |
k8s.io/client-go |
~25 MB | Full client library |
k8s.io/constants |
~10 KB | Just constants - nothing else |
Size reduction for "just need a constant":
Before: Import k8s.io/apimachinery → 2.5 MB dependency
After: Import k8s.io/constants → 10 KB dependency
Reduction: 99.6%
BEFORE: Current State - Constants Scattered Across Modules
flowchart TD
subgraph "Current Dependency Flow for Constants"
API[k8s.io/api<br/>PoolNameMaxLength = validation.DNS1123SubdomainMaxLength]
APM[k8s.io/apimachinery<br/>DNS1123SubdomainMaxLength = 253<br/>DNS1123LabelMaxLength = 63<br/>LabelValueMaxLength = 63]
KC[kubectl<br/>imports validation for constants]
AS[apiserver<br/>imports validation for FieldManagerMaxLength]
API -->|imports pkg/util/validation| APM
KC -->|imports pkg/util/validation| APM
AS -->|imports meta/v1/validation| APM
end
subgraph "Problem Areas"
P1[❌ api depends on apimachinery<br/>for a simple integer]
P2[❌ Constants buried in<br/>validation packages]
P3[❌ 12+ files define<br/>well-known labels separately]
end
style API fill:#ffcccc
style P1 fill:#ffcccc
style P2 fill:#ffcccc
style P3 fill:#ffcccc
AFTER: With k8s.io/constants Module
flowchart TD
subgraph "New Dependency Flow"
CONST[k8s.io/constants<br/>ZERO dependencies<br/>Single source of truth]
APM2[k8s.io/apimachinery<br/>Re-exports for compatibility]
API2[k8s.io/api<br/>Re-exports for compatibility<br/>Uses constants directly]
KC2[kubectl<br/>Can import constants directly]
AS2[apiserver<br/>Can import constants directly]
EXT[External consumers<br/>Can import constants directly]
CONST --> APM2
CONST --> API2
CONST --> KC2
CONST --> AS2
CONST --> EXT
APM2 -.->|deprecated re-exports| API2
end
subgraph "Benefits"
B1[✅ Zero-dependency<br/>foundation module]
B2[✅ Single source of truth<br/>for all constants]
B3[✅ External consumers<br/>get minimal dependency]
end
style CONST fill:#ccffcc
style B1 fill:#ccffcc
style B2 fill:#ccffcc
style B3 fill:#ccffcc
flowchart LR
subgraph "BEFORE: Tight Coupling"
direction TB
A1[api/resource/v1] -->|imports| V1[apimachinery/pkg/util/validation]
A2[api/resource/v1beta1] -->|imports| V1
A3[api/resource/v1beta2] -->|imports| V1
V1 -->|contains| C1[DNS constants<br/>+ validation funcs<br/>+ regex patterns<br/>+ error handling]
end
subgraph "AFTER: Loose Coupling"
direction TB
B1[api/resource/v1] -->|imports| K1[constants/rfc]
B2[api/resource/v1beta1] -->|imports| K1
B3[api/resource/v1beta2] -->|imports| K1
K1 -->|contains| C2[DNS constants only<br/>3 integers<br/>~20 lines]
end
style V1 fill:#ffcccc
style C1 fill:#ffcccc
style K1 fill:#ccffcc
style C2 fill:#ccffcc
Who benefits from k8s.io/constants?
flowchart TD
subgraph "External Consumers"
OP[Operators & Controllers]
CL[CLI Tools]
WH[Webhooks]
AU[Automation Scripts]
TP[Third-party Libraries]
end
subgraph "What They Need"
L[Label Keys<br/>e.g., kubernetes.io/hostname]
A[Annotation Keys<br/>e.g., last-applied-config]
T[Taint Keys<br/>e.g., node.kubernetes.io/not-ready]
V[Validation Constants<br/>e.g., DNS1123SubdomainMaxLength]
end
subgraph "Current Pain"
P1[Must import large modules]
P2[Constants scattered across packages]
P3[Hard to discover all constants]
P4[Version skew between copies]
end
subgraph "With k8s.io/constants"
S1[Single lightweight import]
S2[One place to find everything]
S3[Clear API contract]
S4[Minimal dependency footprint]
end
OP --> L
OP --> A
CL --> V
WH --> L
AU --> A
TP --> V
L --> P1
A --> P2
T --> P3
V --> P4
P1 -.->|solved by| S1
P2 -.->|solved by| S2
P3 -.->|solved by| S3
P4 -.->|solved by| S4
style P1 fill:#ffcccc
style P2 fill:#ffcccc
style P3 fill:#ffcccc
style P4 fill:#ffcccc
style S1 fill:#ccffcc
style S2 fill:#ccffcc
style S3 fill:#ccffcc
style S4 fill:#ccffcc
Before:
import (
corev1 "k8s.io/api/core/v1" // Pulls in ALL of api/core/v1
)
func isNodeInZone(node *corev1.Node, zone string) bool {
return node.Labels[corev1.LabelTopologyZone] == zone
}
// Dependency: k8s.io/api/core/v1 → k8s.io/apimachinery (all of it)After:
import (
"k8s.io/constants/labels" // Just the constants, nothing else
)
func isNodeInZone(node *Node, zone string) bool {
return node.Labels[labels.LabelTopologyZone] == zone
}
// Dependency: k8s.io/constants/labels (zero transitive dependencies)| Action | Effort | Impact | Risk | Priority |
|---|---|---|---|---|
Create k8s.io/constants module |
Medium | High | Low | P0 |
| Add to publishing rules | Low | High | Low | P0 |
| Update apimachinery re-exports | Low | Medium | Low | P1 |
| Update api re-exports | Low | Medium | Low | P1 |
| Consolidate scattered well-known files | Medium | High | Medium | P1 |
| Update kubectl to use constants | Low | Low | Low | P2 |
| Update apiserver to use constants | Low | Low | Low | P2 |
| Document for external consumers | Low | Medium | None | P2 |
| Remove re-exports (breaking) | Low | Low | High | P3 (future) |
| Justification | Evidence |
|---|---|
| Solves real problem | Issue #127889 and similar patterns in kubectl, apiserver |
| Zero breaking changes | Re-exports maintain full backwards compatibility |
| Minimal effort | ~35 files, mostly mechanical changes |
| High discoverability | All Kubernetes "magic strings" in one searchable place |
| External consumer win | 99%+ reduction in dependency size for constant imports |
| Future-proof | Clean path to remove re-exports in hypothetical v2 |
| Precedent exists | Similar to how k8s.io/utils was extracted |
| Low risk | Constants don't change, no runtime behavior affected |
- Issue #127889: Avoid adding a dependency to k8s.io/api on an apimachinery constant
staging/publishing/rules.yaml: Defines publishing dependenciesstaging/publishing/import-restrictions.yaml: Defines allowed imports
Report generated by analyzing kubernetes/kubernetes staging modules Total staging modules analyzed: 31 Commit: c255acea91f541a6f57a57fb6a6a400506ca2456