Serveurs à gogo: utilisation du CherryPy de Python pour des prototypes évolutifs rapides dans un environnement maigre
TL; DR
Un des résultats de devenir un ingénieur plus âgé est que je veux simplement obtenir des projets faits. Passer du temps à pirater une bibliothèque qui ne fonctionne pas, est mal mis en œuvre, ou mal documenté rend le codage une corvée plutôt que d'une joie. C'est pourquoi j'ai été très heureux de travailler avec CherryPy Récemment:
- Le fichier docker n'est pas compliqué
- Le contenu statique peut être servi rapidement
- Les scripts Python sont des citoyens de première classe
- Les tests sont simples
- Mise à l'échelle mon application est une doublure
Qu'est-ce que CherryPy?
CherryPy est un Framework Web python minimaliste. Il ne remplacera jamais un plus grand cadre plus complet pour les logiciels d'entreprise, mais pour obtenir quelque chose et en cours d'exécution, il est fantastique. Traditionnellement, je voudrais utiliser un SimpleHttpServer pour le prototypage, mais il est fonction de lumière-me demandant de mettre à jour dès que j'ai fait quelque chose qui a vraiment poussé le serveur. CherryPy permet à mes applications d'évoluer suffisamment pour être utile avant d'avoir à passer à un plus grand cadre (le cas échéant). Pour référence, Digital Ocean a fait une belle Comparaison de Python WSGI Serveurs.
Docker et CherryPy
Nous utilisons Docker pour tout ce que nous faisons chez ShareThis, donc faisons le en premier pour que nous ayons un environnement de développement complet avec des tests. Vous pouvez commencer en clonant simplement notre repo. La mise en place de ce système au début prend 10 minutes de plus, mais il sera amorti plusieurs fois dans les premiers jours de codage. C'est sans doute le seul coût que nous payons pour échanger CherryPy dans le Docker contre SimpleHttpServer en ligne de commande.
Dockerfile
FROM python
RUN pip install cherrypy coverage # cherrypy server + python 3 test coverage
ADD *.py /
ADD *.sh /
CMD python run.py
testing.sh
#!/bin/bash
coverage run -a --omit=test.py /test.py
coverage report -m
Run.sh
#!/bin/bash
# docker kill and rm prevent funny errors
docker kill cherrypy > /dev/null 2>&1
docker rm cherrypy > /dev/null 2>&1
# build, test, and run the image
docker build -t cherrypy .
docker run cherrypy /testing.sh
docker run -ti --name cherrypy -p 80:80 cherrypy $@
test.py
import unittest
class TestMethods(unittest.TestCase):
def test_simple(self):
self.assertTrue(True)
if __name__ == '__main__':
unittest.main()
Premier passage: contenu scénarisé
Supposons que vous voulez commencer une expérience Lean pour voir si quelqu'un va atterrir sur une page. La chose la plus rapide à faire est de servir une page statique et des visites de log. Ajoutons le script Run.py manquant:
Run.py (V1)
import cherrypy
class Landing(object):
@cherrypy.expose
def index(self):
return scripted landing
if __name__ == "__main__":
cherrypy.quickstart(Landing(), '/')
À ce stade, vous avez un serveur Web évolutif qui peut faire à peu près n'importe quoi. Je l'utilise pour les tableaux de bord, la surveillance, et une multitude d'outils internes.
À ce stade, vous aurez envie de commencer à mucking avec la configuration. Par exemple, vous souhaitez que votre serveur Web soit déclaré sur 0.0.0.0 au lieu de 127.0.0.1 pour le routage public. Vous voulez servir sur le port 80 pour le trafic en direct. Vous voulez plus de 10 threads pour plusieurs connexions Live (plus sur cela ci-dessous). Vous voulez que le trafic soit acheminé malgré la barre oblique. Vous voulez un hôte d'autres choses de se produire.
Ajoutons quelques configs en ajoutant un appel à cherrypy.config.update
. En outre, nous allons permettre le chargement de contenu statique et un fichier de configuration en changeant cherrypy.quickstart(Landing(), '/')
À cherrypy.quickstart(Landing(), '/', "prod.conf")
. N'oubliez pas d'ajouter prod. conf à votre Dockerfile!!
Run.py (V2)
import cherrypy
class Landing(object):
@cherrypy.expose
def index(self):
return scripted landing
# The config.update is optional, but this will prevent scaling issues in a moment
cherrypy.config.update({
'server.socket_host': '0.0.0.0', # 127.0.0.1 is default
'server.socket_port': 80, # 8080 is default
'server.thread_pool': 100, # 10 is default
'tools.trailing_slash.on': False # True is default
})
if __name__ == "__main__":
cherrypy.quickstart(Landing(), '/', "prod.conf")
À peu près à ce stade, vous aurez probablement besoin d'actifs statiques ainsi. Ajoutons le fichier de configuration avec une référence à l'image statique! Vous pouvez ajouter n'importe quel nombre d'actifs statiques. Je préfère une approche déclarative, mais vous pouvez servir un répertoire Trop.
prod. conf
[/logo.png]
tools.staticfile.on = True
tools.staticfile.filename = "/logo.png"
Expansion rapide
Mise à l'échelle d'un prototype signifie se débarrasser des bloqueurs rapidement. Discutons de quelques-uns des problèmes les plus communs qui affectent CherryPy et comment les sortir de la voie aussi vite que possible.
Contrôles de santé et gestionnaires supplémentaires
Chez ShareThis, nous faisons tourner nos applications à l'intérieur de Kubernetes sur AWS. Nous avons besoin de surveiller l'état de notre conteneur de docker pour les systèmes de savoir si le conteneur est vivant et si l'équilibreur de charge est en bonne santé. Cela peut être largement déposée en vertu de la nécessité de gestionnaires supplémentaires. Voici un exemple simple d'écriture d'un point de terminaison HealthCheck simple et l'ajout du gestionnaire à notre objet d'atterrissage. Maintenant, nous pouvons pointer vers http://localhost/healthcheck pour voir si le serveur est en vie.
...
class HealthCheck(object):
def index(self):
return "Yay
\n"
index.exposed = True
class Landing(object):
healthcheck = HealthCheck()
...
Un effet secondaire de la surveillance sur Kubernetes est que le réseau cache une certaine complexité. Chaque nœud d'un cluster Kubernetes possède un proxy qui mélange les données à chaque fois que le conteneur du docker se trouve à s'exécuter à ce moment. L'équilibreur de charge élastique (ELB) d'Amazon va essayer de garder les connexions en vie à chaque nœud pour faire le contrôle de la santé contre le cluster. Cela signifie que pour chaque nœud du cluster, vous devez en tenir compte dans le nombre de threads disponibles pour le serveur. Dans notre cluster de 20 noeuds, notre application de test ne fonctionnera pas car le ELB accède à 20 threads à la fois. CherryPy a une valeur par défaut de 10 threads, et donc les proxies seront continuellement expulsés de la rotation. Par conséquent, notre augmentation à 100 discussions ci-dessus permettra à l'échelle sans problèmes opérationnels.
Ajout de tests
La complexité entrave la productivité lorsque vous ne savez pas ce qui se passera à mesure que des changements seront apportés. L'ajout de contrats simples à votre suite de tests qui indiquent aux futurs développeurs (y compris vous-même) ce que le code est destiné à faire, peut garder votre code de mise à l'échelle sans beaucoup de frais généraux.
La façon dont nous avons inclus les scripts et l'outil de couverture, vous devriez être en mesure d'ajouter une déclaration d'importation pour tout script Python et la couverture prendra soin du reste. Vous voudrez peut-être ajouter le mock
paquet à votre pip install
pour faciliter des tests plus faciles.
Résumé
CherryPy est bien documenté et s'étend plutôt bien. Souvent, c'est tout ce dont vous avez besoin pour faire fonctionner une application dans un environnement à l'échelle. En commençant par un template, vous pouvez immédiatement avoir un serveur web qui exécute du code python en natif. Pour nousShareThis, nous l'avons utilisé pour le traitement des données en arrière-plan via une interface de repos, la surveillance des flux sur S3 et BQ, et la mise en place rapide de tableaux de bord qui doivent être connectés à des API pour les données. En utilisant Docker, il devient trivial de déployer et de maintenir l'application en production.