Cloud & Déploiement angularforall.com

- Angular sur Kubernetes : déploiement avec Helm Charts

Kubernetes Helm Angular Deploiement Ingress-Nginx Cert-Manager Hpa Configmap Argocd Gitops Devops Rollback Multi-Environnements Tls
Angular sur Kubernetes : déploiement avec Helm Charts

Déployez votre app Angular sur Kubernetes avec Helm : chart complet, ingress nginx, secrets, autoscaling HPA et release management multi-environnements.

Architecture Kubernetes + Helm pour Angular

Déployer une application Angular sur Kubernetes consiste à empaqueter le bundle statique généré par ng build dans une image Docker servie par nginx, puis à orchestrer l'ensemble (Deployment, Service, Ingress, autoscaling, secrets) via un chart Helm. Helm agit comme un gestionnaire de paquets : un seul chart, plusieurs releases paramétrées pour dev, staging et production.

Vue d'ensemble du déploiement

                       Internet
                          │
                          ▼
            ┌─────────────────────────────┐
            │   Ingress nginx + TLS       │  ← cert-manager (Let's Encrypt)
            │   app.example.com           │
            └──────────────┬──────────────┘
                           │
                           ▼
                ┌──────────────────────┐
                │  Service (ClusterIP) │  port 80 → 8080
                └──────────┬───────────┘
                           │
            ┌──────────────┼──────────────┐
            ▼              ▼              ▼
       ┌────────┐    ┌────────┐    ┌────────┐
       │ Pod #1 │    │ Pod #2 │    │ Pod #N │  ← HPA (CPU 70 %)
       │ nginx  │    │ nginx  │    │ nginx  │
       │ Angular│    │ Angular│    │ Angular│
       └────┬───┘    └────┬───┘    └────┬───┘
            │             │             │
            └─────────────┴─────────────┘
                          │
                          ▼
                   ┌────────────┐
                   │ ConfigMap  │  ← env.js runtime
                   │ Secret     │  ← API keys
                   └────────────┘

Pourquoi Helm et pas du YAML brut ?

Approche YAML brut (kubectl apply) Helm Chart
Multi-environnements Copier-coller 3 dossiers 1 chart + N values.yaml
Versionning Git tags manuels Chart.yaml.version + helm history
Rollback kubectl rollout undo par ressource helm rollback <release> 3 en 1 commande
Réutilisation Aucune Chart publiable sur OCI registry
Templating envsubst, kustomize Go templates natifs

Coûts mensuels approximatifs (cluster managé)

ProviderControl plane2 nodes (t3.medium / e2-medium)Total / mois
AWS EKS~73 $~60 $~135 $
GCP GKE Standard~73 $~50 $~125 $
Azure AKS0 $ (free tier)~70 $~70 $
DigitalOcean DOKS0 $~24 $ (2 × s-2vcpu)~24 $
Astuce coût : pour démarrer, AKS et DigitalOcean DOKS offrent le control plane gratuitement. Vous ne payez que les workers. À l'inverse, EKS et GKE Standard facturent ~73 $/mois rien que pour l'API server.

Créer le chart Helm — structure et fichiers

Helm fournit un scaffold prêt à l'emploi via helm create. On supprime ensuite les templates inutiles (ServiceAccount par défaut, tests basiques) et on adapte au contexte Angular SPA.

Initialisation du chart

# Prérequis : Helm 3.14+, kubectl, accès cluster
helm version
kubectl get nodes

# Génération du squelette
helm create angular-app
cd angular-app

# Structure générée
tree
# .
# ├── Chart.yaml          ← métadonnées du chart
# ├── values.yaml         ← valeurs par défaut
# ├── charts/             ← sous-charts (vide ici)
# └── templates/
#     ├── _helpers.tpl    ← fonctions Go template
#     ├── deployment.yaml
#     ├── service.yaml
#     ├── ingress.yaml
#     ├── hpa.yaml
#     ├── serviceaccount.yaml
#     ├── NOTES.txt
#     └── tests/

Chart.yaml — métadonnées et version

# Chart.yaml — décrit le chart lui-même
apiVersion: v2
name: angular-app
description: Helm chart pour déployer une application Angular sur Kubernetes
type: application

# version : version du chart (incrémenter à chaque modif des templates)
version: 1.0.0

# appVersion : version de l'application Angular packagée
# Récupérée souvent depuis package.json côté CI
appVersion: "19.0.4"

keywords:
  - angular
  - frontend
  - spa
  - nginx

maintainers:
  - name: AngularForAll
    email: contact@angularforall.com
    url: https://angularforall.com

# Helm 3.7+ : permet de publier sur OCI registry (ECR, GHCR, ACR)
home: https://angularforall.com
sources:
  - https://github.com/votre-org/angular-app

values.yaml — toutes les valeurs paramétrables

# values.yaml — défauts utilisés si rien n'est surchargé
replicaCount: 2

image:
  repository: ghcr.io/votre-org/angular-app
  pullPolicy: IfNotPresent
  tag: ""              # par défaut = .Chart.AppVersion

imagePullSecrets:
  - name: ghcr-registry-secret

nameOverride: ""
fullnameOverride: ""

# Configuration runtime injectée via ConfigMap (window.__env)
runtimeConfig:
  apiUrl: "https://api.example.com"
  enableAnalytics: false
  featureFlags:
    newDashboard: false

# Secrets référencés (créés hors chart pour sécurité)
secrets:
  apiKeySecretName: angular-api-key
  apiKeySecretKey: API_KEY

service:
  type: ClusterIP
  port: 80
  targetPort: 8080      # port exposé par nginx dans le conteneur

ingress:
  enabled: true
  className: nginx
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
  hosts:
    - host: app.example.com
      paths:
        - path: /
          pathType: Prefix
  tls:
    - secretName: angular-app-tls
      hosts:
        - app.example.com

resources:
  limits:
    cpu: 500m
    memory: 256Mi
  requests:
    cpu: 100m
    memory: 64Mi

autoscaling:
  enabled: true
  minReplicas: 2
  maxReplicas: 10
  targetCPUUtilizationPercentage: 70
  targetMemoryUtilizationPercentage: 80

probes:
  liveness:
    path: /healthz
    initialDelaySeconds: 10
  readiness:
    path: /healthz
    initialDelaySeconds: 5

nodeSelector: {}
tolerations: []
affinity: {}
Le champ image.tag: "" volontairement vide laisse Helm utiliser .Chart.AppVersion par défaut. Cela force la cohérence entre la version applicative et l'image Docker pushée.

Image Docker Angular — Dockerfile multi-stage

# Dockerfile à la racine du projet Angular
# Étape 1 : build du bundle de production
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci --no-audit --no-fund
COPY . .
RUN npm run build -- --configuration=production

# Étape 2 : runtime nginx (image finale ~25 MB)
FROM nginx:1.27-alpine
# Configuration nginx custom (gzip, cache, fallback SPA)
COPY nginx.conf /etc/nginx/conf.d/default.conf
# Bundle Angular
COPY --from=build /app/dist/angular-app/browser /usr/share/nginx/html
# Script env.js sera écrasé au runtime par le ConfigMap
COPY env.template.js /usr/share/nginx/html/env.js
EXPOSE 8080
CMD ["nginx", "-g", "daemon off;"]

Templates Deployment et Service

Le Deployment décrit les pods nginx servant le bundle Angular. Avec Helm, chaque champ est paramétrable via la syntaxe Go template {{ .Values.* }}.

templates/deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "angular-app.fullname" . }}
  labels:
    {{- include "angular-app.labels" . | nindent 4 }}
