Helm
Quando mencionado projeto, projeto atual ou meditation-timer; me refiro ao projeto usado como teste e acessivel na URL https://gitlab.setic.ufsc.br/marcosxavier/meditation-timer
Índice
- O que é o Helm e por que existe
- Os três conceitos fundamentais
- A estrutura de um Chart
- Como o Helm se encaixa no que já existe
- O problema que o Helm resolve no projeto atual
- Como ficaria um projeto com Helm
- Controle de acesso por perfil de usuário
- Como funciona ter vários Charts
- Quem usa qual Chart e como isso é garantido
- Referência de comandos Helm
1. O que é o Helm e por que existe
O Helm é um gerenciador de pacotes para Kubernetes. A analogia direta é o apt do Debian: assim como apt install nginx sabe que o nginx está instalado, em qual versão, e consegue desinstalar tudo de uma vez — o Helm sabe que uma aplicação está instalada no cluster, em qual versão, quais recursos ela criou, e consegue fazer rollback para a versão anterior com um único comando.
Sem o Helm, o deploy de uma aplicação no Kubernetes é feito com kubectl apply -f pasta/, que simplesmente empurra arquivos YAML estáticos para o cluster. Isso funciona, mas tem limitações sérias:
- Os arquivos YAML têm valores hardcoded (nome da imagem, hostname, número de réplicas).
- Para mudar qualquer valor é preciso editar o arquivo antes de aplicar.
- O Kubernetes não guarda histórico de versões — se um deploy quebrar, voltar atrás significa re-executar o pipeline da versão anterior.
- Para remover todos os recursos de uma aplicação é preciso saber exatamente quais foram criados.
O Helm resolve todos esses pontos de uma vez.
2. Os três conceitos fundamentais
Chart
É o pacote — o equivalente a um pacote .deb do apt. Contém os templates dos arquivos YAML da aplicação, os valores padrão, e as regras de validação. Um Chart é criado e mantido por quem entende de infraestrutura (a SeTIC), não pelo desenvolvedor da aplicação.
Values
São os parâmetros configuráveis do Chart — o equivalente às opções de configuração de um pacote. O desenvolvedor ou aluno preenche um arquivo values.yaml com os valores específicos do projeto dele (nome da imagem, hostname, número de réplicas). O Chart usa esses valores para gerar os YAMLs finais que vão para o cluster.
Release
É uma instalação específica de um Chart no cluster. Quando o Chart web-app é instalado para o projeto minha-aplicacao, isso cria uma Release chamada minha-aplicacao. O Helm rastreia o histórico completo dessa Release: qual versão do Chart foi usada, quais values foram passados, se o deploy teve sucesso ou falhou.
Chart (template) + values.yaml (parâmetros) = Release (instalação rastreada no cluster)
3. A estrutura de um Chart
Um Chart é um diretório com a seguinte estrutura:
meu-chart/
├── Chart.yaml ← identidade do chart (nome, versão)
├── values.yaml ← valores padrão de todos os parâmetros
├── values.schema.json ← regras de validação dos parâmetros
└── templates/
├── deployment.yaml ← template do Deployment
├── service.yaml ← template do Service
└── ingress.yaml ← template do Ingress
Chart.yaml — identidade do Chart
apiVersion: v2
name: student-web-app
description: Chart padrão para projetos de alunos — UFSC SeTIC
version: 1.0.0 # versão do chart em si
appVersion: "main" # versão padrão da aplicação
values.yaml — os parâmetros que o usuário pode preencher
image:
repository: "" # caminho da imagem no registry (obrigatório)
tag: main # tag da imagem
replicaCount: 1 # número de réplicas
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 128Mi
ingress:
host: "" # hostname da aplicação (obrigatório)
templates/deployment.yaml — o YAML com variáveis
Em vez de valores fixos, o template usa {{ .Values.* }} para referenciar o values.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }} # ← nome da Release, definido no helm install
spec:
replicas: {{ .Values.replicaCount }}
template:
spec:
# SecurityContext fixo — não é um parâmetro, sempre igual
securityContext:
runAsNonRoot: true
runAsUser: 101
runAsGroup: 101
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: Always # ← fixo no template, não é parâmetro
resources:
{{- toYaml .Values.resources | nindent 12 }}
securityContext:
allowPrivilegeEscalation: false # ← fixo, não é parâmetro
capabilities:
drop: [ALL] # ← fixo, não é parâmetro
Perceba a diferença: replicaCount, image.repository, image.tag, e resources são variáveis que o usuário controla. securityContext, allowPrivilegeEscalation, e imagePullPolicy são valores fixos no template — o usuário não tem como alterá-los porque eles simplesmente não existem no values.yaml.
4. Como o Helm se encaixa projeto do meditation-timer
O Helm não substitui nenhuma das peças do cenário atual (somente GitLab-managed Kubernetes resources)— ele se posiciona como uma nova camada entre o pipeline e o cluster.
Antes (sem Helm):
Pipeline GitLab
└── kubectl apply -f k8s/ → Cluster (manifests estáticos aplicados diretamente)
Depois (com Helm):
Pipeline GitLab
└── helm upgrade --install → Chart (template parametrizado) → Cluster
O que não muda:
- GitLab-managed resources continua criando namespace e RoleBindings automaticamente quando o environment inicia. O Helm não interfere nisso.
- GitLab Agent continua sendo o mecanismo de autenticação — o
helmusa o mesmo kubeconfig que okubectl. - Namespace por projeto (
gl-<project_id>) continua sendo o padrão. - Registry de imagens (
registry.setic.ufsc.br) continua o mesmo.
O que muda é só a forma como os manifests da aplicação chegam no cluster: em vez de YAMLs estáticos com kubectl apply, são templates parametrizados com helm upgrade.
5. O problema que o Helm resolve no projeto atual
Um projeto típico tem hoje um problema concreto: a tag da imagem está hardcoded no deployment.yaml:
# k8s/deployment.yaml — hoje
image: registry.setic.ufsc.br/<grupo>/<projeto>:main
Por isso o pipeline precisa de dois comandos para fazer o que deveria ser um:
# .gitlab-ci.yml — hoje
- kubectl apply -f k8s/ --namespace="$KUBE_NAMESPACE"
# ↑ aplica o YAML com a imagem hardcoded (tag errada)
- kubectl set image deployment/"$APP_NAME" "$APP_CONTAINER=$REGISTRY_IMAGE" --namespace="$KUBE_NAMESPACE"
# ↑ corrige a tag em cima do que foi aplicado
Com Helm, o deployment.yaml recebe {{ .Values.image.tag }} no lugar da tag hardcoded, e o pipeline passa o valor correto no momento do deploy:
# .gitlab-ci.yml — com Helm
- helm upgrade --install minha-aplicacao ./chart \
--namespace "$KUBE_NAMESPACE" \
--set image.tag="$CI_COMMIT_REF_SLUG" \
--wait --timeout 3m
Um único comando. O YAML sai do template já com a tag correta. O --wait aguarda todos os pods ficarem prontos e substitui o kubectl rollout status que existia separado.
O job de parada também simplifica:
# hoje
- kubectl scale deployment "$APP_NAME" --replicas=0 --namespace="$KUBE_NAMESPACE"
# com Helm
- helm upgrade minha-aplicacao ./chart \
--namespace "$KUBE_NAMESPACE" \
--set replicaCount=0 \
--reuse-values
O que o Helm adiciona que o kubectl apply não tem
Histórico de versões rastreado no cluster:
helm history minha-aplicacao -n gl-<project_id>
# REVISION STATUS CHART DESCRIPTION
# 1 superseded web-app-1.0.0 Install complete
# 2 superseded web-app-1.0.0 Upgrade complete
# 3 deployed web-app-1.0.0 Upgrade complete
Rollback com um comando:
helm rollback minha-aplicacao 2 -n gl-<project_id>
# volta exatamente para o estado do deploy 2, sem re-executar pipeline
Remoção limpa de todos os recursos:
helm uninstall minha-aplicacao -n gl-<project_id>
# remove Deployment + Service + Ingress de uma vez, sem precisar listar
Visualizar o diff antes de aplicar (com o plugin helm-diff):
helm diff upgrade minha-aplicacao ./chart \
--namespace gl-<project_id> \
--set image.tag="nova-tag"
# mostra exatamente o que vai mudar antes de mudar
6. Como ficaria um projeto com Helm
Estrutura do repositório
minha-aplicacao/
├── Dockerfile
├── src/
├── .gitlab-ci.yml
├── values.yaml ← parâmetros específicos deste projeto
└── chart/ ← Chart (poderia vir de um repositório central)
├── Chart.yaml
├── values.yaml
├── values.schema.json
└── templates/
├── deployment.yaml
├── service.yaml
└── ingress.yaml
values.yaml do projeto
image:
repository: registry.setic.ufsc.br/<grupo>/<projeto>
tag: main
replicaCount: 1
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 128Mi
ingress:
host: minha-aplicacao.setic.ufsc.br
.gitlab-ci.yml com Helm
deploy:
image: alpine/helm:latest
script:
- kubectl config use-context "$KUBE_CONTEXT"
- helm upgrade --install minha-aplicacao ./chart \
--namespace "$KUBE_NAMESPACE" \
--values values.yaml \
--set image.tag="$CI_COMMIT_REF_SLUG" \
--wait --timeout 3m
stop-producao:
script:
- kubectl config use-context "$KUBE_CONTEXT"
- helm upgrade minha-aplicacao ./chart \
--namespace "$KUBE_NAMESPACE" \
--set replicaCount=0 \
--reuse-values
when: manual
7. Controle de acesso por perfil de usuário
Esta é a principal razão para ter Charts diferentes para perfis diferentes. O Helm permite três níveis de controle sobre o que um usuário pode configurar:
Nível 1 — Bloqueado: o usuário não pode alterar de jeito nenhum
Esses valores são hardcoded diretamente no template e não aparecem no values.yaml. O usuário nem sabe que existem como parâmetro porque simplesmente não há como passá-los.
Exemplos do que bloquear:
| O que | Como fica no template |
|---|---|
allowPrivilegeEscalation |
Sempre false, sem variável |
capabilities.drop |
Sempre [ALL], sem variável |
seccompProfile |
Sempre RuntimeDefault, sem variável |
ingressClassName |
Sempre nginx, sem variável |
imagePullPolicy |
Sempre Always, sem variável |
| Prefixo do registry | Template concatena registry.setic.ufsc.br/ com o valor do usuário |
# templates/deployment.yaml — valores bloqueados
securityContext:
runAsNonRoot: true # bloqueado — não está no values.yaml
allowPrivilegeEscalation: false # bloqueado
capabilities:
drop: [ALL] # bloqueado
image: "registry.setic.ufsc.br/{{ .Values.image.repository }}:{{ .Values.image.tag }}"
# ↑ prefixo fixo ↑ usuário só fornece esta parte
Nível 2 — Liberado com restrição: o usuário pode alterar dentro de limites
Esses valores aparecem no values.yaml mas o values.schema.json define regras rígidas. Se o usuário passar um valor fora das regras, o Helm rejeita antes de qualquer coisa chegar no cluster, com mensagem de erro clara.
O values.schema.json é escrito em JSON Schema e suporta:
minimum/maximum— limites numéricosenum— lista de valores permitidospattern— expressão regular (para validar formato de strings)additionalProperties: false— proíbe qualquer chave fora do schema
Exemplo para alunos:
{
"$schema": "http://json-schema.org/schema#",
"additionalProperties": false,
"properties": {
"replicaCount": {
"type": "integer",
"minimum": 1,
"maximum": 2,
"description": "Máximo 2 réplicas por projeto de aluno."
},
"resources": {
"properties": {
"limits": {
"properties": {
"cpu": { "type": "string", "enum": ["200m", "500m"] },
"memory": { "type": "string", "enum": ["128Mi", "256Mi"] }
}
}
}
},
"ingress": {
"properties": {
"host": {
"type": "string",
"pattern": "^[a-z0-9-]+\\.inf\\.ufsc\\.br$",
"description": "O hostname deve terminar em .inf.ufsc.br"
}
}
}
}
}
Se um aluno tentar replicaCount: 10 ou ingress.host: meusite.com:
Error: values don't meet the specifications of the schema(s):
- replicaCount: Must be less than or equal to 2
- ingress.host: Does not match pattern '^[a-z0-9-]+\.inf\.ufsc\.br$'
Nível 3 — Liberado totalmente: o usuário controla sem restrição de valor
Esses valores o usuário preenche livremente. O schema só valida o tipo para evitar erro humano (passar um número onde deveria ser string, por exemplo).
Exemplos:
"image": {
"type": "object",
"required": ["repository", "tag"],
"properties": {
"repository": { "type": "string" },
"tag": { "type": "string" }
}
}
Tabela resumo dos três níveis
| O que | Nível | Mecanismo |
|---|---|---|
securityContext completo |
Bloqueado | Hardcoded no template, sem variável exposta |
ingressClassName |
Bloqueado | Hardcoded no template |
imagePullPolicy |
Bloqueado | Hardcoded no template |
| Prefixo do registry | Bloqueado | Template concatena o prefixo fixo com o valor do usuário |
| Número de réplicas | Restrito | schema.json com minimum e maximum |
| Limites de CPU/memória | Restrito | schema.json com enum de tiers permitidos |
| Hostname do Ingress | Restrito | schema.json com pattern que força sufixo *.ufsc.br |
| Tag da imagem | Livre | schema.json só valida que é uma string |
| Variáveis de ambiente | Livre | Lista de pares chave/valor sem restrição de conteúdo |
| Nome da aplicação | Livre | String com validação de formato Kubernetes |
8. Como funciona ter vários Charts
Cada Chart é um diretório independente com seus próprios templates e values.schema.json. Você pode ter quantos quiser. Eles ficam armazenados no registry do GitLab como artefatos OCI — o mesmo registry que já armazena imagens Docker, sem nenhuma infraestrutura adicional.
Estrutura de repositórios recomendada
gitlab.setic.ufsc.br/
│
├── dtr/
│ └── helm-charts/ ← projeto da SeTIC (privado)
│ ├── student-web-app/ ← chart para alunos
│ │ ├── Chart.yaml
│ │ ├── values.yaml
│ │ ├── values.schema.json ← restrições rígidas
│ │ └── templates/
│ └── web-app/ ← chart para desenvolvedores UFSC
│ ├── Chart.yaml
│ ├── values.yaml
│ ├── values.schema.json ← restrições mais relaxadas
│ └── templates/
│
├── disciplinas/
│ └── inf-xxx/ ← grupo da disciplina
│ ├── <usuario-a>/ ← repositório do aluno
│ │ ├── Dockerfile
│ │ ├── src/
│ │ └── values.yaml ← ÚNICO arquivo de deploy que o aluno escreve
│ └── <usuario-b>/
│ └── values.yaml
│
└── CSS/ ← grupo dos desenvolvedores UFSC
└── <projeto>/
├── Dockerfile
├── src/
└── values.yaml
O que cada Chart tem de diferente
student-web-app |
web-app |
|
|---|---|---|
| Réplicas máximas | 2 | 10 |
| CPU limit máximo | 500m | 2000m |
| Memória limit máxima | 256Mi | 1Gi |
| Padrão do hostname | *.inf.ufsc.br |
*.setic.ufsc.br |
| Variáveis de ambiente extras | Não exposto | Exposto |
additionalProperties |
false (nenhuma chave extra) |
Flexível |
Os templates em si podem ser idênticos — a diferença está no values.schema.json de cada um.
9. Quem usa qual Chart e como isso é garantido
O controle de qual Chart um usuário pode acessar tem duas camadas que funcionam juntas.
Camada 1 — Acesso ao registry (quem pode fazer pull do Chart)
O projeto dtr/helm-charts é privado. O acesso ao registry é herdado do papel do membro no projeto GitLab. Quando um pipeline tenta fazer pull de um Chart, o GitLab verifica se o CI job token do projeto tem permissão.
Você configura isso uma vez nas configurações de CI/CD token access de cada grupo:
- Projetos dentro de
disciplinas/→ acesso de pull apenas emstudent-web-app - Projetos dentro de
CSS/→ acesso de pull em ambos os charts
Se um aluno tentar apontar o pipeline dele para web-app (chart dos devs), o GitLab retorna 401 Unauthorized e o pipeline falha.
Camada 2 — O .gitlab-ci.yml controlado pela SeTIC (quem aponta para qual Chart)
Esta é a camada mais importante para o cenário de disciplina. O aluno não escreve o .gitlab-ci.yml — ele herda um template centralizado que a SeTIC mantém em dtr/k8s-agents, exatamente como já funciona hoje com o gitlab-k8s-group-rbac.yml:
# .gitlab-ci.yml do aluno — ele NÃO escreve isso, herda do template
include:
- project: dtr/k8s-agents
ref: main
file: templates/student-deploy.yml # ← template que a SeTIC controla
O student-deploy.yml na SeTIC hardcoda o Chart:
# dtr/k8s-agents/templates/student-deploy.yml — controlado pela SeTIC
.student_deploy:
image: alpine/helm:latest
script:
- kubectl config use-context "$KUBE_CONTEXT"
- helm upgrade --install "$CI_PROJECT_NAME" \
oci://registry.setic.ufsc.br/dtr/helm-charts/student-web-app \
--version 1.0.0 \
--namespace "$KUBE_NAMESPACE" \
--values values.yaml \
--set image.tag="$CI_COMMIT_REF_SLUG" \
--wait --timeout 3m
O aluno nunca toca nessa linha. Ele só preenche o values.yaml do próprio repositório — que é validado pelo values.schema.json do student-web-app automaticamente quando o helm upgrade executa.
Para desenvolvedores UFSC, o template incluído é diferente e aponta para o web-app.
O que o aluno vê e faz
O aluno tem um repositório com:
<usuario>/
├── Dockerfile ← escreve normalmente
├── src/ ← escreve normalmente
└── values.yaml ← ÚNICO arquivo relacionado ao deploy que ele preenche
O values.yaml que ele preenche:
image:
repository: disciplinas/inf-xxx/<usuario>/<projeto>
tag: main
replicaCount: 1
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m # só pode escolher 200m ou 500m
memory: 128Mi # só pode escolher 128Mi ou 256Mi
ingress:
host: <projeto>.inf.ufsc.br # deve terminar em .inf.ufsc.br
Não tem securityContext. Não tem ingressClassName. Não tem nada de infraestrutura — só o que é da aplicação dele. Se ele tentar colocar qualquer chave que não esteja no schema, o Helm rejeita com mensagem clara antes de qualquer coisa chegar no cluster.
Fluxo completo do deploy do aluno
Aluno faz push no GitLab
│
▼
Pipeline executa (usando template da SeTIC)
│
├── job build: docker build + push no registry
│
└── job deploy:
│
├── helm lê o values.yaml do aluno
│
├── helm valida contra o values.schema.json do student-web-app
│ │
│ ├── OK: continua
│ └── ERRO: pipeline falha com mensagem clara, nada chega no cluster
│
├── helm gera os YAMLs finais combinando template + values
│ (com securityContext correto, ingressClassName correto, etc.)
│
└── helm aplica os YAMLs no namespace gl-<project_id>
│
▼
Cluster Kubernetes
(namespace isolado, criado pelo GitLab-managed resources)
10. Referência de comandos Helm
Instalar / atualizar uma aplicação
# instala se não existir, atualiza se já existir
helm upgrade --install <nome-da-release> <caminho-ou-url-do-chart> \
--namespace <namespace> \
--values values.yaml \
--set image.tag="abc123" \
--wait --timeout 3m
Listar releases instaladas
helm list -n gl-<project_id>
Ver o histórico de versões de uma release
helm history <nome-da-release> -n gl-<project_id>
Fazer rollback para uma versão anterior
helm rollback <nome-da-release> 2 -n gl-<project_id>
# o número é o REVISION do helm history
Ver os values que foram passados no último deploy
helm get values <nome-da-release> -n gl-<project_id>
Ver os YAMLs exatos que o Helm aplicou no cluster
helm get manifest <nome-da-release> -n gl-<project_id>
Testar o que seria gerado sem aplicar nada (dry-run)
helm upgrade --install <nome-da-release> ./chart \
--namespace gl-<project_id> \
--values values.yaml \
--dry-run
Ver o diff entre o que está no cluster e o que seria aplicado
# requer o plugin helm-diff
helm diff upgrade <nome-da-release> ./chart \
--namespace gl-<project_id> \
--set image.tag="nova-tag"
Remover uma release e todos os recursos que ela criou
helm uninstall <nome-da-release> -n gl-<project_id>
Fazer push de um Chart para o registry do GitLab
# empacotar o chart em um .tgz
helm package ./chart
# autenticar no registry
helm registry login registry.setic.ufsc.br \
-u "$CI_REGISTRY_USER" \
-p "$CI_REGISTRY_PASSWORD"
# publicar
helm push <nome-do-chart>-1.0.0.tgz \
oci://registry.setic.ufsc.br/dtr/helm-charts
Instalar a partir do registry (sem ter o chart localmente)
helm upgrade --install <nome-da-release> \
oci://registry.setic.ufsc.br/dtr/helm-charts/<nome-do-chart> \
--version 1.0.0 \
--namespace gl-<project_id> \
--values values.yaml