Missão Helm: industrializar uma implantação multi-ambiente

15 min

Projeto 13 — Helm no Kubernetes · Nível intermédio → avançado · Duração estimada: 4 a 6 h

Parte de uma aplicação composta por dois serviços Python (um portail visual e uma api backend) e vai implantá-la três vezes, lado a lado — em DEV (azul), STAGING (laranja), PROD (verde) — com um único Chart Helm e três ficheiros de valores. No fim, fará um helm upgrade e depois um helm rollback, e reparará três templates cheios de erros reais vistos em empresa.


Índice


O contexto

Trabalha numa equipa em que cada nova versão de uma aplicação deve passar por três ambientes:

  • DEV — caixa de areia dos programadores, dados descartáveis, uma só réplica.
  • STAGING — pré-produção, dados de teste, duas réplicas para validar a escalabilidade.
  • PROD — produção, dados reais, três réplicas no mínimo, nenhum tempo de inatividade tolerado.

Hoje, a equipa copia-cola os mesmos manifestos YAML para cada ambiente, a alterar à mão os valores diferentes. Resultado: os ficheiros divergem, um patch aplicado em dev não chega a prod, e uma implantação pede meia jornada.

A sua missão: industrializar tudo isto com Helm. Um só Chart, três ficheiros de valores, um comando por ambiente. Provará que funciona ao mostrar três painéis lado a lado no navegador — cada um com a sua cor, o seu número de Pods e a sua própria mensagem.


Conceitos essenciais antes de começar

Este documento é autónomo. Não precisa de nenhuma referência externa para o terminar.

1. O papel do Helm numa frase

O Helm gera manifestos Kubernetes a partir de templates e de variáveis. Lá onde kubectl apply toma um YAML estático, o Helm toma um template YAML e um ficheiro de valores, produz o YAML final e depois aplica-o como uma unidade versionada a que se chama uma release.

2. Anatomia de um Chart

mon-chart/
├── Chart.yaml            # metadata (nom, version)
├── values.yaml           # valeurs PAR DEFAUT
└── templates/            # templates YAML
    ├── deployment.yaml
    ├── service.yaml
    └── _helpers.tpl      # fonctions de template partagees (nom prefixe _)

Os ficheiros cujo nome começa por _ não produzem nenhum manifesto: servem para definir «helpers» reutilizáveis via {{ include "nom" . }}.

3. A sintaxe dos templates (Go template)

EscritoRenderizado
{{ .Values.portail.replicas }}O valor definido em values.yaml
{{ .Release.Name }}O nome que passou a helm install (ex.: hedge-dev)
{{ .Chart.Name }}O nome do chart (definido em Chart.yaml)
{{ .Chart.AppVersion }}A versão da aplicação (definida em Chart.yaml)
{{ include "hedge.labels" . }}Chamada de um helper definido em _helpers.tpl
{{- ... -}}O - suprime os espaços antes/depois da renderização
{{ .Values.env | quote }}Acrescenta aspas à volta do valor
{{ .Values.replicas | default 1 }}Usa 1 se o valor não estiver definido

4. Os 5 comandos Helm que vai usar

powershell
helm lint ./chart                                              # verifier la syntaxe
helm template <release> ./chart -f values-<env>.yaml           # rendu SEC (aucun deploiement)
helm install <release> ./chart -f values-<env>.yaml -n <ns>    # deploiement reel
helm upgrade <release> ./chart -f values-<env>.yaml -n <ns>    # modification incrementale
helm rollback <release> <revision> -n <ns>                     # retour arriere

5. A palavra-chave «release»

Uma release é uma instalação de um chart. O mesmo chart pode ser instalado várias vezes, cada um com um nome de release diferente (hedge-dev, hedge-staging, hedge-prod) — é o fundamento do multiambiente.

{{ .Release.Name }} muda em cada install, {{ .Chart.Name }} permanece idêntico. Retenha este contraste: é central.

6. A regra de ouro do seletor imutável

O campo spec.selector.matchLabels de um Deployment fica fixo de uma vez por todas na criação. Se o seu template puser neste campo um valor que pode mudar (como uma versão, um ambiente, uma data), o primeiro helm install funciona, mas o primeiro helm upgrade falha com:

spec.selector: Invalid value: ...: field is immutable

