Helm hooks: executando ações durante install/upgrade

1. Introdução aos Helm Hooks

Helm hooks são mecanismos que permitem executar ações em pontos específicos do ciclo de vida de um release. Diferente dos templates regulares, que são processados e aplicados sempre durante uma operação, os hooks são executados apenas em momentos determinados, como antes ou depois de um install, upgrade, rollback ou delete.

A principal motivação para usar hooks é automatizar tarefas que precisam ocorrer em momentos específicos: migrações de banco de dados antes de um upgrade, notificações após uma instalação bem-sucedida, backups antes de uma alteração crítica, ou validações de saúde após um rollout.

O ciclo de vida de um release Helm segue esta sequência: pre-install → instalação → post-installpre-upgrade → upgrade → post-upgradepre-rollback → rollback → post-rollbackpre-delete → delete → post-delete. Em cada um desses pontos, podemos injetar hooks.

A diferença fundamental entre hooks e templates regulares está no controle de execução: templates são sempre aplicados durante a operação principal, enquanto hooks são condicionais e seguem uma ordenação específica definida por pesos.

2. Tipos de Hooks e Seus Gatilhos

Helm oferece 8 tipos de hooks principais, cada um associado a um gatilho específico:

  • pre-install: Executado antes da instalação dos recursos do chart.
  • post-install: Executado após todos os recursos do chart serem instalados com sucesso.
  • pre-upgrade: Executado antes do upgrade, útil para validações ou backup do estado atual.
  • post-upgrade: Executado após o upgrade ser concluído, ideal para notificações ou limpeza.
  • pre-delete: Executado antes da deleção dos recursos, permitindo cleanup ou backup final.
  • post-delete: Executado após a deleção, para ações como remoção de DNS ou notificações.
  • pre-rollback: Executado antes de reverter para uma versão anterior.
  • post-rollback: Executado após o rollback ser concluído.

Além desses, existem dois hooks especiais:

  • test: Usado com helm test para validar que um release está funcionando corretamente.
  • crds: Executado antes de qualquer outro hook para instalar Custom Resource Definitions (CRDs) primeiro.

3. Anatomia de um Recurso de Hook

Um hook é definido através de anotações no metadata do recurso Kubernetes. A anotação principal é helm.sh/hook, que especifica em qual fase o recurso deve ser executado:

apiVersion: batch/v1
kind: Job
metadata:
  name: "{{ .Release.Name }}-db-migration"
  annotations:
    "helm.sh/hook": pre-upgrade
    "helm.sh/hook-weight": "5"
    "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: migration
          image: myapp/migration:1.0
          command: ["/bin/sh", "-c", "echo 'Running migration...'"]

A anotação helm.sh/hook-weight controla a ordem de execução entre hooks do mesmo tipo. Hooks com pesos menores executam primeiro. O peso padrão é 0, e valores negativos são permitidos.

A anotação helm.sh/hook-delete-policy gerencia quando o recurso do hook deve ser removido. As políticas disponíveis são:
- before-hook-creation: Remove o recurso antes de criar um novo hook do mesmo tipo.
- hook-succeeded: Remove o recurso após execução bem-sucedida.
- hook-failed: Remove o recurso após falha na execução.

4. Exemplos Práticos: Hooks em Ação

Job de migração de banco de dados (hook pre-upgrade)

apiVersion: batch/v1
kind: Job
metadata:
  name: "{{ .Release.Name }}-db-migrate"
  annotations:
    "helm.sh/hook": pre-upgrade
    "helm.sh/hook-weight": "10"
    "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
  template:
    spec:
      containers:
        - name: migrate
          image: myapp/migration:{{ .Values.image.tag }}
          env:
            - name: DB_URL
              value: "{{ .Values.database.url }}"
            - name: DB_USER
              valueFrom:
                secretKeyRef:
                  name: {{ .Values.database.secretName }}
                  key: username
          command: ["/migration-tool", "upgrade"]
      restartPolicy: Never
  backoffLimit: 2

Notificação via webhook (hook post-install)

