Carpe Diem

備忘録

KubernetesでEnvoyを使ったSidecarパターンを実装

概要

前回書いた構成をKubernetesで実装してみます。

christina04.hatenablog.com

環境

成果物

今回のソースです。

github.com

構成図

全体図

全体のイメージ図です。
REST Gatewayがあり、そこからAliveUserサービスへ転送されます。

f:id:quoll00:20180701004845p:plain

詳細図

EnvoyをSidecarとして建てた場合の構成図です。
今回はKubernetesを使っているのでService DiscoveryにはHeadless Serviceを使います。

f:id:quoll00:20180701005347p:plain

実装

ConfigMap

Envoyの設定をConfigMapに登録しておいて、そこからマウントできるようにしておきます。
中身は後述します。

$ kubectl create configmap envoy-config --from-file=envoy-config

Userサービス

User系の機能を持つサービスの設定です。

Deployment

https://github.com/jun06t/kubernetes-sample/blob/master/envoy-service-mesh/deployment-user.yml

PodにはSidecar用のEnvoyとUserサービスの2つがあります。

Envoy

ConfigMapから設定をマウントさせます。

      volumes:
        - name: envoy
          configMap:
            name: envoy-config
      containers:
~~~~
        - name: envoy
          image: envoyproxy/envoy:latest
          imagePullPolicy: IfNotPresent
          volumeMounts:
            - name: envoy
              mountPath: /etc/envoy
          command:
            - "/usr/local/bin/envoy"
          args:
            - "--config-path /etc/envoy/sidecar-service.yaml"
          ports:
            - containerPort: 10000
              name: envoy-sidecar
            - containerPort: 10001
              name: envoy-admin

Userサービス

      containers:
        - name: user
          image: "jun06t/grpc-backend"
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 8080

Envoy

Sidecarとして建てるEnvoyのconfigです。
https://github.com/jun06t/kubernetes-sample/blob/master/envoy-service-mesh/envoy-config/sidecar-service.yaml

これはAliveサービスのSidecar Envoyも同様です。

Listeners

ポート10000に来たIngressトラフィックをそのままUserサービスに流します。

  listeners:
  - address:
      socket_address:
        address: 0.0.0.0
        port_value: 10000
    filter_chains:
    - filters:
      - name: envoy.http_connection_manager
        config:
          codec_type: auto
          stat_prefix: ingress_http
          route_config:
            name: local_route
            virtual_hosts:
            - name: service
              domains:
              - "*"
              routes:
              - match:
                  prefix: "/"
                route:
                  cluster: local_service

Clusters

Sidecarとして同じPodで用意するので、hostsは127.0.0.1になります。
gRPC通信を許可するためhttp2_protocol_optionsを付けます。

  clusters:
  - name: local_service
    http2_protocol_options: {}
    connect_timeout: 0.25s
    type: static
    lb_policy: round_robin
~~~~
    hosts:
    - socket_address:
        address: 127.0.0.1
        port_value: 8080

Service

UserサービスのService Discoveryのため、Headless serviceを用意します。

https://github.com/jun06t/kubernetes-sample/blob/master/envoy-service-mesh/service-user.yml

spec:
  clusterIP: None
  ports:
  - name: headless
    port: 12345
    protocol: TCP
    targetPort: 12345
  selector:
    app: user-service

DNSとして使うので本来ポートは不要ですが、無いとEndpointの作成をしてくれないバグがあるので適当な値で用意します。

Aliveサービス

Aliveサービスも同様の感じなので省略します。

ゲートウェイ

次にゲートウェイ側です。

Deployment

https://github.com/jun06t/kubernetes-sample/blob/master/envoy-service-mesh/deployment-gateway.yml

先ほどとはdocker imageやEnvoyのconfigを変えます。

Envoy

      volumes:
        - name: envoy
          configMap:
            name: envoy-config
      containers:
~~~~
        - name: envoy
          image: envoyproxy/envoy:latest
          imagePullPolicy: IfNotPresent
          volumeMounts:
            - name: envoy
              mountPath: /etc/envoy
          command:
            - "/usr/local/bin/envoy"
          args:
            - "--config-path /etc/envoy/sidecar-gateway.yaml"
          ports:
            - containerPort: 10000
              name: envoy-sidecar
            - containerPort: 10001
              name: envoy-admin

Gateway

      containers:
        - name: gateway
          image: "jun06t/grpc-gateway"
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 3000

Envoy

今回一番重要な設定です。

https://github.com/jun06t/kubernetes-sample/blob/master/envoy-service-mesh/envoy-config/sidecar-gateway.yaml

Ingress Listener

