envoy, docker e websockets - depuração e configuração
Os Websockets são uma tecnologia excitante, permitindo-lhe actualizar uma ligação HTTP para uma ligação binária persistente de longa duração, que pode utilizar para enviar mensagens bidireccionais.
Como um aparte, o protocolo MQTT pode ser transportado usando websockets - que é a única forma (?) para um cliente JavaScript entregue pelo site, por exemplo.
Em qualquer caso, como os websockets funcionam nas mesmas portas que o tráfego normal HTTP & HTTPS (80 / 443), é mais provável que as redes corporativas as deixem passar.
Enviado e websockets
O Envoy suporta websockets. Há algumas gotchas:
Incapaz de analisar JSON como protótipo (INVALID_ARGUMENT:(route_config.virtual_hosts[3].routes[0].route) use_websocket: Não é possível encontrar o campo.):
Enviado usado para suportar websockets com uma diretiva antiga, "use_websocket". Em uma instalação atual do envoy (por exemplo, eu atualmente uso envoy 1.10.0-dev para fins de teste) esta diretiva desapareceu e foi substituída.
A idéia é ter pedidos / possibilidades mais genéricos de atualização.
A sintaxe correcta está agora a usar atualizar_configs. Dê uma olhada no meu enviado.yaml:
estática_recursos:
Ouvintes:
- morada:
endereço_da_cova:
morada: 0.0.0.0
port_value: 80
filter_chains:
- filtros:
- nome: envoy.http_connection_manager
configurar:
upgrade_configs:
- upgrade_type: websocket
codec_type: auto
stat_prefix: ingresso_http
use_remote_address: true
xff_num_trusted_hops: 0
route_config:
virtual_hosts:
- nome: debug
domínios: ["debug.local:80"]
rotas:
- ...partida: Prefixo: “/” }
rota:
cluster: target_dwebsocket
- nome: backend
domínios: ["morpheus.local"]
rotas:
- ...partida: Prefixo: “/” }
redireccionar:
path_redirect: "/"
https_redirect: true(corte)
- morada:
endereço_da_cova:
morada: 0.0.0.0
port_value: 443
filter_chains:
- tls_context:
texto_comum_tls_context:
tls_certificates:
- cadeia_cade certificado: { nome do ficheiro: "/etc/envoy/example.crt" }
private_key: { filename: "/etc/envoy/example.key" }
alpn_protocols: ["h2,http/1.1" ]
filtros:
- nome: envoy.http_connection_manager
configurar:
upgrade_configs:
- upgrade_type: websocket
stat_prefix: ingresso_https
use_remote_address: true
xff_num_trusted_hops: 0
route_config:
virtual_hosts:
- nome: debug
domínios: ["debug.local:443"]
rotas:
- ...partida: Prefixo: “/” }
rota:
cluster: target_dwebsocket
(corte)grupos:
- nome: target_dwebsocket
connect_timeout: 0.25s
tipo: strict_dns
lb_policy: round_robin
anfitriões:
- endereço_da_cova:
endereço: dwse
port_value: 8080
Você pode usar esta diretiva upgrade_configs em dois lugares, de acordo com a documentação. no http_connection_manager (como no meu exemplo acima), e em rotas individuais.
upgrade_configs:
- upgrade_type: websocket
Itinerários não correspondentes
A outra coisa importante a notar, se você receber "404s" ( erro: resposta inesperada do servidor: 404 ): o cabeçalho :authority será, por alguma razão estranha, definido incluindo a porta - e, portanto, não será compatível se você fornecer apenas o domínio.
Olha para esta rota:
["debug.local:80"]
A porta é especificada depois do domínio neste caso. Nos meus testes - somente se a porta foi especificada, fui capaz de me conectar através do envoy ao servidor websocket que eu configurei. Este é um grande tropeço.
A seguir, vou discutir, como depurar o enviado em casos como este.
Enviado para a depuração, websockets, Docker:
Fazendo verbose de saída do enviado
Aqui está o meu docker-compose.yaml:
versão: '3.7'.
serviços:
Enviado:
construir:
contexto: ./
Arquivo de docas: Arquivo de doca
container_name: penvoyage-morpheus-envoy
portos:
– “80:80”
– “443:443”
volumes:
- tipo: volume
fonte: penvoyage_volume
alvo: /etc/envoy
redes:
- envoy_net
#user: “2000:2000”
usuário: "raiz:raiz"
reinício: a não ser que seja parado
ambiente:
nível de registo: debugvolumes:
penvoyage_volume:
externo:
nome: penvoyage_volumeredes:
envoy_net:
externo:
nome: my-bridge-network
Note que uma variável de ambiente "loglevel: debug" é definida.
O nível de registo pode ser um de:
- vestígio
- depurar
- info
- avise
A partir de agora, o contentor irá produzir muito mais resultados:
construir uma doca
Note que não nos separamos do contentor, para ver directamente a sua saída. (Quando estiver satisfeito, você pode executá-lo usando Componente -d)
A tua produção deve agora ser algo parecido com isto:
penvoyage-morpheus-envoy | [2019-05-18 14:20:32.093][1][debug][main] [fonte/servidor/servidor.cc:143] flushing stats
em uma conexão de um cliente websocket, você terá a seguinte saída:
penvoyage-morpheus-envoy | [2019-05-18 14:21:14.915][23][debug][main] [source/server/connection_handler_implpl.cc:257] [C0] nova conexão
penvoyage-morpheus-envoy | [2019-05-18 14:21:14.916][23][debug][http] [source/common/http/conn_manager_implpl.cc:210] [C0] novo fluxo
penvoyage-morpheus-envoy | [2019-05-18 14:21:14.918][23][debug][http] [source/common/http/conn_manager_implpl.cc:548] [C0][S4222457489265630919] request headers complete (end_stream=false):
penvoyage-morpheus-envoy | ':autoridade', 'debug.local:80'.
penvoyage-morpheus-envoy | ':path', '/mqtt_drinks_are_free', '/mqtt_drinks_are_free'.
penvoyage-morpheus-envoy | ':método', 'GET'.
penvoyage-morpheus-envoy | 'sec-websocket-version', '13
penvoyage-morpheus-envoy | 'sec-websocket-key', 'HQFiCTWiFMktGDPFXwzrjQ=='
penvoyage-morpheus-envoy | 'conexão', 'Upgrade'.
penvoyage-morpheus-envoy | 'upgrade', 'websocket'.
penvoyage-morpheus-envoy | 'sec-websocket-extensions', 'permessage-deflate; client_max_window_bits'.
penvoyage-morpheus-envoy |
penvoyage-morpheus-envoy | [2019-05-18 14:21:14.918][23][debug][roteador] [source/common/router/router.cc:321] [C0][S4222457489265630919] cluster 'target_dwebsocket' correspondente à 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] cabeçalhos de descodificação do router:
penvoyage-morpheus-envoy | ':autoridade', 'debug.local:80'.
penvoyage-morpheus-envoy | ':path', '/mqtt_drinks_are_free'.
penvoyage-morpheus-envoy | ':método', 'GET'.
penvoyage-morpheus-envoy | ':esquema', 'http
penvoyage-morpheus-envoy | 'sec-websocket-version', '13
penvoyage-morpheus-envoy | 'sec-websocket-key', 'HQFiCTWiFMktGDPFXwzrjQ=='
penvoyage-morpheus-envoy | 'conexão', 'Upgrade'.
penvoyage-morpheus-envoy | 'upgrade', 'websocket
penvoyage-morpheus-envoy | 'sec-websocket-extensions', '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' (verdadeiro)
penvoyage-morpheus-envoy | 'x-request-id', 'ca8a765c-e549-4c45-988c-58b6c853df7b'.
penvoyage-morpheus-envoy | 'x-envoy-expectedd-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] criando uma nova conexão
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] conectando a 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] connection in progress
penvoyage-morpheus-envoy | [2019-05-18 14:21:14.919][23][debug][pool] [source/common/http/conn_pool_base.cc:20] pedido de enfileiramento devido à falta de conexões disponíveis
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] connected
penvoyage-morpheus-envoy | [2019-05-18 14:21:14.919][23][debug][pool] [source/common/http/http1/conn_pool.cc:236] [C1] anexando à próxima solicitação
penvoyage-morpheus-envoy | [2019-05-18 14:21:14.919][23][debug][router] [source/common/router/router.cc:1122] [C0][S4222457489265630919] pool ready
penvoyage-morpheus-envoy | [2019-05-18 14:21:14.920][23][debug][router] [source/common/router/router.cc:669] [C0][S4222457489265630919] cabeçalhos upstream completos: end_stream=false
penvoyage-morpheus-envoy | [2019-05-18 14:21:14.920][23][debug][http] [source/common/http/conn_manager_implpl.cc:1234] [C0][S4222457489265630919] encoding headers via codec (end_stream=false):
penvoyage-morpheus-envoy | ':status', '101'
penvoyage-morpheus-envoy | 'upgrade', 'websocket
penvoyage-morpheus-envoy | 'conexão', 'Upgrade'.
penvoyage-morpheus-envoy | 'sec-websocket-accept', '2W9caJQU0JKW3MhWV6T8FHychjk='
penvoyage-morpheus-envoy | 'data', 'Sábado, 18 de Maio de 2019 14:21:14 GMT'.
penvoyage-morpheus-envoy | 'server', 'envoy' (servidor)
penvoyage-morpheus-envoy | 'content-length', '0
penvoyage-morpheus-envoy |
Observe o cabeçalho :autoridade como mencionei acima:
':autoridade', 'debug.local:80'.
Aqui está um cabeçalho de autoridade para um pedido normal de website:
penvoyage-morpheus-envoy | ':método', 'GET'.
penvoyage-morpheus-envoy | ':autoridade', 'morpheus.local'.
penvoyage-morpheus-envoy | ':esquema', 'https'
Observe a presença do porto no primeiro caso (pedido via websocket), e a ausência do porto no segundo caso.
Testcontainer
Note, por favor, que https://hub.docker.com/r/dataferret/websocket-echo a imagem dataferret/websocket-echo parece ser quebrado!
Parece haver alguma incompatibilidade no twisted / Autobahn para a versão particular que foi usada, e por isso decidi apenas rolar a minha própria:
Vamos construir um recipiente de teste que irá ecoar a entrada que lhe enviamos como um websocket.
Aqui está o Dockerfile:
DE píton:esticar
COPY assetss/websocketd /usr/bin/websocketd
EXECUÇÃO chmod +x /usr/bin/websocketd
COPY ativo/corrida.py /opt/corrida.py
WORKDIR /optEXPOSIÇÃO 8080
ENTRYPOINT ["websocketd", "-port=8080", "python3", "/opt/run.py"]
Note que usando ENTRYPOINT, quando o recipiente é iniciado com CMD do docker-compose.yml, o valor CMD será passado como parâmetro para o comando ENTRYPOINT.
websocketd é um daemon websocket, você pode obter o executável autônomo a partir daqui:
Eu baixei https://github.com/joewalnes/websocketd/releases/download/v0.3.0/websocketd-0.3.0-linux_amd64.zip e descompactou-a na pasta de activos que criei para o meu contentor portuário.
A ideia geral desta aplicação sofisticada é fornecer um invólucro em torno da sua aplicação para poder servir os seus websockets.
Tudo o que sua aplicação tem que fazer é ouvir em stdin, e escrever as respostas / qualquer comunicação em stdout.
correr.py - este é um script simples que ecoará tudo o que lhe for escrito:
#!/usr/bin/pithon
sistema de importaçãoenquanto é verdade:
read_co = sys.stdin.readline()
print(read_co, end="")
sys.stdout.flush()
Observe o uso da readline(), que retornará a string imediatamente após uma nova linha. Caso contrário você tem que lidar com buffers / esperar que a entrada se acumule.
Observe também o sys.stdout.flush()
Finalmente, aqui está o docker-compose.yml:
versão: '3.6'.
serviços:
Dormir:
construir:
contexto: ./
Arquivo de docas: Arquivo de doca
container_name: dwse
hostname: dwse
portos:
– “8078:8080”
redes:
- envoy_net
reinício: sempre
...o comando: “80”
#user: "raiz:raiz"redes:
envoy_net:
externo:
nome: my-bridge-network
Note que eu o coloquei na mesma rede "my-bridge-network" que o enviado acima.
o comando aqui é um "80" aleatório que não é interpretado pela aplicação, mas você poderia criar linhas de comando mais elaboradas, etc. usando isto.
depuração
Finalmente, queremos que um cliente se conecte e seja capaz de ver as mensagens que enviamos para o nosso serviço de teste websocket, o qual é proxied através do envoy.
Eu sugiro wscat para isto.
O wscat é um pacote npm (node.js). Portanto, você tem que instalar o npm primeiro:
apt install npm
Então você pode instalar o wscat:
npm install -g wscat
Teste o wscat com um serviço público de eco de websocket:
wscat -c ws://echo.websocket.org
obter uma lista de opções de linha de comando para o wscat:
wscat -help
Para verificar uma conexão segura via websocket através de um enviado com certificados autografados faça:
wscat -c ws://debug.local/mqtt_drinks_are_free -no-check
Dicas
Resposta inesperada do servidor
erro: Resposta inesperada do servidor: 404
Dê uma olhada na sua rota: você coloca o porto no domínio para combinar, como discutido acima? Aparentemente, parece ser necessário para clientes websocket.
Quando em dúvida, inclua uma correspondência de todos os domínios no final dos seus hosts virtuais, para facilitar a depuração. Eu sugiro definir um caminho fixo de correspondência nisto, ao invés de prefixo - alterando o caminho que você usa no wscat, desta forma, você pode verificar qual regra foi correspondida:
- nome: matcheverything
domínios: [“*”]
rotas:
- ...partida: {\an8}{\an8}{\an8}{\an8} "/mqtt" }
rota: { cluster: target_dwebsocket }
Incapaz de analisar JSON / não consegue encontrar o campo:
Incapaz de analisar JSON como protótipo (INVALID_ARGUMENT:(route_config.virtual_hosts[3].routes[0].route) use_websocket: Não é possível encontrar o campo.):
Como discutido acima, este é um estilo legado de configuração que foi substituído pela nova configuração:
upgrade_configs:
- upgrade_type: websocket
sem correspondência de cluster para URL
[source/common/router/router.cc:278] [C0][S5067510418337690596] nenhuma correspondência de cluster para a URL '/'
Veja acima - a URL é igualada incluindo a porta para Websockets aparentemente (nos meus testes), portanto inclua uma regra para combinar todos os domínios, e debug a partir daí / use a sintaxe que eu forneci acima:
route_config:
virtual_hosts:
- nome: debug
domínios: ["debug.local:80"]
rotas:
- ...partida: Prefixo: “/” }
rota:
cluster: target_dwebsocket
certificado autoassinado
erro: certificado autoassinado
Execute o wscat com o parâmetro -no-check da linha de comando (este é um traço duplo que o WordPress vai estragar):
A ligação Websocket falhou:
pcp-code.js:15 A ligação WebSocket a 'wss://key:[email protected]/mqtt' falhou: Erro durante o aperto de mão do WebSocket: Código de resposta inesperado: 403
Ver informação acima: muito provavelmente, esta mensagem de erro javascript deve-se a uma má configuração da correspondência do domínio (ver acima).
Erro de autobahn:
"Para usar o txaio, você deve primeiro selecionar uma estrutura " exceções.RuntimeError: Para usar o txaio, você deve primeiro selecionar um framework com .use_twisted() ou .use_txaio()
Este é o erro que recebi ao executar a imagem dataferret/websocket-echo. Eu sugiro usar meu código que eu forneci acima como uma alternativa.
Referências:
- https://hub.docker.com/r/dataferret/websocket-echo não funciona para mim! Eu forneci código alternativo neste post de blog para um simples contentor de eco Docker websocket
- https://github.com/vi/websocat uma alternativa ao wscat
- https://github.com/websockets/wscat
- https://github.com/erebe/wstunnel permite que você estabeleça túneis de conexão através de websockets, incluindo guarda-fios - coisas interessantes!
- ws://echo.websocket.org - serviço echo em um websocket
- http://websocketd.com/ se encarrega de lidar com as conexões do WebSocket e lançar suas aplicações para lidar com os WebSockets (isolados um do outro)
- https://github.com/envoyproxy/envoy/issues/3301 aqui é discutido o novo estilo de atualização de configuração
- https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/websocket.html - documentação oficial do enviado
- https://www.envoyproxy.io/docs/envoy/latest/api-v2/api/v2/route/route.proto#envoy-api-field-route-routeaction-upgrade-configs
- https://www.envoyproxy.io/docs/envoy/latest/api-v2/api/v2/route/route.proto#envoy-api-msg-route-routeaction-upgradeconfig
- https://github.com/envoyproxy/envoy/issues/4740 - Eu vi a forma correcta de configurar aqui, e estou a elaborar sobre isso.
- https://www.envoyproxy.io/docs/envoy/latest/operations/cli informação da linha de comando do enviado