この記事について
Envoy の初心者である自分が Getting Started などをやってみてハマったことやその調査方法・解決策をまとめたものです。
- 環境
- Envoy 1.16.2
- Ubuntu 18.04
ハマったこと
Google にしかリクエストが通らない?
概要
Envoy のドキュメントの Getting Started では、以下のような Google にリクエストをルーティングする設定ファイルを作る。
static_resources:
listeners:
- name: listener_0
address:
socket_address: { address: 0.0.0.0, port_value: 10000 }
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: ingress_http
codec_type: AUTO
route_config:
name: local_route
virtual_hosts:
- name: local_service
domains: ["*"]
routes:
- match: { prefix: "/" }
route: { host_rewrite_literal: www.google.com, cluster: service_google }
http_filters:
- name: envoy.filters.http.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.extensions.transport_sockets.tls.v3.UpstreamTlsContext
sni: www.google.com
admin:
access_log_path: /tmp/admin_access.log
address:
socket_address: { address: 0.0.0.0, port_value: 9901 }
これ自体は問題ないが、試しに上記 YAML で www.google.com
になってる個所を全て www.yahoo.com
や www.httpbin.org
など別のサイトにしたらエラーが返るようになった。ただ、まれに応答が返る場合もある。
$ curl http://localhost:10000
upstream connect error or disconnect/reset before headers. reset reason: local reset
原因
コネクションタイムアウトの時間が 0.25s(250ms) と短いためタイムアウトになっていた。10s とかにしたら問題が無くなった。
clusters:
- name: service_google
connect_timeout: 0.25s #ここを 10s とかにすればOK
調査方法
- Envoy から upstream(この場合の www.yahoo.com や www.httpbin.org など)に送るパケットをキャプチャ。
- Envoy が TLS の ClientHello を送ったすぐ後に自ら TCP の FIN を送っており、タイムアウトではないかと判断。
Upstream のサーバーから切られる
概要
これは単純ミスなので書くほどでないが、何が起きるかという記録のために記載。
先述の問題で試行錯誤しているうちに envoy.yaml の clusters
配下から transport_socket:
以下を削除してしまった。なお、問題とは直接関係ないがその過程で http2_protocol_options
を付けている。
clusters:
- name: target_service
connect_timeout: 10s
type: LOGICAL_DNS
http2_protocol_options: {}
dns_lookup_family: V4_ONLY
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: target_service
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: www.google.com
port_value: 443
すると、以下のようなエラーになる。
$ curl http://localhost:10000
upstream connect error or disconnect/reset before headers. reset reason: connection termination
原因
上記の通り transport_socket:
を削除してしまったため、HTTPSサーバー(Port 443)に対して TLS を介さずクリアテキスト(HTTP, h2c)でリクエストを送っていた。
ファイルベースの xDS で YAML が使えない
概要
TRY ENVOY の File Based Dynamic Routing Configuration を流すと EDS(Endpoint Discovery Service)の設定を eds.conf
というファイルに外だしするが、中身は JSON で書かされる。
YAML で書きたいとググっていたところ、このあたりの記事で YAML で書いてる人を発見。
さっそく eds.conf
を YAML 形式で書いてみたが全くリクエストが通らない。
$ curl http://localhost:10000
no healthy upstream
原因
eds.conf
の拡張子を .conf
から .yaml
に変える必要がある。
調査方法
1. 管理サーバー
Envoy の 管理サーバー(っていう呼び方なのか知らないですが)にアクセスすると、クラスターの Endpoint が登録されてない。やはり Endpoint を定義してる eds.conf
を読み込めてないらしい。
2. デバッグログ
Envoy を --log-level
オプションでデバッグログを有効にして起動。この時は docker-compose を使ったが command を以下のようにした。
version: "3.7"
services:
envoy:
// 中略
command:
- "--config-path /etc/envoy/envoy.yaml"
- "--log-level trace"
docker logs
で見たらこんなログが出ていた(見やすさのために一部の情報は削除)。やはり JSON としてパースされてそう。
[2020-12-29 09:40:00.438] added watch for directory: '/etc/envoy' file: 'eds.conf' fd: 1
[2020-12-29 09:40:00.438] adding TLS initial cluster target_cluster
[2020-12-29 09:40:00.438] Filesystem config refresh for /etc/envoy/eds.conf
[2020-12-29 09:40:00.439] Filesystem config update failure: Unable to parse JSON as proto (INVALID_ARGUMENT:Unexpected token.
version_info: "0"
(以下略)
ログを元にソースを見たら拡張子を見て読み込み処理を分けていた。YAML の場合は .yaml
にする必要がある(なお、該当するものがなければJSONとしてパースしてる)。
void MessageUtil::loadFromFile(const std::string& path, Protobuf::Message& message,
ProtobufMessage::ValidationVisitor& validation_visitor,
Api::Api& api, bool do_boosting) {
// ".pb" の場合
if (absl::EndsWith(path, FileExtensions::get().ProtoBinary)) {
//...
}
// ".pb_text" の場合
if (absl::EndsWith(path, FileExtensions::get().ProtoText)) {
//...
}
// YAML の場合
if (absl::EndsWith(path, FileExtensions::get().Yaml)) {
loadFromYaml(contents, message, validation_visitor, do_boosting);
} else {
// その他は JSON とみなす
loadFromJson(contents, message, validation_visitor, do_boosting);
}
class FileExtensionValues {
public:
const std::string ProtoBinary = ".pb";
const std::string ProtoBinaryLengthDelimited = ".pb_length_delimited";
const std::string ProtoText = ".pb_text";
const std::string Json = ".json";
const std::string Yaml = ".yaml"; // YAML の場合の拡張子。
};
using FileExtensions = ConstSingleton<FileExtensionValues>;
調査方法まとめ
Envoy のデバッグログ
envoy のコマンドラインオプションで -l
または --log-level
によりログレベルを指定できる。詳細なログを見る場合は trace
か debug
を指定すればよさそう。
-l <string>, --log-level <string>
Log levels: [trace][debug][info][warning
|warn][error][critical][off]
Docker で起動する場合はこんな感じ。
docker run --name=envoy -d \
-p 10000:10000 \
-p 9901:9901 \
-v $(pwd)/envoy.yaml:/etc/envoy/envoy.yaml \
envoyproxy/envoy:v1.16.2 \
--config-path /etc/envoy/envoy.yaml \
--log-level debug
他にもログ関連のオプションがあるため、Command line options を見てください。
管理サーバー
envoy.yaml で admin:
で定義するやつです。
admin:
access_log_path: /tmp/admin_access.log
address:
socket_address: { address: 0.0.0.0, port_value: 9901 }
Envoy の構成やステータス情報を見れて、自分は今回主にclusters
を見たけど他にも調査に役立つはず。
ところで、envoy.yaml の access_log_path: /tmp/admin_access.log
で指定してるログは、自分がハマったように Upstream からエラーが返るといった場合は記録が残らなかった(あくまで試した範囲では)。
その他
その他は定番ですがパケットキャプチャ、あとは、中身を見ないと分からない場合はEnvoyのソースを追う、などでしょうか。