Heroku pour déployer votre application Python/Flask dans le cloud

Vous avez développé une application Python à l'aide du framework Flask. Tout se passe bien sur votre poste et maintenant, vous vous demandez comment rendre ça accessible au monde entier ? Avec Heroku, c'est trivial, et c'est ce qu'on va voir dans cet article !

Heroku est une Platform as a Service (PaaS) dont le but est justement de déployer des applications le plus facilement possible dans le cloud. Ici, on ne configurera aucun serveur, on n'installera pas un environnement complet. On va juste écrire notre application avec Python et Flask, puis on va la pousser sur Heroku. On va l'accompagner de quelques petits fichiers de configuration pour expliquer à Heroku comment la lancer. Et c'est tout !

Le but de cet article n'est pas de faire un tutoriel complet sur Flask et Heroku. On va donc faire une application très simple, avec une seule route, juste pour afficher "hello, world". On va la tester en local sur notre PC avant de la déployer sur Heroku avec une configuration basique mais fonctionnelle.

Créer son application Flask

Installation

La première étape est d'installer Flask. Le plus simple est de faire :

pip install flask

Si vous voulez plus de finesse dans votre installation, comme par exemple utiliser un virtual environment, je vous laisse consulter la section Installation de la documentation officielle.

Code

En s'inspirant du quickstart guide officiel, on écrit notre main.py:

from flask import Flask

app = Flask(__name__)


@app.route('/')
def index():
    return 'hello, world'

Je suis sous Windows, j'utilise dans un virtual environment, je me suis donc créé un petit script run.bat pour lancer facilement mon application :

set FLASK_APP=main.py
.\venv\Scripts\flask run

C'est un peu surprenant quand on est habitué au développement en Python : on n'exécute pas directement notre script avec python mais on dit à flask quel fichier décrit notre application grâce à une variable d'environnement.

Quand on exécute flask, il démarre un serveur local, par défaut sur le port 5000, pour servir notre application :

C:\python_flask_heroku>.\venv\bin\flask run
 * Serving Flask app "main.py"
   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)

Notez le warning au lancement : par défaut, Flask utilise un serveur fait pour le développement mais déconseillé pour la production. On verra dans la suite que ça impactera notre déploiement sur Heroku, puisqu'on voudra utiliser un serveur d'application plus adapté.

Pour vérifier que notre application fonctionne, il suffit d'ouvrir le lien donné dans les logs avec Firefox :

flask en local sous firefox

Simple, efficace, fonctionnel.

Dans ma console, je vois les logs des requêtes faites au serveur :

127.0.0.1 - - [22/Apr/2021 11:56:20] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [22/Apr/2021 11:58:30] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [22/Apr/2021 11:58:38] "GET /try HTTP/1.1" 404 -

Ici, il y a eu 2 accès sur la racine / du serveur qui ont fonctionné avec le code 200, ainsi qu'un accès à /try qui a échoué avec le code 404. En effet, mon application définit bien une route vers /, mais pas vers /try.

Mon application est prête, il ne reste plus qu'à la rendre accessible sur l'internet mondial grâce à Heroku.

Déploiement sur Heroku

Inscription et installation

Pour commencer, il faut créer un compte et installer l'interface en ligne de commande.

La commande heroku est désormais disponible depuis le terminal et nous pouvons nous connecter à notre compte avec heroku login. Une page internet s'ouvre dans notre navigateur par défaut pour entrer nos identifiants.

Créer son application

Le concept est totalement génial : le déploiement consiste juste à faire un git push vers un dépôt distant qui est sur Heroku. Ce Git contient le code ainsi que des fichiers de configuration pour expliquer à Heroku comment lancer tout ça.

On a déjà une application fonctionnelle en local, on va donc créer un dépôt Git avec git init.

On crée ensuite l'application Heroku, en lui donnant ou pas un nom. Dans mon cas, j'ai choisi de la nommer :

$ heroku apps
You have no apps.

$ heroku apps:create younup-flask
Creating ⬢ younup-flask... done
https://younup-flask.herokuapp.com/ | https://git.heroku.com/younup-flask.git

