Heroku - Quelques bonnes pratiques

Nous avons expliqué  dans un précédent article  les raisons pour lesquelles nous considérons Heroku comme la solution d'hébergement idéale.  Heroku est un service puissant et simple d'utilisation et l'objectif de cet article est de présenter quelques astuces pour exploiter au mieux ses fonctionnalités.

Minimiser le temps de démarrage

Pour vérifier le bon démarrage de votre application, Heroku lui laisse 30 secondes pour s'initialiser et écouter les requêtes entrantes sur le port fourni en variable d'environnement. Passé ce délai votre application sera arrêtée.

Vous l'aurez compris, il est nécessaire que votre application s'initialise en moins de 30 secondes.

Cet objectif est parfaitement atteignable avec tous les frameworks, y compris ceux nécessitant de démarrer une machine virtuelle au préalable. Néanmoins il faut quand même s'assurer de ne pas effectuer de tâches superflues. Pour faire simple : au démarrage votre application doit simplement ... démarrer. Il faut éviter les tâches chronophage : collecte des assets, compilation du code, migration de la base de données, ...

Conclusion : KEEP IT SIMPLE. Le script permettant de démarrer l'application doit se contenter de lancer le serveur web.

Quel est le bon moment pour exécuter une tâche ?

Comme évoqué précédemment , il est nécessaire d'exécuter certaines tâches avant le démarrage effectif de l'application. Quelles sont les options pour jouer une tâche ?

Buildpack

La solution la plus efficace est d'intégrer ces tâches à un buildpack. De cette manière, cette tâche fera partie intégrante du processus de build Heroku. Si vous devez vraiment exécuter cette tâche, alors il est fort probable que d'autres personnes aient eu à l'exécuter avant vous. Il est donc possible qu'un buildpack intègre cette fonctionnalité.

Par exemple, Phoenix Static est un buildpack conçu pour assurer la collecte des assets statiques d'une application Phoenix. Ce genre de buildpack utilitaire offre généralement la possibilité de configurer son comportement pour s'adapter à vos besoins.

Si aucun buildpack ne propose cette fonctionnalité, il reste possible d'en créer un soi-même. La tâche n'est pas trop complexe mais nécessite un peu d'expérience en terme de scripting.

Certains buildpacks proposent leur propre solution pour insérer des tâches personnalisées

App.json

La rubrique scripts du app.json supporte aujourd'hui 2 scripts permettant d'effectuer des traitements à des instants spécifique du cycle de vie des review apps.

Le script pr-predestroy s'exécute lorsqu'une review app est détruite suite à la cloture ou la fusion d'une PR. Ce script peut-être utilisé pour toute tâche de clôture. Par exemple, pour supprimer un compte de test créé spécifiquement pour cette application.

Le script postdeploy s'exécute une seule fois, lorsqu'une review app est créée. Ce script est utile pour tout traitement permettant d'initialiser correctement une application de test, par exemple pour insérer des données de test dans la base de données.

Il faut néanmoins garder en mémoire que ces scripts seront exécutés uniquement sur les review apps et non sur les applications principales.

Release Phase

Une release phase est une simple commande à définir dans le Procfile du projet et qui sera exécutée à chaque déploiement d'une application. L'exécution de cette commande apparaitra comme une étape de déploiement à part entière sur Heroku, ce qui en facilite le déverminage.

Voici un exemple de Procfile pour une application Phoenix. La phase de release permet de jouer les migrations de la base de données ainsi que d'insérer - si besoin - des données initiales en base.

release: mix do ecto.migrate, ecto.seeds
web: mix phx.server

Bien choisir et comprendre ses buildpacks

Un conteneur Heroku ne contient initialement qu'un petit système d'exploitation appelé Stack. Il est nécessaire d'enrichir ce système avec les différents outils nécessaires au déploiement de l'application (écosystème, gestionnaire de dépendances, ...). Pas d'inquiétude à avoir, il n'est pas nécessaire d'installer tout cela soi-même,  Heroku propose une liste de buildpacks pour faciliter ce processus.

Un buildpack est un ensemble de scripts adaptés à un langage spécifique. Ces scripts gèrent l'intégralité des étapes nécessaires à la construction d'une application :

  • installation de l'écosystème
  • installation des dépendances
  • compilation du code source
  • génération des assets
  • ...

Heroku propose des buildpacks adaptés aux technologies majeures du web (Python, Ruby, Java, PHP, ...) et il existe également une vaste bibliothèque de buildpacks créés par la communauté pour intégrer des outils moins communs ou pour des besoins plus spécifiques (Elixir,  Create-React-App, ...).

Bien comprendre le app.json

Le fichier app.json est un fichier descriptif utilisé pour la création des review apps pour indiquer à Heroku  comment construire lesdites applications.

