Déployer une Application Python Flask sur AWS Lightsail avec une Gitlab CI

Cet article vous explique comment mettre en place un serveur web utilisant Flask, hébergé sur AWS et utilisant Gitlab pour les pipelines d'intégration et déploiement continue.

Ce qui permettra à une équipe de plusieurs développeurs de travailler dessus sereinement. À noter que le neck plus ultra serait d'encapsuler notre application Flask dans un docker, mais cette partie ne sera pas abordée ici. Il faut bien s'arrêter à un moment 😉.

Pré-requis

  • Python
  • Un compte AWS pour l'hébergement.
  • Un compte Gitlab pour le déploiement automatique.

Préparation de notre application Python Flask

Flask est un module Python permettant de développer des applications web facilement.

Nous allons commencer par mettre en place l'arborescence standard d'une application Flask. Elle contiendra 3 fichiers principaux (pour le moment 😉) :

- ./
  - web/
    - templates/
      - index.html
    - app.py
  - requirements.txt

index.html

Il s'agit la partie frontend. La vue de notre application.

<!DOCTYPE html>
<html>
  <head>
    <title>Example</title>
  </head>
  <body>
    <h1>Hello {{ myString }}</h1>
  </body>
</html>

app.py

Il s'agit de la partie backend. Il contient à la fois le code de l'application et la partie serveur web, géré par Flask.

from flask import Flask, render_template

app = Flask(__name__)

@app.route("/")
def index():
    return render_template("index.html", myString="Flask")

if __name__ == "__main__":
    app.run()

requirements.txt

$ flask

Ce fichier n'est pas nécessaire pour le fonctionnement du serveur mais c'est une bonne pratique de proposer un fichier requirements.txt lorsqu'on développe une application python. Ce fichier contient tous les modules nécessaires. Ainsi, après avoir cloné le repos, un développeur n'aura qu'à faire pip install -r requirements.txt pour installer toutes les dépendances.

Enfin, placez-vous à la racine du repos et lancé le serveur avec :

$ python -m web.app

Grossièrement, cette commande demande à python d'exécuter le module app.py présent dans le dossier web.

Vous pouvez aussi executer python web/app.py, mais raisonner en termes de module est une meilleure pratique pour tout un tas de raison dont l'énumération serait longue et fastidieuse. Pour plus de détail, vous pouvez aller faire un tour ici.

Vous devriez avoir la sortie suivante :

* Serving Flask app 'app' (lazy loading)
* Environment: production
  WARNING: This is a development server. Do not use it in a production deployment.
  Use a production WSGI server instead.
* Debug mode: off
* Running on http://127.0.0.1:5000 (Press CTRL+C to quit)

Ouvrez enfin votre navigateur à l'adresse http://127.0.0.1:5000 pour voir votre page.

Déploiement de l'application

Nous allons attaquer la partie hébergement et déploiement de l'application sur Amazon Web Service.

AWS Lightsail VS AWS EC2 pour déployer une App Flask

Lightsail est parfois comparé à EC2 pour le déploiement d'application dans le cloud. EC2 est adapté aux applications qui doivent absorber des gros piques de charge ponctuels, ou qui prévoient une grosse montée en charge dans un futur proche. Lightsail est plus adapté aux applis ayant une charge modérée, mais relativement constante.

En caricaturant, EC2 remplace un cluster de calcul, là où Lightsail se rapproche plus d'un Raspberry Pi.

Vous pouvez aller voir ici pour plus de détails.

Déployer notre application Python Flask sur AWS Lightsail

Lightsail est l'un des (très nombreux) services offerts par Amazon Web Service. Ce service permet de créer une ou plusieurs machines et de l'administrer à distance en ssh. Vous pouvez choisir différent hardware ainsi que l'OS et certaines stack techno pré-installé.

Pour 3 $ par mois, vous pouvez par exemple disposer d'une machine avec 512 Mo de RAM, 1 CPU et 20 Go de SSD tournant sur Ubuntu 20.04 LTS. Vous avez aussi accès à 3 mois d'essai gratuit, ce qui est toujours bon à prendre 😉.

Créez-vous un compte AWS et allez sur la page d'accueil de lightsail.

Cliquez sur "Create Instance"

