Как использовать Traefik и Envoy в одном проекте для обработки grpc-web?

У меня есть запущенная служба на traefik с LetsEncrypt на grpc.mydomain.com. Однако traefik не поддерживает маршрутизацию запросов grpc-web из-за некоторых проблем с CORS (https://github.com/containous/traefik/issues/4210). Envoy кажется альтернативой traefik, который работает с grpc-web, но я не хочу переконфигурировать все.

Если я помещаю envoy в envoy.mydomain.com, тогда он сначала попадает в traefik, и traefik не может направлять запросы grpc-web к envoy. Так что это не работает.

Если я помещаю посланника вне traefik (mydomain.com:9091), тогда у envoy нет поддержки TLS, как у traefik.

Мне нужно переключить все на посланника? Есть ли альтернатива, которую я не рассматривал? Любое руководство приветствуется :)

Текущая настройка Traefik:

  traefik:
    image: traefik:v2.0.0
    container_name: traefik
    command:
      - --entrypoints.web.address=:80
      - --entrypoints.websecure.address=:443
      - --entrypoints.grpc.address=:8090
      - --providers.docker
      - --api
      - --serversTransport.rootCAs=/certs/grpc.cert
      # Lets Encrypt Resolvers
      - --certificatesresolvers.leresolver.acme.email=${EMAIL}
      - --certificatesresolvers.leresolver.acme.storage=/etc/acme/cert.json
      - --certificatesresolvers.leresolver.acme.tlschallenge=${TLS_CHALLENGE}
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - /etc/acme/:/etc/acme/
      - ./secrets/grpc.cert:/certs/grpc.cert
    # Dynamic Configuration
    labels:
      # Dashboard
      - "traefik.http.routers.traefik.rule=Host(`traefik.${DOMAIN}`)"
      - "traefik.http.routers.traefik.service=api@internal"
      - "traefik.http.routers.traefik.tls.certresolver=leresolver"
      - "traefik.http.routers.traefik.entrypoints=websecure"
      - "traefik.http.routers.traefik.middlewares=authtraefik"

      # https://docs.traefik.io/middlewares/basicauth/
      # password generated from `echo $(htpasswd -nb admin $PASSWORD) | sed -e s/\\$/\\$\\$/g`
      - "traefik.http.middlewares.authtraefik.basicauth.users=admin:$$apr1$$6VzI3S0N$$29FC82dYEbjFN9tPSfWLX1"

      # global redirect to https
      - "traefik.http.routers.http-catchall.rule=hostregexp(`{host:.+}`)"
      - "traefik.http.routers.http-catchall.entrypoints=web"
      - "traefik.http.routers.http-catchall.middlewares=redirect-to-https"

      # middleware redirect
      - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
    ports:
      - 80:80
      - 443:443
      - 8090:8090
    networks:
      - internal
      - proxied

  grpc_server:
    image: ${GRPC_IMAGE}
    container_name: grpc_server
    volumes:
      - /tmp/keyset.json:/tmp/keyset.json
      - ./secrets/:/secrets/
    working_dir: /app/__main__/
    labels:
      - "traefik.http.routers.combined_server.rule=Host(`grpc.${DOMAIN}`)"
      - "traefik.http.routers.combined_server.entrypoints=grpc"
      - "traefik.http.routers.combined_server.tls=true"
      - "traefik.http.routers.combined_server.tls.certresolver=leresolver"
      # http
      - "traefik.http.services.grpc-svc.loadbalancer.server.scheme=h2c"
      - "traefik.http.services.grpc-svc.loadbalancer.server.port=8090"
    expose:
      - 8090
    networks:
      - internal
      - proxied

Я также попытался установить их, чтобы исправить ошибку CORS, но ничего не добился.

      - "traefik.http.middlewares.testheader.headers.accesscontrolallowmethods=GET,PUT,DELETE,POST,OPTIONS"
      - "traefik.http.middlewares.testheader.headers.accesscontrolallowheaders=keep-alive,user-agent,cache-control,content-type,content-transfer-encoding,custom-header-1,x-accept-content-transfer-encoding,x-accept-response-streaming,x-user-agent,x-grpc-web,grpc-timeout"
      - "traefik.http.middlewares.testheader.headers.accesscontrolmaxage=100"
      - "traefik.http.middlewares.testheader.headers.addvaryheader=true"
      - "traefik.http.middlewares.testheader.headers.alloworigin=*"


person epigram-engineer    schedule 14.01.2020    source источник


Ответы (2)


