Debuggen von MQTT über Websockets auf Envoy 1.28.0

Ich habe unsere Envoy-Installation von Envoy 1.11.1 auf 1.28.0 migriert und verwende nun auch SNI zur Auswahl des richtigen Zertifikats.

Ein großer Teil dieser Migration ist die Aktualisierung der Syntax der Konfiguration für Envoy von der API v2 auf die API v3.

Das Upgrade verlief gut, außer dass unser Websocket-basierter MQTT-Dienst (basierend auf VerneMQ) nicht wie erwartet funktionierte.

Zuerst nahm ich an, dass das Problem in envoy lag. Nachdem ich viele Timeout-Optionen ausprobiert und mir die envoy-Dokumentation angesehen habe, habe ich beschlossen, mit einer neuen Route und einem anderen Broker (Mosquitto) zu experimentieren.

Die folgende Konfiguration funktioniert mit Mosquitto als Broker, für den Fall, dass jemand anderes über das gleiche Problem stolpert.

Hier ein Auszug aus meiner envoy.yaml (die vollständige Konfiguration besteht aus über 87000 Zeilen, die von einem Template-Skript generiert werden, wegen der SNI und der Notwendigkeit, individuelle Listener pro Domain zu haben, wie ich oben erwähnt habe):

statische_Ressourcen:
  Hörer:
  - Adresse:
      socket_address:
        Adresse: 0.0.0.0
        port_value: 443
    per_connection_buffer_limit_bytes: 32768 # 32 KiB
    Listener_Filter:
    - name: tls_inspector
      typed_config:
        "@type": type.googleapis.com/envoy.extensions.filters.listener.tls_inspector.v3.TlsInspector
    filter_chains:
    - filter_chain_match:
        server_names: ["picockpit.com", "www.picockpit.com", "picockpit.com:443", "www.picockpit.com:443"]
      transport_socket:
        Name: envoy.transport_sockets.tls
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext
          common_tls_context:
            tls_certificates:
            - certificate_chain: { filename: "/certs/letsencrypt/live/picockpit.com/fullchain.pem" }
              private_key: { dateiname: "/certs/letsencrypt/live/picockpit.com/privkey.pem" }
            alpn_protocols: [ "h2,http/1.1" ]
      filter:
      - name: envoy.filters.network.http_connection_manager
        typed_config:
          '@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
          stat_präfix: ingress_http
          codec_type: AUTO

          http_filters:
          - Name: envoy.filters.http.compressor
            typed_config:
              '@type': type.googleapis.com/envoy.extensions.filters.http.compressor.v3.Compressor
              compressor_library:
                name: text_optimiert
                typed_config:
                  '@type': type.googleapis.com/envoy.extensions.compression.gzip.compressor.v3.Gzip
                  komprimierungsstufe: BEST_SPEED
                  komprimierung_strategie: DEFAULT_STRATEGY
                  speicher_level: 9
                  fenster_bits: 15
                  chunk_size: 16384
          - Name: envoy.filters.http.router
            typed_config:
             "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
          common_http_protocol_options:
            idle_timeout: 3600s # 1 Stunde

          use_remote_address: true
          xff_num_trusted_hops: 0
          route_config:
            virtual_hosts:
            - name: backend
              Domains: ["picockpit.com", "www.picockpit.com", "picockpit.com:443", "www.picockpit.com:443"]
              Routen:

              - match: { path: "/pidoctor"}
                redirect:
                  path_redirect: "/raspberry-pi/pidoctor-raspberry-pi-system-health-monitor/"
              - match: { prefix: "/pidoctor/"}
                redirect:
                  path_redirect: "/raspberry-pi/pidoctor-raspberry-pi-system-health-monitor/"


              - match: { prefix: "/mqtt/test" }
                route:
                  prefix_rewrite: "/mqtt"
                  cluster: ziel_test
                  Timeout: 0s
                  idle_timeout: 0s
                  upgrade_configs:
                    - upgrade_type: "Websocket"
                      aktiviert: true
                      
              - übereinstimmen: { prefix: "/" }
                route:
                  cluster: target_main
                  Timeout: 0s
  cluster:

    - name: ziel_test
      connect_timeout: 5s
      per_connection_buffer_limit_bytes: 32768 # 32 KiB
      Typ: STRICT_DNS
      lb_policy: ROUND_ROBIN
      load_assignment:
        cluster_name: ziel_test
        Endpunkte:
        - lb_endpoints:
          - endpoint:
              Adresse:
                socket_address:
                  Adresse: mosquitto-test.test-network
                  port_wert: 8025

Beachten Sie, dass ich einen großen Teil der Konfiguration anderer Dienste und Routen ausgelassen habe und Ihnen die target_main-Cluster-Informationen nicht gegeben habe (weil sie für die MQTT-über-Websockets-Situation irrelevant sind).

Beachten Sie den Wert timeout: 0s, was wichtig ist, damit MQTT-Verbindungen fortgesetzt werden können und nicht wie standardmäßig vorgesehen nach 15 Sekunden abgebrochen werden.

Ich habe auch andere Teile hervorgehoben, die meiner Meinung nach relevant sind, damit Verbindungen zu Websockets aufgerüstet werden können (damit MQTT über sie übertragen werden kann). Beachten Sie auch die Port-Nummern die als zusätzliche Domänenübereinstimmungen eingegeben werden.

Mosquitto docker-compose.yml:

Version: '3.6'

Dienstleistungen:
  mosquitto:
    image: eclipse-mosquitto
    container_name: mosquitto-test
    Rechnername: mosquitto-test
    Netzwerke:

      - test_net
    Neustart: "nein"
    Benutzer: "root:root"
    Datenträger:
      - Typ: bind
        Quelle: ./mosquitto.conf
        Ziel: /mosquitto/config/mosquitto.conf


Netzwerke:

  test_net:
    extern:
      name: test-netze


mosquitto.conf:

Hörer 8025
Protokoll Websockets

allow_anonymous wahr
log_type all

Tool zur Überprüfung der Verbindung:

HiveMQ Websocket-Client

Aktualisierung 13.11.2023

MQTT ist wieder online, auch mit VerneMQ:

Ich hatte VerneMQ zwar mehrmals neu gestartet, aber offenbar nicht lange genug gewartet, bis es sich stabilisiert hatte. Ein Kollege hat es heute neu gestartet, und jetzt funktioniert es. Es scheint, dass es 10 - 15 Minuten braucht (in unserem Setup), um voll ansprechbar zu werden und angemessen zu arbeiten.

Daher kann ich bestätigen, dass die obige Konfiguration für envoy auch mit VerneMQ funktioniert.

Gelernte Lektion

Wenn etwas nicht funktioniert, versuchen, das Problem im Zusammenspiel mit einem anderen Werkzeug zu reproduzieren - Wenn es dort funktioniert, dann liegt das Problem möglicherweise nicht bei dem ersten Werkzeug, das Sie geändert haben, sondern bei dem zweiten Werkzeug, mit dem es zusammenarbeiten muss.

und einige zusätzliche Leckereien:

Online-Dokumentation

Nützliche Tools