powered by TechFeed
表示モード
Deep Dive

AIエージェントをKubernetesで本番運用する前に直すべき「6つのミス」— ループ失敗でノードが落ちる前に

9月9日、Tigeraが「AI Agents on Kubernetes 101: From Laptop Script to Production Pod」と題した記事を公開した。ラップトップ上のPythonスクリプトとして動くAIエージェントをKubernetesクラスタ上の本番Podとして動作させるまでの手順を解説したものだ。記事のキモは末尾にまとめられた「初回デプロイが犯す6つのミス」にある。Step 1〜4(コンテナ化・Secret・Deployment・Service)は「通常のKubernetes入門でも扱う内容」として簡潔に示し、エージェント固有の脅威に直結するStep 5(Egress制限)に紙幅の大半を割く構成になっている。ローカルのkindクラスタで通しても所要時間は約1時間とされている。

9月9日、Tigeraが「AI Agents on Kubernetes 101: From Laptop Script to Production Pod」と題した記事を公開した。ラップトップ上のPythonスクリプトとして動くAIエージェントをKubernetesクラスタ上の本番Podとして動作させるまでの手順を解説したものだ。記事のキモは末尾にまとめられた「初回デプロイが犯す6つのミス」にある。Step 1〜4(コンテナ化・Secret・Deployment・Service)は「通常のKubernetes入門でも扱う内容」として簡潔に示し、エージェント固有の脅威に直結するStep 5(Egress制限)に紙幅の大半を割く構成になっている。ローカルのkindクラスタで通しても所要時間は約1時間とされている。


「とりあえず動く」から「コードレビューに耐える」へ

AIエージェントは大抵こういう経緯で産まれる。誰かがPythonスクリプトを書き、.envファイルにAPIキーを置き、LLMを呼ぶwhileループでデモを成功させる。そこへ予算を持った誰かが「本番に載せよう」と言い出す。

この記事はその「載せる」プロセスを1ステップずつ丁寧に解説したものだ。

エージェントをKubernetesで動かすことの本質的な価値は、ランタイムの挙動を制御する層がエージェントの外側に置けることにある。通常のWebサービスはコードを読めば何をするかわかる。AIエージェントの次の行動はモデルが実行時に決定するため、コードを読んでも予測できない。だからこそ、ルールはエージェントの外に置く必要がある。


5ステップで何をするか

記事が示す手順は以下の5つだ。

  1. コンテナ化
  2. Secretへのキー格納
  3. Deploymentの記述
  4. Serviceによる公開
  5. NetworkPolicyによるEgress制限

チュートリアルの多くはStep 4で止まる。記事は明示的に「そこで止まるな」と述べている。Step 1〜4はKubernetes一般のベストプラクティスとして簡潔に扱い、エージェント固有のリスクに直結するStep 5に最も重点を置く構成だ。


一番刺さるのはStep 5:エグレス制限

Webサービスなら「雑然としている程度」で済むネットワーク設定が、エージェントでは攻撃面そのものになる。

プロンプトインジェクション攻撃の典型的な末尾は「データの外部送信」だ。エージェントが悪意あるテキストを読み込み、それを命令として解釈し、本来呼ぶべきでないエンドポイントにデータを送り出す。Egress制限なしの状態は、この経路を全開にしたままにすることを意味する。

記事が示すNetworkPolicyは以下のとおりだ。DNS(UDP/TCP 53)とアウトバウンドHTTPS(TCP 443)のみを許可し、それ以外を遮断する。

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: support-agent-egress
  namespace: agents
spec:
  podSelector:
    matchLabels:
      app: support-agent
  policyTypes: ["Egress"]
  egress:
    - to:
        - namespaceSelector: {}
      ports:
        - protocol: UDP
          port: 53
        - protocol: TCP
          port: 53
    - ports:
        - protocol: TCP
          port: 443

DNS許可のブロック(to: - namespaceSelector: {})は全namespaceのPodへのDNSトラフィックを許可する記述だ。実運用ではkube-systemnamespaceのkube-dnsPodだけに絞る方が望ましいケースもあるが、記事はまず「101」として動作するポリシーを示している。

このYAMLには意図的に残された穴がある。「TCP 443 to anywhere」はHTTPSを話す全エンドポイントへの接続を許すため、攻撃者のサーバーも含まれる。標準のNetworkPolicyはIPとポートしか見られず、「api.anthropic.comだけを許可する」といった名前ベースの制御はできない。それを実現するにはDNS対応ポリシーエンジン(Calicoが対応)やEgressゲートウェイが必要になる。記事はここを「101の終わり」と位置付けている。

なお、kindのデフォルトネットワークはNetworkPolicyを実装していないため、ローカルで試す場合はCalicoのインストールが必要だ。


最重要:Deploymentに仕込む6つの判断

Deploymentマニフェストの各フィールドには意図がある。記事が特に強調する点を整理する。

automountServiceAccountToken: false
デフォルトでは全Podにk8s APIへのトークンがマウントされる。エージェントがk8s APIと通信する必要はないので切る。これを怠ると、プロンプトインジェクションがクラスタのコントロールプレーンへの足がかりを得る経路になる。「チャットログが盗まれた」で済む話が「クラスタへの侵入口を取られた」になる。

リソースリミット
エージェントはループで失敗する。モデルが無限リトライを決断したり、巨大ドキュメントを一括でメモリに読み込もうとすると、制限がなければノードを食い尽くす。limitsはその「ノードがダウン」を「Podがスロットルされた」に変換する安全弁だ。

APIキーはイメージに含めない
イメージはレジストリにpushされ、ノードにキャッシュされ、アクセス権のある人間が誰でも取得できる。イメージに焼き込んだキーはすでに漏洩している、ただし誰にかはまだわからない、という状態だ。


「初回デプロイが犯す6つのミス」

記事は末尾にミスのまとめ表を置いている。タイトルにある「6つのミス」とはこの表を指す。

ミス エージェントで特に問題になる理由 代わりに
APIキーをイメージに焼き込む キーはエージェントの権限そのもの KubernetesのSecretで実行時注入
リソースリミットなし ループ失敗でノードを枯渇させる requestsとlimitsを設定
latestタグ 何が動いているか説明できず、ロールバックもできない バージョンタグ(ideally digest)
ヘルスプローブなし 詰まったエージェントが正常に見え、仕事を受け続ける readinessProbe + livenessProbe
ServiceAccountトークンのマウント 注入されたエージェントがk8s APIへのパスを持つ automountServiceAccountToken: false
Egress無制限 プロンプトインジェクションがデータ持ち出しに使える デフォルト拒否NetworkPolicy+許可リスト

中央の列が重要だ。どれもエージェント固有の問題ではなく、通常のKubernetesの衛生管理だ。エージェントはそれを怠ったときのコストを引き上げるだけである。


101の先にある未解決の問題

記事はこのデプロイが本番品質に達していない点も正直に列挙する。

  • エージェントのID:Podにはラベルがあるが、暗号的な証明がない。「このエージェントとしての権限」を付与する仕組みがない。
  • 何を許可するか:NetworkPolicyは「HTTPS、どこかへ」としか言えない。「このエージェントは課金サーバーのdelete_recordsを呼んでいいか」はどのレイヤーも判断できない。
  • 何をしたか:Podログはエージェント自身が書くものだ。エージェントの自己申告は監査証跡ではない。

エージェントが2つになれば、それらが互いに通信し始め(A2Aプロトコル)、問題はペア数だけ増える。


詳細はAI Agents on Kubernetes 101: From Laptop Script to Production Podを参照していただきたい。