ポート10000へのIngressトラフィックgateway-containerに流します

  listeners:
  - address:
      socket_address:
        address: 0.0.0.0
        port_value: 10000
    filter_chains:
    - filters:
      - name: envoy.http_connection_manager
        config:
          codec_type: auto
          stat_prefix: ingress_http
          route_config:
            name: local_route
            virtual_hosts:
            - name: service
              domains:
              - "*"
              routes:
              - match:
                  prefix: "/"
                route:
                  cluster: local_service

Egress Listener

127.0.0.1:8080へのEgressトラフィック(アウトバウンド)をAliveサービスやUserサービスに流します。
これはEnvoyを挟む前のgateway->各サービスのエンドポイントをlocalhost:8080にしていたためです。
またAliveUserかは、protobufのパスでルーティングします。

  listeners:
~~~~
  - address:
      socket_address:
        address: 127.0.0.1
        port_value: 8080
    filter_chains:
    - filters:
      - name: envoy.http_connection_manager
        config:
          codec_type: auto
          stat_prefix: egress_http
          route_config:
            name: local_route
            virtual_hosts:
            - name: alive
              domains:
              - "*"
              routes:
              - match:
                  prefix: "/gateway.AliveService"
                route:
                  cluster: alive
              - match:
                  prefix: "/gateway.UserService"
                route:
                  cluster: user

Clusters

Aliveサービス等を検知するために、clusterにはHeadless serviceを指定します。

  clusters:
~~~~
  - name: alive
    http2_protocol_options: {}
    connect_timeout: 0.25s
    type: strict_dns
    lb_policy: round_robin
~~~~
    hosts:
    - socket_address:
        address: alive-svc
        port_value: 10000
  - name: user
    http2_protocol_options: {}
    connect_timeout: 0.25s
    type: strict_dns
    lb_policy: round_robin
~~~~
    hosts:
    - socket_address:
        address: user-svc
        port_value: 10000

Service

https://github.com/jun06t/kubernetes-sample/blob/master/envoy-service-mesh/service-gateway.yml

外部からアクセスできるよう、NodePort Serviceを用意します。

spec:
  type: NodePort
  ports:
  - name: proxy
    port: 3000
    protocol: TCP
    targetPort: 10000
  - name: admin
    port: 3001
    protocol: TCP
    targetPort: 10001
  selector:
    app: gateway

動作確認

実行

クラスタを構築します。

$ minikube start
$ make configmap
$ make apply

NodePortのチェック

$ minikube service gateway-svc --url
http://192.168.99.100:32593
http://192.168.99.100:30569

curlで検証します。

$ curl http://192.168.99.100:32593/alive
{"status":true}

$ curl http://192.168.99.100:32593/user
{"users":[{"name":"Alice","age":20},{"name":"Bob","age":24}]}

大丈夫そうですね。

ログ

$ stern gateway
+ gateway-deployment-c5dc5f87-9rwxk › envoy
+ gateway-deployment-c5dc5f87-9rwxk › gateway
gateway-deployment-c5dc5f87-9rwxk envoy [2018-07-02 09:51:17.869][1][info][main] source/server/server.cc:183] initializing epoch 0 (hot restart version=10.200.16384.127.options=capacity=16384, num_slots=8209 hash=228984379728933363 size=2654312)
gateway-deployment-c5dc5f87-9rwxk envoy [2018-07-02 09:51:17.869][1][info][main] source/server/server.cc:185] statically linked extensions:
gateway-deployment-c5dc5f87-9rwxk envoy [2018-07-02 09:51:17.869][1][info][main] source/server/server.cc:187]   access_loggers: envoy.file_access_log,envoy.http_grpc_access_log
gateway-deployment-c5dc5f87-9rwxk envoy [2018-07-02 09:51:17.869][1][info][main] source/server/server.cc:190]   filters.http: envoy.buffer,envoy.cors,envoy.ext_authz,envoy.fault,envoy.filters.http.header_to_metadata,envoy.filters.http.jwt_authn,envoy.filters.http.rbac,envoy.grpc_http1_bridge,envoy.grpc_json_transcoder,envoy.grpc_web,envoy.gzip,envoy.health_check,envoy.http_dynamo_filter,envoy.ip_tagging,envoy.lua,envoy.rate_limit,envoy.router,envoy.squash
gateway-deployment-c5dc5f87-9rwxk envoy [2018-07-02 09:51:17.869][1][info][main] source/server/server.cc:193]   filters.listener: envoy.listener.original_dst,envoy.listener.proxy_protocol,envoy.listener.tls_inspector
gateway-deployment-c5dc5f87-9rwxk envoy [2018-07-02 09:51:17.869][1][info][main] source/server/server.cc:196]   filters.network: envoy.client_ssl_auth,envoy.echo,envoy.ext_authz,envoy.filters.network.thrift_proxy,envoy.http_connection_manager,envoy.mongo_proxy,envoy.ratelimit,envoy.redis_proxy,envoy.tcp_proxy
gateway-deployment-c5dc5f87-9rwxk envoy [2018-07-02 09:51:17.869][1][info][main] source/server/server.cc:198]   stat_sinks: envoy.dog_statsd,envoy.metrics_service,envoy.stat_sinks.hystrix,envoy.statsd
gateway-deployment-c5dc5f87-9rwxk envoy [2018-07-02 09:51:17.869][1][info][main] source/server/server.cc:200]   tracers: envoy.dynamic.ot,envoy.lightstep,envoy.zipkin
gateway-deployment-c5dc5f87-9rwxk envoy [2018-07-02 09:51:17.869][1][info][main] source/server/server.cc:203]   transport_sockets.downstream: envoy.transport_sockets.capture,raw_buffer,tls
gateway-deployment-c5dc5f87-9rwxk envoy [2018-07-02 09:51:17.869][1][info][main] source/server/server.cc:206]   transport_sockets.upstream: envoy.transport_sockets.capture,raw_buffer,tls
gateway-deployment-c5dc5f87-9rwxk envoy [2018-07-02 09:51:17.876][1][info][config] source/server/configuration_impl.cc:50] loading 0 static secret(s)
gateway-deployment-c5dc5f87-9rwxk envoy [2018-07-02 09:51:17.878][1][info][config] source/server/configuration_impl.cc:60] loading 2 listener(s)
gateway-deployment-c5dc5f87-9rwxk envoy [2018-07-02 09:51:17.881][1][info][config] source/server/configuration_impl.cc:94] loading tracing configuration
gateway-deployment-c5dc5f87-9rwxk envoy [2018-07-02 09:51:17.881][1][info][config] source/server/configuration_impl.cc:116] loading stats sink configuration
gateway-deployment-c5dc5f87-9rwxk envoy [2018-07-02 09:51:17.882][1][info][main] source/server/server.cc:410] starting main dispatch loop
gateway-deployment-c5dc5f87-9rwxk envoy [2018-07-02 09:51:17.965][1][info][upstream] source/common/upstream/cluster_manager_impl.cc:132] cm init: all clusters initialized
gateway-deployment-c5dc5f87-9rwxk envoy [2018-07-02 09:51:17.965][1][info][main] source/server/server.cc:390] all clusters initialized. initializing init manager
gateway-deployment-c5dc5f87-9rwxk envoy [2018-07-02 09:51:17.965][1][info][config] source/server/listener_manager_impl.cc:798] all dependencies initialized. starting workers
gateway-deployment-c5dc5f87-9rwxk envoy [2018-07-02T09:52:46.797Z] "POST /gateway.AliveService/GetStatus HTTP/2" 200 - 5 7 55 22 "127.0.0.1" "grpc-go/1.13.0" "ef16252e-ce39-42c8-963a-46eb06364624" "localhost:8080" "172.17.0.7:10000"
gateway-deployment-c5dc5f87-9rwxk envoy [2018-07-02T09:52:46.773Z] "GET /alive HTTP/1.1" 200 - 0 15 105 103 "-" "curl/7.54.0" "214f1bb3-758f-42cf-9968-d18d0e3f66ca" "192.168.99.100:32593" "127.0.0.1:3000"
gateway-deployment-c5dc5f87-9rwxk envoy [2018-07-02T09:52:57.068Z] "POST /gateway.UserService/GetUsersByGroup HTTP/2" 200 - 5 25 4 3 "127.0.0.1" "grpc-go/1.13.0" "858c0acb-e991-48ac-9faa-b2de83588241" "localhost:8080" "172.17.0.8:10000"
gateway-deployment-c5dc5f87-9rwxk envoy [2018-07-02T09:52:57.065Z] "GET /user HTTP/1.1" 200 - 0 61 17 17 "-" "curl/7.54.0" "9304a8a7-5bed-485c-a51b-fc822c76cf90" "192.168.99.100:32593" "127.0.0.1:3000"

アクセスログもきちんと出てます。

負荷分散

replicas: 2にしてPodが複数あったUserサービスではちゃんとリクエストがバランシングされてます。

f:id:quoll00:20180703054212p:plain

まとめ

EnvoyつかってマイクロサービスのService Meshのdata plain構成(Sidecarパターン)を実装しました。
Envoyはcontrol plainを用意して設定を動的に変更させたりもできますし、分散トレーシングのサンプルなども公式リポジトリにあるので今回の構成がひとまずできれば拡張はしやすいと思います。
まだまだ検証は必要ですが、これまでのマイクロサービスの問題が一気に解決できそうですね。