Comment configurer uWSGI et Nginx pour servir les applications Python sur CentOS 7

introduction

Dans ce guide, nous allons configurer une application WSGI simple servie par uWSGI. Nous allons utiliser le serveur Web Nginx en tant que proxy inverse du serveur d’applications afin de fournir une gestion de connexion plus robuste. Nous allons installer et configurer ces composants sur un serveur CentOS 7.

Définitions et concepts

Clarifier certains termes

Avant d’intervenir, nous devrions aborder une terminologie déroutante associée aux concepts interdépendants que nous traiterons. Ces trois termes distincts qui semblent interchangeables, mais ont en réalité une signification distincte:

  • * WSGI *: Python spec définissant une interface standard pour la communication entre une application ou une infrastructure et un serveur d’applications / Web. Ceci a été créé afin de simplifier et normaliser la communication entre ces composants pour la cohérence et l’interchangeabilité. Cela définit fondamentalement une interface API qui peut être utilisée sur d’autres protocoles.

  • * uWSGI *: conteneur de serveur d’applications destiné à fournir une pile complète pour le développement et le déploiement d’applications et de services Web. Le composant principal est un serveur d’applications capable de gérer des applications de différentes langues. Il communique avec l’application à l’aide des méthodes définies par la spécification WSGI et avec d’autres serveurs Web via divers protocoles. Il s’agit de la pièce qui traduit les demandes d’un serveur Web conventionnel dans un format que l’application peut traiter.

  • * uwsgi *: Protocole binaire rapide implémenté par le serveur uWSGI pour communiquer avec un serveur Web plus complet. Ceci est un wire protocole, pas un protocole de transport. C’est le moyen préféré de parler aux serveurs Web qui envoient des requêtes à uWSGI.

Conditions requises pour l’application WSGI

La spécification WSGI définit l’interface entre le serveur Web et les parties d’application de la pile. Dans ce contexte, «serveur Web» fait référence au serveur uWSGI, qui est chargé de la traduction des demandes client vers l’application à l’aide de la spécification WSGI. Cela simplifie la communication et crée des composants à couplage lâche afin que vous puissiez facilement échanger les deux côtés sans trop de peine.

Le serveur Web (uWSGI) doit pouvoir envoyer des demandes à l’application en déclenchant un «appelable» défini. L’appelable est simplement un point d’entrée dans l’application où le serveur Web peut appeler une fonction avec certains paramètres. Les paramètres attendus sont un dictionnaire de variables d’environnement et un appelable fournis par le composant serveur Web (uWSGI).

En réponse, l’application retourne une valeur itérable qui sera utilisée pour générer le corps de la réponse du client. Il appellera également le composant de serveur Web appelable qu’il a reçu en tant que paramètre. Le premier paramètre lors du déclenchement du serveur Web appelable sera le code d’état HTTP et le second sera une liste de n-uplets, chacun définissant un en-tête de réponse et une valeur à renvoyer au client.

Avec le composant «serveur Web» de cette interaction fourni par uWSGI, nous devrons simplement nous assurer que nos applications possèdent les qualités décrites ci-dessus. Nous allons également configurer Nginx pour gérer les demandes des clients et les envoyer par proxy au serveur uWSGI.

Installer les composants

Pour commencer, nous devrons installer les composants nécessaires sur notre serveur CentOS 7. Nous pouvons principalement faire cela en utilisant + yum + et + pip +.

Premièrement, nous devons installer le référentiel EPEL afin d’avoir accès à un plus grand nombre de packages. Nous pouvons le faire facilement en une seule commande + yum + en tapant:

sudo yum install epel-release

Maintenant, nous pouvons installer nos composants. Nous devons obtenir les bibliothèques et les en-têtes de développement Python, le gestionnaire de paquets Python + pip +, ainsi que le serveur Web Nginx et le proxy inverse. Nous aurons également besoin d’un compilateur pour construire momentanément le binaire uWSGI:

sudo yum install python-pip python-devel nginx gcc

Une fois l’installation du paquet terminée, vous aurez accès au gestionnaire de paquets Python + pip +. Nous pouvons l’utiliser pour installer le package + virtualenv +, que nous utiliserons pour isoler l’environnement Python de notre application de tout autre environnement pouvant exister sur le système:

sudo pip install virtualenv

