Skip to content

Instantly share code, notes, and snippets.

@dims
Last active April 26, 2026 12:44
Show Gist options
  • Select an option

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

Select an option

Save dims/c8dd44ba82ed51dfc242b065a6ff2284 to your computer and use it in GitHub Desktop.
Kubernetes unwanted vendor dependencies status — April 2026

Kubernetes Unwanted Dependencies: Status Report

Date: April 2026
Branch: master (commit 5a555755ba2)
Scope: hack/unwanted-dependencies.json — modules listed in spec.unwantedModules that are still present in vendor/


Background

Kubernetes maintains a blocklist of dependencies that should not appear in the vendor tree, defined in hack/unwanted-dependencies.json. The file has two sections:

  • spec.unwantedModules — the full blocklist with rationale for each entry
  • status.unwantedVendored — modules from the blocklist that are currently vendored (updated when the lint check runs)
  • status.unwantedReferences — which of our direct dependencies are responsible for pulling each blocked module in transitively

As of April 2026, the blocklist contains ~80 modules. The vast majority have already been removed from vendor. 10 remain, all pulled in transitively by upstream dependencies that have not yet removed them. This report documents the current state of each, the upstream blockers, and the known issues/PRs in those repositories.


The 10 Remaining Unwanted Modules

Identified by exact match against vendor/modules.txt:

Module Reason in blocklist
github.com/davecgh/go-spew refer to #103942
github.com/gogo/protobuf unmaintained
github.com/golang/protobuf replace with google.golang.org/protobuf
github.com/google/btree unmaintained, archive mode
github.com/json-iterator/go refer to #105030
github.com/mailru/easyjson unmaintained
github.com/modern-go/concurrent problematic reliance on golang internals
github.com/modern-go/reflect2 problematic reliance on golang internals
golang.org/x/exp experimental/deprecated subrepository
gopkg.in/yaml.v3 prefer sigs.k8s.io/yaml/goyaml.v3

Note: github.com/modern-go/concurrent and github.com/modern-go/reflect2 are transitive dependencies of github.com/json-iterator/go. Their fate is fully coupled to json-iterator's removal.


Per-Module Analysis

1. github.com/google/btree

Rationale: Archived/unmaintained upstream.

Kubernetes vendor version: (via go.etcd.io/etcd/server/v3 v3.6.8)

Primary upstream blocker: go.etcd.io/etcd/server/v3

Upstream status:

  • etcd issue #20991 ("Evaluate and replace deprecated github.com/google/btree") — closed/completed. The last comment reads "Thanks! Closing this ticket..." confirming the work landed.
  • On etcd main, github.com/google/btree has been fully replaced with a fork embedded in k8s.io/utils/third_party/forked/golang/btree. The change is in server/storage/mvcc/index.go and related files.
  • etcd v3.6.8 (vendored by Kubernetes): still depends on google/btree v1.1.3
  • etcd v3.6.10 (latest release, 2026-04-01): still depends on google/btree v1.1.3

Prognosis: ✅ Done upstream — waiting on the next etcd release after v3.6.10 to cut a release from a commit that includes the main-branch removal.


2. github.com/davecgh/go-spew

Rationale: See kubernetes/kubernetes#103942. Unmaintained; Go's fmt package (%#v, sorted map output) now covers most use cases.

⚠️ k/k itself is a direct user (not just transitive): k8s.io/utils/dump imports go-spew and exposes a ForHash function. The file contains an explicit comment: "The config MUST NOT be changed because that could change the result of a hash operation." This makes removal harder than a simple dependency bump — it requires either a byte-for-byte compatible replacement or a checkpoint/label migration on running clusters.

Production call sites in k/k (non-test):

  • pkg/util/hash/hash.goDeepHashObject calls dump.ForHash; used for pod template hashes, controller revision hashes, endpoint slice hashes
  • pkg/kubelet/cm/cpumanager/state/checkpoint.goMarshalCheckpoint and VerifyChecksum call dump.ForHash directly; the hash is stored in and validated from kubelet checkpoint files on disk
  • pkg/kubelet/cm/memorymanager/state/checkpoint.go — same pattern
  • pkg/controller/controller_utils.go, pkg/controller/history/controller_history.go, pkg/controller/job/job_scheduling_manager.go, pkg/kubelet/config/common.go, pkg/kubelet/container/helpers.go, pkg/api/v1/endpoints/util.go, staging/.../endpointslice/util/controller_utils.go

