Skip to content

AWS Migration: Modernisation d'un Legacy PHP Monolith

Published:
| 11 min read

Section A : Présentation du Projet

Approche Globale

Lorsque je commence à réfléchir à tout ce qui touche à l’infrastructure, plusieurs facteurs clés me viennent à l’esprit : le budget, le temps, l’effort disponible, et bien sûr, les tradeoffs associés à chaque décision. À partir de ces éléments, nous pouvons commencer à architecturer l’infrastructure. Pour ce legacy project, je pense que la priorité de cette migration est de découpler l’architecture monolithique existante et de faire évoluer l’application vers un modèle stateless. Cela signifie séparer strictement la compute layer de la data layer et du file storage. Si la décomposition en microservices est une option à long terme, la première étape immédiate doit être la standardisation de l’infrastructure. Passer directement aux microservices depuis un legacy monolith est souvent un overkill, sauf si de nouveaux besoins métier le justifient. Une fois le monolith containerisé et l’infrastructure modernisée sur AWS, le découplage architectural futur devient beaucoup plus simple. De plus, nous auditerons les versions actuelles de PHP et de MariaDB, en les mettant à jour vers des releases stables si nécessaire pour atténuer tout risque de sécurité potentiel.

Risques et Défis Principaux

Vue d’Ensemble de l’Architecture

Target AWS Architecture Diagram

Section B : Plan d’Action Détaillé

Phase : Analyse et Préparation

TâcheResponsableTemps EstiméPrérequis
Auditer le code, les dependencies et la compatibilité des versions PHP/MariaDBDev / DevOps2 JoursAccès au source code
Définir le budget infrastructure et les décisions d’architectureDevOps2 JoursAucun
Provisionner le compte AWS et configurer les billing alertsDevOps0.5 JourBudget approuvé
Sélectionner et finaliser les outils infrastructure (Terraform, plateforme CI/CD, etc.)DevOps0.5 JoursBudget approuvé
Établir les canaux de communication Dev/DevOpsDevOps / Dev0.5 JoursAucun

Phase : Application Refactoring

TâcheResponsableTemps EstiméPrérequis
Créer une Git branch isolée pour la migration et mettre à jour l’application pour utiliser des environment variablesDeveloper0.5 JoursAccès au version control
Mettre à jour la version PHP et résoudre les dependency conflictsDeveloper2 JoursAudit du code terminé
Intégrer l’AWS SDK et mettre à jour la logique de file upload/lecture pour utiliser les S3 presigned URLsDeveloper1 JourPHP upgrade terminé, Lambda endpoint disponible

Phase : Infrastructure Provisioning

TâcheResponsableTemps EstiméPrérequis
Provisionner les fondations réseau : VPC, subnets, security groups et NAT GatewayDevOps1 JourCompte AWS prêt
Provisionner les S3 buckets avec des IAM roles et bucket policies sécurisésDevOps0.5 JourVPC configuré
Déployer une Lambda function pour générer des S3 presigned URLs pour l’applicationDevOps0.5 JourS3 buckets créés
Provisionner l’instance Amazon RDS (MariaDB)DevOps0.25 JourVPC et security groups configurés
Configurer un AWS Backup plan pour RDS (snapshots quotidiens automatisés avec politique de rétention)DevOps0.25 JourInstance RDS provisionnée
Créer un Amazon ECR repository pour les Docker imagesDevOps0.25 JourCompte AWS prêt
Provisionner l’Identity Provider (AWS Cognito user pool ou configurer un IdP externe : Okta, Entra ID)DevOps1 JourCompte AWS prêt
Configurer la règle d’authentification OIDC sur l’ALB pour imposer le SSO avant de router le trafic vers ECSDevOps0.25 JourCognito ou IdP provisionné, ALB déployé

Phase : Containerization et Déploiement Initial

TâcheResponsableTemps EstiméPrérequis
Écrire le Dockerfile et containeriser l’applicationDevOps2 JoursCode refactorisé prêt
Builder et push manuellement le Docker image vers ECRDevOps0.1 JoursECR repository créé, Dockerfile prêt
Écrire le Terraform code (IaC) pour définir l’ECS Fargate cluster, l’ALB et les task definitionsDevOps2 JoursDocker image dans ECR, toutes les dépendances provisionnées
Valider la santé de l’application après le déploiement TerraformDev / DevOps0.5 JourTerraform code écrit

Phase : CI/CD et Automation