apiVersion: v1
kind: Pod
metadata:
  name: "{{ .Release.Name }}-notify"
  annotations:
    "helm.sh/hook": post-install
    "helm.sh/hook-weight": "5"
    "helm.sh/hook-delete-policy": hook-succeeded
spec:
  containers:
    - name: notify
      image: curlimages/curl:latest
      command:
        - /bin/sh
        - -c
        - |
          curl -X POST -H "Content-Type: application/json" \
          -d '{"release":"{{ .Release.Name }}","status":"installed"}' \
          https://hooks.example.com/helm-notify
  restartPolicy: Never

Backup de estado antes de upgrade (hook pre-upgrade com ConfigMap + Job)

apiVersion: v1
kind: ConfigMap
metadata:
  name: "{{ .Release.Name }}-backup-config"
  annotations:
    "helm.sh/hook": pre-upgrade
    "helm.sh/hook-weight": "1"
    "helm.sh/hook-delete-policy": before-hook-creation
data:
  backup.sh: |
    #!/bin/sh
    kubectl get configmap {{ .Release.Name }}-config -o yaml > /tmp/backup.yaml
    echo "Backup completed"
---
apiVersion: batch/v1
kind: Job
metadata:
  name: "{{ .Release.Name }}-backup"
  annotations:
    "helm.sh/hook": pre-upgrade
    "helm.sh/hook-weight": "2"
    "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
  template:
    spec:
      containers:
        - name: backup
          image: bitnami/kubectl:latest
          command: ["/bin/sh", "/backup/backup.sh"]
          volumeMounts:
            - name: script
              mountPath: /backup
      volumes:
        - name: script
          configMap:
            name: "{{ .Release.Name }}-backup-config"
      restartPolicy: Never

5. Boas Práticas e Armadilhas Comuns

Timeouts e bloqueios: Hooks podem causar timeouts se demorarem muito. Configure backoffLimit e activeDeadlineSeconds nos Jobs para evitar bloqueios eternos.

Recursos efêmeros: Use hook-delete-policy adequadamente para evitar acúmulo de recursos. A combinação before-hook-creation,hook-succeeded é geralmente segura.

Hooks vs. initContainers: Use hooks para ações que devem ocorrer apenas durante operações específicas (instalação, upgrade). Use initContainers para ações que devem ocorrer sempre que um pod for iniciado.

Labels para depuração: Adicione labels como app.kubernetes.io/component: hook nos recursos de hook para facilitar a identificação e limpeza manual.

6. Depuração e Logs de Hooks

Para inspecionar hooks executados em um release:

helm get hooks my-release

Para visualizar logs de jobs/pods de hook específicos:

kubectl logs -l app.kubernetes.io/component=hook --all-containers

Para simular a execução de hooks sem aplicá-los:

helm install my-release ./mychart --dry-run

O dry-run mostra todos os recursos que seriam criados, incluindo hooks, permitindo validar a sintaxe e a lógica antes da execução real.

7. Integração com CI/CD e Estratégias Avançadas

Em pipelines GitOps com ArgoCD ou Flux, hooks de longa duração podem causar problemas. Configure syncPolicy no ArgoCD para respeitar os hooks ou use skipCrds: true se necessário.

Ao combinar hooks com subcharts, lembre-se que cada subchart pode ter seus próprios hooks. A ordem de execução entre hooks de diferentes subcharts segue a mesma lógica de pesos, mas não há garantia de ordenação entre subcharts.

Hooks podem ser combinados com helm test para validação pós-upgrade:

apiVersion: v1
kind: Pod
metadata:
  name: "{{ .Release.Name }}-health-check"
  annotations:
    "helm.sh/hook": test
spec:
  containers:
    - name: health
      image: curlimages/curl:latest
      command:
        - /bin/sh
        - -c
        - |
          curl -f http://{{ .Release.Name }}-service:8080/health
  restartPolicy: Never

Este hook é executado apenas quando helm test my-release é chamado, permitindo validações automatizadas pós-instalação ou pós-upgrade.

Referências