Il s'agit du troisième article de blog de notre série sur le déploiement de NGINX Open Source et NGINX Plus en tant que passerelle API.
Note: Sauf indication contraire, toutes les informations contenues dans cet article s'appliquent à la fois à NGINX Plus et à NGINX Open Source. Pour faciliter la lecture, le reste du blog fait simplement référence à « NGINX » lorsque la discussion s’applique aux deux versions.
Les concepts et les avantages des architectures d’applications de microservices ont été bien documentés ces dernières années, et nulle part mieux que sur le blog NGINX . Au cœur des applications de microservices se trouve l’API HTTP, et les deux premiers articles de blog de cette série utilisent une API REST hypothétique pour illustrer comment NGINX aborde ce style d’application.
Malgré la popularité des API REST avec les formats de messages JSON pour les applications modernes, ce n’est pas une approche idéale pour tous les scénarios ou toutes les organisations. Les défis les plus courants sont :
Ces dernières années, gRPC est apparu comme une approche alternative pour la création d’applications distribuées, et d’applications de microservices en particulier. Développé à l'origine chez Google, gRPC a été open source en 2015 et est désormais un projet de la Cloud Native Computing Foundation. De manière significative, gRPC utilise HTTP/2 comme mécanisme de transport, tirant parti de son format de données binaires et de ses capacités de streaming multiplexé.
Les principaux avantages de gRPC sont :
Les deux premiers articles de cette série décrivent comment plusieurs API peuvent être fournies via un seul point d’entrée (par exemple, https://api.example.com ). Le comportement par défaut et les caractéristiques du trafic gRPC nous amènent à adopter la même approche lorsque NGINX est déployé en tant que passerelle gRPC. Bien qu'il soit possible de partager le trafic HTTP et gRPC sur le même nom d'hôte et le même port, il existe un certain nombre de raisons pour lesquelles il est préférable de les séparer :
Pour réaliser cette séparation, nous plaçons la configuration de notre passerelle gRPC dans son propre bloc server{}
dans le fichier de configuration gRPC principal, grpc_gateway.conf , situé dans le répertoire /etc/nginx/conf.d .
Nous commençons par définir le format des entrées dans le journal d’accès pour le trafic gRPC (lignes 1 à 4). Dans cet exemple, nous utilisons un format JSON pour capturer les données les plus pertinentes de chaque requête. Notez, par exemple, que la méthode HTTP n'est pas incluse, car toutes les requêtes gRPC utilisent POST
. Nous enregistrons également le code d’état gRPC avec le code d’état HTTP. Cependant, le code d’état gRPC peut être généré de différentes manières. Dans des conditions normales, grpc-status
est renvoyé sous forme de bande-annonce HTTP/2 depuis le backend, mais pour certaines conditions d'erreur, il peut être renvoyé sous forme d'en-tête HTTP/2, soit par le backend, soit par NGINX lui-même. Pour simplifier le journal d’accès, nous utilisons un bloc de mappage
(lignes 6 à 9) pour évaluer une nouvelle variable $grpc_status
et obtenir le statut gRPC d’où qu’il provienne.
Cette configuration contient deux directives d'écoute
(lignes 12 et 13) afin que nous puissions tester à la fois le trafic en texte clair (port 50051) et le trafic protégé par TLS (port 443). Le paramètre http2
configure NGINX pour accepter les connexions HTTP/2 – notez que cela est indépendant du paramètre ssl
. Notez également que le port 50051 est le port en texte clair conventionnel pour gRPC, mais qu'il n'est pas adapté à une utilisation en production.
La configuration TLS est conventionnelle, à l'exception de la directive ssl_protocols
(ligne 23), qui spécifie TLS 1.2 comme le protocole acceptable le plus faible. La spécification HTTP/2 impose l'utilisation de TLS 1.2 (ou supérieur), ce qui garantit que tous les clients prennent en charge l'extension Server Name Indication (SNI) de TLS. Cela signifie que la passerelle gRPC peut partager le port 443 avec des serveurs virtuels définis dans d'autres blocs de serveurs
.
Pour explorer les capacités gRPC de NGINX, nous utilisons un environnement de test simple qui représente les composants clés d'une passerelle gRPC, avec plusieurs services gRPC déployés. Nous utilisons deux exemples d'applications issus des guides officiels gRPC : helloworld (écrit en Go) et RouteGuide (écrit en Python). L'application RouteGuide est particulièrement utile car elle inclut chacune des quatre méthodes de service gRPC :
Les deux services gRPC sont installés en tant que conteneurs Docker sur notre hôte NGINX. Pour obtenir des instructions complètes sur la création de l’environnement de test, consultez l’ annexe .
Nous configurons NGINX pour connaître les services RouteGuide et helloworld, ainsi que les adresses des conteneurs disponibles.
Nous ajoutons un bloc en amont
pour chacun des services gRPC (lignes 40 à 45 et 47 à 51) et les remplissons avec les adresses des conteneurs individuels qui exécutent le code du serveur gRPC.
Avec NGINX à l'écoute sur le port en texte clair conventionnel pour gRPC (50051), nous ajoutons des informations de routage à la configuration, afin que les demandes des clients atteignent le bon service backend. Mais nous devons d’abord comprendre comment les appels de méthode gRPC sont représentés sous forme de requêtes HTTP/2. Le diagramme suivant montre une version abrégée du fichier route_guide.proto pour le service RouteGuide, illustrant comment le package, le service et la méthode RPC forment l'URI, tel que vu par NGINX.
Les informations transportées dans la requête HTTP/2 peuvent donc être utilisées à des fins de routage en faisant simplement correspondre le nom du package (ici, routeguide
ou helloworld
).
Le premier bloc d'emplacement
(ligne 26), sans aucun modificateur, définit une correspondance de préfixe telle que /routeguide.
correspond à tous les services et méthodes RPC définis dans le fichier .proto correspondant pour ce package. La directive grpc_pass
(ligne 27) transmet donc toutes les requêtes du client RouteGuide au groupe en amont routeguide_service . Cette configuration (et celle parallèle pour le service helloworld sur les lignes 29 et 30) fournit un mappage simple entre un package gRPC et ses services backend.
Notez que l'argument des directives grpc_pass
commence par le schéma grpc://
, qui achemine les requêtes à l'aide d'une connexion gRPC en texte clair. Si le backend est configuré pour TLS, nous pouvons utiliser le schéma grpcs://
pour sécuriser la connexion gRPC avec un chiffrement de bout en bout.
Après avoir exécuté le client RouteGuide, nous pouvons confirmer le comportement du routage en examinant les entrées du fichier journal. Ici, nous voyons que la méthode RPC RouteChat a été acheminée vers le conteneur exécuté sur le port 10002.
$ python route_guide_client.py ... $ tail -1 /var/log/nginx/grpc_log.json | jq { "timestamp": "2021-01-20T12:17:56+01:00", "client": "127.0.0.1", "uri": "/routeguide.RouteGuide/RouteChat", "http-status": 200, « état-grpc » : 0, « en amont » : "127.0.0.1:10002", "rx-octets": 161, « tx-octets » : 212 }
Comme indiqué ci-dessus, le routage de plusieurs services gRPC vers différents backends est simple, efficace et nécessite très peu de lignes de configuration. Cependant, les exigences de routage dans un environnement de production peuvent être plus complexes et nécessiter un routage basé sur d’autres éléments de l’URI (le service gRPC ou même des méthodes RPC individuelles).
L'extrait de configuration suivant étend l'exemple précédent afin que la méthode RPC de streaming bidirectionnel RouteChat
soit acheminée vers un backend et toutes les autres méthodes RouteGuide
vers un backend différent.
La deuxième directive d'emplacement
(ligne 7) utilise le modificateur =
(signe égal) pour indiquer qu'il s'agit d'une correspondance exacte sur l'URI pour la méthode RPC RouteChat
. Les correspondances exactes sont traitées avant les correspondances de préfixe, ce qui signifie qu'aucun autre bloc d'emplacement
n'est pris en compte pour l'URI RouteChat
.
Les erreurs gRPC sont quelque peu différentes de celles du trafic HTTP conventionnel. Les clients s'attendent à ce que les conditions d'erreur soient exprimées sous forme de réponses gRPC, ce qui rend l'ensemble par défaut de pages d'erreur NGINX (au format HTML) inadapté lorsque NGINX est configuré comme passerelle gRPC. Nous abordons ce problème en spécifiant un ensemble de réponses d’erreur personnalisées pour les clients gRPC.
L'ensemble complet des réponses d'erreur gRPC est une configuration relativement longue et largement statique, nous les conservons donc dans un fichier séparé, errors.grpc_conf , et utilisons la directive include
(ligne 34) pour les référencer. Contrairement aux clients HTTP/REST, les applications clientes gRPC ne sont pas censées gérer une large gamme de codes d’état HTTP. La documentation gRPC spécifie comment un proxy intermédiaire tel que NGINX doit convertir les codes d'erreur HTTP en codes d'état gRPC afin que les clients reçoivent toujours une réponse appropriée. Nous utilisons la directive error_page
pour effectuer ce mappage.
Chacun des codes d’état HTTP standard est transmis à un emplacement nommé à l’aide du préfixe @
afin qu’une réponse conforme à gRPC puisse être générée. Par exemple, le HTTP404
la réponse est redirigée en interne vers l'emplacement @grpc_unimplemented
, qui est défini plus loin dans le fichier :
L'emplacement nommé @grpc_unimplemented
est disponible uniquement pour le traitement interne NGINX ; les clients ne peuvent pas le demander directement, car aucun URI routable n'existe. Dans cet emplacement, nous construisons une réponse gRPC en remplissant les en-têtes gRPC obligatoires et en les envoyant, sans corps de réponse, à l'aide du code d'état HTTP204
( Aucun
contenu
).
Nous pouvons utiliser la commande curl(1)
pour imiter un client gRPC mal comporté demandant une méthode gRPC inexistante. Notez cependant que curl
n'est généralement pas adapté comme client de test gRPC car les tampons de protocole utilisent un format de données binaires. Pour tester gRPC sur la ligne de commande, pensez à utiliser grpc_cli
.
$ curl -i --http2 -H "Content-Type: application/grpc" -H "TE: bandes-annonces" -X POST https://grpc.example.com/does.Not/Exist HTTP/2 204 serveur: nginx/1.19.5 date: Mercredi 20 janvier 2021 15:03:41 GMT grpc-status: 12 grpc-message : non implémenté
Le fichier grpc_errors.conf référencé ci-dessus contient également des mappages de codes d’état HTTP vers gRPC pour d’autres réponses d’erreur que NGINX peut générer, telles que les délais d’attente et les erreurs de certificat client.
Les métadonnées gRPC permettent aux clients d'envoyer des informations supplémentaires parallèlement aux appels de méthode RPC, sans exiger que ces données fassent partie de la spécification des tampons de protocole (fichier .proto ). Les métadonnées sont une simple liste de paires clé-valeur, chaque paire étant transmise sous forme d'en-tête HTTP/2 distinct. Les métadonnées sont donc facilement accessibles à NGINX.
Parmi les nombreux cas d’utilisation des métadonnées, l’authentification client est la plus courante pour une passerelle API gRPC. L'extrait de configuration suivant montre comment NGINX Plus peut utiliser les métadonnées gRPC pour effectuer l'authentification JWT (l'authentification JWT est exclusive à NGINX Plus). Dans cet exemple, le JWT est envoyé dans les métadonnées du jeton d'authentification
.
Chaque en-tête de requête HTTP est disponible pour NGINX Plus sous la forme d'une variable appelée $http_ header
. Les tirets ( -
) dans le nom de l'en-tête sont convertis en traits de soulignement ( _
) dans le nom de la variable, de sorte que le JWT est disponible sous la forme $http_auth_token
(ligne 2).
Si des clés API sont utilisées pour l'authentification, peut-être avec des API HTTP/REST existantes, elles peuvent également être transportées dans les métadonnées gRPC et validées par NGINX. Une configuration pour l'authentification par clé API<.htmla> est fournie dans la partie 1 de cette série de blogs.
Lors de l'équilibrage de la charge du trafic vers plusieurs backends, il est important d'éviter d'envoyer des requêtes à des backends en panne ou indisponibles. Avec NGINX Plus, nous pouvons utiliser des contrôles de santé actifs pour envoyer de manière proactive des demandes hors bande aux backends et les supprimer de la rotation d'équilibrage de charge lorsqu'ils ne répondent pas aux contrôles de santé comme prévu. De cette manière, nous garantissons que les demandes des clients n'atteignent jamais des backends hors service.
L'extrait de configuration suivant active les contrôles de santé actifs pour les services gRPC RouteGuide et helloworld ; pour mettre en évidence la configuration pertinente, il omet certaines directives incluses dans le fichier grpc_gateway.conf utilisé dans les sections précédentes.
Pour chaque itinéraire, nous spécifions désormais également la directive health_check
(lignes 17 et 21). Comme spécifié par l'argument type=grpc
, NGINX Plus utilise le protocole de vérification de l'état gRPC pour envoyer un contrôle de l'état à chaque serveur du groupe en amont. Cependant, nos services gRPC simples n’implémentent pas le protocole de vérification de l’état gRPC et nous nous attendons donc à ce qu’ils répondent avec le code d’état qui signifie « non implémenté » ( grpc_status=12
). Lorsque c’est le cas, cela suffit à indiquer que nous communiquons avec un service gRPC actif.
Avec cette configuration en place, nous pouvons supprimer n'importe lequel des conteneurs backend sans que les clients gRPC ne subissent de retards ou d'expirations de délai. Les contrôles de santé actifs sont exclusifs à NGINX Plus ; pour en savoir plus sur les contrôles de santé gRPC, consultez notre blog.
L'exemple de configuration dans grpc_gateway.conf convient à une utilisation en production, avec quelques modifications mineures pour TLS. La possibilité d'acheminer les requêtes gRPC en fonction du package, du service ou de la méthode RPC signifie que les fonctionnalités NGINX existantes peuvent être appliquées au trafic gRPC exactement de la même manière que pour les API HTTP/REST, ou même comme pour le trafic Web classique. Dans chaque cas, le bloc d'emplacement
concerné peut être étendu avec une configuration supplémentaire, telle que la limitation du débit ou le contrôle de la bande passante.
Dans ce troisième et dernier article de blog de notre série sur le déploiement de NGINX Open Source et NGINX Plus en tant que passerelle API, nous nous sommes concentrés sur gRPC en tant que technologie cloud native pour la création d'applications de microservices. Nous avons démontré comment NGINX est capable de fournir des applications gRPC aussi efficacement que des API HTTP/REST, et comment les deux styles d’API peuvent être publiés via NGINX en tant que passerelle API polyvalente.
Les instructions pour configurer l'environnement de test utilisé dans cet article de blog se trouvent dans l' annexe ci-dessous, et vous pouvez télécharger tous les fichiers depuis notre référentiel GitHub Gist .
Consultez les autres articles de blog de cette série :
Pour essayer NGINX Plus en tant que passerelle API, démarrez votre essai gratuit de 30 jours dès aujourd'hui ou contactez-nous pour discuter de vos cas d'utilisation . Pendant votre essai, utilisez l'ensemble complet des fichiers de configuration de notre référentiel GitHub Gist .
Les instructions suivantes installent l’environnement de test sur une machine virtuelle afin qu’il soit isolé et répétable. Cependant, il n’y a aucune raison pour qu’il ne puisse pas être installé sur un serveur physique « bare metal ».
Pour simplifier l'environnement de test, nous utilisons des conteneurs Docker pour exécuter les services gRPC. Cela signifie que nous n’avons pas besoin de plusieurs hôtes pour l’environnement de test, mais que nous pouvons toujours demander à NGINX d’établir des connexions proxy avec un appel réseau, comme dans un environnement de production.
L'utilisation de Docker nous permet également d'exécuter plusieurs instances de chaque service gRPC sur un port différent sans nécessiter de modifications de code. Chaque service gRPC écoute sur le port 50051 dans le conteneur qui est mappé à un port localhost unique sur la machine virtuelle. Cela libère à son tour le port 50051 afin que NGINX puisse l'utiliser comme port d'écoute. Par conséquent, lorsque les clients de test se connectent à l’aide de leur port préconfiguré 50051, ils atteignent NGINX.
Installez NGINX Open Source ou NGINX Plus selon les instructions du Guide d'administration NGINX Plus.
Copiez les fichiers suivants du dépôt GitHub Gist vers /etc/nginx/conf.d :
Note: Si vous n'utilisez pas TLS, commentez les directives ssl_*
dans grpc_gateway.conf .
Démarrez NGINX Open Source ou NGINX Plus.
$ sudo nginx
Pour Debian et Ubuntu, exécutez :
$ sudo apt-get install docker.io
Pour CentOS, RHEL et Oracle Linux, exécutez :
$ sudo yum install docker
Créez l’image Docker pour les conteneurs RouteGuide à partir du Dockerfile suivant.
Vous pouvez soit copier le Dockerfile dans un sous-répertoire local avant la construction, soit spécifier l'URL du Gist pour le Dockerfile comme argument de la commande docker
build
:
$ sudo docker build -t routeguide https://gist.githubusercontent.com/nginx-gists/87ed942d4ee9f7e7ebb2ccf757ed90be/raw/ce090f92f3bbcb5a94bbf8ded4d597cd47b43cbe/routeguide.Dockerfile
Le téléchargement et la création de l'image peuvent prendre quelques minutes. L'apparition du message Successfully
built
et d'une chaîne hexadécimale (l'ID de l'image) signalent la fin de la construction.
Confirmez que l’image a été créée en exécutant docker
images
.
$ sudo docker images RÉFÉRENTIEL BALISE ID IMAGE CRÉÉ TAILLE routeguide le plus récent 63058a1cf8ca il y a 1 minute 1,31 Go python le plus récent 825141134528 il y a 9 jours 923 Mo
Démarrez les conteneurs RouteGuide.
$ sudo docker run --name rg1 -p 10001:50051 -d guide d'itinéraire $ sudo docker run --name rg2 -p 10002:50051 -d guide d'itinéraire $ sudo docker run --name rg3 -p 10003:50051 -d guide d'itinéraire
À chaque fois qu'une commande est exécutée, une longue chaîne hexadécimale apparaît, représentant le conteneur en cours d'exécution.
Vérifiez que les trois conteneurs sont opérationnels en exécutant docker
ps
. (La sortie de l'exemple est divisée sur plusieurs lignes pour faciliter la lecture.)
$ sudo docker ps ID DU CONTENEUR IMAGE ÉTAT DE LA COMMANDE ... d0cdaaeddf0f routeguide "python route_g..." En hausse de 2 secondes... c04996ca3469 routeguide "python route_g..." En hausse de 9 secondes...
2170ddb62898 guide d'itinéraire "python route_g..." En hausse d'une minute ... ... NOMS DES PORTS ... 0.0.0.0:10003->50051/tcp rg3 ... 0.0.0.0:10002->50051/tcp rg2 ... 0.0.0.0:10001->50051/tcp rg1
La colonne PORTS
dans la sortie montre comment chacun des conteneurs a mappé un port local différent au port 50051 à l'intérieur du conteneur.
Créez l’image Docker pour les conteneurs helloworld à partir du Dockerfile suivant.
Vous pouvez soit copier le Dockerfile dans un sous-répertoire local avant la construction, soit spécifier l'URL du Gist pour le Dockerfile comme argument de la commande docker
build
:
$ sudo docker build -t helloworld https://gist.githubusercontent.com/nginx-gists/87ed942d4ee9f7e7ebb2ccf757ed90be/raw/ce090f92f3bbcb5a94bbf8ded4d597cd47b43cbe/helloworld.Dockerfile
Le téléchargement et la création de l'image peuvent prendre quelques minutes. L'apparition du message Successfully
built
et d'une chaîne hexadécimale (l'ID de l'image) signalent la fin de la construction.
Confirmez que l’image a été créée en exécutant docker
images
.
$ sudo docker images RÉFÉRENTIEL BALISE ID IMAGE CRÉÉ TAILLE helloworld le plus récent e5832dc0884a il y a 10 secondes 926 Mo routeguide le plus récent 170761fa3f03 il y a 4 minutes 1,31 Go python le plus récent 825141134528 il y a 9 jours 923 Mo golang le plus récent d0e7a411e3da il y a 3 semaines 794 Mo
Démarrez les conteneurs helloworld.
$ sudo docker run --name hw1 -p 20001:50051 -d helloworld $ sudo docker run --name hw2 -p 20002:50051 -d helloworld
À chaque fois qu'une commande est exécutée, une longue chaîne hexadécimale apparaît, représentant le conteneur en cours d'exécution.
Vérifiez que les deux conteneurs helloworld sont opérationnels en exécutant docker
ps
.
$ sudo docker ps ID DU CONTENEUR IMAGE ÉTAT DE LA COMMANDE ... e0d204ae860a helloworld "go run greeter..." En hausse de 5 secondes...
66f21d89be78 helloworld "va courir salutation..." En hausse de 9 secondes... d0cdaaeddf0f routeguide "python route_g..." En hausse depuis 4 minutes... c04996ca3469 routeguide "python route_g..." En hausse de 4 minutes...
2170ddb62898 guide d'itinéraire "python route_g..." En hausse de 5 minutes ... ... NOMS DES PORTS ... 0.0.0.0:20002->50051/tcp hw2 ... 0.0.0.0:20001->50051/tcp hw1 ... 0.0.0.0:10003->50051/tcp rg3 ... 0.0.0.0:10002->50051/tcp rg2 ... 0.0.0.0:10001->50051/tcp rg1
Installez les prérequis du langage de programmation, dont certains peuvent déjà être installés sur l’environnement de test.
Pour Ubuntu et Debian, exécutez :
$ sudo apt-get install golang-go python3 python-pip git
Pour CentOS, RHEL et Oracle Linux, exécutez :
$ sudo miam installer golang python python-pip git
Notez que python-pip
nécessite que le référentiel EPEL soit activé (exécutez d'abord sudo
yum
install
epel-release
si nécessaire).
Téléchargez l'application helloworld :
$ allez sur google.golang.org/grpc
Téléchargez l'application RouteGuide :
$ git clone -b v1.14.1 https://github.com/grpc/grpc $ pip install grpcio-tools
Exécutez le client helloworld :
$ go run go/src/google.golang.org/grpc/examples/helloworld/greeter_client/main.go
Exécutez le client RouteGuide :
$ cd grpc/exemples/python/route_guide $ python route_guide_client.py
Vérifiez les journaux NGINX pour confirmer que l'environnement de test est opérationnel :
$ tail /var/log/nginx/grpc_log.json
« Cet article de blog peut faire référence à des produits qui ne sont plus disponibles et/ou qui ne sont plus pris en charge. Pour obtenir les informations les plus récentes sur les produits et solutions F5 NGINX disponibles, explorez notre famille de produits NGINX . NGINX fait désormais partie de F5. Tous les liens NGINX.com précédents redirigeront vers un contenu NGINX similaire sur F5.com."