Créer et déployer l'infrastructure d'une web app avec AWS Lambda, API Gateway & DynamoDB grâce à Terraform

6
minutes
Mis à jour le
25/3/2024

Share this post

Déployez votre application serverless, scalable et résiliente dans le cloud en suivant des étapes simples !

#
Cloud
#
Serverless
#
AWS Amazon
#
API
Maxime Descouts
Software Engineer

1. Introduction

Lors de la conception d’une application web, opter pour une architecture Cloud Native est une manière simple pour obtenir une application scalable, hautement disponible et réduire les coûts de son infrastructure.

Imaginez avoir une application de liste de courses hébergée sur un service cloud, avec une API, une base de donnée, mais dont l’infrastructure ne coûte rien !

Dans cet article, nous allons suivre les étapes permettant de créer de bout en bout l’application Cloud Native suivante, basée sur le cloud AWS :

Cloud Native Webapp



Nous allons voir les fondamentaux pour créer une architecture Cloud Native et comment automatiser la création des ressources de notre infrastructure avec Terraform.

Dans chaque section, des extraits de code Terraform seront fournit afin de vous guider dans la création de l’application.

Voici l’architecture cible que nous allons construire progressivement :

Schéma de l’infrastructure finale

  • Terraform : Outil d’Infrastructure as Code (IaC) pour définir et provisionner les ressources de l'infrastructure.
  • API Gateway : Service AWS pour créer, publier, maintenir, surveiller et sécuriser les API.
  • Lambda : Service pour exécuter du code sans provisionnement ni gestion de serveurs (serverless).
  • DynamoDB : Service de base de données NoSQL d’AWS qui simplifie la gestion de scalabilité et la résilience de la base de donnée.
  • S3 Bucket : Simple Storage Service, un service de stockage de data. Dans notre architecture, nous avons un bucket contenant le code source de la fonction Lambda, et un autre bucket contenant le code source de notre application web.
  • Cloudfront Distribution : Content Delivery Network (CDN) d'AWS pour distribuer des applications et des API à des utilisateurs du monde entier avec une latence faible.

Tous les fichiers Terraform utilisés pour créer notre webapp, incluant les fichiers HTML et JS, sont disponible sur repository GitHub suivant : https://github.com/mdesc/cloud-native-webapp

N’oubliez pas d’exécuter la commande suivante dans le directory après avoir clone le projet : terraform init

Architecture Cloud-Native ?

Le Cloud-Native est une approche du développement et du déploiement d'applications qui se basent sur le cloud, faisant le plus souvent usage de microservices, de conteneurisation, d'automatisation et de serverless. Ces pratiques permettent aux applications de bénéficier d'une meilleure résilience, d’une mise à l’échelle simplifiée et de réduire les coûts liés à la maintenance des machines faisant tourner l’infrastructure.

💡 Avantages d’une architecture Cloud-Native :
- Scalabilité
: Avec la capacité à ajuster dynamiquement les ressources en fonction de la demande, les applications cloud-native peuvent gérer efficacement les fluctuations du nombre d’utilisateurs et les pics de charges.
- Cost Management
: Les architectures cloud-native permettent de ne payer que pour les ressources consommées, minimisant le gaspillage et optimisant les coûts.

Concrètement, dans le cadre de cet article, notre application de liste de courses utilise une architecture serverless, ce qui signifie que l'infrastructure sous-jacente est entièrement gérée par le fournisseur de services cloud, ici AWS.

Cela comprend la gestion des serveurs, des ressources de stockage et de la mise en réseau.

Nous n’avons donc pas à payer pour des ressources inutilisées ou à gérer la mise à l'échelle : les ressources nécessaires seront automatiquement allouées lorsque l'application sera sollicitée.

Pourquoi Terraform ?

Terraform est un outil d'Infrastructure as Code (IaC) qui vous permet de définir et de provisionner une infrastructure en utilisant un langage déclaratif.