Une fois cette opération terminée, nous pouvons commencer à créer la structure générale de notre application. Nous allons créer l’environnement virtuel décrit ci-dessus et installer le serveur d’applications uWSGI dans cet environnement.

Configurer un répertoire d’applications et un serveur virtuel

Nous allons commencer par créer un dossier pour notre application. Cela peut contenir un dossier imbriqué contenant le code de l’application réelle dans une application plus complète. Pour nos besoins, ce répertoire contiendra simplement notre environnement virtuel et notre point d’entrée WSGI:

mkdir ~/myapp/

Ensuite, déplacez-vous dans le répertoire afin que nous puissions configurer l’environnement pour notre application:

cd ~/myapp

Créez un environnement virtuel avec la commande + virtualenv +. Nous appellerons cela + myappenv + pour simplifier:

virtualenv

Un nouvel environnement Python sera configuré dans un répertoire nommé + myappenv +. Nous pouvons activer cet environnement en tapant:

source /bin/activate

Votre invite devrait changer pour indiquer que vous opérez maintenant dans l’environnement virtuel. Cela ressemblera à ceci:

()@:~/my_app$

Si vous souhaitez quitter cet environnement à tout moment, vous pouvez simplement taper:

deactivate

Si vous avez désactivé votre environnement, réactivez-le pour continuer avec le guide.

Lorsque cet environnement est actif, tous les packages Python installés seront contenus dans cette hiérarchie de répertoires. Ils n’interféreront pas avec l’environnement Python du système. Dans cet esprit, nous pouvons maintenant installer le serveur uWSGI dans notre environnement en utilisant + pip +. Le paquet pour cela s’appelle + uwsgi + (c’est toujours le serveur uWSGI et pas le protocole + uwsgi +):

pip install uwsgi

Vous pouvez vérifier qu’il est maintenant disponible en tapant:

uwsgi --version

S’il renvoie un numéro de version, le serveur uWSGI est disponible.

Créer une application WSGI

Ensuite, nous allons créer une application WSGI incroyablement simple en utilisant les exigences de la spécification WSGI décrites précédemment. Pour réitérer, le composant d’application que nous devons fournir devrait avoir les propriétés suivantes:

  • Il doit fournir une interface via un appelable (une fonction ou une autre construction de langage pouvant être appelée)

  • L’appelable doit prendre comme paramètres un dictionnaire contenant des paires clé-valeur de type variable d’environnement et un appelable accessible sur le serveur (uWSGI).

  • L’appelable de l’application doit renvoyer un itérable qui produira le corps à envoyer au client.

  • L’application doit appeler le serveur Web appelable avec les en-têtes d’état et de demande HTTP.

Nous allons écrire notre application dans un fichier nommé + wsgi.py + dans notre répertoire d’application:

nano ~//wsgi.py

À l’intérieur de ce fichier, nous allons créer l’application compatible WSGI la plus simple possible. Comme pour tout code Python, assurez-vous de faire attention à l’indentation:

def application(environ, start_response):
   start_response('200 OK', [('Content-Type', 'text/html')])
   return ["<h1 style='color:blue'>Hello There!</h1>"]

Le code ci-dessus constitue une application WSGI complète. Par défaut, uWSGI recherchera un appelable appelé + application +, c’est pourquoi nous avons appelé notre fonction + application +. Comme vous pouvez le constater, il faut deux paramètres.

La première s’appelle + environ + car ce sera un dictionnaire clé-valeur ressemblant à une variable environnementale. La seconde s’appelle + start_response + et est le nom que l’application utilisera en interne pour faire référence au serveur Web (uWSGI) appelable qui est envoyé. Ces deux noms de paramètre ont simplement été sélectionnés en raison de leur utilisation dans les exemples de la spécification PEP 333 qui définit les interactions WSGI.

Notre application doit prendre cette information et faire deux choses. Tout d’abord, il doit appeler l’appelable qu’il a reçu avec un code d’état HTTP et les en-têtes qu’il souhaite renvoyer. Dans ce cas, nous envoyons une réponse «200 OK» et définissons l’en-tête + Content-Type sur` + text / html`.