La documentation sur le sujet est très claire donc je ne rentrerai pas en détail sur le sujet. Néanmoins voici quelques explications sur certains points parfois mal compris :

  • Le fichier app.json n'est lu QUE[1] pour la création de review apps, il n'a aucune incidence sur les applications de staging et de production.
  • Chaque changement sur l'une de ces applications devra probablement être reporté sur l'app.json et vice-versa.
  • Une variable d'environnement qui n'est pas renseignée dans la rubrique env du fichier ne sera pas disponible dans les review apps
  • L'essentiel du fichier n'est utilisé qu'à la création des review apps. Si le app.json est modifié alors que l'application a déjà été créée, il sera probablement nécessaire de détruire et re-créer celle-ci pour que les modifications soient effectives.
  • En cas de problème survenant plus loin dans le pipeline, il faut toujours vérifier que la configuration des différentes application (review, staging et production) soit la même

[1] En vérité, ce fichier est également lu lors du déploiement d'une application via un bouton deploy to Heroku

Dyno Metadata

Il arrive souvent que nos applications aient besoin du nom de l'application Heroku courante pour fonctionner correctement. Le plus souvent, cela arrive lorsqu'il faut générer un lien pour un email automatique car, dans ce cas de figure, il n'y a pas de "connexion" sur laquelle se baser pour récupérer le nom d'hôte.

Le moyen le plus simple est d'utiliser les Dyno Metadata. Cette fonctionnalité ajoute à l'application différentes métadonnées la concernant sous la forme de variables d'environnement.

Ainsi il devient possible d'accéder à la variable d'environnement HEROKU_APP_NAME qui contiendra le nom de l'application Heroku. De même en l'ajoutant à la liste des variables requises du fichier app.json, cette variable sera disponible dans chaque review app.

Comment installer des dépendances privées

Un projet inclus parfois des dépendances privées, par exemple un algorithme écrit à part ou une bibliothèque de composants partagés. Il n'y a aucun problème particulier en développement puisque votre gestionnaire de dépendance délèguera généralement l'authentification à votre agent SSH ou alors vous demandera de saisir vos identifiants.

En revanche le problème se pose lors du déploiement Heroku. Comment faire dans ce cas ?

Prenons le cas d'usage typique : votre dépendance privée est disponible sur GitHub (ou un concurrent).

Dans ce cas, il faut commencer par y ajouter une clé de déploiement pour des raisons de sécurité. Une clé de déploiement permet à Heroku d'accéder en lecture à votre dépendance. De cette manière si par malheur cette clé tombe entre les mains d'autrui, cette personne n'aurait accès qu'à CE projet et uniquement en lecture.

Une fois fait, il faut pouvoir ajouter votre clé de déploiement aux applications Heroku qui la requièrent. Pour cela il faut encoder la clé d'accès privée générée précédemment en Base64.

cat deploy_key | base64

Cet encodage est nécessaire pour pouvoir ajouter votre clé en tant que variable d'environnement à votre application Heroku. Une fois la clé ajoutée, il faudra ajouter un script au build de votre application qui pourra décoder la clé et l'ajouter à la configuration SSH de la dyno Heroku courante.

Voici un example de script BASH effectuant ces étapes :

#!/bin/bash
# Generates an SSH config file for connections if a config var exists.

if [ "$GIT_SSH_KEY" != "" ]; then
    echo "Detected SSH key for git. Adding SSH config" >&1
    echo "" >&1

    # Ensure we have an ssh folder
    if [ ! -d ~/.ssh ]; then
    mkdir -p ~/.ssh
    chmod 700 ~/.ssh
    fi

    # Load the private key into a file.
    echo $GIT_SSH_KEY | base64 --decode > ~/.ssh/deploy_key

    # Change the permissions on the file to
    # be read-only for this user.
    chmod 400 ~/.ssh/deploy_key

    # Setup the ssh config file.
    echo -e "Host github.com\n"\
            " IdentityFile ~/.ssh/deploy_key\n"\
            " IdentitiesOnly yes\n"\
            " UserKnownHostsFile=/dev/null\n"\
            " StrictHostKeyChecking no"\
            > ~/.ssh/config
fi

Pour mettre en place tout ça, le mieux est de passer par un buildpack qui fera ce traitement si nécessaire.

Le buildpack Node.JS offre déjà la possibilité d'intercaler une étape juste avant l'installation des dépendances via l'ajout d'un script heroku-prebuild (voir ce post pour plus d'informations).

De manière plus générique, il existe un buildpack dédié permettant de réaliser tout ça. Attention tout de même à ce que ce buildpack soit installé avant les autres pour que les suivants bénéficient de cette configuration.

Dans le cas où la dépendance privée est déjà hébergée sur un outil plus spécifique (NPM, Hex, ...), la logique reste la même. La seule différence est qu'au lieu de définir une configuration SSH via une clé de déploiement, il faudra définir un token d'accès dans un fichier de configuration dédié (.npmrc, ...).

Conclusion

Cet article tire sa substance de notre expérience dans l'utilisation d'Heroku. Chacun des points évoqués ici correspond à une problématique que nous avons rencontrée et résolue en exploitant au mieux les fonctionnalités proposées par cette plateforme. Auparavant, le déploiement de nos applications était laborieux et souvent synonyme de stress. Aujourd'hui nos processus de déploiement sont fluides et ne sont plus qu'une formalité très vite expédiée, ce qui nous permet de consacrer plus de temps au développement de nos applications. Et la vie est plus belle...

Jean-Serge Monbailly

Jean-Serge Monbailly

Lire d'autres articles par cet auteur.

Lille