istio の sidecar である pilot-agent, envoy が Pod の終了時にどう振る舞うのかをまとめてみました。
デフォルトの istio-proxy
- Pod Delete されたタイミングで各コンテナに SIGTERM が送られる
- アプリコンテナが SIGTERM を受け取った場合の挙動は実装依存
- istio-proxy は Envoy を graceful drain モードにした上で terminationDrainDuration (default 5s) 待って終了する
- Envoy は graceful drain 中も新規接続を受け付け、HTTP/1.1 では Response のヘッダーに
Connection: close
をセットしてくれたり HTTP2 では GOAWAY を返してくれたりする - アプリコンテナの終了よりも先に istio-proxy (Envoy) が終了してしまうと、クライアント↔︎アプリ間やアプリ↔︎DB間との通信も切断されてしまう
- Graceful drain 時間は
terminationDrainDuration
(default 5s) 設定で調整可能
preStop hook で main container の listener port の close を待つパターン
istio-proxy の preStop hook に次のような script を設定して、TCP Port を Listen しているプロセスがいなくなるまで istio-proxy への SIGTERM を遅らせるという hack が行われている場合の例です。
while [ $(netstat -plunt | grep tcp | grep -v envoy | wc -l | xargs) -ne 0 ]; do sleep 1; done
- アプリが終了時に必要な処理を完了した後に listen port を close する場合は istio-proxy が先に終了しているということが発生しない
- 処理中の connection 以外は port の listen も含め、すぐに close してしまう nginx のような場合は効果がなく、
terminationDrainDuration
(default 5s) を調整する必要がある
EXIT_ON_ZERO_ACTIVE_CONNECTIONS を有効にした場合
EXIT_ON_ZERO_ACTIVE_CONNECTIONS
を有効にした場合、istio-proxy は Envoy を Graceful drain モードにした後、minDrainDuration
(default 5s) 待ってから envoy のdownstream_cx_active
メトリクスを毎秒監視して 0 になるまで待機するEXIT_ON_ZERO_ACTIVE_CONNECTIONS
を有効にするなら preStop hook はなくても良いminDrainDuration
中に新規のリクエストが来ないようになっていないと active connection 0 を確認した直後に増える可能性がある
→ Service の Endpoint からの削除や istio の EDS 反映が終わること
→ クラスタ外の Load Balancer を使用している場合はそこから外れていること (istio-ingressgateway)
Drain 中の healthcheck
istio-proxy (pilot-agent) が Envoy を drain モードにした後は 15021 port の /healthz/ready
は 503 を返すようになります。(Load Balancer からは早く外れたいのでこれは望ましい設定だと思われる)
この healthcheck 用 endpoint は Envoy が 15021 port で受けた後に pilot-agent の 15000 port に転送し、そこで既に drain 中かどうかや Envoy の stats を確認しているので、Envoy も istio-pilot も生きているかどうかの確認になっています。
istio-proxy の healthcheck
istio-proxy の healthcheck endpoint で行われていることの紹介です。
前提
istio-proxy は pilot-agent が Envoy を管理しており、どちらのプロセスも機能している必要がある
istio-proxy を監視する際に指定している 15021 port の /healthz/ready
は何をやっているのか?
- 15021 port を Listen しているのは Envoy
そのため Envoy が死んだら healthcheck も失敗する - 15021 port で受けた
/healthz/ready
は agent cluster に proxy される
agent cluster の endpoint は 127.0.0.1:15020 となっており、これは pilot-agent が Listen している portpilot/cmd/pilot-agent/status/ready/probe.go
でhttp://127.0.0.1:15000/stats?usedonly&filter=^(server\.state|listener_manager\.workers_started)
にアクセスして Envoy が機能していることを確認している (起動するまでだけ) - Listener
"static_resources": { "listeners": [ { "address": { "socket_address": { "address": "0.0.0.0", "port_value": 15021 } }, "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", "stat_prefix": "agent", "route_config": { "virtual_hosts": [ { "name": "backend", "domains": [ "*" ], "routes": [ { "match": { "prefix": "/healthz/ready" }, "route": { "cluster": "agent" } } ] } ] }, "http_filters": [ { "name": "envoy.filters.http.router", "typed_config": { "@type": "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router" } } ] } } ] } ] } ]
- Cluster
"clusters": [ { "name": "agent", "type": "STATIC", "connect_timeout": "0.250s", "load_assignment": { "cluster_name": "agent", "endpoints": [ { "lb_endpoints": [ { "endpoint": { "address": { "socket_address": { "address": "127.0.0.1", "port_value": 15020 } } } } ] } ] } }, ... ]
Pod 起動時のコンテナ起動順序問題
Pod から出ていく通信も Envoy を経由するようになるため、起動時にもメインのアプリより先に istio-proxy が起動してくれていないと困ります。
これは kubelet が Pod manifest の containers に書かれた順に container を起動させ、postStart hook があると、それの実行が完了してから次の container に進むという仕様を利用して sidecar injection で istio-proxy をメインのコンテナよりも先に挿入し、postStart hook で pailot-agent wait
コマンドを実行して istio-proxy の完了を待つようにする設定項目があるためこれを有効にします。(holdApplicationUntilProxyStarts で検索)