弊社には以前 Shadow Proxy というものがいた。
当時のモチベーションとして Database への Index 追加等をぶつけでなく事前にやりたい、ということだった。現状は Production database の Snapshot から Develop database を毎晩リストアしており、事前にそちらで実行するようにしている。
2020年の今やるなら Envoy かな、と。
で、ここでモチベーションとしてあげている負荷テストとは特に、Database の Migration のことを考えている。
現状 EC2 上に MongoDB をセルフホストしている。そのためインスタンスのスケールアップが必要なときにはいろいろあって1日ほどかかる。マネージドサービスの Atlas にすればスケールアップも容易で早くなるし、パフォーマンスに関する分析も提供してくれるようだ。
この Atlas に本番環境を移行する前に、Prodcution と同様の Query を実行しても Performance に問題がないことを事前に確認できればいい。
最近、一部のサービスで、Rails Application から MongoDB への接続の間に Envoy を経由するようにした。
admin: access_log_path: /dev/stdout address: socket_address: { address: 127.0.0.1, port_value: 9901 } static_resources: listeners: - name: listener_0 address: socket_address: { address: 0.0.0.0, port_value: 10000 } filter_chains: - filters: - name: envoy.http_connection_manager config: stat_prefix: ingress_http codec_type: AUTO route_config: name: local_route virtual_hosts: - name: service_http domains: ["*"] routes: # Envoy admin endpoints - match: { prefix: "/ready" } route: { cluster: envoy_admin } - match: { prefix: "/stats" } route: { cluster: envoy_admin } http_filters: - name: envoy.router config: {} - name: mongo_proxy address: socket_address: { address: 0.0.0.0, port_value: 27017 } filter_chains: - filters: - name: envoy.mongo_proxy typed_config: "@type": type.googleapis.com/envoy.config.filter.network.mongo_proxy.v2.MongoProxy stat_prefix: mongo_proxy - name: envoy.tcp_proxy typed_config: "@type": type.googleapis.com/envoy.config.filter.network.tcp_proxy.v2.TcpProxy stat_prefix: mongo_tcp_proxy cluster: mongo_proxy clusters: - name: mongo_proxy connect_timeout: 0.25s type: strict_dns lb_policy: round_robin load_assignment: cluster_name: mongo_proxy endpoints: - lb_endpoints: - endpoint: address: socket_address: address: mongodb-0 port_value: 27017 - endpoint: address: socket_address: address: mongodb-1 port_value: 27017 - name: envoy_admin connect_timeout: 0.250s type: LOGICAL_DNS lb_policy: ROUND_ROBIN hosts: - socket_address: protocol: TCP address: 127.0.0.1 port_value: 9901
MongoProxy filter で metrics をとりつつ、基本的には TCP で proxy しているだけである。そもそも普段、アプリケーションやコマンドラインでは、MongoDB Wired Protocol で MongoDB と通信している。
Cluster を Load Balancing しているのは Primary と Secondary Node である。
さて、このような状態でどうするか?複数の Cluster を定義し、Mirror したものを別の Cliuster (Atlas) に送り、レスポンスは棄却する、ということがやりたい。
request mirror policies が使えそうだが、これは http だ。
残念ながら TCP Layer でこれらは実現できそうにない。
Envoy Slack で "The upstream envoy filter is only for http right now" という回答を得た。やむなし。
とすると http でやるしかない。
こういう例。
どこでミラーするのかあらためて考える。
graph TD A[Nginx] -->|http://service-a.quipper.com| B[k8s service] subgraph Kubernetes B[Nginx] -->|http://service-a| C[k8s service] C -->D[Rails Application Pods] subgraph Pod D -->|mongo://localhost:27017| E[Envoy] end end E -->|mongo://mongo-fqdn:27017| F[MongoDB]
Front Proxy 相当の動きをしている、Kubernetes 内の Nginx でやるしかあるまい。
その場合、この Nginx を Envoy にすぐに置き換えることは難しいので、これまた Sidecar として Envoy を使うことになるだろう。
こうなる。
graph TD A[Nginx] -->|http://service-a.quipper.com| B[k8s service] subgraph Kubernetes subgraph Pod-Nginx B[Nginx] -->|http://localhost| C[Envoy] end C[Envoy] -->|http://service-a| D[k8s service a] D -->E[Rails Application Pods] subgraph Pod-Rails-a E -->|mongo://localhost:27017| F[Envoy] end C -->|Mirroring| H[k8s service b] H -->I[Rails Application Pods] subgraph Pod-Rails-b I -->|mongo://localhost:27017| J[Envoy] end end F -->|mongo://mongo-fqdn:27017| G[MongoDB] J -->|mongo://mongo-fqdn:27017| K[Managed MongoDB]
何が問題になりそうか
- 外部サービスもすべて同様に用意する必要がある。Redis とか PostgreSQL とか。めんどい。
- Mirroring をはじめるタイミング。どこかでうんしょと MongoDB のデータを同期した状態からはじめる必要があるが、Production のデータは常に変わりはじめるので、完全に同期はできない。そのためデータ不整合等のエラーが出る可能性は高い。割り切るしかない?
良い点
- 何より現実のトラフィックと同じものを用意できる点。なんだかんだ自前で負荷テストをやろうとすると Application の知識も必要だし、「これは本当に実際の負荷を再現できているのか?」という永遠に解決しない悩みと向き合い続ける必要がある。それがなくなる点が大きい。
- Kubernetes Cluster を別に作る必要がない点。単純に Deployment が増えているだけなので、下の Node が Cluster Autoscaler で倍増するだけのはずだ。
- 段階的に試せる。ミラーリングはパーセンテージを指定できるので、最初は 1% とかで、本番リソース全体を枯渇させる、みたいなリスクを回避できる。
次回は具体的にどういう config でやるのかを試す。