Один из способов решения этой проблемы - использовать 2 разных URL-адреса, которые сначала обрабатываются traefik. Один URL используется для "прямого grpc" (grpc.mydomain.com), другой - для grpc-web (назовем его grpc-web.mydomain.com). Traefik выполняет завершение TLS для обоих.

Трафик grpc.mydomain.com напрямую передается в контейнер, на котором запущен grpc_server. Трафик grpc-web.mydomain.com передается на envoy, который действует как grpc-web-proxy, а затем передает трафик на grpc_server.

Итак, поскольку вы используете docker-compose, вам нужно будет добавить службу посланника в свой docker-compose.yml:

---
version: '3'
services:
  traefik:
    # traefik configuration from your question
    # ...
   grpc-server:
    # grpc_server configuration from your question
    # ...
  envoy:
    image: envoyproxy/envoy:v1.14.1
    restart: unless-stopped
    volumes:
      - ./envoy.yaml:/etc/envoy/envoy.yaml
    labels:
      - traefik.enable=true
      - traefik.http.routers.envoy.rule=Host(`grpc-web.mydomain.com`)
      - traefik.http.services.envoy.loadbalancer.server.port=8080
      - traefik.http.routers.envoy.tls=true
      - traefik.http.routers.envoy.tls.certresolver=leresolver

Конфигурация envoy.yaml (смонтированная в разделе томов выше) выглядит так:

admin:
  access_log_path: /tmp/admin_access.log
  address:
    socket_address: { address: 0.0.0.0, port_value: 9901 }

static_resources:
  listeners:
    - name: listener_0
      address:
        socket_address: { address: 0.0.0.0, port_value: 8080 }
      filter_chains:
        - filters:
            - name: envoy.http_connection_manager
              config:
                codec_type: auto
                stat_prefix: ingress_http
                route_config:
                  name: local_route
                  virtual_hosts:
                    - name: local_service
                      domains: ["*"]
                      routes:
                        - match: { prefix: "/" }
                          route:
                            cluster: grpc_service
                            max_grpc_timeout: 0s
                      cors:
                        allow_origin_string_match:
                          - prefix: "*"
                        allow_methods: GET, PUT, DELETE, POST, OPTIONS
                        allow_headers: keep-alive,user-agent,cache-control,content-type,content-transfer-encoding,custom-header-1,x-accept-content-transfer-encoding,x-accept-response-streaming,x-user-agent,x-grpc-web,grpc-timeout
                        max_age: "1728000"
                        expose_headers: custom-header-1,grpc-status,grpc-message
                http_filters:
                  - name: envoy.grpc_web
                  - name: envoy.cors
                  - name: envoy.router
  clusters:
    - name: grpc_service
      connect_timeout: 0.25s
      type: logical_dns
      http2_protocol_options: {}
      lb_policy: round_robin
      hosts: [{ socket_address: { address: grpc-server, port_value: 8090 }}]

Это довольно простая конфигурация grpc-web для envoy. Важно отметить, что мы установили address: grpc-server, port_value: 8090 в конфигурации кластера "grpc_service" на имя службы из docker-compose.yml и на порт, который прослушивает ваш grpc-сервер. Обратите внимание, что я переименовал вашу службу с grpc_server в grpc-server, поскольку подчеркивание не является допустимым символом в именах хостов.

На стороне клиента используйте:

  • "grpc-web.mydomain.com" в вашем коде javascript (grpc-web).
  • "grpc.mydomain.com" при написании клиента на другом языке (например, на golang).

Я создал рабочий пример, который можно найти по ссылке: https://github.com/rbicker/greeter

person Raphael Bicker    schedule 18.04.2020

Если вы хотите избавиться от устаревших предупреждений в envoy, вы можете обновить envoy.yaml из этого ответа этими тремя изменениями:

  1. заменять:
            - name: envoy.http_connection_manager
              config:

с участием:

            - name: envoy.filters.network.http_connection_manager
              typed_config:
                "@type": type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager
  1. заменять
                  - name: envoy.grpc_web
                  - name: envoy.cors
                  - name: envoy.router

с участием

                  - name: envoy.filters.http.grpc_web
                  - name: envoy.filters.http.cors
                  - name: envoy.filters.http.router
  1. заменять
hosts: [{ socket_address: { address: grpc-server, port_value: 8090 }}]

с участием

      load_assignment:
        cluster_name: cluster_0
        endpoints:
          - lb_endpoints:
              - endpoint:
                  address:
                    socket_address:
                      address: grpc-server
                      port_value: 8090
person dmaixner    schedule 20.06.2020