でこてっくろぐ ねお

UbieのSRE。でこらいふろぐ(http://dekolife.hatenablog.com/)の姉妹版。デコテックログ(deko tech log)である

Kubernetes Jobでサイドカーを動かす際の問題と解決策

UbieでSREをしているdekokunです。この記事は Ubie Engineering Advent Calendar 2023 の 21 日目の記事です。

導入

Kubernetes (k8s) のワンショットのJobにサイドカーを組み合わせると、様々な問題が生じることがあります。Ubie社には、複数の言語で書かれた多数のマイクロサービスが存在します。そしてご多分にもれず、複数マイクロサービスの共通課題をインフラレイヤで管理するためにIstioやCloud SQL Proxy、AlloyDB Auth Proxyなどを使用しています。 この記事では、それらサイドカーをJobと共に使用する際に直面する問題と、世の中でよく語られている解決策、および条件付きながら私たちがどのようにこれらの問題を解決しているのかについて解説します。

k8s Job + サイドカーで発生する問題

かつての私は"Job以外でサイドカーを使えてるしJobでも簡単に使えるだろう"と思っていましたがそんなに甘くはありませんでした。 k8s Job + サイドカーを使うと以下のような問題が発生します*1

問題1: コンテナの起動順序が不定

サイドカーコンテナが起動する前に、メインコンテナの処理が実行され、失敗するケースがあります。例えば、メインコンテナがDB接続を必要とするのに、DB接続に必要なCloud SQL Proxyが起動していないために接続できない場合などです。これはjobじゃない場合でも似たようなことは発生しますが、その場合はreadiness probeなどを適切に設定することで回避することができます。

もちろん、アプリケーション側で"DB接続に失敗した場合は再試行を続ける"や"起動時に数秒待つ"などの制御も可能ですが、多数のマイクロサービスが存在する状況ではできれば統一的に解決したいものです。

問題2: サイドカーコンテナが終了しない問題

メインコンテナの処理が完了した後もサイドカーコンテナは稼働し続けるため、本来メインコンテナの処理完了と同時に終了してほしいJobが終了しない問題もあります。これは、リソースの無駄遣いや管理の複雑化を引き起こします。

世の中で一般的に語られている解決策

以下が一般的な解決策です。コンテナイメージにshellさえあればあとはmanifestを修正することでパッとできるのが嬉しいですが、逆にいうと、shellが存在しない場合を想定した場合は、何らかのツールを作った上でmanifest上でもいくつか変更しないといけない点があり、また一見して何をしているのかわかりづらいmanifestになるため、少し複雑に感じられます。

  • 起動順の問題: Container hooksのlifecycle.postStartを定義すると、この処理が完了するまで後続のコンテナの起動がブロックされるため、コンテナの起動順を制御できます。
  • 終了しない問題: プロセス空間を共有してメインコンテナ内でサイドカーを終了させる、またはファイルシステムを共有してメインコンテナが終了したことをファイルに記録し、サイドカー側が定期的にそれを読みに行き終了する
  • Kubernetes 1.28から入ったSidecar Container機能: この機能により、起動順と終了しない問題が解決されるようです。また、今後さらに複雑な終了順序の制御が可能になる見込みとのことです。
    • 参照: Kubernetes Blog
    • これが現在の一番の本命解決策だと思っています。12/14にリリースされたk8s 1.29でベータに昇格されましたので、待ち焦がれています。

Ubieにおける条件付きの解決策

Ubieでは、以下のような自作ツールを活用することで、manifest上では"command"欄を書き換えるだけで問題への対応が完結するアプローチをとっています。

  • Jobコマンド実行前にサイドカーのヘルスチェック用のendpointを定期的に叩きサイドカーが準備完了になるのを待つ
  • Jobコマンドを実行する
  • Jobコマンド完了後、サイドカーに存在する自分自身をシャットダウンさせるエンドポイントを叩き、サイドカーを終了させる
    • istioやcloud-sql-proxy, alloydb-auth-proxyには"/quitquitquit"という、自分自身をシャットダウンさせるエンドポイントが存在するのでそれを叩くだけで終了してくれます

上記を読んで分かる通り、このアプローチは、サイドカーに自分自身をシャットダウンさせるエンドポイントがある場合(そしてもちろんヘルスチェック用のendpointが存在する場合)にのみ適用可能です

istioの場合の実行イメージ図。ツール名は"jobtio"

例えば "sleep 1" をするだけの単純なJobの場合は、"jobtio sleep 1"のように書き換えるだけで上記の動作になります。

なお、 Istioに関しては、基本的にネットワークに関するものであるので起動順の問題に対しては"Global Mesh Options"のholdApplicationUntilProxyStartsを設定することで最初に起動するようにしていますが、終了しない問題についてはやはり同様に上記記載のいずれかの方法での対処が必要です。

現在の課題と今後の展望

現在、istio/db系のサイドカー用にそれぞれのツールが存在する状態です。複数のサイドカーを同時に走らせたい場合を考えると、それらのツールを統合して複数のサイドカーを管理しつつ、起動順と終了順の依存関係を解決するツールを開発したい、というのが今後の課題としてあります。

また、基本的にjobとサイドカーを使うすべてのコンテナにこのツールをばら撒かなくてはいけないため、配布方法も考える必要があります。現在は素朴にコンテナイメージに同梱していますが、例えばInitContainerでツールをコピーするなど、manifestの変更で配布が完結する方法も考えられます。ただ、元からのイメージに存在しない実行ファイルが実行されるというのは本来セキュリティの観点からは防ぎたいところではあるので悩ましいなと感じているところです。

*1:この問題を初めて知ったのは、前職時代、以下ページを読んでです。ありがとう id:pokutuna さん: k8s Job + Sidecar の悲しみ - pokutuna