Upstream transitive blockers:
stretchr/testify, prometheus/common, go-openapi/jsonpointer, go-openapi/swag, go-logr/zapr, sirupsen/logrus, go.uber.org/{goleak,multierr,zap}, grpc-ecosystem/go-grpc-middleware/v2, github.com/google/cadvisor, go.etcd.io/etcd/*, all go.opentelemetry.io/* packages, k8s.io/kube-openapi, k8s.io/utils, sigs.k8s.io/kustomize/*

Upstream status:

  • stretchr/testify is the most impactful transitive blocker.
    • Issue testify#1061 ("Possible to remove go-spew?") — open since 2018. Long-running discussion with no resolution.
    • testify master has removed go-spew — the require block no longer includes it. However, the last release was v1.11.1 (August 2025) and no new release has been cut since.
  • All other transitive blockers — prometheus/common, go-logr/zapr, sirupsen/logrus, go.uber.org/, go-openapi/, cadvisor, etcd, otel packages — still carry go-spew at their latest releases with no active removal efforts identified.

Prognosis: 🔴 Even if all transitive upstreams dropped go-spew, k/k itself would still need it through k8s.io/utils/dump.ForHash. Removal requires replacing ForHash with a compatible alternative and providing a migration path for kubelet checkpoint files and hashed labels already written on running nodes. This is a k/k-internal design problem, not just an upstream one.


3. gopkg.in/yaml.v3

Rationale: Prefer sigs.k8s.io/yaml/goyaml.v3 (the same upstream, re-exported under a stable import path).

Primary upstream blockers:
stretchr/testify, prometheus/common, go-openapi/jsonpointer, go-openapi/swag, go-logr/zapr, grpc-ecosystem/go-grpc-middleware/v2, go.uber.org/{goleak,multierr,zap}, github.com/google/cadvisor, go.etcd.io/etcd/*, all go.opentelemetry.io/* packages, k8s.io/kube-openapi, sigs.k8s.io/kustomize/*, github.com/containerd/containerd/api

Upstream status:

  • PR testify#1772 ("change yaml library to go.yaml.in/yaml/v3") — open. This would switch testify from gopkg.in/yaml.v3 to the new canonical import path go.yaml.in/yaml/v3 (same code, maintained by the same author under a stable domain).
  • Note: go-openapi/swag master already uses go.yaml.in/yaml/v3 (alongside gopkg.in/yaml.v3 transitively), and their newest jsonpointer v0.23.1 drops the gopkg.in path entirely — but those versions require Go 1.25 (see §5 below).
  • Every other upstream still carries gopkg.in/yaml.v3 with no active removal PRs.

Prognosis: 🔴 Very widely embedded. Even removing it from testify (via PR#1772) would only reduce the count. The long tail across etcd, otel, containerd, cadvisor, kustomize makes this the hardest module to fully remove.


4. github.com/mailru/easyjson

Rationale: Unmaintained. kube-openapi should use its own internal JSON machinery.

Primary upstream blockers:
k8s.io/kube-openapigo-openapi/jsonpointer v0.21.0, go-openapi/swag v0.23.0; also sigs.k8s.io/kustomize/{api,kyaml}

Upstream status:

The go-openapi project has undergone a major restructuring:

  • go-openapi/swag refactored into sub-modules starting around v0.25.x. The sub-modules (swag/jsonname, swag/jsonutils, etc.) do not depend on mailru/easyjson. The root swag package re-exports the same symbols so no import-path changes are needed in consumers.
  • go-openapi/jsonpointer v0.23.1 (released 2026-04-18): completely rewritten, depends on go-openapi/swag/jsonname v0.26.0 instead of mailru/easyjson. Go directive: go 1.25.0.
  • go-openapi/swag master has also fully removed easyjson, requiring Go 1.25.

The critical path for Kubernetes:

  • PR kube-openapi#590open, filed by dims. Upgrades go-openapi/swag to v0.25.4, which is the last version of the split-module approach that works with Go 1.24. The PR also bumps the go directive to 1.24 and fixes two pre-existing vet errors exposed by the bump. Status: self-approved by dims, assigned to Jefftree and apelisse, needs LGTM from sttts.
  • kustomize also carries easyjson transitively; a bump of kube-openapi in kustomize would likely pull it through.

Prognosis: 🟡 There is a concrete, mergeable PR in kube-openapi (#590). The main bottleneck is reviewer bandwidth.


5. github.com/json-iterator/go, github.com/modern-go/concurrent, github.com/modern-go/reflect2

Rationale: json-iterator (and its modern-go dependencies) relies on unstable Go runtime internals via reflect2. See kubernetes/kubernetes#105030. Every new Go release requires a patch to keep it working.

Primary upstream blockers:
prometheus/client_golang v1.23.2, k8s.io/kube-openapi (commit 43fb72c5454a), sigs.k8s.io/structured-merge-diff/v6 v6.3.2

Upstream status:

  • prometheus/client_golang: No removal effort. The project went in the opposite direction — PR #1225 (merged 2023) extended json-iterator usage. json-iterator is used for performance-sensitive scrape-result encoding.
  • k8s.io/kube-openapi: PR #326 ("use k8s.io/apimachinery/pkg/util/json instead of json-iterator") was closed without merge. Currently no open replacement PR. An older PR #246 ("Switch to stdlib json encoding") was merged, but kube-openapi still has json-iterator in its go.mod as of the March 2026 commit we vendor.
  • sigs.k8s.io/structured-merge-diff: Issue #202 ("remove use of json-iterator / reflect2") — open and actively tracked. The latest comment explicitly states: "I don't expect us to merge this before stdlib ships json/v2 in a non-experimental fashion (Go 1.26 at the earliest)". The replacement under investigation is the new encoding/json/v2 package (tracking at golang/go#71497).

Prognosis: 🔴 Gated on Go 1.26 stdlib encoding/json/v2. Go 1.25 is expected mid-2026 and does not include json/v2 as stable; Go 1.26 would be early 2027 at the earliest. This is the longest tail on the list.


6. github.com/golang/protobuf

Rationale: The github.com/golang/protobuf package is the deprecated v1 API for protobuf in Go. It should be replaced with google.golang.org/protobuf (the modern v2 API).

Primary upstream blockers:
google.golang.org/grpc v1.80.0 (primary), github.com/containerd/containerd/api v1.10.0, github.com/containerd/ttrpc v1.2.7, go.etcd.io/etcd/{api,client,server}/v3 v3.6.8, sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0, github.com/container-storage-interface/spec

Upstream status:

  • google.golang.org/grpc: Issue grpc-go#8940 ("depends on deprecated github.com/golang/protobuf") was closed with an explicit maintainer response: "As long as we are on grpc major version 1 (and there is no plan of doing a new major version), github.com/golang/protobuf is required for backward compatibility with applications using proto-generated code from older versions of the code generator."
  • grpc-go master still carries github.com/golang/protobuf v1.5.4.
  • The golang/protobuf package itself still exists as a compatibility shim over google.golang.org/protobuf, and google.golang.org/protobuf itself lists github.com/golang/protobuf in its own go.mod (they cross-reference for the shim).
  • konnectivity-client v0.34.0 has github.com/gogo/protobuf v1.3.2 // indirect.

Prognosis: 🔴 Effectively a won't-fix for grpc-go v1.x. Would require grpc-go to cut a major version (v2), which has no announced timeline. This is structurally the hardest removal.


7. github.com/gogo/protobuf

Rationale: Unmaintained. The project has not had a release since 2021 (v1.3.2). gRPC and protobuf work has consolidated around google.golang.org/protobuf.

Primary upstream blockers:
github.com/containerd/ttrpc v1.2.7, github.com/containerd/typeurl/v2 v2.2.3, github.com/containerd/containerd/api v1.10.0, github.com/google/cadvisor v0.56.2, go.etcd.io/etcd/server/v3 v3.6.8, sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0

Upstream status:

  • containerd/ttrpc: Issue ttrpc#97 ("New branch for removing gogo/protobuf") — open but stalled. The issue describes creating a v2 branch to hold gogo-free code, since removal requires backward-incompatible API changes. The last two comments are: "Let me clean up open PRs. I'd like to merge 1.0.x enhancements before cutting the branch." and "Should this be closed?" — indicating no concrete progress.
  • ttrpc v1.2.8 was released March 2026 and still carries gogo/protobuf v1.3.2. The main branch also still carries it.
  • containerd/typeurl/v2: No open issues for gogo removal.
  • google/cadvisor: No active issues for gogo removal.
  • etcd: etcd's gogo usage is deeply embedded in its wire protocol; no specific removal issue found.

Prognosis: 🔴 Long-term. Requires a ttrpc v2 (breaking change) and coordinated updates across containerd, cadvisor, and etcd. No active development toward this goal.


Summary Table

Module Key blocker Blocker status Prognosis
github.com/google/btree etcd ✅ Removed on main Next etcd release
github.com/davecgh/go-spew k/k itself via k8s.io/utils/dump.ForHash (kubelet checkpoints, controller hashes) 🔴 Load-bearing in k/k; testify master removed it but k/k design change needed Requires ForHash replacement + checkpoint migration
github.com/mailru/easyjson kube-openapi 🟡 Open PR #590 (by dims) Needs LGTM from sttts
gopkg.in/yaml.v3 testify + many 🟡 Open PR testify#1772 Medium-term; many upstreams
golang.org/x/exp antlr4-go, cel-go 🟡 Open PR antlr#15 Medium-term; cel-go has no plan
github.com/gogo/protobuf containerd/ttrpc 🔴 Stalled (ttrpc#97) Long-term; needs ttrpc v2
github.com/json-iterator/go prometheus, kube-openapi, SMD 🔴 Blocked on Go 1.26 json/v2 Long-term (Go 1.26+)
github.com/modern-go/concurrent (via json-iterator) 🔴 Same as json-iterator Long-term (Go 1.26+)
github.com/modern-go/reflect2 (via json-iterator) 🔴 Same as json-iterator Long-term (Go 1.26+)
github.com/golang/protobuf grpc-go 🔴 Explicit won't-fix in v1.x Indefinite (needs grpc v2)

Recommended Actions

Immediate (can act now)

  1. kube-openapi PR #590 — The PR dropping mailru/easyjson was filed by dims and is waiting for review. Ping sttts, Jefftree, or apelisse to get a LGTM. Once merged and a kube-openapi snapshot is vendored into Kubernetes, easyjson leaves the vendor tree.

  2. Watch for testify release — testify master has removed go-spew. Subscribe to testify releases for a new tag. Once released, a bump in Kubernetes would remove go-spew from many (but not all) transitive paths.

  3. Watch for next etcd release — etcd main has removed google/btree. The next minor/patch release after v3.6.10 should carry this fix.

Medium-term (nudge upstreams)

  1. antlr4-go PR #15 — Remove golang.org/x/exp in antlr4-go. The PR replaces x/exp/slices with stdlib slices. Two prior attempts were closed; this one has been open longer. A comment or review from the Kubernetes community would help. Note: cel-go also carries x/exp as an indirect dep, and has no open issues or PRs about removing it — this would need a separate upstream conversation.

  2. testify PR #1772 — Switch yaml to go.yaml.in/yaml/v3. This would begin clearing the gopkg.in/yaml.v3 transitive chain but many other upstreams would still need to follow.

Long-term (track externally)

  1. gogo/protobuf — Track ttrpc#97. The containerd ecosystem moving to a ttrpc v2 that uses google.golang.org/protobuf is the prerequisite. This likely needs to be driven from within the containerd project.

  2. json-iterator / modern-go — Track golang/go#71497 (stdlib encoding/json/v2). structured-merge-diff will migrate once json/v2 ships in Go 1.26 (earliest: early 2027). prometheus/client_golang will be harder — they value json-iterator for scrape performance.

  3. golang/protobuf — No action available. grpc-go has explicitly said they will not remove it in v1.x. Track the grpc-go v2 announcement when/if it happens.


Methodology

To reproduce this analysis for a future Kubernetes commit:

# 1. Find all unwanted modules still in vendor (exact match)
python3 -c "
import json, re
with open('hack/unwanted-dependencies.json') as f:
    data = json.load(f)
unwanted = set(data['spec']['unwantedModules'].keys())
vendored = set()
with open('vendor/modules.txt') as f:
    for line in f:
        m = re.match(r'^# (\S+) ', line)
        if m:
            vendored.add(m.group(1))
found = sorted(unwanted & vendored)
for mod in found:
    print(mod, '--', data['spec']['unwantedModules'][mod])
"

# 2. For each module, check current vendor version of primary blockers
grep '^# ' vendor/modules.txt | grep '<blocker-module>'

# 3. For each blocker, check their latest release go.mod
curl -s "https://raw.githubusercontent.com/<org>/<repo>/refs/tags/<version>/go.mod" | grep '<unwanted-module>'

# 4. Check main/master branch of blocker
curl -s "https://raw.githubusercontent.com/<org>/<repo>/main/go.mod" | grep '<unwanted-module>'

# 5. Search for upstream issues/PRs
gh search issues --repo <org>/<repo> "<unwanted-module-name>" --json number,title,state,url
gh search prs --repo <org>/<repo> "<unwanted-module-name>" --json number,title,state,url

Appendix: Prompt to Recreate This Document

The following prompt, given to Claude Code in the root of the Kubernetes repository, will reproduce an equivalent analysis:


I want a detailed status report on the unwanted dependencies still present in the
Kubernetes vendor/ directory, similar to a previous analysis done in April 2026.

Here is the full workflow to follow:

### Step 1 — Find all unwanted modules still vendored (exact match, no prefix false-positives)

Run this Python snippet:

    python3 -c "
    import json, re
    with open('hack/unwanted-dependencies.json') as f:
        data = json.load(f)
    unwanted = set(data['spec']['unwantedModules'].keys())
    vendored = set()
    with open('vendor/modules.txt') as f:
        for line in f:
            m = re.match(r'^# (\S+) ', line)
            if m:
                vendored.add(m.group(1))
    found = sorted(unwanted & vendored)
    print(f'Still vendored ({len(found)}):')
    for mod in found:
        print(f'  {mod}  ({data[\"spec\"][\"unwantedModules\"][mod]})')
    "

### Step 2 — For each remaining module, identify which direct dependencies bring it in

Read hack/unwanted-dependencies.json → status.unwantedReferences. This maps
each unwanted module to the list of our direct dependencies that pull it in.

### Step 3 — Determine current vendor versions of all primary blockers

    grep '^# ' vendor/modules.txt | grep -E '<list of primary blockers>'

### Step 4 — For each primary blocker at its current vendored version, check whether
the unwanted module is in its go.mod

    curl -s "https://raw.githubusercontent.com/<org>/<repo>/refs/tags/<version>/go.mod" \
      | grep '<unwanted-module>'

### Step 5 — Check the blocker's main/master branch for in-progress removal

    curl -s "https://raw.githubusercontent.com/<org>/<repo>/main/go.mod" \
      | grep '<unwanted-module>'

### Step 6 — Find the latest release of each blocker (to catch cases where main is
ahead of what we vendor)

    gh api repos/<org>/<repo>/releases/latest --jq '.tag_name'

Then check that latest release's go.mod too.

### Step 7 — Search for open issues and PRs in upstream repos about removal

For each blocker repo, run:

    gh search issues --repo <org>/<repo> "<short-name-of-unwanted-module>" \
      --limit 5 --json number,title,state,url
    gh search prs --repo <org>/<repo> "<short-name-of-unwanted-module>" \
      --limit 5 --json number,title,state,url

Fetch the body and latest comments of the most relevant results:

    gh issue view <number> --repo <org>/<repo> --json title,state,body,comments

### Step 8 — Compile the report

Write a 6-7 page markdown to ~/notes/k8s-unwanted-deps-<YYYY-MM>.md with:

1. Background section explaining hack/unwanted-dependencies.json
2. Table of all currently-vendored unwanted modules
3. Per-module section for each one covering:
   - Blocklist rationale
   - Primary upstream blockers (from status.unwantedReferences)
   - Upstream status (latest release still has it? main/master removed it?)
   - Known open issues and PRs in upstream repos
   - Prognosis (near / medium / long term)
4. Summary table with prognosis column
5. Recommended actions (immediate / medium-term / long-term)
6. Methodology section with reproducible shell commands
7. This appendix prompt

Use ✅ 🟡 🔴 to indicate status (done-unreleased / in-progress / blocked).

### Key repos to check for each module (from the April 2026 analysis):

- google/btree → etcd-io/etcd (server/v3)
- go-spew → stretchr/testify (primary), prometheus/common, go-openapi/*
- yaml.v3 → stretchr/testify (primary), same long tail as go-spew
- mailru/easyjson → kubernetes/kube-openapi → go-openapi/jsonpointer, go-openapi/swag
- json-iterator + modern-go → prometheus/client_golang, kubernetes/kube-openapi,
  kubernetes-sigs/structured-merge-diff
- gogo/protobuf → containerd/ttrpc, containerd/typeurl, google/cadvisor,
  etcd-io/etcd (server/v3)
- golang/protobuf → grpc/grpc-go (primary), containerd, etcd, konnectivity-client
- golang.org/x/exp → antlr4-go/antlr, google/cel-go,
  grpc-ecosystem/go-grpc-middleware

After writing the file, push it to a GitHub Gist using:

    gh gist create ~/notes/k8s-unwanted-deps-<YYYY-MM>.md \
      --public \
      --desc "Kubernetes unwanted vendor dependencies status — <Month YYYY>"

Return the gist URL.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment