Voici l'architecture classique qu'on va utiliser :
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ TEST RUNNER │───▶│ YOUR APP │───▶│ DATABASE │
│ (ton framework)│ │ (SUT - System │ │ + REDIS │
│ │ │ Under Test) │ │ │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │
▼ │
┌─────────────────┐ ┌─────▼───────────┐
│ ARTIFACTS │ │ MONITORING │
│ (rapports, │◀──────────────────────────────│ (logs, metrics)│
│ screenshots) │ │ │
└─────────────────┘ └─────────────────┘
qa-infrastructure/
├── docker-compose.yml # Orchestration locale
├── Dockerfile # Image de test
├── environments/
│ ├── local.yml # Config dev local
│ ├── ci.yml # Config CI/CD
│ └── staging.yml # Config staging
├── tests/
│ ├── smoke/ # Tests smoke
│ ├── integration/ # Tests d'intégration
│ └── e2e/ # Tests end-to-end
├── scripts/
│ ├── run-tests.sh # Script d'exécution
│ └── collect-results.sh # Collecte des résultats
├── k8s/
│ ├── namespace.yaml
│ ├── deployments/
│ └── jobs/
└── reports/ # Sortie des résultats
├── junit/
├── coverage/
└── screenshots/
version: '3.8'
services:
# Votre application sous test
app:
image: ${SUT_IMAGE}:${VERSION}
environment:
- DATABASE_URL=postgresql://qa:qa@db:5432/qa_db
- REDIS_URL=redis://redis:6379
- NODE_ENV=test
depends_on:
- db
- redis
ports:
- "8080:8080"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 10s
retries: 5
# Base de données
db:
image: postgres:15-alpine
environment:
POSTGRES_DB: qa_db
POSTGRES_USER: qa
POSTGRES_PASSWORD: qa
volumes:
- ./fixtures/init.sql:/docker-entrypoint-initdb.d/init.sql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U qa"]
interval: 5s
# Cache Redis
redis:
image: redis:7-alpine
# Vos tests
test-runner:
build: .
environment:
- TARGET_URL=http://app:8080
- TEST_SUITE=${TEST_SUITE:-smoke}
- OUTPUT_DIR=/output
volumes:
- ./reports:/output
depends_on:
app:
condition: service_healthy
# Stockage des résultats
minio:
image: minio/minio:latest
command: server /data --console-address ":9001"
environment:
MINIO_ROOT_USER: qa-user
MINIO_ROOT_PASSWORD: qa-password
ports:
- "9000:9000"
- "9001:9001"
volumes:
- minio-data:/data
volumes:
minio-data:
docker-compose up -d - Démarre toutdocker-compose run --rm test-runner - Lance les testsdocker-compose down - Nettoie tout
# Étape 1: Base avec outils communs
FROM alpine:3.18 AS base
RUN apk add --no-cache \
curl \
jq \
bash \
ca-certificates
# Étape 2: Runtime spécifique (exemple Node.js)
FROM node:18-alpine AS nodejs-runtime
RUN apk add --no-cache chromium
ENV CHROME_BIN=/usr/bin/chromium-browser
# Étape 3: Outils de test
FROM nodejs-runtime AS test-tools
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
# Étape 4: Tests
FROM test-tools AS test-runner
COPY tests/ /tests/
COPY scripts/ /scripts/
# Variables d'environnement
ENV TARGET_URL=""
ENV TEST_SUITE="smoke"
ENV OUTPUT_DIR="/output"
ENV HEADLESS="true"
# Point d'entrée flexible
COPY entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
#!/bin/bash
# entrypoint.sh - Point d'entrée flexible
set -euo pipefail
echo "🚀 Starting test execution"
echo "Target: ${TARGET_URL}"
echo "Suite: ${TEST_SUITE}"
echo "Output: ${OUTPUT_DIR}"
# Attendre que l'app soit prête
echo "⏳ Waiting for application..."
timeout 300 bash -c 'until curl -sf $TARGET_URL/health; do sleep 2; done'
# Créer les répertoires de sortie
mkdir -p "$OUTPUT_DIR"/{reports,screenshots,videos}
# Exécuter selon le type de suite
case "$TEST_SUITE" in
"smoke")
echo "🟢 Running smoke tests"
npm run test:smoke
;;
"integration")
echo "🔗 Running integration tests"
npm run test:integration
;;
"e2e")
echo "🎭 Running E2E tests"
npm run test:e2e
;;
*)
echo "🧪 Running custom suite: $TEST_SUITE"
npm run "test:$TEST_SUITE"
;;
esac
echo "✅ Tests completed"
Une base commune + des overrides par environnement :
# docker-compose.override.yml pour CI
version: '3.8'
services:
app:
mem_limit: 512m
restart: "no"
test-runner:
environment:
- CI=true
- BUILD_NUMBER=${CI_BUILD_NUMBER}
- PARALLEL_JOBS=4
mem_limit: 1g
db:
tmpfs:
- /var/lib/postgresql/data # DB en RAM pour CI
# .env.staging
SUT_IMAGE=myapp
VERSION=staging-latest
TARGET_URL=https://staging.myapp.com
TEST_SUITE=smoke,integration
DATABASE_URL=postgresql://staging_user:pass@staging-db:5432/myapp
#!/bin/bash
# deploy-env.sh
ENV=${1:-"local"}
case "$ENV" in
"local")
export COMPOSE_FILE="docker-compose.yml"
;;
"ci")
export COMPOSE_FILE="docker-compose.yml:ci.yml"
;;
"staging")
export COMPOSE_FILE="docker-compose.yml:staging.yml"
;;
esac
echo "🌍 Deploying to: $ENV"
# Démarrer l'environnement
docker-compose --env-file=".env.$ENV" up -d
# Lancer les tests
docker-compose run --rm test-runner
# Nettoyer
docker-compose down --volumes
name: QA Pipeline
on: [push, pull_request]
jobs:
qa-tests:
runs-on: ubuntu-latest
strategy:
matrix:
suite: [smoke, integration, e2e]
steps:
- uses: actions/checkout@v4
- name: Build test image
run: docker build -t qa-runner:${{ github.sha }} .
- name: Start environment
run: |
export SUT_IMAGE=myapp:${{ github.sha }}
docker-compose up -d app db redis
- name: Wait for app
run: timeout 60 bash -c 'until curl -sf http://localhost:8080/health; do sleep 2; done'
- name: Run ${{ matrix.suite }} tests
run: |
docker-compose run --rm \
-e TEST_SUITE=${{ matrix.suite }} \
test-runner
- name: Upload results
uses: actions/upload-artifact@v4
if: always()
with:
name: test-results-${{ matrix.suite }}
path: reports/
- name: Cleanup
if: always()
run: docker-compose down --volumes
stages:
- test
variables:
DOCKER_DRIVER: overlay2
.test-template: &test-template
image: docker:latest
services:
- docker:dind
before_script:
- docker build -t qa-runner:$CI_COMMIT_SHA .
- docker-compose up -d app db redis
- timeout 60 bash -c 'until curl -sf http://app:8080/health; do sleep 2; done'
after_script:
- docker-compose down --volumes
artifacts:
reports:
junit: reports/*.xml
paths:
- reports/
when: always
smoke-tests:
<<: *test-template
stage: test
script:
- docker-compose run --rm -e TEST_SUITE=smoke test-runner
integration-tests:
<<: *test-template
stage: test
script:
- docker-compose run --rm -e TEST_SUITE=integration test-runner
e2e-tests:
<<: *test-template
stage: test
script:
- docker-compose run --rm -e TEST_SUITE=e2e test-runner
apiVersion: batch/v1
kind: Job
metadata:
name: qa-smoke-tests
namespace: qa-testing
spec:
parallelism: 3 # 3 pods en parallèle
completions: 3 # 3 tâches à compléter
backoffLimit: 2 # 2 tentatives max
template:
spec:
restartPolicy: Never
containers:
- name: test-runner
image: qa-runner:latest
env:
- name: TARGET_URL
value: "http://app-service:8080"
- name: TEST_SUITE
value: "smoke"
- name: WORKER_ID
valueFrom:
fieldRef:
fieldPath: metadata.name
resources:
requests:
memory: "256Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "200m"
volumeMounts:
- name: results
mountPath: /output
volumes:
- name: results
persistentVolumeClaim:
claimName: test-results-pvc
apiVersion: batch/v1
kind: CronJob
metadata:
name: nightly-regression
namespace: qa-testing
spec:
schedule: "0 2 * * *" # Tous les jours à 2h
jobTemplate:
spec:
template:
spec:
restartPolicy: Never
containers:
- name: regression-tests
image: qa-runner:nightly
env:
- name: TEST_SUITE
value: "regression"
- name: SLACK_WEBHOOK
valueFrom:
secretKeyRef:
name: qa-secrets
key: slack-webhook
resources:
requests:
memory: "1Gi"
cpu: "500m"
# helm install qa-infra ./qa-chart
# values.yaml
app:
image: myapp:latest
replicas: 2
testRunner:
image: qa-runner:latest
jobs:
smoke:
enabled: true
schedule: "*/30 * * * *"
nightly:
enabled: true
schedule: "0 2 * * *"
resources:
requests:
memory: 256Mi
cpu: 100m
limits:
memory: 512Mi
cpu: 200m
# docker-compose.monitoring.yml
version: '3.8'
services:
prometheus:
image: prom/prometheus:latest
ports:
- "9090:9090"
volumes:
- ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml
grafana:
image: grafana/grafana:latest
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=qa-password
volumes:
- ./monitoring/dashboards:/var/lib/grafana/dashboards
# Collecte des logs
loki:
image: grafana/loki:latest
ports:
- "3100:3100"
# Envoi des logs vers Loki
promtail:
image: grafana/promtail:latest
volumes:
- /var/log:/var/log:ro
- ./monitoring/promtail.yml:/etc/promtail/promtail.yml
// Dans votre test runner
const client = require('prom-client');
const testCounter = new client.Counter({
name: 'qa_tests_total',
help: 'Total number of tests executed',
labelNames: ['suite', 'status', 'environment']
});
const testDuration = new client.Histogram({
name: 'qa_test_duration_seconds',
help: 'Test execution time',
labelNames: ['suite', 'test_name']
});
// À la fin de chaque test
testCounter.inc({ suite: 'smoke', status: 'passed', environment: 'ci' });
testDuration.observe({ suite: 'smoke', test_name: 'login' }, 2.5);
# Dockerfile sécurisé
FROM node:18-alpine
# Créer un utilisateur non-root
RUN addgroup -g 1001 -S qauser && \
adduser -S qauser -u 1001 -G qauser
# Installer les dépendances en tant que root
COPY package*.json ./
RUN npm ci --only=production && \
npm cache clean --force
# Changer vers utilisateur non-root
USER qauser
# Copier le code
COPY --chown=qauser:qauser . .
# Pas d'exposition de ports inutiles
# EXPOSE 8080
# Healthcheck
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1
apiVersion: v1
kind: Pod
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1001
fsGroup: 1001
containers:
- name: test-runner
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
# Volumes temporaires pour écriture
volumeMounts:
- name: tmp
mountPath: /tmp
- name: cache
mountPath: /.cache
volumes:
- name: tmp
emptyDir: {}
- name: cache
emptyDir: {}
# Ne JAMAIS faire ça
ENV API_KEY=abc123 # ❌
# Faire ça à la place
apiVersion: v1
kind: Secret
metadata:
name: qa-secrets
type: Opaque
data:
api-key: YWJjMTIz # base64 encoded
db-password: c2VjcmV0
---
# Dans le pod
env:
- name: API_KEY
valueFrom:
secretKeyRef:
name: qa-secrets
key: api-key
# Docker Compose
docker-compose logs test-runner # Logs du test runner
docker-compose exec test-runner bash # Shell dans le container
docker-compose ps # Status des services
docker-compose top # Processus en cours
# Kubernetes
kubectl logs -f pod/test-runner-abc123 # Logs en temps réel
kubectl exec -it test-runner-abc123 bash # Shell dans le pod
kubectl describe pod test-runner-abc123 # Détails du pod
kubectl get events --sort-by=.metadata.creationTimestamp
curl http://app:8080/healthdocker-compose logs appdocker stats
mem_limit: 2gdocker exec app top#!/bin/bash
# health-check.sh
echo "🏥 QA Infrastructure Health Check"
# Check Docker
docker version >/dev/null || { echo "❌ Docker not running"; exit 1; }
# Check Compose services
echo "📋 Checking services..."
docker-compose ps
# Check app health
if curl -sf http://localhost:8080/health >/dev/null; then
echo "✅ App is healthy"
else
echo "❌ App is not responding"
fi
# Check database
if docker-compose exec -T db pg_isready -U qa >/dev/null; then
echo "✅ Database is ready"
else
echo "❌ Database is not ready"
fi
echo "🎉 Health check complete"
# Développement
docker-compose up -d # Démarrer les services
docker-compose run --rm test-runner # Lancer les tests
docker-compose logs -f app # Voir les logs en temps réel
docker-compose exec app bash # Shell dans le container
docker-compose down --volumes # Tout nettoyer
# Debug
docker-compose ps # Status des services
docker-compose top # Processus actifs
docker-compose config # Valider la config
docker system prune -f # Nettoyer Docker
# Jobs
kubectl create job smoke-test --image=qa-runner:latest
kubectl get jobs -w # Suivre les jobs
kubectl logs job/smoke-test # Logs du job
kubectl delete job smoke-test # Supprimer le job
# Pods
kubectl get pods -o wide # Lister les pods
kubectl describe pod test-runner-123 # Détails du pod
kubectl logs -f test-runner-123 # Logs en temps réel
kubectl exec -it test-runner-123 bash # Shell dans le pod
# Debugging
kubectl get events --sort-by=.metadata.creationTimestamp
kubectl top pods # Utilisation CPU/RAM
kubectl describe node # Infos des nœuds
# Prometheus queries utiles
rate(qa_tests_total[5m]) # Taux de tests par seconde
qa_tests_failed_total / qa_tests_total # Taux d'échec
histogram_quantile(0.95, qa_test_duration_seconds) # 95e percentile des durées
# Grafana
- Dashboard ID: 12345 (QA Overview)
- Alert sur taux d'échec > 10%
- Panel durée moyenne des tests
# Tests qui ne passent pas
curl http://localhost:8080/health # L'app répond ?
docker-compose logs db # Problème DB ?
docker stats # Ressources suffisantes ?
# Performance
docker-compose exec app htop # Utilisation CPU/RAM
docker-compose exec db pgbench -i qa_db # Test perf DB
curl -w "@curl-format.txt" http://app/api # Temps de réponse API
# Nettoyage
docker system prune -a --volumes # Nettoyage complet
kubectl delete --all jobs # Supprimer tous les jobs
kubectl delete pvc --all # Supprimer volumes (ATTENTION!)
# Développement
docker-compose up -d # Démarrer les services
docker-compose run --rm test-runner # Lancer les tests
docker-compose logs -f app # Voir les logs en temps réel
docker-compose exec app bash # Shell dans le container
docker-compose down --volumes # Tout nettoyer
# Debug
docker-compose ps # Status des services
docker-compose top # Processus actifs
docker-compose config # Valider la config
docker system prune -f # Nettoyer Docker
# Jobs
kubectl create job smoke-test --image=qa-runner:latest
kubectl get jobs -w # Suivre les jobs
kubectl logs job/smoke-test # Logs du job
kubectl delete job smoke-test # Supprimer le job
# Pods
kubectl get pods -o wide # Lister les pods
kubectl describe pod test-runner-123 # Détails du pod
kubectl logs -f test-runner-123 # Logs en temps réel
kubectl exec -it test-runner-123 bash # Shell dans le pod
# Debugging
kubectl get events --sort-by=.metadata.creationTimestamp
kubectl top pods # Utilisation CPU/RAM
kubectl describe node # Infos des nœuds
# Prometheus queries utiles
rate(qa_tests_total[5m]) # Taux de tests par seconde
qa_tests_failed_total / qa_tests_total # Taux d'échec
histogram_quantile(0.95, qa_test_duration_seconds) # 95e percentile des durées
# Grafana
- Dashboard ID: 12345 (QA Overview)
- Alert sur taux d'échec > 10%
- Panel durée moyenne des tests
# Tests qui ne passent pas
curl http://localhost:8080/health # L'app répond ?
docker-compose logs db # Problème DB ?
docker stats # Ressources suffisantes ?
# Performance
docker-compose exec app htop # Utilisation CPU/RAM
docker-compose exec db pgbench -i qa_db # Test perf DB
curl -w "@curl-format.txt" http://app/api # Temps de réponse API
# Nettoyage
docker system prune -a --volumes # Nettoyage complet
kubectl delete --all jobs # Supprimer tous les jobs
kubectl delete pvc --all # Supprimer volumes (ATTENTION!)
Vous faites partie d’une équipe QA qui doit tester une application de gestion d’articles (API REST). L’application expose des endpoints HTTP et dépend d’une base PostgreSQL. Votre mission est de mettre en place un environnement de test containerisé et d’y exécuter une première suite de tests automatisés.
Dockerfile pour
construire l’image de test runner. L’image doit contenir Node.js (ou le
langage de votre choix) et les dépendances nécessaires pour exécuter une
suite de tests.
app basé sur une image simple (ex: Node ou
Python API),db basé sur
postgres:15-alpine,test-runner qui dépend de app
et exécute automatiquement une commande de test.db.
/health) afin que le test-runner n’attende
pas indéfiniment.
./reports).
Job Kubernetes (1 pod applicatif, 1 pod DB, 1 pod
test-runner). Fournir les YAML nécessaires.
CronJob pour exécuter
les tests chaque nuit à 2h du matin.
Dockerfile fonctionneldocker-compose.yml capable de démarrer l’environnement completinit.sql)job.yaml, cronjob.yaml)reports/ contenant au moins un rapport de testUtilisez alpine ou slim pour réduire la taille et accélérer vos builds.
Placez vos fichiers de dépendances avant COPY pour tirer parti du cache Docker.
Montez vos répertoires en volumes pour tester en live sans rebuild complet.
Avec docker logs -f, suivez vos services en temps réel.
Ouvrez un shell dans vos conteneurs avec docker exec -it pour déboguer.
Construisez vos tests en plusieurs étapes pour réduire vos images finales.
Un script entrypoint.sh générique rend vos images réutilisables.
Centralisez vos configs dans des fichiers .env pour plus de clarté.
Partagez vos reports/ via volumes pour collecter facilement les résultats.
Exécutez vos conteneurs avec un utilisateur non-root pour plus de sécurité.
Exécutez vos tests ponctuels avec des Jobs K8s.
Automatisez vos régressions nocturnes avec des CronJobs.
Stabilisez vos pods grâce aux readiness et livenessProbes.
Isolez vos environnements de test dans un namespace dédié.
Stockez vos paramètres applicatifs dans des ConfigMaps au lieu de hardcoder.
Attribuez le strict minimum de permissions à vos runners.
Adaptez la capacité de vos tests avec un HorizontalPodAutoscaler.
Ajoutez des sidecars (ex: Fluentbit) pour collecter les logs.
Déployez vos applis tout en continuant vos tests.
Conservez vos rapports via des volumes persistants.
Exposez vos tests comme métriques pour mesurer le succès et la durée.
Créez des dashboards dédiés à vos suites QA.
Regroupez vos logs avec Loki ou Elastic pour accélérer le debug.
Mettez des alertes (taux d’échec >10%) pour réagir rapidement.
Ajoutez du tracing (ex: Jaeger) pour comprendre les chemins de vos tests.
Évitez root dans vos Dockerfiles → attaquable.
Stockez vos clés sensibles dans des Secrets K8s.
Analysez vos images avec trivy ou clair pour détecter des failles.
Appliquez des PodSecurityPolicies pour limiter les privilèges.
Même pour vos environnements QA, activez TLS.
Automatisez build/test avec des workflows YAML.
Réutilisez des templates de jobs pour vos suites QA.
Conservez vos rapports dans des artefacts CI/CD.
Lancez smoke, integration, e2e en parallèle.
Ne déployez en staging que si vos tests passent en CI.
Versionnez vos jeux de données comme du code.
Traitez la flaky test comme un bug prioritaire.
Découpez vos tests en jobs indépendants pour accélérer.
Capturez logs, screenshots, vidéos pour chaque run.
Un test doit donner le même résultat localement et en CI.
docker-compose logs -f → premier réflexe en cas d’échec.
Avec docker inspect ou kubectl describe, récupérez l’état complet.
docker stats / kubectl top pods pour vérifier CPU/RAM.
Un test qui timeout ? Vérifiez d’abord l’endpoint /health.
Configurez des politiques de redémarrage pour éviter les faux négatifs.
Démarrez par Docker Compose avant K8s complet.
Ajoutez la complexité progressivement → valeur métier d’abord.
Considérez Git comme source unique de vérité pour vos envs.
Injectez des pannes volontaires pour tester la résilience.
Mesurez vos gains (temps, stabilité) pour justifier vos investissements QA.