Dans une approche déclarative, vous définissez l'état désiré de votre infrastructure, et Terraform gère l'orchestration pour atteindre cet état. Cela contraste avec les outils IaC impératifs, comme Ansible, où vous devez spécifier les étapes pour atteindre un état désiré en configurant les ressources.

La nature déclarative de Terraform permet de se concentrer sur le résultat final plutôt que sur les détails procéduraux. Cela simplifie la gestion de l'infrastructure : il est plus simple de gérer les différentes versions de son infrastructure, faire des rollbacks etc.

💡 Configurer votre environnement local :
Voici un lien détaillant la démarche à suivre pour installer Terraform et le configurer avec la CLI AWS : https://developer.hashicorp.com/terraform/tutorials/aws-get-started/aws-build
Le but de cet article n’étant pas d’expliquer le fonctionnement de Terraform, voici quelques explications rudimentaires :
https://developer.hashicorp.com/terraform/tutorials/aws-get-started/infrastructure-as-code

2. Création d’un site statique

Pour obtenir rapidement un premier exemple de notre future webaapp, nous allons commencer par créer les ressources nous permettant d’avoir un simple site web statique.

Nous allons dans un premier temps héberger notre application statique via un Bucket S3, qui sera exposé par un CloudFront (CDN), et donc accessible via une url cloudfront.net.

Concrètement, voici un schéma représentant l’architecture de notre webapp à la fin de cette première section :

Static webapp Infrastructure

2.1 Stocker des fichiers dans S3

Commençons par créer un Bucket S3 afin d’y stocker les fichiers statiques de notre site.

S3 peut être comparé à un Google Drive, mais pour application : nous allons rendre disponible des fichiers qui seront utilisé par les futures ressources de notre architecture.

Voici le code à mettre dans votre fichier main.tf :

Vous pouvez ensuite exécuter la commande suivante pour créer les ressources décrites par le fichier main.tf :

terraform apply --auto-approve

Nous nous attendions bien à avoir 2 ressources : un nom aléatoire pour notre bucket, (random_pet), et un bucket S3 (aws_s3_bucket).

⚠️ N’oubliez pas d’exécuter un terraform destroy --auto-approve à chaque fois que vous avez finit de travailler sur votre infra, pour ne pas laisser des ressources tourner inutilement dans AWS, bien que l’ensemble des ressources de cet article soient dans le free tier : https://aws.amazon.com/free.*

Maintenant, nous pouvons créer des ressources permettant de gérer la visibilité du bucket, et y upload notre fichier HTML :

2.2 Configurer le Content Delivery Network (CDN)

Intégrer CloudFront à notre architecture permet d’assurer que notre application est accessible rapidement partout dans le monde, et également nous évitons de communiquer le lien direct vers notre bucket, exposé à tout internet…

Pour cela, créons une distribution CloudFront avec les permissions nécessaires pour accéder au fichier index.html afin de l’exposer via une URL “cloudfront.net” :

3. Ajout des fonctionnalités dynamiques

Pour passer d’un site web statique à dynamique, nous allons ajouter une base de donnée (DynamoDB), du code JS (exécuté via une Lambda function) et une API Gateway.

3.1 Création d’une table DynamoDB:

DynamoDB est la base de donnée NoSQL scalable de AWS. Contrairement aux bases de données relationnelles, DynamoDB e requiert pas de schéma fixe.

Cela rend DynamoDB idéal pour les applications web où les besoins en données peuvent évoluer rapidement.

3.2 Implémentation d’une Lambda Function:

Nous allons utiliser une fonction Lambda pour interagir avec la table DynamoDB, voici une introduction au service Lambda de AWS : https://docs.aws.amazon.com/lambda/latest/dg/welcome.html

Dans notre cas, nous devons upload un fichier lambda.js sur un bucket S3 pour ensuite créer la ressource Lambda, qui exécutera le code JS :

Le code contenu dans lambda.js va écrire ou lire dans la DB en fonction de l’évènement que la lambda reçoit en input. Libre à vous d’y ajouter des fonctionnalités ou de les séparer dans plusieurs lambdas pour améliorer l’architecture !

