Aller au contenu principal

Bonnes pratiques


1 - Structure et organisation

1.1 Structure recommandée d'un chart

my-chart/
├── Chart.yaml # Métadonnées obligatoires
├── Chart.lock # Lock des dépendances
├── values.yaml # Valeurs par défaut documentées
├── values.schema.json # Validation des values
├── .helmignore # Fichiers à ignorer
├── README.md # Documentation utilisateur
├── CHANGELOG.md # Historique des changements
├── LICENSE # Licence
├── charts/ # Dépendances
├── crds/ # CRDs (si nécessaire)
├── templates/
│ ├── NOTES.txt # Instructions post-install
│ ├── _helpers.tpl # Fonctions helper
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── ingress.yaml
│ ├── configmap.yaml
│ ├── secret.yaml
│ ├── serviceaccount.yaml
│ ├── hpa.yaml
│ ├── pdb.yaml
│ ├── networkpolicy.yaml
│ ├── hooks/
│ │ └── pre-upgrade-job.yaml
│ └── tests/
│ └── test-connection.yaml
└── ci/
├── ci-values.yaml # Values pour CI
└── test-values.yaml # Values pour tests

1.2 Nommage cohérent

# Conventions de nommage
# - Nom du chart: kebab-case (my-app)
# - Fichiers templates: kebab-case (my-deployment.yaml)
# - Helpers: chart-name.helper-name (my-app.labels)
# - Releases: env-app-component (prod-api-backend)

# _helpers.tpl
{{- define "my-app.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}

{{- define "my-app.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}

2 - Values et configuration

2.1 Organisation des values

# values.yaml bien organisé

# ========================================
# Global configuration
# ========================================
global:
imageRegistry: ""
imagePullSecrets: []
storageClass: ""

# ========================================
# Application
# ========================================
replicaCount: 1

image:
repository: nginx
tag: ""
pullPolicy: IfNotPresent

# ========================================
# Naming
# ========================================
nameOverride: ""
fullnameOverride: ""

# ========================================
# Service Account
# ========================================
serviceAccount:
# -- Specifies whether a service account should be created
create: true
# -- Annotations to add to the service account
annotations: {}
# -- The name of the service account to use
name: ""

# ========================================
# Security
# ========================================
podSecurityContext:
fsGroup: 1000

securityContext:
runAsNonRoot: true
runAsUser: 1000
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true

# ========================================
# Networking
# ========================================
service:
type: ClusterIP
port: 80

ingress:
enabled: false
className: ""
annotations: {}
hosts: []
tls: []

# ========================================
# Resources
# ========================================
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 100m
memory: 128Mi

# ========================================
# Scaling
# ========================================
autoscaling:
enabled: false
minReplicas: 1
maxReplicas: 10
targetCPUUtilizationPercentage: 80

# ========================================
# Scheduling
# ========================================
nodeSelector: {}
tolerations: []
affinity: {}

# ========================================
# Dependencies
# ========================================
postgresql:
enabled: false
redis:
enabled: false

2.2 Documentation des values

# Utiliser les commentaires helm-docs
# -- Nombre de réplicas du deployment
replicaCount: 1

image:
# -- Image repository
repository: nginx
# -- Image tag (defaults to chart appVersion)
tag: ""
# -- Image pull policy
# @default -- IfNotPresent
pullPolicy: IfNotPresent

# -- (list) Image pull secrets
# @default -- []
imagePullSecrets: []

2.3 Validation avec JSON Schema

// values.schema.json
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": ["replicaCount", "image"],
"properties": {
"replicaCount": {
"type": "integer",
"minimum": 0,
"description": "Number of replicas"
},
"image": {
"type": "object",
"required": ["repository"],
"properties": {
"repository": {
"type": "string",
"minLength": 1
},
"tag": {
"type": "string"
},
"pullPolicy": {
"type": "string",
"enum": ["Always", "IfNotPresent", "Never"]
}
}
},
"service": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": ["ClusterIP", "NodePort", "LoadBalancer"]
},
"port": {
"type": "integer",
"minimum": 1,
"maximum": 65535
}
}
}
}
}