Deuxièmement, il doit être renvoyé avec un itératif à utiliser comme corps de réponse. Ici, nous venons d’utiliser une liste contenant une seule chaîne de code HTML. Les chaînes sont également éditables, mais à l’intérieur d’une liste, uWSGI sera en mesure de traiter la chaîne entière en une seule itération.

Dans un scénario réel, ce fichier serait probablement utilisé comme lien vers le reste du code de votre application. Par exemple, les projets Django incluent un fichier + wsgi.py + par défaut qui traduit les requêtes du serveur Web (uWSGI) en application (Django). L’interface WSGI simplifiée reste la même quelle que soit la complexité du code de l’application. C’est l’un des points forts de l’interface.

Enregistrez et fermez le fichier lorsque vous avez terminé.

Pour tester le code, nous pouvons démarrer uWSGI. Nous lui dirons d’utiliser HTTP pour l’instant et d’écouter sur le port + 8080 +. Nous allons lui passer le nom du script (suffixe supprimé):

uwsgi --socket 0.0.0.0:8080 --protocol=http -w wsgi

Maintenant, si vous visitez l’adresse IP ou le nom de domaine de votre serveur dans votre navigateur Web suivi de +: 8080 +, vous devriez voir le texte d’en-tête de premier niveau que nous avons transmis en tant que corps dans notre fichier + wsgi.py +:

image: https: //assets.digitalocean.com/articles/nginx_uwsgi_wsgi_1404/test_app.png [exemple d’application wsgi]

Arrêtez le serveur avec CTRL-C lorsque vous avez vérifié que cela fonctionne.

Nous en avons terminé avec la conception de notre application réelle à ce stade. Vous pouvez désactiver notre environnement virtuel si vous le souhaitez:

deactivate

Configurer un fichier de configuration uWSGI

Dans l’exemple ci-dessus, nous avons démarré manuellement le serveur uWSGI et lui avons transmis certains paramètres sur la ligne de commande. Nous pouvons éviter cela en créant un fichier de configuration. Le serveur uWSGI peut lire les configurations dans divers formats, mais nous utiliserons le format + .ini + pour plus de simplicité.

Pour continuer à nommer ce que nous avons utilisé jusqu’à présent, nous allons appeler le fichier + myapp.ini + et le placer dans notre dossier d’application:

nano ~/myapp/myapp.ini

À l’intérieur, nous devons établir une section appelée + [uwsgi] +. Cette section est où vivront tous nos éléments de configuration. Nous allons commencer par identifier notre application. Le serveur uWSGI doit savoir où se trouve l’appelable de l’application. Nous pouvons donner le fichier et la fonction dans:

[uwsgi]
module = wsgi:application

Nous voulons marquer le processus initial + uwsgi + en tant que maître, puis générer un certain nombre de processus de travail. Nous allons commencer avec cinq travailleurs:

[uwsgi]
module = wsgi:application

master = true
processes = 5

Nous allons en fait changer le protocole utilisé par uWSGI pour parler avec le monde extérieur. Lors du test de notre application, nous avons spécifié + - protocol = http + afin que nous puissions le voir depuis un navigateur Web. Puisque nous allons configurer Nginx en tant que proxy inverse devant uWSGI, nous pouvons changer cela. Nginx implémente un mécanisme de proxy + uwsgi +, qui est un protocole binaire rapide que uWSGI peut utiliser pour dialoguer avec d’autres serveurs. Le protocole + uwsgi + est en fait le protocole par défaut de uWSGI. Ainsi, en omettant simplement une spécification de protocole, il retombera sur + uwsgi +.

Puisque nous concevons cette configuration pour une utilisation avec Nginx, nous allons également utiliser un port réseau et utiliser un socket Unix à la place. Ceci est plus sécurisé et plus rapide.

