istio-proxy 停止時の挙動

Yoshinori Teraoka

2022.5.26

istio の sidecar である pilot-agent, envoy が Pod の終了時にどう振る舞うのかをまとめてみました。

デフォルトの istio-proxy

default-istio
  • 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 している port
    pilot/cmd/pilot-agent/status/ready/probe.gohttp://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 で検索)

ブログ一覧へ戻る

お気軽にお問い合わせください

SREの設計・技術支援から、
SRE運用内で使用する
ツールの導入など、
SRE全般についてご支援しています。

資料請求・お問い合わせ