Misión Helm: industrializar un despliegue multi-entorno

15 min

Proyecto 13 — Helm sobre Kubernetes · Nivel intermedio → avanzado · Duración estimada: 4 a 6 h

Partes de una aplicación compuesta de dos services Python (un portail visual y una api backend) y vas a desplegarla tres veces lado a lado — en DEV (azul), STAGING (naranja), PROD (verde) — con un solo Chart Helm y tres archivos de valores. Al final harás un helm upgrade y luego un helm rollback, y repararás tres templates llenos de bugs reales vistos en empresa.


Tabla de contenidos


El contexto

Trabajas en un equipo donde cada nueva versión de una aplicación debe pasar por tres entornos:

  • DEV — caja de arena de los desarrolladores, datos desechables, una sola réplica.
  • STAGING — preproducción, datos de prueba, dos réplicas para validar la escalabilidad.
  • PROD — producción, datos reales, tres réplicas mínimo, ningún downtime tolerado.

Hoy, el equipo copia y pega los mismos manifiestos YAML para cada entorno cambiando a mano los valores distintos. Resultado: los archivos divergen, un parche aplicado en dev no llega a prod, y un despliegue pide medio día.

Tu misión: industrializar todo eso con Helm. Un solo Chart, tres archivos de valores, un comando por entorno. Demostrarás que funciona mostrando tres tableros lado a lado en el navegador — cada uno con su propio color, su propio número de Pods y su propio mensaje.


Conceptos esenciales antes de empezar

Este documento es autosuficiente. No necesitas ninguna referencia externa para terminarlo.

1. El papel de Helm en una frase

Helm genera manifiestos Kubernetes a partir de templates y de variables. Donde kubectl apply toma un YAML estático, Helm toma un template YAML y un archivo de valores, produce el YAML final y lo aplica como una unidad versionada que se llama una release.

2. Anatomía de un Chart

mon-chart/
├── Chart.yaml            # metadata (nombre, versión)
├── values.yaml           # valores POR DEFECTO
└── templates/            # templates YAML
    ├── deployment.yaml
    ├── service.yaml
    └── _helpers.tpl      # funciones de template compartidas (nombre prefijado _)

Los archivos cuyo nombre empieza por _ no producen ningún manifiesto: sirven para definir «helpers» reutilizables vía {{ include "nom" . }}.

3. La sintaxis de los templates (Go template)

EscritoRenderizado
{{ .Values.portail.replicas }}El valor definido en values.yaml
{{ .Release.Name }}El nombre que pasaste a helm install (p. ej. hedge-dev)
{{ .Chart.Name }}El nombre del chart (definido en Chart.yaml)
{{ .Chart.AppVersion }}La versión de la aplicación (definida en Chart.yaml)
{{ include "hedge.labels" . }}Llamada a un helper definido en _helpers.tpl
{{- ... -}}El - elimina los espacios antes/después del renderizado
{{ .Values.env | quote }}Añade comillas alrededor del valor
{{ .Values.replicas | default 1 }}Usa 1 si el valor no está definido

4. Los 5 comandos Helm que usarás

powershell
helm lint ./chart                                              # verificar la sintaxis
helm template <release> ./chart -f values-<env>.yaml           # render SECO (ningún despliegue)
helm install <release> ./chart -f values-<env>.yaml -n <ns>    # despliegue real
helm upgrade <release> ./chart -f values-<env>.yaml -n <ns>    # modificación incremental
helm rollback <release> <revision> -n <ns>                     # vuelta atrás

5. La palabra clave «release»

Una release es una instalación de un chart. Un mismo chart puede instalarse varias veces, cada una con un nombre de release distinto (hedge-dev, hedge-staging, hedge-prod) — es el fundamento del multi-entorno.

{{ .Release.Name }} cambia en cada install, {{ .Chart.Name }} se queda idéntico. Retén este contraste: es central.

6. La regla de oro del selector inmutable

El campo spec.selector.matchLabels de un Deployment queda fijado de una vez por todas en la creación. Si tu template pone en ese campo un valor que puede cambiar (como una versión, un entorno, una fecha), el primer helm install funcionará, pero el primer helm upgrade fallará con:

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

Regla para grabar en mármol: en matchLabels, pon solo cosas que no cambiarán NUNCA para esta instancia — típicamente name, instance, component.


