Kubernetes-certificaten verlopen: kubeadm-PKI vernieuwen

Self-managed Kubernetes-control planes vallen ongeveer een jaar na kubeadm init om, omdat leaf-certificaten standaard 365 dagen meegaan. Dit artikel diagnosticeert de x509-fouten die je ziet als kubeadm-PKI verloopt, loopt door kubeadm certs renew all heen, herstart de static pods, lost het kubelet-client-certificaat op (dat renew all niet meeneemt) en zet je zo neer dat het je geen tweede keer overkomt.

Symptoom: control plane onbereikbaar met x509-fouten

Je doet een routinematige kubectl get nodes en het cluster is weg:

Unable to connect to the server: x509: certificate has expired or is not yet valid:
current time 2026-04-24T10:14:23Z is after 2026-04-23T16:42:11Z

Of, op een control-plane node, draait de kube-apiserver static pod in CrashLoopBackOff en laat crictl logs voor de etcd- of apiserver-container dit zien:

W0424 10:14:23.118233  authentication.go:73] Unable to authenticate the request
due to an error: x509: certificate has expired or is not yet valid

Beheer je je control plane zelf en draait het cluster ongeveer een jaar zonder dat er een kubeadm upgrade overheen is gegaan, dan is dit vrijwel zeker verlopen kubeadm-PKI. De fix is reëel maar mechanisch: vernieuwen, herstarten, verifiëren.

Dit artikel gaat over clusters die met kubeadm zijn opgezet. EKS, GKE en AKS regelen control-plane-certificaatrotatie voor je, dus dit pad geldt daar niet. AKS roteert non-CA-certificaten zelfs automatisch op 80% van hun valid time op clusters die na maart 2022 zijn aangemaakt of geüpgraded. De kubelet-client-certificaten op worker nodes worden door Kubernetes zelf beheerd, los van het kubeadm-pad dat hier centraal staat.

Wat dit eigenlijk betekent

kubeadm zet een zelfondertekende PKI-hiërarchie op onder /etc/kubernetes/pki/. Drie CA's (Kubernetes CA, etcd CA, front-proxy CA), een service-account-keypair, en een waaier aan leaf-certificaten die elke component-naar-component-verbinding in de control plane authenticeren.

Standaard gaan leaf-certificaten 1 jaar mee (8760h), CA's 10 jaar (87600h). Als een leaf-certificaat verloopt, kan de bijhorende component zich niet meer authenticeren bij de API server. En dan ligt de hele control plane plat: de kube-apiserver komt niet meer bij etcd, controllers kunnen geen resources lijsten, de scheduler kan geen pods binden, en kubectl op je werkstation accepteert het servercertificaat niet meer.

De CA's zijn nog wel goed. Alleen de leaves zijn verlopen. Daarom werkt vernieuwen ook zonder dat je het cluster opnieuw moet opbouwen: kubeadm gebruikt de CA-keys die nog op disk staan om verse leaves uit te geven met dezelfde SANs.

Twee misverstanden om uit de weg te ruimen voor je begint:

  • kubeadm certs renew all roteert de CA's niet. Het vernieuwt alleen leaf-certificaten en kubeconfig-client-certs. De CA's behouden hun oorspronkelijke 10 jaar. Loopt een CA zelf richting expiry, dan is dat een ander (en lastiger) traject.
  • Na vernieuwen blijft de API server het oude certificaat gebruiken tot je hem herstart. Static pods op de control plane pikken nieuwe bestanden op disk niet automatisch op. Je moet ze echt even herstarten.

Veelvoorkomende oorzaken, op volgorde van waarschijnlijkheid

  1. Het cluster is met kubeadm opgezet en is bijna een jaar niet geüpgraded. Veruit de vaakst voorkomende oorzaak. kubeadm vernieuwt certificaten automatisch tijdens een kubeadm upgrade apply als ze binnen 180 dagen van expiry zijn, oftewel ongeveer 50% van de leaf-levensduur. Sla genoeg upgradecycli over en je rijdt zo de muur in.
  • Een node was offline tijdens het renewal-window. Liep kubeadm upgrade wel, maar lag een control-plane node op dat moment plat, dan zijn de leaves op die node niet vernieuwd.
  • External CA mode is in gebruik. Vindt kubeadm ca.crt zonder ca.key, dan schakelt het over op external CA mode en weigert het zelf certificaten uit te geven. Je moet dan extern vernieuwen en de bestanden handmatig terugzetten op elke node. kubeadm certs renew all faalt met external CA mode: cannot renew certificates.
  • Kubelet-client-certificaat los verlopen. De kubelet roteert zijn eigen client-certificaat tussen 30% en 10% van de resterende levensduur. Maar als de API server al onbereikbaar was toen dat rotatievenster opende, kon de kubelet niet bij het CSR-endpoint en is het certificaat alsnog verlopen. Dat geeft specifiek system:node x509-fouten en heeft een eigen fix (zie verderop).
  • Klokafwijking op de node. Springt de systeemklok vooruit, dan lijken certificaten "not yet valid" of "expired" terwijl er niets aan de hand is. Check timedatectl status voor je expiry als oorzaak aanneemt.