Créez une instance AWS Lightsail pour votre projet Python Flask
Créez une instance AWS Lightsail pour votre projet Python Flask

Choisissez "Linux/Unix", "OS Only", "Ubuntu 20.04 LTS".

Sélectionnez votre distribution pour votre machine AWS Lightsail
Sélectionnez votre distribution pour votre machine AWS Lightsail

Choisissez votre machine :

Sélectionnez votre machine AWS Lightsail
Sélectionnez votre machine AWS Lightsail

Puis cliquez sur "Create instance".

La machine est ensuite accessible en ssh en cliquant ici :

Machine AWS Lightsail accessible SSH
Machine AWS Lightsail accessible SSH

Utilisation de GUnicorn avec Flask en Python

Les développeurs web aiment se compliquer la vie et empiler des outils les uns sur les autres. Flask ne déroge pas à la règle et pour fonctionner correctement, il doit tourner derrière une "Web Service Gateway Interface" ou WSGI. Cette couche va recevoir les requêtes du client, les passer à l'application et renvoyer la réponse au client. En gros, elle traduit un appel HTTP en appel python.

Flask embarque un WSGI qui se lance avec la commande python -m web.app, mais il est très limité. La principale limitation est que ce serveur est mono-thread. Cela signifie que si 2 requêtes arrivent en même temps, l'une d'elle devra attendre son tour. Il est aussi peu robuste en termes de sécurité et de fiabilité. Il n'est pas conçu pour tourner en boucle pendant plusieurs jours.

Bref, pour un petit site perso tournant dans une sandbox, flask peut suffire. Mais pour une application un peu plus conséquente, il est fortement conseillé d'opter pour un WSGI de production. Nous allons ici utiliser Gunicorn:

$ pip install gunicorn

Profitez-en pour rajouter gunicorn dans votre requirements.txt qui ressemble maintenant à ceci :

flask
gunicorn

Pour finir, créez un fichier run.sh contenant :

#!/bin/sh
gunicorn --workers 1 --bind 0.0.0.0:9062 --reload --chdir web app:app

Petit descriptif des arguments :

  • --workers: Nombre de worker. À ajuster selon la charge que doit absorber votre site et la puissance de la machine. En général, nbWorker = 2*nbCore+1.
  • --bind: Écoute le port 9062. Vous pouvez mettre n'importe quel port libre.
  • --reload: Recharge le serveur lorsque le code change. Bien pratique 😉
  • --chdir: Change de répertoire avant d'exécuter le serveur (pour aller ici dans le dossier web).
  • app:app: Nom du module python a exécuter. Le serveur app au sein du fichier app.py.

Vous pouvez maintenant lancer votre serveur avec ./run.sh.

Gestion des processus Python avec Supervisor

Continuons d'empiler les couches en rajoutant Supervisor. Supervisor est un gestionnaire de processus qui va s'occuper de lancer GUnicorn au démarrage de la machine et le redémarrer en cas de crash.

$ sudo apt-get update -y
$ sudo apt-get install supervisor -y
$ sudo service supervisor start

Ensuite, créez un fichier /etc/supervisor/conf.d/myproject-gunicorn.conf contenant :

[program:myproject_gunicorn]
user=root
directory=/home/myuser/myproject/src/
command=/home/myuser/myproject/src/run.sh

autostart=true
autorestart=true
stdout_logfile=/var/log/myproject/gunicorn.log
stderr_logfile=/var/log/myproject/gunicorn.err.log

On update supervisor:

$ sudo supervisorctl reread
$ sudo supervisorctl update

Et un petit check pour voir si tout s'est bien passé :

$ sudo supervisorctl status myproject_gunicorn

Redirection des ports

Il reste une dernière chose à faire pour rendre votre serveur accessible sur internet. On doit lui attribuer une IP statique. Pour ce faire, rendez-vous sur la page Networking de lightsail, puis cliquez sur Create static IP

Créez une IP statique pour déployer votre projet Python sur AWS
Créez une IP statique pour déployer votre projet Python sur AWS

Donnez un nom à votre IP, puis cliquez sur Create, puis attachez cette IP à votre instance AWS

Il reste à configurer la redirection de port. Rendez-vous sur la page Networking de votre instance cette fois :

