寝て起きて寝る

過ぎたるは及ばざるが如し

EnvoyとJaegerを使用した分散トレーシングを試してみた

Envoyとは

ライドシェアサービスのLyftが開発し現在はCNCFでホストされている大規模サービス指向アーキテクチャ用L4/L7プロキシです。 また、Kubernetesを使用した場合にサービスメッシュをマネージメントために使用されるIstioのコアコンポーネントの一つです。

www.envoyproxy.io

ただ、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へトレーシングデータの送信がされます。

f:id:yasu7ri:20181228054957p:plain

  • 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)の概要
    • 0.0.0.0:80 を listenして全てのリクエストをclusterで定義されているlocal_serviceへルーティングする
    • 0.0.0.0:9000 を listenしてprefixが'/trace/2'のリクエストの場合はclusterで定義されているservice2へルーティングする
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
リクエストのシーケンス

f:id:yasu7ri:20181228043607p:plain

Jaeger UI でトレーシング結果を見る

f:id:yasu7ri:20181228050457p:plain

f:id:yasu7ri:20181228050512p:plain

[おまけ]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;
    }
}

参考サイト