Un guide de Pipy, un proxy réseau programmable pour le cloud

Pipy est un processeur de flux réseau open source, natif du cloud. Il est de conception modulaire et peut créer un proxy réseau hautes performances. Il est écrit en C++ et repose sur la bibliothèque d’E/S asynchrones Asio. Pipy est idéal pour une variété de cas d’utilisation allant des routeurs de périphérie, des équilibreurs de charge, des solutions proxy, des passerelles API, des serveurs HTTP statiques, des sidecars de maillage de services, etc.
Pipy est également livré avec un support JavaScript intégré via PipyJS. PipyJS est hautement personnalisable et prévisible en termes de performances, sans surcharge de récupération de place. Actuellement, PipyJS fait partie de la base de code Pipy, mais il n’en dépend pas et, à l’avenir, il pourrait être déplacé vers un package autonome.
Contents
Guide de démarrage rapide Pipy
Vous pouvez exécuter la version de production de Pipy en utilisant Podman ou Docker avec l’un des scripts de didacticiel fournis sur le référentiel officiel Pipy Git. L’image du conteneur Pipy peut être configurée avec quelques variables d’environnement :
-
PIPY_CONFIG_FILE=</path/to/config-file>
définit l’emplacement du fichier de configuration de Pipy. -
PIPY_SPAWN=n
définit le nombre d’instances de Pipy que vous souhaitez démarrer, oùn
est le nombre d’instances. Il s’agit d’un index de base zéro, donc 0 représente 1 instance. Par exemple, utilisezPIPY_SPAWN=3
pour 4 instances.
Démarrez le serveur Pipy avec cet exemple de script :
$ docker run --rm -e PIPY_CONFIG_FILE=\
https://raw.githubusercontent.com/flomesh-io/pipy/main/tutorial/01-hello \
-e PIPY_SPAWN=1 -p 8080:8080 flomesh/pipy-pjs:latest
Vous remarquerez peut-être qu’au lieu d’un fichier local, ce code fournit un lien vers un script Pipy distant via la variable d’environnement PIPY_CONFIG_FILE
. Pipy est assez intelligent pour gérer cela.
Pour information, voici le contenu du fichier tutorial/01-hello/hello.js
:
pipy()
.listen(8080)
.serveHTTP(
new Message('Hi, there!\n')
)
Ce script simple définit un pipeline Port, qui écoute sur le port 8080 et renvoie “Salut, là !” pour chaque requête HTTP reçue sur le port d’écoute.
Comme vous avez exposé le port local 8080 avec la commande docker run, vous pouvez procéder à un test sur le même port :
$ curl http://localhost:8080
L’exécution de la commande ci-dessus affiche Hi, there!
sur la console.
À des fins d’apprentissage, de développement ou de débogage, il est recommandé de procéder à l’installation locale (soit en construisant Pipy à partir des sources, soit en téléchargeant une version pour votre système d’exploitation) de Pipy, car il est livré avec une console Web d’administration ainsi qu’une documentation et des didacticiels.
Une fois installé localement, en cours d’exécution pipy
sans aucun argument démarre la console d’administration sur le port 6060, mais il peut être configuré pour écouter sur le port différent avec le --admin-port
option.
(Ali Naqvi, CC BY-SA 40)
Pour compiler Pipy à partir des sources ou pour installer un binaire précompilé pour votre système d’exploitation, reportez-vous à README.md sur le pipi Référentiel Git.
Exécuter Pipy dans un terminal
Pour démarrer un proxy Pipy, exécutez Pipy avec un fichier de script PipyJS, par exemple, le script dans tutorial/01-hello/hello.js
si vous avez besoin d’un simple serveur d’écho qui répond avec le même corps de message reçu à chaque requête entrante :
$ pipy tutorial/01-hello/hello.js
Alternativement, lors du développement et du débogage, on peut démarrer Pipy avec une interface utilisateur Web intégrée :
$ pipy tutorial/01-hello/hello.js --admin-port=6060
Pour voir toutes les options de ligne de commande, utilisez le --help
drapeau:
Pipy est un processeur de flux
Pipy fonctionne sur des flux réseau à l’aide d’un pipeline piloté par les événements où il consomme le flux d’entrée, effectue des transformations fournies par l’utilisateur et génère le flux. Un flux de données pipy prend des données brutes et les résume dans un événement. Un événement peut appartenir à l’une des quatre catégories suivantes :
- Données: Les flux réseau sont composés d’octets de données et se présentent sous forme de blocs. Pipy résume les morceaux dans un événement Data.
-
MessageStart, MessageEnd, StreamEnd: Ces trois événements non liés aux données fonctionnent comme des marqueurs, donnant aux flux d’octets bruts une sémantique de haut niveau sur laquelle la logique métier peut s’appuyer.
Conception Pipy
Le fonctionnement interne de Pipy est similaire à celui des pipelines Unix, mais contrairement aux pipelines Unix, qui traitent des octets discrets, Pipy traite des flux d’événements.
Pipy traite les flux entrants via une chaîne de filtres, où chaque filtre traite des problèmes généraux tels que la journalisation des demandes, l’authentification, le déchargement SSL, le transfert des demandes, etc. Chaque filtre lit sur son entrée et écrit sur sa sortie, la sortie d’un filtre étant connectée à l’entrée du suivant.
Pipelines
Une chaîne de filtres s’appelle un pipeline et Pipy classe les pipelines en 3 catégories différentes en fonction de leurs sources d’entrée.
-
Canalisation portuaire : Lit les événements de données à partir d’un port réseau, les traite, puis réécrit le résultat sur le même port. Il s’agit du modèle de demande et de réponse le plus couramment utilisé. Par exemple, lorsque Pipy fonctionne comme un serveur HTTP, l’entrée d’un pipeline Port est une requête HTTP des clients, et la sortie du pipeline serait une réponse HTTP renvoyée aux clients.
-
Canalisation de minuterie : Obtient périodiquement une paire d’événements MessageStart et MessageEnd comme entrée. Utile quand Cron semblable à un travail fonctionnalité est requise.
-
Sous-pipeline : Fonctionne en conjonction avec un filtre de jointure, tel que link, qui prend les événements de son pipeline prédécesseur, les alimente dans un sous-pipeline pour le traitement, relit la sortie du sous-pipeline, puis la pompe vers le filtre suivant .
La meilleure façon d’examiner les sous-pipelines et les filtres de jointure est de les considérer comme des appelés et des appelants d’un sous-programme dans la programmation procédurale. L’entrée du filtre conjoint est constituée des paramètres du sous-programme, la sortie du filtre conjoint est sa valeur de retour.
Un pipeline racine, tel que Port ou Timer, ne peut pas être appelé à partir de filtres de jointure.
Pour obtenir une liste des filtres intégrés et de leurs paramètres :
$ pipy --list-filters
$ pipy --help-filters
Le contexte
Une autre notion importante dans Pipy est celle des contextes. Un contexte est un ensemble de variables attachées à un pipeline. Chaque pipeline a accès au même ensemble de variables sur une instance Pipy. En d’autres termes, les contextes ont la même forme. Lorsque vous démarrez une instance de Pipy, la première chose que vous faites est de définir la forme du contexte en définissant la ou les variable(s) et leurs valeurs initiales.
Chaque pipeline racine clone le contexte initial que vous définissez au départ. Lorsqu’un sous-pipeline démarre, il partage ou clone le contexte de son parent, selon le filtre commun que vous utilisez. Par exemple, un filtre de lien partage le contexte de son parent tandis qu’un filtre de démultiplexage le clone.
Pour les scripts intégrés dans un pipeline, ces variables de contexte sont leurs variables globales, ce qui signifie que ces variables sont toujours accessibles aux scripts de n’importe où s’ils résident dans le même fichier de script.
Cela peut sembler étrange à un programmeur chevronné, car les variables globales signifient généralement qu’elles sont uniques au monde. Vous n’avez qu’un seul ensemble de ces variables, alors que dans Pipy, nous pouvons en avoir plusieurs ensembles (contextes) en fonction du nombre de pipelines racine ouverts pour les connexions réseau entrantes et du nombre de sous-pipelines clonant les contextes de leurs parents.
Écrire un proxy réseau
Supposons que vous exécutiez des instances distinctes de différents services et que vous souhaitiez ajouter un proxy pour transférer le trafic vers les services concernés en fonction du chemin de l’URL de la demande. Cela vous donnerait l’avantage d’exposer une seule URL et de faire évoluer vos services dans le back-end sans que les utilisateurs aient à se souvenir d’une URL de service distincte. Dans des situations normales, vos services s’exécuteraient sur différents nœuds et chaque service pourrait avoir plusieurs instances en cours d’exécution. Dans cet exemple, supposons que vous exécutez les services ci-dessous et que vous souhaitez leur distribuer le trafic en fonction de l’URI.
-
service-bonjour à
/hi/*
(127.0.0.1:8080, 127.0.0.1:8082) -
service-écho à
/echo
(127.0.0.1:8081) -
service-tell-ip à
/ip_/_*
(127.0.0.1:8082)
Les scripts Pipy sont écrits en JavaScript et vous pouvez utiliser n’importe quel éditeur de texte de votre choix pour les modifier. Alternativement, si vous avez installé Pipy localement, vous pouvez utiliser l’interface utilisateur Web d’administration de Pipy, qui comprend la coloration syntaxique, l’auto-complétion, des conseils, ainsi que la possibilité d’exécuter des scripts, le tout à partir de la même console.
Démarrez une instance Pipy, sans aucun argument, afin que la console d’administration Pipy se lance sur le port 6060. Ouvrez maintenant votre navigateur Web préféré et accédez à [http://localhost:6060](http://localhost:6060/ pour voir l’interface utilisateur Web d’administration Pipy intégrée.
(Ali Naqvi, CC BY-SA 40)
Créer un programme Pipy
Une bonne pratique de conception consiste à séparer le code et les configurations. Pipy prend en charge une telle conception modulaire grâce à ses plugins, que vous pouvez considérer comme des modules JavaScript. Cela dit, vous stockez vos données de configuration dans le dossier config et votre logique de codage dans des fichiers séparés sous le dossier plugins. Le script principal du serveur proxy est stocké dans le dossier racine, le script principal du proxy (proxy.js
) inclura et combinera les fonctionnalités définies dans des modules distincts. Au final, votre structure de dossier finale est :
├── config
│ ├── balancer.json
│ ├── proxy.json
│ └── router.json
├── plugins
│ ├── balancer.js
│ ├── default.js
│ └── router.js
└── proxy.js
1.Cliquez Nouvelle base de codeEntrer /proxy
pour la base de code Nom dans la boîte de dialogue, puis cliquez sur Créer.
-
Clique le + bouton pour ajouter un nouveau fichier. Entrer
/config/proxy.json
pour son nom de fichier, puis cliquez sur Créer. Il s’agit du fichier de configuration utilisé pour configurer votre proxy. -
Vous voyez maintenant
proxy.json
répertorié sous le dossier de configuration dans le volet de gauche. Cliquez sur le fichier pour l’ouvrir et ajoutez la configuration ci-dessous et assurez-vous d’enregistrer votre fichier en cliquant sur l’icône du disque sur le panneau supérieur.{ "listen": 8000, "plugins": [ "plugins/router.js", "plugins/balancer.js", "plugins/default.js" ] }
-
Répétez les étapes 2 et 3 pour créer un autre fichier, /config/router.json, pour stocker les informations de route. Saisissez ces données de configuration :
{ "routes": { "/hi/*": "service-hi", "/echo": "service-echo", "/ip/*": "service-tell-ip" } }
-
Répétez les étapes 2 et 3 pour créer un autre fichier, /config/balancer.json pour stocker votre carte service-to-target. Saisissez les données suivantes :
{ "services": { "service-hi" : ["127.0.0.1:8080", "127.0.0.1:8082"], "service-echo" : ["127.0.0.1:8081"], "service-tell-ip" : ["127.0.0.1:8082"] } }
-
Il est maintenant temps d’écrire votre tout premier script Pipy, qui sera utilisé comme solution de secours par défaut lorsque votre serveur recevra une requête pour laquelle vous n’avez configuré aucune cible (un point de terminaison). Créer le fichier
/plugins/default.js
. Le nom ici n’est qu’une convention et Pipy ne s’appuie pas sur les noms, vous pouvez donc choisir n’importe quel nom que vous aimez. Le script contiendra le code ci-dessous, qui renvoie le code d’état HTTP 404 avec le message No handler found :pipy()
.pipeline('request')
.replaceMessage(
new Message({ status: 404 }, 'No handler found'))
7.Créez le fichier /plugins/router.js
qui stocke votre logique de routage :
(config =>
pipy({
_router: new algo.URLRouter(config.routes), })
.export('router', {
__serviceID: '', })
.pipeline('request')
.handleMessageStart(
msg => (
__serviceID = _router.find(
msg.head.headers.host,
msg.head.path, )
) )
)(JSON.decode(pipy.load('config/router.json')))
-
Créer le fichier
/plugins/balancer.js
, qui stocke votre logique d’équilibrage de charge sous forme de note secondaire. Pipy est livré avec plusieurs algorithmes d’équilibrage de charge, mais pour plus de simplicité, vous utilisez ici l’algorithme Round Robin.(config =>pipy({
_services: (
Object.fromEntries(
Object.entries(config.services).map(
([k, v]) => [
k, new algo.RoundRobinLoadBalancer(v)
]
)
)
),_balancer: null,
_balancerCache: null,
_target: '',
}).import({
__turnDown: 'proxy',
__serviceID: 'router',
}).pipeline('session')
.handleStreamStart(
() => (
_balancerCache = new algo.Cache(
// k is a balancer, v is a target
(k ) => k.select(),
(k,v) => k.deselect(v),
)
)
)
.handleStreamEnd(
() => (
_balancerCache.clear()
)
).pipeline('request')
.handleMessageStart(
() => (
_balancer = _services[__serviceID],
_balancer && (_target = _balancerCache.get(_balancer)),
_target && (__turnDown = true)
)
)
.link(
'forward', () => Boolean(_target),
''
).pipeline('forward')
.muxHTTP(
'connection',
() => _target
).pipeline('connection')
.connect(
() => _target
))(JSON.decode(pipy.load('config/balancer.json')))
-
Écrivez maintenant le point d’entrée, ou le script du serveur proxy, pour utiliser les plugins ci-dessus. La création d’une nouvelle base de code (étape 1) crée une valeur par défaut
main.js
fichier comme point d’entrée. Vous pouvez l’utiliser comme point d’entrée principal, ou si vous préférez utiliser un nom différent, n’hésitez pas à supprimermain.js
et créez un nouveau fichier avec le nom de votre choix. Pour cet exemple, supprimez-le et créez un nouveau fichier nommé/proxy.js
. Assurez-vous de cliquer sur l’icône du drapeau supérieur pour en faire le point d’entrée principal, afin de vous assurer que l’exécution du script est lancée lorsque vous appuyez sur le bouton d’exécution (l’icône de flèche à droite).(config =>pipy()
.export('proxy', {
__turnDown: false,
}).listen(config.listen)
.use(config.plugins, 'session')
.demuxHTTP('request').pipeline('request')
.use(
config.plugins,
'request',
'response',
() => __turnDown
))(JSON.decode(pipy.load('config/proxy.json')))
Jusqu’à présent, votre espace de travail ressemble à ceci :
(Ali Naqvi, CC BY-SA 40)
Pour exécuter votre script, cliquez sur le bouton de l’icône de lecture (4e à partir de la droite). Pipy exécute votre script proxy et vous voyez une sortie semblable à celle-ci :
(Ali Naqvi, CC BY-SA 40)
Cela montre que votre serveur proxy écoute sur le port 8000 (que vous avez configuré dans votre /config/proxy.json
). Utiliser curl pour lancer un test:
$ curl -i [http://localhost:8000](http://localhost:8000)
HTTP/1.1 404 Not Found
content-length: 10
connection: keep-alive
No handler found
Cette réponse est logique car vous n’avez configuré aucune cible pour root. Essayez l’un de vos itinéraires configurés, tels que /hi
:
$ curl -i [http://localhost:8000/hi](http://localhost:8000/hi)
HTTP/1.1 502 Connection Refused
content-length: 0
connection: keep-alive
Vous obtenez 502 Connection Refused
car vous n’avez aucun service en cours d’exécution sur votre port cible configuré.
Vous pouvez mettre à jour /config/balancer.json
avec des détails tels que l’hôte et le port de vos services déjà en cours d’exécution pour l’adapter à votre cas d’utilisation, ou vous pouvez simplement écrire un script dans Pipy pour écouter sur vos ports configurés et renvoyer des messages simples.
Enregistrez ce code dans un fichier sur votre ordinateur local nommé mock-proxy.js
et souvenez-vous de l’emplacement où vous l’avez stocké :
pipy().listen(8080)
.serveHTTP(
new Message('Hi, there!\n')
).listen(8081)
.serveHTTP(
msg => new Message(msg.body)
).listen(8082)
.serveHTTP(
msg => new Message(
`You are requesting ${msg.head.path} from ${__inbound.remoteAddress}\n`
)
)
Ouvrez une nouvelle fenêtre de terminal et exécutez ce script avec Pipy (modifiez /path/to
à l’emplacement où vous avez stocké ce fichier de script) :
$ pipy /path/to/mock-proxy.js
2022-01-11 18:56:31 [INF] [config]
2022-01-11 18:56:31 [INF] [config] Module /mock-proxy.js
2022-01-11 18:56:31 [INF] [config] ================
2022-01-11 18:56:31 [INF] [config]
2022-01-11 18:56:31 [INF] [config] [Listen on :::8080]
2022-01-11 18:56:31 [INF] [config] ----->|
2022-01-11 18:56:31 [INF] [config] |
2022-01-11 18:56:31 [INF] [config] serveHTTP
2022-01-11 18:56:31 [INF] [config] |
2022-01-11 18:56:31 [INF] [config] <-----|
2022-01-11 18:56:31 [INF] [config]
2022-01-11 18:56:31 [INF] [config] [Listen on :::8081]
2022-01-11 18:56:31 [INF] [config] ----->|
2022-01-11 18:56:31 [INF] [config] |
2022-01-11 18:56:31 [INF] [config] serveHTTP
2022-01-11 18:56:31 [INF] [config] |
2022-01-11 18:56:31 [INF] [config] <-----|
2022-01-11 18:56:31 [INF] [config]
2022-01-11 18:56:31 [INF] [config] [Listen on :::8082]
2022-01-11 18:56:31 [INF] [config] ----->|
2022-01-11 18:56:31 [INF] [config] |
2022-01-11 18:56:31 [INF] [config] serveHTTP
2022-01-11 18:56:31 [INF] [config] |
2022-01-11 18:56:31 [INF] [config] <-----|
2022-01-11 18:56:31 [INF] [config]
2022-01-11 18:56:31 [INF] [listener] Listening on port 8080 at ::
2022-01-11 18:56:31 [INF] [listener] Listening on port 8081 at ::
2022-01-11 18:56:31 [INF] [listener] Listening on port 8082 at ::
Vos services fictifs écoutent maintenant sur les ports 8080, 8081 et 8082. Effectuez à nouveau un test sur votre serveur proxy pour voir la réponse correcte renvoyée par votre service fictif.
Résumé
Vous avez utilisé un certain nombre de fonctionnalités Pipy, y compris la déclaration de variables, l’importation et l’exportation de variables, les plugins, les pipelines, les sous-pipelines, le chaînage de filtres, les filtres Pipy comme handleMessageStart
, handleStreamStart
et link, et des classes Pipy comme JSON, algo.URLRouter
, algo.RoundRobinLoadBalancer
, algo.Cache
, et d’autres. Pour plus d’informations, lisez l’excellent Documentation Pipyet via l’interface utilisateur Web d’administration de Pipy, et suivez les didacticiels pas à pas qui l’accompagnent.
Conclusion
Pipy de Flomesh est un processeur de trafic réseau open source, extrêmement rapide et léger. Vous pouvez l’utiliser dans une variété de cas d’utilisation allant des routeurs de périphérie, de l’équilibrage de charge et du proxy (forward et reverse), des passerelles API, des serveurs HTTP statiques, des sidecars de service mesh et de nombreuses autres applications. Pipy est en développement actif et est maintenu par des committers et des contributeurs à plein temps.