Apagando Pods, Escalando e Namespaces Presos
Objective
Apagar coisas no Kubernetes normalmente é um pedido, não uma ação: o API server marca o objeto para remoção, controllers e o kubelet fazem o trabalho, e finalizers podem segurar o objeto até alguma limpeza terminar. É por isso que um Pod apagado volta (o controller dele o recria), por que uma remoção forçada pode deixar um processo rodando, e por que um namespace pode ficar dias em Terminating. Este conceito cobre reiniciar e remover Pods com segurança, escalar para zero, e diagnosticar e resolver um namespace preso, com as condições exatas que o Kubernetes reporta.
Use Cases
- Reiniciar um Pod com problema sem mexer no resto do Deployment.
- Parar um workload temporariamente (manutenção, um backup que precisa de um volume parado).
- Limpar um ambiente apagando o namespace dele.
- Destravar um namespace preso em
Terminating.
Deep Dive
Apagando um Pod
plaintextkubectl -n team-a delete pod web-6b48bf5ccc-4cvpp
O que acontece:
- O Pod ganha um
deletionTimestampe entra emTerminating; ele é removido dos endpoints do Service. - O kubelet roda os hooks
preStop, enviaSIGTERM, espera atéterminationGracePeriodSeconds(padrão 30) e depoisSIGKILL. - O objeto Pod desaparece.
Enquanto isso, se o Pod pertence a um ReplicaSet, o ReplicaSet percebe que tem uma réplica a menos e cria uma substituta imediatamente. Apagar um Pod gerenciado é, portanto, o jeito padrão de reiniciar uma instância. Para reiniciar todas de forma ordenada, use kubectl rollout restart em vez de apagar Pods por label (o que remove todos de uma vez).
Remoção forçada
plaintextkubectl delete pod web-xyz --grace-period=0 --force # Warning: Immediate deletion does not wait for confirmation that the running resource has been terminated. # The resource may continue to run on the cluster indefinitely.
Isso remove o objeto Pod da API sem esperar o kubelet confirmar que os containers sumiram. Se o nó está saudável, o kubelet os mata logo depois. Se o nó está inacessível, os processos podem continuar rodando enquanto o Kubernetes já considera o Pod encerrado e sobe um substituto em outro lugar: duas cópias de um singleton, possivelmente no mesmo volume. Use só para Pods presos em Terminating num nó que você sabe que morreu, nunca como um delete mais rápido.
Escalar em vez de apagar
plaintextkubectl -n team-a scale deploy/worker --replicas=0 # para, mantém todo o resto kubectl -n team-a scale deploy/worker --replicas=2 # sobe de novo
Escalar para zero mantém o Deployment, suas ConfigMaps, Secrets, PVCs e o Service; nada precisa ser reaplicado. É a ferramenta certa para janelas de manutenção e para parar a escrita num volume antes de um backup. Note que o próximo kubectl apply de um manifest com replicas: 2 escala de volta. Se um HPA gerencia o Deployment, ele briga com o escalonamento manual; pause ou apague o HPA antes.
Apagando um namespace
plaintextkubectl delete namespace team-b
O namespace vai para Terminating, e o controller de namespaces apaga todos os objetos dentro dele: Deployments, Secrets, PVCs (e, com reclaim policy Delete, os dados), tudo. Só quando o namespace está vazio o objeto Namespace em si é removido. Não há como desfazer.
Preso em Terminating: finalizers
Um finalizer é uma string em metadata.finalizers que diz "algum controller precisa limpar algo antes de este objeto poder sumir". O objeto fica, com um deletionTimestamp, até a lista ficar vazia. Se o controller que deveria remover o finalizer não existe mais (operator desinstalado, webhook quebrado, controller de CRD apagado), o objeto nunca some, e o namespace dele também não.
Verificado no k3s: uma ConfigMap com um finalizer inventado, e depois kubectl delete ns stuck:
plaintext$ kubectl get ns stuck NAME STATUS AGE stuck Terminating 9s $ kubectl get ns stuck -o jsonpath='{.status.conditions}' ... "type":"NamespaceContentRemaining", "message":"Some resources are remaining: configmaps. has 1 resource instances" ... "type":"NamespaceFinalizersRemaining", "message":"Some content in the namespace has finalizers remaining: example.com/cleanup in 1 resource instances"
As conditions dizem exatamente o que procurar. Depois:
plaintext# descubra o que sobrou (o get all não mostra tudo) kubectl api-resources --verbs=list --namespaced -o name \ | xargs -n 1 kubectl get --show-kind --ignore-not-found -n stuck # veja os finalizers do objeto que sobrou kubectl -n stuck get configmap c -o jsonpath='{.metadata.finalizers}'
A correção certa, em ordem de preferência:
- Traga o controller de volta (reinstale o operator) e deixe-o terminar a limpeza. O finalizer pode estar protegendo algo fora do cluster, como um load balancer de nuvem ou um registro de DNS.
- Remova o finalizer do objeto preso, quando você sabe que a limpeza externa é desnecessária ou foi feita à mão:
plaintextkubectl -n stuck patch configmap c --type=json \ -p '[{"op":"remove","path":"/metadata/finalizers"}]'
Verificado: segundos depois desse patch, kubectl get ns stuck retornou NotFound.
Outra causa comum é uma API agregada indisponível (um metrics-server quebrado, um API server de extensão removido): a condition é NamespaceDeletionDiscoveryFailure, e a correção é consertar ou apagar esse APIService (kubectl get apiservice | grep False).
O truque popular de remover o finalizer do próprio Namespace pelo subresource /finalize faz o namespace sumir deixando os objetos órfãos dele no etcd, invisíveis e ainda com os seus finalizers. Corrija os objetos, não o namespace.
Trade-offs
- Apagar um Pod vs rollout restart. Apagar um Pod é pontual e imediato; o
rollout restartsubstitui todos os Pods aos poucos e respeita as configurações de disponibilidade. - Escalar para zero vs apagar. Escalar é reversível e mantém configuração e dados; apagar é definitivo e limpa tudo.
- Remover finalizers à mão vs consertar o controller. Removê-los destrava na hora e pode deixar vazar recursos externos que o finalizer protegia; restaurar o controller é mais lento e limpa direito.