Diagnose: welke certificaten zijn verlopen

SSH naar een willekeurige control plane node en draai:

sudo kubeadm certs check-expiration

Verwachte output (kolommen ingekort):

CERTIFICATE                EXPIRES                  RESIDUAL TIME   EXTERNALLY MANAGED
admin.conf                 Apr 23, 2026 16:42 UTC          no
apiserver                  Apr 23, 2026 16:42 UTC          no
apiserver-etcd-client      Apr 23, 2026 16:42 UTC          no
apiserver-kubelet-client   Apr 23, 2026 16:42 UTC          no
controller-manager.conf    Apr 23, 2026 16:42 UTC          no
etcd-healthcheck-client    Apr 23, 2026 16:42 UTC          no
etcd-peer                  Apr 23, 2026 16:42 UTC          no
etcd-server                Apr 23, 2026 16:42 UTC          no
front-proxy-client         Apr 23, 2026 16:42 UTC          no
scheduler.conf             Apr 23, 2026 16:42 UTC          no
super-admin.conf           Apr 23, 2026 16:42 UTC          no

CERTIFICATE AUTHORITY   EXPIRES                  RESIDUAL TIME   EXTERNALLY MANAGED
ca                      Apr 21, 2035 16:42 UTC   8y              no
etcd-ca                 Apr 21, 2035 16:42 UTC   8y              no
front-proxy-ca          Apr 21, 2035 16:42 UTC   8y              no

<invalid> in de kolom RESIDUAL TIME bevestigt dat ze verlopen zijn. De CA's onderaan moeten nog jaren te gaan hebben. super-admin.conf zie je op clusters die met kubeadm 1.29 of later zijn opgezet of bijgewerkt; dat bestand is daar geïntroduceerd als break-glass-credential los van de dagelijkse admin.conf.

Kun je het cluster niet bereiken vanaf je werkstation, controleer dan ook even of de nodeklok klopt:

timedatectl status

Je wilt System clock synchronized: yes. Een node die maandenlang voorop is gelopen toont "verlopen" certificaten die in werkelijkheid prima zijn.

Voor een individueel certificaatbestand (handig als check-expiration zelf om wat voor reden ook faalt):

sudo openssl x509 -in /etc/kubernetes/pki/apiserver.crt -noout -dates

Output:

notBefore=Apr 23, 2025 16:42:11 GMT
notAfter=Apr 23, 2026 16:42:11 GMT

Stap 1: backup van de PKI-directory en admin.conf

Vernieuwen overschrijft bestanden ter plekke. Voor je iets onomkeerbaars doet, snapshot eerst de huidige state op elke control-plane node:

sudo cp -a /etc/kubernetes/pki /etc/kubernetes/pki.bak-$(date +%Y%m%d)
sudo cp -a /etc/kubernetes/admin.conf /etc/kubernetes/admin.conf.bak-$(date +%Y%m%d)
sudo cp -a /etc/kubernetes/controller-manager.conf /etc/kubernetes/controller-manager.conf.bak-$(date +%Y%m%d)
sudo cp -a /etc/kubernetes/scheduler.conf /etc/kubernetes/scheduler.conf.bak-$(date +%Y%m%d)
sudo cp -a /etc/kubernetes/kubelet.conf /etc/kubernetes/kubelet.conf.bak-$(date +%Y%m%d)

Houd je etcd-snapshots bij (en dat zou je moeten doen, zie Kubernetes etcd: backup, restore en disaster recovery), maak dan ook nu even een verse. Vernieuwen raakt etcd-data niet aan, maar als er ergens in deze procedure wat misgaat, wil je een recente snapshot achter de hand hebben.

Stap 2: vernieuwen met kubeadm certs renew all

Draai dit op de eerste control-plane node:

sudo kubeadm certs renew all

Verwachte output:

[renew] Reading configuration from the cluster...
[renew] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -o yaml'

certificate embedded in the kubeconfig file for the admin to use and for kubeadm itself renewed
certificate for serving the Kubernetes API renewed
certificate the apiserver uses to access etcd renewed
certificate for the API server to connect to kubelet renewed
certificate embedded in the kubeconfig file for the controller manager to use renewed
certificate for liveness probes to healthcheck etcd renewed
certificate for etcd nodes to communicate with each other renewed
certificate for serving etcd renewed
certificate for the front proxy client renewed
certificate embedded in the kubeconfig file for the scheduler manager to use renewed
certificate embedded in the kubeconfig file for the super-admin renewed

Done renewing certificates. You must restart the kube-apiserver, kube-controller-manager,
kube-scheduler and etcd, so that they can use the new certificates.

Het commando draait onvoorwaardelijk, ongeacht expiry, en hergebruikt de SANs van de bestaande certificaten. De kubeadm-reference documenteert elk individueel sub-commando voor als je ooit één enkel certificaat wil vernieuwen (kubeadm certs renew apiserver, etc.).

Bij HA-clusters herhaal je dit commando op elke control-plane node. Iedere node heeft zijn eigen kopie van de PKI; vernieuwen op één node propageert niet.

Faalt kubeadm certs renew all met external CA mode: cannot renew certificates, dan zit je niet in kubeadm-managed mode. De CA-private-key staat bewust niet op disk. Je moet vervangende leaf-certificaten uitgeven vanuit je externe CA en die handmatig in /etc/kubernetes/pki/ zetten. kubeadm helpt je hier niet verder mee.

Stap 3: control-plane-componenten herstarten

De static pods draaien nog met de oude certificaten in geheugen. Ze pikken de nieuwe bestanden op disk pas op als ze herstarten. De cleane manier om dat af te dwingen: elk manifest uit /etc/kubernetes/manifests/ halen en weer terugzetten. De kubelet polt die directory elke 20 seconden (fileCheckFrequency) en reconcilt op basis van welke manifests hij ziet.

# Verplaats alle vier de control-plane manifests
sudo mv /etc/kubernetes/manifests/kube-apiserver.yaml /tmp/
sudo mv /etc/kubernetes/manifests/kube-controller-manager.yaml /tmp/
sudo mv /etc/kubernetes/manifests/kube-scheduler.yaml /tmp/
sudo mv /etc/kubernetes/manifests/etcd.yaml /tmp/

# Wacht ~25 seconden, controleer dan dat de containers gestopt zijn
sleep 25
sudo crictl ps | grep -E 'apiserver|controller-manager|scheduler|etcd_'

Je hoort niets terug te krijgen. Staan er nog containers te draaien, wacht dan nog 10 seconden en check opnieuw. De kubelet heeft soms een extra polcyclus nodig.

Daarna terugzetten:

sudo mv /tmp/etcd.yaml /etc/kubernetes/manifests/
sudo mv /tmp/kube-apiserver.yaml /etc/kubernetes/manifests/
sudo mv /tmp/kube-controller-manager.yaml /etc/kubernetes/manifests/
sudo mv /tmp/kube-scheduler.yaml /etc/kubernetes/manifests/

Etcd eerst weer omhoog. De API server is daarvan afhankelijk. Wacht 30 tot 60 seconden tot de kubelet alle vier de containers opnieuw heeft aangemaakt.

Eén opmerking specifiek over etcd: sinds etcd v3.2.0 worden TLS-certificaten herladen bij elke client-connectie, dus een al draaiende etcd pikt de nieuwe server.crt en peer.crt op zodra er nieuwe verbindingen binnenkomen. In theorie hoef je etcd voor de certificaatwissel dus niet te herstarten. In de praktijk is een schone restart simpeler dan uitvogelen welke bestaande connecties welk certificaat gebruiken, en geeft het je een duidelijk before/after voor de verificatie.

Tot slot: herstart de kubelet zelf zodat hij de vernieuwde kubelet.conf inleest (met een kanttekening; zie de speciale gevallen verderop):

sudo systemctl restart kubelet

Stap 4: verifieer de vernieuwing en herstel clustertoegang

Bevestig op de control-plane node de nieuwe expiratiedata:

sudo kubeadm certs check-expiration

Elk leaf-certificaat hoort nu ongeveer een jaar resterend te tonen.

Controleer dat de static pods terug zijn:

sudo crictl ps | grep -E 'apiserver|controller-manager|scheduler|etcd_'

Je hoort alle vier de containers te zien.

