Cilium + Kubernetes で AI エージェント用 Pod の外部通信を制御する構成を作った
1. 背景
以前、Docker コンテナの外向き通信制御について調査し、最終的には DOCKER-USER + ipset による IP allowlist 制御へ到達した。
この方式では、Docker の NAT / MASQUERADE は Docker に任せ、自前の外向き通信制御は DOCKER-USER chain と ipset で行う。
一方で、以下の課題が残っていた。
- 許可対象は本来ドメインで管理したい
- DNS 解決結果の IP を管理する必要がある
- CDN や共有 IP を利用するサービスとの相性が悪い
- IP ベース制御では、許可した IP 上の別ドメインへ到達できる可能性がある
- AI エージェントに作業させる場合、外部通信先や内部リソースへの到達範囲をより明示的に制御したい
AI エージェントを開発環境内で動かす場合、以下のような動作が発生する。
- 外部 API へ通信する
- Git リポジトリを読む
- npm / PyPI / Go module などの package registry へアクセスする
- ソースコードを書き換える
- テストや静的解析を実行する
そのため、
どの Pod が
どの内部サービスへ
どの外部ドメインへ
通信できるのか
を明示的に管理する必要がある。
今回は、Docker 単体の DOCKER-USER + ipset 方式から、Kubernetes + Cilium を利用した構成へ変更した。
本記事は、現時点で作成した構成と、その構成がこれまでの課題にどう対応しているかを整理するものである。
2. やりたいこと
今回の構成で扱う対象は以下。
- AI / Codex 用の作業 Pod を Kubernetes 上に用意する
- Pod から Kubernetes API や Docker socket へ直接触れないようにする
- 外部通信は原則拒否する
- 必要な OpenAI / Codex 系ドメインだけ FQDN ベースで許可する
- npm / PyPI / Go module は直接 public registry へ行かせず、mirror / approver 経由にする
- 内部 Git サーバーや mirror など、必要な Service だけ通信を許可する
- NetworkPolicy / CiliumNetworkPolicy を Infrastructure as Code として管理する
- 許可通信 / 拒否通信を検証スクリプトで確認できるようにする
3. これまでの課題と今回の対応
以前の DOCKER-USER + ipset 構成では、許可したいドメインを DNS 解決し、その結果の IP を ipset に登録して制御していた。
今回の構成では、以下のように対応した。
| 課題 | 今回の対応 |
|---|---|
| ドメイン単位で制御したい | Cilium の toFQDNs を利用 |
| 未許可通信を拒否したい | namespace 全体に default deny を適用 |
| 任意の DNS 解決を許可したくない | Cilium DNS rule で許可 DNS 名を制限 |
| package registry へ直接アクセスさせたくない | npm / PyPI / Go module を mirror / approver 経由に変更 |
| AI / Codex 用 Pod から Kubernetes API を触らせたくない | ServiceAccount token を mount しない |
| Docker socket や hostPath を触らせたくない | Pod に Docker socket / hostPath / kubeconfig を渡さない |
| 設定ミスを早めに検知したい | kubeconform / kube-score / kube-linter を利用 |
| 許可 / 拒否通信を確認したい | verify-network.sh で検証 |
4. Cilium FQDN Policy
Cilium には FQDN ベースの Egress 制御機能がある。
今回の構成では、OpenAI / Codex 系の通信先を toFQDNs で許可している。
例:
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: todo-scheduler-egress
namespace: default
spec:
endpointSelector:
matchLabels:
app: todo-scheduler
egress:
- toFQDNs:
- matchName: api.openai.com
- matchName: auth.openai.com
- matchName: chatgpt.com
- matchName: ab.chatgpt.com
- matchName: platform.openai.com
toPorts:
- ports:
- port: "443"
protocol: TCP
この設定により、対象 Pod から指定 FQDN の TCP 443 への Egress を許可する。
5. 現在の構成
現時点の構成は以下。
flowchart TB
subgraph Host["Host / minikube"]
subgraph K8s["Kubernetes Cluster"]
Deny["Default Deny<br/>NetworkPolicy"]
subgraph Policies["CiliumNetworkPolicy"]
FQDN["FQDN Egress<br/>OpenAI / Codex domains"]
DNS["DNS Rules<br/>allowed names only"]
Internal["Internal Service Egress<br/>Git / mirrors / approvers"]
end
subgraph Workloads["Workloads"]
App["todo-scheduler<br/>Application Pod"]
Codex["infra-codex<br/>AI / Codex workspace Pod"]
end
subgraph InternalServices["Internal Services"]
Git["git-bare<br/>internal Git"]
Npm["npm-mirror<br/>npm-approver"]
PyPI["pypi-mirror<br/>pypi-approver"]
GoMod["go-module-mirror<br/>gomod-approver"]
Mock["mock-server"]
MCP["mcp-playwright"]
end
end
Internet["Internet<br/>allowed FQDN only"]
K8sAPI["Kubernetes API"]
PublicRegistry["Public registries<br/>npm / PyPI / Go"]
end
Deny --> Policies
App --> FQDN
App --> DNS
App --> Internal
Codex --> FQDN
Codex --> DNS
Codex --> Internal
Internal --> Git
Internal --> Npm
Internal --> PyPI
Internal --> GoMod
Internal --> Mock
Internal --> MCP
FQDN --> Internet
Codex -. blocked .-> K8sAPI
Codex -. blocked .-> PublicRegistry
classDef allowed fill:#e6ffed,stroke:#008000,color:#000;
classDef blocked fill:#ffe6e6,stroke:#cc0000,color:#000;
classDef control fill:#e6f0ff,stroke:#005fcc,color:#000;
class FQDN,DNS,Internal,Deny control;
class Internet,Git,Npm,PyPI,GoMod,Mock,MCP allowed;
class K8sAPI,PublicRegistry blocked;
主要な Pod / Service は以下。
| 名称 | 役割 |
|---|---|
todo-scheduler | アプリケーション本体 |
infra-codex | インフラソース編集用の隔離ワークスペース |
git-bare | app.git / infra.git を提供する内部 Git サーバー |
npm-mirror / npm-approver | npm 依存関係取得と承認 |
pypi-mirror / pypi-approver | PyPI 依存関係取得と承認 |
go-module-mirror / gomod-approver | Go module 依存関係取得と承認 |
mock-server | アプリケーション検証用 |
mcp-playwright | Playwright / MCP 関連の検証用 |
infra-codex は infra リポジトリを編集するための Pod として用意している。
infra-codex で可能な操作は以下。
/workspace/infraの編集- 静的検証
- Git 操作
- OpenAI / Codex 系エンドポイントへの通信
- mirror / approver への通信
git-bareへの通信
infra-codex で許可しない操作は以下。
- Kubernetes API への到達
- Docker socket の利用
- hostPath へのアクセス
- public package registry への直接通信
- 任意の外部ドメイン解決
6. infra-codex Pod の隔離
infra-codex Pod では、ServiceAccount token を mount しない。
spec:
automountServiceAccountToken: false
また、以下を持たせない。
ServiceAccount token
kubeconfig
kubectl
minikube
Helm
Docker socket
hostPath
application data
application source mount
Pod の securityContext は以下。
securityContext:
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
container 側の securityContext は以下。
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
7. package registry への直接通信を避ける
今回の構成では、public registry へ直接行かせず、mirror / approver を経由させる。
flowchart LR
Codex["infra-codex Pod"]
subgraph Mirrors["Internal mirrors / approvers"]
NpmMirror["npm-mirror"]
NpmApprover["npm-approver"]
PyPIMirror["pypi-mirror"]
PyPIApprover["pypi-approver"]
GoMirror["go-module-mirror"]
GoApprover["gomod-approver"]
end
Public["Public package registries<br/>npm / PyPI / Go"]
Codex --> NpmMirror
Codex --> NpmApprover
Codex --> PyPIMirror
Codex --> PyPIApprover
Codex --> GoMirror
Codex --> GoApprover
Codex -. blocked .-> Public
classDef allowed fill:#e6ffed,stroke:#008000,color:#000;
classDef blocked fill:#ffe6e6,stroke:#cc0000,color:#000;
class NpmMirror,NpmApprover,PyPIMirror,PyPIApprover,GoMirror,GoApprover allowed;
class Public blocked;
infra-codex Pod では、環境変数で mirror / approver を参照する。
env:
- name: NPM_CONFIG_REGISTRY
value: http://npm-mirror:4873
- name: PIP_INDEX_URL
value: http://pypi-mirror:3141/root/pypi/+simple/
- name: PYPI_APPROVER_URL
value: http://pypi-approver:8080
- name: NPM_APPROVER_URL
value: http://npm-approver:8080
- name: GOPROXY
value: http://go-module-mirror:3000
- name: GOSUMDB
value: "off"
- name: GOMOD_APPROVER_URL
value: http://gomod-approver:8080
8. 検証スクリプト
ネットワーク制御については、verify-network.sh で確認する。
確認項目は以下。
todo-scheduler can read npm mirror
todo-scheduler cannot publish anonymously
unlabelled workload is denied cluster egress
todo-scheduler cannot query arbitrary DNS names
npm mirror cannot reach the public internet
infra-codex can reach git-bare
infra-codex can reach mirrors/approvers
infra-codex cannot resolve public registry directly
infra-codex cannot reach Kubernetes API
9. 静的検証
Kubernetes manifest については、validate.sh で静的検証を実行する。
実行内容は以下。
Shell syntax check
kubeconform
kube-score
kube-linter
git diff --check
kubeconform では Kubernetes schema と Cilium CRD schema を使って検証する。
kube-score では運用上の注意点を確認する。
kube-linter ではセキュリティ寄りの静的解析を行う。
この検証は静的検証であり、以下は対象外としている。
Cilium enforcement
PVC permissions
probes
Service routing
rollout behavior
image execution
10. Docker 単体構成から変わったこと
以前の Docker 単体構成では、主に以下のような制御をしていた。
flowchart LR
App["Docker container"]
Bridge["Docker bridge"]
DU["DOCKER-USER"]
IPSet["ipset<br/>allowed IPs"]
Internet["Internet"]
App --> Bridge
Bridge --> DU
DU --> IPSet
IPSet --> Internet
今回の構成では、以下のように制御する。
flowchart LR
Pod["Pod"]
NP["NetworkPolicy<br/>default deny"]
CNP["CiliumNetworkPolicy"]
FQDN["FQDN / Service / Label based policy"]
Dest["Allowed destinations"]
Pod --> NP
NP --> CNP
CNP --> FQDN
FQDN --> Dest
変更点は以下。
- IP ではなく FQDN ベースの許可をマニフェストで表現する
- Pod label 単位で通信制御する
- 内部 Service への通信と外部 FQDN への通信を分ける
- namespace default deny を前提にする
- Git / package mirror / approver などを Kubernetes Service として定義する
- AI / Codex 用 workspace を Pod として分離する
- 静的検証とネットワーク検証をスクリプト化する
11. 現時点でできていること
現時点でできていることは以下。
- namespace 全体の default deny
- CiliumNetworkPolicy による FQDN ベースの OpenAI / Codex 系通信許可
- Cilium DNS rule による許可 DNS 名の制御
- 内部 Service への明示的な Egress 許可
infra-codexPod の ServiceAccount token 無効化infra-codexPod から Kubernetes API へ到達できないことの検証- Docker socket / kubectl / Helm / minikube を持たない作業 Pod
- npm / PyPI / Go module の mirror / approver 経由化
- public registry への直接 DNS 解決拒否
- unlabelled workload の egress 拒否
- kubeconform / kube-score / kube-linter による静的検証
- 検証スクリプトによる許可通信 / 拒否通信の確認
12. 未対応項目
未対応の項目は以下。
| 項目 | 状況 |
|---|---|
| Hubble による通信可視化 | 未対応 |
| DNS TTL / キャッシュ / IP 変動時の挙動確認 | 未対応 |
| CDN / 共有 IP 利用サービスでの制御精度確認 | 未対応 |
| Kyverno / OPA による独自ポリシー強制 | 未対応 |
| Docker Sandboxes との詳細比較 | 未対応 |
| Kata Containers / gVisor / Firecracker との比較 | 未対応 |
| Credential Proxy | 未対応 |
| rootless Docker / Podman / BuildKit を使った sandbox 内ビルド | 未対応 |
| 本番運用前提の監査ログ設計 | 未対応 |
今後、Docker Sandboxes、Kata Containers、gVisor、Firecracker などの sandbox 構成との比較を行いたい。
また、Cilium と Calico の比較も未対応である。
13. まとめ
以前は、Docker 単体で DOCKER-USER + ipset を使い、外向き通信を IP allowlist で制御していた。
この方式では、ドメイン単位の制御、DNS 解決結果の管理、AI エージェント用 Pod の通信先制御に課題があった。
今回、Kubernetes + Cilium を使い、以下の構成を作成した。
- namespace default deny
- Cilium FQDN Policy
- DNS rule による名前解決制御
- mirror / approver 経由の package 取得
infra-codexPod の隔離- Kubernetes API / Docker socket への到達禁止
- 静的検証
- ネットワーク検証スクリプト
現時点では、Hubble による可視化、DNS TTL / CDN 挙動、Kyverno / OPA によるポリシー強制、Docker Sandboxes や Kata Containers との比較、Credential Proxy などは未対応である。