k8s-removed-api-lint / index.html
getreadystack's picture
Upload index.html with huggingface_hub
8b0e1af verified
Raw History Blame Contribute Delete
50.4 kB
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Kubernetes Removed API check — find removed api versions and</title>
<meta name="description" content="This offline browser tool finds Kubernetes apiVersions that kubectl no longer serves and provides the replacement for each.">
<style>
:root{color-scheme:light}body{margin:0;font:15px/1.55 system-ui,-apple-system,"Segoe UI",sans-serif;background:#faf9f5;color:#1a1512}
.wrap{max-width:960px;margin:0 auto;padding:28px 18px 60px}h1{font-size:26px;margin:0 0 6px}.lede{color:#4a403a;margin:0 0 16px}
textarea{width:100%;min-height:260px;font:13px/1.45 ui-monospace,Menlo,Consolas,monospace;padding:12px;border:1px solid #d9d0c4;border-radius:8px;box-sizing:border-box}
.bar{display:flex;gap:10px;align-items:center;margin:12px 0}button{font:600 15px system-ui;padding:10px 18px;border:0;border-radius:8px;background:#1a1512;color:#fff;cursor:pointer}
input[type=date]{font:14px system-ui;padding:8px;border:1px solid #d9d0c4;border-radius:6px}
table{border-collapse:collapse;width:100%;font-size:14px;margin-top:10px}th,td{text-align:left;padding:8px 10px;border-bottom:1px solid #eee;vertical-align:top}th{font-size:12px;letter-spacing:.06em;text-transform:uppercase;color:#7a6e65}
.sev.error{color:#a8551f;font-weight:700}.sev.warn,.sev.warning{color:#96691f}.ok{color:#2a6446;font-weight:600}.sum{font-weight:600}
.note{font-size:13px;color:#7a6e65;margin-top:18px}#paid{margin-top:22px;padding:14px 16px;border:1px solid #e2d8cb;border-radius:10px;background:#fffdfa}
</style>
<script data-grow-initializer="">!(function(){window.growMe||((window.growMe=function(e){window.growMe._.push(e);}),(window.growMe._=[]));var e=document.createElement("script");(e.type="text/javascript"),(e.src="https://faves.grow.me/main.js"),(e.defer=!0),e.setAttribute("data-grow-faves-site-id","U2l0ZTphZjljMzgyMC0yMmU5LTRhYjgtYTIxNS03OTRhMGY1N2ExYzM=");var t=document.getElementsByTagName("script")[0];t.parentNode.insertBefore(e,t);})();</script>
<!--yj-canon-s101--><link rel="canonical" href="https://getreadystack.com/tools/k8s-removed-api-lint"></head>
<body>
<div class="wrap">
<h1>Kubernetes Removed API check — find removed api versions and</h1>
<p class="lede">Find the apiVersions kubectl no longer serves: extensions/v1beta1, batch/v1beta1 CronJob, policy/v1beta1 PDB, PodSecurityPolicy, autoscaling/v2beta2. 15 rules, offline, each with the replacement. Runs entirely in your browser — nothing is uploaded.</p>
<textarea id="src" spellcheck="false"># payments-api - manifests as an assistant wrote them from the shape of older examples
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: payments-api
annotations:
seccomp.security.alpha.kubernetes.io/pod: runtime/default
spec:
replicas: 3
template:
metadata:
labels:
app: payments-api
spec:
nodeSelector:
beta.kubernetes.io/os: linux
containers:
- name: api
image: registry.internal/payments-api:1.8.2
---
apiVersion: batch/v1beta1
kind: CronJob
metadata:
name: payments-reconcile
spec:
schedule: "*/15 * * * *"
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: reconcile
image: registry.internal/reconcile:0.4.1
---
apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
name: payments-api-pdb
spec:
minAvailable: 2
selector:
matchLabels:
app: payments-api
---
apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
name: payments-api-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: payments-api
minReplicas: 3
maxReplicas: 12
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
---
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: payments-api
annotations:
kubernetes.io/ingress.class: nginx
spec:
rules:
- host: pay.internal.example
http:
paths:
- path: /v1
backend:
serviceName: payments-api
servicePort: 8080
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: paymentroutes.payments.example
spec:
group: payments.example
version: v1alpha1
scope: Namespaced
names:
kind: PaymentRoute
plural: paymentroutes
---
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
name: payments-restricted
spec:
privileged: false
runAsUser:
rule: MustRunAsNonRoot
fsGroup:
rule: RunAsAny
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: payments-worker
spec:
replicas: 2
template:
metadata:
labels:
app: payments-worker
spec:
containers:
- name: worker
image: registry.internal/payments-worker:1.8.2</textarea>
<div class="bar"><button id="go">Check</button><label>as of <input type="date" id="today"></label></div>
<div id="out" aria-live="polite"></div>
<div id="paid"><strong>Free here:</strong> Check the manifest open in the editor against all 15 rules and get the replacement apiVersion for every hit. <strong>In the editor:</strong> Sweep every manifest in the workspace and write one dated migration report ordered by removal version — that part asks for a licence key. <a href="https://getreadystack.com/api/buy/cl/polar_cl_KTPvcCTdnbtM5Aee1mJ5vKkTnXi90z2QikiQy1sh8m7">Get the workspace sweep and the dated migration report &mdash; $29 once</a>, one licence key per person or CI seat. A freelance Kubernetes consultant bills about 120 US dollars an hour, and reading one chart repo for beta apiVersions before an upgrade is most of a day.</div>
<p class="note">Same engine as the VS Code extension, byte for byte.</p>
</div>
<script>var K8S_RULES = [
{"id": "removed_workloads_beta", "title": "Workload on extensions/v1beta1, apps/v1beta1 or apps/v1beta2", "sev": "error", "basis": "Removed in Kubernetes 1.16 - Deployment, DaemonSet, ReplicaSet and StatefulSet are served only by apps/v1"},
{"id": "removed_ingress_beta", "title": "Ingress on extensions/v1beta1 or networking.k8s.io/v1beta1", "sev": "error", "basis": "Removed in Kubernetes 1.22 - networking.k8s.io/v1, and the backend block changed shape"},
{"id": "removed_crd_beta", "title": "CustomResourceDefinition on apiextensions.k8s.io/v1beta1", "sev": "error", "basis": "Removed in Kubernetes 1.22 - apiextensions.k8s.io/v1 requires a structural schema per version"},
{"id": "removed_cluster_beta", "title": "Webhook, RBAC, CSR, Lease or PriorityClass on a v1beta1 group", "sev": "error", "basis": "Removed in Kubernetes 1.22 - admissionregistration, rbac.authorization, certificates, coordination and scheduling are v1"},
{"id": "removed_storage_beta", "title": "storage.k8s.io/v1beta1 object", "sev": "error", "basis": "Removed in Kubernetes 1.22, and CSIStorageCapacity in 1.27 - storage.k8s.io/v1"},
{"id": "removed_cronjob_beta", "title": "CronJob on batch/v1beta1", "sev": "error", "basis": "Removed in Kubernetes 1.25 - batch/v1"},
{"id": "removed_pdb_beta", "title": "PodDisruptionBudget on policy/v1beta1", "sev": "error", "basis": "Removed in Kubernetes 1.25 - policy/v1, where an empty selector now selects no pods"},
{"id": "removed_psp", "title": "PodSecurityPolicy", "sev": "error", "basis": "Removed outright in Kubernetes 1.25 - replaced by Pod Security Admission namespace labels"},
{"id": "removed_hpa_beta", "title": "HorizontalPodAutoscaler on autoscaling/v2beta1 or v2beta2", "sev": "error", "basis": "Removed in Kubernetes 1.25 and 1.26 - autoscaling/v2"},
{"id": "removed_events_beta", "title": "Event, EndpointSlice or RuntimeClass on a v1beta1 group", "sev": "error", "basis": "Removed in Kubernetes 1.25 - events.k8s.io/v1, discovery.k8s.io/v1, node.k8s.io/v1"},
{"id": "removed_flowcontrol_beta", "title": "FlowSchema or PriorityLevelConfiguration on a beta group", "sev": "error", "basis": "Removed in Kubernetes 1.26, 1.29 and 1.32 - flowcontrol.apiserver.k8s.io/v1"},
{"id": "selector_required", "title": "apps/v1 workload with no spec.selector", "sev": "error", "basis": "apps/v1 makes spec.selector required and immutable; extensions/v1beta1 defaulted it from the pod labels"},
{"id": "ingress_class_annotation", "title": "kubernetes.io/ingress.class annotation", "sev": "warn", "basis": "Deprecated with the v1beta1 Ingress - networking.k8s.io/v1 uses spec.ingressClassName"},
{"id": "beta_node_labels", "title": "beta.kubernetes.io or failure-domain.beta.kubernetes.io node label", "sev": "warn", "basis": "Deprecated node labels - kubernetes.io/os, kubernetes.io/arch, topology.kubernetes.io/zone, node.kubernetes.io/instance-type"},
{"id": "alpha_pod_annotations", "title": "seccomp or critical-pod alpha annotation", "sev": "error", "basis": "The seccomp alpha annotations stopped being honoured in Kubernetes 1.25 and critical-pod went in 1.16 - securityContext.seccompProfile and priorityClassName"}
]
;</script>
<script>// Kubernetes Removed API Lint - the brain. The same file runs in Node (the extension) and in the browser (the free web page).
'use strict';
var RULES = (typeof module !== 'undefined') ? require('./rules.json') : window.K8S_RULES;
// Every apiVersion the API server no longer serves, with the minor release that removed it and what replaces it.
// kinds:null means the whole group/version went away.
var TABLE = {
'extensions/v1beta1': [
{kinds: ['Ingress'], to: 'networking.k8s.io/v1', removed: '1.22', rule: 'removed_ingress_beta'},
{kinds: ['PodSecurityPolicy'], to: '', removed: '1.25', rule: 'removed_psp'},
{kinds: null, to: 'apps/v1', removed: '1.16', rule: 'removed_workloads_beta'}
],
'apps/v1beta1': [{kinds: null, to: 'apps/v1', removed: '1.16', rule: 'removed_workloads_beta'}],
'apps/v1beta2': [{kinds: null, to: 'apps/v1', removed: '1.16', rule: 'removed_workloads_beta'}],
'networking.k8s.io/v1beta1': [{kinds: null, to: 'networking.k8s.io/v1', removed: '1.22', rule: 'removed_ingress_beta'}],
'apiextensions.k8s.io/v1beta1': [{kinds: null, to: 'apiextensions.k8s.io/v1', removed: '1.22', rule: 'removed_crd_beta'}],
'admissionregistration.k8s.io/v1beta1': [{kinds: null, to: 'admissionregistration.k8s.io/v1', removed: '1.22', rule: 'removed_cluster_beta'}],
'rbac.authorization.k8s.io/v1beta1': [{kinds: null, to: 'rbac.authorization.k8s.io/v1', removed: '1.22', rule: 'removed_cluster_beta'}],
'rbac.authorization.k8s.io/v1alpha1': [{kinds: null, to: 'rbac.authorization.k8s.io/v1', removed: '1.22', rule: 'removed_cluster_beta'}],
'certificates.k8s.io/v1beta1': [{kinds: null, to: 'certificates.k8s.io/v1', removed: '1.22', rule: 'removed_cluster_beta'}],
'coordination.k8s.io/v1beta1': [{kinds: null, to: 'coordination.k8s.io/v1', removed: '1.22', rule: 'removed_cluster_beta'}],
'scheduling.k8s.io/v1beta1': [{kinds: null, to: 'scheduling.k8s.io/v1', removed: '1.22', rule: 'removed_cluster_beta'}],
'storage.k8s.io/v1beta1': [
{kinds: ['CSIStorageCapacity'], to: 'storage.k8s.io/v1', removed: '1.27', rule: 'removed_storage_beta'},
{kinds: null, to: 'storage.k8s.io/v1', removed: '1.22', rule: 'removed_storage_beta'}
],
'batch/v1beta1': [{kinds: null, to: 'batch/v1', removed: '1.25', rule: 'removed_cronjob_beta'}],
'policy/v1beta1': [
{kinds: ['PodSecurityPolicy'], to: '', removed: '1.25', rule: 'removed_psp'},
{kinds: null, to: 'policy/v1', removed: '1.25', rule: 'removed_pdb_beta'}
],
'autoscaling/v2beta1': [{kinds: null, to: 'autoscaling/v2', removed: '1.25', rule: 'removed_hpa_beta'}],
'autoscaling/v2beta2': [{kinds: null, to: 'autoscaling/v2', removed: '1.26', rule: 'removed_hpa_beta'}],
'discovery.k8s.io/v1beta1': [{kinds: null, to: 'discovery.k8s.io/v1', removed: '1.25', rule: 'removed_events_beta'}],
'events.k8s.io/v1beta1': [{kinds: null, to: 'events.k8s.io/v1', removed: '1.25', rule: 'removed_events_beta'}],
'node.k8s.io/v1beta1': [{kinds: null, to: 'node.k8s.io/v1', removed: '1.25', rule: 'removed_events_beta'}],
'flowcontrol.apiserver.k8s.io/v1beta1': [{kinds: null, to: 'flowcontrol.apiserver.k8s.io/v1', removed: '1.26', rule: 'removed_flowcontrol_beta'}],
'flowcontrol.apiserver.k8s.io/v1beta2': [{kinds: null, to: 'flowcontrol.apiserver.k8s.io/v1', removed: '1.29', rule: 'removed_flowcontrol_beta'}],
'flowcontrol.apiserver.k8s.io/v1beta3': [{kinds: null, to: 'flowcontrol.apiserver.k8s.io/v1', removed: '1.32', rule: 'removed_flowcontrol_beta'}]
};
var SELECTOR_KINDS = {Deployment: 1, StatefulSet: 1, DaemonSet: 1, ReplicaSet: 1};
function minorOf(v) { var m = /^(\d+)\.(\d+)$/.exec(String(v || '')); return m ? (Number(m[1]) * 1000 + Number(m[2])) : 0; }
// Split a multi-document YAML stream into documents, keeping the real line number of every line.
function documents(text) {
var lines = String(text == null ? '' : text).split(/\r?\n/);
var docs = [], cur = {lines: [], from: 1};
for (var i = 0; i < lines.length; i++) {
if (/^---\s*$/.test(lines[i])) { docs.push(cur); cur = {lines: [], from: i + 2}; continue; }
cur.lines.push({n: i + 1, t: lines[i]});
}
docs.push(cur);
var out = [];
for (var d = 0; d < docs.length; d++) {
var doc = docs[d], api = '', kind = '', apiLine = doc.from, kindLine = doc.from, body = '';
for (var j = 0; j < doc.lines.length; j++) {
var L = doc.lines[j], m;
body += L.t + '\n';
if (!api && (m = /^\s{0,2}apiVersion:\s*["']?([A-Za-z0-9./_-]+)/.exec(L.t))) { api = m[1]; apiLine = L.n; }
if (!kind && (m = /^\s{0,2}kind:\s*["']?([A-Za-z0-9]+)/.exec(L.t))) { kind = m[1]; kindLine = L.n; }
}
if (api || kind || /\S/.test(body)) out.push({api: api, kind: kind, apiLine: apiLine, kindLine: kindLine, body: body, lines: doc.lines});
}
return out;
}
function check(text, opts) {
opts = opts || {};
var today = opts.today || new Date().toISOString().slice(0, 10);
var F = [];
function add(id, sev, msg, line) { F.push({check: id, sev: sev, msg: msg, line: line || 1}); }
var docs = documents(text), dead = {}, worst = 0, worstV = '';
for (var i = 0; i < docs.length; i++) {
var doc = docs[i];
// 1-11 - the apiVersion the API server stopped serving
var entries = TABLE[doc.api];
if (entries) {
for (var e = 0; e < entries.length; e++) {
var hit = entries[e];
if (hit.kinds && hit.kinds.indexOf(doc.kind) < 0) continue;
var what = (doc.kind || 'object') + ' on ' + doc.api;
var fix = hit.to
? 'Use ' + hit.to + '.'
: 'PodSecurityPolicy has no replacement apiVersion: move the namespace to Pod Security Admission labels (pod-security.kubernetes.io/enforce).';
add(hit.rule, 'error', what + ' was removed in Kubernetes ' + hit.removed +
'. A cluster at ' + hit.removed + ' or newer answers kubectl apply with "no matches for kind ' +
(doc.kind || '?') + ' in version ' + doc.api + '". ' + fix, doc.apiLine);
dead[doc.api] = hit.removed;
if (!worst || minorOf(hit.removed) < worst) { worst = minorOf(hit.removed); worstV = hit.removed; }
break;
}
}
// 12 - apps/v1 made spec.selector required; the beta versions defaulted it from the pod template labels
if (doc.api === 'apps/v1' && SELECTOR_KINDS[doc.kind] && !/\n?\s{1,4}selector:/.test(doc.body))
add('selector_required', 'error', doc.kind + ' on apps/v1 has no spec.selector. apps/v1 requires it and never defaults it, so the API server rejects this object even though the same YAML worked on extensions/v1beta1.', doc.kindLine);
// 13-15 - annotations and labels that moved into real fields
for (var k = 0; k < doc.lines.length; k++) {
var t = doc.lines[k].t, n = doc.lines[k].n;
if (/kubernetes\.io\/ingress\.class\s*:/.test(t))
add('ingress_class_annotation', 'warn', 'The kubernetes.io/ingress.class annotation went away with the v1beta1 Ingress. networking.k8s.io/v1 reads spec.ingressClassName, and a controller that only watches IngressClass will ignore this Ingress.', n);
var lb = /(failure-domain\.beta\.kubernetes\.io\/(zone|region)|beta\.kubernetes\.io\/(os|arch|instance-type))/.exec(t);
if (lb) {
var repl = {zone: 'topology.kubernetes.io/zone', region: 'topology.kubernetes.io/region', os: 'kubernetes.io/os', arch: 'kubernetes.io/arch', 'instance-type': 'node.kubernetes.io/instance-type'};
add('beta_node_labels', 'warn', lb[1] + ' is a deprecated node label. Use ' + repl[lb[2] || lb[3]] + '. A node selector on the beta label can match nothing and leave the pod Pending with no scheduling event to explain it.', n);
}
if (/seccomp\.security\.alpha\.kubernetes\.io/.test(t))
add('alpha_pod_annotations', 'error', 'The seccomp alpha annotation stopped being honoured in Kubernetes 1.25. It is still accepted as a plain annotation, so nothing errors - the pod simply runs unconfined. Use securityContext.seccompProfile.', n);
if (/scheduler\.alpha\.kubernetes\.io\/critical-pod/.test(t))
add('alpha_pod_annotations', 'error', 'The scheduler.alpha.kubernetes.io/critical-pod annotation was removed in Kubernetes 1.16 and does nothing now. Use priorityClassName: system-cluster-critical.', n);
}
}
F.sort(function (a, b) { return a.line - b.line; });
var apis = Object.keys(dead);
return {
findings: F, rule_count: RULES.length, today: today,
documents: docs.length, dead_apis: apis.length, blocked_at: worstV,
summary: F.length
? apis.length + ' removed apiVersion(s) across ' + docs.length + ' document(s); this bundle already fails on any cluster at ' + (worstV || '1.16') + ' or newer'
: 'No removed apiVersion in ' + docs.length + ' document(s)'
};
}
var API = {engine: {check: check}, RULES: RULES, RULE_COUNT: RULES.length, TABLE: TABLE};
if (typeof module !== 'undefined') module.exports = API;
if (typeof window !== 'undefined') window.K8SENGINE = API;
</script>
<script>// ⛔뼈대 — kit/scaffold.py 가 찍는다 (s143). findings [{check,sev,msg,line?}] → 글/HTML. 같은 파일이 Node 와 브라우저에서 산다.
'use strict';
function escapeHtml(s) { return String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;'); }
function summaryLine(res) {
const f = (res && res.findings) || []; const e = f.filter(function (x) { return String(x.sev || 'error').toLowerCase().startsWith('err'); }).length;
return f.length ? (f.length + ' finding' + (f.length === 1 ? '' : 's') + ' (' + e + ' error' + (e === 1 ? '' : 's') + ')') : 'Clean — no findings.';
}
function toText(res, label) {
const f = (res && res.findings) || [];
const lines = ['== ' + (label || '') + ' — ' + summaryLine(res)];
f.forEach(function (x) { lines.push(' ' + String(x.sev || 'error').toUpperCase().slice(0, 4) + ' ' + (x.line ? 'L' + x.line + ' ' : '') + (x.check || x.id || '') + ' ' + (x.msg || x.message || '')); });
return lines.join('\n');
}
function toHtml(res) {
const f = (res && res.findings) || [];
if (!f.length) return '<p class="ok">' + escapeHtml(summaryLine(res)) + '</p>';
const rows = f.map(function (x) { return '<tr><td class="sev ' + escapeHtml(String(x.sev || 'error')) + '">' + escapeHtml(String(x.sev || 'error')) + '</td><td>' + (x.line ? 'L' + escapeHtml(x.line) : '') + '</td><td><code>' + escapeHtml(x.check || x.id || '') + '</code></td><td>' + escapeHtml(x.msg || x.message || '') + '</td></tr>'; }).join('');
return '<p class="sum">' + escapeHtml(summaryLine(res)) + '</p><table><thead><tr><th>Level</th><th>Line</th><th>Check</th><th>What</th></tr></thead><tbody>' + rows + '</tbody></table>';
}
var K8SREPORT = { escapeHtml: escapeHtml, summaryLine: summaryLine, toText: toText, toHtml: toHtml };
if (typeof module !== 'undefined' && module.exports) module.exports = K8SREPORT;
else window.K8SREPORT = K8SREPORT;
</script>
<script>
(function(){
var src=document.getElementById('src'), out=document.getElementById('out'), go=document.getElementById('go'), today=document.getElementById('today');
today.value=new Date().toISOString().slice(0,10);
function run(){ try { var res=window.K8SENGINE.engine.check(src.value,{today:today.value}); out.innerHTML=window.K8SREPORT.toHtml(res); } catch(e){ out.innerHTML='<p class="sev error">Could not run: '+String(e.message||e)+'</p>'; } }
go.onclick=run; if (src.value.trim()) run();
})();
</script>
<section data-yj-install="free" style="margin:2rem auto 1rem;max-width:46rem;padding:1.25rem 1.25rem;border:1px solid #cfe3d6;border-radius:12px;background:#f6fbf8;font:16px/1.6 system-ui,-apple-system,Segoe UI,sans-serif"><h2 style="margin:0 0 .4rem;font-size:1.2rem">Install free</h2><p style="margin:0 0 .8rem;color:#41505f">Free for the file open in your editor - no key, no limit. The workspace sweep and the report ask for a key.</p><a href="https://marketplace.visualstudio.com/items?itemName=ReadyStack.k8s-removed-api-lint" rel="noopener" data-yj-install-door="mp" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'k8s-removed-api-lint',src:'mp',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">VS Code Marketplace</a><a href="https://open-vsx.org/extension/readystack/k8s-removed-api-lint" rel="noopener" data-yj-install-door="ovsx" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'k8s-removed-api-lint',src:'ovsx',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">Open VSX (Cursor, VSCodium)</a><a href="https://www.npmjs.com/package/@readystack/k8s-removed-api-lint" rel="noopener" data-yj-install-door="npm" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'k8s-removed-api-lint',src:'npm',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">npm</a><a href="https://www.npmjs.com/package/@readystack/k8s-removed-api-lint" rel="noopener" data-yj-install-door="mcp" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'k8s-removed-api-lint',src:'mcp',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">MCP server (npx)</a><a href="https://hub.docker.com/r/getreadystack/k8s-removed-api-lint" rel="noopener" data-yj-install-door="docker" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'k8s-removed-api-lint',src:'docker',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">Docker Hub</a><pre style="margin:.5rem 0 0;padding:.6rem .8rem;background:#f3f5f8;border-radius:8px;font-size:.9rem;overflow:auto">ext install ReadyStack.k8s-removed-api-lint
npx @readystack/k8s-removed-api-lint &lt;file&gt;
npx @readystack/k8s-removed-api-lint --mcp</pre><p style="margin:.6rem 0 0;font-size:.92rem;color:#41505f"><a href="https://getreadystack.com/api/buy/cl/polar_cl_l6iN1uWt0FwWu7tBsczD0jWpP2vxFM54Wdwqb3KPi1G" rel="nofollow" style="color:#0f7b3f;text-decoration:underline">Team? One key for every ReadyStack linter, 5 seats, $149 once</a></p></section>
<section data-yj-alert="rule" style="margin:1rem auto 1.5rem;max-width:46rem;padding:1.1rem 1.25rem;border:1px solid #d9dee6;border-radius:12px;background:#fbfcfd;font:15px/1.55 system-ui,-apple-system,Segoe UI,sans-serif"><b style="font-size:1.05rem">Get the full rule table as a PDF</b><div style="opacity:.78;margin:.35rem 0 .8rem">All 15 rules Kubernetes Removed API Lint checks, each with its source and date, on printable pages you can hand to your team or auditor. We email it now, and once more only if a rule changes. No newsletter.</div><div style="display:flex;gap:8px;flex-wrap:wrap"><input id="yjae" type="email" placeholder="you@example.com" style="flex:1;min-width:210px;padding:11px 13px;border:1px solid rgba(140,150,165,.34);border-radius:8px;background:#fff;color:inherit"><button id="yjab" style="padding:11px 18px;border-radius:8px;border:0;cursor:pointer;background:#0f7b3f;color:#fff;font:600 15px system-ui,sans-serif">Email me the PDF</button></div><div id="yjao" hidden style="margin-top:.6rem;color:#0f7b3f">Sent. It arrives within the hour - check spam too.</div><script>(function(){var b=document.getElementById("yjab"),e=document.getElementById("yjae");if(!b||!e)return;b.addEventListener("click",function(){var v=e.value.trim();if(!/.+@.+\..+/.test(v))return;try{localStorage.setItem("yj_em","1");}catch(_){}fetch(location.origin+"/api/subscribe",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({email:v,slug:"k8s-removed-api-lint",lang:"en",source:"rule_table"+((b.closest&&b.closest("section")&&b.closest("section").getAttribute("data-yj-moved"))?"_at_result":"")})}).then(function(r){var o=document.getElementById("yjao");o.hidden=false;if(!r.ok){o.textContent="Could not save - please try again.";}}).catch(function(){var o=document.getElementById("yjao");o.hidden=false;o.textContent="Could not save - please try again.";});});})();</script><script>(function(){var e=document.getElementById("yjae"),s=e&&e.closest?e.closest("section"):null,o=document.getElementById("out");if(!s||!o)return;var d=false;function mv(ev){if(d)return;if(ev&&ev.target&&s.contains(ev.target))return;d=true;var b=o;while(b.parentNode&&b.parentNode!==document.body&&b.parentNode.tagName!=="MAIN"&&b.parentNode.tagName!=="ARTICLE")b=b.parentNode;if(!b.parentNode||b===s||b.contains(s))return;try{b.parentNode.insertBefore(s,b.nextSibling);s.setAttribute("data-yj-moved","1");}catch(_){}}document.addEventListener("input",mv,true);document.addEventListener("change",mv,true);})();</script></section>
<section data-yj-cta="paid" style="margin:3rem auto;max-width:46rem;padding:1.5rem 1.25rem;border:1px solid #d8dee9;border-radius:12px;background:#fbfcfe;font:16px/1.6 system-ui,-apple-system,Segoe UI,sans-serif"><h2 style="margin:0 0 .5rem;font-size:1.25rem">Get the complete version <span class="yj-price">$29</span></h2><p style="margin:.25rem 0 1rem;color:#41505f">This page is the working piece. The full pack has everything below.</p><p style="margin:0 0 1rem;color:#41505f">15 checks for the apiVersions kubectl no longer serves, on the manifest open in your editor</p><p style="margin:0 0 1rem;color:#5b6b7c;font-size:.94rem">A freelance Kubernetes consultant bills about 120 US dollars an hour, and reading one chart repo for beta apiVersions before an upgrade is most of a day</p><a href="https://getreadystack.com/api/buy/cl/polar_cl_KTPvcCTdnbtM5Aee1mJ5vKkTnXi90z2QikiQy1sh8m7" rel="nofollow" style="display:inline-block;padding:.7rem 1.3rem;border-radius:8px;background:#1b64f2;color:#fff;text-decoration:none;font-weight:600">Buy the full version — $29</a></section>
<!--yj-share-s101--><div style="margin:28px 0 8px;text-align:center"><button id="yjcp" type="button" style="font:inherit;padding:10px 18px;border:1px solid #8884;border-radius:10px;background:transparent;cursor:pointer">Copy link</button><button id="yjimg" type="button" style="font:inherit;padding:10px 18px;margin-left:8px;border:1px solid #8884;border-radius:10px;background:transparent;cursor:pointer">Save result as image</button></div><script>(function(){var b=document.getElementById("yjimg");if(!b)return;b.addEventListener("click",function(){var o=document.getElementById("out");var txt=(o&&(o.innerText||o.textContent)||"").trim();if(!txt){b.textContent="(no result yet)";setTimeout(function(){b.textContent="Save result as image";},1500);return;}var lines=txt.split(String.fromCharCode(10)).filter(function(x){return x.trim();}).slice(0,10);var W=1200,P=56,c=document.createElement("canvas");c.width=W;var lh=38,H=P*2+70+lines.length*lh+60;c.height=H;var x=c.getContext("2d");x.fillStyle="#1a1512";x.fillRect(0,0,W,H);x.fillStyle="#f3e9d2";x.font="600 34px system-ui,Segoe UI,Arial";var t=(document.title||"").split("|")[0].trim();x.fillText(t.slice(0,54),P,P+34);x.font="22px ui-monospace,Menlo,Consolas,monospace";x.fillStyle="#e8dcc4";for(var i=0;i<lines.length;i++){x.fillText(lines[i].slice(0,88),P,P+70+i*lh+22);}x.font="600 22px system-ui,Segoe UI,Arial";x.fillStyle="#c9a227";var d=location.hostname.replace("www.","");x.fillText(d,W-P-x.measureText(d).width,H-P+6);c.toBlob(function(bl){var u=URL.createObjectURL(bl),a=document.createElement("a");a.href=u;a.download=(t||"result").replace(/[^a-z0-9]+/gi,"-").toLowerCase()+"-"+d+".png";document.body.appendChild(a);a.click();setTimeout(function(){URL.revokeObjectURL(u);a.remove();},2000);},"image/png");});})();</script><script>(function(){var b=document.getElementById("yjcp");if(!b)return;var t=b.textContent;b.addEventListener("click",function(){var u=location.href.split("#")[0];function d(){b.textContent="Link copied";setTimeout(function(){b.textContent=t;},1800);}if(navigator.clipboard&&navigator.clipboard.writeText){navigator.clipboard.writeText(u).then(d,function(){prompt("",u);});}else{prompt("",u);}});})();</script><div class="yj-home-find"><div class="yj-lgbar"><a href="/" class="yj-lg on">EN</a><a href="/de/" class="yj-lg">DE</a><a href="/ja/" class="yj-lg">JA</a><a href="/es/" class="yj-lg">ES</a><a href="/pt/" class="yj-lg">PT</a></div><section class="yj-find"><h2>Find a tool</h2><input id="yjq" type="search" autocomplete="off" placeholder="Type a product name, format or task"><div id="yjr"></div></section><style>.yj-lgbar{position:absolute;top:14px;right:18px;display:flex;gap:6px;z-index:60}.yj-lg{font:600 12px/1 system-ui,sans-serif;padding:6px 9px;border-radius:4px;text-decoration:none;color:#8b96a3;border:1px solid rgba(140,150,165,.28)}.yj-lg:hover{color:#e8eaed;border-color:rgba(160,175,195,.6)}.yj-lg.on{color:#0e1116;background:#cfd6de;border-color:#cfd6de}.yj-find{max-width:760px;margin:34px auto 8px;padding:0 20px}.yj-find h2{font:600 20px/1.3 system-ui,sans-serif;margin:0 0 10px}#yjq{width:100%;box-sizing:border-box;padding:13px 15px;font:400 15.5px system-ui,sans-serif;border:1px solid rgba(140,150,165,.34);border-radius:8px;background:rgba(255,255,255,.04);color:inherit}#yjq:focus{outline:none;border-color:#7FD1C4}#yjr{margin-top:10px;display:flex;flex-direction:column;gap:6px}#yjr a{display:flex;justify-content:space-between;gap:12px;padding:11px 14px;border:1px solid rgba(140,150,165,.22);border-radius:6px;text-decoration:none;font:400 14.5px/1.4 system-ui,sans-serif;color:inherit}#yjr a:hover{border-color:#7FD1C4}#yjr .pr{font:600 13px system-ui,sans-serif;color:#5fbf9a;white-space:nowrap}</style><script>(function(){var q=document.getElementById("yjq"),r=document.getElementById("yjr"),D=null;function load(){if(D)return Promise.resolve(D);return fetch("/search-index.json").then(function(x){return x.json();}).then(function(j){D=j;return j;}).catch(function(){D=[];return D;});}function esc(t){return String(t).replace(/[&<>"]/g,function(c){return {"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"}[c];});}function run(){var v=q.value.trim().toLowerCase();if(v.length<2){r.innerHTML="";return;}load().then(function(j){var w=v.split(/\s+/),o=[];for(var i=0;i<j.length&&o.length<12;i++){var t=j[i].t.toLowerCase();var ok=true;for(var k=0;k<w.length;k++){if(t.indexOf(w[k])<0){ok=false;break;}}if(ok)o.push(j[i]);}r.innerHTML=o.length?o.map(function(x){return "<a href=\""+esc(x.u)+"\">"+"<span>"+esc(x.t)+"</span>"+(x.p?"<span class=\"pr\">$"+x.p+"</span>":"")+"</a>";}).join(""):"";});}q.addEventListener("input",run);q.addEventListener("focus",load);})();</script></div>
<script data-yj="yj-view-beacon">(function(){try{var p=location.pathname;var k='yjv:'+p;if(sessionStorage.getItem(k))return;sessionStorage.setItem(k,'1');fetch('/api/ev',{method:'POST',keepalive:true,headers:{'Content-Type':'application/json'},body:JSON.stringify({path:p,lang:"en",ref:(document.referrer||''),src:(function(){try{return (new URLSearchParams(location.search).get('ref')||'').replace(/[^a-z_]/g,'').slice(0,12)}catch(e){return ''}})()})}).catch(function(){});}catch(e){}})();</script>
<script data-yj="yj-use-beacon">(function(){try{var o=document.getElementById('out');if(!o)return;var p=location.pathname,k='yju:'+p,sent=false;function fire(){if(sent||sessionStorage.getItem(k))return;if(!(o.textContent||'').replace(/\s+/g,'').length)return;sent=true;sessionStorage.setItem(k,'1');fetch('/api/ev',{method:'POST',keepalive:true,headers:{'Content-Type':'application/json'},body:JSON.stringify({path:p,lang:"en",t:'use',src:(function(){try{return (new URLSearchParams(location.search).get('ref')||'').replace(/[^a-z_]/g,'').slice(0,12)}catch(e){return ''}})()})}).catch(function(){});}var mo=new MutationObserver(function(){fire();if(sent)mo.disconnect();});mo.observe(o,{childList:true,characterData:true,subtree:true});}catch(e){}})();</script>
<section data-yj="yj-facts-v1" style="max-width:920px;margin:48px auto 0;padding:0 20px;font-family:system-ui,sans-serif"><style>.yj-article p{font:400 15.5px/1.7 system-ui,sans-serif;color:#26313b;margin:0 0 12px;max-width:72ch}.yj-article h2,.yj-article h3,.yj-article h4{font:600 17px/1.35 system-ui,sans-serif;color:#1b1f24;margin:22px 0 8px}.yj-article table{border-collapse:collapse;margin:10px 0 16px;font:14px/1.45 system-ui,sans-serif}.yj-article th,.yj-article td{border:1px solid #e2e6ea;padding:6px 10px;text-align:left}.yj-article th{background:#f4f6f8}.yj-article pre{background:#0f1720;color:#e6edf3;padding:12px 14px;border-radius:8px;overflow:auto;font:13px/1.5 ui-monospace,Menlo,monospace}.yj-article code{font:13px ui-monospace,Menlo,monospace;background:#f1f3f5;padding:1px 4px;border-radius:3px}.yj-article pre code{background:none;padding:0}.yj-article ul,.yj-article ol{font:15px/1.6 system-ui,sans-serif;color:#26313b}</style><p style="font:400 13px/1.5 system-ui,sans-serif;color:#6b7680;margin:0 0 14px"><time datetime="2026-09-17">Updated 2026-09-17</time> · ReadyStack</p><script type="application/ld+json">{"@context":"https://schema.org","@type":"WebPage","dateModified":"2026-09-17","author":{"@type":"Organization","name":"ReadyStack"}}</script><h2 style="font:600 21px/1.3 system-ui,sans-serif;margin:0 0 6px;color:#1b1f24">Worked example</h2><p style="font:400 14.5px/1.5 system-ui,sans-serif;margin:0 0 16px;color:#5b6570">Real numbers from this tool, line by line.</p><div class="yj-article"><p><img src="https://getreadystack.com/img/promo/sku53222_result_card.jpg" alt="Kubernetes Removed API Lint" loading="lazy" style="max-width:100%;border-radius:6px"></p>
<p>6 dead apiVersions in one eight-object bundle - that is the first lint pass over one service&#x27;s manifests, for the platform engineer who owns the chart repo on a managed Kubernetes cluster.</p>
<pre><code>_fixtures/dirty.yaml - 11 findings
L2 error Deployment on extensions/v1beta1 was removed in 1.16. Use apps/v1.
L7 error The seccomp alpha annotation stopped being honoured in Kubernetes 1.25. Use securityContext.seccompProfile.
L16 warn beta.kubernetes.io/os is a deprecated node label. Use kubernetes.io/os.
L21 error CronJob on batch/v1beta1 was removed in 1.25. Use batch/v1.
L36 error PodDisruptionBudget on policy/v1beta1 was removed in 1.25. Use policy/v1.
L46 error HorizontalPodAutoscaler on autoscaling/v2beta2 was removed in 1.26. Use autoscaling/v2.
L65 error Ingress on networking.k8s.io/v1beta1 was removed in 1.22. Use networking.k8s.io/v1.
L70 warn The kubernetes.io/ingress.class annotation went away with the v1beta1 Ingress. Use spec.ingressClassName.
L81 error CustomResourceDefinition on apiextensions.k8s.io/v1beta1 was removed in 1.22. Use apiextensions.k8s.io/v1.
L93 error PodSecurityPolicy was removed in 1.25. Move the namespace to Pod Security Admission labels.
L105 error Deployment on apps/v1 has no spec.selector. apps/v1 requires it and never defaults it.</code></pre>
<p>Eleven findings over six distinct removed apiVersions - policy/v1beta1 appears twice, for the PodDisruptionBudget and the PodSecurityPolicy. One service, eight objects - Deployment, CronJob, PodDisruptionBudget, autoscaler, Ingress, CRD, PodSecurityPolicy, worker Deployment - written in an afternoon or asked for in one prompt.</p>
<h3>The YAML is out of date before you read it</h3>
<p>Ask an assistant for a Kubernetes Deployment and there is a fair chance you get apiVersion: extensions/v1beta1 - that is what most published Kubernetes YAML says, and all of it is training data. It stopped being served in 1.16.</p>
<p>Two of those removals carry a trap the apiVersion line does not show: policy/v1beta1 PodDisruptionBudget went in 1.25 and the empty selector flipped meaning - under v1beta1 it matched no pods, under policy/v1 it matches every pod in the namespace; and the v1beta1 Ingress backend block changed shape in 1.22, so editing the apiVersion alone still leaves a rejected manifest.</p>
<h3>Nothing warns you until the cluster moves</h3>
<p>kubectl apply validates against the cluster you are pointing at right now. If that cluster is still on 1.24, a batch/v1beta1 CronJob applies without a word: the manifest is not correct, it is early. A managed control plane upgrades on the provider&#x27;s calendar, not yours. The first hard error arrives partway through a rollout: kubectl applies documents in order, so objects before the dead one are live and objects after it are not. The message itself is calm: no matches for kind &quot;Deployment&quot; in version &quot;extensions/v1beta1&quot;.</p>
<h3>The ones that never error at all</h3>
<p>Three of the fifteen rules catch failures that raise no error at all. The seccomp alpha annotation at L7 stopped being honoured in 1.25 but is still a legal annotation, so the pod applies and runs unconfined. The kubernetes.io/ingress.class annotation at L70 was retired with the v1beta1 Ingress, and a controller watching only IngressClass ignores it. The beta.kubernetes.io/os node selector at L16 can match no node and leave the pod Pending.</p>
<h3>What the lint does</h3>
<p>It reads the manifest open in the editor and runs fifteen rules over every apiVersion and kind, offline - no cluster connection, no kubectl context. Each finding carries the line number, the removal release, and the replacement. On the clean version of the same eight objects it reports zero.</p>
<p>Free in the editor and in the browser: all fifteen rules on one file. The licensed part is scope, not depth - sweep every manifest in the workspace into one dated migration report, ordered by removal version.</p>
<p>Free web version: https://getreadystack.com/tools/k8s-removed-api-lint</p></div><h2 style="font:600 19px/1.3 system-ui,sans-serif;margin:30px 0 10px;color:#1b1f24">15 seconds — what it actually does</h2><video controls muted playsinline preload="none" poster="/img/promo/sku53222_result_card.jpg" style="width:100%;max-width:360px;border-radius:10px;display:block;margin:0 auto"><source src="/media/sku53222_short.mp4" type="video/mp4"></video><h2 style="font:600 21px/1.3 system-ui,sans-serif;margin:34px 0 6px;color:#1b1f24">Questions people ask</h2><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What does Kubernetes Removed API Lint actually do?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">It reads the YAML manifest open in your editor, splits it into documents, and runs 15 rules over every apiVersion and kind. For each hit it names the minor release that removed that apiVersion, quotes the error the API server returns, and gives the replacement. On the sample bundle it reports 11 findings over 6 distinct removed apiVersions.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">Who is this for?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Platform and backend engineers who own Kubernetes manifests or a Helm chart in a repo, and anyone accepting YAML from an assistant. If you have ever run kubectl apply against a freshly upgraded cluster and read no matches for kind in version, you are the reader. It is written for people who find out at rollout.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">Why is a chatbot or kubectl not enough?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">kubectl validates against the cluster you are pointing at, not the one you get after the upgrade, so a manifest that applies today tells you nothing about 1.25. A chatbot is the source of the problem: it was trained on years of published YAML, and most published Kubernetes YAML uses apiVersions that were removed in 1.16 and 1.22.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What is free and what needs a licence key?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Free, with no key: check the manifest open in the editor against all 15 rules and get the removal release and replacement for every hit, with line numbers. That is a complete answer for that file. The key buys scope, not depth: sweep every manifest in the workspace and write one dated migration report ordered by removal version.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What would this cost me otherwise?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">A freelance Kubernetes consultant bills about 120 US dollars an hour, and reading one chart repo for beta apiVersions before an upgrade is most of a day. The other price is the rollback: a removed apiVersion fails partway through kubectl apply, so half the bundle is live and half is not. The licence is 29 US dollars once.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">Why not just ask ChatGPT or another AI chat?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">A general AI chat answers from training data with a cutoff date, cannot read your repository and names no rule version. Kubernetes Removed API Lint checks the file you open against 15 rules, each naming its legal or official basis, from a rule set dated 2026-09-20, and points at the exact line. For a filing, an audit or a client you need that dated result on your own files.</p></details><script type="application/ld+json" data-yj="yj-faq-ld">{"@context": "https://schema.org", "@type": "FAQPage", "mainEntity": [{"@type": "Question", "name": "What does Kubernetes Removed API Lint actually do?", "acceptedAnswer": {"@type": "Answer", "text": "It reads the YAML manifest open in your editor, splits it into documents, and runs 15 rules over every apiVersion and kind. For each hit it names the minor release that removed that apiVersion, quotes the error the API server returns, and gives the replacement. On the sample bundle it reports 11 findings over 6 distinct removed apiVersions."}}, {"@type": "Question", "name": "Who is this for?", "acceptedAnswer": {"@type": "Answer", "text": "Platform and backend engineers who own Kubernetes manifests or a Helm chart in a repo, and anyone accepting YAML from an assistant. If you have ever run kubectl apply against a freshly upgraded cluster and read no matches for kind in version, you are the reader. It is written for people who find out at rollout."}}, {"@type": "Question", "name": "Why is a chatbot or kubectl not enough?", "acceptedAnswer": {"@type": "Answer", "text": "kubectl validates against the cluster you are pointing at, not the one you get after the upgrade, so a manifest that applies today tells you nothing about 1.25. A chatbot is the source of the problem: it was trained on years of published YAML, and most published Kubernetes YAML uses apiVersions that were removed in 1.16 and 1.22."}}, {"@type": "Question", "name": "What is free and what needs a licence key?", "acceptedAnswer": {"@type": "Answer", "text": "Free, with no key: check the manifest open in the editor against all 15 rules and get the removal release and replacement for every hit, with line numbers. That is a complete answer for that file. The key buys scope, not depth: sweep every manifest in the workspace and write one dated migration report ordered by removal version."}}, {"@type": "Question", "name": "What would this cost me otherwise?", "acceptedAnswer": {"@type": "Answer", "text": "A freelance Kubernetes consultant bills about 120 US dollars an hour, and reading one chart repo for beta apiVersions before an upgrade is most of a day. The other price is the rollback: a removed apiVersion fails partway through kubectl apply, so half the bundle is live and half is not. The licence is 29 US dollars once."}}, {"@type": "Question", "name": "Why not just ask ChatGPT or another AI chat?", "acceptedAnswer": {"@type": "Answer", "text": "A general AI chat answers from training data with a cutoff date, cannot read your repository and names no rule version. Kubernetes Removed API Lint checks the file you open against 15 rules, each naming its legal or official basis, from a rule set dated 2026-09-20, and points at the exact line. For a filing, an audit or a client you need that dated result on your own files."}}]}</script><div id="yj-ask" style="margin:36px 0 0;padding:18px 18px 16px;border:1px solid #e2e6ea;border-radius:10px;background:#fbfbfc"><h2 style="font:600 19px/1.3 system-ui,sans-serif;margin:0 0 4px;color:#1b1f24">Ask about this tool</h2><p style="font:400 13.5px/1.5 system-ui,sans-serif;margin:0 0 12px;color:#5b6570">One question, answered by the person who built it. Your email only if you want the answer sent.</p><form onsubmit="return yjAsk(event)" style="display:grid;gap:8px"><textarea name="question" required minlength="10" maxlength="2000" rows="3" placeholder="Your question" style="font:14px/1.5 system-ui,sans-serif;padding:10px;border:1px solid #cfd6dd;border-radius:6px"></textarea><div style="display:flex;gap:8px;flex-wrap:wrap"><input type="email" name="email" placeholder="Email (optional)" style="flex:1;min-width:200px;font:14px system-ui,sans-serif;padding:9px 10px;border:1px solid #cfd6dd;border-radius:6px"><button type="submit" style="font:600 14px system-ui,sans-serif;padding:9px 18px;border:0;border-radius:6px;background:#1b1f24;color:#fff;cursor:pointer">Send</button></div><p id="yj-ask-msg" style="font:13px system-ui,sans-serif;margin:2px 0 0;color:#0a6238;min-height:1em"></p></form><script>function yjAsk(e){e.preventDefault();var f=e.target,m=document.getElementById("yj-ask-msg");m.style.color="#0a6238";fetch(location.origin+"/api/ask",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({sku:53222,slug:"k8s-removed-api-lint",lang:"en",question:f.question.value,email:f.email.value,page:location.pathname})}).then(function(r){if(!r.ok)throw 0;m.textContent="Thanks \u2014 noted. Answers land on this page and, if you left an email, in your inbox.";f.question.value="";}).catch(function(){m.style.color="#a8551f";m.textContent="Could not send. Please try again in a minute.";});return false;}</script></div></section><section class="yj-related" style="max-width:920px;margin:56px auto 40px;padding:0 20px"><h2 style="font:600 21px/1.3 system-ui,sans-serif;margin:0 0 6px;color:#1b1f24">Same job, next tool</h2><p style="font:400 14.5px/1.5 system-ui,sans-serif;margin:0 0 20px;color:#5b6570">Free in the browser. The full version is a one-time licence.</p><div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:14px"><a class="yj-rel-c" href="https://getreadystack.com/tools/cloud-manifest-audit-kit"><span class="yj-rel-t">Manifest Snippets + Removed-API Audit (8 Sets)</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/angular-removed-api-lint"><span class="yj-rel-t">Angular Removed-API Lint</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/cloud-cost-landmine-lint"><span class="yj-rel-t">Cloud Cost Landmine Lint for Terraform, CloudFormation and Kubernetes</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/cron-schedule-lint"><span class="yj-rel-t">Cron Schedule Lint for crontab, Kubernetes CronJob, GitHub Actions, Spring and</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/python-upgrade-removed-api-lint"><span class="yj-rel-t">Python Upgrade Lint: Removed Stdlib 3.12-3.14</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/ai-model-license-lint"><span class="yj-rel-t">AI Model License Lint</span><span class="yj-rel-p">$29</span></a></div><style>.yj-rel-c{display:flex;flex-direction:column;gap:8px;padding:15px 16px;border:1px solid #e2e6ea;border-radius:6px;text-decoration:none;background:#fff}.yj-rel-c:hover{border-color:#b9c2cb;background:#fafbfc}.yj-rel-t{font:500 14.5px/1.42 system-ui,sans-serif;color:#1b1f24}.yj-rel-p{font:600 13px system-ui,sans-serif;color:#0a6238}</style></section></body>
</html>