Regra a gravar em pedra: em matchLabels, coloque apenas coisas que NUNCA mudarão para esta instância — tipicamente name, instance, component.


A arquitetura alvo: DEV / STAGING / PROD

Vai implantar o mesmo chart em três namespaces distintos, cada um com os seus parâmetros:

ParâmetroDEVSTAGINGPROD
Namespacehedge-devhedge-staginghedge-prod
Release namehedge-devhedge-staginghedge-prod
Cor do bandeauazul #2563eblaranja #ea580cverde #16a34a
Mensagem«Ambiente de desenvolvimento…»«Pré-produção — dados de teste apenas»«Produção — cada ação tem um impacto real»
Réplicas portail123
Réplicas api123
Porta exposta (NodePort)301303013130132
URL de testehttp://localhost:30130http://localhost:30131http://localhost:30132

No fim do TP, abre três separadores lado a lado e vê três painéis coloridos de forma diferente, cada um a mostrar o seu ambiente, a sua versão, os seus Pods e o estado do seu backend.


Organização dos ficheiros

Parte da árvore seguinte — o ANEXO fornece o conteúdo exato de cada ficheiro:

projet13-kubernetes-helm-tp/
├── 00-ENONCE.md                              <- este documento

├── apps/                                     <- O CODIGO (ANEXO A) — NAO MODIFICAR
│   ├── portail/
│   │   ├── app.py
│   │   ├── requirements.txt
│   │   └── Dockerfile
│   └── api/
│       ├── app.py
│       ├── requirements.txt
│       └── Dockerfile

├── chart/                                    <- O CHART A COMPLETAR
│   ├── Chart.yaml                            <- esqueleto (ANEXO B)
│   ├── values.yaml                           <- valores por omissao (ANEXO B)
│   │
│   ├── environments/                         <- A SUA VEZ
│   │   ├── values-dev.yaml                   <- esqueleto TODO (ANEXO B)
│   │   ├── values-staging.yaml               <- esqueleto TODO (ANEXO B)
│   │   └── values-prod.yaml                  <- esqueleto TODO (ANEXO B)
│   │
│   ├── templates/                            <- A SUA VEZ
│   │   ├── _helpers.tpl                      <- esqueleto TODO (ANEXO B)
│   │   ├── portail-deployment.yaml           <- esqueleto TODO (ANEXO B)
│   │   ├── portail-service.yaml              <- esqueleto TODO (ANEXO B)
│   │   ├── api-deployment.yaml               <- esqueleto TODO (ANEXO B)
│   │   └── api-service.yaml                  <- esqueleto TODO (ANEXO B)
│   │
│   └── casses/                               <- FORNECIDOS mas DEFEITUOSOS (ANEXO C)
│       ├── casse-1-configmap.yaml
│       ├── casse-2-worker-deployment.yaml
│       └── casse-3-cache-deployment.yaml

├── outils/
│   └── valider.ps1                           <- FORNECIDO (ANEXO D)

└── RAPPORT.md                                <- A REDIGIR por si

Ponto crucial: os ficheiros de chart/casses/ não estão em chart/templates/. O Helm portanto não os carrega automaticamente. A missão 6 pedir-lhe-á que os copie um a um para templates/ a fim de observar o erro, e depois que os repare antes de os guardar.


As regras do jogo

  1. Proibição absoluta de modificar a pasta apps/. O código da aplicação já está escrito — você é o DevOps, não o programador.
  2. Só modifica os ficheiros da pasta chart/.
  3. Nenhuma configuração de ambiente escrita em concreto num template: replicas, nodePort, cor, mensagem, ambiente — tudo deve vir de um .Values.*.
  4. Os 3 ficheiros values-<env>.yaml devem diferir unicamente pelos valores que distinguem DEV, STAGING e PROD. Um ficheiro values-prod.yaml que redefine inutilmente image.repository ou service.targetPort é um erro — essas coisas vêm de values.yaml.
  5. Trabalha no Kubernetes integrado no Docker Desktop.

Preparação