Nous allons spécifier le nom de notre propre utilisateur pour exécuter le serveur + uwsgi + et posséder le fichier de socket. Nous allons créer un répertoire sous + / run + pour placer le fichier de socket de manière à ce que uWSGI et Nginx puissent y accéder. Nous appellerons le socket lui-même + myapp.sock +. Nous allons modifier les autorisations en «664» afin que Nginx puisse y écrire (nous allons démarrer uWSGI avec le groupe + www-data + utilisé par Nginx. Nous ajouterons également l’option + vacuum +, qui supprimera le socket lorsque le processus s’arrête:

[uwsgi]
module = wsgi:application

master = true
processes = 5

uid =
socket = /run/uwsgi/myapp.sock
chown-socket = :nginx
chmod-socket = 660
vacuum = true

Nous avons besoin d’une dernière option car nous allons créer un fichier systemd pour démarrer notre application au démarrage. Systemd et uWSGI ont des idées différentes sur ce que le signal SIGTERM devrait faire pour une application. Pour résoudre cet écart afin que les processus puissent être gérés comme prévu avec Systemd, il suffit d’ajouter une option appelée + die-on-term + afin que uWSGI tue le processus au lieu de le recharger:

[uwsgi]
module = wsgi:application

master = true
processes = 5

uid =
socket = /run/uwsgi/myapp.sock
chown-socket = :nginx
chmod-socket = 660
vacuum = true

die-on-term = true

Enregistrez et fermez le fichier lorsque vous avez terminé. Ce fichier de configuration est maintenant configuré pour être utilisé avec un script Upstart.

Créer un fichier d’unité Systemd pour gérer l’application

Nous pouvons lancer une instance uWSGI au démarrage afin que notre application soit toujours disponible. Pour ce faire, nous pouvons créer un fichier unité systemd. Nous placerons cela dans le répertoire + / etc / systemd / system, qui est le meilleur emplacement pour les fichiers unité créés par l’utilisateur. Nous allons appeler le fichier d’unité + uwsgi.service:

sudo nano /etc/systemd/system/uwsgi.service

Premièrement, nous commençons par la section + [Unit] +, où nous pouvons suivre nos métadonnées. La seule chose que nous allons mettre ici est une description de notre service:

[Unit]
Description=uWSGI instance to serve myapp

Ensuite, nous allons ouvrir la section + [Service] +. Comme nous utilisons un environnement virtuel, nos commandes de démarrage de service seront plus complexes qu’elles ne le seraient habituellement. Nous utiliserons une commande + ExecStartPre + pour nous assurer que notre répertoire de socket est créé et appartient aux parties appropriées. Cela sera autorisé à échouer (en mettant un + - + après le signe égal) au cas où ils sont déjà configurés. Cela sera passé dans un seul appel à + ​​bash +.

Pour la commande + ExecStart + qui lancera uWSGI, nous allons également transmettre les commandes réelles à + ​​bash +. Cela nous permet d’exécuter quelques commandes différentes car une seule commande (+ bash dans ce cas) peut être exécutée par cette directive. Nous allons l’utiliser pour changer notre répertoire d’application, activer l’environnement virtuel et démarrer uWSGI avec le fichier + .ini + que nous avons créé:

[Unit]
Description=uWSGI instance to serve myapp

[Service]
ExecStartPre=-/usr/bin/bash -c 'mkdir -p /run/uwsgi; chown :nginx /run/uwsgi'
ExecStart=/usr/bin/bash -c 'cd /home//myapp; source myappenv/bin/activate; uwsgi --ini myapp.ini'

Maintenant, il ne reste plus qu’à formuler la section + [Install] +. Cela déterminera ce qui se passera lorsque nous activerons + l’unité. Fondamentalement, il spécifie quels états l’unité doit démarrer automatiquement. Nous voulons spécifier que, lorsqu’elle est activée, cette unité doit démarrer chaque fois que le serveur est en mode multi-utilisateur:

[Unit]
Description=uWSGI instance to serve myapp

[Service]
ExecStartPre=-/usr/bin/bash -c 'mkdir -p /run/uwsgi; chown :nginx /run/uwsgi'
ExecStart=/usr/bin/bash -c 'cd /home//myapp; source myappenv/bin/activate; uwsgi --ini myapp.ini'

[Install]
WantedBy=multi-user.target

Une fois que vous avez écrit la configuration ci-dessus, enregistrez et fermez le fichier.

Maintenant, nous pouvons démarrer le service en tapant:

sudo systemctl start uwsgi

Vérifiez qu’il a démarré sans problème en tapant:

systemctl status uwsgi

S’il n’y a pas d’erreur, activez le service pour qu’il démarre au démarrage en tapant:

sudo systemctl enable uwsgi

Vous pouvez arrêter le service à tout moment en tapant:

sudo systemctl stop uwsgi

Configurer Nginx sur Proxy sur uWSGI

À ce stade, nous avons une application WSGI et nous avons vérifié qu’UWSGI peut la lire et la servir. Nous avons créé un fichier de configuration et un fichier d’unité Systemd. Notre processus uWSGI écoute sur un socket et communique à l’aide du protocole + uwsgi +.

Nous sommes maintenant sur le point de pouvoir configurer Nginx en tant que proxy inverse. Nginx peut utiliser un proxy en utilisant le protocole + uwsgi + pour communiquer avec uWSGI. C’est un protocole plus rapide que HTTP et donnera de meilleurs résultats.

La configuration de Nginx que nous allons configurer est extrêmement simple. Nous allons modifier le fichier + nginx.conf + existant et ajouter un nouveau bloc serveur. Ouvrez le fichier avec + sudo + pour le modifier:

sudo nano /etc/nginx/nginx.conf

Avant le bloc serveur par défaut, nous ajouterons votre propre bloc serveur:

http {

   . . .

   include /etc/nginx/conf.d/*.conf;




   server {
       listen 80 default_server;
       server_name localhost;

       . . .

Le bloc que nous avons créé contiendra la configuration de notre proxy uWSGI. Les autres éléments de configuration ci-dessous sont placés dans ce bloc. Le bloc serveur doit écouter sur le port 80 et répondre au nom de domaine ou à l’adresse IP de votre serveur:

server {
   listen 80;
   server_name ;
}

Ensuite, nous pouvons ouvrir un seul bloc d’emplacement qui traitera toutes les demandes. Dans ce bloc, nous inclurons les paramètres + uwsgi + trouvés dans le fichier + / etc / nginx / uwsgi_params +, et nous transmettrons le trafic au socket où uWSGI écoute:

server {
   listen 80;
   server_name ;

   location / {
       include uwsgi_params;
       uwsgi_pass unix:/run/uwsgi/myapp.sock;
   }
}

C’est tout ce dont nous avons besoin pour une application simple. Certaines améliorations pourraient être apportées pour une application plus complète. Par exemple, nous pourrions définir un nombre de serveurs uWSGI en amont en dehors de ce bloc, puis les transmettre à celui-ci. Nous pourrions inclure d’autres paramètres uWSGI. Nous pouvons également gérer directement les fichiers statiques de Nginx et transmettre uniquement les requêtes dynamiques à l’instance uWSGI.

Nous n’avons cependant besoin d’aucune de ces fonctionnalités dans notre application à trois lignes afin de pouvoir enregistrer et fermer le fichier.

Vous pouvez tester pour vous assurer que votre configuration Nginx est valide en tapant:

sudo nginx -t

Si cela retourne sans erreur, démarrez le service en tapant:

sudo systemctl start nginx

Démarrez Nginx au démarrage en activant le service:

sudo systemctl enable nginx

Vous devriez pouvoir accéder au nom de domaine ou à l’adresse IP de votre serveur (sans numéro de port) et voir l’application que vous avez configurée:

image: https: //assets.digitalocean.com/articles/nginx_uwsgi_wsgi_1404/full_app.png [application WSGI complète]

Conclusion

Si vous avez réussi jusque-là, vous avez créé une application WSGI simple et avez une idée de la manière dont des applications plus complexes devraient être conçues. Nous avons installé le conteneur / serveur d’applications uWSGI dans un environnement virtuel spécialement conçu pour servir notre application. Nous avons créé un fichier de configuration et un fichier d’unité Systemd pour automatiser ce processus. Devant le serveur uWSGI, nous avons configuré un proxy inverse Nginx pouvant communiquer avec le processus uWSGI à l’aide du protocole filaire + uwsgi +.

Vous pouvez facilement voir comment cela peut être étendu lors de la configuration d’un environnement de production réel. Par exemple, uWSGI a la capacité de gérer plusieurs applications en utilisant ce que l’on appelle le «mode empereur». Vous pouvez développer la configuration de Nginx pour équilibrer la charge entre les instances uWSGI ou pour gérer les fichiers statiques de votre application. Lorsque vous utilisez plusieurs applications, il peut être dans votre intérêt d’installer uWSGI globalement plutôt que dans un environnement virtuel, selon vos besoins. Les composants sont tous assez flexibles, vous devriez donc pouvoir ajuster leur configuration pour s’adapter à de nombreux scénarios différents.