De plus, nous devons accorder à notre lambda certaines permissions pour qu’elle puisse interagir avec la table DynamoDB. Il suffit les définir via les fichiers policy.json  et asssume_role_policy.json :

⚠️ N’oubliez pas de remplacer <your-account-id> par l’id de votre compte AWS.


Nous utilisons ces 2 fichiers JSON pour créer les ressources aws_iam_role et aws_iam_role_policy de notre lambda  :


Nous pouvons maintenant créer un fichier ZIP contenant le code source JS de notre lambda puis l’uploader sur le bucket S3 afin de créer la ressource Lambda dans AWS :

Si vous êtes attentif, vous avez remarqué la présence de permissions liée à des “logs” dans le fichier de policy de la Lambda.

Cela octroie à la lambda les autorisations requises pour créer des fichiers de logs dans une ressource CloudWatch, très utiles pour débugger notre code ou monitorer la lambda.


À cette étape, notre lambda peut lire et écrire dans la table myDB de DynamoDB.

N’hésitez pas à tester cette interaction depuis la CLI AWS ou directement sur le dashboard AWS pour mieux comprendre son fonctionnement.

Commande pour invoke une lambda depuis la CLI :  aws lambda invoke --function-name <nom_de_la_fonction>

Évidemment, nous ne voulons pas appeler notre lambda directement depuis notre future webapp. Il manque une couche essentielle à notre architecture : une API Gateway.

3.3 Création de l’API Gateway & Intégration à la Lambda:

L'API Gateway est le point d'entrée pour notre webapp et agit comme une passerelle pour toutes les requêtes entrantes.

Le but est d’arriver à cette architecture :

Backend Infrastructure of the webapp

Cette ressource fonctionne en associant des routes HTTP à des actions spécifiques, telles que l'appel de fonctions AWS Lambda (ça nous intéresses !).

Lorsqu'une requête est reçue sur une route spécifique, l'API Gateway la transmet à la fonction Lambda appropriée, qui peut ensuite traiter la demande et renvoyer une réponse.

Cette intégration permet une exécution de code serverless, basée sur des événements déclencheurs, depuis notre HTML :


Une fois cette ressource créée, nous ajoutons l'URL de l'API Gateway, pour créer le index.html, via un script local (sed "s|BACKENDURL|$( cat ./site.env )|" ./site/template.html > ./site/index.html) qui lit l’URL écrite dynamiquement dans un fichier site.env en local suite à la création de l’API Gateway :


Il faut également ajouter cette ressource en dépendance à celle qui upload le fichier index.html, pour ne pas risquer de l’upload avant que l’URL de l’API Gateway n’y soit écrite :

dans la ressource null_resource.upload_to_s3, ajoutez null_resource.build dans la liste  depends_on.

Nous pouvons maintenant tester l'intégration entre l'API Gateway et la Lambda. Pour ce faire, lancez Chrome sans vérifier les CORS dans un premier temps (avec le flag --disable-web-security), ce qui nous permettra de voir si les requêtes sont traitées correctement sans restriction de politique CORS.

Conclusion :

Vous avez maintenant une webapp pour gérer votre liste de courses avec des fonctionnalités simples, déployable sur le cloud AWS à tout moment !

En plus d’être hébergé par un cloud provider, notre infrastructure se basera sur plusieurs couches qui seront résilientes aux pannes, et qui seront mises à l’échelle automatiquement : nous n’avons rien à gérer, tout est délégué.

L’infrastructure mise en place durant cet article n’est évidemment pas parfaite et est simplifiée par soucis de simplicité, néanmoins voici quelques next-steps pour améliorer l’infrastructure :

  • Ajouter une couche pour gérer l’authentification des utilisateurs sur notre app (avec le service Cognito de AWS)
  • Configurer plus précisément la visibilités et l’accès de nos ressources : par exemple, ne pas mettre les buckets en public
  • Gérer les CORS avec l’intégration API Gateway - Lambda pour ne pas avoir à lancer Chrome en désactivant la vérification des CORS