Pré-requisitos — a verificar uma só vez

  1. O Docker Desktop está arrancado e o Kubernetes está ativado (Settings → Kubernetes → Enable Kubernetes).
  2. O Docker Desktop tem pelo menos 4 Go de RAM atribuídos (Settings → Resources → Memory ≥ 4 GB). Este TP faz correr 12 Pods em simultâneo (1+1 + 2+2 + 3+3).
  3. O Helm está instalado:
    powershell
    helm version --short           # doit afficher v3.x ou v4.x
    Senão: winget install Helm.Helm (ou choco install kubernetes-helm).
  4. Está mesmo no cluster certo:
    powershell
    kubectl config use-context docker-desktop
    kubectl get nodes              # docker-desktop   Ready

Construção das imagens

O chart referencia duas imagens locais que deve construir uma só vez:

powershell
docker build -t hedge-portail:1.0 .\apps\portail
docker build -t hedge-api:1.0     .\apps\api

docker images | Select-String "^hedge"      # doit afficher les 2 images

Lembrete: o Docker Desktop partilha o seu daemon com o Kubernetes; nenhuma etapa de «carregamento» é necessária (ao contrário de kind ou minikube).


As missões

Missão 1 — Fazer viver um Chart mínimo (10 pontos)

Complete chart/Chart.yaml (nome, apiVersion, type, version, appVersion). Verifique depois:

powershell
helm lint .\chart
# doit afficher : 1 chart(s) linted, 0 chart(s) failed

Esperado: um Chart que passa o lint sem erro.


Missão 2 — Parametrizar portail e api (20 pontos)

Complete os 4 ficheiros de chart/templates/:

  • portail-deployment.yaml — um Deployment que usa .Values.portail.replicas, .Values.portail.image.*, e injeta as variáveis de ambiente ENVIRONMENT, APP_VERSION, THEME_COLOR, BANNIERE_MESSAGE, BACKEND_URL, REPLICAS_INFO.
  • portail-service.yaml — um Service NodePort que aponta para os Pods do portal.
  • api-deployment.yaml — um Deployment para a api (variáveis ENVIRONMENT, APP_VERSION).
  • api-service.yaml — um Service ClusterIP.

