envoy, docker en websockets - debuggen en configuratie
Websockets zijn een opwindende technologie, die het mogelijk maakt een HTTP-verbinding op te waarderen tot een langlopende persistente binaire verbinding, die je kunt gebruiken om bi-directionele berichten te versturen.
Terzijde: het MQTT-protocol kan worden getransporteerd met behulp van websockets - wat de enige (?) manier is voor een JavaScript-client die bijvoorbeeld door de website wordt geleverd.
Hoe dan ook, aangezien websockets op dezelfde poorten draaien als het normale HTTP- en HTTPS-verkeer (80 / 443), is het waarschijnlijker dat bedrijfsnetwerken ze doorlaten.
Envoy en websockets
Envoy ondersteunt websockets. Er zijn wat haken en ogen:
Kan JSON niet parseren als proto (INVALID_ARGUMENT:(route_config.virtual_hosts[3].routes[0].route) use_websocket: Kan veld niet vinden.):
Envoy ondersteunde vroeger websockets met een oude directive, "use_websocket". Op een huidige envoy installatie (bijv. Ik gebruik momenteel envoy 1.10.0-dev voor testdoeleinden) is deze directive verdwenen en is hij vervangen.
Het idee is om meer generieke upgrade verzoeken / mogelijkheden te hebben.
De correcte syntaxis is nu het gebruik van upgrade_configs. Kijk eens naar mijn envoy.yaml:
static_resresources:
luisteraars:
- adres:
socket_address:
adres: 0.0.0.0
port_value: 80
filter_ketens:
- filters:
- naam: 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:
- naam: debug
domeinen: ["debug.local:80"]
routes:
- match: { prefix: "/" }
route:
cluster: target_dwebsocket
- naam: backend
domeinen: ["morpheus.local"]
routes:
- match: { prefix: "/" }
omleiden:
path_redirect: "/"
https_redirect: true(snip)
- adres:
socket_address:
adres: 0.0.0.0
port_value: 443
filter_ketens:
- tls_context:
common_tls_context:
tls_certificaten:
- certificate_chain: { bestandsnaam: "/etc/envoy/example.crt" }
private_key: { bestandsnaam: "/etc/envoy/example.key" }
alpn_protocollen: [ "h2,http/1.1" ]
filters:
- naam: 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:
- naam: debug
domeinen: ["debug.local:443"]
routes:
- match: { prefix: "/" }
route:
cluster: target_dwebsocket
(snip)clusters:
- naam: target_dwebsocket
connect_timeout: 0.25s
type: strict_dns
lb_policy: round_robin
gastheren:
- socket_address:
adres: dwse
port_value: 8080
Je kan deze upgrade_configs directive op twee plaatsen gebruiken, volgens de documentatie. op http_connection_manager (zoals in mijn voorbeeld hierboven), en op individuele routes.
upgrade_configs:
- upgrade_type: websocket
Routes komen niet overeen
Het andere belangrijke ding om op te merken, als u "404s" krijgt ( fout: Onverwachte serverreactie: 404 ): de :authority header zal, om de een of andere vreemde reden, ingesteld zijn inclusief de poort - en zal daarom niet overeenkomen als u alleen het domein opgeeft.
Kijk naar deze route:
[ "debug.local:80"]
De poort wordt gespecificeerd na het domein in deze instantie. In mijn tests - alleen als de poort was gespecificeerd, was ik in staat om via envoy verbinding te maken met de websocket server die ik heb opgezet. Dit is een groot struikelblok.
Als volgende zal ik bespreken, hoe envoy te debuggen in gevallen als deze.
Debuggen van Envoy, websockets, Docker:
Envoy uitvoer verbose maken
Hier is mijn docker-compose.yaml:
versie: '3.7'
diensten:
gezant:
bouwen:
context: ./
dockerfile: Dockerfile
container_naam: penvoyage-morpheus-envoy
havens:
– “80:80”
– “443:443”
volumes:
- type: volume
bron: penvoyage_volume
doelwit: /etc/envoy
netwerken:
- gezantschap_net
#user: "2000:2000"
gebruiker: "root:root"
herstarten: tenzij-gestopt
omgeving:
loglevel: debugvolumes:
penvoyage_volume:
uitwendig:
naam: penvoyage_volumenetwerken:
envoy_net:
uitwendig:
naam: mijn-brug-netwerk
Merk op dat een omgevingsvariabele "loglevel: debug" is ingesteld.
loglevel kan een zijn van:
- spoor
- debug
- info
- waarschuw
Het starten van de container zal nu veel meer uitvoer opleveren:
docker-compose up
Merk op dat we niet ontkoppelen van de container, om direct de uitvoer te zien. (Als je tevreden bent, kun je het uitvoeren met docker-compose up -d)
Je uitvoer zou er nu ongeveer zo uit moeten zien:
penvoyage-morpheus-envoy | [2019-05-18 14:20:32.093][1][debug][main] [source/server/server.cc:143] flushing stats
op een verbinding van een websocket client, krijg je de volgende uitvoer:
penvoyage-morpheus-envoy | [2019-05-18 14:21:14.915][23][debug][main] [source/server/connection_handler_impl.cc:257] [C0] nieuwe verbinding
penvoyage-morpheus-envoy | [2019-05-18 14:21:14.916][23][debug][http] [source/common/http/conn_manager_impl.cc:210] [C0] nieuwe stroom
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 | ':methode', 'GET'
penvoyage-morpheus-envoy | "sec-websocket-versie", "13
penvoyage-morpheus-envoy | "sec-websocket-key", "HQFiCTWiFMktGDPFXwzrjQ==
penvoyage-morpheus-envoy | "verbinding", "Upgrade
penvoyage-morpheus-envoy | "upgrade", "websocket
penvoyage-morpheus-envoy | 'sec-websocket-extensies', '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 voor 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 decodeert headers:
penvoyage-morpheus-envoy | ':authority', 'debug.local:80'.
penvoyage-morpheus-envoy | ':path', '/mqtt_drinks_are_free'
penvoyage-morpheus-envoy | ':methode', 'GET'
penvoyage-morpheus-envoy | ':scheme', 'http'
penvoyage-morpheus-envoy | "sec-websocket-versie", "13
penvoyage-morpheus-envoy | "sec-websocket-key", "HQFiCTWiFMktGDPFXwzrjQ==
penvoyage-morpheus-envoy | "verbinding", "Upgrade
penvoyage-morpheus-envoy | 'upgrade', 'websocket'
penvoyage-morpheus-envoy | 'sec-websocket-extensies', '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'.
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] een nieuwe verbinding maken
penvoyage-morpheus-envoy | [2019-05-18 14:21:14.919][23][debug][client] [source/common/http/codec_client.cc:26] [C1] connecting
penvoyage-morpheus-envoy | [2019-05-18 14:21:14.919][23][debug][connection] [source/common/network/connection_impl.cc:638] [C1] verbinding maken met 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] verbinding in uitvoering
penvoyage-morpheus-envoy | [2019-05-18 14:21:14.919][23][debug][pool] [source/common/http/conn_pool_base.cc:20] queueing request due to no available connections
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] aangesloten
penvoyage-morpheus-envoy | [2019-05-18 14:21:14.919][23][debug][pool] [source/common/http/http1/conn_pool.cc:236] [C1] aankoppelen aan volgend verzoek
penvoyage-morpheus-envoy | [2019-05-18 14:21:14.919][23][debug][router] [source/common/router/router.cc:1122] [C0][S4222457489265630919] pool klaar
penvoyage-morpheus-envoy | [2019-05-18 14:21:14.920][23][debug][router] [source/common/router/router.cc:669] [C0][S4222457489265630919] upstream headers compleet: 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] coderen van headers via codec (end_stream=false):
penvoyage-morpheus-envoy | ':status', '101'
penvoyage-morpheus-envoy | 'upgrade', 'websocket'
penvoyage-morpheus-envoy | "verbinding", "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 |
Let op de :authority header zoals ik hierboven vermeldde:
':authority', 'debug.local:80'.
Hier is een authority header voor een normale website aanvraag:
penvoyage-morpheus-envoy | ':methode', 'GET'
penvoyage-morpheus-envoy | ':authority', 'morpheus.local'
penvoyage-morpheus-envoy | ':scheme', 'https'
Let op de aanwezigheid van de poort in het eerste geval (verzoek via websocket), en de afwezigheid van de poort in het tweede geval.
Testcontainer
Let wel, dat https://hub.docker.com/r/dataferret/websocket-echo het dataferret/websocket-echo beeld lijkt te zijn gebroken!
Er schijnt enige incompatibiliteit te zijn in twisted / Autobahn voor de specifieke versie die werd gebruikt, en daarom heb ik besloten om gewoon mijn eigen versie te maken:
We gaan een test container bouwen die de invoer echoot die we er naar toe sturen als een websocket.
Hier is de Dockerfile:
VAN python:stretch
KOPIE assets/websocketd /usr/bin/websocketd
RUN chmod +x /usr/bin/websocketd
KOPIE assets/run.py /opt/run.py
WERKDIR /opt8080 BLOOTLEGGEN
ENTRYPOINT ["websocketd", "-port=8080", "python3", "/opt/run.py"]
Merk op dat bij gebruik van ENTRYPOINT, wanneer de container wordt gestart met CMD vanuit docker-compose.yml, de CMD waarde zal worden doorgegeven als een parameter aan het ENTRYPOINT commando.
websocketd is een websocket daemon, u kunt de standalone executable hier verkrijgen:
Ik heb https://github.com/joewalnes/websocketd/releases/download/v0.3.0/websocketd-0.3.0-linux_amd64.zip en het uitgepakt in de asset map die ik gemaakt heb voor mijn docker container.
Het algemene idee van deze handige applicatie is om een omhulsel rond je applicatie te maken om in staat te zijn websockets te serveren.
Het enige wat uw applicatie hoeft te doen is luisteren op stdin, en de antwoorden / eventuele communicatie schrijven op stdout.
run.py - dit is een eenvoudig script dat alles echot wat er naar toe geschreven wordt:
#!/usr/bin/python
importeren systerwijl dat waar is:
read_co = sys.stdin.readline()
print(read_co, end="")
sys.stdout.flush()
Let op het gebruik van readline(), die de string onmiddellijk teruggeeft na een newline. Anders heb je te maken met buffers / wachten tot de invoer zich ophoopt.
Let ook op de sys.stdout.flush()
Tenslotte, hier is de docker-compose.yml:
versie: '3.6'
diensten:
dwse:
bouwen:
context: ./
dockerfile: Dockerfile
container_naam: dwse
hostnaam: dwse
havens:
– “8078:8080”
netwerken:
- gezantschap_net
herstarten: altijd
commando: "80"
#user: "root:root"netwerken:
envoy_net:
uitwendig:
naam: mijn-brug-netwerk
Merk op dat ik het in hetzelfde netwerk "my-bridge-network" heb gezet als envoy hierboven.
het commando hier is een willekeurige "80" die niet wordt geïnterpreteerd door de applicatie, maar je zou uitgebreidere commandoregels, enz. kunnen maken door dit te gebruiken.
debuggen
Tenslotte willen we dat een client verbinding maakt en in staat is om de berichten te zien die we sturen naar onze test websocket service, die wordt geproxied via envoy.
Ik stel voor wscat voor dit.
wscat is een npm (node.js) pakket. Daarom moet je npm eerst installeren:
apt install npm
Dan kun je wscat installeren:
npm install -g wscat
Test wscat met een publieke websocket echo dienst:
wscat -c ws://echo.websocket.org
een lijst met opdrachtregelopties voor wscat:
wscat -help
Om een beveiligde websocket verbinding via envoy met zelf-ondertekende certificaten te controleren doe:
wscat -c ws://debug.local/mqtt_drinks_are_free -no-check
Hints
Onverwachte server reactie
fout: Onverwacht antwoord van de server: 404
Kijk eens naar je route: zet je de poort in het domein om overeen te komen, zoals hierboven besproken? Blijkbaar is dat nodig voor websocket clients.
Wanneer je twijfelt, voeg dan een match all domein toe aan het einde van je virtuele hosts, om debuggen gemakkelijker te maken. Ik stel voor om hier een vast pad op af te stemmen, in plaats van een prefix - door het pad dat je gebruikt in wscat te veranderen, kun je op deze manier verifiëren welke regel gematcht werd:
- naam: matcheverything
domeinen: ["*"]
routes:
- match: { path: "/mqtt" }
route: { cluster: target_dwebsocket }
Kan JSON niet ontleden / kan veld niet vinden:
Kan JSON niet parseren als proto (INVALID_ARGUMENT:(route_config.virtual_hosts[3].routes[0].route) use_websocket: Kan veld niet vinden.):
Zoals hierboven besproken, is dit een verouderde configuratie die door de nieuwe configuratie is vervangen:
upgrade_configs:
- upgrade_type: websocket
geen cluster overeenkomst voor URL
[source/common/router/router.cc:278] [C0][S5067510418337690596] geen cluster overeenkomst voor URL '/'
Zie hierboven - de URL wordt gematched inclusief de poort voor Websockets blijkbaar (in mijn tests), dus voeg een regel toe om alle domeinen te matchen, en debug vanaf daar / gebruik de syntax die ik hierboven heb gegeven:
route_config:
virtual_hosts:
- naam: debug
domeinen: ["debug.local:80"]
routes:
- match: { prefix: "/" }
route:
cluster: target_dwebsocket
zelf ondertekend certificaat
fout: zelf ondertekend certificaat
Voer wscat uit met de -no-check opdrachtregel parameter (dit is een dubbel streepje dat WordPress zal verknoeien):
Websocket verbinding mislukt:
pcp-code.js:15 WebSocket verbinding met 'wss://key:[email protected]/mqtt' mislukt: Fout tijdens WebSocket handshake: Onverwachte antwoordcode: 403
Zie informatie hierboven: hoogstwaarschijnlijk is deze javascript foutmelding te wijten aan een verkeerde configuratie van de domain match (zie hierboven).
autobahn fout:
"Om txaio te gebruiken, moet je eerst een framework selecteren " exceptions.RuntimeError: Om txaio te gebruiken, moet je eerst een framework selecteren met .use_twisted() of .use_txaio()
Dit is de fout die ik kreeg toen ik de dataferret/websocket-echo image draaide. Ik stel voor om mijn code te gebruiken die ik hierboven heb gegeven als een alternatief.
Referenties:
- https://hub.docker.com/r/dataferret/websocket-echo werkt niet voor mij! Ik heb alternatieve code gegeven in deze blogpost voor een eenvoudige Docker websocket echo container
- https://github.com/vi/websocat een alternatief voor wscat
- https://github.com/websockets/wscat
- https://github.com/erebe/wstunnel kunt u verbindingen over websockets tunnelen, inclusief draadbescherming - interessant spul!
- ws://echo.websocket.org - echodienst op een websocket
- http://websocketd.com/ zorgt voor het afhandelen van de WebSocket verbindingen, en het opstarten van uw toepassingen om de WebSockets af te handelen (geïsoleerd van elkaar)
- https://github.com/envoyproxy/envoy/issues/3301 hier wordt de upgrade van de configuratie nieuwe stijl besproken
- https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/websocket.html - officiële documentatie van de gezant
- 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 - Ik zag hier de juiste manier om te configureren, en werk die nu verder uit
- https://www.envoyproxy.io/docs/envoy/latest/operations/cli envoy commandoregel informatie