$ heroku apps
=== p.gradot@younup.fr Apps
younup-flask

Comme le dépôt existe déjà en local, les remotes sont ajoutées automatiquement :

$ git remote -v
heroku  https://git.heroku.com/younup-flask.git (fetch)
heroku  https://git.heroku.com/younup-flask.git (push)

L'adresse de mon application est déjà accessible, mais il n'y a pas grand chose d'intéressant...

app créée mais non deployée

D'ailleurs, elle est normalement toujours accessible (mais peut-être qu'un jour je la désactiverai....).

Configuration

Sur notre PC, l'interpréteur Python est installé ainsi que les paquets nécessaires. Par défaut, Heroku ne sait pas ce dont notre application a besoin, il faut donc lui préciser tout ça. Pour ça, il faut créer deux fichiers qu'on ajoute au dépôt Git.

Le premier est runtime.txt dans lequel on indique la version de Python qu'on souhaite utiliser. Il y a une seule ligne dans ce fichier :

python-3.9.2

Le second est requirements.txt, qui sert à pip pour connaitre les paquets à installer :

flask
gunicorn

flask, OK. Mais pourquoi gunicorn ? Souvenez-vous plus haut d'un warning lors du lancement de Flask, nous conseillant de "Use a production WSGI server instead". Et bien c'est exactement ce qu'est gunicorn :

Gunicorn 'Green Unicorn' is a Python WSGI HTTP Server for UNIX. It's a pre-fork worker model. The Gunicorn server is broadly compatible with various web frameworks, simply implemented, light on server resources, and fairly speedy.

Pour expliquer à Heroku qu'il faut utiliser gunicorn pour servir notre application Flask, on va utiliser le fichier Procfile :

Heroku apps include a Procfile that specifies the commands that are executed by the app on startup. You can use a Procfile to declare a variety of process types, including your app’s web server".

Une ligne suffit pour notre Procfile :

web: gunicorn main:app

Test en local

Heroku nous permet de tester en local avec la commande heroku local.

Malheureusement, ce n'est pas possible dans mon cas car je suis sous Windows et gunicorn ne fonctionne pas Windows. Si vous êtes sous Linux ou macOS, vous pouvez exécuter cette commande pour vérifier que tout est OK avant de passer au déploiement.

Déploiment sur l'internet mondial

Tout est OK ? Il suffit de faire git commit et git push :

