Par Emile Heitor – avec la participation de Lucie Saunois –

Il y a quelques mois, nous avions publié un article présentant cette nouvelle tendance de fond qu’est le serverless. Nous vous proposons aujourd’hui de mettre les mains dans le cambouis et d’utiliser cette technologie pour créer une application de représentation graphique de la latence d’un site tiers. Sans déployer un seul serveur physique ou virtuel (en tous cas pas sciemment), et avec très peu de code, nous arriverons ainsi au résultat suivant :

AWS Lambda site serverless

Application sans serveur : le choix des armes

Comme nous l’avons expliqué lors notre article précédent, la pierre angulaire d’une architecture serverless est le duo AWS Lambda / API Gateway. Il est bien sûr possible de manipuler ces deux services indépendamment “à la main”, mais pas seulement ! Une foule de nouveaux projets, permettant de gérer au sein d’un seul logiciel l’envoi de fonctions Lambda et leur association avec un point d’entrée via l’API Gateway, ont récemment vu le jour.

Parmi les plus en vue en ce moment, on note Serverless, Zappa et Chalice. C’est Chalice, issu directement de chez Amazon, que nous avons choisi pour le reste de cet article. Sa prise en main et sa logique nous ont immédiatement séduit et ont fait pencher la balance ! (merci Jean 😉 )

La première brique de l’architecture serverless

L’utilisation et l’installation de Chalice sont très simples et suivent les standards python. Assurons-nous de ne pas polluer notre système de fichiers avec des dépendances inutiles, et créons un virtualenv python spécialement pour l’occasion :

$ virtualenv ve/chalice

$ . ve/chalice/bin/activate

(chalice) $ pip install chalice

Afin de se connecter à AWS, Chalice lit le fichier ~/.aws/config, tout comme awscli. S’il n’existe pas, il faut donc créer ce dernier avec les informations suivantes :

$ cat >> ~/.aws/config

[default]

aws_access_key_id=VOTRE_ACCESS_KEY

aws_secret_access_key=VOTRE_SECRET_KEY

region=VOTRE_REGION (eu-central-1, eu-west-1, etc)

Votre access key et votre secret key sont disponibles dans votre console IAM AWS. Une fois le fichier créé, on peut dès lors lancer notre premier projet :

(chalice) $ chalice new-project sltest

Un répertoire peuplé de plusieurs fichiers apparaît alors :

(chalice) $ ls -l

-rw-r--r--   app.py

-rw-r--r--   requirements.txt

Ici, requirements.txt contient la liste des potentielles dépendances de votre application. App.py est un code python basique, dont la structure n’est pas sans rappeler le fabuleux microframework Flask. Le principe de base est le même : on déclare des routes, qui seront par la suite déclarées dans l’API gateway AWS. Dans le fichier app.py, on associe du code python à ces routes, comme par exemple celui-ci qui permettrait d’afficher « Hello world » :

from chalice import Chalice

 

app = Chalice(app_name='helloworld')

 

@app.route('/')

def index():

return {'hello': 'world'}

Nous allons modifier ce code d’exemple, pour lui faire réaliser quelque chose de plus utile. En l’occurrence, en utilisant la librairie python-requests, nous le feront retourner le temps d’accès et le code de retour d’un site passé en paramètre :

from chalice import Chalice

import requests

 

app = Chalice(app_name='sltest')

app.debug = True

 

@app.route('/status/{website}')

def status(website):

try:

r = requests.get('http://{0}/'.format(website))

return {'rc': r.status_code, 'time': r.elapsed.total_seconds()}

except requests.ConnectionError:

return {'rc': -1, 'time': -1}

Ici, on déclare une route qui aura un format /status/<fqdn>, et on retourne, comme demandé, un dictionnaire contenant le code de retour de la page ainsi que son temps de réponse.

L’utilisation du module tiers python-requests (non obligatoire) implique l’ajout dans le fichier requiremens.txt de la ligne suivante :

requests

