Envoy. 1. Introduzione

Salve! Questo è un breve articolo che risponde a domande come: "che cos'è envoy?", "a cosa serve?" e "da dove cominciare?".

Che cos'è

Envoy è un bilanciatore L4-L7 scritto in C++, progettato per alta performance e disponibilità. Da un lato, è in qualche modo simile a nginx e haproxy, comparabile in termini di performance. Dall'altro, è più orientato verso l'architettura a microservizi e presenta funzionalità non inferiori a quelle di bilanciatori in java e go, come zuul o traefik.

Tabella di confronto tra haproxy/nginx/envoy, che non pretende di essere la verità assoluta, ma fornisce un quadro generale.

nginx
haproxy
envoy
traefik

stelle su github
11.2k/mirror
1.1k/mirror
12.4k
27.6k

scritto in
C
C
C++
go

API
no
solo socket/push
dataplane/pull
pull

healthcheck attivo
no


Open tracing
plugin esterno
no

JWT
plugin esterno
no

no

Espansione
Lua/C
Lua/C
Lua/C++
no

Perché

Questo è un progetto giovane, mancano molte cose e alcune sono in fase alpha. Tuttavia, envoy, grazie anche alla sua giovinezza, si sta sviluppando rapidamente e ha già molte funzionalità interessanti: configurazione dinamica, numerosi filtri pronti all'uso, e un'interfaccia semplice per scrivere i propri filtri.
Da questo derivano le aree di applicazione, ma per iniziare con 2 antipattern:

  • Fornire statico.

