概要
前回書いた構成をKubernetesで実装してみます。
環境
- minikube v0.26.1
- kubernetes v1.10.0
成果物
今回のソースです。
構成図
全体図
全体のイメージ図です。
REST Gatewayがあり、そこからAlive
やUser
サービスへ転送されます。
詳細図
EnvoyをSidecarとして建てた場合の構成図です。
今回はKubernetesを使っているのでService DiscoveryにはHeadless Service
を使います。
実装
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
今回一番重要な設定です。
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
にしていたためです。
またAlive
かUser
かは、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
サービスではちゃんとリクエストがバランシングされてます。
まとめ
EnvoyつかってマイクロサービスのService Meshのdata plain構成(Sidecarパターン)を実装しました。
Envoyはcontrol plainを用意して設定を動的に変更させたりもできますし、分散トレーシングのサンプルなども公式リポジトリにあるので今回の構成がひとまずできれば拡張はしやすいと思います。
まだまだ検証は必要ですが、これまでのマイクロサービスの問題が一気に解決できそうですね。