TâcheResponsableTemps EstiméPrérequis
Configurer les environment variables et secrets du CI/CDDevOps0.5 JoursInfrastructure déployée
Implémenter l’analyse statique du code (SonarQube)DevOps1 JourAccès à l’outil CI/CD
Automatiser le build du Docker imageDevOps0.5 JoursDockerfile prêt
Implémenter le scan de vulnérabilités du container (Trivy)DevOps1 JourÉtape de build du image terminée
Automatiser le tagging et le push du image vers Amazon ECRDevOps0.5 JoursECR repository créé
Déclencher la mise à jour du service ECS après le push du imageDevOps1 JourECS cluster en cours d’exécution

Phase : Migration et Production Cutover

TâcheResponsableTemps EstiméPrérequis
Synchronisation initiale des fichiers utilisateurs existants du legacy server vers S3 (AWS DataSync)DevOps0.5 JourS3 bucket prêt
Mettre en place une réplication continue de la base de données legacy vers RDS (AWS DMS)DevOps0.5 JoursInstance RDS en cours d’exécution
Tests end-to-end complets du nouvel environnementDev / DevOps1 JourTous les services en cours d’exécution
Planifier et annoncer le code freeze sur l’application legacyDev / Product0.5 JoursToutes les fonctionnalités validées
Effectuer le transfert de données final (copier uniquement les nouveaux fichiers et enregistrements de base de données créés depuis la synchronisation initiale) et valider l’intégritéDevOps0.5 JoursCode freeze actif
DNS cutover (Route 53) vers le nouvel Application Load BalancerDevOps0.5 JoursDonnées entièrement synchronisées
Décommissionner le legacy server après une période de monitoring de stabilitéDevOps0.5 JoursNouvel environnement stable

Section C : Focus Technique

Stratégie de Monitoring

Étant donné que nous utilisons des managed services comme Amazon ECS Fargate et Amazon RDS, nous n’avons pas besoin de gérer ni de monitorer les serveurs sous-jacents. Notre monitoring strategy se concentrera entièrement sur la santé des containers, les performances de l’application et les métriques de la base de données. L’utilisation des services AWS natifs est très rentable et réduit la charge opérationnelle. Nous utiliserons Amazon CloudWatch Logs pour le logging centralisé de l’application et CloudWatch Metrics pour configurer des alertes sur les seuils de CPU, de mémoire et de connexions à la base de données. Optionnellement, nous pouvons intégrer AWS X-Ray pour le request tracing afin d’identifier les bottlenecks de performance dans le PHP monolith. Nous construirons des CloudWatch Dashboards personnalisés pour visualiser l’état général de la nouvelle infrastructure.

Containerization et Orchestration

Amazon ECS sur Fargate est le choix d’orchestration idéal pour ce cas d’usage. Kubernetes (EKS) introduit une complexité et une charge opérationnelle inutiles pour une application monolithique simple. ECS offre un moyen plus simple et plus rentable d’exécuter des containers, sans avoir à gérer les serveurs sous-jacents. De plus, ECS Fargate supporte le scaling horizontal automatique : via Application Auto Scaling, le nombre de containers en cours d’exécution augmente ou diminue automatiquement en fonction de la charge (CPU, mémoire ou métriques personnalisées), sans aucune intervention manuelle. Nous utiliserons Amazon Elastic Container Registry (ECR) pour stocker nos Docker images. ECR s’intègre nativement à l’écosystème AWS et offre de meilleures contrôles de sécurité que les registries publics comme Docker Hub. Notre CI/CD pipeline buildera, scannera et pushera automatiquement les images vers ECR, déclenchant ECS pour déployer la dernière version. Si l’entreprise décide de passer aux microservices à l’avenir, cette mise en place fondamentale d’ECR et de containerization facilitera grandement la migration vers Kubernetes.

Sécurité et Gestion des Secrets

La sécurité est intégrée à chaque couche. Pendant le CI/CD pipeline, le source code et les Docker images sont scannés pour détecter des vulnérabilités. Au niveau réseau, la compute layer (ECS) et la base de données (RDS) sont déployées dans des private subnets. La base de données n’est pas accessible publiquement ; les Security Groups limitent strictement le trafic entrant aux ECS containers.

Nous pouvons également éviter la gestion de règles d’IP statiques en utilisant AWS Systems Manager (SSM) Session Manager avec du port forwarding, ou en déployant un Bastion Host temporaire. Cela offre un accès sécurisé et auditable sans dépendre d’un whitelisting d’IP manuel.

Pour le file storage, l’accès à Amazon S3 est étroitement contrôlé via des IAM roles et des bucket policies strictes. L’application utilise des presigned URLs temporaires pour gérer les file uploads de manière sécurisée. De plus, en mettant à jour PHP et MariaDB vers des versions supportées, nous atténuons les CVEs connus. Enfin, les credentials codés en dur sont entièrement supprimés ; toutes les données sensibles sont stockées de manière sécurisée dans AWS Secrets Manager et injectées dynamiquement dans les ECS containers au runtime.


