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: debug

volumes:
   penvoyage_volume:
     uitwendig:
       naam: penvoyage_volume

netwerken:
   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 /opt

8080 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:

http://websocketd.com/

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 sys

terwijl 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

afbeelding

een lijst met opdrachtregelopties voor wscat:

wscat -help

afbeelding

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

afbeelding

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):

afbeelding

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.

Ref: https://stackoverflow.com/questions/34157314/autobahn-websocket-issue-while-running-with-twistd-using-tac-file

Referenties: