Kami Nemuin Outage Kubernetes yang Gak Pernah Ngasih Alert
Log DaemonSet yang berisik berujung ke certificate control plane yang udah expired 16 hari tanpa alert sama sekali. Ini ceritanya lengkap sama fix-nya.

Awalnya cuma hal receh. Ada pod DaemonSet yang terus terusan lempar error mount yang sama, puluhan ribu kali, dan gak ada yang nyadar soalnya pod di sebelahnya keliatan sehat sehat aja. Gue buka event list-nya cuma karena angkanya kelihatan aneh aja sih.
Dua jam kemudian gue udah mantengin cluster yang control plane-nya diem diem gak bisa self healing selama lebih dari dua minggu, dan gak ada satupun, dashboard, alert, atau on call yang ngasih tau. Jarak antara “aplikasinya masih jalan” sama “cluster-nya beneran sehat” itu yang bikin incident ini layak banget buat ditulis.
Key Takeaways
- Log
FailedMountyang berisik di satu DaemonSet ternyata cuma kosmetik doang. Penyebabnya Docker yang kematian di beberapa node abis reboot. - Ngejar noise itu malah nemuin dead end kedua yang gak berhubungan: Docker Swarm sisa lama yang certificate internalnya udah expired, masih jalan bertahun tahun setelah cluster pindah ke Kubernetes.
- Temuan sebenarnya adalah semua certificate control plane yang di-issue kubeadm di cluster ini udah expired 16 hari sebelumnya, bikin
kube-controller-managersamakube-schedulerdi kedua node diem diem gak bisa autentikasi. - User akhir gak ngerasain downtime sama sekali, soalnya kubelet tetep jalanin pod pod yang udah ke-schedule secara independen. Yang beneran rusak itu self healing-nya: gak ada rollout baru, gak ada scaling HPA, gak ada recovery node otomatis, selama dua minggu lebih.
- Fix-nya
kubeadm certs renew allplus restart hati hati satu node satu node, mulai dari etcd, API server, controller manager, sampe scheduler. - Abis itu kami bikin dua DaemonSet: satu yang jagain Docker tetep enabled di semua node, satu lagi yang ngecek expiry certificate tiap hari dan auto renew file-nya sebelum jadi krisis.
Apa Gejala Pertamanya?
Gejala pertamanya cuma noise doang: event FailedMount yang berulang di satu pod DaemonSet, dengan jumlah lebih dari 90 ribu occurrence di satu pod aja. kubectl describe langsung nunjukin alasannya:
MountVolume.SetUp failed for volume "docker-sock":
hostPath type check failed: /var/run/docker.sock is not a socket fileDaemonSet itu mount /var/run/docker.sock pake hostPath.type: Socket, yang bikin kubelet ngecek dulu apa file-nya beneran socket sebelum mau di-mount. Kalau Docker-nya mati, file-nya bakal gak ada atau bukan socket, dan kubelet bakal retry terus, nge-log event setiap kali nyoba.
Tiga dari enam node kena. SSH ke salah satu node langsung konfirmasi cuma satu baris: docker.service statusnya disabled dan inactive (dead). Docker udah terinstall di semua node buat workflow CI lama, tapi cuma setengahnya yang pernah di-enable biar auto start pas boot. Reboot rutin diem diem matiin Docker di setengah sisanya, dan satu satunya jejak yang keliatan cuma counter event yang terus naik yang udah dianggap biasa aja sama semua orang.
Fix-nya: sudo systemctl enable --now docker di tiap node yang kena. Pod-nya langsung self healing dalam hitungan detik, soalnya retry loop bawaan kubelet itu sendiri udah jadi bentuk self healing begitu kondisi dasarnya clear.
Kenapa Ada Masalah Kedua yang Gak Berhubungan?
Pas lagi ngecek status Docker di semua node, log daemon-nya di semua node nunjukin error yang sama berulang:
level=error msg="failed to renew the certificate" error="...dial tcp <internal-ip>:2377: connect: connection refused"
level=warning msg="the current TLS certificate is expired..."Cluster ini dulu migrasi dari Docker Swarm ke Kubernetes udah lama, dan Swarm mode-nya gak pernah beneran di-decommission. Certificate manager internalnya udah expired, dan tiap node terus nyoba konek ke manager yang udah gak ada. docker node ls konfirmasi state manager swarm-nya emang udah ilang, jadi gak mungkin ada workload Swarm yang masih jalan. Ini murni log noise sama buang buang cycle retry, aman buat dibersihin pake docker swarm leave --force di semua node.
Ini aturan yang perlu diulang: kalau lo decommission orkestrasi layer lama, decommission-nya di mana mana, jangan cuma berhenti make aja. Sistem yang setengah dihapus bakal terus berisik lama setelah semua orang berhenti peduli.
Bikin Watchdog Buat Masalah Pertama
Daripada ngandelin ada yang nyadar lagi soal event spam-nya, kami bikin DaemonSet watchdog kecil. Beda sama pod cleanup aslinya, watchdog ini sama sekali gak mount docker.sock, jadi bisa start dan jalan meskipun Docker-nya lagi mati total. Sebagai gantinya dia pake hostPID: true sama container privileged buat nsenter masuk ke namespace host-nya sendiri dan jalanin systemctl enable --now docker tiap lima menit kalau ketemu Docker-nya inactive.
Satu keputusan desain itu, gak bergantung sama hal yang mau lo perbaiki, ternyata penting lagi nanti di incident yang sama ini.
Gimana DaemonSet Controller-nya Bisa Stuck?
Deploy watchdog baru itu malah ngasilin masalah yang lebih parah dari noise aslinya: DaemonSet-nya stuck di DESIRED: 0, CURRENT: 0 dan gak gerak sama sekali. Field status.observedGeneration-nya gak pernah keisi, tanda kuat kalau DaemonSet controller-nya emang belum pernah proses object itu sama sekali.
Cross check ke DaemonSet lama yang udah jalan lebih dulu konfirmasi hal yang sama: status-nya udah beku berjam jam, ngelaporin ready count basi padahal semua pod-nya beneran sehat. DaemonSet yang observedGeneration-nya gak pernah sama sama generation-nya artinya controller yang ngurusin dia emang gak lagi reconcile apa apa, bukan cuma object yang satu itu doang.
Hipotesis pertama adalah leader process yang stuck, jadi kubectl delete pod dicoba ke pod controller manager-nya. Itu gak ngefek apa apa, soalnya buat static pod, delete lewat API cuma ngilangin mirror object yang di-publish kubelet. Container asli di baliknya gak kesentuh sama sekali dan tetep jalan. Cara yang bener buat maksa restart static pod itu stop container asli-nya di host, misalnya pake crictl stop, dan biarin restartPolicy: Always di kubelet yang bawa dia balik.
Restart beneran kejadian. Reconciliation tetep gak jalan lagi. Itu ngilangin hipotesis “process stuck” sepenuhnya dan ngarah ke sesuatu yang lebih dalam.
Apa Root Cause Sebenarnya?
Log dari controller manager yang baru direstart langsung ngasih jawabannya:
error retrieving resource lock kube-system/kube-controller-manager:
tls: failed to verify certificate: x509: certificate has expired or is not yet valid:
current time ... is after ...kubeadm certs check-expiration di kedua node control plane konfirmasi semuanya. Semua leaf certificate yang dikelola kubeadm, cert API server, cert peer dan client etcd, controller-manager.conf, scheduler.conf, admin.conf, sama front-proxy-client, semuanya expired di hari yang sama, sekitar dua minggu sebelumnya. Cuma root certificate authority-nya, yang valid sepuluh tahun lagi, yang gak kesentuh.
Leaf certificate yang dikelola kubeadm itu certificate TLS yang di-issue pas bootstrap cluster buat autentikasi antar komponen control plane, dan defaultnya cuma valid satu tahun. Cluster ini emang belum pernah di-upgrade sejak pertama kali bootstrap, jadi jam satu tahun itu abis tanpa ada yang me-reset.
Kenapa Gak Ada yang Dapet Alert Selama 16 Hari?
Soalnya tool yang dipake buat akses cluster sehari hari lewat proxy yang dikonfigurasi insecure-skip-tls-verify: true, yang gak pernah validasi certificate API server yang udah expired. Tiap command kubectl yang lewat jalur itu keliatan normal aja.
Pod aplikasi yang udah jalan juga gak kena dampak, soalnya client certificate kubelet sendiri rotate independen dari file file statis yang di-issue kubeadm. Satu satunya yang beneran verifikasi TLS ketat langsung ke certificate yang kena itu kube-controller-manager sama kube-scheduler, dan mereka cuma nge-log error autentikasi ke log stream yang gak ada yang mantengin.
Ini pelajaran inti dari seluruh incident ini: sebuah cluster bisa keliatan sehat total dari semua sudut yang biasa lo cek padahal self healing loop-nya udah mati lebih dari dua minggu.
Gimana Cara Fix Certificate Control Plane Kubernetes yang Expired?
Jawaban singkatnya kubeadm certs renew all, terus restart hati hati satu node satu node buat semua komponen control plane yang kena. Jawaban panjangnya, urutan itu penting dan skip step bisa bikin outage beneran, bukan cuma fix outage yang diem diem.
- Renew certificate di node control plane pertama pake
sudo kubeadm certs renew all. Ini cuma nulis ulang file di disk, jadi belum ngefek ke process yang lagi jalan. - Restart etcd di node itu dulu, soalnya API server bergantung sama dia.
- Di cluster yang cuma punya dua node control plane, ini step yang bahaya: etcd butuh mayoritas member setuju, dan dua node artinya gak ada slack sama sekali. Restart etcd di satu node sementara sisi lainnya certificate-nya masih expired bakal bikin kedua member saling nolak, dan quorum-nya ilang sampe kedua sisi fresh lagi.
- Langsung renew dan restart node kedua dengan cara yang sama. Quorum bakal balik lagi dalam hitungan detik begitu kedua sisi saling percaya certificate barunya lagi.
- Restart API server di tiap node, verifikasi
kubectl get nodesabis tiap restart. - Restart controller manager sama scheduler di tiap node, dan konfirmasi identity leader election-nya beneran berubah, yang buktiin election baru beneran kejadian bukan lease basi yang ikut selamat pas restart.
# Di node yang lagi di-renew
sudo kubeadm certs renew all
# etcd duluan, restartPolicy: Always bikin kubelet recreate otomatis
sudo crictl ps | grep etcd
sudo crictl stop <etcd-container-id>
sudo crictl logs --tail 20 <new-container-id>
# terus API server
sudo crictl ps | grep kube-apiserver
sudo crictl stop <apiserver-container-id>
# terus controller manager sama scheduler
sudo crictl stop <controller-manager-id>
sudo crictl stop <scheduler-id>
kubectl get nodes
kubectl get lease -n kube-system kube-controller-manager -o jsonpath='{.spec.holderIdentity}'Di kasus kami, restart etcd di node pertama emang sempet bikin quorum-nya ilang bentar, nyebabin sekitar satu menit API-nya bener bener gak bisa diakses. Itu satu satunya dampak yang keliatan ke user di seluruh incident ini, dan itu disebabin sama fix-nya sendiri, bukan sama outage 16 hari yang asli.
Apa Control Plane Dua Node Beneran Aman?
Gak juga, dan incident ini demonstrasi yang jelas kenapa. Dengan dua member etcd, kehilangan salah satu artinya kehilangan mayoritas, yang secara teknis bikin dua member lebih buruk dari cuma jalan satu buat urusan quorum. Maintenance apapun yang nyentuh etcd di control plane dua node bakal nyebabin blip availability bentar, titik.
Fix standarnya nambahin node control plane ketiga, yang bikin cluster bisa tahan satu node down bentar tanpa kehilangan quorum. Kalau node ketiga emang gak feasible, hal terbaik selanjutnya adalah dokumentasiin kerapuhan ini dengan jelas, biar tiap renewal certificate, patch, atau reboot ke depannya dikerjain satu node satu node sambil ngecek quorum sebelum nyentuh node kedua.
Apa yang Kami Bangun Biar Gak Kejadian Lagi?
Dua safeguard, keduanya sengaja dibikin sempit scope-nya.
Yang pertama watchdog Docker yang udah dijelasin di atas: ngecek state Docker tiap lima menit di semua node dan re-enable kalau perlu, tanpa bergantung sama Docker-nya sendiri buat bisa jalan.
Yang kedua watchdog expiry certificate yang jalan tiap hari di kedua node control plane. Dia ngecek tiap file certificate yang aktif, nge-log secara jelas di threshold 90, 60, sama 14 hari, dan otomatis renew file-nya (bukan process yang lagi jalan) begitu ada certificate yang sisa waktunya di bawah 60 hari. Renew file certificate yang masih valid itu operasi yang aman dan idempotent menurut dokumentasi kubeadm sendiri, gak pernah bikin invalid sesuatu yang lagi dipercaya. Dia sengaja gak auto restart komponen control plane manapun, soalnya keputusan itu harus mempertimbangkan risiko quorum di atas, dan step itu tetep keputusan manusia.
Versi pertama watchdog ini punya bug beneran: dia nyoba install tool yang kurang di dalam container dan baca certificate lewat host mount, tapi jaringan pod di cluster ini gak punya akses outbound yang reliable buat ambil tool itu. Tiap check gagal dengan error command not found, yang logic fail safe di script-nya sendiri salah baca jadi “certificate lagi mau expired,” nge-trigger alert critical palsu dan call renewal tanpa batas tanpa timeout. Langsung dicabut dalam hitungan menit, dikonfirmasi gak ada kerusakan, dan dibangun ulang biar jalanin check-nya langsung di host lewat pattern privileged yang udah terbukti aman di watchdog Docker, dengan tiap call eksternal dibungkus timeout biar gak ada yang bisa hang lagi.
Pertanyaan yang Sering Diajukan
Berapa lama certificate kubeadm valid secara default? Kubeadm nge-issue sebagian besar leaf certificate control plane dengan validity default satu tahun sejak bootstrap cluster. Root certificate authority-nya jauh lebih lama, biasanya sepuluh tahun, makanya cuma leaf certificate yang expired di incident ini.
Certificate control plane yang expired bikin aplikasi yang lagi jalan down juga? Enggak. Pod yang udah ke-schedule tetep jalan soalnya kubelet ngatur mereka secara independen dan rotate client certificate-nya sendiri sesuai jadwalnya sendiri. Yang rusak itu self healing cluster-nya: deployment baru, keputusan scaling HPA, sama reconciliation kesehatan node, yang semuanya bergantung sama controller manager dan scheduler yang berhasil autentikasi.
Kenapa monitoring gak nangkep ini selama dua minggu lebih? Soalnya jalur akses yang dipake buat pengecekan rutin skip validasi certificate sepenuhnya, jadi tetep jalan normal meskipun certificate aslinya udah expired. Cuma komponen yang ngelakuin verifikasi TLS ketat ke PKI aslinya yang kena, dan error mereka masuk ke log yang gak ada yang mantengin.
Aman gak renew certificate kubeadm di cluster yang lagi live? Renew file certificate-nya sendiri aman dan gak ganggu process yang lagi jalan. Risikonya sepenuhnya ada di restart process-nya setelah itu, apalagi etcd di control plane yang gak punya slack quorum, makanya urutan satu node satu node dengan health check di antara tiap step itu penting.
Upgrade cluster juga renew certificate? Iya. kubeadm upgrade apply sama kubeadm upgrade node renew semua certificate control plane sebagai efek samping secara default. Cluster yang setahun penuh gak pernah di-upgrade kehilangan safety net itu sepenuhnya, dan itu persis yang kejadian di sini.
Penutup
Kebiasaan paling berguna yang keluar dari incident ini bukan tool baru, tapi pertanyaan baru: apa “aplikasinya up” beneran berarti “cluster-nya sehat,” atau bisa jadi self healing-nya udah mati berminggu minggu tanpa ada yang tau? Itu dua klaim yang beda, dan cuma satu yang beneran dicek di sini.
Kalau lo jalanin kubeadm di mana pun, worth banget jalanin sudo kubeadm certs check-expiration sekarang juga, hari ini, daripada nunggu log yang berisik nyuruh lo nyari. Kalau artikel ini kepake, lo mungkin bakal suka juga MySQL Gone Away: HAProxy, Galera, dan Ghost Processes, incident production lain di mana gejala yang keliatan sama penyebab aslinya ternyata dua hal yang beda banget, atau Kenapa Sertifikat SSL Lo Sekarang Cuma 6 Bulan? buat tau lebih lanjut kenapa umur certificate makin dipendekin di industri ini.