Vanaf je werkstation refereert je lokale kubeconfig nog naar het oude client-certificaat. Zelfs als de API server gezond is, authenticeert kubectl niet. Refresh het dus vanuit de vernieuwde admin.conf:

# Op de control-plane node
sudo cp /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

Of, vanaf je werkstation, kopieer het bestand via SSH terug en overschrijf ~/.kube/config. Dit is de stap waarop mensen het vaakst struikelen: de API server is goed, etcd is goed, kubectl roept x509: certificate has expired, en de oorzaak is gewoon een muffe lokale kubeconfig. Op het Kubernetes-discussionforum staan meerdere threads over precies deze verwarring.

Je weet dat het herstel gelukt is als:

kubectl get nodes
kubectl get pods --all-namespaces

hun normale output geven zonder x509-fouten en alle nodes Ready melden.

Speciaal geval: etcd-certificaten (eigen renewal-pad; external etcd-caveat)

kubeadm certs renew all dekt etcd-certificaten af als etcd als kubeadm-managed static pod draait (de standaard stacked etcd-topologie). De vier etcd-gerelateerde certificaten zijn:

Certificaat Wat het doet
etcd-server Server cert dat etcd presenteert aan clients op poort 2379
etcd-peer Server- en client-cert voor verkeer tussen members op poort 2380
etcd-healthcheck-client Client-cert voor de liveness probe van etcd
apiserver-etcd-client Client-cert dat de kube-apiserver gebruikt om met etcd te praten