Configurez la redirection de port sur AWS Lightsail
Configurez la redirection de port sur AWS Lightsail

Puis, rajouter la règle pour rediriger le port 9062 (celui qu'on a spécifié dans le run.sh) vers votre machine :

Configurez le firewall AWS Lightsail pour votre application Python Flask
Configurez le firewall AWS Lightsail pour votre application Python Flask

Pour voir si tout s'est bien passé, redémarrez votre machine lightsail et essayez de vous connecter depuis un navigateur sur l'IP statique cré précédemment. Vous devriez avoir la page d'accueil de votre site.

(Bonus) Déploiement automatique avec Gitlab CI sur AWS

Cette dernière étape consiste à uploader automatiquement les fichiers sur le serveur, par exemple lorsqu'un tag est posé sur le repos. Cette partie peut être overkill pour un site amateur, mais si vous comptez développer un site professionnel au sein d'une équipe de plusieurs développeurs, automatiser le déploiement vous fera gagner énormément de temps au quotidien.

J'utilise ici GitlabCI, mais le principe est le même si vous êtes sur Azure DevOps, Bitbucket ou GitHub. Créé donc un fichier .gitlab-ci.yml à la racine du dépôt contenant :

stages:
  - deploy

deploy job:
  stage: deploy
  before_script:
    # Toute cette partie sert a rajouter notre clef ssh privé pour pouvoir scp les fichiers sur aws
    # Voir ici pour les détails: https://gitlab.com/gitlab-examples/ssh-private-key/-/blob/master/.gitlab-ci.yml
    - echo "$SSH_PRIVATE_KEY" > private_key.pem
    - chmod 600 private_key.pem
    - mkdir ~/.ssh
    - touch ~/.ssh/known_hosts
    - ssh-keyscan $AWS_ADDRESS > ~/.ssh/known_hosts

  script:
    # supprime l'ancienne version de l'application.
    # ATTENTION: Cette commande supprime des fichiers sur le serveur (rm -rf).
    # Si vous ne comprenez pas cette ligne, trouvez quelqu'un qui la comprend.
    - ssh -i private_key.pem [email protected]$AWS_ADDRESS 'rm -rf myServer;mkdir myServer'
    # copy run.sh et le dossier web sur aws
    - scp -r -i private_key.pem run.sh [email protected]$AWS_ADDRESS:myServer/run.sh
    - scp -r -i private_key.pem web [email protected]$AWS_ADDRESS:myServer/web
  only:
    # execute le script de deploy uniquement pour la branche master.
    - master

Pour des raisons de sécurité, les données sensibles comme l'utilisateur, l'adresse du serveur et surtout la clef d'accès du serveur ne sont pas commité dans le code source. Gitlab possède une manière sécurisée de stocker ces informations aux travers de variable CI.

Le but est d'éviter aux "simples" développeurs d'avoir accès à ces variables. Seul l'administrateur du dépôt Gitlab peut les voir.

Pour y accéder, allez dans settings->CI/CD:

Configurer votre Gitlab CI/CD pour votre projet Flask
Configurer votre Gitlab CI/CD pour votre projet Flask

Puis Variables:

Configurer vos variables Gitlab CI pour Python Flask
Configurer vos variables Gitlab CI pour Python Flask

Ici, vous aurez besoin de spécifier :

  • AWS_ADDRESS: L'adresse du serveur AWS. Par ex 35.12.128.54.
  • AWS_USER: L'utilisateur du serveur.
  • SSH_PRIVATE_KEY: La clef ssh privée pour se connecter au serveur.

Vous n'avez plus qu'à pusher votre code sur master et le tour est joué !

Conclusion

Vous avez maintenant un squelette d'application Flask, déployé sur AWS et capable d'absorber la montée en charge au moins dans un premier temps grâce à Gunicorn. Supervisor permet de relancer automatiquement l'application en cas de crash, et pour finir GitlabCI permet à une équipe de plusieurs développeurs de travailler sur le produit de manière professionnel.


➡️ Que vous soyez Développeur, Product Owner ou même Scrum Master, ça peut arriver à tous le monde 😉. Retrouvez-moi sur mon blog.

Apprenez à programmer avec des exemples simples et concrets.

English / Français