spec:
  # Si HPA actif, on laisse l'autoscaler gérer replicas
  {{- if not .Values.autoscaling.enabled }}
  replicas: {{ .Values.replicaCount }}
  {{- end }}
  selector:
    matchLabels:
      {{- include "angular-app.selectorLabels" . | nindent 6 }}
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1            # 1 pod en plus pendant le déploiement
      maxUnavailable: 0      # zéro downtime
  template:
    metadata:
      annotations:
        # Force le rollout du pod si le ConfigMap change
        checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
      labels:
        {{- include "angular-app.selectorLabels" . | nindent 8 }}
    spec:
      {{- with .Values.imagePullSecrets }}
      imagePullSecrets:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      containers:
        - name: angular
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          ports:
            - name: http
              containerPort: {{ .Values.service.targetPort }}
              protocol: TCP
          # Probes : crucial pour le rolling update zéro-downtime
          livenessProbe:
            httpGet:
              path: {{ .Values.probes.liveness.path }}
              port: http
            initialDelaySeconds: {{ .Values.probes.liveness.initialDelaySeconds }}
            periodSeconds: 10
          readinessProbe:
            httpGet:
              path: {{ .Values.probes.readiness.path }}
              port: http
            initialDelaySeconds: {{ .Values.probes.readiness.initialDelaySeconds }}
            periodSeconds: 5
          resources:
            {{- toYaml .Values.resources | nindent 12 }}
          # Injection de la clé API depuis Kubernetes Secret
          env:
            - name: API_KEY
              valueFrom:
                secretKeyRef:
                  name: {{ .Values.secrets.apiKeySecretName }}
                  key:  {{ .Values.secrets.apiKeySecretKey }}
          # Montage du ConfigMap runtime en remplacement de env.js
          volumeMounts:
            - name: runtime-config
              mountPath: /usr/share/nginx/html/env.js
              subPath: env.js
              readOnly: true
      volumes:
        - name: runtime-config
          configMap:
            name: {{ include "angular-app.fullname" . }}-config