Draai je etcd extern (op aparte VM's of een eigen cluster, niet stacked op de control-plane nodes), dan kan kubeadm de etcd-certificaten niet voor je vernieuwen. Je beheert etcd's PKI dan via het proces dat het oorspronkelijk heeft opgezet (vaak een aparte etcd-managed CA, of handmatig uitgegeven certificaten). Aan de kubeadm-kant valt alleen apiserver-etcd-client binnen de scope, want die is wel met de etcd-CA ondertekend maar staat in /etc/kubernetes/pki/ voor gebruik door de API server. De rest woont bij etcd.

Bij stacked-etcd-kubeadm-setups (wat kubeadm init standaard oplevert) heb je geen aparte procedure nodig. kubeadm certs renew all dekt alle vier af, en de static-pod-restart in stap 3 pakt ze op.

Voor het bredere operationele plaatje rond etcd, inclusief snapshots en disaster recovery, zie Kubernetes etcd: backup, restore en disaster recovery.

Speciaal geval: kubelet-client-certificaat (auto-rotatie, niet in renew all)

Het kubelet-client-certificaat (/var/lib/kubelet/pki/kubelet-client-current.pem) wordt niet beheerd door kubeadm certs renew all. Het wordt door de kubelet zelf geroteerd, tussen 30% en 10% van de resterende levensduur, via de RotateKubeletClientCertificate feature gate (stable en standaard aan sinds Kubernetes 1.19). De kubelet plaatst een CertificateSigningRequest bij de API server, de controller-manager keurt die automatisch goed, en de kubelet schrijft het nieuwe certificaat naar disk.

Eén edge case waar dit omvalt: als de API server al onbereikbaar was toen het rotatievenster opende, kan de kubelet het CSR-endpoint niet bereiken en verloopt het certificaat alsnog. Je ziet dit op een node die tijdens het rotatievenster offline was, of op elke node als de API server zelf platlag (precies het scenario uit dit artikel).

kubelet.conf is ook apart geregeld. De historische kubeadm-issues #1361 en #2185 leggen uit waarom: het embedded client-certificaat in kubelet.conf zou worden vervangen door het auto-geroteerde certificaat dat elders staat (/var/lib/kubelet/pki/), dus kubeadm raakt kubelet.conf bewust niet aan tijdens certs renew all.

Heb je een verlopen kubelet-client-certificaat en is de auto-rotatie al gefaald, dan is de herstelprocedure:

# Op de getroffen node, regenereer kubelet.conf vanuit de CA
sudo kubeadm kubeconfig user \
  --org system:nodes \
  --client-name system:node:$(hostname) \
  > /tmp/kubelet.conf

# Vervang de bestaande kubelet.conf
sudo mv /etc/kubernetes/kubelet.conf /etc/kubernetes/kubelet.conf.expired
sudo mv /tmp/kubelet.conf /etc/kubernetes/kubelet.conf

# Verwijder de oude auto-geroteerde certs zodat de kubelet vers bootstrapt
sudo rm /var/lib/kubelet/pki/kubelet-client-*.pem

# Herstart de kubelet
sudo systemctl restart kubelet

Na de herstart gebruikt de kubelet kubelet.conf om te bootstrappen, plaatst een CSR, en de controller-manager (die nu zelf weer een vers certificaat heeft en weer kan signen) keurt die automatisch goed. Een nieuwe kubelet-client-current.pem verschijnt binnen een paar seconden in /var/lib/kubelet/pki/. Volg de kubelet-logs: journalctl -u kubelet -f.

Je weet dat het werkt als de node terugkeert naar Ready:

kubectl get node $(hostname)

Wanneer escaleren

Brengt deze procedure het cluster niet terug, verzamel dan dit voor je hulp inschakelt:

  • Output van sudo kubeadm certs check-expiration van elke getroffen node
  • Output van kubectl version (specifiek de server-build, ook als de client het cluster niet kan bereiken)
  • Output van timedatectl status van elke getroffen node (klokafwijking is een stille moordenaar)
  • De exacte x509-foutregel uit crictl logs voor de kube-apiserver- en etcd-containers
  • De eerste 50 regels van journalctl -u kubelet --since "30 minutes ago"
  • Of het cluster external CA mode gebruikt (check of /etc/kubernetes/pki/ca.key bestaat)
  • Of etcd stacked of extern draait
  • Met welke kubeadm-versie het cluster oorspronkelijk is gebootstrapt (vaak zichtbaar in de image tag history van /etc/kubernetes/manifests/kube-apiserver.yaml)
  • Of /var/lib/kubelet/pki/kubelet-client-current.pem een symlink is en waar die naar wijst
  • Of de werkstation-kubeconfig al ververst is vanuit de vernieuwde admin.conf

Multi-node HA-uitval (waarbij meer dan één control-plane node geraakt is en quorum onduidelijk is) is het meest waarschijnlijke scenario waarin escaleren ook echt nodig is. Single-node-vernieuwen is mechanisch; HA-vernieuwen waarbij node 1 lukte maar node 2 tijdens de restart zijn etcd-quorum kwijtraakte, dáár wordt het interessant.

Voorkomen dat het opnieuw gebeurt

Het doel: het symptoom uit dit artikel nooit meer zien.

  • Draai kubeadm upgrade minstens elke 6 maanden. Daarmee worden alle certificaten binnen het 180-dagen-window automatisch vernieuwd. Het is ook een veiligere cadans om op een ondersteunde Kubernetes-versie te blijven, want een Kubernetes minor release wordt ongeveer 14 maanden ondersteund.
  • Monitor certificate expiry actief. Een simpele Prometheus-alert op de metric apiserver_client_certificate_expiration_seconds, of een CronJob die kubeadm certs check-expiration draait en mailt als residual <60d is, vangt dit voor het een outage wordt. Alert op 90 dagen, page op 30 dagen.
  • Kun je naar managed Kubernetes, doe dat dan. De operationele kosten van een eigen kubeadm-cluster runnen omvatten dit artikel. EKS, GKE en AKS roteren control-plane-certificaten voor je. AKS roteert non-CA-certificaten op 80% van de valid time op clusters vanaf maart 2022. GKE roteert etcd-certificaten 6 maanden voor expiry automatisch.
  • Voor workload-TLS, gebruik cert-manager. kubeadm's PKI is voor de interne control plane van het cluster; het is niet de juiste tool om certificaten aan je applicaties uit te geven. Zie daarvoor Kubernetes TLS met cert-manager: geautomatiseerd certificaatbeheer.
  • Verleng de standaardlevensduur als je operationele cadans lang is. kubeadm 1.31 en later ondersteunen certificateValidityPeriod in ClusterConfiguration (defaults: 8760h voor leaves, 87600h voor CA's). Een leaf-levensduur van 3 jaar is ongebruikelijk maar niet verkeerd als je weet dat je cadans niet anders kan en je accepteert dat de blast radius bij een gecompromitteerd certificaat groter is.

Het doel is niet nul certificaatwerk. Het doel is geen verrassingen. Een vernieuwing van vijf minuten in een gepland upgradevenster verslaat een x509-outage om vier uur 's nachts op elke operationele meetlat.

Terugkerende server- of deploymentproblemen?

Ik help teams productie betrouwbaar maken met CI/CD, Kubernetes en cloud—zodat fixes blijven en deploys geen stress meer zijn.

Bekijk DevOps consultancy

Doorzoek deze site

Begin met typen om te zoeken, of blader door de kennisbank en blog.