$ git push --set-upstream heroku master
Énumération des objets: 6, fait.
Décompte des objets: 100% (6/6), fait.
Compression par delta en utilisant jusqu'à 4 fils d'exécution
Compression des objets: 100% (3/3), fait.
Écriture des objets: 100% (6/6), 1.18 Kio | 1.18 Mio/s, fait.
Total 6 (delta 0), réutilisés 0 (delta 0), réutilisés du pack 0
remote: Compressing source files... done.
remote: Building source:
remote: 
remote: -----> Building on the Heroku-20 stack
remote: -----> Determining which buildpack to use for this app
remote: -----> Python app detected
remote:  !     Python has released a security update! Please consider upgrading to python-3.9.4
remote:        Learn More: https://devcenter.heroku.com/articles/python-runtimes
remote: -----> Installing python-3.9.2
remote: -----> Installing pip 20.2.4, setuptools 47.1.1 and wheel 0.36.2
remote: -----> Installing SQLite3
remote: -----> Installing requirements with pip
remote:        Collecting flask
remote:          Downloading Flask-1.1.2-py2.py3-none-any.whl (94 kB)
remote:        Collecting gunicorn
remote:          Downloading gunicorn-20.1.0.tar.gz (370 kB)
remote:        Collecting itsdangerous>=0.24
remote:          Downloading itsdangerous-1.1.0-py2.py3-none-any.whl (16 kB)
remote:        Collecting Werkzeug>=0.15
remote:          Downloading Werkzeug-1.0.1-py2.py3-none-any.whl (298 kB)
remote:        Collecting click>=5.1
remote:          Downloading click-7.1.2-py2.py3-none-any.whl (82 kB)
remote:        Collecting Jinja2>=2.10.1
remote:          Downloading Jinja2-2.11.3-py2.py3-none-any.whl (125 kB)
remote:        Collecting MarkupSafe>=0.23
remote:          Downloading MarkupSafe-1.1.1-cp39-cp39-manylinux2010_x86_64.whl (32 kB)
remote:        Building wheels for collected packages: gunicorn
remote:          Building wheel for gunicorn (setup.py): started
remote:          Building wheel for gunicorn (setup.py): finished with status 'done'
remote:          Created wheel for gunicorn: filename=gunicorn-20.1.0-py3-none-any.whl size=78917 sha256=389135bf7e57c6bee7f1675ce59482c4d7ab3500ab4debf1c633e23986c5326c
remote:          Stored in directory: /tmp/pip-ephem-wheel-cache-c0xwgcsz/wheels/ee/ca/72/3e9be4033d3993d4d78e2f4accdfcfff6c690921fef5ea0d57
remote:        Successfully built gunicorn
remote:        Installing collected packages: colorama, itsdangerous, Werkzeug, click, MarkupSafe, Jinja2, flask, gunicorn
remote:        Successfully installed Jinja2-2.11.3 MarkupSafe-1.1.1 Werkzeug-1.0.1 click-7.1.2 colorama-0.4.4 flask-1.1.2 gunicorn-20.1.0 itsdangerous-1.1.0
remote: -----> Discovering process types
remote:        Procfile declares types -> web
remote: 
remote: -----> Compressing...
remote:        Done: 52.6M
remote: -----> Launching...
remote:        Released v3
remote:        https://younup-flask.herokuapp.com/ deployed to Heroku
remote: 
remote: Verifying deploy... done.
To https://git.heroku.com/younup-flask.git
 * [new branch]      master -> master

C'est magnifique. Heroku nous installe l'interpréteur Python et les paquets indiqués avec pip, puis il redémarre notre application, qui est maintenant disponible :

app créee et deployée

Publier une mise à jour

En fait, notre premier push était déjà une mise à jour. Les mises à jour suivantes se font de la même façon. Imaginons qu'on souhaite mettre à jour notre version de Python, pour suivre la recommandation donnée dans les logs d'installation : "Python has released a security update! Please consider upgrading to python-3.9.4". On modifie le fichier runtime.txt, on commit, on push, et le processus d'installation et de déploiement recommence. Cette fois, par contre, on lira :

remote: -----> Python app detected
remote: -----> Found python-3.9.2, removing
remote: -----> No change in requirements detected, installing from cache
remote: -----> Installing python-3.9.4

Conclusion

Nous avons vu comment déployer très simplement notre application Flask sur Heroku et ainsi la rendre accessible au monde entier. Nous avons utilisé un compte gratuit, suffisant pour des tests, des hobbyists, des petites applications. Si vous avez des besoins plus importants, il existe des versions payantes.

Heroku supporte plusieurs autres langages out-of-the-box et fournit des getting started guides pour chacun d'eux. Il y a des astuces pour utiliser d'autres langages. Certains se servent de Docker (par exemple pour faire du C#) ; d'autres utilisent le système de buildpacks (par exemple pour faire du C).

Désormais, vous ne pourrez plus dire "je ne sais pas comment mettre mon application dans le cloud" !

Pierre

Que la vie de Pierre, expert embarqué Younup, serait terne sans les variadic templates et les fold expressions de C++17. Heureusement pour lui, Python a tué l'éternel débat sur l’emplacement de l’accolade : "alors, en fin de la ligne courante ou en début de la ligne suivante ?"

Homme de terrain, il est aussi à l’aise au guidon de son VTT à sillonner les chemins de forêt, dans une salle de concert de black metal ou les mains dans les soudures de sa carte électronique quand il doit déboguer du code (bon ça, il aime moins quand même !)

Son vœu pieux ? Il hésite encore... Faire disparaitre le C embarqué au profit du C++ embarqué ? Ou stopper la génération sans fin d'entropie de son bureau ?

Retours aux publications