templates/service.yaml

apiVersion: v1
kind: Service
metadata:
  name: {{ include "angular-app.fullname" . }}
  labels:
    {{- include "angular-app.labels" . | nindent 4 }}
spec:
  type: {{ .Values.service.type }}
  ports:
    - port: {{ .Values.service.port }}
      targetPort: http
      protocol: TCP
      name: http
  selector:
    {{- include "angular-app.selectorLabels" . | nindent 4 }}

templates/configmap.yaml — config runtime Angular

apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ include "angular-app.fullname" . }}-config
  labels:
    {{- include "angular-app.labels" . | nindent 4 }}
data:
  # env.js chargé par index.html AVANT le bootstrap Angular
  # Permet de changer apiUrl sans rebuild de l'image Docker
  env.js: |
    (function (window) {
      window.__env = window.__env || {};
      window.__env.apiUrl          = "{{ .Values.runtimeConfig.apiUrl }}";
      window.__env.enableAnalytics = {{ .Values.runtimeConfig.enableAnalytics }};
      window.__env.featureFlags = {
        newDashboard: {{ .Values.runtimeConfig.featureFlags.newDashboard }}
      };
    }(this));
Pattern runtime config : dans index.html, ajoutez <script src="env.js"></script> avant le bundle Angular. Vos services lisent ensuite (window as any).__env.apiUrl. Une seule image Docker fonctionne pour tous les environnements — le ConfigMap injecte les bons paramètres au déploiement.

Ingress nginx, TLS et cert-manager

L'Ingress expose l'application sur Internet via un nom de domaine. Combiné à cert-manager, il provisionne automatiquement les certificats Let's Encrypt et les renouvelle tous les 60 jours.

Installation préalable des dépendances cluster

# 1) ingress-nginx — controller Ingress officiel
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
helm upgrade --install ingress-nginx ingress-nginx/ingress-nginx \
  --namespace ingress-nginx --create-namespace \
  --set controller.service.type=LoadBalancer

# Récupérer l'IP publique du LoadBalancer (à pointer en DNS A record)
kubectl -n ingress-nginx get svc ingress-nginx-controller \
  -o jsonpath='{.status.loadBalancer.ingress[0].ip}'