On déploie maintenant ce code avec Chalice, qui nous fournira à la fin de son travail l’URL à laquelle nous pouvons accéder au fruit de notre travail (ici, https://monbackend/) :

(chalice) $ chalice deploy

...

Initiating first time deployment...

https://monbackend/

Et on peut d’ores et déjà utiliser ce service, par exemple avec curl ou httpie :

$ http https://monbackend/status/nbs-system.com

HTTP/1.1 200 OK

Access-Control-Allow-Origin: *

Connection: keep-alive

Content-Length: 29

Content-Type: application/json

Date: Mon, 12 Dec 2016 12:04:33 GMT

Via: 1.1 dbde8ac00ce406599a623c34e6eb8a7b.cloudfront.net (CloudFront)

X-Amz-Cf-Id: ZSSmtUHWfTDw08jZ52BQ3zddJ_yk-g0u9sX1y_H68Vg9DVBUh8xNEg==

X-Amzn-Trace-Id: Root=1-584e924f-8352808d2988ddd4ca8be7b1

X-Cache: Miss from cloudfront

x-amzn-RequestId: 23c4964f-c063-11e6-a6ec-e79dcf59fbd8

 

{

"rc": 200,

"time": 0.204534

}

Ça fonctionne, c’est infiniment scalable et nous n’avons pas déployé une seule machine nous-même.

Une présentation avec un site web… toujours sans serveur !

Bien évidemment, un backend seul ne satisfera l’appétit que d’un développeur. Pour présenter les données de façon élégante, il faudra les afficher par exemple via un site web. Et dans cette optique, c’est AWS S3 qui va nous permettre de mettre en place une simple page statique, accessible publiquement, et dont le code javascript / jQuery ira interroger l’API gateway afin de présenter les données “en live”.

Highcharts est un framework javascript connu, simple et très utilisé pour représenter des données graphiquement. Un code de démonstration parfaitement fonctionnel nous servira de base pour notre mini moniteur de latence. Par souci de présentation, nous avons également inclus boostrap, mais libre à vous d’agrémenter votre site avec vos décorateurs préférés.

<!DOCTYPE html>

<html lang="en">

<head>

<meta charset="utf-8">

<meta http-equiv="X-UA-Compatible" content="IE=edge">

<meta name="viewport" content="width=device-width, initial-scale=1">

<title>Bootstrap 101 Template</title>

 

<!-- Bootstrap -->

<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
 integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" 
 crossorigin="anonymous">

 

</head>

<body>

 

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>

<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"
 integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" 
 crossorigin="anonymous"></script>

<script src="https://code.highcharts.com/highcharts.js"></script>

 

<script>

 

var chart;

var fqdn = 'www.example.com';

 

function statuslabel(s) {

$('span.label').removeClass().addClass('label label-' + s);

}

 

function requestData() {

 

$.ajax({

// Ici on accède au backend déployé précédemment

url: 'https://monbackend/status/' + fqdn,

method: 'GET',

success: function(rc) {

var series = chart.series[0],

shift = series.data.length > 20;

point = [(new Date()).getTime(), rc.time];

 

// add the point

chart.series[0].addPoint(point, true, shift);

 

// call it again after one second

setTimeout(requestData, 1000);

fqdn = $('#fqdn').val();

if (rc.rc < 0) {

statuslabel('danger');

} else {

statuslabel('success');

}

 

},

error: function() {

fqdn = $('#fqdn').val();

setTimeout(requestData, 1000);

statuslabel('danger');

},

cache: false

});

}

 

$(document).ready(function() {

chart = new Highcharts.Chart({

chart: {

renderTo: 'container',

defaultSeriesType: 'spline',

events: {

load: requestData

}

},

title: {

text: 'Live latency'

},

xAxis: {

type: 'datetime',

tickPixelInterval: 150,

maxZoom: 20 * 1000

},

yAxis: {

minPadding: 0.2,

maxPadding: 0.2,

title: {

text: 'Value',

margin: 80

}

},

series: [{

name: 'Random data',

data: []

}]

});

});

</script>

 

<div id="container" style="width:100%; height:400px;"></div>

 

<div class="form-group" style="width:30%; margin-left:20px;">

<label for="usr">FQDN

<span class="label label-default">status</span>

</label>

<input type="text" class="form-control" id="fqdn">

</div>

 

</body>

</html>

Dans ce morceau de code Javascript / HTML, on notera en particulier la fonction AJAX qui fait appel au backend précédemment publié avec Chalice. Toujours pour la cosmétique, nous avons également ajouté un petit label qui changera de couleur en fonction de la disponibilité du site mentionné dans la zone de texte input.

On publie ce minuscule site sur AWS S3, à l’aide d’awscli :

$ aws s3 cp sltest.html s3://monbucket/ --acl public-read

À ce stade, une action est nécessaire pour permettre à votre navigateur de réaliser la requête AJAX que nous mentionnions plus haut. En effet, sur les navigateurs récents, notre petite démonstration ne fonctionnera pas car ceux-ci refuseront d’envoyer une requête vers une autre domaine sans l’accord de celui-ci. Pour autoriser l’appel du backend par le frontend depuis votre navigateur, il faut activer l’option CORS (cross-origin resource sharing) pour l’URL du backend qui est appelée, dans la section API Gateway de la console AWS :

AWS Lambda activation de Cors

Une fois validée l’activation de CORS, il ne faut pas oublier de re-déployer l’API (dans le même menu). Oui, j’ai oublié quelques fois et ai passé plusieurs précieuses minutes à essayer de comprendre pourquoi rien ne s’affichait…

Technologie serverless : c’est que le début (air connu)…

Avouez que nous avons réalisé notre premier site serverless et scalable à l’infini avec une déconcertante facilité ! Ok, ok, j’entends les ronchons du fond me dire qu’on avait nullement besoin de la section Lambda en python et qu’on aurait parfaitement pu réaliser ce graphe de latence en temps réel uniquement en Javascript; et c’est vrai. Mais l’idée ici est d’exposer la méthodologie et d’ouvrir les possibilités ! À partir de ce simple exemple, nous pouvons désormais extrapoler toutes les parties du code, interagir avec une base de données, créer de nouveaux points d’entrée à notre backend, réaliser un site d’une redoutable complexité… sans avoir à se soucier de la maintenance des instances qui sous-tiennent ce code.