Annexe : Extraits de Configuration

A1 — Dockerfile (PHP Application)

FROM php:8.3-fpm-alpine

WORKDIR /var/www/html

# Installer les dépendances système
RUN apk add --no-cache nginx supervisor

# Copier le code source de l'application
COPY . .

# Installer les extensions PHP
RUN docker-php-ext-install pdo pdo_mysql

# Utiliser des variables d'environnement pour la configuration (aucun identifiant codé en dur)
ENV APP_ENV=production

EXPOSE 80

CMD ["supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]

A2 — ECS Task Definition (Terraform)

resource "aws_ecs_task_definition" "app" {
  family                   = "php-app"
  requires_compatibilities = ["FARGATE"]
  network_mode             = "awsvpc"
  cpu                      = 512
  memory                   = 1024
  execution_role_arn       = aws_iam_role.ecs_task_execution.arn

  container_definitions = jsonencode([{
    name  = "php-app"
    image = "${aws_ecr_repository.app.repository_url}:latest"
    portMappings = [{ containerPort = 80 }]

    # Secrets injectés depuis AWS Secrets Manager au runtime
    secrets = [
      { name = "DB_PASSWORD", valueFrom = aws_secretsmanager_secret.db_password.arn },
      { name = "APP_KEY",     valueFrom = aws_secretsmanager_secret.app_key.arn }
    ]

    logConfiguration = {
      logDriver = "awslogs"
      options = {
        "awslogs-group"         = "/ecs/php-app"
        "awslogs-region"        = var.aws_region
        "awslogs-stream-prefix" = "ecs"
      }
    }
  }])
}

A3 — ALB + AWS Cognito Authentication (Terraform)

# Cognito User Pool
resource "aws_cognito_user_pool" "app" {
  name = "php-app-users"

  password_policy {
    minimum_length    = 12
    require_uppercase = true
    require_numbers   = true
    require_symbols   = true
  }

  auto_verified_attributes = ["email"]
}

# Cognito App Client (used by the ALB)
resource "aws_cognito_user_pool_client" "alb" {
  name         = "alb-client"
  user_pool_id = aws_cognito_user_pool.app.id

  generate_secret                      = true
  allowed_oauth_flows                  = ["code"]
  allowed_oauth_scopes                 = ["email", "openid", "profile"]
  allowed_oauth_flows_user_pool_client = true
  callback_urls                        = ["https://app.example.com/oauth2/idpresponse"]
  supported_identity_providers         = ["COGNITO"]
}

# Cognito Domain (héberge la page de login SSO)
resource "aws_cognito_user_pool_domain" "app" {
  domain       = "php-app-auth"
  user_pool_id = aws_cognito_user_pool.app.id
}

# ALB Listener Rule — authentification Cognito avant de router vers ECS
resource "aws_lb_listener_rule" "cognito_auth" {
  listener_arn = aws_lb_listener.https.arn
  priority     = 100

  action {
    type = "authenticate-cognito"

    authenticate_cognito {
      user_pool_arn       = aws_cognito_user_pool.app.arn
      user_pool_client_id = aws_cognito_user_pool_client.alb.id
      user_pool_domain    = aws_cognito_user_pool_domain.app.domain
      on_unauthenticated_request = "authenticate"
    }
  }

  action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.app.arn
  }

  condition {
    path_pattern { values = ["/*"] }
  }
}

A4 — CI/CD Pipeline (GitHub Actions)

name: Build & Deploy

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: eu-west-1

      - name: Static code analysis (SonarQube)
        uses: sonarsource/sonarqube-scan-action@v2

      - name: Build Docker image
        run: docker build -t ${{ secrets.ECR_REGISTRY }}/php-app:${{ github.sha }} .

      - name: Scan image for vulnerabilities (Trivy)
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: ${{ secrets.ECR_REGISTRY }}/php-app:${{ github.sha }}
          exit-code: 1
          severity: HIGH,CRITICAL

      - name: Push image to ECR
        run: |
          aws ecr get-login-password | docker login --username AWS --password-stdin ${{ secrets.ECR_REGISTRY }}
          docker push ${{ secrets.ECR_REGISTRY }}/php-app:${{ github.sha }}

      - name: Deploy to ECS
        run: |
          aws ecs update-service \
            --cluster php-app-cluster \
            --service php-app-service \
            --force-new-deployment