# 2) cert-manager — émission automatique des certificats TLS
helm repo add jetstack https://charts.jetstack.io
helm repo update
helm upgrade --install cert-manager jetstack/cert-manager \
  --namespace cert-manager --create-namespace \
  --set crds.enabled=true \
  --version v1.16.1

ClusterIssuer Let's Encrypt (production)

# cluster-issuer.yaml — appliquer une seule fois sur le cluster
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: admin@example.com
    privateKeySecretRef:
      name: letsencrypt-prod-key
    solvers:
      - http01:
          ingress:
            class: nginx

# kubectl apply -f cluster-issuer.yaml

templates/ingress.yaml

{{- if .Values.ingress.enabled -}}
{{- $fullName := include "angular-app.fullname" . -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: {{ $fullName }}
  labels:
    {{- include "angular-app.labels" . | nindent 4 }}
  {{- with .Values.ingress.annotations }}
  annotations:
    {{- toYaml . | nindent 4 }}
  {{- end }}
spec:
  ingressClassName: {{ .Values.ingress.className }}
  {{- if .Values.ingress.tls }}
  tls:
    {{- range .Values.ingress.tls }}
    - hosts:
        {{- range .hosts }}
        - {{ . | quote }}
        {{- end }}
      secretName: {{ .secretName }}
    {{- end }}
  {{- end }}
  rules:
    {{- range .Values.ingress.hosts }}
    - host: {{ .host | quote }}
      http:
        paths:
          {{- range .paths }}
          - path: {{ .path }}
            pathType: {{ .pathType }}
            backend:
              service:
                name: {{ $fullName }}
                port:
                  number: {{ $.Values.service.port }}
          {{- end }}
    {{- end }}
{{- end }}

Lors du helm install, cert-manager détecte l'annotation cert-manager.io/cluster-issuer et déclenche automatiquement la résolution ACME HTTP-01. Un certificat valide est livré dans le Secret angular-app-tls en moins de 60 secondes.

Pour les environnements de staging, utilisez letsencrypt-staging comme issuer afin d'éviter d'atteindre le rate limit Let's Encrypt (5 certificats par domaine et par semaine en production).

Autoscaling avec HorizontalPodAutoscaler

Le HorizontalPodAutoscaler (HPA) ajoute ou supprime des pods en fonction de la charge CPU et mémoire. Pour qu'il fonctionne, le metrics-server doit être installé sur le cluster (présent par défaut sur GKE/AKS, à installer manuellement sur EKS).

Vérifier metrics-server

# Test : si la commande renvoie des valeurs, metrics-server fonctionne
kubectl top nodes
kubectl top pods -A

# Sinon (notamment sur EKS) :
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml

templates/hpa.yaml

{{- if .Values.autoscaling.enabled }}
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: {{ include "angular-app.fullname" . }}
  labels:
    {{- include "angular-app.labels" . | nindent 4 }}
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: {{ include "angular-app.fullname" . }}
  minReplicas: {{ .Values.autoscaling.minReplicas }}
  maxReplicas: {{ .Values.autoscaling.maxReplicas }}
  metrics:
    {{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
    {{- end }}
    {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
    - type: Resource
      resource:
        name: memory
        target:
          type: Utilization
          averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
    {{- end }}
  # Comportement : éviter le yo-yo (scale up rapide, scale down progressif)
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 60
      policies:
        - type: Percent
          value: 100        # peut doubler le nombre de pods en 60s
          periodSeconds: 60
    scaleDown:
      stabilizationWindowSeconds: 300   # attend 5 min avant de réduire
      policies:
        - type: Percent
          value: 50          # supprime au max 50 % des pods par période
          periodSeconds: 60
{{- end }}

Tester l'autoscaling sous charge

# Générer du trafic avec hey (https://github.com/rakyll/hey)
hey -z 5m -c 100 https://app.example.com/

# Observer la montée en charge
kubectl get hpa -w

# NAME           REFERENCE                 TARGETS         MINPODS  MAXPODS  REPLICAS
# angular-app    Deployment/angular-app    52%/70%, 38%/80%   2        10        2
# angular-app    Deployment/angular-app    89%/70%, 41%/80%   2        10        4
# angular-app    Deployment/angular-app    74%/70%, 45%/80%   2        10        6
Stabilization windows : les fenêtres scaleUp: 60s et scaleDown: 300s empêchent l'effet yo-yo. Le scale-up reste réactif pour absorber les pics, mais le scale-down attend 5 minutes de stabilité avant de retirer des pods — vital pour les sessions utilisateur Angular.

Multi-environnements : values dev/staging/prod

Un seul chart Helm, trois fichiers de surcharge. Chaque environnement vit dans son propre namespace Kubernetes pour isoler ressources, secrets et quotas.

values-dev.yaml

# env/values-dev.yaml — environnement de développement
replicaCount: 1

image:
  repository: ghcr.io/votre-org/angular-app
  tag: develop                          # branche develop poussée par CI

runtimeConfig:
  apiUrl: "https://dev-api.example.com"
  enableAnalytics: false
  featureFlags:
    newDashboard: true                  # toutes les features flags activées

ingress:
  hosts:
    - host: dev.app.example.com
      paths:
        - path: /
          pathType: Prefix
  tls:
    - secretName: angular-app-dev-tls
      hosts:
        - dev.app.example.com
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-staging  # certificats de test

resources:
  limits:   { cpu: 200m, memory: 128Mi }
  requests: { cpu: 50m,  memory: 32Mi  }

autoscaling:
  enabled: false                        # pas d'autoscaling en dev

values-staging.yaml

# env/values-staging.yaml — pré-production iso-prod
replicaCount: 2

image:
  tag: "1.4.0-rc.1"                     # release candidate avant prod

runtimeConfig:
  apiUrl: "https://staging-api.example.com"
  enableAnalytics: true
  featureFlags:
    newDashboard: true

ingress:
  hosts:
    - host: staging.app.example.com
      paths:
        - path: /
          pathType: Prefix
  tls:
    - secretName: angular-app-staging-tls
      hosts:
        - staging.app.example.com

autoscaling:
  enabled: true
  minReplicas: 2
  maxReplicas: 4
  targetCPUUtilizationPercentage: 75

values-prod.yaml

# env/values-prod.yaml — production
replicaCount: 3

image:
  tag: "1.4.0"                          # tag immuable, jamais latest

runtimeConfig:
  apiUrl: "https://api.example.com"
  enableAnalytics: true
  featureFlags:
    newDashboard: false                 # rollout progressif via flag

ingress:
  hosts:
    - host: app.example.com
      paths:
        - path: /
          pathType: Prefix
  tls:
    - secretName: angular-app-prod-tls
      hosts:
        - app.example.com

resources:
  limits:   { cpu: 1000m, memory: 512Mi }
  requests: { cpu: 200m,  memory: 128Mi }

autoscaling:
  enabled: true
  minReplicas: 3
  maxReplicas: 20
  targetCPUUtilizationPercentage: 70

# Anti-affinity : éviter que tous les pods soient sur le même node
affinity:
  podAntiAffinity:
    preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        podAffinityTerm:
          labelSelector:
            matchExpressions:
              - key: app.kubernetes.io/name
                operator: In
                values: [angular-app]
          topologyKey: kubernetes.io/hostname

Commandes de déploiement par environnement

# Création des namespaces (une fois)
kubectl create namespace angular-dev
kubectl create namespace angular-staging
kubectl create namespace angular-prod

# Création des secrets de registry et API key (par namespace)
kubectl create secret docker-registry ghcr-registry-secret \
  --docker-server=ghcr.io \
  --docker-username=USER --docker-password=$GHCR_TOKEN \
  --namespace angular-prod

kubectl create secret generic angular-api-key \
  --from-literal=API_KEY=$PROD_API_KEY \
  --namespace angular-prod

# Déploiement DEV
helm upgrade --install angular-dev ./angular-app \
  --namespace angular-dev \
  --values env/values-dev.yaml \
  --wait --timeout 5m

# Déploiement STAGING
helm upgrade --install angular-staging ./angular-app \
  --namespace angular-staging \
  --values env/values-staging.yaml \
  --wait --timeout 5m

# Déploiement PROD avec dry-run préalable
helm upgrade --install angular-prod ./angular-app \
  --namespace angular-prod \
  --values env/values-prod.yaml \
  --dry-run --debug

helm upgrade --install angular-prod ./angular-app \
  --namespace angular-prod \
  --values env/values-prod.yaml \
  --atomic --wait --timeout 10m
Le flag --atomic est crucial en production : si le déploiement échoue à mi-parcours (timeout, probe en erreur), Helm rollback automatiquement vers la version précédente. Combiné à --wait, vous obtenez une garantie de zéro état partiel.

GitOps avec ArgoCD (alternative à helm upgrade manuel)

# argocd-app-prod.yaml — déclaration de l'app à syncer depuis Git
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: angular-prod
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/votre-org/angular-app-deploy.git
    targetRevision: main
    path: charts/angular-app
    helm:
      valueFiles:
        - ../../env/values-prod.yaml
  destination:
    server: https://kubernetes.default.svc
    namespace: angular-prod
  syncPolicy:
    automated:
      prune: true                       # supprime les ressources orphelines
      selfHeal: true                    # corrige les drifts manuels
    syncOptions:
      - CreateNamespace=true
      - PrunePropagationPolicy=foreground

# kubectl apply -f argocd-app-prod.yaml

Release management : upgrade, rollback, helm test

Helm conserve l'historique de chaque release dans un Secret Kubernetes. Cela permet de revenir à n'importe quelle version précédente en une seule commande, sans toucher à kubectl.

Inspecter l'historique d'une release

# Liste des releases sur tous les namespaces
helm list -A

# Historique des révisions d'une release
helm history angular-prod -n angular-prod

# REVISION  UPDATED                   STATUS      CHART              APP VERSION
# 1         2026-04-12 14:22:18       superseded  angular-app-1.0.0  19.0.2
# 2         2026-04-25 09:15:03       superseded  angular-app-1.1.0  19.0.3
# 3         2026-05-02 10:48:55       superseded  angular-app-1.2.0  19.0.4
# 4         2026-05-08 11:30:12       deployed    angular-app-1.3.0  19.0.4

# Inspecter les valeurs effectives d'une révision donnée
helm get values angular-prod -n angular-prod --revision 3
helm get manifest angular-prod -n angular-prod --revision 3

Rollback en 30 secondes

# Rollback à la révision précédente (= dernière "superseded")
helm rollback angular-prod -n angular-prod

# Rollback à une révision spécifique
helm rollback angular-prod 3 -n angular-prod --wait --timeout 5m

# Vérifier
helm history angular-prod -n angular-prod
# REVISION  UPDATED                   STATUS      CHART              APP VERSION
# 4         2026-05-08 11:30:12       superseded  angular-app-1.3.0  19.0.4
# 5         2026-05-08 11:42:01       deployed    angular-app-1.2.0  19.0.4   ← rollback

templates/tests/test-connection.yaml — helm test

apiVersion: v1
kind: Pod
metadata:
  name: "{{ include "angular-app.fullname" . }}-test-connection"
  labels:
    {{- include "angular-app.labels" . | nindent 4 }}
  annotations:
    "helm.sh/hook": test
    "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
  restartPolicy: Never
  containers:
    - name: smoke-test
      image: curlimages/curl:8.10.1
      command: ['sh', '-c']
      args:
        - |
          set -e
          # Vérifie que le service répond bien
          curl -fsS http://{{ include "angular-app.fullname" . }}:{{ .Values.service.port }}/healthz
          echo "✓ /healthz OK"
          # Vérifie que index.html est servi
          curl -fsS http://{{ include "angular-app.fullname" . }}:{{ .Values.service.port }}/ | grep -q '<app-root>'
          echo "✓ index.html contains <app-root>"
          # Vérifie que env.js contient bien la bonne apiUrl
          curl -fsS http://{{ include "angular-app.fullname" . }}:{{ .Values.service.port }}/env.js | grep -q '{{ .Values.runtimeConfig.apiUrl }}'
          echo "✓ env.js contains correct apiUrl"

Lancer les tests après chaque déploiement

# Tests post-déploiement
helm test angular-prod -n angular-prod

# NAME: angular-prod
# LAST DEPLOYED: Fri May  8 11:42:01 2026
# NAMESPACE: angular-prod
# STATUS: deployed
# TEST SUITE:     angular-prod-test-connection
# Last Started:   Fri May  8 11:43:15 2026
# Last Completed: Fri May  8 11:43:18 2026
# Phase:          Succeeded

Lint et validation avant push

# Vérification syntaxique du chart
helm lint ./angular-app

# Rendu local du template avec values prod (sans déployer)
helm template angular-prod ./angular-app \
  --values env/values-prod.yaml \
  --namespace angular-prod \
  --debug

# Diff entre l'état actuel et la prochaine release (plugin helm-diff)
helm plugin install https://github.com/databus23/helm-diff
helm diff upgrade angular-prod ./angular-app \
  --values env/values-prod.yaml \
  --namespace angular-prod
Checklist mise en production :
  • Tag image Docker immuable (1.4.0, jamais latest)
  • helm lint et helm template --debug sans erreur
  • helm diff upgrade revu et validé
  • Secrets API créés dans le namespace cible
  • Probes liveness/readiness pointant sur /healthz
  • Resource requests/limits définis (jamais à vide)
  • HPA actif avec minReplicas: 3 en prod
  • PodAntiAffinity pour répartir sur plusieurs nodes
  • --atomic --wait sur la commande de release
  • helm test passe avec succès
  • Sauvegarde de la révision N-1 documentée pour rollback
Sécurité Kubernetes : activez Pod Security Standards (restricted) sur vos namespaces production, scannez les images avec Trivy ou Grype dans la CI, et n'utilisez jamais le ServiceAccount default — créez-en un dédié avec les RBAC minimums nécessaires.

Conclusion

Helm transforme un déploiement Kubernetes Angular en un workflow industriel : un chart unique, plusieurs values-*.yaml par environnement, un helm upgrade --atomic reproductible et un helm rollback en une commande. Les pièces fondamentales — Deployment avec rolling update, Service ClusterIP, Ingress nginx, cert-manager pour TLS, HPA pour la charge, ConfigMap pour la config runtime — composent une architecture production-grade en moins de 500 lignes de YAML templatées.

La prochaine étape consiste à brancher ce chart dans un pipeline GitOps avec ArgoCD ou Flux : chaque commit sur la branche main déclenche un sync automatique. Combiné à un dépôt séparé pour les manifests (pattern repo applicatif vs repo de déploiement), vous obtenez traçabilité totale, rollback Git natif, et zéro accès kubectl direct sur la production. Côté coût, partez d'un cluster AKS ou DOKS (control plane gratuit) avec 2 nodes pour minimiser la facture en phase d'apprentissage.

Récapitulatif des bonnes pratiques :
  • Empaqueter Angular dans une image nginx multi-stage (~25 MB finale)
  • Externaliser apiUrl via ConfigMap + window.__env — une image, plusieurs envs
  • Utiliser --atomic --wait en production pour rollback automatique en cas d'échec
  • Versionner Chart.yaml à chaque modification de templates, séparé de appVersion
  • Activer cert-manager + Let's Encrypt pour des certificats TLS gratuits et auto-renouvelés
  • Configurer HPA avec scaleDown.stabilizationWindowSeconds: 300 pour éviter le yo-yo
  • Tester chaque release avec helm test (smoke tests via curl dans un Pod éphémère)
  • Adopter ArgoCD pour le sync GitOps automatique au lieu de helm upgrade manuel
  • Choisir AKS ou DOKS pour le free tier control plane si le budget est serré (~70 $/mois total)

Partager