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 chartchart.description: Description du chartchart.requirementsSection: Section des dépendanceschart.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 nomtemplate "chart.description" .pour la descriptiontemplate "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 :
| Template | Description |
|---|---|
.Chart.Name | Nom du chart |
.Release.Name | Nom de la release |
.Chart.Version | Version du chart |
.Chart.AppVersion | Version de l'application |
.Values.ingress.enabled | Vérifier si ingress activé |
.Values.service.port | Port 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