envoy, docker et websockets - débogage et configuration
Les websockets sont une technologie passionnante, qui vous permet de transformer une connexion HTTP en une connexion binaire persistante de longue durée, que vous pouvez utiliser pour envoyer des messages bidirectionnels.
Soit dit en passant, le protocole MQTT peut être transporté à l'aide de websockets - ce qui est le seul ( ?) moyen pour un client JavaScript livré par le site web, par exemple.
Dans tous les cas, comme les websockets fonctionnent sur les mêmes ports que le trafic HTTP et HTTPS normal (80 / 443), les réseaux d'entreprise sont plus susceptibles de les laisser passer.
Envoy et websockets
Envoy supporte les websockets. Il y a quelques problèmes :
Impossible d'analyser JSON comme proto (INVALID_ARGUMENT :(route_config.virtual_hosts[3].routes[0].route) use_websocket : Impossible de trouver le champ.):
Envoy avait l'habitude de supporter les websockets avec une ancienne directive, "use_websocket". Sur une installation actuelle d'Envoy (par exemple, j'utilise actuellement Envoy 1.10.0-dev à des fins de test), cette directive a disparu et a été remplacée.
L'idée est d'avoir des demandes/possibilités de mise à niveau plus génériques.
La syntaxe correcte consiste maintenant à utiliser mise à niveau_configs. Jetez un coup d'oeil à mon envoy.yaml :
ressources_statiques :
des auditeurs :
- l'adresse :
adresse_socle :
adresse : 0.0.0.0
valeur_du_port : 80
chaînes de filtres :
- filtres :
- nom : envoy.http_connection_manager
config :
upgrade_configs :
- upgrade_type : websocket
codec_type : auto
stat_prefix : ingress_http
use_remote_address : true
xff_num_trusted_hops : 0
route_config :
virtual_hosts :
- nom : debug
domaines : ["debug.local:80"]
routes :
- match : { préfixe : "/" }
route :
cluster : target_dwebsocket
- nom : backend
domaines : ["morpheus.local"]
routes :
- match : { préfixe : "/" }
réorienter :
path_redirect : "/"
https_redirect : true(snip)
- l'adresse :
adresse_socle :
adresse : 0.0.0.0
port_value : 443
chaînes de filtres :
- tls_context :
common_tls_context :
tls_certificats :
- certificate_chain : { filename : "/etc/envoy/example.crt" }
private_key : { filename : "/etc/envoy/example.key" }
alpn_protocols : [ "h2,http/1.1" ]
filtres :
- nom : envoy.http_connection_manager
config :
upgrade_configs :
- upgrade_type : websocket
stat_prefix : ingress_https
use_remote_address : true
xff_num_trusted_hops : 0
route_config :
virtual_hosts :
- nom : debug
domaines : ["debug.local:443"]
routes :
- match : { préfixe : "/" }
route :
cluster : target_dwebsocket
(snip)clusters :
- nom : target_dwebsocket
connect_timeout : 0.25s
type : strict_dns
lb_policy : round_robin
hôtes :
- adresse_socle :
adresse : dwse
valeur_du_port : 8080
Vous pouvez utiliser cette directive upgrade_configs à deux endroits, selon la documentation. sur http_connection_manager (comme dans mon exemple ci-dessus), et sur les routes individuelles.
upgrade_configs :
- upgrade_type : websocket
Routes ne correspondant pas
L'autre chose importante à noter, si vous obtenez des "404" ( error : Unexpected server response : 404 ) : l'en-tête :authority sera, pour une raison étrange, défini en incluant le port - et ne correspondra donc pas si vous fournissez uniquement le domaine.
Regardez cette route :
["debug.local:80"]
Dans ce cas, le port est spécifié après le domaine. Lors de mes tests, ce n'est que si le port était spécifié que je pouvais me connecter via envoy au serveur websocket que j'ai mis en place. C'est une pierre d'achoppement majeure.
La prochaine fois, je vais discuter, comment déboguer envoy dans des cas comme celui-ci.
Débogage d'Envoy, websockets, Docker :
Rendre la sortie d'envoy verbeuse
Voici mon docker-compose.yaml :
version : " 3.7 ".
services :
envoyé :
construire :
contexte : ./
dockerfile : Dockerfile
nom_du_conteneur : penvoyage-morpheus-envoy
ports :
– “80:80”
– “443:443”
volumes :
- type : volume
source : penvoyage_volume
cible : /etc/envoy
réseaux :
- envoy_net
#user : "2000:2000"
utilisateur : "root:root"
redémarrage : à moins qu'il ne s'agisse d'un arrêt
l'environnement :
loglevel : debugvolumes :
penvoyage_volume :
externe :
nom : penvoyage_volumeréseaux :
envoy_net :
externe :
nom : my-bridge-network
Notez qu'une variable d'environnement "loglevel : debug" est définie.
loglevel peut être l'un des suivants :
- trace
- déboguer
- info
- avertir
Le démarrage du conteneur donne maintenant beaucoup plus de résultats :
docker-compose up
Notez que nous ne nous détachons pas du conteneur, pour voir directement sa sortie. (Une fois que vous êtes satisfait, vous pouvez l'exécuter en utilisant docker-compose up -d)
Votre résultat devrait maintenant ressembler à quelque chose comme ceci :
penvoyage-morpheus-envoy | [2019-05-18 14:20:32.093][1][debug][main] [source/server/server.cc:143] vidage des statistiques
sur une connexion d'un client websocket, vous obtiendrez le résultat suivant :
penvoyage-morpheus-envoy | [2019-05-18 14:21:14.915][23][debug][main] [source/server/connection_handler_impl.cc:257] [C0] nouvelle connexion
penvoyage-morpheus-envoy | [2019-05-18 14:21:14.916][23][debug][http] [source/common/http/conn_manager_impl.cc:210] [C0] nouveau flux
penvoyage-morpheus-envoy | [2019-05-18 14:21:14.918][23][debug][http] [source/common/http/conn_manager_impl.cc:548] [C0][S4222457489265630919] en-têtes de requête complets (end_stream=false) :
penvoyage-morpheus-envoy | ':authority', 'debug.local:80'.
penvoyage-morpheus-envoy | ':path", "/mqtt_drinks_are_free
penvoyage-morpheus-envoy | ':method', 'GET
penvoyage-morpheus-envoy | 'sec-websocket-version', '13'
penvoyage-morpheus-envoy | 'sec-websocket-key', 'HQFiCTWiFMktGDPFXwzrjQ=='
penvoyage-morpheus-envoy | 'connexion', 'Mise à jour'.
penvoyage-morpheus-envoy | mise à jour, websocket
penvoyage-morpheus-envoy | 'sec-websocket-extensions', 'permessage-deflate ; client_max_window_bits'.
penvoyage-morpheus-envoy |
penvoyage-morpheus-envoy | [2019-05-18 14:21:14.918][23][debug][router] [source/common/router/router.cc:321] [C0][S4222457489265630919] correspondance du cluster 'target_dwebsocket' pour l'URL '/mqtt_drinks_are_free'.
penvoyage-morpheus-envoy | [2019-05-18 14:21:14.918][23][debug][router] [source/common/router/router.cc:379] [C0][S4222457489265630919] routeur décodant les en-têtes :
penvoyage-morpheus-envoy | ':authority', 'debug.local:80'.
penvoyage-morpheus-envoy | ':path', '/mqtt_drinks_are_free
penvoyage-morpheus-envoy | ':method', 'GET
penvoyage-morpheus-envoy | ':scheme', 'http
penvoyage-morpheus-envoy | 'sec-websocket-version', '13'
penvoyage-morpheus-envoy | 'sec-websocket-key', 'HQFiCTWiFMktGDPFXwzrjQ=='
penvoyage-morpheus-envoy | 'connexion', 'Mise à jour'.
penvoyage-morpheus-envoy | 'upgrade', 'websocket' (mise à jour)
penvoyage-morpheus-envoy | 'sec-websocket-extensions', 'permessage-deflate ; client_max_window_bits'.
penvoyage-morpheus-envoy | 'content-length', '0'
penvoyage-morpheus-envoy | 'x-forwarded-for', '192.168.1.2'
penvoyage-morpheus-envoy | 'x-forwarded-proto', 'http
penvoyage-morpheus-envoy | 'x-envoy-internal', 'true
penvoyage-morpheus-envoy | 'x-request-id', 'ca8a765c-e549-4c45-988c-58b6c853df7b', 'x-request-id'.
penvoyage-morpheus-envoy | 'x-envoy-expected-rq-timeout-ms', '15000'
penvoyage-morpheus-envoy |
penvoyage-morpheus-envoy | [2019-05-18 14:21:14.918][23][debug][pool] [source/common/http/http1/conn_pool.cc:82] création d'une nouvelle connexion
penvoyage-morpheus-envoy | [2019-05-18 14:21:14.919][23][debug][client] [source/common/http/codec_client.cc:26] [C1] connexion
penvoyage-morpheus-envoy | [2019-05-18 14:21:14.919][23][debug][connexion] [source/common/network/connection_impl.cc:638] [C1] connexion à 172.18.0.8:8080
penvoyage-morpheus-envoy | [2019-05-18 14:21:14.919][23][debug][connexion] [source/common/network/connection_impl.cc:647] [C1] connexion en cours
penvoyage-morpheus-envoy | [2019-05-18 14:21:14.919][23][debug][pool] [source/common/http/conn_pool_base.cc:20] mise en file d'attente de la demande en raison d'aucune connexion disponible
penvoyage-morpheus-envoy | [2019-05-18 14:21:14.919][23][debug][connexion] [source/common/network/connection_impl.cc:516] [C1] connecté
penvoyage-morpheus-envoy | [2019-05-18 14:21:14.919][23][debug][client] [source/common/http/codec_client.cc:64] [C1] connecté
penvoyage-morpheus-envoy | [2019-05-18 14:21:14.919][23][debug][pool] [source/common/http/http1/conn_pool.cc:236] [C1] attachement à la prochaine requête
penvoyage-morpheus-envoy | [2019-05-18 14:21:14.919][23][debug][router] [source/common/router/router.cc:1122] [C0][S4222457489265630919] pool prêt
penvoyage-morpheus-envoy | [2019-05-18 14:21:14.920][23][debug][router] [source/common/router/router.cc:669] [C0][S4222457489265630919] en-têtes amont complets : end_stream=false
penvoyage-morpheus-envoy | [2019-05-18 14:21:14.920][23][debug][http] [source/common/http/conn_manager_impl.cc:1234] [C0][S4222457489265630919] encodage des en-têtes via le codec (end_stream=false) :
penvoyage-morpheus-envoy | ':status', '101'
penvoyage-morpheus-envoy | 'upgrade', 'websocket' (mise à jour)
penvoyage-morpheus-envoy | 'connexion', 'Mise à jour'.
penvoyage-morpheus-envoy | 'sec-websocket-accept', '2W9caJQU0JKW3MhWV6T8FHychjk='
penvoyage-morpheus-envoy | "date", "Sat, 18 May 2019 14:21:14 GMT
penvoyage-morpheus-envoy | 'serveur', 'envoy'.
penvoyage-morpheus-envoy | 'content-length', '0'
penvoyage-morpheus-envoy |
Notez l'en-tête :authority que j'ai mentionné plus haut :
':authority', 'debug.local:80'.
Voici un en-tête d'autorité pour une demande normale de site web :
penvoyage-morpheus-envoy | ':method', 'GET
penvoyage-morpheus-envoy | ':authority', 'morpheus.local
penvoyage-morpheus-envoy | ':scheme', 'https
Notez la présence du port dans le premier cas (demande via websocket), et l'absence du port dans le second cas.
Testcontainer
Veuillez noter que https://hub.docker.com/r/dataferret/websocket-echo l'image dataferret/websocket-echo semble être brisé!
Il semble y avoir une certaine incompatibilité dans twisted / Autobahn pour la version particulière qui a été utilisée, et j'ai donc décidé de simplement rouler mon propre :
Nous allons construire un conteneur de test qui fera écho à l'entrée que nous lui envoyons par websocket.
Voici le Dockerfile:
FROM python:stretch
COPY assets/websocketd /usr/bin/websocketd
RUN chmod +x /usr/bin/websocketd
COPY assets/run.py /opt/run.py
répertoire de travail /optEXPOSE 8080
ENTRYPOINT ["websocketd", "-port=8080", "python3", "/opt/run.py"]
Notez qu'en utilisant ENTRYPOINT, lorsque le conteneur est démarré avec CMD à partir de docker-compose.yml, la valeur CMD sera passée comme paramètre à la commande ENTRYPOINT.
websocketd est un démon websocket, vous pouvez obtenir l'exécutable autonome ici :
J'ai téléchargé https://github.com/joewalnes/websocketd/releases/download/v0.3.0/websocketd-0.3.0-linux_amd64.zip et l'a décompressé dans le dossier des actifs que j'ai créé pour mon conteneur docker.
L'idée générale de cette application astucieuse est de fournir une enveloppe autour de votre application pour pouvoir servir des websockets.
Tout ce que votre application doit faire, c'est écouter sur stdin, et écrire les réponses / toute communication sur stdout.
run.py - Il s'agit d'un script simple qui fera écho à tout ce qui lui est écrit :
#!/usr/bin/python
importer syspendant que Vrai :
read_co = sys.stdin.readline()
print(read_co, end="")
sys.stdout.flush()
Remarquez l'utilisation de readline(), qui retournera la chaîne immédiatement après une nouvelle ligne. Sinon, vous devez gérer les tampons et attendre que l'entrée s'accumule.
Remarquez aussi que sys.stdout.flush()
Enfin, voici le docker-compose.yml:
version : "3.6
services :
dwse :
construire :
contexte : ./
dockerfile : Dockerfile
nom_du_conteneur : dwse
nom d'hôte : dwse
ports :
– “8078:8080”
réseaux :
- envoy_net
redémarrage : toujours
commande : "80"
#user : "root:root"réseaux :
envoy_net :
externe :
nom : my-bridge-network
Remarquez que je l'ai mis dans le même réseau "my-bridge-network" que envoy ci-dessus.
la commande ici est un "80" aléatoire qui n'est pas interprété par l'application, mais vous pourriez créer des lignes de commande plus élaborées, etc. en utilisant ceci.
Débogage
Enfin, nous voulons qu'un client se connecte et soit capable de voir les messages que nous envoyons à notre service websocket de test, qui est proxié par envoy.
Je propose wscat pour ça.
wscat est un paquet npm (node.js). Par conséquent, vous devez d'abord installer npm :
apt install npm
Ensuite, vous pouvez installer wscat :
npm install -g wscat
Testez wscat avec un service public d'écho websocket :
wscat -c ws://echo.websocket.org
obtenir une liste d'options de ligne de commande pour wscat :
wscat -help
Pour vérifier une connexion websocket sécurisée via envoy avec des certificats auto-signés, faites :
wscat -c ws://debug.local/mqtt_drinks_are_free -no-check
Conseils
Réponse inattendue du serveur
erreur : Réponse inattendue du serveur : 404
Jetez un coup d'oeil à votre route : mettez-vous le port dans le domaine pour correspondre, comme discuté ci-dessus ? Apparemment, cela semble être nécessaire pour les clients websocket.
En cas de doute, incluez un domaine match all à la fin de vos hôtes virtuels, pour faciliter le débogage. Je suggère de définir un chemin d'accès fixe pour cette correspondance, au lieu d'un préfixe - en changeant le chemin que vous utilisez dans wscat, de cette façon, vous pouvez vérifier quelle règle a été satisfaite :
- nom : matcheverything
domaines : ["*"]
routes :
- match : { path : "/mqtt" }
route : { cluster : target_dwebsocket }
Impossible d'analyser le JSON / Impossible de trouver le champ :
Impossible d'analyser JSON comme proto (INVALID_ARGUMENT :(route_config.virtual_hosts[3].routes[0].route) use_websocket : Impossible de trouver le champ.):
Comme indiqué plus haut, il s'agit d'un ancien style de configuration qui a été remplacé par la nouvelle configuration :
upgrade_configs :
- upgrade_type : websocket
aucun cluster ne correspond à l'URL
[source/common/router/router.cc:278] [C0][S5067510418337690596] Aucun cluster ne correspond à l'URL '/'.
Voir ci-dessus - l'URL correspond y compris le port pour les Websockets apparemment (dans mes tests), donc inclure une règle pour correspondre à tous les domaines, et déboguer à partir de là / utiliser la syntaxe que j'ai fourni ci-dessus :
route_config :
virtual_hosts :
- nom : debug
domaines : ["debug.local:80"]
routes :
- match : { préfixe : "/" }
route :
cluster : target_dwebsocket
certificat auto-signé
erreur : certificat auto-signé
Exécutez wscat avec le paramètre de ligne de commande -no-check (il s'agit d'un double tiret que WordPress va gâcher) :
La connexion Websocket a échoué :
pcp-code.js:15 La connexion WebSocket à 'wss://key:[email protected]/mqtt' a échoué : Erreur pendant l'établissement de la connexion WebSocket : Unxpected response code : 403
Voir les informations ci-dessus : il est fort probable que ce message d'erreur javascript soit dû à une mauvaise configuration de la correspondance de domaine (voir ci-dessus).
erreur d'autobahn :
" Pour utiliser txaio, vous devez d'abord sélectionner un framework " exceptions.RuntimeError : Pour utiliser txaio, vous devez d'abord sélectionner un framework avec .use_twisted() ou .use_txaio().
Voici l'erreur que j'ai obtenue en exécutant l'image dataferret/websocket-echo. Je vous suggère d'utiliser le code que j'ai fourni ci-dessus comme alternative.
Références :
- https://hub.docker.com/r/dataferret/websocket-echo ne fonctionne pas pour moi ! J'ai fourni un code alternatif dans ce blogpost pour un simple conteneur Docker websocket echo.
- https://github.com/vi/websocat une alternative à la wscat
- https://github.com/websockets/wscat
- https://github.com/erebe/wstunnel vous permet de tunneliser des connexions sur des websockets, y compris garde-barrière - intéressant !
- ws://echo.websocket.org - service d'écho sur une websocket
- http://websocketd.com/ se charge de gérer les connexions WebSocket et de lancer vos applications pour gérer les WebSockets (isolées les unes des autres).
- https://github.com/envoyproxy/envoy/issues/3301 Nous abordons ici la mise à niveau de la configuration du nouveau style
- https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/websocket.html - documentation officielle de l'envoyé
- https://www.envoyproxy.io/docs/envoy/latest/api-v2/api/v2/route/route.proto#envoy-api-field-route-routeaction-upgrade-configs
- https://www.envoyproxy.io/docs/envoy/latest/api-v2/api/v2/route/route.proto#envoy-api-msg-route-routeaction-upgradeconfig
- https://github.com/envoyproxy/envoy/issues/4740 - J'ai vu la manière correcte de configurer ici, et je l'approfondis.
- https://www.envoyproxy.io/docs/envoy/latest/operations/cli Informations sur la ligne de commande d'Envoy