Projeto no GitHub: DavidFerreira21/K8s-OffHours
O K8s OffHours automatiza o shutdown e startup de workloads Kubernetes fora do horário útil, reduzindo desperdício em ambientes não produtivos.
Para gerar economia real de infraestrutura, a solução precisa ser combinada com autoscaling de nós, como Karpenter ou Cluster Autoscaler, permitindo remover workers ociosos de forma dinâmica.
Contexto
Em muitos clusters Kubernetes, ambientes de desenvolvimento, QA e homologação continuam rodando 24x7, mesmo quando não estão sendo utilizados.
O resultado é consumo desnecessário de recursos, com pods e nós ativos fora da janela operacional.
Em ambientes corporativos, é comum que workloads usados apenas no horário comercial consumam muitas horas ociosas ao longo do mês, gerando desperdício significativo de infraestrutura.
O K8s OffHours resolve esse problema automatizando dois momentos:
- shutdown: reduz workloads elegíveis para
replicas=0 - startup: restaura as réplicas no início da janela operacional
Com isso, o time mantém previsibilidade operacional e reduz desperdício em ambientes não produtivos.
Como funciona
O funcionamento é baseado em CronJob + uma engine em Python:
flowchart TD
A[CronJob shutdown/startup] --> B[Engine Python offhours.py]
B --> C[Lê configuração]
B --> D[Descobre alvos por label]
B --> E[Escala deployments]
B --> F[Integra com Argo CD opcional]
B --> G[Salva estado replicas/HPA]
Resumo da arquitetura:
- Dois CronJobs executam em horários diferentes (
shutdownestartup). - O alvo é definido por label
offhours.platform.io/schedule=<nome>. - O escopo pode ser por namespace ou por deployment.
Esse fluxo permite desligar workloads fora da janela operacional e restaurar automaticamente o estado anterior no início do próximo ciclo de trabalho.
Implementação prática
Pré-requisitos
- Cluster Kubernetes com permissão para criar namespace, RBAC, CronJob e ConfigMap.
- Helm instalado na máquina de operação.
kubectlconfigurado para o contexto correto.- Workloads alvo com label
offhours.platform.io/schedule=<nome>. - Autoscaler de nós habilitado para economia real (Karpenter ou Cluster Autoscaler).
Instalação via Helm
helm repo add k8s-offhours https://davidferreira21.github.io/K8s-OffHours
helm repo update
helm upgrade --install offhours k8s-offhours/k8s-offhours \
-n offhours-system --create-namespace \
--set config.scheduleName=default \
--set config.scheduleScope=namespace \
--set argocd.enabled=false
Marcar alvos com label
# Escopo namespace
kubectl label ns <namespace> offhours.platform.io/schedule=default --overwrite
# Escopo deployment
kubectl -n <namespace> label deploy <deployment> offhours.platform.io/schedule=default --overwrite
Como testar
Você pode executar manualmente os jobs para validar o comportamento sem esperar o horário do cron:
kubectl -n offhours-system create job --from=cronjob/offhours-k8s-offhours-shutdown manual-shutdown
kubectl -n offhours-system create job --from=cronjob/offhours-k8s-offhours-startup manual-startup
Validações úteis:
kubectl -n offhours-system get jobs,pods
kubectl -n <namespace> get deploy
kubectl -n <namespace> get deploy <deployment> -o jsonpath='{.spec.replicas}{"\n"}'
Resultado esperado:
- no
manual-shutdown, os Deployments elegíveis devem ir parareplicas=0; - no
manual-startup, as réplicas devem voltar ao valor anterior; - workloads protegidos por annotation devem ser ignorados.
Como visualizar logs
kubectl -n offhours-system logs -f job/manual-shutdown
kubectl -n offhours-system logs -f job/manual-startup
Se preferir acompanhar o pod diretamente:
kubectl -n offhours-system get pods -l job-name=manual-shutdown
kubectl -n offhours-system logs -f <pod-name>
Por que autoscaling de nós é essencial para economia real
O K8s OffHours reduz pods, mas não remove workers sozinho.
Para que os workers sejam removidos dinamicamente, é necessário um autoscaler de nós, como:
- Karpenter (EKS e ambientes compatíveis)
- Cluster Autoscaler
- VMSS Autoscaling (AKS)
- Node Auto Provisioning (GKE)
Fluxo esperado:
- O
shutdowncoloca os Deployments emreplicas=0. - Os nós ficam vazios ou subutilizados.
- O autoscaler identifica capacidade ociosa e deprovisiona workers.
- No
startup, novos pods são criados e o autoscaler reprovisiona nós se necessário.
Sem autoscaling de nós, os pods até param, mas os workers continuam ligados e o custo de infraestrutura permanece alto.
Pontos de atenção para o scale-down funcionar bem:
- habilitar consolidação ou scale-down no autoscaler;
- revisar
PodDisruptionBudget; - avaliar
DaemonSetsque mantêm node ocupado; - ajustar janelas para evitar ciclos frequentes de liga/desliga.
Desafios comuns
Problemas comuns na adoção:
- Label incorreta:
scheduleNamediferente do valor aplicado no namespace/deployment. - GitOps revertendo escala: Argo CD ou Flux pode restaurar réplicas automaticamente.
- HPA reescalando workloads: em alguns cenários, o HPA pode conflitar com
replicas=0. - Falta de autoscaling de nós: reduz pods, mas não reduz custo de infraestrutura.
- Workloads stateful: alguns serviços podem exigir tratamento específico antes de escalar para zero.
Conclusão
O K8s OffHours é uma abordagem simples e eficaz para reduzir custos em ambientes Kubernetes não produtivos sem perder controle operacional.
Exemplo rápido de economia:
- ambiente de desenvolvimento com 4 workers rodando 24x7;
- desligando por 12 horas por dia em dias úteis;
- com autoscaling ativo, workers ociosos podem ser removidos automaticamente.
Principais aprendizados:
- padronizar labels e janelas de execução evita comportamentos inesperados;
- validar com jobs manuais antes de ativar em produção reduz riscos;
- observabilidade por logs é essencial para operação segura;
- OffHours + autoscaling de nós é a combinação que realmente reduz custo de compute.
Mais do que apenas reduzir custo, o K8s OffHours introduz um padrão operacional simples para ambientes não produtivos, alinhando consumo de infraestrutura com a demanda real dos workloads.
