EnvoyとJaegerを使用した分散トレーシングを試してみた
Envoyとは
ライドシェアサービスのLyftが開発し現在はCNCFでホストされている大規模サービス指向アーキテクチャ用L4/L7プロキシです。 また、Kubernetesを使用した場合にサービスメッシュをマネージメントために使用されるIstioのコアコンポーネントの一つです。
ただ、Envoy自体は単なるプロキシサーバなのでKubernetesを使わなくても使用する事は可能です。 今回はEnvoyを使用して分散トレーシングデータの収集を行いJaegerを使用して可視化して見たいと思います。
サンドボックス[Jaeger Tracing]
Envoyの公式サイトにサンドボックスが用意されているのでその中の'Jaeger Tracing'を試してみました。
Jaeger Tracingを立ち上げる
提供されているdocker-composeを使用し立ち上げると下記の様に4つのコンテナが立ち上がります。
$ docker-compose up -d Creating network "jaeger-tracing_envoymesh" with the default driver Creating jaeger-tracing_service1_1 ... done Creating jaeger-tracing_front-envoy_1 ... done Creating jaeger-tracing_service2_1 ... done Creating jaeger-tracing_jaeger_1 ... done $ docker-compose ps Name Command State Ports -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- jaeger-tracing_front-envoy_1 /docker-entrypoint.sh /bin ... Up 10000/tcp, 0.0.0.0:8000->80/tcp, 0.0.0.0:8001->8001/tcp jaeger-tracing_jaeger_1 /go/bin/all-in-one-linux - ... Up 14250/tcp, 14268/tcp, 0.0.0.0:16686->16686/tcp, 5775/udp, 5778/tcp, 6831/udp, 6832/udp, 0.0.0.0:9411->9411/tcp jaeger-tracing_service1_1 /bin/sh -c /usr/local/bin/ ... Up 10000/tcp, 80/tcp jaeger-tracing_service2_1 /bin/sh -c /usr/local/bin/ ... Up 10000/tcp, 80/tcp
されにそれぞれどのようプロセルが立ち上がっているか見てみます。
- Front Envoyコンテナ:フロントに立ってルーティングを行うEnvoyのコンテナです
$ docker exec -it jaeger-tracing_front-envoy_1 bash root@63dc868edcda:/# ps auxwwf USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND root 47 1.5 0.1 18232 3316 pts/0 Ss 16:58 0:00 bash root 57 0.0 0.1 34420 2904 pts/0 R+ 16:58 0:00 \_ ps auxwwf root 1 0.0 0.0 4500 696 ? Ss 16:22 0:00 /bin/sh -c /usr/local/bin/envoy -c /etc/front-envoy.yaml --service-cluster front-proxy root 7 0.3 1.0 116736 22152 ? Sl 16:22 0:07 /usr/local/bin/envoy -c /etc/front-envoy.yaml --service-cluster front-proxy
- Serviceコンテナ:提供するRest APIサービスとEnvoyが同梱されたコンテナです
$ docker exec -it jaeger-tracing_service1_1 bash bash-4.4# ps auxwwf PID USER TIME COMMAND 1 root 0:00 {start_service.s} /bin/sh /usr/local/bin/start_service.sh 7 root 0:00 python3 /code/service.py 8 root 0:07 envoy -c /etc/service-envoy.yaml --service-cluster service1 19 root 0:29 /usr/bin/python3 /code/service.py 23 root 0:00 bash 30 root 0:00 ps auxwwf
- Jaegerコンテナ:Jaegerに必要なコンポートが一式入ったAll in Oneのコンテナです。今回はUI(port:16686)とcollector(port:9411)を使用しています
Front EnvoyコンテナとServiceコンテナは下記の図の様な構成になっており、各ServiceのEnvoyからJaegerコンテナのcollectorへトレーシングデータの送信がされます。
- front-envoyの設定ファイル(front-envoy-jaeger.yaml)の概要
0.0.0.0:80
を listenして全てのリクエストをclusterで定義されているservice1へルーティングする
static_resources: listeners: - address: # 0.0.0.0:80でlistenする socket_address: address: 0.0.0.0 port_value: 80 filter_chains: - filters: - name: envoy.http_connection_manager config: tracing: operation_name: egress codec_type: auto stat_prefix: ingress_http route_config: name: local_route virtual_hosts: # 全てのリクエストをservice1と宣言しているclusterへルーティングする - name: backend domains: - "*" routes: - match: prefix: "/" route: cluster: service1 decorator: operation: checkAvailability http_filters: - name: envoy.router config: {} clusters: # service1のcluster、service1のポート80へ - name: service1 connect_timeout: 0.250s type: strict_dns lb_policy: round_robin http2_protocol_options: {} hosts: - socket_address: address: service1 port_value: 80 - name: jaeger connect_timeout: 1s type: strict_dns lb_policy: round_robin hosts: - socket_address: address: jaeger port_value: 9411 tracing: http: name: envoy.zipkin config: collector_cluster: jaeger collector_endpoint: "/api/v1/spans" shared_span_context: false admin: access_log_path: "/dev/null" address: socket_address: address: 0.0.0.0 port_value: 8001
- service1-envoyの設定ファイル(service1-envoy-jaeger.yaml)の概要
static_resources: listeners: - address: socket_address: address: 0.0.0.0 port_value: 80 filter_chains: - filters: - name: envoy.http_connection_manager config: tracing: operation_name: ingress codec_type: auto stat_prefix: ingress_http route_config: name: service1_route virtual_hosts: - name: service1 domains: - "*" routes: - match: prefix: "/" route: cluster: local_service decorator: operation: checkAvailability http_filters: - name: envoy.router config: {} - address: socket_address: address: 0.0.0.0 port_value: 9000 filter_chains: - filters: - name: envoy.http_connection_manager config: tracing: operation_name: egress codec_type: auto stat_prefix: egress_http route_config: name: service2_route virtual_hosts: - name: service2 domains: - "*" routes: - match: prefix: "/trace/2" route: cluster: service2 decorator: operation: checkStock http_filters: - name: envoy.router config: {} clusters: - name: local_service connect_timeout: 0.250s type: strict_dns lb_policy: round_robin hosts: - socket_address: address: 127.0.0.1 port_value: 8080 - name: service2 connect_timeout: 0.250s type: strict_dns lb_policy: round_robin http2_protocol_options: {} hosts: - socket_address: address: service2 port_value: 80 - name: jaeger connect_timeout: 1s type: strict_dns lb_policy: round_robin hosts: - socket_address: address: jaeger port_value: 9411 tracing: http: name: envoy.zipkin config: collector_cluster: jaeger collector_endpoint: "/api/v1/spans" shared_span_context: false admin: access_log_path: "/dev/null" address: socket_address: address: 0.0.0.0 port_value: 8001
Service1へのリクエストを行う
$ curl -v localhost:8000/trace/1 * Trying ::1... * TCP_NODELAY set * Connected to localhost (::1) port 8000 (#0) > GET /trace/1 HTTP/1.1 > Host: localhost:8000 > User-Agent: curl/7.54.0 > Accept: */* > < HTTP/1.1 200 OK < content-type: text/html; charset=utf-8 < content-length: 89 < server: envoy < date: Thu, 27 Dec 2018 17:50:09 GMT < x-envoy-upstream-service-time: 62 < Hello from behind Envoy (service 1)! hostname: 74aee62abcce resolvedhostname: 172.22.0.2 * Connection #0 to host localhost left intact
リクエストのシーケンス
Jaeger UI でトレーシング結果を見る
[おまけ]ServiceをSpring Bootで作成してみる
トレースしたいアプリケーションを作成する際の注意点として、Envoyが生成したトレースヘッダーを独自に伝播させる実装を行う必要があります。 サンドボックスでは、service1として機能する単純なアプリケーション(service.py)が、service2への呼び出しを行う際にトレースヘッダーを伝播を実装しています。
@RestController @RequestMapping("/trace/1") public class MainController { private static final String[] TRACING_HEADERS = { "X-Ot-Span-Context", "X-Request-Id", // Zipkin headers "X-B3-TraceId", "X-B3-SpanId", "X-B3-ParentSpanId", "X-B3-Sampled", "X-B3-Flags", // Jaeger header (for native client) "uber-trace-id"}; @Autowired private RestTemplate restTemplate; @RequestMapping(method = RequestMethod.GET) public String main(HttpServletRequest request) { HttpEntity entity = new HttpEntity(tracingHeaders(request)); ResponseEntity<String> response = restTemplate.exchange( "http://localhost:9000/trace/2", HttpMethod.GET, entity, String.class); return response.getBody(); } private HttpHeaders tracingHeaders(HttpServletRequest request) { final HttpHeaders headers = new HttpHeaders(); Arrays.asList(TRACING_HEADERS).stream().forEach(s -> { String h = request.getHeader(s); if (StringUtils.isEmpty(h)) return; headers.set(s, h); }); return headers; } }