MCP tunnelsをHelmでデプロイする手順 — 通信制限とOIDCトークン供給
HelmチャートでMCP tunnelsをKubernetesにデプロイし、NetworkPolicyでの通信制限とOIDCトークンの差し替えまで、チャート2.0.2の設定キー単位でまとめます。
HelmチャートはKubernetes上に何を作るのか
Anthropicが配布するHelmチャートは、MCP tunnelsのトンネルスタック(cloudflaredとプロキシの2コンテナ)を単一のDeploymentとしてKubernetesクラスタにインストールします。接続先は、チャートのsetupフックが作る新規トンネルか、Consoleで先に作った既存トンネルのどちらかです。作成手順はMCP tunnelsをConsoleで作成・管理する手順で扱っています。
チャートのバージョンは2.0.2です。バージョンは変わり得るので、実行前にhelm show valuesで最新のデフォルト値を取得してから進めます。
| 観点 | Helm(本記事) | Docker Compose |
|---|---|---|
| 想定環境 | Helm(本記事)既にKubernetesクラスタを運用中 | Docker Compose単一ホスト・小規模構成 |
| 証明書更新 | Helm(本記事)CronJobで自動化可能 | Docker Compose手動での定期更新 |
| スケール | Helm(本記事)Deploymentのレプリカ数で調整 | Docker Composeホスト単位でしか増やせない |
| 導入の手数 | Helm(本記事)values.yaml設計とfederation rule設定が必要 | Docker Compose少ない |
単一ホストでの検証や小規模構成なら、MCP tunnelsをDocker Composeでデプロイする手順のほうが手数は少なくて済みます。
事前に必要なもの
- トンネル。プログラム的アクセスではチャートのsetupフックが未指定なら新規作成し、指定すればConsoleで作った既存トンネルに接続します。手動フローでは必ずConsoleで先に作成し、トンネルトークンとドメインを控えます
- Tunnels APIへの認証手段。プログラム的アクセスなら
workspace:manage_tunnelsスコープのfederation rule、手動ならトンネルトークンとCA証明書 helmとkubectlが使えるKubernetesクラスタ。手動フローではopenssl(1.1.1以降)も使います- アウトバウンド通信。
api.anthropic.comへの443/TCPと、トンネルエッジへの7844/TCP・UDP - 到達可能なMCPサーバーが1つ以上。無ければFastMCPのサンプルDeploymentで代用できます
- Zero Data RetentionやHIPAA BAAの対象範囲の確認。研究プレビュー段階の機能は対象可否が個別に決まるため、規制対象データを扱う前に自組織の要件と突き合わせます
手元にMCPサーバーが無いときのテスト用サンプル
到達可能なMCPサーバーをまだ持っていない場合、helloツールが1つあるだけのFastMCPサンプルをDeploymentとして立てられます。
kubectl create namespace mcp-tunnel --dry-run=client -o yaml | kubectl apply -f -
kubectl -n mcp-tunnel apply -f - <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata: { name: hello-mcp }
spec:
replicas: 1
selector: { matchLabels: { app: hello-mcp } }
template:
metadata: { labels: { app: hello-mcp } }
spec:
containers:
- name: hello-mcp
image: python:3.13-slim
command: ["sh", "-c", "pip install --quiet mcp && python /app/hello_server.py"]
---
apiVersion: v1
kind: Service
metadata: { name: hello-mcp }
spec:
selector: { app: hello-mcp }
ports: [{ port: 9000, targetPort: 9000 }]
EOFこのサンプルを使う場合、gateway.config.routesはecho: http://hello-mcp:9000にします。以降の検証ではこのechoルートを使います。
プログラム的アクセスでインストールする
まずクラスタのOIDC発行者を登録し、federation ruleを作ります。setupコンポーネントはリリース名から導出される専用のServiceAccountで動くため、リリース名がmcp-tunnel以外ならhelm templateでServiceAccount名を確認してから進めます。
| 項目 | 値 |
|---|---|
| Subject | 値system:serviceaccount:mcp-tunnel:mcp-tunnel-setup |
| Audience | 値api.anthropic.com(スキームなし) |
| Scope | 値workspace:manage_tunnels |
デフォルト値を取得し、federation ruleのIDとルート先を設定します。
helm show values \
oci://us-docker.pkg.dev/anthropic-public-registry/charts/mcp-tunnel \
--version 2.0.2 > values.yamlvalues.yaml(プログラム的アクセスで編集する主要キー)
api:
wif:
federationRuleId: "fdrl_..."
organizationId: "00000000-0000-0000-0000-000000000000"
# workspaceId: "wrkspc_..." # 非デフォルトワークスペースのときだけ設定
tunnel:
id: "" # 空なら新規作成。既存トンネルに繋ぐならtnl_...を指定
tokenVersion: "1" # ローテーション時にこの値を上げる
gateway:
config:
routes:
docs: http://docs-mcp.internal:8080
search: http://search-mcp.internal:8080レンダリング結果を確認してからインストールします。
helm template mcp-tunnel \
oci://us-docker.pkg.dev/anthropic-public-registry/charts/mcp-tunnel \
--version 2.0.2 -n mcp-tunnel -f values.yaml > rendered.yaml
helm install mcp-tunnel \
oci://us-docker.pkg.dev/anthropic-public-registry/charts/mcp-tunnel \
--version 2.0.2 \
--namespace mcp-tunnel --create-namespace \
-f values.yamlsetupコンポーネントはHelmのpre-installフックJobとして動くため、helm installはこのJobが完了するまでブロックします。成功するとHelmがJobを自動で削除します。tunnel.idを空にした場合、作成されたトンネルのIDとドメインはmcp-tunnelという名前のSecretに保存され、次のコマンドで読み出せます。
kubectl -n mcp-tunnel get secret mcp-tunnel \
-o jsonpath='{.data.tunnel-domain}' | base64 -dapi.wif.*の値は識別子であって秘密情報ではないため、Helmのリリース履歴Secretに残ってもリスクにはなりません。実際に機微なのは、setupコンポーネントが作るmcp-tunnel Secret(トンネルトークンとTLS秘密鍵を保持)のほうです。
手動フローでインストールする
setup.enabled: falseにすると、チャートはAPI呼び出しを一切行わず、setupコンポーネントも証明書更新のCronJobも動きません。Workload Identity Federationを組みたくない場合はこちらを使います。
ConsoleでトンネルとCA証明書を用意した後、チャートが読む2つのSecretを作ります。Secret名は変更できますが、中のキー名は固定です。
kubectl create namespace mcp-tunnel --dry-run=client -o yaml | kubectl apply -f -
kubectl -n mcp-tunnel create secret generic mcp-tunnel-token \
--from-literal=tunnel-token='eyJ...'
kubectl -n mcp-tunnel create secret generic mcp-tunnel-cert \
--from-file=tls.crt=data/tls.crt --from-file=tls.key=data/tls.keyvalues.yamlでは以下を設定します。
setup.enabled: falseexternal.tunnelTokenSecretNameexternal.serverCertSecretNamegateway.config.tunnel_domain(setup有効時はSecretから自動注入されるが、無効時は明示が必須)
この先のhelm templateとhelm installはプログラム的アクセスと同じコマンドです。
NetworkPolicyで外向き通信(egress)を絞る(任意設定)
プロキシPodへの受信通信は既定で遮断されます(networkPolicy.ingress.enabledの既定値がtrue)。さらにPodからの送信も絞るなら、networkPolicy.egress.enabled: trueにし、networkPolicy.egress.mcpServersにPodラベルセレクタかCIDRを列挙して、アップストリームのMCPサーバー以外への通信を遮断します。cloudflaredからトンネルエッジへの送信は別枠のnetworkPolicy.egress.cloudflaredEgressCIDRsで許可されるので、この2つは独立して設定します。
NetworkPolicyはネットワーク経路を絞るだけで、トンネル自体はアップストリームのMCPサーバーを認証しません。各MCPサーバー側にOAuthかbearer認証を個別に設定する必要があり、仕組みはリモートMCPのOAuth認証で解説しています。
自前のOIDCトークンを供給する(任意設定)
チャートは既定でKubernetesのServiceAccountトークンをsetupコンポーネントに投影します。SPIFFEやVault、クラウドSDKのサイドカーなど別のIDプロバイダのトークンを使いたい場合は、setup.extraVolumesとsetup.extraVolumeMountsでトークンファイルをマウントし、api.wif.tokenFileにそのマウントパスを指定します。チャートはANTHROPIC_IDENTITY_TOKEN_FILE環境変数をそのパスに設定し、setupコンポーネントはそこからトークンを読みます。
トークンをローテーションする
プログラム的アクセスでは、values.yamlのtunnel.tokenVersionを上げてから--set setup.force=trueを付けてアップグレードします。setupコンポーネントは、フォースしない限りアップグレード時には再実行されません。
helm upgrade mcp-tunnel \
oci://us-docker.pkg.dev/anthropic-public-registry/charts/mcp-tunnel \
--version 2.0.2 -n mcp-tunnel \
-f values.yaml --set setup.force=true手動フローでは、ConsoleでRotate tokenを押した後、mcp-tunnel-token Secretを更新してDeploymentをロールアウトし直します。
証明書を更新する
プログラム的アクセスでは、リリース名にちなんで命名されたCronJob(サフィックス-cert-renew)が既定で毎日UTC 0時にsetup renew-certを実行します。有効期限までserverCert.renewBefore(既定30日)を切っていない限りno-opです。更新はローカルで完結し、Secretに保存済みのCAで新しい証明書に署名するだけなのでAPI呼び出しは発生せず、プロキシはSecretのマウントを自動でホットリロードします。
手動フローにはCronJobが無いため、既存のCAで証明書を作り直し、Secretを更新する作業を利用者側で定期的に行います。
チャート1.xからアップグレードする
チャート2.0.0で、トンネルIDの置き場所がapi.wif.tunnelIdからtunnel.idに変わりました。アップグレード前にvalues.yamlを編集し、tnl_...の値をtunnel.idへ移してapi.wif.tunnelIdを削除します。federation ruleのスコープも、Console側でorg:manage_tunnelsからworkspace:manage_tunnelsへ更新が必要です。
よくあるつまずき
- AudienceのURIをそのままコピーする。Console画面が提案する
https://api.anthropic.comをfederation ruleにそのまま設定すると、チャートの既定値api.anthropic.comとスキームの有無が一致せず認証が失敗します --reuse-valuesに頼る。Helmのdeep-merge挙動は、削除したはずのルートを暗黙に残すことがあります。ルート変更時はvalues.yaml全体を管理し、--reuse-valuesは使いませんsetup.force=trueを付け忘れてトークンをローテーションする。tokenVersionを上げただけではsetupコンポーネントは再実行されません- Service CIDRが標準外のマネージドKubernetesで
routesが届かない。クラスタ内Serviceを指す場合はgateway.config.upstream.allowed_ipsに自クラスタのCIDRを追加します - NetworkPolicyの送信許可を1本にまとめる。MCPサーバーへの許可(
networkPolicy.egress.mcpServers)とトンネルエッジへの許可(cloudflaredEgressCIDRs)は別々の設定項目です
まとめ
Helmでのデプロイは、Workload Identity FederationのAudience一致とNetworkPolicyの2軸を最初に押さえれば、あとはDocker Composeと同じ考え方で運用できます。トークンローテーションはプログラム的アクセスならtokenVersionとforceアップグレード、証明書更新は既定で日次CronJobが肩代わりするため、手動フローに比べて運用の手離れがよい設計です。