ゼロから作る『V-Net閉域化』- Azure Functionsを完全プライベート化
本稿は、MCPハンズオンシリーズの第4弾です。前回のAPIM前段化 - Remote MCPをAPIMで公開するの続編として、APIMは公開ゲートウェイのまま、バックエンドのAzure FunctionsをV-Netの中に完全に隠す構成を実現します。
📖 MCPハンズオンシリーズ
このシリーズでは、MCPの基本から実践まで段階的に学習できます:
記事 | 内容 | 形態 |
---|---|---|
🧩 入門ガイド | MCPの概要・エージェンティックWebの基本理解 | 導入編 |
🤖 ローカル編 | Azure Functionsでローカル MCPサーバーを構築 | ハンズオン① |
🌐 リモート編 | Azure FunctionsによるリモートMCPサーバー構築 | ハンズオン② |
🛰 APIM前段化 | APIMによる MCPエンドポイントのAPI管理 | ハンズオン③ |
📍 本記事 | V-Net閉域化による完全プライベート化 | ハンズオン④ |
この記事で得られること
前回のハンズオン③では、Azure Functionsの前段にAPI Management(APIM)を配置し、Functionsのシステムキー(mcp_extension
)をNamed Valuesに秘匿化して自動付与する仕組みを構築しました。これにより、システムキー漏洩リスクや運用ガバナンスは改善できましたが、その時点でもAzure Functionsは依然としてパブリックに公開されており、ネットワーク面の攻撃面積は十分に絞り込めていませんでした。
本記事では、前回のAPIM+Functions構成を踏まえつつ、パブリック経路を閉じてPrivate Linkのみで到達可能にすることで、セキュリティを次の段階へ高めます。APIMのポリシーやMCPクライアント側の設定は最小変更のまま、FunctionsにPrivate Endpointを作成し、Private DNS(privatelink.azurewebsites.net)で名前解決します。これによりFunctionsへの接続は仮想ネットワーク内に限定されます。
APIM側はStandard v2の「Outbound V-Net Integration」を有効にし、V-Net経由でプライベート化されたFunctionsバックエンドへ到達させます。クライアント(例:VS Code Copilot)は引き続きAPIMの公開URLに接続し、APIMがV-Net経由でPrivate EndpointのFunctionsに中継する形です。これによりゼロトラスト/Defense in Depthの考え方に沿って、インターネット面に露出するのはAPIMのみとなり、バックエンドは閉域化されます。
ゴールと全体像
構築する構成は以下の通りです:
重要なポイント:
- APIM Standard v2(またはPremium v2)の Outbound V-Net Integration でV-Net内へ"出ていく"
- Functions側は Private Endpoint(PE) を張り、
privatelink.azurewebsites.net
のPrivate DNSゾーンで解決 - APIMのバックエンドURLは従来どおり
https://<function>.azurewebsites.net/...
のまま(DNSがPEに解決) - APIMポリシー(SSE対応やx-functions-key付与)は変更不要、ネットワークだけ閉じる設計
事前確認
V-Net閉域化を実施する前に、以下の要件を満たしているか確認してください。特にAPIM v2のOutbound V-Net Integrationには明確な前提条件があるため、事前チェックを必ずクリアしてから作業を進めることが重要です。
1. SKU要件
APIMについては Standard v2またはPremium v2(プレビュー版)が必要です。これらのSKUでのみOutbound V-Net Integration機能が提供されています。
2. 配置要件
APIMインスタンスと作成するV-Netは 同一リージョン・同一サブスクリプション に配置する必要があります。リージョンやサブスクリプションが異なる場合は統合できません。
3. Functions要件
Azure Functionsについては Private Endpointに対応したプラン(Flex / Premium / Dedicated等)を使用し、Private DNSゾーン privatelink.azurewebsites.net
が正しく構成されている必要があります。
Step 1. V-Netと2つのサブネットを作成
Portal → 検索 → 仮想ネットワーク → + 作成
1-1. V-Net設定項目
基本設定:
- リソースグループ:APIMを作成したリソースグループ
-
仮想ネットワーク名:
mcp-vnet
(グローバル一意) - リージョン:APIMと同じリージョン推奨(例:Southeast Asia)
セキュリティ:
既定
IPアドレス:
以下のアドレス設計で進めます。
-
V-Net:
10.20.0.0/16
サブネット1: snet-pe(FunctionsのPrivate Endpoint用)
用途:Function AppにPrivate Endpointを張る“受け口”のサブネット。
- サブネットの目的:Default(特別な予約サブネットではない)
-
名前:
snet-pe
-
IPv4 アドレス範囲:
10.20.0.0/24
-
開始アドレス:
10.20.0.0
- プライベート サブネット:オフ
- NAT ゲートウェイ:なし
- ネットワーク セキュリティ グループ:なし(PE用サブネットではNSG/UDRは適用されないため)
- ルート テーブル:なし
- サービス エンドポイント:なし(Private Link を使用するため不要)
- サブネット委任:なし
- プライベート エンドポイントのネットワーク ポリシー:無効(既定のまま・PE配置の要件)
サブネット2: snet-apim-int(APIM Outbound V-Net Integration用)
用途:APIM が VNet 内へ“出ていく” ための専用サブネット。ここに リソースは直接配置しません(APIMの統合先として“予約”するだけ)
- サブネットの目的:Default
-
名前:
snet-apim-int
-
IPv4 アドレス範囲:
10.20.1.0/24
-
開始アドレス:
10.20.1.0
-
サイズ:
/24
(最小/27
、推奨/24
。このサブネットはAPIM専用) -
プライベート サブネット(既定の送信アクセスなし):オフ(推奨)
APIMは統合後、Azure Storage(443) などのパブリック依存先への送信が必要です
- NAT ゲートウェイ:なし(既定の送信アクセスありのため不要)
-
ネットワーク セキュリティ グループ:なし(後ほど作成・関連付け)
少なくとも
宛先:Service Tag = Storage / 443 / Allow(Outbound)
が必要 - ルート テーブル:なし(既定では不要)
-
サービス エンドポイント:なし
-
サブネット委任:
Microsoft.Web/serverFarms
(必須)v2の"Outbound VNet Integration"は委任付きの専用サブネットが要件
- プライベート エンドポイントのネットワーク ポリシー:既定(このサブネットにPEは配置しません)
2つのサブネットが作成させていることを確認後、レビューと作成
に移り、作成
。
1-2. NSGを作成しsnet-apim-intに関連付け
Portal → 検索 → ネットワーク セキュリティ グループ → +作成
基本設定:
- リソースグループ:V-Netを作成したリソースグループ
- リソースグループ:V-Netを作成したリソースグループ
-
名前:
mcp-nsg
- リージョン:V-Netと同じリージョン
作成完了後、NSGリソースを開いて送信ルールを追加します。
送信セキュリティ規則の追加
NSG画面で [設定 > 送信セキュリティ規則] → [+追加]
設定項目:
-
ソース:
Service Tag
-
ソースサービスタグ:
VirtualNetWork
-
ソースポート範囲:
*
-
宛先:
Service Tag
-
宛先サービスタグ:
Storage
-
サービス:
Custom
-
宛先ポート範囲:
443
- プロトコル:TCP
- アクション:許可
- 優先度:100
-
名前:
allow-out-storage-443
サブネットへの関連付け
NSG画面で [設定 > サブネット] → [+関連付け]
-
仮想ネットワーク:作成したV-Net(
mcp-vnet
) -
サブネット:
snet-apim-int
これでAPIMがAzure Storageなどの依存サービスに送信通信できるようになります。
Step 2. Function AppにPrivate Endpointを追加
Azure Portalから以下の手順でPrivate Endpointを設定します
2-1. Private Endpointの作成
- Azure Portal → Azure Functions → ネットワーク → プライベート エンドポイント → +追加 (簡易)
- 設定項目:
-
名前:
pe-func
(任意) -
仮想ネットワーク:作成したV-Net(
mcp-vnet
) -
V-Net/サブネット:
snet-pe
- Private DNS統合:はい
2-2. Private DNSの自動設定
Private DNS統合を有効化すると、以下が自動で設定されます
-
privatelink.azurewebsites.net
のPrivate DNSゾーンにAレコードが自動登録 - V-Netにリンクされます(Zone Group)
以降、APIMからhttps://<app>.azurewebsites.net
にアクセスしても、VNet内のDNSが <app>.privatelink.azurewebsites.net
(PEのプライベートIP)へ解決して到達します。
Step 3. APIM(Standard v2)でOutbound V-Net Integrationを有効化
3-1. V-Net統合の設定
- Azure Portal → API Management → ネットワーク
- Outbound features: virtual network integration を選択
- 設定項目:
-
Virtual network:Step1で作成したV-Net(
mcp-vnet
) -
サブネット:
snet-apim-int
3-2. 統合後の状態
インテグレーション後もAPIMのゲートウェイは パブリックのまま です。バックエンドに対してはV-Net経由でプライベート到達します。
Step 4. APIMのバックエンドURLはそのまま
4-1. URL設定
バックエンドのURLは これまで通り で変更不要です:
https://<function>.azurewebsites.net/...
4-2. DNS解決の仕組み
V-Net内DNSがPrivate EndpointのAレコードへ自動解決してくれるため、APIMポリシー(SSE向け)は前回のままでOKです。
Step 5. 完全クローズドにする
最後にAzure Functions側の公開アクセスを無効化すれば、Private Endpoint経由以外の受信を拒否できます。
- Azure Portal → Azure Function → ネットワーク
- 公衆ネットワーク アクセス: 無効 に設定
Step 6. 動作確認
V-Net統合とPrivate Endpoint設定後、VS CodeからAPIM経由でMCPサーバーに接続できることを確認します。
6-1. VS Codeの設定
.vscode/mcp.json
の設定は前回と同じです:
{
"servers": {
"remote-mcp-via-apim": {
"type": "sse",
"url": "https://<apim-name>.azure-api.net/mcp/runtime/webhooks/mcp/sse"
}
}
}
設定値:
-
<apim-name>
:APIMの既定ドメイン - APIMリソース概要のゲートウェイのURLで確認できます
6-2. 接続テスト
Copilot Agentチャット画面で以下をテスト:
テスト手順:
-
reverse_text
関数を呼び出し -
text: "いろはにほへと ちりぬるを"
を渡す -
"をるぬりち とへほにはろい"
が返ってくれば成功!
まとめ
本記事では、前回のAPIM前段化に続いて、Azure Functionsで構築したMCPサーバーの完全閉域化を実現する方法を学びました。
仮想ネットワーク(V-Net)に2つの専用サブネット(snet-pe
、snet-apim-int
)を構築し、Azure FunctionsにPrivate Endpointを配置することで、インターネットからの直接アクセスを完全に遮断しました。Private DNS(privatelink.azurewebsites.net
)による内部名前解決により、APIMからFunctionsへのアクセスは従来のURL(https://<function>.azurewebsites.net
)のまま、DNS解決だけがプライベートIPアドレスに向くよう設計しました。
APIM Standard v2のOutbound V-Net Integrationを活用することで、APIMゲートウェイ自体はパブリックのままクライアント接続を受けつつ、バックエンドへの通信はV-Net経由で行う混合構成を実現しました。NSG(Network Security Group)によりAPIMサブネットからAzure Storageへの送信通信を許可し、委任サブネット(Microsoft.Web/serverFarms
)の要件も満たした運用可能なアーキテクチャを構築しました。
このV-Net閉域化により、前回のAPI管理機能(システムキー秘匿、SSEストリーミング最適化、監査ログ)を維持しながら、さらなるセキュリティ強化を達成しました。MCPサーバーは完全にプライベートネットワーク内に隠蔽され、Defense in Depthの原則に従ってインターネットの攻撃面積を最小化できました。クライアントからの接続方式は変わらず、運用面でのセキュリティと可観測性を両立した実用的なアーキテクトとなりました。
本シリーズを通じて、MCPの基礎からV-Net構成の実装まで段階的に学習できました。皆さんのMCPプロジェクトの参考になれば幸いです!
連載ナビ
- Part 1:『エージェンティックWeb』と『MCP入門』
- Part 2: ゼロから作る『ローカルMCP』
- Part 3: ゼロから作る『リモートMCP』
- Part 4:ゼロから作る『APIM構築』
- part 5: ゼロから作る『V-Net閉域化』
Discussion