Utiliser Symfony dans un environnement multi-serveurs AWS

Lorsqu’un site voit son trafic augmenter fortement, les ressources nécessaires à son bon fonctionnement augmentent en conséquence. Il devient obligatoire de renforcer la puissance du serveur d’hébergement (RAM, CPU, réseau, …) mais cette démarche a ses limites qu’une infrastructure redondée entre plusieurs serveurs sera la seule à pouvoir couvrir. Pour s’adapter à une infrastructure multi-serveurs et gérer correctement les médias, les sessions PHP et la base de données, le fonctionnement du framework Symfony doit également être adapté.

Sans rentrer dans les détails de configuration de l’infrastructure, voici les composants utilisés qui ont servi de base à la rédaction de cet article :

  • Deux instances Amazon EC2 t2.micro, sur lesquelles sont installés deux serveurs Apache HTTP ;
  • Une instance Amazon ELB, assurant la répartition de charge entre les deux (ou plus) serveurs EC2 ;
  • Deux instances Amazon RDS, l’une en maître pour les opérations CRUD et l’autre en slave pour les opération de lecture uniquement ;
  • Une instance Amazon ElastiCache Redis, dans laquelle seront stockées les sessions PHP ;
  • Un bucket Amazon S3, dans lequel seront stockés les médias dynamiques du site (images des utilisateurs par exemple).

Étape 1 : Dupliquer le code sur chacun des serveurs web

Le premier prérequis est de s’assurer que l’ensemble de votre code est bien dupliqué sur chacune de vos instances de serveur web. Vous pouvez le faire manuellement pour chacune d’entre elles (le plus simple) ou industrialiser d’avantage si vous utilisez des solutions cloud. Par exemple, Amazon via AWS met à disposition des outils permettant de déployer automatiquement de nouvelles instances en fonction de la sollicitation des ressources. Vous pouvez donc créer un modèle d’instance contenant votre code, qui sera alors automatiquement instancié et désinstancié en fonction du besoin.

Étape 2 : Installer les dépendances

Pour fonctionner dans cette nouvelle infrastructure, Symfony va avoir besoin de plusieurs nouveaux packages :

  • aws/aws-sdk-php et aws/aws-sdk-php-symfony : le framework PHP officiel d’Amazon Web Services et son implémentation dans Symfony ;
  • knplabs/knp-gaufrette-bundle : bundle intégrant gaufrette, une couche d’abstraction du système de fichier ;
  • gaufrette/aws-s3-adapter : un complément à gaufrette, permettant de supporter AWS S3 ;
  • predis/predis et snc/redis-bundle : un client PHP Redis et un bundle l’intégrant dans Symfony ;
  • liip/imagine-bundle : un bundle permettant de manipuler les images ;
  • vich/uploader-bundle : un bundle permettant de gérer les soumissions de formulaires avec des fichiers.

Les deux derniers bundles listés ne sont pas obligatoires mais permettent de comprendre l’utilisation d’un bucket S3 pour partager des fichiers entre plusieurs instances Symfony.

Pour installer ces packages, exécutez la commande suivante :

composer require aws/aws-sdk-php:^3.48 aws/aws-sdk-php-symfony:^1.3 knplabs/knp-gaufrette-bundle:~0.5 gaufrette/aws-s3-adapter:^0.4 snc/redis-bundle:^2.0 predis/predis:^1.1 liip/imagine-bundle:^1.9 vich/uploader-bundle:^1.3

Étape 3 : Activer les nouveaux bundles

Pour informer Symfony de l’utilisation de nouveaux bundles, modifiez le fichier AppKernel.php situé dans le répertoire app/ et ajoutez les lignes suivantes :

new Snc\RedisBundle\SncRedisBundle(),
new Aws\Symfony\AwsBundle(),
new Knp\Bundle\GaufretteBundle\KnpGaufretteBundle(),
new Liip\ImagineBundle\LiipImagineBundle(),
new Vich\UploaderBundle\VichUploaderBundle(),

Étape 4 : Configuration et utilisation d’un CDN dans Symfony pour la gestion des fichiers (AWS S3)

Pour intégrer AWS S3 à Symfony, plusieurs paramètres seront nécessaires :

  • La clé et le secret permettant l’identification ;
  • Le nom du bucket S3 ;
  • L’URL du bucket S3 ;
  • La région du bucket S3, par exemple « eu-west-1 » ;
  • Le répertoire utilisé pour le stockage des fichiers.

Pour plus de facilité, vous pouvez réunir ces paramètres à un seul endroit de votre fichier config.yml :

parameters:
    amazon_s3.key: '<votre-clé>'
    amazon_s3.secret: '<votre-secret>'
    amazon_s3.bucket_name: '<votre-bucket>'
    amazon_s3.bucket_url: 'https://s3-<votre-région>.amazonaws.com/<votre-bucket>/'
    amazon_s3.region: '<votre-région>'
    amazon_s3.directories.images_users: 'images/users'

Il convient ensuite de créer un nouveau service, qui fera office de client AWS S3. Pour cela, modifiez le fichier services.yml :

services:
    myapp.aws_s3.client:
        class: Aws\S3\S3Client
        factory: [Aws\S3\S3Client, 'factory']
        arguments:
            -
                version: 'latest'
                region: '%amazon_s3.region%'
                credentials:
                    key: '%amazon_s3.key%'
                    secret: '%amazon_s3.secret%'
Configuration du service client AWS S3

De retour dans le fichier config.yml, vous pouvez maintenant utiliser gaufrette pour créer un système de fichier virtuel, basé sur le service AWS S3 précédemment créé :

knp_gaufrette:
    adapters:
        images_users:
            aws_s3:
                service_id: "yourapp.aws_s3.client"
                bucket_name: "%amazon_s3.bucket_name%"
                detect_content_type: true
                options:
                    directory: "%amazon_s3.directories.images_users%"
                    acl: 'public-read'
    filesystems:
        images_users:
            adapter: "images_users"
Configuration de KnpGaufretteBundle

Ensuite, vous pouvez configurer le bundle VichUploader pour afin de lui indiquer d’enregistrer les fichiers sur le système de fichier virtuel AWS S3 :

vich_uploader:
    db_driver: "orm"
    storage:   "gaufrette"
    mappings:
        user_image:
            uri_prefix:         "%amazon_s3.bucket_url%%amazon_s3.directories.images_users%"
            upload_destination: "images_users"
            namer:              "vich_uploader.namer_uniqid"
            delete_on_remove:   true
            delete_on_update:   false
Configuration de VichUploaderBundle

De même, pour le bundle LiipImagine, utilisé ici pour créer des miniatures d’images, vous pouvez lui indiquer le système de fichier AWS S3 et configurer son cache pour éviter de faire trop de requêtes au service inutilement :

liip_imagine:
    loaders:
        loader_aws_s3:
            stream:
                wrapper: "gaufrette://images_users/"
    resolvers:
        cache_resolver_aws_s3:
            aws_s3:
                client_config:
                    credentials:
                        key:    "%amazon_s3.key%"
                        secret: "%amazon_s3.secret%"
                    region: "%amazon_s3.region%"
                    version: "latest"
                bucket: "%amazon_s3.bucket_name%"
                get_options:
                    Scheme: 'https'
                put_options:
                    CacheControl: 'max-age=86400'
    filter_sets:
        image_user_250_250:
            data_loader: loader_aws_s3
            cache: cache_resolver_aws_s3
            quality: 75
            filters:
                thumbnail: { size: [250, 250], mode: outbound }
Configuration de LiipImagineBundle

Étape 5 : Configuration et utilisation d’un modèle de bases de données master/slave dans Symfony (AWS RDS)

Si vous utilisez Doctrine, vous pouvez simplement indiquer à Symfony comment gérer la réplication de données master/slave entre vos bases. Pour cela, dans le fichier config.yml, utilisez la configuration suivante :

doctrine:
    dbal:
        driver:   "pdo_mysql"
        host:     "%database_host%"
        port:     "%database_port%"
        dbname:   "%database_name%"
        user:     "%database_user%"
        password: "%database_password%"
        charset:  "UTF8"
        slaves:
            slave:
                host:     "%database_slave_host%"
                port:     "%database_port%"
                dbname:   "%database_name%"
                user:     "%database_user%"
                password: "%database_password%"
                charset:  "UTF8"

Pensez bien au préalable à créer le paramètre %database_slave_host% dans votre fichier parameters.yml.

Étape 6 : Configuration et utilisation d’une base de données Redis dans Symfony pour le partage des sessions PHP (AWS ElastiCache)

Dans le fichier config.yml, déclarez un nouveau client Redis grâce au bundle SncRedis :

snc_redis:
    clients:
        default:
            type: "predis"
            alias: "amazon_redis"
            dsn: "redis://<votre-url>:6379"
    session:
        client: "amazon_redis"
        prefix: "session"
        ttl: "3600"
Configuration de SncRedisBundle

Enfin, toujours dans le fichier config.yml, indiquez à Symfony d’utiliser le client Redis précédemment créé pour écrire les sessions :

framework:
    session:
        handler_id:  "snc_redis.session.handler"

Conclusion

Votre application Symfony est maintenant prête à fonctionner sur plusieurs serveurs HTTP en simultanée :

  • Les fichiers créés dynamiquement sont sauvegardés dans un bucket AWS S3 ;
  • Les données gérées par Doctrine sont lues depuis une instance slave AWS RDS et les opérations CRUD sont effectuées depuis une instance master ;
  • Les sessions PHP sont enregistrées dans un cache Redis.

Quelle que soit l’instance Symfony sur laquelle vos utilisateurs sont redirigés par l’équilibreur de charge, les données dynamiques seront toujours identiques. De plus, vos utilisateurs authentifiés peuvent changer de serveur à tout moment sans perdre leur session courante.

2 réponses sur “Utiliser Symfony dans un environnement multi-serveurs AWS”

  1. Bonjour
    Tres bon article pour une introduction à l’archi multi serveur. Je suis actuellement en train de choisir l’architecture pour la mise en prod d’une appli et je désirerais savoir si vous pouviez me fournir quelques métrique?
    Combien cette config vous coute t elle en moyenne par mois
    Combien d’utilisateur en moyenne par mois et pic d’utilisation
    Que pensez vous de l’offre AWS vs celle de OVH (vous avez du aussi faire des comparatif et je n’arrive pas à me décider entre les deux)
    Merci beaucoup pour votre aide

    1. Ça revient grosso modo à 60€ par mois avec :
      – 1 ELB
      – 3 instances EC2 small (le minimum pour que ça marche car avec 2 instances uniquement, on a vite fait de planter celle qui reste car tout le traffic retombe dessus)
      – 2 RDS (1 RW et 1 RO)
      – 1 bucket S3
      – Quelques zones DNS Route53

      En terme de trafic, je n’ai pas de métrique précise par contre, je vais faire le calcul sur les pics de charge.

      Pour la comparaison avec OVH, j’avoue ne pas l’avoir faire car AWS dispose à mon sens d’outils plus poussés et avec des API beaucoup plus ouvertes et mieux documentées. Ca permet d’automatiser plus de choses (telles que les déploiements) plus facilement. Je penes que les tarifs sont moins élevés chez OVH par contre.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *