envoy, Docker und Websockets - Fehlersuche und Konfiguration
Websockets sind eine aufregende Technologie, die es Ihnen ermöglicht, eine HTTP-Verbindung zu einer dauerhaften binären Verbindung auszubauen, die Sie zum Senden bidirektionaler Nachrichten verwenden können.
Nebenbei bemerkt kann das MQTT-Protokoll über Websockets transportiert werden - die einzige (?) Möglichkeit für einen JavaScript-Client, der beispielsweise von der Website bereitgestellt wird.
Da Websockets auf denselben Ports laufen wie der normale HTTP- und HTTPS-Verkehr (80 / 443), werden sie in Unternehmensnetzen mit größerer Wahrscheinlichkeit durchgelassen.
Envoy und Websockets
Envoy unterstützt Websockets. Es gibt einige Probleme:
JSON kann nicht als Proto geparst werden (INVALID_ARGUMENT:(route_config.virtual_hosts[3].routes[0].route) use_websocket: Feld kann nicht gefunden werden.):
Envoy unterstützte früher Websockets mit einer alten Direktive, "use_websocket". Auf einer aktuellen Envoy-Installation (z.B. verwende ich derzeit envoy 1.10.0-dev für Testzwecke) ist diese Direktive verschwunden und wurde ersetzt.
Die Idee ist, allgemeinere Upgrade-Anfragen/Möglichkeiten zu haben.
Die korrekte Syntax ist nun die Verwendung von upgrade_configs. Werfen Sie einen Blick auf meine envoy.yaml:
statische_Ressourcen:
Hörer:
- Adresse:
socket_address:
Adresse: 0.0.0.0
port_wert: 80
filter_chains:
- Filter:
- Name: envoy.http_connection_manager
Konfiguration:
upgrade_configs:
- upgrade_type: Websocket
codec_type: auto
stat_prefix: ingress_http
use_remote_address: true
xff_num_trusted_hops: 0
route_config:
virtuelle_hosts:
- Name: Fehlersuche
Domänen: ["debug.local:80"]
Routen:
- übereinstimmen: { prefix: "/" }
Route:
cluster: ziel_websocket
- Name: Backend
Domains: ["morpheus.local"]
Routen:
- übereinstimmen: { prefix: "/" }
umleiten:
path_redirect: "/"
https_redirect: true(Schnipsel)
- Adresse:
socket_address:
Adresse: 0.0.0.0
port_wert: 443
filter_chains:
- tls_context:
common_tls_context:
tls_zertifikate:
- certificate_chain: { filename: "/etc/envoy/example.crt" }
private_key: { filename: "/etc/envoy/example.key" }
alpn_protocols: [ "h2,http/1.1" ]
Filter:
- Name: envoy.http_connection_manager
Konfiguration:
upgrade_configs:
- upgrade_type: Websocket
stat_prefix: ingress_https
use_remote_address: true
xff_num_trusted_hops: 0
route_config:
virtuelle_hosts:
- Name: Fehlersuche
Domains: ["debug.local:443"]
Routen:
- übereinstimmen: { prefix: "/" }
Route:
cluster: ziel_websocket
(Schnipsel)Clustern:
- Name: target_dwebsocket
connect_timeout: 0.25s
Typ: strict_dns
lb_policy: round_robin
Gastgeber:
- socket_address:
Anschrift: dwse
port_wert: 8080
Sie können diese upgrade_configs-Direktive laut Dokumentation an zwei Stellen verwenden: im http_connection_manager (wie in meinem Beispiel oben) und in einzelnen Routen.
upgrade_configs:
- upgrade_type: Websocket
Nicht übereinstimmende Routen
Ein weiterer wichtiger Punkt, den Sie beachten sollten, wenn Sie "404s" erhalten (Fehler: Unerwartete Serverantwort: 404): der :authority-Header wird aus irgendeinem Grund einschließlich des Ports gesetzt - und stimmt daher nicht überein, wenn Sie nur die Domain angeben.
Sehen Sie sich diese Route an:
["debug.local:80"]
Der Port wird in diesem Fall nach der Domäne angegeben. In meinen Tests konnte ich mich nur dann über envoy mit dem von mir eingerichteten Websocket-Server verbinden, wenn der Port angegeben war. Dies ist ein großer Stolperstein.
Als nächstes werde ich besprechen, wie man envoy in solchen Fällen debuggen kann.
Debugging envoy, Websockets, Docker:
Ausführliche Ausgabe von envoy
Hier ist meine docker-compose.yaml:
Version: '3.7'
Dienstleistungen:
Gesandter:
bauen:
Kontext: ./
Dockerfile: Dockerfile
container_name: penvoyage-morpheus-envoy
Häfen:
– “80:80”
– “443:443”
Volumen:
- Typ: Volumen
Quelle: penvoyage_volume
Ziel: /etc/envoy
Netzwerke:
- envoy_net
1TP3Benutzer: "2000:2000"
Benutzer: "root:root"
Neustart: wenn nicht gestoppt
Umwelt:
Loglevel: DebugVolumen:
penvoyage_volume:
extern:
name: penvoyage_volumeNetzwerke:
envoy_net:
extern:
Name: Mein-Brücken-Netz
Beachten Sie, dass eine Umgebungsvariable "loglevel: debug" gesetzt ist.
loglevel kann einer der folgenden Werte sein:
- Spur
- Fehlersuche
- Infos
- warnen
Das Starten des Containers wird nun viel mehr Output liefern:
docker-compose aufwärts
Beachten Sie, dass wir den Container nicht abkoppeln, um seine Ausgabe direkt zu sehen. (Wenn Sie zufrieden sind, können Sie es mit docker-compose up -d)
Ihre Ausgabe sollte nun in etwa so aussehen:
penvoyage-morpheus-envoy | [2019-05-18 14:20:32.093][1][debug][main] [source/server/server.cc:143] flushing stats
auf eine Verbindung von einem Websocket-Client, erhalten Sie die folgende Ausgabe:
penvoyage-morpheus-envoy | [2019-05-18 14:21:14.915][23][debug][main] [source/server/connection_handler_impl.cc:257] [C0] neue Verbindung
penvoyage-morpheus-envoy | [2019-05-18 14:21:14.916][23][debug][http] [source/common/http/conn_manager_impl.cc:210] [C0] neuer Stream
penvoyage-morpheus-envoy | [2019-05-18 14:21:14.918][23][debug][http] [source/common/http/conn_manager_impl.cc:548] [C0][S4222457489265630919] request headers complete (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 | 'Verbindung', 'Upgrade'
penvoyage-morpheus-envoy | Upgrade', '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] cluster 'target_dwebsocket' match for 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] Router decodiert Header:
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 | 'Verbindung', 'Upgrade'
penvoyage-morpheus-envoy | 'upgrade', 'websocket'
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-intern', 'wahr'
penvoyage-morpheus-envoy | 'x-request-id', 'ca8a765c-e549-4c45-988c-58b6c853df7b'
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] Erstellen einer neuen Verbindung
penvoyage-morpheus-envoy | [2019-05-18 14:21:14.919][23][debug][client] [source/common/http/codec_client.cc:26] [C1] verbindet
penvoyage-morpheus-envoy | [2019-05-18 14:21:14.919][23][debug][connection] [source/common/network/connection_impl.cc:638] [C1] Verbindung zu 172.18.0.8:8080
penvoyage-morpheus-envoy | [2019-05-18 14:21:14.919][23][debug][connection] [source/common/network/connection_impl.cc:647] [C1] Verbindung in Arbeit
penvoyage-morpheus-envoy | [2019-05-18 14:21:14.919][23][debug][pool] [source/common/http/conn_pool_base.cc:20] Anfrage in der Warteschlange, da keine Verbindungen verfügbar sind
penvoyage-morpheus-envoy | [2019-05-18 14:21:14.919][23][debug][connection] [source/common/network/connection_impl.cc:516] [C1] connected
penvoyage-morpheus-envoy | [2019-05-18 14:21:14.919][23][debug][client] [source/common/http/codec_client.cc:64] [C1] connected
penvoyage-morpheus-envoy | [2019-05-18 14:21:14.919][23][debug][pool] [source/common/http/http1/conn_pool.cc:236] [C1] Anhängen an nächste Anfrage
penvoyage-morpheus-envoy | [2019-05-18 14:21:14.919][23][debug][router] [source/common/router/router.cc:1122] [C0][S4222457489265630919] Pool bereit
penvoyage-morpheus-envoy | [2019-05-18 14:21:14.920][23][debug][router] [source/common/router/router.cc:669] [C0][S4222457489265630919] upstream headers complete: 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] Kodierung von Headern über Codec (end_stream=false):
penvoyage-morpheus-envoy | ':status', '101'
penvoyage-morpheus-envoy | 'upgrade', 'websocket'
penvoyage-morpheus-envoy | 'Verbindung', 'Upgrade'
penvoyage-morpheus-envoy | 'sec-websocket-accept', '2W9caJQU0JKW3MhWV6T8FHychjk='
penvoyage-morpheus-envoy | 'date', 'Sat, 18 May 2019 14:21:14 GMT'
penvoyage-morpheus-envoy | 'server', 'envoy'
penvoyage-morpheus-envoy | 'content-length', '0'
penvoyage-morpheus-envoy |
Beachten Sie die oben erwähnte Überschrift :authority:
':authority', 'debug.local:80'
Hier ist ein Autoritäts-Header für eine normale Website-Anfrage:
penvoyage-morpheus-envoy | ':method', 'GET'
penvoyage-morpheus-envoy | ':authority', 'morpheus.local'
penvoyage-morpheus-envoy | ':scheme', 'https'
Beachten Sie das Vorhandensein des Ports im ersten Fall (Anfrage über Websocket) und das Fehlen des Ports im zweiten Fall.
Testcontainer
Bitte beachten Sie, dass https://hub.docker.com/r/dataferret/websocket-echo das dataferret/websocket-echo-Bild scheint zu sein gebrochen!
Es scheint eine Inkompatibilität zwischen Twisted / Autobahn und der verwendeten Version zu geben, so dass ich mich entschlossen habe, meine eigene Version zu entwickeln:
Wir werden einen Testcontainer bauen, der die Eingaben, die wir als Websocket an ihn senden, als Echo wiedergibt.
Hier ist die Dockerdatei:
FROM python:stretch
COPY assets/websocketd /usr/bin/websocketd
RUN chmod +x /usr/bin/websocketd
COPY assets/run.py /opt/run.py
WORKDIR /optEXPOSE 8080
ENTRYPOINT ["websocketd", "-port=8080", "python3", "/opt/run.py"]
Beachten Sie, dass bei der Verwendung von ENTRYPOINT beim Starten des Containers mit CMD aus docker-compose.yml der CMD-Wert als Parameter an den ENTRYPOINT-Befehl übergeben wird.
websocketd ist ein Websocket-Daemon. Sie können die eigenständige ausführbare Datei von hier beziehen:
Ich habe heruntergeladen https://github.com/joewalnes/websocketd/releases/download/v0.3.0/websocketd-0.3.0-linux_amd64.zip und entpackte es in den Ordner assets, den ich für meinen Docker-Container erstellt habe.
Die allgemeine Idee dieser raffinierten Anwendung ist es, einen Wrapper um Ihre Anwendung herum bereitzustellen, um Websockets bereitstellen zu können.
Alles, was Ihre Anwendung tun muss, ist, auf stdin zu hören und die Antworten / jegliche Kommunikation auf stdout zu schreiben.
run.py - Dies ist ein einfaches Skript, das alles ausgibt, was in es geschrieben wird:
#!/usr/bin/python
System einführenwhile True:
read_co = sys.stdin.readline()
print(read_co, end="")
sys.stdout.flush()
Beachten Sie die Verwendung von readline(), das die Zeichenkette sofort nach einem Zeilenumbruch zurückgibt. Andernfalls müssen Sie mit Puffern arbeiten bzw. darauf warten, dass die Eingabe akkumuliert wird.
Beachten Sie auch die Funktion sys.stdout.flush()
Zum Schluss noch die docker-compose.yml:
Version: '3.6'
Dienstleistungen:
dwse:
bauen:
Kontext: ./
Dockerfile: Dockerfile
container_name: dwse
Hostname: dwse
Häfen:
– “8078:8080”
Netzwerke:
- envoy_net
Neustart: immer
Befehl: "80"
1TP3Benutzer: "root:root"Netzwerke:
envoy_net:
extern:
Name: Mein-Brücken-Netz
Beachten Sie, dass ich es in dasselbe Netzwerk "my-bridge-network" wie envoy oben eingefügt habe.
der Befehl hier ist ein zufälliges "80", das von der Anwendung nicht interpretiert wird, aber Sie können damit auch komplexere Befehlszeilen usw. erstellen.
Fehlersuche
Schließlich möchten wir, dass ein Client eine Verbindung herstellt und in der Lage ist, die Nachrichten zu sehen, die wir an unseren Test-Websocket-Dienst senden, der durch envoy vermittelt wird.
Ich schlage vor wscat für diese.
wscat ist ein npm (node.js) Paket. Daher müssen Sie zuerst npm installieren:
apt install npm
Dann können Sie wscat installieren:
npm install -g wscat
Testen Sie wscat mit einem öffentlichen Websocket-Echodienst:
wscat -c ws://echo.websocket.org
erhalten Sie eine Liste der Befehlszeilenoptionen für wscat:
wscat -help
Um eine sichere Websocket-Verbindung über envoy mit selbstsignierten Zertifikaten zu prüfen, gehen Sie wie folgt vor
wscat -c ws://debug.local/mqtt_drinks_are_free -no-check
Hinweise
Unerwartete Serverantwort
Fehler: Unerwartete Serverantwort: 404
Schauen Sie sich Ihre Route an: Haben Sie den Port in der Domain angepasst, wie oben beschrieben? Offenbar scheint dies für Websocket-Clients erforderlich zu sein.
Im Zweifelsfall sollten Sie am Ende Ihrer virtuellen Hosts eine Match-All-Domain einfügen, um die Fehlersuche zu erleichtern. Ich schlage vor, einen festen Pfad anstelle eines Präfixes zu verwenden - durch Ändern des Pfades, den Sie in wscat verwenden, können Sie auf diese Weise überprüfen, welche Regel erfüllt wurde:
- Name: matcheverything
Domänen: ["*"]
Routen:
- übereinstimmen: { path: "/mqtt" }
Route: { cluster: target_dwebsocket }
JSON kann nicht geparst werden / Feld kann nicht gefunden werden:
JSON kann nicht als Proto geparst werden (INVALID_ARGUMENT:(route_config.virtual_hosts[3].routes[0].route) use_websocket: Feld kann nicht gefunden werden.):
Wie bereits erwähnt, handelt es sich hierbei um eine alte Konfiguration, die durch die neue Konfiguration abgelöst wurde:
upgrade_configs:
- upgrade_type: Websocket
keine Clusterübereinstimmung für URL
[source/common/router/router.cc:278] [C0][S5067510418337690596] kein Cluster-Match für URL '/'
Siehe oben - die URL wird anscheinend (in meinen Tests) einschließlich des Ports für Websockets abgeglichen, also fügen Sie eine Regel ein, um alle Domänen abzugleichen, und debuggen Sie von dort aus / verwenden Sie die Syntax, die ich oben angegeben habe:
route_config:
virtuelle_hosts:
- Name: Fehlersuche
Domänen: ["debug.local:80"]
Routen:
- übereinstimmen: { prefix: "/" }
Route:
cluster: ziel_websocket
selbstsigniertes Zertifikat
Fehler: selbst signiertes Zertifikat
Führen Sie wscat mit dem Befehlszeilenparameter -no-check aus (dies ist ein doppelter Bindestrich, den WordPress durcheinanderbringt):
Websocket-Verbindung fehlgeschlagen:
pcp-code.js:15 WebSocket-Verbindung zu 'wss://key:[email protected]/mqtt' fehlgeschlagen: Fehler beim WebSocket-Handshake: Unerwarteter Antwortcode: 403
Siehe obige Informationen: Höchstwahrscheinlich ist diese Javascript-Fehlermeldung auf eine Fehlkonfiguration der Domainübereinstimmung zurückzuführen (siehe oben).
Fehler auf der Autobahn:
"Um txaio zu verwenden, müssen Sie zuerst ein Framework auswählen " exceptions.RuntimeError: Um txaio zu verwenden, müssen Sie zunächst ein Framework mit .use_twisted() oder .use_txaio() auswählen
Dies ist der Fehler, den ich beim Ausführen des dataferret/websocket-echo-Images erhielt. Ich schlage vor, meinen Code zu verwenden, den ich oben als Alternative angegeben habe.
Referenzen:
- https://hub.docker.com/r/dataferret/websocket-echo funktioniert bei mir nicht! Ich habe in diesem Blogpost alternativen Code für einen einfachen Docker-Websocket-Echo-Container bereitgestellt
- https://github.com/vi/websocat eine Alternative zu wscat
- https://github.com/websockets/wscat
- https://github.com/erebe/wstunnel ermöglicht es Ihnen, Verbindungen über Websockets zu tunneln, einschließlich Drahtschutz - Interessantes Zeug!
- ws://echo.websocket.org - Echodienst über einen Websocket
- http://websocketd.com/ kümmert sich um die Handhabung der WebSocket-Verbindungen und den Start Ihrer Anwendungen zur Handhabung der WebSockets (isoliert voneinander)
- https://github.com/envoyproxy/envoy/issues/3301 Hier wird das neue Konfigurations-Upgrade besprochen
- https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/websocket.html - offizielle Abgesandtenunterlagen
- 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 - Ich habe hier den richtigen Weg zur Konfiguration gesehen und erläutere ihn hier
- https://www.envoyproxy.io/docs/envoy/latest/operations/cli Informationen zur Kommandozeile von envoy