3 - Templates

3.1 Labels standards

# _helpers.tpl
{{- define "my-app.labels" -}}
helm.sh/chart: {{ include "my-app.chart" . }}
{{ include "my-app.selectorLabels" . }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/part-of: {{ .Chart.Name }}
{{- with .Values.commonLabels }}
{{ toYaml . }}
{{- end }}
{{- end }}

{{- define "my-app.selectorLabels" -}}
app.kubernetes.io/name: {{ include "my-app.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

3.2 Génération conditionnelle

# Toujours vérifier si une ressource doit être créée
{{- if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
# ...
{{- end }}

# Vérifier les valeurs optionnelles
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 2 }}
{{- end }}

# Valeurs par défaut sûres
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"

3.3 DRY (Don't Repeat Yourself)

# _helpers.tpl - Helper réutilisable pour les probes
{{- define "my-app.probes" -}}
{{- if .Values.probes.liveness.enabled }}
livenessProbe:
{{- if .Values.probes.liveness.httpGet }}
httpGet:
path: {{ .Values.probes.liveness.httpGet.path }}
port: {{ .Values.probes.liveness.httpGet.port | default "http" }}
{{- end }}
initialDelaySeconds: {{ .Values.probes.liveness.initialDelaySeconds | default 10 }}
periodSeconds: {{ .Values.probes.liveness.periodSeconds | default 10 }}
timeoutSeconds: {{ .Values.probes.liveness.timeoutSeconds | default 5 }}
failureThreshold: {{ .Values.probes.liveness.failureThreshold | default 3 }}
{{- end }}
{{- if .Values.probes.readiness.enabled }}
readinessProbe:
{{- if .Values.probes.readiness.httpGet }}
httpGet:
path: {{ .Values.probes.readiness.httpGet.path }}
port: {{ .Values.probes.readiness.httpGet.port | default "http" }}
{{- end }}
initialDelaySeconds: {{ .Values.probes.readiness.initialDelaySeconds | default 5 }}
periodSeconds: {{ .Values.probes.readiness.periodSeconds | default 5 }}
{{- end }}
{{- end }}

# Utilisation dans deployment.yaml
containers:
- name: {{ .Chart.Name }}
{{- include "my-app.probes" . | nindent 10 }}

4 - Sécurité

4.1 Pod Security Standards

# values.yaml
podSecurityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
seccompProfile:
type: RuntimeDefault

securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 1000
capabilities:
drop:
- ALL

4.2 Network Policies

# templates/networkpolicy.yaml
{{- if .Values.networkPolicy.enabled }}
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: {{ include "my-app.fullname" . }}
spec:
podSelector:
matchLabels:
{{- include "my-app.selectorLabels" . | nindent 6 }}
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector:
matchLabels:
app.kubernetes.io/name: ingress-nginx
ports:
- protocol: TCP
port: {{ .Values.service.port }}
egress:
- to:
- podSelector:
matchLabels:
app.kubernetes.io/name: postgresql
ports:
- protocol: TCP
port: 5432
# Permettre DNS
- to:
- namespaceSelector: {}
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
{{- end }}

4.3 Secrets management

# Ne jamais hardcoder de secrets dans values.yaml
# Utiliser des références externes

# Option 1: Secret externe
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: {{ .Values.existingSecret | default (include "my-app.fullname" .) }}
key: db-password

# Option 2: Helm secrets plugin (valeurs chiffrées)
# values.yaml.dec -> values.yaml (chiffré avec sops)

# Option 3: External Secrets Operator
# templates/external-secret.yaml
{{- if .Values.externalSecrets.enabled }}
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: {{ include "my-app.fullname" . }}
spec:
refreshInterval: 1h
secretStoreRef:
name: {{ .Values.externalSecrets.secretStore }}
kind: SecretStore
target:
name: {{ include "my-app.fullname" . }}-secrets
data:
- secretKey: db-password
remoteRef:
key: {{ .Values.externalSecrets.path }}
property: password
{{- end }}

5 - CI/CD

5.1 Linting et validation

# .github/workflows/lint-test.yaml
name: Lint and Test Charts

on:
pull_request:
paths:
- 'charts/**'

jobs:
lint-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Set up Helm
uses: azure/setup-helm@v3
with:
version: v3.13.0

- name: Set up chart-testing
uses: helm/chart-testing-[email protected]

- name: Lint charts
run: ct lint --config ct.yaml

- name: Create kind cluster
uses: helm/kind-[email protected]

- name: Install and test charts
run: ct install --config ct.yaml

5.2 Configuration chart-testing

# ct.yaml
remote: origin
target-branch: main
chart-dirs:
- charts
chart-repos:
- bitnami=https://charts.bitnami.com/bitnami
validate-maintainers: false
check-version-increment: true

5.3 Release automatique

# .github/workflows/release.yaml
name: Release Charts

on:
push:
branches:
- main

jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Configure Git
run: |
git config user.name "$GITHUB_ACTOR"
git config user.email "[email protected]"

- name: Install Helm
uses: azure/setup-helm@v3

- name: Run chart-releaser
uses: helm/chart-releaser-[email protected]
env:
CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"

6 - Documentation

6.1 README avec helm-docs

Créez un fichier README.md.gotmpl qui sera utilisé par helm-docs pour générer automatiquement la documentation :

Structure du template :

  • chart.name : Nom du chart
  • chart.description : Description du chart
  • chart.requirementsSection : Section des dépendances
  • chart.valuesSection : Tableau des values généré automatiquement

Exemple de template README.md.gotmpl :

Le template utilise des directives Go template comme :

  • template "chart.name" . pour insérer le nom
  • template "chart.description" . pour la description
  • template "chart.valuesSection" . pour le tableau des values
# Générer le README automatiquement
helm-docs --chart-search-root=charts

# Ou pour un chart spécifique
helm-docs --chart-search-root=charts/my-app

6.2 NOTES.txt informatif

Le fichier templates/NOTES.txt affiche des informations après l'installation :

Contenu typique :

  • Nom de la release et version
  • Instructions d'accès (URL si ingress activé, port-forward sinon)
  • Liens vers la documentation

Éléments Go template utilisés :

TemplateDescription
.Chart.NameNom du chart
.Release.NameNom de la release
.Chart.VersionVersion du chart
.Chart.AppVersionVersion de l'application
.Values.ingress.enabledVérifier si ingress activé
.Values.service.portPort du service

Bonnes pratiques NOTES.txt :

  • Afficher les informations de version
  • Donner les instructions d'accès selon la configuration
  • Inclure des liens utiles (docs, issues)
  • Utiliser des emojis pour la lisibilité

7 - Checklist avant release

## Pre-release Checklist

### Chart.yaml
- [ ] Version incrémentée (SemVer)
- [ ] appVersion mis à jour
- [ ] Description claire
- [ ] Maintainers à jour

### values.yaml
- [ ] Valeurs par défaut sensées
- [ ] Commentaires explicatifs
- [ ] Pas de secrets en dur

### Templates
- [ ] Labels standards appliqués
- [ ] Resources requests/limits définis
- [ ] Security context configuré
- [ ] Probes configurées

### Tests
- [ ] helm lint passe
- [ ] helm template fonctionne
- [ ] Tests unitaires passent
- [ ] Tests d'intégration passent

### Documentation
- [ ] README à jour
- [ ] CHANGELOG mis à jour
- [ ] NOTES.txt informatif

Résumé

Dans ce chapitre, nous avons couvert les bonnes pratiques :

  • Structure et organisation des charts
  • Values bien documentées et validées
  • Templates DRY et maintenables
  • Sécurité (Pod Security, Network Policies)
  • CI/CD avec linting et tests automatiques
  • Documentation complète

Prochaine étape

Dans le prochain chapitre, nous mettrons en pratique avec des Exercices et Projets.

→ Chapitre suivant : Exercices et Projets


← Retour à la table des matières