เชื่อมต่อ AI Agent บน Microsoft Foundry กับ MCP ภายในองค์กร แบบปลอดภัย 100% ไม่ต้องออก Internet!
Author
Admin
Date Published

หลายท่านที่ใช้งาน Microsoft Foundry อาจเคยติดปัญหาว่า ทำไม Agent ถึงเรียกใช้ MCP (Model Context Protocol) Server ที่ใช้ภายในองค์กร หรือ On-Premises ของบริษัทไม่ได้?
ก่อนอื่นเลย...ทีมพยายามหา Solution อื่นๆเช่น
Solution Allowlist IP -> ผลคือช่วยไม่ได้: เพราะ IP ขาออกของ Shared runtime ไม่ได้เป็นของเราคนเดียว การ Allowlist จะเสี่ยงต่อความปลอดภัย และตัว Endpoint ก็ยังเป็น Public อยู่ดี
นั่นเพราะ "ค่าเริ่มต้นของ Agent Runtime อยู่ในระบบของ Microsoft" ทำให้มองไม่เห็น Private IP ภายในองค์กรนั่นเองครับ

💡 ทางออกคือการใช้ "VNet Injection" บทความนี้เราสรุปแนวทางการแก้ปัญหาทางสถาปัตยกรรม (Architecture) ที่ช่วยให้:
-> Inject Agent Runtime เข้ามาอยู่ใน VNet ของเราเองผ่าน Subnet ที่กำหนด
-> เรียก MCP ผ่าน Internal Load Balancer, Private Endpoint หรือเชื่อมต่อไปยังระบบ On-Premises ผ่าน ExpressRoute / VPN ได้โดยตรง
-> ปิด Public Internet Access ของ Foundry ไปได้เลย ปลอดภัยขึ้นอีกระดับ

ขั้นตอนที่เราจะต้องทำ มีลำดับตามนี้ครับ ซึ่งจะเป็นตัวอย่างของ terraform (*.tf) เพราะปัจจุบัน หน้า Azure portal ของ Microsoft ไม่สามารถ provisioning เองแบบ manual จากหน้าจอ (บางขั้นตอนด้านล่าง -> ไม่มีหน้าจอให้เราเข้าไปจัดการ)
Steps คร่าวๆ ของการ provisioning ด้วย terraform
12① VNet3② snet-agent (delegated) ─────────► ③ Foundry account4 snet-pe (networkInjections → snet-agent,5 Private DNS × 3 + links publicNetworkAccess: Disabled)6 │7④ Private Endpoint ◄────────────────────┘ (ต้องรู้ account.id)8 (snet-pe → account) │9 └─────────────────► ⑤ Project (รอ PE เสร็จก่อน)10 ⑥ Capability Host (Agents)11 ⑥ Model deployment (รอแค่ account)
1. สร้าง VNet
เป็น network ส่วนตัว ที่ใช้ช่วง IP ซึ่งทุกอย่างที่อยู่ในนี้คุยกันด้วย -> private IP ได้
1## Virtual network and subnets2##3resource "azurerm_virtual_network" "this" {4 name = "vnet-${var.name_prefix}-${local.suffix}"5 location = var.location6 resource_group_name = azurerm_resource_group.this.name7 address_space = [var.vnet_address_space]8 tags = var.tags9}10
2. สร้าง Subnet "snet-agent" สำหรับ Agent และ Subnet "snet-pe" สำหรับ Private Endpoint
delegation เป็นจุดที่สำคัญที่สุดของไฟล์ คือการบอก Azure ว่ายกห้องนี้ให้ Microsoft.App/environments ใช้ Foundry จึงนำ Agent runtime เข้ามาวางใน subnet นี้ได้ ซึ่งก็คือ VNet Injection
เมื่อ Agent อยู่ในห้องนี้ ตอนเรียก MCP มันจะเรียกจากใน VNet ของเรา จึงเข้าถึง MCP ที่ใช้ private IP ได้
subnet ที่ delegate แล้วจะใช้วาง VM หรืออย่างอื่นปนไม่ได้ เพราะเป็นห้องที่จองไว้ให้ Foundry เท่านั้น
1## Agent subnet delegated to Microsoft.App/environments — this delegation is what2## makes VNet injection for AI Foundry agents possible.3##4resource "azurerm_subnet" "agent" {5 name = "snet-agent"6 resource_group_name = azurerm_resource_group.this.name7 virtual_network_name = azurerm_virtual_network.this.name8 address_prefixes = [var.agent_subnet_prefix]910 delegation {11 name = "Microsoft.App/environments"12 service_delegation {13 name = "Microsoft.App/environments"14 actions = ["Microsoft.Network/virtualNetworks/subnets/join/action"]15 }16 }17}1819## Subnet for the AI Foundry private endpoint20##21resource "azurerm_subnet" "pe" {22 name = "snet-pe"23 resource_group_name = azurerm_resource_group.this.name24 virtual_network_name = azurerm_virtual_network.this.name25 address_prefixes = [var.pe_subnet_prefix]26}
2.5 สร้าง Foundry Account (ที่คลุม Foundry Projects อีกที)
1########## Create AI Foundry resource2##########34## Create the AI Foundry resource with VNet injection5##6resource "azapi_resource" "ai_foundry" {7 depends_on = [8 azurerm_subnet.subnet_agent,9 azapi_resource_action.purge_ai_foundry10 ]1112 type = "Microsoft.CognitiveServices/accounts@2025-06-01"13 name = "aifoundry${random_string.unique.result}"14 parent_id = azurerm_resource_group.rg.id15 location = var.location16 schema_validation_enabled = false1718 body = {19 kind = "AIServices",20 sku = {21 name = "S0"22 }23 identity = {24 type = "SystemAssigned"25 }2627 properties = {28 # Support both Entra ID and API Key authentication29 disableLocalAuth = false3031 # Specifies that this is an AI Foundry resource32 allowProjectManagement = true3334 # Set custom subdomain name for DNS35 customSubDomainName = "aifoundry${random_string.unique.result}"3637 # Network-related controls38 # Disable public access but allow Trusted Azure Services exception39 publicNetworkAccess = "Disabled"40 networkAcls = {41 defaultAction = "Allow"42 }4344 # Enable VNet injection for Agents45 networkInjections = [46 {47 scenario = "agent"48 subnetArmId = azurerm_subnet.subnet_agent.id49 useMicrosoftManagedNetwork = false50 }51 ]52 }53 }54}555657

