Envoy Kubernetes Network gRPC Envoy Gateway を使った gRPC のリトライ Envoy Gateway を使った gRPC のリトライを試してみました。検証で使う fake-service の fault-injection により、一定の確率でエラーを返すように設定しています。Envoy Gateway は、エラーが発生した場合にリトライを行います。
構成 Envoy Gateway を使わない場合 まず、Envoy Gateway を用いずにリトライを行う場合の構成を確認します。 この構成ではクライアントがサービスにリクエストを送信し、サービスがエラーを返すとクライアントがリトライを行います。
sequenceDiagram
participant C as Client
participant S as Service
C->>S: Request
S->>C: Response 500
C->>S: Request
S->>C: Response 500
C->>S: Request
S->>C: Response 200
Envoy Gateway を使った場合 次に、Envoy Gateway を使ったリトライの構成を確認します。 この構成ではクライアントが Envoy Gateway のサービスにリクエストを送信し、Envoy Gateway がサービスにリクエストを送信します。サービスがエラーを返すと Envoy Gateway がリトライを行います。
sequenceDiagram
participant C as Client
participant G as Gateway
participant S as Service
C->>G: Request
G->>S: Request
S->>G: Response 500
G->>S: Request
S->>G: Response 500
G->>S: Request
S->>G: Response 200
G->>C: Response 200
以降の手順では、Envoy Gateway を使ったリトライの構成を検証します。
セットアップ クラスターに Envoy Gateway をインストールします。envoyproxies.gateway の CRD がインストールされていることを確認します。
kubectl get crd | grep envoyproxies.gateway
インストールされていない場合は、以下のコマンドでインストールします。
helm install eg oci://docker.io/envoyproxy/gateway-helm --version v1.3.0 -n envoy-gateway-system --create-namespace
サンプルの gRPC アプリケーションをデプロイします。
kubectl apply -f https://raw.githubusercontent.com/Nishikoh/envoy-sandbox/refs/heads/main/grpc-retry/grpc-routing.yaml
コードの簡単な解説 apiVersion : gateway.envoyproxy.io/v1alpha1
kind : EnvoyProxy
metadata :
name : custom-envoy-proxy
namespace : envoy-gateway-system
spec :
provider :
type : Kubernetes
kubernetes :
envoyService : # (2)
name : grpc-gateway # (1)
---
apiVersion : gateway.networking.k8s.io/v1
kind : GatewayClass
metadata :
name : eg-example
labels :
example : grpc-routing
spec :
controllerName : gateway.envoyproxy.io/gatewayclass-controller
parametersRef :
group : gateway.envoyproxy.io
kind : EnvoyProxy # (3)
name : custom-envoy-proxy
namespace : envoy-gateway-system
---
apiVersion : gateway.networking.k8s.io/v1
kind : Gateway
metadata :
name : eg-example
labels :
example : grpc-routing
spec :
gatewayClassName : eg-example
listeners : # (4)
- name : http
protocol : HTTP
port : 80
---
apiVersion : apps/v1
kind : Deployment
metadata :
labels :
app : fake
example : grpc-routing
name : fake
spec :
selector :
matchLabels :
app : fake
replicas : 1
template :
metadata :
labels :
app : fake
spec :
containers :
- name : grpcsrv
image : nicholasjackson/fake-service:v0.26.2
env :
- name : ERROR_RATE
value : "0.5" # (5)
- name : ERROR_TYPE
value : "http_error"
- name : ERROR_CODE
value : "14"
- name : SERVICE_TYPE
value : "grpc"
- name : LISTEN_ADDR
value : "0.0.0.0:9000"
ports :
- containerPort : 9000
protocol : TCP
---
apiVersion : v1
kind : Service
metadata :
labels :
app : fake
example : grpc-routing
name : fake
spec :
type : ClusterIP
ports :
- name : http
port : 9000
protocol : TCP
targetPort : 9000
selector :
app : fake
---
apiVersion : gateway.networking.k8s.io/v1
kind : GRPCRoute # (6)
metadata :
name : fake
labels :
example : grpc-routing
spec :
parentRefs :
- name : eg-example # (7)
hostnames :
- "grpc-example.com"
rules :
- backendRefs : # (8)
- group : ""
kind : Service
name : fake
port : 9000
weight : 1
---
apiVersion : gateway.envoyproxy.io/v1alpha1
kind : BackendTrafficPolicy
metadata :
name : retry-policy
namespace : default
spec :
# faultInjection:
# abort:
# grpcStatus: 14
# percentage: 50
retry : # (9)
numRetries : 5
perRetry :
backOff :
baseInterval : 100ms
maxInterval : 10s
timeout : 250ms
retryOn :
triggers :
- unavailable
- retriable-status-codes
targetRefs : # (10)
- group : gateway.networking.k8s.io
kind : GRPCRoute
name : fake
Gatewayを新しく作る際に、この指定がないとランダムな文字列が付与され、識別しにくい。指定することを強く推奨 Serviceの設定以外にもHPAやDeploymentなどの設定も可能 GatewayClassにEnvoyProxyを紐付ける ここではport 80のHTTPリクエストを受け付ける。他のportやTCPなどのプロトコルも設定可能 リトライ検証のためにfake-serviceのエラー率を50%に設定 gRPCRouteの設定 Gatewayの紐付け Serviceの紐付け リトライの設定 リトライの対象を指定 各リソースの依存関係 graph TD
subgraph Gateways
envoyProxy[EnvoyProxy: custom-envoy-proxy]
gatewayClass[GatewayClass: eg-example]
gateway[Gateway: eg-example]
end
subgraph Deployments & Services
fake[Deployment: fake]
fakeService[Service: fake]
end
subgraph Routing & Policies
grpcRoute[GRPCRoute: fake]
faultInjection[BackendTrafficPolicy: retry-policy]
end
gatewayClass --> envoyProxy
gateway --> gatewayClass
faultInjection --> grpcRoute
grpcRoute --> gateway
grpcRoute --> fakeService
リトライの動作検証 Envoy Gateway の gRPC リトライを検証します。
export GATEWAY_HOST = $( kubectl get gateway/eg-example -o jsonpath = '{.status.addresses[0].value}' )
grpcurl -plaintext -authority= grpc-example.com ${ GATEWAY_HOST } :80 FakeService.Handle
結果は以下のようになります。
{
"Message" : "{\n \"name\": \"Service\",\n \"type\": \"gRPC\",\n \"ip_addresses\": [\n \"x.y.z.32\"\n ],\n \"start_time\": \"2025-02-19T06:06:12.655413\",\n \"end_time\": \"2025-02-19T06:06:12.655454\",\n \"duration\": \"40.876µs\",\n \"body\": \"Hello World\",\n \"code\": 0\n}\n"
}
上記のコマンドでログを見ると error_injector によってエラーが発生しています。x-request-id value
が同じであることから、Envoy Gateway によって成功するまでリトライされていることがわかります。
2025-02-19T06:06:12.653Z [INFO] Handling request gRPC request:
context=
| key: :authority value: [grpc-example.com]
| key: content-type value: [application/grpc]
| key: grpc-accept-encoding value: [gzip]
| key: x-forwarded-for value: [xx.yy.zz.1]
| key: user-agent value: [grpcurl/1.9.2 grpc-go/1.61.0]
| key: x-forwarded-proto value: [http]
| key: x-envoy-external-address value: [xx.yy.zz.1]
| key: x-request-id value: [b93fa578-5291-4023-9f2a-63feca3d07ea]
2025-02-19T06:06:12.653Z [INFO] error_injector: Injecting error: request_count=2 error_percentage=0.5 error_type=http_error
2025-02-19T06:06:12.653Z [ERROR] Error handling request: error="Service error automatically injected"
2025-02-19T06:06:12.653Z [INFO] Finished handling request: duration="125.503µs"
2025-02-19T06:06:12.655Z [INFO] Handling request gRPC request:
context=
| key: user-agent value: [grpcurl/1.9.2 grpc-go/1.61.0]
| key: grpc-accept-encoding value: [gzip]
| key: x-request-id value: [b93fa578-5291-4023-9f2a-63feca3d07ea]
| key: :authority value: [grpc-example.com]
| key: x-forwarded-for value: [xx.yy.zz.1]
| key: x-forwarded-proto value: [http]
| key: x-envoy-external-address value: [xx.yy.zz.1]
| key: content-type value: [application/grpc]
2025-02-19T06:06:12.655Z [INFO] Finished handling request: duration="52.877µs"
同じ namespace からのアクセス確認 default の namespace にある fake に同じ名前空間から Envoy Gateway を通してアクセスする場合、以下のようになります。 grpcurl を使って Envoy Gateway のサービスにリクエストを送信します。
kubectl run grpcurl --image= fullstorydev/grpcurl:latest --namespace default -it --rm -- -plaintext -authority= grpc-example.com grpc-gateway.envoy-gateway-system.svc.cluster.local:80 FakeService.Handle
クライアントのログを確認すると通信が成功していることがわかります。
{
"Message" : "{\n \"name\": \"Service\",\n \"type\": \"gRPC\",\n \"ip_addresses\": [\n \"xxx.yyy.zzz.34\"\n ],\n \"start_time\": \"2025-02-20T07:55:33.765312\",\n \"end_time\": \"2025-02-20T07:55:33.765457\",\n \"duration\": \"145.169µs\",\n \"body\": \"Hello World\",\n \"code\": 0\n}\n"
}
サーバーのログを確認すると、先ほどと同様に失敗した後に同じx-request-id
でリトライされていることがわかります。
fake-74cb56bd56-kfx9h grpcsrv 2025-02-20T07:58:17.985Z [INFO] Handling request gRPC request:
fake-74cb56bd56-kfx9h grpcsrv context=
fake-74cb56bd56-kfx9h grpcsrv | key: x-forwarded-for value: [xxx.yyy.zzz.42]
fake-74cb56bd56-kfx9h grpcsrv | key: x-forwarded-proto value: [http]
fake-74cb56bd56-kfx9h grpcsrv | key: :authority value: [grpc-example.com]
fake-74cb56bd56-kfx9h grpcsrv | key: content-type value: [application/grpc]
fake-74cb56bd56-kfx9h grpcsrv | key: x-envoy-external-address value: [xxx.yyy.zzz.42]
fake-74cb56bd56-kfx9h grpcsrv | key: x-request-id value: [6e8c6e70-0133-475c-a070-6aeb3638de5c]
fake-74cb56bd56-kfx9h grpcsrv | key: user-agent value: [grpcurl/v1.9.2 grpc-go/1.61.0]
fake-74cb56bd56-kfx9h grpcsrv | key: grpc-accept-encoding value: [gzip]
fake-74cb56bd56-kfx9h grpcsrv
fake-74cb56bd56-kfx9h grpcsrv 2025-02-20T07:58:17.985Z [INFO] error_injector: Injecting error: request_count=36 error_percentage=0.5 error_type=http_error
fake-74cb56bd56-kfx9h grpcsrv 2025-02-20T07:58:17.986Z [ERROR] Error handling request: error="Service error automatically injected"
fake-74cb56bd56-kfx9h grpcsrv 2025-02-20T07:58:17.986Z [INFO] Finished handling request: duration=1.097642ms
fake-74cb56bd56-kfx9h grpcsrv 2025-02-20T07:58:18.052Z [INFO] Handling request gRPC request:
fake-74cb56bd56-kfx9h grpcsrv context=
fake-74cb56bd56-kfx9h grpcsrv | key: content-type value: [application/grpc]
fake-74cb56bd56-kfx9h grpcsrv | key: grpc-accept-encoding value: [gzip]
fake-74cb56bd56-kfx9h grpcsrv | key: x-request-id value: [6e8c6e70-0133-475c-a070-6aeb3638de5c]
fake-74cb56bd56-kfx9h grpcsrv | key: user-agent value: [grpcurl/v1.9.2 grpc-go/1.61.0]
fake-74cb56bd56-kfx9h grpcsrv | key: x-forwarded-for value: [xxx.yyy.zzz.42]
fake-74cb56bd56-kfx9h grpcsrv | key: x-forwarded-proto value: [http]
fake-74cb56bd56-kfx9h grpcsrv | key: x-envoy-external-address value: [xxx.yyy.zzz.42]
fake-74cb56bd56-kfx9h grpcsrv | key: :authority value: [grpc-example.com]
fake-74cb56bd56-kfx9h grpcsrv
fake-74cb56bd56-kfx9h grpcsrv 2025-02-20T07:58:18.052Z [INFO] Finished handling request: duration="210.211µs"
気になったこと Envoy Gateway で Fault Injection と Retry をまとめてやろうとしたらうまくいかなかった これまでの手順では fake-service を使って Fault Injection の設定をしていました。Envoy Gateway の機能にも Fault Injection がありますが、うまく動作しなかったので fake-service を使って Fault Injection を設定しました。 Envoy Gateway で Fault Injection と Retry を設定したら下記のような挙動になると想定していました。 しかし、実際は Fault Injection が設定されていると、Envoy Gateway での Retry が行われませんでした。
sequenceDiagram
participant C as Client
participant G as Gateway
participant S as Service
C->>G: Request
G->>S: Request
S->>G: Response 500
G->>S: Request
S->>G: Response 500
G->>S: Request
S->>G: Response 200
G->>C: Response 200
上記のような挙動になると想定していましたが、実際には Fault Injection が Gateway で実行される以下のような挙動になりました。
sequenceDiagram
participant C as Client
participant G as Gateway
participant S as Service
C->>G: Request
G->>C: Response 500
2025-02-22 03:46:58 2025-02-20 17:54:47