分散システム
分散システムを構築する場合、Python Go Mysql Redis 多様なプロトコルがある
それぞれのサーバが稼働しているか、安全かは信頼することができない
常に落ちている可能性を考慮すべきだ
自分のサービスには興味を持ち、他のサービスには関心を持たない分散が重要
サービスメッシュとサイドカーのRailsアプリケーションへの適応
例えば、一般的なRailsアプリケーションにおいてサービスメッシュを適用する場合を考えてみましょう。
この場合、アプリケーションのコンテナ(appコンテナと呼ぶことにしましょう)とは別に、サービスメッシュが提供するサイドカープロキシ(Envoy Proxyなど)が配置されます。
サイドカープロキシはappコンテナと一緒にPod内で動作し、appコンテナが発生させる全てのネットワークトラフィック(発信もしくは受信)を透過的にインターセプトします。
これにより、appコンテナは他のサービスと直接通信することなく、自身のローカルホスト上のサイドカープロキシと通信します。
サイドカープロキシがこれをサービスメッシュ内の他のすべてのプロキシと通信するための中継として機能します。
このセットアップにより、トラフィックのルーティング、ロードバランシング、エラーリトライ、ログ記録、メトリクス収集、トレーシング、セキュリティポリシーの強制など、ネットワーク関連の多くの事項をappコンテナから分離して、サービスメッシュの機能として提供することが可能になります。
Envoy (DataPlane)
"Envoy"は、高性能でプログラム可能な、サービスメッシュやマイクロサービス設計に適しているモダンなエッジとサービスプロキシです。Envoyは、独立したプロキシとしても、あるいはサービスメッシュのデータプレーンとしても利用できます。
アプリケーションコンテナ
Railsアプリケーション
Data Plane
サービスメッシュアーキテクチャで、実際のデータ通信(リクエストとレスポンス)を処理する部分。Data Plane内では通常、サービスプロキシまたはサイドカープロキシが動作します。
Service Proxy
特定のサービスのインスタンス間で通信を行い、調整する役割を果たします。
Sidecar Proxy
アプリケーションコンテナと共にデプロイされ、そのコンテナの全ネットワークトラフィックを透過的にハンドリングします。リクエストの監視、ルーティング、セキュリティポリシー強制などを担当します。
Railsが通信する例
Railsアプリケーションは、アプリケーションコンテナ内で動作します。
Railsアプリケーションの全てのネットワーク通信は、
Data Plane内のサイドカープロキシにより透過的にインターセプトされます。
Railsアプリケーションが他のサービスと通信を試みる場合、直接通信するのではなく、ローカルホスト上のサイドカープロキシと通信します。
サイドカープロキシはこの通信を適切な宛先のサービスプロキシあるいはサイドカープロキシへルーティングし、その結果をRailsアプリケーションに返します。
こうすることで、アプリケーションのコードからネットワーク通信の面倒な詳細を抽象化することができ、エラーハンドリング、リトライロジック、セキュリティ、観測性などの側面を強化できます。なお、Service Proxyという用語は、一部のコンテキストではSidecar Proxyと同義に使われることもあります。
docker-composeで表現するなら
version: "3"
services:
rails:
build: .
command: bundle exec rails s -p 3000 -b '0.0.0.0'
volumes:
- .:/myapp
links:
- db
ports:
- "3000:3000"
depends_on:
- envoy
db:
image: postgres:latest
volumes:
- ./tmp/db:/var/lib/postgresql/data
envoy:
image: envoyproxy/envoy:latest
volumes:
- ./envoy/envoy.yaml:/etc/envoy/envoy.yaml
ports:
- "10000:10000"
depends_on:
- rails
Envoyが8080ポートでリッスンし、すべての受信リクエストをローカルのrailsアプリケーション(localhostの3000ポート)に転送します。
static_resources:
listeners:
- name: listener_0
address:
socket_address: { address: 0.0.0.0, port_value: 8080 }
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
codec_type: AUTO
stat_prefix: ingress_http
route_config:
name: local_route
virtual_hosts:
- name: local_service
domains: ["*"]
routes:
- match: { prefix: "/" }
route: { host_rewrite_literal: localhost, cluster: web_service_proxy, timeout: 0s }
http_filters:
- name: envoy.filters.http.router
clusters:
- name: web_service_proxy
connect_timeout: 0.25s
type: STATIC
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: web_service_proxy
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: host.docker.internal
port_value: 3000
死活監視機能を入れるなら
clusters:
- name: web_service_proxy
connect_timeout: 0.25s
type: STRICT_DNS
lb_policy: ROUND_ROBIN
http2_protocol_options: {}
load_assignment:
cluster_name: web_service_proxy
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: localhost
port_value: 3000
health_checks:
- timeout: 1s
interval: 10s
unhealthy_threshold: 2
healthy_threshold: 2
http_health_check: { path: "/health_check" }
prometheus
admin:
access_log_path: "/var/log/envoy/admin_access.log"
address:
socket_address:
address: 0.0.0.0
port_value: 8001
stats_config:
stats_tags:
- tag_name: "listening_port"
regex: "^listener.((?:[_.[:digit:]]*)).http"
- tag_name: "cluster_name"
regex: "^cluster.((?:[_.[:digit:]]*))"
stats_sink:
name: envoy.stat_sinks.metrics_service
typed_config:
"@type": type.googleapis.com/envoy.config.metrics.v2.MetricsServiceConfig
grpc_service:
envoy_grpc: { cluster_name: stats_sink_cluster }
clusters:
- name: stats_sink_cluster
type: LOGICAL_DNS
load_assignment:
cluster_name: stats_sink_cluster
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address: { address: localhost, port_value: 1234 }
死活状況のチェック
curl http://localhost:8001/stats
参考