3 + 4 สร้าง Private DNS Zone 3 zone คือ cognitiveservices, services.ai และ openai โดยแต่ละ zone มี virtual_network_link ผูกเข้ากับ VNet จาก Step 1
ผลคือเมื่อเครื่องใน VNet ค้นชื่อ xxx.services.ai.azure.com จะได้ private IP ของ Private Endpoint กลับมาแทน public IP
ถ้าไม่มี zone เหล่านี้ ชื่อจะ resolve เป็น public IP และการเชื่อมต่อจะล้มเหลว เพราะ Foundry ปิด public access ไว้แล้ว
1## Private DNS zones required to resolve the AI Foundry account privately2##3resource "azurerm_private_dns_zone" "cognitiveservices" {4 name = "privatelink.cognitiveservices.azure.com"5 resource_group_name = azurerm_resource_group.this.name6 tags = var.tags7}89resource "azurerm_private_dns_zone" "ai_services" {10 name = "privatelink.services.ai.azure.com"11 resource_group_name = azurerm_resource_group.this.name12 tags = var.tags13}1415resource "azurerm_private_dns_zone" "openai" {16 name = "privatelink.openai.azure.com"17 resource_group_name = azurerm_resource_group.this.name18 tags = var.tags19}2021resource "azurerm_private_dns_zone_virtual_network_link" "cognitiveservices" {22 name = "link-cogsvc-${local.suffix}"23 resource_group_name = azurerm_resource_group.this.name24 private_dns_zone_name = azurerm_private_dns_zone.cognitiveservices.name25 virtual_network_id = azurerm_virtual_network.this.id26 registration_enabled = false27}2829resource "azurerm_private_dns_zone_virtual_network_link" "ai_services" {30 name = "link-aiservices-${local.suffix}"31 resource_group_name = azurerm_resource_group.this.name32 private_dns_zone_name = azurerm_private_dns_zone.ai_services.name33 virtual_network_id = azurerm_virtual_network.this.id34 registration_enabled = false35}3637resource "azurerm_private_dns_zone_virtual_network_link" "openai" {38 name = "link-openai-${local.suffix}"39 resource_group_name = azurerm_resource_group.this.name40 private_dns_zone_name = azurerm_private_dns_zone.openai.name41 virtual_network_id = azurerm_virtual_network.this.id42 registration_enabled = false43}44
5. สร้าง ประตูหลังบ้านเพื่อเข้าถึง Foundry ของเราได้
azurerm_private_endpoint "ai_foundry" สร้าง network card ที่มี private IP อยู่ใน snet-pe แล้วต่อเข้า Foundry account ผ่าน subresource_names = ["account"]
private_dns_zone_group ทำให้ Azure ลงทะเบียน IP นี้ใน DNS zone ทั้ง 3 ให้อัตโนมัติ
depends_on บังคับให้สร้าง DNS link ให้เสร็จก่อน แล้วค่อยสร้าง PE
1## Private endpoint for the AI Foundry account2##3resource "azurerm_private_endpoint" "ai_foundry" {4 depends_on = [5 azurerm_private_dns_zone_virtual_network_link.cognitiveservices,6 azurerm_private_dns_zone_virtual_network_link.ai_services,7 azurerm_private_dns_zone_virtual_network_link.openai,8 ]910 name = "pe-${azapi_resource.ai_foundry.name}"11 location = var.location12 resource_group_name = azurerm_resource_group.this.name13 subnet_id = azurerm_subnet.pe.id14 tags = var.tags1516 private_service_connection {17 name = "psc-${azapi_resource.ai_foundry.name}"18 private_connection_resource_id = azapi_resource.ai_foundry.id19 subresource_names = ["account"]20 is_manual_connection = false21 }2223 private_dns_zone_group {24 name = "dns-${azapi_resource.ai_foundry.name}"25 private_dns_zone_ids = [26 azurerm_private_dns_zone.cognitiveservices.id,27 azurerm_private_dns_zone.ai_services.id,28 azurerm_private_dns_zone.openai.id,29 ]30 }31}32
6. สร้าง Foundry Project ,กำหนด Capability host, และ deploy model
1########## Create the AI Foundry project and capability host2##########34## Create AI Foundry project5##6resource "azapi_resource" "ai_foundry_project" {7 depends_on = [8 azapi_resource.ai_foundry,9 azurerm_private_endpoint.pe_aifoundry10 ]1112 type = "Microsoft.CognitiveServices/accounts/projects@2025-06-01"13 name = "project${random_string.unique.result}"14 parent_id = azapi_resource.ai_foundry.id15 location = var.location16 schema_validation_enabled = false1718 body = {19 sku = {20 name = "S0"21 }22 identity = {23 type = "SystemAssigned"24 }2526 properties = {27 displayName = "project"28 description = "A project for the AI Foundry account with network secured basic Agent"29 }30 }3132 response_export_values = [33 "identity.principalId",34 "properties.internalId"35 ]36}3738## Create the AI Foundry project capability host (basic agent - no BYO connections)39##40resource "azapi_resource" "ai_foundry_project_capability_host" {41 depends_on = [42 azapi_resource.ai_foundry_project43 ]44 type = "Microsoft.CognitiveServices/accounts/projects/capabilityHosts@2025-04-01-preview"45 name = "caphostproj"46 parent_id = azapi_resource.ai_foundry_project.id47 schema_validation_enabled = false4849 body = {50 properties = {51 capabilityHostKind = "Agents"52 }53 }54}5556## Create a deployment for the model in the AI Foundry resource57##58resource "azurerm_cognitive_deployment" "aifoundry_deployment_gpt_4o" {59 depends_on = [60 azapi_resource.ai_foundry61 ]6263 name = var.model_name64 cognitive_account_id = azapi_resource.ai_foundry.id6566 sku {67 name = var.model_sku_name68 capacity = var.model_capacity69 }7071 model {72 format = "OpenAI"73 name = var.model_name74 version = var.model_version75 }76}
การทดลอง : เราจะ provisioning foundry project 2 แบบ จากนั้นให้ agent ที่อยู่ใน foundry project นั้นๆ เรียกใช้ mcp ที่ดึงราคาหุ้น (mcp เดียวกัน)
แบบที่ 1
ทดลอง provisioning แบบ manual นั่นหมายความว่า Foundry นี้จะไม่มี Capability Host และ VNet Injection ทำให้ Agent ไม่สามารถเรียกใช้ MCP ได้ ตามรูป
** mcp-stock-price-no-authen (http://192.168.2.8/mcp)

แบบที่ 2
ทดลอง provisioning ด้วย terraform ตามขั่้นตอน Step 1 - 6 นั่นหมายความว่า Foundry นี้จะมี Capability Host และ VNet Injection ทำให้ Agent สามารถเรียกใช้ MCP ได้ ตามรูป
** mcp-stock-price-no-authen ตัวเดียวกับ แบบที่ 1

แหล่งอ้างอิง