La arquitectura objetivo: DEV / STAGING / PROD

Vas a desplegar el mismo chart en tres namespaces distintos, cada uno con sus parámetros:

ParámetroDEVSTAGINGPROD
Namespacehedge-devhedge-staginghedge-prod
Release namehedge-devhedge-staginghedge-prod
Color de la franjaazul #2563ebnaranja #ea580cverde #16a34a
Mensaje«Entorno de desarrollo…»«Preproducción — datos de prueba únicamente»«Producción — cada acción tiene un impacto real»
Réplicas portail123
Réplicas api123
Puerto expuesto (NodePort)301303013130132
URL de pruebahttp://localhost:30130http://localhost:30131http://localhost:30132

Al final del TP, abres tres pestañas lado a lado y ves tres dashboards de colores distintos, cada uno mostrando su entorno, su versión, sus Pods y el estado de su backend.


Disposición de los archivos

Partes del árbol siguiente — el ANEXO da el contenido exacto de cada archivo:

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

├── apps/                                     <- EL CÓDIGO (ANEXO A) — NO MODIFICAR
│   ├── portail/
│   │   ├── app.py
│   │   ├── requirements.txt
│   │   └── Dockerfile
│   └── api/
│       ├── app.py
│       ├── requirements.txt
│       └── Dockerfile

├── chart/                                    <- EL CHART A COMPLETAR
│   ├── Chart.yaml                            <- esqueleto (ANEXO B)
│   ├── values.yaml                           <- valores por defecto (ANEXO B)
│   │
│   ├── environments/                         <- TE TOCA A TI
│   │   ├── values-dev.yaml                   <- esqueleto TODO (ANEXO B)
│   │   ├── values-staging.yaml               <- esqueleto TODO (ANEXO B)
│   │   └── values-prod.yaml                  <- esqueleto TODO (ANEXO B)
│   │
│   ├── templates/                            <- TE TOCA A TI
│   │   ├── _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/                               <- SUMINISTRADOS pero DEFECTUOSOS (ANEXO C)
│       ├── casse-1-configmap.yaml
│       ├── casse-2-worker-deployment.yaml
│       └── casse-3-cache-deployment.yaml

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

└── RAPPORT.md                                <- A REDACTAR por ti

Punto crucial: los archivos de chart/casses/ no están en chart/templates/. Helm por tanto no los carga automáticamente. La misión 6 te pedirá copiarlos uno a uno en templates/ para observar el bug, y luego repararlos antes de conservarlos.


Las reglas del juego

  1. Prohibición absoluta de modificar la carpeta apps/. El código de la aplicación ya está escrito — tú eres el DevOps, no el desarrollador.
  2. Solo modificas los archivos de la carpeta chart/.
  3. Ninguna configuración de entorno codificada a pelo en un template: replicas, nodePort, color, mensaje, entorno — todo debe venir de un .Values.*.
  4. Los 3 archivos values-<env>.yaml deben diferir únicamente por los valores que distinguen DEV, STAGING y PROD. Un archivo values-prod.yaml que redefine inútilmente image.repository o service.targetPort es un error — esas cosas vienen de values.yaml.
  5. Trabajas sobre el Kubernetes integrado en Docker Desktop.

Preparación

Requisitos previos — a verificar una sola vez

  1. Docker Desktop está arrancado y Kubernetes está activado (Settings → Kubernetes → Enable Kubernetes).
  2. Docker Desktop dispone de al menos 4 GB de RAM asignados (Settings → Resources → Memory ≥ 4 GB). Este TP hace correr 12 Pods a la vez (1+1 + 2+2 + 3+3).
  3. Helm está instalado:
    powershell
    helm version --short           # debe mostrar v3.x o v4.x
    Si no: winget install Helm.Helm (o choco install kubernetes-helm).
  4. Estás en el clúster correcto:
    powershell
    kubectl config use-context docker-desktop
    kubectl get nodes              # docker-desktop   Ready

Construcción de las imágenes

El chart referencia dos imágenes locales que debes construir una sola 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"      # debe mostrar las 2 imágenes

Recordatorio: Docker Desktop comparte su daemon con Kubernetes; no hace falta ningún paso de «carga» (a diferencia de kind o minikube).


Las misiones

Misión 1 — Dar vida a un Chart mínimo (10 puntos)

