Debug di MQTT su websocket su Envoy 1.28.0

Ho migrato la nostra installazione di Envoy da Envoy 1.11.1 a 1.28.0 e ora sto usando anche SNI per selezionare il certificato corretto.

Una parte importante di questa migrazione è l'aggiornamento della sintassi della configurazione di Envoy dall'API v2 all'API v3.

L'aggiornamento è andato bene, tranne che per il nostro servizio MQTT basato su websocket (basato su VerneMQ) che non funziona come previsto.

All'inizio ho pensato che il problema fosse in envoy. Dopo aver provato molte opzioni di timeout e aver consultato la documentazione di envoy, ho deciso di sperimentare una nuova rotta e un diverso broker (Mosquitto) dietro di essa.

La seguente configurazione funziona con Mosquitto come broker, nel caso in cui qualcun altro si imbatta nello stesso problema.

Ecco un estratto del mio envoy.yaml (la configurazione completa è di oltre 87000 righe, generate da uno script template, a causa dell'SNI e della necessità di avere singoli ascoltatori per dominio, come ho detto sopra):

risorse_statiche:
  ascoltatori:
  - indirizzo:
      socket_address:
        indirizzo: 0.0.0.0
        valore_porta: 443
    limite_buffer_per_connessione_byte: 32768 # 32 KiB
    filtri_ascoltatore:
    - nome: tls_inspector
      typed_config:
        "@type": type.googleapis.com/envoy.extensions.filters.listener.tls_inspector.v3.TlsInspector
    catene_di_filtro:
    - filter_chain_match:
        nomi_server: ["picockpit.com", "www.picockpit.com", "picockpit.com:443", "www.picockpit.com:443"]
      transport_socket:
        nome: 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: { filename: "/certs/letsencrypt/live/picockpit.com/privkey.pem" }
            alpn_protocols: [ "h2,http/1.1" ]
      filters:
      - nome: envoy.filters.network.http_connection_manager
        typed_config:
          '@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
          prefisso_statale: ingress_http
          codec_type: AUTO

          http_filters:
          - nome: envoy.filters.http.compressor
            typed_config:
              '@type': type.googleapis.com/envoy.extensions.filters.http.compressor.v3.Compressor
              compressore_library:
                nome: text_optimized
                typed_config:
                  '@type': type.googleapis.com/envoy.extensions.compression.gzip.compressor.v3.Gzip
                  livello_di_compressione: BEST_SPEED
                  strategia di compressione: DEFAULT_STRATEGY
                  livello_memoria: 9
                  bit_finestra: 15
                  chunk_size: 16384
          - nome: 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 ora

          use_remote_address: true
          xff_num_trusted_hops: 0
          route_config:
            virtual_hosts:
            - nome: backend
              domini: ["picockpit.com", "www.picockpit.com", "picockpit.com:443", "www.picockpit.com:443"]
              percorsi:

              - match: { percorso: "/pidoctor"}
                redirect:
                  path_redirect: "/raspberry-pi/pidoctor-raspberry-pi-system-health-monitor/"
              - match: { prefisso: "/pidoctor/"}
                redirect:
                  path_redirect: "/raspberry-pi/pidoctor-raspberry-pi-system-health-monitor/"


              - match: { prefisso: "/mqtt/test" }
                route:
                  prefisso_rewrite: "/mqtt"
                  cluster: target_test
                  timeout: 0s
                  idle_timeout: 0s
                  upgrade_configs:
                    - upgrade_type: "websocket"
                      abilitato: vero
                      
              - match: { prefisso: "/" }
                route:
                  cluster: target_main
                  timeout: 0s
  cluster:

    - nome: target_test
      timeout di connessione: 5s
      limite_buffer_per_connessione_bytes: 32768 # 32 KiB
      tipo: STRICT_DNS
      lb_policy: ROUND_ROBIN
      assegnazione del carico:
        nome_cluster: target_test
        endpoint:
        - lb_endpoints:
          - endpoint:
              indirizzo:
                socket_address:
                  indirizzo: mosquitto-test.test-network
                  valore_porta: 8025

Si noti che ho omesso gran parte della configurazione di altri servizi, delle rotte e non ho fornito le informazioni sul cluster target_main (perché è irrilevante per la situazione MQTT su websocket).

Si noti il valore timeout: 0s, importante perché le connessioni MQTT continuino invece di essere interrotte dopo 15 secondi, come avviene di default.

Ho anche evidenziato altre parti che, a mio parere, sono rilevanti per consentire l'aggiornamento delle connessioni a websocket (in modo che MQTT possa essere trasportato attraverso di esse). Si noti anche l'opzione numeri di porta che vengono passati come corrispondenze di dominio aggiuntive.

Mosquitto docker-compose.yml:

versione: '3.6'

servizi:
  mosquitto:
    immagine: eclipse-mosquitto
    nome_contenitore: mosquitto-test
    nome host: mosquitto-test
    reti:

      - test_net
    riavvio: "no"
    utente: "root:root"
    volumi:
      - tipo: bind
        sorgente: ./mosquitto.conf
        destinazione: /mosquitto/config/mosquitto.conf


reti:

  test_net:
    esterno:
      nome: rete-test


mosquitto.conf:

ascoltatore 8025
protocollo websockets

allow_anonymous true
log_type all

Strumento utilizzato per verificare la connessione:

Client websocket HiveMQ

Aggiornamento 13.11.2023

MQTT è di nuovo online, con anche VerneMQ:

Ho riavviato VerneMQ diverse volte, ma a quanto pare non ho aspettato abbastanza perché si stabilizzasse. Un collega lo ha riavviato oggi e ora funziona. Sembra che ci vogliano 10-15 minuti (nella nostra configurazione) per diventare completamente reattivo e funzionare in modo appropriato.

Posso quindi confermare che la configurazione di cui sopra per envoy funziona anche con VerneMQ.

Lezione imparata

Se qualcosa non funziona, provare a replicare il problema nell'interazione con un altro strumento - se funziona lì, forse il problema non è nel primo strumento che avete cambiato, ma nel secondo strumento con cui deve funzionare.

e alcune chicche aggiuntive:

Documentazione online

Strumenti utili