Pod の起動時に、IP アドレスがどのように割り当てられるのか、について説明していきます。
Pod がノードにスケジューリングされ、そのノードで Pod が立ち上がると、VPC CNI プラグインに、 Pod のネットワークのセットアップが依頼されます。
その際、VPC CNI プラグインは、ノードのセカンダリ IP アドレスから、使われていないものを選んで、Pod の IP アドレスとして割り当てます。
これにより、Pod は VPC 内で通信できるようになります。
VPC の IP アドレスというのは、 Kubernetes の外にあるリソースで、AWS の API を経由して管理されるものです。
そのため、Pod の立ち上げ時に、いちいち AWS の API を経由しないように、あらかじめノード起動時にセカンダリ IPアドレスとして、まとまった数を持たせておくという方法が取られています。
このとき、VPC CNI プラグインのデフォルトでは、そのノードの Elastic Network Interface (ENI)に、この図で言うところの eth0 に、付けられるだけ IP を付けるという挙動をします。
このとき、VPC CNI のデフォルトでは、Pod に割り当てられていない IP アドレスを ENI 1個分確保するという設定になっています。
DaemonSet などにより Pod が立ち上がると、IP アドレスが Pod にアサインされていき、
ENI 1個分の IP アドレスを確保するために、新しい ENI、この図の eth1 がノードに割り当てられます。
このとき、新しい ENI にも セカンダリ IP アドレスが付けられるだけ付けられます。
このような動きとなるため、結果として、ノード起動時は、ENI 2個分の IP アドレスが確保されるということになります。
ここで気になるのが実際のところ、ノードごとにどのくらいの IPアドレスが事前に確保されるかというところだと思います。
これはノードのインスタンスタイプによって異なり、
EC2 のサービス制限により、インスタンスタイプごとに、アタッチできる ENI の数と、 1つの ENI に付けられる IP アドレスの数が変わってきます。
本番用のワーカーノードとして多く使われるであろう、c5 と m5 ファミリーのインスタンスでは、1インスタンスごとに最低でもこの表の数だけ確保されることになります。
例えば、c5.4xlarge だと 1インスタンスあたり 60 個ということになるので、
例えば 10インスタンス立てる場合は 600個の IPアドレスが必要ということになります。
ここまでは IP アドレスが多く使われるという話をしましたが、
逆に t3 のような小さいインスタンスタイプだとノードに付けられる IP が少ないため、スケジューリングできる Pod が低く制限されています。
例えば、t3.small だとスケジューリングできる Pod は 11個までです。
小さいインスタンスを使うときはこの点も注意が必要です。
もう1つは、ターゲットタイプ IP モードというもので、ALB のターゲットグループには各 Pod の IPアドレスが登録され、ALB からのトラフィックは直接 Pod に届くという構成です。
本案件では、この IP モードの構成を採用しています。
ここで問題に戻りますと、発生した問題はローリングアップデートを行うと、HTTP の Bad gateway エラーが多発したというものでした。
この Bad gateway エラーというのは、ALB から見て、ターゲット、この構成では Pod が、予期しないレスポンスを返したときに発生します。
よくあるケースとしては3つあり、1つ目はALB がターゲットに接続しようとするとポートが閉じられていた場合です。
これは既に Pod が終了しているにも関わらず、ALB がリクエストを送った場合などに発生します。
2つ目は HTTP のキープアライブを利用している場合で、キープアライブタイムアウトの設定が ALB側と Pod 側で異なるときに起きます。
3つ目は、ALB から Pod を削除した際に、実行時間の長いリクエストを処理していた場合です。
今回のケースでは、
ローリングアップデート時に必ず起きていて、長期実行リクエストはなかったので、
どうやらターゲットグループから Pod が削除される前に、 Pod が終了している可能性が高いということが分かりました。
この問題は有名な話なので、ご存知の方も多いと思いますが、
Pod の終了処理と Service のエンドポイントから Pod が削除される処理は、それぞれ非同期で行われるので、先に Pod が終了してしまうということが起きるようです。
この図では、4つのコンポーネントの内、一番上が Pod の終了処理、残りの3つが ALB から Pod を削除する処理を表しています。
Pod 自体の終了処理は、 preStop フックがあればそれを実行し、その後 STOPSIGNAL を送って、各コンテナが終了するという流れです。
一方で、ALB から Pod を削除する処理は、まず Kubernetes のエンドポイントコントローラーが Pod の削除開始を検知すると、エンドポイントリソースから Pod が削除します。
次に ALB Ingress Controller はエンドポイントの変更を検知すると、ターゲットグループから Pod を登録解除します。
通常はこのタイミングで ALB からのトラフィックが Pod に流れなくなります。
各処理は非同期なので、ALB から削除される前に Pod が終了してしまえば、そのときに Pod にルーティングされていたリクエストは Bad gateway のエラーになってしまうということでした。