Infatti, attualmente in envoy non c'è supporto per la cache. I ragazzi di Google stanno cercando di risolverlo. L'idea è di implementarlo una volta per tutte envoy tutte le complessità (zoo di header) di conformità agli RFC, e per implementazioni specifiche creare un'interfaccia. Ma per ora non siamo nemmeno in alpha, l'architettura è in discussione, PR è aperta (mentre scrivevo l'articolo, la PR è stata fusa, ma questo punto è ancora attuale).

Nel frattempo, usa nginx per la parte statica.

  • Configurazione statica.

Puoi utilizzarla, ma envoy non è stata creata per questo. Le possibilità nella configurazione statica non saranno sfruttate. Ci sono molti aspetti:

Modificando la configurazione in yaml, commetterai errori, maledirai gli sviluppatori per la verbosità e penserai che le configurazioni di nginx/haproxy, sebbene meno strutturate, siano più concise. Questo è il punto. La configurazione di Nginx e Haproxy è stata creata per essere modificata a mano, mentre envoy per la generazione da codice. L'intera configurazione è descritta in protobuf, generando il codice dai file proto è molto più difficile sbagliarsi.

Gli scenari canary, b/g deployment e molto altro sono realizzati normalemente solo nella configurazione dinamica. Non dico che non si possa fare in statico, lo facciamo tutti. Ma per questo bisogna fare delle forzature, in qualsiasi bilanciatore, envoy incluso.

Attività in cui Envoy è indispensabile:

  • Bilanciamento del traffico in sistemi complessi e dinamici. Qui rientra il service mesh, ma non è necessariamente solo questo.
  • Necessità di funzionalità di tracciamento distribuito, autorizzazione complessa o altro che sia già presente in envoy modo nativo o facilmente implementabile, mentre in nginx/haproxy è necessario appoggiarsi a lua e a plugin discutibili.

Entrambi devono garantire alte prestazioni, se necessario.

Come funziona

Envoy è distribuito in pacchetti binari solo come immagine docker. Nell'immagine è già presente un esempio di configurazione statica. Tuttavia, ci interessa solo per comprendere la struttura.

envoy.yaml configurazione statica

static_resources:
  listeners:
  - name: listener_0
    address:
      socket_address:
        protocol: TCP
        address: 0.0.0.0
        port_value: 10000
    filter_chains:
    - filters:
      - name: envoy.http_connection_manager
        typed_config:
          "@type": type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager
          stat_prefix: ingress_http
          route_config:
            name: local_route
            virtual_hosts:
            - name: local_service
              domains: ["*"]
              routes:
              - match:
                  prefix: "/"
                route:
                  host_rewrite: www.google.com
                  cluster: service_google
          http_filters:
          - name: envoy.router
  clusters:
  - name: service_google
    connect_timeout: 0.25s
    type: LOGICAL_DNS
    # Comment out the following line to test on v6 networks
    dns_lookup_family: V4_ONLY
    lb_policy: ROUND_ROBIN
    load_assignment:
      cluster_name: service_google
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: www.google.com
                port_value: 443
    transport_socket:
      name: envoy.transport_sockets.tls
      typed_config:
        "@type": type.googleapis.com/envoy.api.v2.auth.UpstreamTlsContext
        sni: www.google.com

Configurazione dinamica

Quale problema stiamo cercando di risolvere? Non possiamo semplicemente ricaricare la configurazione del bilanciatore sotto carico, sorgerebbero "piccole" problematiche:

  • Validazione della configurazione.

La configurazione può essere grande, può essere molto grande; se la sovraccarichiamo, aumentano le probabilità di errori da qualche parte.

  • Connessioni a lungo termine.

Durante l'inizializzazione di un nuovo listener, è importante tenere conto delle connessioni attive nel sistema vecchio. Se ci sono frequenti modifiche e ci sono connessioni persistenti, sarà necessario trovare un compromesso. Ciao, kubernetes ingress su nginx.

  • Controlli di stato attivi.

Se abbiamo controlli di stato attivi, dovremmo ricontrollarli tutti sulla nuova configurazione prima di inviare il traffico. Se ci sono molti upstream, questo richiede tempo. Ciao, haproxy.

Come viene risolto in envoy, caricando il file di configurazione dinamicamente, secondo il modello pool, possiamo suddividerlo in parti separate e non ri-inizializzare quella parte che non è stata modificata. Ad esempio, un listener che è costoso da ri-inizializzare, ma che cambia raramente.

Configurazione envoy (dal file sopra) ha le seguenti entità:

  • listener — listener attaccato a un certo ip/porta
  • virtual host — virtual host per nome di dominio
  • rotta — regola di bilanciamento
  • cluster — gruppo di upstream con parametri di bilanciamento
  • endpoint — indirizzo dell'istanza upstream

Ognuna di queste entità, insieme ad alcune altre, può essere popolata dinamicamente; a tal fine, nella configurazione deve essere specificato l'indirizzo del servizio da cui sarà ottenuta la configurazione. Il servizio può essere REST o gRPC, si consiglia di utilizzare gRPC.

I servizi sono denominati rispettivamente: LDS, VHDS, RDS, CDS e EDS. È possibile combinare configurazioni statiche e dinamiche, con la limitazione che una risorsa dinamica non può essere specificata in una configurazione statica.

Per la maggior parte delle attività, è sufficiente implementare gli ultimi tre servizi, noti come ADS (Aggregated Discovery Service), per java e go esiste un'implementazione gRPC dataplane pronta in cui è sufficiente riempire solo gli oggetti dalla propria fonte.

La configurazione assume il seguente aspetto:

envoy.yaml configurazione dinamica

dynamic_resources:
  ads_config:
    api_type: GRPC
    grpc_services:
      envoy_grpc:
        cluster_name: xds_clr
  cds_config:
    ads: {}
static_resources:
  listeners:
  - name: listener_0
    address:
      socket_address:
        protocol: TCP
        address: 0.0.0.0
        port_value: 10000
    filter_chains:
    - filters:
      - name: envoy.http_connection_manager
        typed_config:
          "@type": type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager
          stat_prefix: ingress_http
          rds:
            route_config_name: local_route
            config_source:
              ads: {}
          http_filters:
          - name: envoy.router
  clusters:
  - name: xds_clr
    connect_timeout: 0.25s
    type: LOGICAL_DNS
    dns_lookup_family: V4_ONLY
    lb_policy: ROUND_ROBIN
    load_assignment:
      cluster_name: xds_clr
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: xds
                port_value: 6565

All'avvio envoy Con questo config, si collegherà al control-plane e cercherà di richiedere la configurazione RDS, CDS e EDS. Il processo di interazione è descritto qui.

In breve, envoy invia una richiesta, specificando il tipo di risorsa richiesta, la versione e i parametri del nodo. In risposta riceve la risorsa e la versione. Se sul control-plane la versione non è cambiata, non risponde.
Ci sono 4 opzioni di interazione:

  • Un flusso gRPC per tutti i tipi di risorse, viene inviato lo stato completo della risorsa.
  • Flussi separati, stato completo.
  • Un flusso, stato incrementale.
  • Stream separati, stato incrementale.

Incremental xDS consente di ridurre il traffico tra il control-plane e envoy, questo è rilevante per configurazioni di grandi dimensioni. Tuttavia, complica l'interazione, in quanto nella richiesta viene passato un elenco di risorse per la disiscrizione e l'iscrizione.

Nel nostro esempio viene utilizzato ADS — uno stream per RDS, CDS, EDS e modalità non incrementale. Per attivare la modalità incrementale, è necessario specificare api_type: DELTA_GRPC

Poiché nella richiesta ci sono parametri del nodo, possiamo inviare risorse diverse al control-plane per diverse istanze envoy, questo è utile per costruire un service mesh.

Warmup

Su envoy all'avvio o quando si riceve una nuova configurazione dal control-plane, viene avviato il processo di warmup delle risorse. È suddiviso in warmup listener e warmup cluster. Il primo viene attivato quando ci sono modifiche in RDS/LDS, il secondo in CDS/EDS. Questo significa che se cambiano solo gli upstream, il listener non viene ricreato.

Durante il processo di riscaldamento, ci si aspetta che risorse dipendenti dal control-plane siano disponibili entro un timeout. Se il timeout scade, l'inizializzazione non sarà riuscita e il nuovo listener non inizierà a monitorare la porta.
Ordine di inizializzazione: EDS, CDS, controllo attivo della salute, RDS, LDS. Con i controlli della salute attivi abilitati, il traffico andrà all'upstream solo dopo un controllo della salute riuscito.

Se il listener è stato ricreato, il vecchio passa allo stato DRAIN e verrà rimosso dopo che tutte le connessioni saranno chiuse o dopo che è scaduto il timeout. --drain-time-s, di default 10 minuti.

Continua.

Fonte: habr.com

Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server 🔥 Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server | ProHoster