<h2>Hooks no Helm: Automatizando Eventos do Ciclo de Vida</h2>
<p>Hooks são pontos de execução que permitem você rodar scripts ou ações em momentos específicos do ciclo de vida de um release Helm. Diferentemente de um chart tradicional que simplesmente instala recursos, hooks permitem executar tarefas críticas como validações de banco de dados, migrations, backups ou limpeza de recursos órfãos antes ou depois de uma instalação, upgrade ou deletion.</p>
<p>Os hooks disponíveis no Helm são: <code>pre-install</code>, <code>post-install</code>, <code>pre-upgrade</code>, <code>post-upgrade</code>, <code>pre-delete</code>, <code>post-delete</code>, <code>pre-rollback</code> e <code>post-rollback</code>. Cada um representa um momento estratégico. Por exemplo, você pode usar um hook <code>pre-install</code> para criar um namespace customizado ou um <code>post-install</code> para esperar que todos os pods estejam prontos. A chave para entender hooks é reconhecer que eles são manifestos Kubernetes normais anotados com metadados especiais — não são lógica mágica, são recursos que rodam em sequência controlada.</p>
<h3>Estrutura e Anotação de Hooks</h3>
<p>Todo hook no Helm é um recurso Kubernetes convencional (Pod, Job, ConfigMap, etc.) que recebe a anotação <code>helm.sh/hook</code> com o tipo de evento. Você também pode especificar <code>helm.sh/hook-weight</code> para controlar a ordem de execução entre múltiplos hooks do mesmo tipo e <code>helm.sh/hook-delete-policy</code> para definir o que fazer com o recurso após execução.</p>
<p>Veja um exemplo prático de um hook <code>pre-install</code> que valida a conectividade com um banco de dados antes de instalar a aplicação:</p>
<pre><code class="language-yaml">apiVersion: v1
kind: Pod
metadata:
name: {{ include "minha-app.fullname" . }}-db-check
namespace: {{ .Release.Namespace }}
annotations:
helm.sh/hook: pre-install
helm.sh/hook-weight: "0"
helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
spec:
serviceAccountName: {{ include "minha-app.serviceAccountName" . }}
restartPolicy: Never
containers:
- name: db-check
image: postgres:15-alpine
command:
- sh
- -c
- |
echo "Verificando conectividade com banco de dados..."
pg_isready -h {{ .Values.database.host }} -p {{ .Values.database.port }} -U {{ .Values.database.user }}
if [ $? -eq 0 ]; then
echo "Banco de dados está acessível!"
exit 0
else
echo "Falha ao conectar ao banco de dados!"
exit 1
fi</code></pre>
<p>Neste exemplo, o pod executa antes de qualquer instalação (<code>pre-install</code>), com peso 0 (executa primeiro se houver múltiplos hooks), e é deletado automaticamente após sucesso ou falha (<code>hook-succeeded</code> ou <code>hook-failed</code>). Se o comando <code>pg_isready</code> falhar, todo o release falha — é uma validação crítica.</p>
<h3>Hooks com Jobs para Operações Complexas</h3>
<p>Em cenários mais robustos, use Kubernetes Jobs em vez de Pods simples. Jobs oferecem retry automático e melhor controle de execução. Um caso clássico é executar database migrations antes de um upgrade:</p>
<pre><code class="language-yaml">apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "minha-app.fullname" . }}-db-migrate-{{ .Release.Revision }}
namespace: {{ .Release.Namespace }}
annotations:
helm.sh/hook: pre-upgrade
helm.sh/hook-weight: "-5"
helm.sh/hook-delete-policy: before-hook-creation
spec:
backoffLimit: 3
template:
metadata:
name: db-migrate
spec:
serviceAccountName: {{ include "minha-app.serviceAccountName" . }}
restartPolicy: Never
containers:
- name: migrate
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
command: ["/bin/sh", "-c"]
args:
- |
echo "Iniciando migrations..."
./migrations.sh
echo "Migrations concluídas com sucesso!"
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: {{ include "minha-app.fullname" . }}-db-secret
key: url
volumeMounts:
- name: migrations
mountPath: /migrations
volumes:
- name: migrations
configMap:
name: {{ include "minha-app.fullname" . }}-migrations
defaultMode: 0755</code></pre>
<p>Este Job roda antes de qualquer upgrade (<code>pre-upgrade</code>), com peso <code>-5</code> para executar antes de outros hooks com peso neutro. A política <code>before-hook-creation</code> garante que se o Job falhar, um novo será criado na próxima tentativa sem conflito de nome. O <code>backoffLimit: 3</code> permite até 3 tentativas automáticas.</p>
<p>---</p>
<h2>Library Charts: Reutilização de Código em Charts Helm</h2>
<p>Library Charts são charts especiais projetados apenas para fornecer templates, helpers e funções reutilizáveis. Diferentemente de um chart normal que instala recursos, um library chart não instala nada por si só — ele serve como dependência que você importa em outros charts. Isso elimina duplicação massiva quando você tem múltiplos microsserviços com padrões similares: mesmo deployment template, mesmos ConfigMaps, mesmas policies.</p>
<p>A beleza de um library chart é que você define uma vez o padrão correto (labels, annotations, resource limits, security contexts) e todos os seus charts herdam isso automaticamente. Se precisar ajustar a policy de segurança globalmente, faz uma mudança em um local. Sem library charts, você teria que atualizar 50 charts diferentes — e garantir consistência manual é praticamente impossível.</p>
<h3>Estrutura e Criação de um Library Chart</h3>
<p>Um library chart mantém a mesma estrutura de um chart normal, mas a flag <code>type: library</code> no <code>Chart.yaml</code> indica seu propósito. O conteúdo fica em <code>templates/</code> e <code>charts/</code> segue as mesmas convenções. A diferença crucial: você quase nunca coloca recursos em <code>templates/</code> — em vez disso, coloca helpers Helm (funções Go template) que outros charts usam.</p>
<p>Crie um library chart básico com <code>helm create meu-library --type library</code>. Estrutura:</p>
<pre><code>meu-library/
├── Chart.yaml
├── values.yaml
├── templates/
│ ├── _helpers.tpl # Helpers reutilizáveis
│ ├── _deployment.tpl # Template de deployment genérico
│ ├── _service.tpl # Template de service genérico
│ └── _ingress.tpl # Template de ingress genérico
└── README.md</code></pre>
<p>No <code>Chart.yaml</code>:</p>
<pre><code class="language-yaml">apiVersion: v2
name: meu-library
description: Library chart com templates comuns para microsserviços
type: library
version: 1.0.0
appVersion: "1.0"
maintainers:
- name: Seu Time
email: seu-time@empresa.com</code></pre>
<h3>Implementação de Templates Reutilizáveis</h3>
<p>Templates em library charts usam a convenção de underscore (ex: <code>_helpers.tpl</code>) e são incluídos em outros charts, não renderizados diretamente. Vamos criar helpers para labels e um template de deployment genérico:</p>
<pre><code class="language-yaml">{{/ templates/_helpers.tpl /}}
{{/*
Cria o nome completo do release
*/}}
{{- define "meu-library.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Labels padrão para toda a organização
*/}}
{{- define "meu-library.labels" -}}
helm.sh/chart: {{ include "meu-library.chart" . }}
{{ include "meu-library.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
organization: acme-corp
team: {{ .Values.team | default "devops" }}
{{- end }}
{{/*
Selector labels para matchLabels
*/}}
{{- define "meu-library.selectorLabels" -}}
app.kubernetes.io/name: {{ include "meu-library.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Template genérico de Deployment
Uso: {{ include "meu-library.deployment" .Values.deploymentConfig }}
*/}}
{{- define "meu-library.deployment" -}}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "meu-library.fullname" . }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "meu-library.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount | default 2 }}
selector:
matchLabels:
{{- include "meu-library.selectorLabels" . | nindent 6 }}
template:
metadata:
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "8080"
labels:
{{- include "meu-library.selectorLabels" . | nindent 8 }}
spec:
serviceAccountName: {{ include "meu-library.fullname" . }}
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 2000
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.image.pullPolicy | default "IfNotPresent" }}
ports:
- name: http
containerPort: 8080
protocol: TCP
livenessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: http
initialDelaySeconds: 10
periodSeconds: 5
resources:
limits:
cpu: {{ .Values.resources.limits.cpu | default "500m" }} memory: {{ .Values.resources.limits.memory | default "512Mi" }}
requests:
cpu: {{ .Values.resources.requests.cpu | default "250m" }} memory: {{ .Values.resources.requests.memory | default "256Mi" }}
{{- end }}</code></pre>
<h3>Usando Library Charts em Charts Dependentes</h3>
<p>Para usar seu library chart em outro chart, declare-o como dependência. No <code>Chart.yaml</code> do seu chart consumidor:</p>
<pre><code class="language-yaml">apiVersion: v2
name: meu-microsservico
description: Microsserviço usando templates de library
type: application
version: 2.1.0
appVersion: "1.5"
dependencies:
- name: meu-library
version: "1.0.0"
repository: file://../meu-library
alias: lib</code></pre>
<p>Execute <code>helm dependency update</code> para trazer a dependência. Agora no <code>values.yaml</code> do seu microsserviço:</p>
<pre><code class="language-yaml">lib:
team: backend
replicaCount: 3
image:
repository: seu-registry.azurecr.io/seu-microsservico
tag: "1.5.0"
resources:
limits:
cpu: "1000m"
memory: "1Gi"
requests:
cpu: "500m"
memory: "512Mi"</code></pre>
<p>E no <code>templates/deployment.yaml</code> do seu chart:</p>
<pre><code class="language-yaml">{{ include "lib.deployment" . }}</code></pre>
<p>Pronto — você tem um deployment completo, com segurança, probes e labels padrão, sem escrever uma linha de YAML novo.</p>
<p>---</p>
<h2>Chart Museum: Hospedando e Gerenciando Repositórios Helm</h2>
<p>ChartMuseum é um servidor HTTP open-source projetado especificamente para hospedar, indexar e servir charts Helm. Ele abstrai a complexidade de manter um repositório em S3, GCS, Azure Blob ou sistema de arquivos. Sem ChartMuseum, você precisaria gerenciar manualmente arquivos <code>.tgz</code>, atualizar <code>index.yaml</code>, configurar CORS, e lidar com versionamento — tudo manualmente e propenso a erros. ChartMuseum automatiza tudo: você faz push de um chart e ele se registra sozinho.</p>
<p>A razão pela qual ChartMuseum é crítico em ambientes corporativos é simples: você tem dezenas ou centenas de charts internos, múltiplas equipes atualizando-os constantemente, e precisa de versionamento automático, rastreamento de metadados, segurança (autenticação/autorização) e replicação entre ambientes. ChartMuseum fornece tudo isso com uma API clean.</p>
<h3>Instalação e Configuração de ChartMuseum</h3>
<p>A forma mais comum é instalar ChartMuseum via Helm (sim, existe um chart para ChartMuseum). Primeiro, adicione o repositório oficial:</p>
<pre><code class="language-bash">helm repo add chartmuseum https://chartmuseum.github.io/charts
helm repo update</code></pre>
<p>Depois, crie um <code>values-chartmuseum.yaml</code> com sua configuração:</p>
<pre><code class="language-yaml">replicaCount: 2
image:
repository: ghcr.io/chartmuseum/chartmuseum
tag: "v0.15.0"
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 8080
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "8080"
ingress:
enabled: true
className: nginx
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/auth-type: basic
nginx.ingress.kubernetes.io/auth-secret: chartmuseum-auth
nginx.ingress.kubernetes.io/auth-realm: "ChartMuseum"
hosts:
- host: charts.sua-empresa.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: chartmuseum-tls
hosts:
- charts.sua-empresa.com
env:
STORAGE: local
DEBUG: "false"
ALLOW_OVERWRITE: "true"
DISABLE_API: "false"
DISABLE_METRICS: "false"
persistence:
enabled: true
storageClass: standard
accessMode: ReadWriteOnce
size: 50Gi
resources:
limits:
cpu: "1000m"
memory: "1Gi"
requests:
cpu: "500m"
memory: "512Mi"
securityContext:
runAsNonRoot: true
runAsUser: 65534
fsGroup: 65534</code></pre>
<p>Instale com:</p>
<pre><code class="language-bash">helm install chartmuseum chartmuseum/chartmuseum \
--namespace chartmuseum \
--create-namespace \
-f values-chartmuseum.yaml</code></pre>
<h3>Configurando Armazenamento Externo e Autenticação</h3>
<p>Para ambientes produção, use armazenamento externo (S3, Azure Blob, etc.) em vez de local. Modifique o <code>values-chartmuseum.yaml</code>:</p>
<pre><code class="language-yaml">env:
STORAGE: amazon
STORAGE_AMAZON_BUCKET: seu-bucket-s3
STORAGE_AMAZON_PREFIX: charts/
STORAGE_AMAZON_REGION: us-east-1
ALLOW_OVERWRITE: "true"
DISABLE_API: "false"
DISABLE_METRICS: "false"
Autenticação básica via Secret
authSecret:
enabled: true
login: seu-usuario
password: sua-senha-segura
Alternativa: autenticação Bearer Token para CI/CD
bearerAuth:
enabled: true
secret: seu-token-secreto
persistence:
enabled: false # Não precisa com S3</code></pre>
<p>Crie o secret de autenticação antes:</p>
<pre><code class="language-bash">kubectl create secret generic chartmuseum-auth \
--from-literal=username=seu-usuario \
--from-literal=password=$(openssl rand -base64 12) \
-n chartmuseum</code></pre>
<h3>Integração com Pipeline CI/CD</h3>
<p>Configurar seu pipeline para fazer push de charts para ChartMuseum é trivial. Usando um Helm plugin chamado <code>helm-push</code>:</p>
<pre><code class="language-bash"># Instale o plugin
helm plugin install https://github.com/chartmuseum/helm-push.git
No seu pipeline (ex: GitHub Actions)
- name: Push chart to ChartMuseum
run: |
helm cm-push ./seu-chart \
--username=${{ secrets.CHARTMUSEUM_USERNAME }} \
--password=${{ secrets.CHARTMUSEUM_PASSWORD }} \
https://charts.sua-empresa.com</code></pre>
<p>Ou usando curl direto (útil se não quiser plugin):</p>
<pre><code class="language-bash">#!/bin/bash
CHART_NAME="seu-chart"
CHART_VERSION="1.2.3"
CHARTMUSEUM_URL="https://charts.sua-empresa.com"
USERNAME="${CHARTMUSEUM_USERNAME}"
PASSWORD="${CHARTMUSEUM_PASSWORD}"
Empacote o chart
helm package ./seu-chart
Faça upload via API
curl -u "$USERNAME:$PASSWORD" \
--data-binary @${CHART_NAME}-${CHART_VERSION}.tgz \
${CHARTMUSEUM_URL}/api/charts</code></pre>
<h3>Consumindo Charts do ChartMuseum</h3>
<p>Seus usuários/times adicionam seu repositório:</p>
<pre><code class="language-bash">helm repo add minha-empresa https://seu-usuario:sua-senha@charts.sua-empresa.com
helm repo update
Instalam charts como normal
helm install meu-app minha-empresa/meu-microsservico --values values-prod.yaml</code></pre>
<p>Para ambientes que não suportam credenciais na URL (ou por segurança preferem evitar), use um Bearer token:</p>
<pre><code class="language-bash">helm repo add minha-empresa https://charts.sua-empresa.com \
--username=token \
--password=seu-token-secreto
helm repo update
helm search repo minha-empresa/
helm install meu-app minha-empresa/meu-microsservico -n producao</code></pre>
<p>---</p>
<h2>Conclusão</h2>
<p>Neste artigo exploramos três tópicos avançados do Helm que transformam-o de uma ferramenta de empacotamento para uma plataforma robusta de gerenciamento de aplicações Kubernetes. <strong>Primeiro, Hooks permitem orquestrar operações críticas</strong> (validações, migrations, backups) em momentos precisos do ciclo de vida de um release, garantindo que pré-requisitos sejam atendidos antes que recursos sejam criados. <strong>Segundo, Library Charts eliminam duplicação e garantem consistência</strong> ao centralizar templates, helpers e padrões de segurança em um único lugar que todos os charts consomem, transformando manutenção de 50 charts em manutenção de 1. <strong>Terceiro, ChartMuseum profissionaliza seu repositório</strong> com versionamento automático, autenticação, armazenamento escalável e APIs que integram naturalmente em pipelines CI/CD.</p>
<p>Esses três pilares — automação de eventos, reutilização de código e distribuição segura — são o que separa equipes Helm amadoras de times enterprise operando centenas de aplicações com confiabilidade e agilidade.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://helm.sh/docs/topics/charts_hooks/" target="_blank" rel="noopener noreferrer">Documentação oficial do Helm - Hooks</a></li>
<li><a href="https://helm.sh/docs/topics/chart_template_guide/#the-chart-type-field" target="_blank" rel="noopener noreferrer">Documentação oficial do Helm - Chart Types (Library Charts)</a></li>
<li><a href="https://github.com/chartmuseum/chartmuseum" target="_blank" rel="noopener noreferrer">ChartMuseum GitHub Repository</a></li>
<li><a href="https://helm.sh/docs/chart_best_practices/" target="_blank" rel="noopener noreferrer">Helm Chart Best Practices and Patterns</a></li>
<li><a href="https://kubernetes.io/docs/concepts/workloads/controllers/job/#pod-backoff-failure-policy" target="_blank" rel="noopener noreferrer">Kubernetes Official Documentation - Job and Pod Hooks</a></li>
</ul>
<p><!-- FIM --></p>