the builder's.log
Cloud Architecture

เชื่อมต่อ 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

1
2① VNet
3② snet-agent (delegated) ─────────► ③ Foundry account
4 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 subnets
2##
3resource "azurerm_virtual_network" "this" {
4 name = "vnet-${var.name_prefix}-${local.suffix}"
5 location = var.location
6 resource_group_name = azurerm_resource_group.this.name
7 address_space = [var.vnet_address_space]
8 tags = var.tags
9}
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 what
2## 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.name
7 virtual_network_name = azurerm_virtual_network.this.name
8 address_prefixes = [var.agent_subnet_prefix]
9
10 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}
18
19## Subnet for the AI Foundry private endpoint
20##
21resource "azurerm_subnet" "pe" {
22 name = "snet-pe"
23 resource_group_name = azurerm_resource_group.this.name
24 virtual_network_name = azurerm_virtual_network.this.name
25 address_prefixes = [var.pe_subnet_prefix]
26}


2.5 สร้าง Foundry Account (ที่คลุม Foundry Projects อีกที)

1########## Create AI Foundry resource
2##########
3
4## Create the AI Foundry resource with VNet injection
5##
6resource "azapi_resource" "ai_foundry" {
7 depends_on = [
8 azurerm_subnet.subnet_agent,
9 azapi_resource_action.purge_ai_foundry
10 ]
11
12 type = "Microsoft.CognitiveServices/accounts@2025-06-01"
13 name = "aifoundry${random_string.unique.result}"
14 parent_id = azurerm_resource_group.rg.id
15 location = var.location
16 schema_validation_enabled = false
17
18 body = {
19 kind = "AIServices",
20 sku = {
21 name = "S0"
22 }
23 identity = {
24 type = "SystemAssigned"
25 }
26
27 properties = {
28 # Support both Entra ID and API Key authentication
29 disableLocalAuth = false
30
31 # Specifies that this is an AI Foundry resource
32 allowProjectManagement = true
33
34 # Set custom subdomain name for DNS
35 customSubDomainName = "aifoundry${random_string.unique.result}"
36
37 # Network-related controls
38 # Disable public access but allow Trusted Azure Services exception
39 publicNetworkAccess = "Disabled"
40 networkAcls = {
41 defaultAction = "Allow"
42 }
43
44 # Enable VNet injection for Agents
45 networkInjections = [
46 {
47 scenario = "agent"
48 subnetArmId = azurerm_subnet.subnet_agent.id
49 useMicrosoftManagedNetwork = false
50 }
51 ]
52 }
53 }
54}
55
56
57


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 privately
2##
3resource "azurerm_private_dns_zone" "cognitiveservices" {
4 name = "privatelink.cognitiveservices.azure.com"
5 resource_group_name = azurerm_resource_group.this.name
6 tags = var.tags
7}
8
9resource "azurerm_private_dns_zone" "ai_services" {
10 name = "privatelink.services.ai.azure.com"
11 resource_group_name = azurerm_resource_group.this.name
12 tags = var.tags
13}
14
15resource "azurerm_private_dns_zone" "openai" {
16 name = "privatelink.openai.azure.com"
17 resource_group_name = azurerm_resource_group.this.name
18 tags = var.tags
19}
20
21resource "azurerm_private_dns_zone_virtual_network_link" "cognitiveservices" {
22 name = "link-cogsvc-${local.suffix}"
23 resource_group_name = azurerm_resource_group.this.name
24 private_dns_zone_name = azurerm_private_dns_zone.cognitiveservices.name
25 virtual_network_id = azurerm_virtual_network.this.id
26 registration_enabled = false
27}
28
29resource "azurerm_private_dns_zone_virtual_network_link" "ai_services" {
30 name = "link-aiservices-${local.suffix}"
31 resource_group_name = azurerm_resource_group.this.name
32 private_dns_zone_name = azurerm_private_dns_zone.ai_services.name
33 virtual_network_id = azurerm_virtual_network.this.id
34 registration_enabled = false
35}
36
37resource "azurerm_private_dns_zone_virtual_network_link" "openai" {
38 name = "link-openai-${local.suffix}"
39 resource_group_name = azurerm_resource_group.this.name
40 private_dns_zone_name = azurerm_private_dns_zone.openai.name
41 virtual_network_id = azurerm_virtual_network.this.id
42 registration_enabled = false
43}
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 account
2##
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 ]
9
10 name = "pe-${azapi_resource.ai_foundry.name}"
11 location = var.location
12 resource_group_name = azurerm_resource_group.this.name
13 subnet_id = azurerm_subnet.pe.id
14 tags = var.tags
15
16 private_service_connection {
17 name = "psc-${azapi_resource.ai_foundry.name}"
18 private_connection_resource_id = azapi_resource.ai_foundry.id
19 subresource_names = ["account"]
20 is_manual_connection = false
21 }
22
23 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 host
2##########
3
4## Create AI Foundry project
5##
6resource "azapi_resource" "ai_foundry_project" {
7 depends_on = [
8 azapi_resource.ai_foundry,
9 azurerm_private_endpoint.pe_aifoundry
10 ]
11
12 type = "Microsoft.CognitiveServices/accounts/projects@2025-06-01"
13 name = "project${random_string.unique.result}"
14 parent_id = azapi_resource.ai_foundry.id
15 location = var.location
16 schema_validation_enabled = false
17
18 body = {
19 sku = {
20 name = "S0"
21 }
22 identity = {
23 type = "SystemAssigned"
24 }
25
26 properties = {
27 displayName = "project"
28 description = "A project for the AI Foundry account with network secured basic Agent"
29 }
30 }
31
32 response_export_values = [
33 "identity.principalId",
34 "properties.internalId"
35 ]
36}
37
38## 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_project
43 ]
44 type = "Microsoft.CognitiveServices/accounts/projects/capabilityHosts@2025-04-01-preview"
45 name = "caphostproj"
46 parent_id = azapi_resource.ai_foundry_project.id
47 schema_validation_enabled = false
48
49 body = {
50 properties = {
51 capabilityHostKind = "Agents"
52 }
53 }
54}
55
56## Create a deployment for the model in the AI Foundry resource
57##
58resource "azurerm_cognitive_deployment" "aifoundry_deployment_gpt_4o" {
59 depends_on = [
60 azapi_resource.ai_foundry
61 ]
62
63 name = var.model_name
64 cognitive_account_id = azapi_resource.ai_foundry.id
65
66 sku {
67 name = var.model_sku_name
68 capacity = var.model_capacity
69 }
70
71 model {
72 format = "OpenAI"
73 name = var.model_name
74 version = var.model_version
75 }
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


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