Completa chart/Chart.yaml (nombre, apiVersion, type, version, appVersion). Luego verifica:

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

Esperado: un Chart que pasa el lint sin error.


Misión 2 — Templatar portail y api (20 puntos)

Completa los 4 archivos de chart/templates/:

  • portail-deployment.yaml — un Deployment que usa .Values.portail.replicas, .Values.portail.image.*, e inyecta las variables de entorno ENVIRONMENT, APP_VERSION, THEME_COLOR, BANNIERE_MESSAGE, BACKEND_URL, REPLICAS_INFO.
  • portail-service.yaml — un Service NodePort que apunta a los Pods del portal.
  • api-deployment.yaml — un Deployment para la api (variables ENVIRONMENT, APP_VERSION).
  • api-service.yaml — un Service ClusterIP.

Punto clave: la variable BACKEND_URL del portal debe contener el nombre del Service api construido con {{ .Release.Name }} (por ejemplo http://hedge-dev-api), no un nombre a pelo.

Validación:

powershell
helm template check .\chart -f .\chart\environments\values-dev.yaml
# debe mostrar 2 Deployments + 2 Services, todos prefijados por "check-"

Misión 3 — Escribir helpers limpios (15 puntos)

Completa chart/templates/_helpers.tpl con tres helpers:

  1. hedge.fullname — devuelve {{ .Release.Name }}-<composant> (p. ej. hedge-dev-portail).
  2. hedge.labels — devuelve las labels estándar:
    • 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 — devuelve solo name, instance, component (las 3 labels garantizadas inmutables para esta instancia).

Restricción fuerte: usa estos helpers en todos tus templates. Ningún nombre de recurso a pelo, ninguna label copiada a mano.

Truco: para pasar varios valores a un helper, usa un dict:

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

El helper recibe entonces .root.Release.Name, .root.Values..., y .composant.


Misión 4 — Tres entornos lado a lado (20 puntos)

Crea los 3 archivos en chart/environments/ — cada uno redefine solo los valores que distinguen su entorno.

Archivoenvironmentreplicas (portail + api)nodePort (portail)ColorMensaje sugerido
values-dev.yamldev130130#2563eb«Entorno de desarrollo — atención, todo puede cambiar»
values-staging.yamlstaging230131#ea580c«Preproducción — datos de prueba únicamente»
values-prod.yamlprod330132#16a34a«Producción — cada acción tiene un impacto real»

Despliegue de los 3 entornos:

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

Espera a que los Pods estén listos (~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

Abre los 3 dashboards:

powershell
start http://localhost:30130       # DEV — franja azul, 1 réplica
start http://localhost:30131       # STAGING — franja naranja, 2 réplicas
start http://localhost:30132       # PROD — franja verde, 3 réplicas

Esperado: tres páginas de colores distintos, cada una mostrando su env, su versión, sus Pods y su backend en OK verde.


Misión 5 — Upgrade y luego rollback (10 puntos)

Simula un incidente de producción y luego anúlalo.

Escenario:

  1. En DEV, pasa portail.replicas a 5:
    powershell
    helm upgrade hedge-dev .\chart -f .\chart\environments\values-dev.yaml --set portail.replicas=5 -n hedge-dev
  2. Comprueba que corren 5 Pods de portail:
    powershell
    kubectl get pods -n hedge-dev -l app.kubernetes.io/component=portail
  3. Consulta el historial:
    powershell
    helm history hedge-dev -n hedge-dev
    Ves al menos 2 revisiones.
  4. Anula el upgrade volviendo a la revisión 1:
    powershell
    helm rollback hedge-dev 1 -n hedge-dev
  5. Comprueba que se ha vuelto a 1 solo Pod portail, y que el historial muestra una nueva revisión de tipo Rollback:
    powershell
    kubectl get pods -n hedge-dev -l app.kubernetes.io/component=portail
    helm history hedge-dev -n hedge-dev

Pregunta a tratar en el informe: ¿cuál es la diferencia fundamental entre helm upgrade --set portail.replicas=5 y kubectl scale deploy/hedge-dev-portail --replicas=5? ¿Por qué Helm prefiere que se pase por él?


Misión 6 — Investigación: reparar 3 templates defectuosos (20 puntos)

La carpeta chart/casses/ contiene tres templates ya escritos que compilan pero introducen cada uno un bug real encontrado en empresa. Debes, para cada uno:

  1. copiarlo a chart/templates/;
  2. reproducir el síntoma descrito al inicio del archivo;
  3. diagnosticar la causa leyendo el mensaje de error;
  4. reparar (modificando el template en chart/templates/, no el original en casses/);
  5. probar que la avería ha desaparecido.
ArchivoComponente añadidoNaturaleza del bug
casse-1-configmap.yamlUn ConfigMap globalColisión de nombre entre releases
casse-2-worker-deployment.yamlUn Deployment workerSelector inmutable violado en el primer helm upgrade
casse-3-cache-deployment.yamlUn Deployment cachePath de valor erróneo (typo silencioso)

Truco de investigación:

powershell
# render SECO de un solo template (no instala nada)
helm template hedge-dev .\chart -f .\chart\environments\values-dev.yaml `
  --show-only templates/casse-3-cache-deployment.yaml --debug

Este comando imprime exactamente lo que Helm enviaría a Kubernetes. Es tu primera herramienta de diagnóstico — úsala sin moderación.


Misión 7 — Bonus: el refinamiento (5 puntos)

A elegir, uno solo basta:

  • a) Añade un hook pre-install (Job) que muestre Bienvenue dans <environnement> en los logs de Helm. La release debe esperar al final del Job antes de continuar.
  • b) Haz que el número de réplicas sea dinámico con un valor de values.yaml que tenga una estructura anidada (por ejemplo portail.autoscaling.enabled, portail.autoscaling.min, portail.autoscaling.max) y genera condicionalmente un HorizontalPodAutoscaler según .enabled.
  • c) Añade un NOTES.txt en templates/ que muestre, después de cada helm install, la URL exacta para abrir el dashboard (con el nodePort correcto según las Values).

Validación automática

Un script te da tu puntuación en cualquier momento:

powershell
.\outils\valider.ps1

Ejemplo de salida en un trabajo a medio hacer:

[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

Si PowerShell se niega a ejecutar el script (l'exécution de scripts est désactivée), usa:

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

Entregables

  1. La carpeta chart/ completa, en estado de marcha (helm lint limpio).
  2. Un RAPPORT.md que contenga:
    • la salida de helm list -A que muestre tus 3 releases;
    • una captura por entorno (3 dashboards de colores);
    • el historial completo de hedge-dev (con upgrade + rollback);
    • para cada avería de la misión 6: comando de diagnóstico, causa, correctivo, prueba;
    • tus respuestas a las preguntas de reflexión.
  3. La salida final de .\outils\valider.ps1.

Preguntas de reflexión

  1. ¿Por qué el campo spec.selector.matchLabels es inmutable en Kubernetes? ¿Qué problema resuelve esta restricción?
  2. Hoy tienes 3 entornos. Mañana el equipo DevSecOps pide un 4.º («pre-prod»). ¿Qué archivos creas y cuáles no tocas?
  3. ¿Cuál es la diferencia entre helm upgrade --set replicas=5 y kubectl scale, desde el punto de vista de la trazabilidad y del rollback?
  4. El portal muestra «backend OK» — ¿por qué esta información es más fiable que un simple kubectl get svc api?
  5. ¿Qué ocurre si eliminas un Pod con kubectl delete pod, habiendo sido creado por un Deployment vía Helm? ¿Helm se entera de la «pérdida»?
  6. Un compañero te propone poner app.kubernetes.io/version: {{ .Chart.AppVersion }} en el matchLabels de un Deployment. ¿Qué le respondes?

Baremo

ElementoPuntos
Misión 1 — Chart válido y lint limpio10
Misión 2 — Templating portail + api20
Misión 3 — Helpers y labels reutilizables15
Misión 4 — Tres entornos lado a lado20
Misión 5 — Upgrade + rollback rastreados10
Misión 6 — Diagnóstico + reparación de las 3 averías20
Calidad del informe y justificación de las elecciones5
Bonus — Misión 7+5
Total100 (+5)

Penalizaciones:

  • −10 por valor de entorno codificado a pelo en un template (replicas: 3 literal en vez de .Values....).
  • −5 por redefinición inútil en un values-<env>.yaml (un valor que no tiene razón de diferir entre entornos).
  • −10 por modificación de un archivo de apps/.

Caja de herramientas Helm

powershell
# ANÁLISIS (ningún despliegue)
helm lint .\chart                                                        # sintaxis + buenas prácticas
helm template <release> .\chart -f <values.yaml>                         # render completo
helm template <release> .\chart -f <values.yaml> --show-only templates/<fichier>   # render puntual
helm template <release> .\chart -f <values.yaml> --debug                 # con trazas
helm show values .\chart                                                 # valores por defecto

# DESPLIEGUE
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>

# OBSERVACIÓN
helm list -A                                                             # todas las releases
helm status <release> -n <ns>
helm history <release> -n <ns>
helm get values <release> -n <ns>                                        # los valores activos
helm get manifest <release> -n <ns>                                      # los manifiestos aplicados

Los 3 reflejos en caso de bug:

  1. Empieza siempre por helm template — es un render SECO, sin riesgo, que muestra exactamente lo que se va a enviar a Kubernetes.
  2. Lee la ruta en el mensaje de error — Helm siempre da el archivo + la línea + el path .Values.* fallido.
  3. helm get manifest te muestra lo que está actualmente en el clúster (útil para comparar con lo que genera tu nuevo template).


ANEXO A — Las aplicaciones

No modifiques ninguno de estos archivos. Cópialos tal cual en las rutas indicadas.

A.1 — El portal (tablero multi-entorno)

Todos los valores mostrados vienen de variables de entorno inyectadas por Helm. La misma imagen se comporta de forma distinta según los env: del Deployment.

Archivo: apps/portail/app.py

python
"""Portail — tablero multi-entorno.

Este Pod muestra el entorno en el que corre (DEV / STAGING / PROD),
la versión de la aplicación, el número de réplicas y el estado del backend.

Todos los valores mostrados vienen de VARIABLES DE ENTORNO inyectadas
por Helm desde values-<env>.yaml. El mismo código se adapta a cada
entorno sin ninguna modificación.
"""

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", "inconnu"),
        "version": os.environ.get("APP_VERSION", "0.0.0"),
        "theme": os.environ.get("THEME_COLOR", "#64748b"),
        "message": os.environ.get("BANNIERE_MESSAGE", "Deploye avec 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():
    """Ruta útil para la validación automática."""
    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"])
    # ... (plantilla HTML completa en el archivo — no se repite aquí para que siga legible)

El archivo completo se suministra en apps/portail/app.py.

Archivo: apps/portail/requirements.txt

text
flask==3.0.3

Archivo: 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 — La api (backend simple)

Archivo: apps/api/app.py

python
"""API — backend simple para el ejemplo Helm."""

import os
import socket
import time

from flask import Flask, jsonify

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

ENV = os.environ.get("ENVIRONMENT", "inconnu")
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)

Archivo: apps/api/requirements.txt

text
flask==3.0.3

Archivo: 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 del Chart

Copia estos archivos y luego sustituye cada TODO por el valor correcto. Las líneas precedidas de # ? son preguntas a resolver: te toca decidir cómo completar el código.

Archivo: chart/Chart.yaml

yaml
# ? Rellena los campos obligatorios de un Chart Helm.
# ? apiVersion debe valer v2 (la v1 está deprecada desde Helm 3).
# ? type es "application" (por oposición a "library").

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

Archivo: chart/values.yaml

yaml
# values.yaml — valores por defecto del chart hedge.
# Cada entorno aporta un archivo values-<env>.yaml que SOBRESCRIBE
# estos valores por encima. Mantén este archivo NEUTRO (ningún valor específico
# de un entorno).

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

Archivo: chart/templates/_helpers.tpl

yaml
{{/*
Nombre completo de un 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 comunes a todos los recursos.
Uso: {{ include "hedge.labels" (dict "root" . "composant" "portail") | nindent 4 }}
*/}}
{{- define "hedge.labels" -}}
# ? rellena los 7 labels pedidos en la Misión 3
app.kubernetes.io/name: TODO
app.kubernetes.io/instance: TODO
# ... continúa ...
{{- end -}}


{{/*
Selector labels: subconjunto ESTABLE de los labels.
Pon aquí SOLO labels que NUNCA cambiarán para una instancia.
*/}}
{{- define "hedge.selectorLabels" -}}
# ? únicamente los 3 labels ESTRICTAMENTE inmutables
{{- end -}}

Archivo: chart/templates/portail-deployment.yaml

yaml
# ? Deployment del portail. Usa:
#   - .Values.portail.replicas
#   - .Values.portail.image.{repository,tag,pullPolicy}
#   - .Values.portail.service.targetPort
#   - .Values.environment
#   - .Values.banniere.{couleur,message}
#   - .Chart.AppVersion (para APP_VERSION)
#   - El NOMBRE del Service api construido con .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
            # ? añade APP_VERSION, THEME_COLOR, BANNIERE_MESSAGE,
            #   BACKEND_URL, REPLICAS_INFO
          readinessProbe:
            httpGet:
              path: /health
              port: TODO
            initialDelaySeconds: 3
            periodSeconds: 5

Archivo: chart/templates/portail-service.yaml

yaml
# ? Service para el portail. Tipo NodePort. Usa la condición
#   {{- if eq .Values.portail.service.type "NodePort" }} ... {{- end }}
#   para incluir "nodePort" SOLO si es realmente un NodePort.
apiVersion: v1
kind: Service
metadata:
  name: TODO
  labels:
    TODO
spec:
  type: TODO
  selector:
    TODO
  ports:
    - port: TODO
      targetPort: TODO
      # ? nodePort únicamente si type == NodePort

Archivo: chart/templates/api-deployment.yaml

yaml
# ? Misma estructura que portail-deployment.yaml, pero:
#   - composant = "api"
#   - variables de entorno: ENVIRONMENT y APP_VERSION solamente
#   - puerto del contenedor = .Values.api.service.targetPort (8000)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: TODO
spec:
  # ... (estructura similar a la del portail) ...

Archivo: chart/templates/api-service.yaml

yaml
# ? Service ClusterIP para la api. Un solo puerto. Sin nodePort.
apiVersion: v1
kind: Service
metadata:
  name: TODO
spec:
  type: TODO
  selector:
    TODO
  ports:
    - port: TODO
      targetPort: TODO

Archivo: chart/environments/values-dev.yaml

yaml
# ? Entorno DEV: 1 réplica, franja azul #2563eb, NodePort 30130.
environment: TODO

banniere:
  message: TODO
  couleur: TODO

portail:
  replicas: TODO
  service:
    nodePort: TODO

api:
  replicas: TODO

Archivo: chart/environments/values-staging.yaml

yaml
# ? Entorno STAGING: 2 réplicas, franja naranja #ea580c, NodePort 30131.
environment: TODO
# ... completa siguiendo el modelo de values-dev.yaml ...

Archivo: chart/environments/values-prod.yaml

yaml
# ? Entorno PROD: 3 réplicas, franja verde #16a34a, NodePort 30132.
environment: TODO
# ...


ANEXO C — Las tres averías a reparar

Cada archivo de abajo está en chart/casses/. No modifiques los originales — cópialos a chart/templates/, reproduce el bug y luego corrige la copia.

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

yaml
# AVERÍA 1
# Síntoma: despliega hedge-dev, luego intenta desplegar hedge-staging
# EN EL MISMO NAMESPACE. La segunda instalación falla con:
#   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"

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

yaml
# AVERÍA 2
# Síntoma: "helm install" funciona. "helm upgrade" con un
# --set environment=recette falla con:
#   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 }}"

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

yaml
# AVERÍA 3
# Síntoma: "helm install" falla con:
#   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 — El script de validación

El archivo outils/valider.ps1 se suministra tal cual. No da ninguna solución — solo una puntuación y el primer punto a corregir. Ejecútalo en cualquier momento:

powershell
.\outils\valider.ps1

Lo que verifica el script:

MisiónCriterios automáticos
1helm lint pasa, Chart.yaml tiene apiVersion: v2, type: application
2helm template produce bien 2 Deployments y 2 Services, nombres prefijados por la release
3_helpers.tpl define los 3 helpers, labels estándar presentes
4Los 3 archivos values-<env>.yaml existen con los valores correctos (env, replicas, puerto, color), las 3 releases están desplegadas
5La release hedge-dev tiene ≥ 2 revisiones y un rollback en el historial
6Ningún hedge-config a pelo, ninguna label variable en matchLabels, ningún .Values.portal (con typo)

El script no ejecuta él mismo ningún comando helm install: te toca a ti desplegar antes de validar.


Curso creado por el Dr. Haythem REHOUMA — Desarrollo y despliegue de soluciones de datos