Ponto-chave: a variável BACKEND_URL do portal deve conter o nome do Service api construído com {{ .Release.Name }} (por exemplo http://hedge-dev-api), não um nome em concreto.

Validação:

powershell
helm template check .\chart -f .\chart\environments\values-dev.yaml
# doit afficher 2 Deployments + 2 Services, tous prefixes par "check-"

Missão 3 — Escrever helpers limpos (15 pontos)

Complete chart/templates/_helpers.tpl com três helpers:

  1. hedge.fullname — devolve {{ .Release.Name }}-<composant> (ex.: hedge-dev-portail).
  2. hedge.labels — devolve os labels standard:
    • app.kubernetes.io/name
    • app.kubernetes.io/instance
    • app.kubernetes.io/component
    • app.kubernetes.io/managed-by
    • app.kubernetes.io/version
    • helm.sh/chart
    • hedge/environment
  3. hedge.selectorLabels — devolve apenas name, instance, component (os 3 labels garantidos imutáveis para esta instância).

Restrição forte: use estes helpers em todos os seus templates. Nenhum nome de recurso em concreto, nenhum label recopiado à mão.

Truque: para passar vários valores a um helper, use um dict:

yaml
{{ include "hedge.labels" (dict "root" . "composant" "portail") | nindent 4 }}

O helper recebe então .root.Release.Name, .root.Values... e .composant.


Missão 4 — Três ambientes lado a lado (20 pontos)

Crie os 3 ficheiros em chart/environments/ — cada um redefine apenas os valores que distinguem o seu ambiente.

Ficheiroenvironmentreplicas (portail + api)nodePort (portail)CorMensagem sugerida
values-dev.yamldev130130#2563eb«Ambiente de desenvolvimento — atenção, tudo pode mudar»
values-staging.yamlstaging230131#ea580c«Pré-produção — dados de teste apenas»
values-prod.yamlprod330132#16a34a«Produção — cada ação tem um impacto real»

Implantação dos 3 ambientes:

powershell
helm install hedge-dev     .\chart -f .\chart\environments\values-dev.yaml     -n hedge-dev     --create-namespace
helm install hedge-staging .\chart -f .\chart\environments\values-staging.yaml -n hedge-staging --create-namespace
helm install hedge-prod    .\chart -f .\chart\environments\values-prod.yaml    -n hedge-prod    --create-namespace

Espere que os Pods estejam prontos (~30 s):

powershell
kubectl wait --for=condition=ready pod --all -n hedge-dev     --timeout=120s
kubectl wait --for=condition=ready pod --all -n hedge-staging --timeout=120s
kubectl wait --for=condition=ready pod --all -n hedge-prod    --timeout=120s

Abra os 3 painéis:

powershell
start http://localhost:30130       # DEV — bandeau bleu, 1 replique
start http://localhost:30131       # STAGING — bandeau orange, 2 repliques
start http://localhost:30132       # PROD — bandeau vert, 3 repliques

Esperado: três páginas de cores diferentes, cada uma a mostrar o seu env, a sua versão, os seus Pods e o seu backend em OK verde.


Missão 5 — Upgrade e depois rollback (10 pontos)

Simule um incidente de produção e depois anule-o.

Cenário:

  1. Em DEV, passe portail.replicas para 5:
    powershell
    helm upgrade hedge-dev .\chart -f .\chart\environments\values-dev.yaml --set portail.replicas=5 -n hedge-dev
  2. Verifique que 5 Pods de portal estão a correr:
    powershell
    kubectl get pods -n hedge-dev -l app.kubernetes.io/component=portail
  3. Consulte o histórico:
    powershell
    helm history hedge-dev -n hedge-dev
    Vê pelo menos 2 revisões.
  4. Anule o upgrade ao voltar à revisão 1:
    powershell
    helm rollback hedge-dev 1 -n hedge-dev
  5. Verifique que se voltou a 1 só Pod portal, e que o histórico mostra uma nova revisão do tipo Rollback:
    powershell
    kubectl get pods -n hedge-dev -l app.kubernetes.io/component=portail
    helm history hedge-dev -n hedge-dev

Pergunta a tratar no relatório: qual é a diferença fundamental entre helm upgrade --set portail.replicas=5 e kubectl scale deploy/hedge-dev-portail --replicas=5? Por que o Helm prefere que se passe por ele?


Missão 6 — Inquérito: reparar 3 templates defeituosos (20 pontos)

A pasta chart/casses/ contém três templates já escritos que compilam mas introduzem cada um um erro real encontrado em empresa. Deve, para cada um:

  1. copiá-lo para chart/templates/ ;
  2. reproduzir o sintoma descrito no topo do ficheiro ;
  3. diagnosticar a causa ao ler a mensagem de erro ;
  4. reparar (ao modificar o template em chart/templates/, não o original em casses/) ;
  5. provar que a avaria desapareceu.
FicheiroComponente acrescentadoNatureza do erro
casse-1-configmap.yamlUm ConfigMap globalColisão de nome entre releases
casse-2-worker-deployment.yamlUm Deployment workerSeletor imutável violado no primeiro helm upgrade
casse-3-cache-deployment.yamlUm Deployment cacheCaminho de valor errado (erro de digitação silencioso)

Truque de inquérito:

powershell
# rendu SEC d'un seul template (n'installe rien)
helm template hedge-dev .\chart -f .\chart\environments\values-dev.yaml `
  --show-only templates/casse-3-cache-deployment.yaml --debug

Este comando imprime exatamente o que o Helm enviaria ao Kubernetes. É a sua primeira ferramenta de diagnóstico — use-a sem moderação.


Missão 7 — Bónus: o refinamento (5 pontos)

À escolha, um só basta:

  • a) Acrescente um hook pre-install (Job) que mostra Bienvenue dans <environnement> nos logs Helm. A release deve esperar o fim do Job antes de continuar.
  • b) Torne o número de réplicas dinâmico com um valor values.yaml que tem uma estrutura aninhada (por exemplo portail.autoscaling.enabled, portail.autoscaling.min, portail.autoscaling.max) e faça gerar condicionalmente um HorizontalPodAutoscaler segundo .enabled.
  • c) Acrescente um NOTES.txt em templates/ que mostra, depois de cada helm install, o URL exato para abrir o painel (com o nodePort certo segundo os Values).

Validação automática

Um script dá-lhe a pontuação a qualquer momento:

powershell
.\outils\valider.ps1

Exemplo de saída num trabalho parcialmente feito:

[OK]    Mission 1 - Chart valide.............. 10/10
[OK]    Mission 2 - Templating de base........ 20/20
[ECHEC] Mission 3 - Helpers et labels.........  0/15   -> helper hedge.selectorLabels manquant
[OK]    Mission 4 - Trois environnements...... 20/20
[ECHEC] Mission 5 - Upgrade + rollback........  0/10   -> aucun rollback detecte dans l'historique
[OK]    Mission 6 - Reparations (3 pannes).... 14/20   -> casse-3 : typo .Values.portal toujours present

SCORE AUTOMATIQUE : 64 / 95

Se o PowerShell recusar executar o script (l'exécution de scripts est désactivée), use:

powershell
powershell -ExecutionPolicy Bypass -File .\outils\valider.ps1

Entregáveis

  1. A pasta chart/ completa, em estado de funcionamento (helm lint limpo).
  2. Um RAPPORT.md contendo:
    • a saída de helm list -A a mostrar as suas 3 releases ;
    • uma captura por ambiente (3 painéis coloridos) ;
    • o histórico completo de hedge-dev (com upgrade + rollback) ;
    • para cada avaria da missão 6: comando de diagnóstico, causa, correção, prova ;
    • as suas respostas às questões de reflexão.
  3. A saída final de .\outils\valider.ps1.

Questões de reflexão

  1. Por que o campo spec.selector.matchLabels é imutável no Kubernetes? Que problema resolve esta restrição?
  2. Tem 3 ambientes hoje. Amanhã, a equipa DevSecOps pede um 4.º («pre-prod»). Que ficheiros cria e quais não toca?
  3. Qual é a diferença entre helm upgrade --set replicas=5 e kubectl scale, do ponto de vista da rastreabilidade e do rollback?
  4. O portal mostra «backend OK» — por que esta informação é mais fiável do que um simples kubectl get svc api?
  5. O que acontece se apagar um Pod com kubectl delete pod, quando foi criado por um Deployment via Helm? O Helm está ao corrente da «perda»?
  6. Um colega propõe-lhe pôr app.kubernetes.io/version: {{ .Chart.AppVersion }} no matchLabels de um Deployment. O que lhe responde?

Cotação

ElementoPontos
Missão 1 — Chart válido e lint limpo10
Missão 2 — Templating portail + api20
Missão 3 — Helpers e labels reutilizáveis15
Missão 4 — Três ambientes lado a lado20
Missão 5 — Upgrade + rollback rastreados10
Missão 6 — Diagnóstico + reparação das 3 avarias20
Qualidade do relatório e justificação das escolhas5
Bónus — Missão 7+5
Total100 (+5)

Penalizações:

  • −10 por valor de ambiente escrito em concreto num template (replicas: 3 literal em vez de .Values....).
  • −5 por redefinição inútil num values-<env>.yaml (um valor que não tem razão para diferir entre ambientes).
  • −10 por alteração de um ficheiro de apps/.

Caixa de ferramentas Helm

powershell
# ANALYSE (aucun deploiement)
helm lint .\chart                                                        # syntaxe + best practices
helm template <release> .\chart -f <values.yaml>                         # rendu complet
helm template <release> .\chart -f <values.yaml> --show-only templates/<fichier>   # rendu ciblé
helm template <release> .\chart -f <values.yaml> --debug                 # avec traces
helm show values .\chart                                                 # valeurs par défaut

# DEPLOIEMENT
helm install <release> .\chart -f <values.yaml> -n <ns> --create-namespace
helm upgrade <release> .\chart -f <values.yaml> -n <ns>
helm upgrade <release> .\chart -f <values.yaml> --set portail.replicas=5 -n <ns>
helm rollback <release> <revision> -n <ns>
helm uninstall <release> -n <ns>

# OBSERVATION
helm list -A                                                             # toutes les releases
helm status <release> -n <ns>
helm history <release> -n <ns>
helm get values <release> -n <ns>                                        # les valeurs actives
helm get manifest <release> -n <ns>                                      # les manifestes appliques

Os 3 reflexos em caso de erro:

  1. Começar sempre por helm template — é uma renderização SECA, sem risco, que mostra exatamente o que vai ser enviado ao Kubernetes.
  2. Ler o caminho na mensagem de erro — o Helm dá sempre o ficheiro + a linha + o caminho .Values.* falhoso.
  3. helm get manifest mostra o que está atualmente no cluster (útil para comparar com o que o seu novo template gera).


ANEXO A — As aplicações

Não modifique nenhum destes ficheiros. Recopie-os tal qual nos caminhos indicados.

A.1 — O portal (painel multiambiente)

Todos os valores mostrados vêm de variáveis de ambiente injetadas pelo Helm. A mesma imagem comporta-se de forma diferente segundo os env: do Deployment.

Ficheiro: apps/portail/app.py

python
"""Portal — painel multiambiente.

Este Pod mostra o ambiente em que corre (DEV / STAGING / PROD),
a versao da aplicacao, o numero de replicas e o estado do backend.

Todos os valores mostrados vem de VARIAVEIS DE AMBIENTE injetadas
pelo Helm a partir de values-<env>.yaml. O mesmo codigo adapta-se a cada
ambiente sem nenhuma alteracao.
"""

import os
import socket
import time
import urllib.error
import urllib.request

from flask import Flask, jsonify, request

app = Flask(__name__)
DEMARRAGE = time.time()


def cfg():
    return {
        "env": os.environ.get("ENVIRONMENT", "desconhecido"),
        "version": os.environ.get("APP_VERSION", "0.0.0"),
        "theme": os.environ.get("THEME_COLOR", "#64748b"),
        "message": os.environ.get("BANNIERE_MESSAGE", "Implantado com Helm"),
        "backend_url": os.environ.get("BACKEND_URL", "http://api"),
        "replicas_info": os.environ.get("REPLICAS_INFO", "?"),
        "pod": socket.gethostname(),
        "uptime": int(time.time() - DEMARRAGE),
    }


def tester_backend(url):
    try:
        with urllib.request.urlopen(url + "/ping", timeout=1.5) as reponse:
            corps = reponse.read(200).decode("utf-8", "ignore")
        return "ok", corps.strip()
    except urllib.error.HTTPError as err:
        return "http", "HTTP %s" % err.code
    except Exception as err:
        return "ko", type(err).__name__


@app.route("/health")
def health():
    return "OK", 200


@app.route("/api-json")
def api_json():
    """Rota util para a validacao automatica."""
    c = cfg()
    etat, detail = tester_backend(c["backend_url"])
    return jsonify(pod=c["pod"], env=c["env"], version=c["version"],
                   backend=etat, backend_detail=detail, uptime=c["uptime"])


@app.route("/")
def accueil():
    c = cfg()
    etat, detail = tester_backend(c["backend_url"])
    # ... (modelo HTML completo no ficheiro — nao repetido aqui para permanecer legivel)

O ficheiro completo é fornecido em apps/portail/app.py.

Ficheiro: apps/portail/requirements.txt

text
flask==3.0.3

Ficheiro: apps/portail/Dockerfile

dockerfile
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
CMD ["python", "app.py"]

A.2 — A api (backend simples)

Ficheiro: apps/api/app.py

python
"""API — backend simples para o exemplo Helm."""

import os
import socket
import time

from flask import Flask, jsonify

app = Flask(__name__)
DEMARRAGE = time.time()

ENV = os.environ.get("ENVIRONMENT", "desconhecido")
VERSION = os.environ.get("APP_VERSION", "0.0.0")


@app.route("/")
@app.route("/ping")
def ping():
    return jsonify(service="api", env=ENV, version=VERSION,
                   pod=socket.gethostname(),
                   uptime=int(time.time() - DEMARRAGE))


@app.route("/health")
def health():
    return "OK", 200


if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8000)

Ficheiro: apps/api/requirements.txt

text
flask==3.0.3

Ficheiro: apps/api/Dockerfile

dockerfile
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
CMD ["python", "app.py"]


ANEXO B — Esqueleto do Chart

Recopie estes ficheiros e depois substitua cada TODO pelo valor certo. As linhas precedidas de # ? são perguntas a decidir: cabe-lhe a si completar o código.

Ficheiro: chart/Chart.yaml

yaml
# ? Preencha os campos obrigatorios de um Chart Helm.
# ? apiVersion deve valer v2 (a v1 esta obsoleta desde o Helm 3).
# ? type e "application" (por oposicao a "library").

apiVersion: TODO
name: hedge
description: TODO
type: TODO
version: 0.1.0
appVersion: "1.0.0"

Ficheiro: chart/values.yaml

yaml
# values.yaml — valores por omissao do chart hedge.
# Cada ambiente fornece um ficheiro values-<env>.yaml que SOBREPOE
# estes valores por cima. Mantenha este ficheiro NEUTRO (nenhum valor especifico
# de um ambiente).

environment: default

banniere:
  message: "Chart Helm — application multi-environnement"
  couleur: "#64748b"

portail:
  image:
    repository: hedge-portail
    tag: "1.0"
    pullPolicy: IfNotPresent
  replicas: 1
  service:
    type: NodePort
    port: 80
    targetPort: 5000
    nodePort: 30130

api:
  image:
    repository: hedge-api
    tag: "1.0"
    pullPolicy: IfNotPresent
  replicas: 1
  service:
    type: ClusterIP
    port: 80
    targetPort: 8000

Ficheiro: chart/templates/_helpers.tpl

yaml
{{/*
Nome completo de um recurso : "<release>-<composant>".
Uso : {{ include "hedge.fullname" (dict "root" . "composant" "portail") }}
*/}}
{{- define "hedge.fullname" -}}
{{- printf "TODO" .root.Release.Name .composant | trunc 63 | trimSuffix "-" -}}
{{- end -}}


{{/*
Labels comuns a todos os recursos.
Uso : {{ include "hedge.labels" (dict "root" . "composant" "portail") | nindent 4 }}
*/}}
{{- define "hedge.labels" -}}
# ? preencha os 7 labels pedidos na Missao 3
app.kubernetes.io/name: TODO
app.kubernetes.io/instance: TODO
# ... continue ...
{{- end -}}


{{/*
Selector labels : subconjunto ESTAVEL dos labels.
Colocar aqui APENAS labels que NUNCA mudarao para uma instancia.
*/}}
{{- define "hedge.selectorLabels" -}}
# ? os 3 labels ESTRITAMENTE imutaveis apenas
{{- end -}}

Ficheiro: chart/templates/portail-deployment.yaml

yaml
# ? Deployment do portal. Use :
#   - .Values.portail.replicas
#   - .Values.portail.image.{repository,tag,pullPolicy}
#   - .Values.portail.service.targetPort
#   - .Values.environment
#   - .Values.banniere.{couleur,message}
#   - .Chart.AppVersion (pour APP_VERSION)
#   - O NOME do Service api construido com .Release.Name (para BACKEND_URL)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: TODO
  labels:
    TODO
spec:
  replicas: TODO
  selector:
    matchLabels:
      TODO
  template:
    metadata:
      labels:
        TODO
    spec:
      containers:
        - name: portail
          image: TODO
          imagePullPolicy: TODO
          ports:
            - containerPort: TODO
          env:
            - name: ENVIRONMENT
              value: TODO
            # ? acrescente APP_VERSION, THEME_COLOR, BANNIERE_MESSAGE,
            #   BACKEND_URL, REPLICAS_INFO
          readinessProbe:
            httpGet:
              path: /health
              port: TODO
            initialDelaySeconds: 3
            periodSeconds: 5

Ficheiro: chart/templates/portail-service.yaml

yaml
# ? Service para o portal. Tipo NodePort. Use a condicao
#   {{- if eq .Values.portail.service.type "NodePort" }} ... {{- end }}
#   para incluir "nodePort" APENAS se for mesmo um NodePort.
apiVersion: v1
kind: Service
metadata:
  name: TODO
  labels:
    TODO
spec:
  type: TODO
  selector:
    TODO
  ports:
    - port: TODO
      targetPort: TODO
      # ? nodePort apenas se type == NodePort

Ficheiro: chart/templates/api-deployment.yaml

yaml
# ? Mesma estrutura que portail-deployment.yaml, mas :
#   - composant = "api"
#   - variaveis de ambiente : ENVIRONMENT e APP_VERSION apenas
#   - porta do contentor = .Values.api.service.targetPort (8000)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: TODO
spec:
  # ... (estrutura semelhante ao portal) ...

Ficheiro: chart/templates/api-service.yaml

yaml
# ? Service ClusterIP para a api. Uma so porta. Sem nodePort.
apiVersion: v1
kind: Service
metadata:
  name: TODO
spec:
  type: TODO
  selector:
    TODO
  ports:
    - port: TODO
      targetPort: TODO

Ficheiro: chart/environments/values-dev.yaml

yaml
# ? Ambiente DEV : 1 replica, bandeau azul #2563eb, NodePort 30130.
environment: TODO

banniere:
  message: TODO
  couleur: TODO

portail:
  replicas: TODO
  service:
    nodePort: TODO

api:
  replicas: TODO

Ficheiro: chart/environments/values-staging.yaml

yaml
# ? Ambiente STAGING : 2 replicas, bandeau laranja #ea580c, NodePort 30131.
environment: TODO
# ... complete no modelo de values-dev.yaml ...

Ficheiro: chart/environments/values-prod.yaml

yaml
# ? Ambiente PROD : 3 replicas, bandeau verde #16a34a, NodePort 30132.
environment: TODO
# ...


ANEXO C — As três avarias a reparar

Cada ficheiro abaixo encontra-se em chart/casses/. Não modifique os originais — copie-os para chart/templates/, reproduza o erro e depois corrija a cópia.

Ficheiro: chart/casses/casse-1-configmap.yaml

yaml
# AVARIA 1
# Sintoma : instale hedge-dev, depois tente instalar hedge-staging
# NO MESMO NAMESPACE. A segunda instalacao falha com :
#   ConfigMap "hedge-config" ... exists and cannot be imported ...
apiVersion: v1
kind: ConfigMap
metadata:
  name: hedge-config
  labels:
    {{- include "hedge.labels" (dict "root" . "composant" "config") | nindent 4 }}
data:
  timezone: "America/Toronto"
  langue: "fr-CA"

Ficheiro: chart/casses/casse-2-worker-deployment.yaml

yaml
# AVARIA 2
# Sintoma : "helm install" funciona. "helm upgrade" com um
# --set environment=recette falha com :
#   spec.selector: Invalid value: ...: field is immutable
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "hedge.fullname" (dict "root" . "composant" "worker") }}
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: {{ .Chart.Name }}
      app.kubernetes.io/instance: {{ .Release.Name }}
      app.kubernetes.io/component: worker
      hedge/environment: {{ .Values.environment | quote }}
  template:
    metadata:
      labels:
        {{- include "hedge.labels" (dict "root" . "composant" "worker") | nindent 8 }}
    spec:
      containers:
        - name: worker
          image: "{{ .Values.api.image.repository }}:{{ .Values.api.image.tag }}"

Ficheiro: chart/casses/casse-3-cache-deployment.yaml

yaml
# AVARIA 3
# Sintoma : "helm install" falha com :
#   Error: template ...at <.Values.portal.replicas>:
#   nil pointer evaluating interface {}.replicas
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "hedge.fullname" (dict "root" . "composant" "cache") }}
spec:
  replicas: {{ .Values.portal.replicas }}
  selector:
    matchLabels:
      {{- include "hedge.selectorLabels" (dict "root" . "composant" "cache") | nindent 6 }}
  template:
    metadata:
      labels:
        {{- include "hedge.selectorLabels" (dict "root" . "composant" "cache") | nindent 8 }}
    spec:
      containers:
        - name: cache
          image: "{{ .Values.api.image.repository }}:{{ .Values.api.image.tag }}"


ANEXO D — O script de validação

O ficheiro outils/valider.ps1 é fornecido tal qual. Não dá nenhuma solução — apenas uma pontuação e o primeiro ponto a corrigir. Execute-o a qualquer momento:

powershell
.\outils\valider.ps1

O que o script verifica:

MissãoCritérios automáticos
1helm lint passa, Chart.yaml tem apiVersion: v2, type: application
2helm template produz mesmo 2 Deployments e 2 Services, nomes prefixados pela release
3_helpers.tpl define os 3 helpers, labels standard presentes
4Os 3 ficheiros values-<env>.yaml existem com os valores certos (env, replicas, porta, cor), as 3 releases estão implantadas
5A release hedge-dev tem ≥ 2 revisões e um rollback no histórico
6Nenhum hedge-config em concreto, nenhum label variável em matchLabels, nenhum .Values.portal (com erro de digitação)

O script não executa ele próprio nenhum comando helm install: cabe-lhe a si implantar antes de validar.


Curso criado pelo Dr. Haythem REHOUMA — Desenvolvimento e implantação de soluções de dados