使用Node.js API创建Azure VM实例后公网IP未显示的问题排查
解决Azure VM创建后公网IP不显示的问题
问题概述
通过Node.js Azure SDK基于自定义镜像创建VM实例,已成功创建公网IP和NIC,但VM实例创建后无法显示公网IP。检查发现,创建的公网IP在Azure门户JSON视图中缺少ipConfigurations-->id属性,而正常关联VM的公网IP具备该属性。核心矛盾在于:公网IP的ipConfigurations.id需要关联NIC,但创建NIC又依赖已存在的公网IP。
代码问题分析
- NIC配置结构错误:在
createNIC函数中,公网IP的引用被错误嵌套在properties字段下。Azure SDK的NetworkInterfaceIPConfiguration模型中,publicIPAddress是顶级属性,而非嵌套在properties内,这会导致NIC与公网IP的关联未正确建立。 - 不必要的公网IP预配置:创建公网IP时尝试提前指定
ipConfigurations是无效的,该属性由Azure自动维护,当NIC关联公网IP后会自动填充,无需手动设置。 - 固定延迟不可靠:使用
setTimeout等待VM创建完成,无法适配Azure后台的实际处理时长,可能导致后续操作执行过早。
解决方案
- 修正NIC的IP配置结构:将
publicIPAddress从properties中移出,作为ipConfigurations数组元素的直接属性。 - 移除公网IP的
ipConfigurations预配置:删除创建公网IP时注释的相关代码,由Azure自动维护关联关系。 - 使用
pollUntilDone替代固定延迟:利用Azure SDK的异步操作轮询机制,确保VM完全创建完成后再执行启动操作。 - 增加错误校验:在获取公网IP和NIC ID时添加空值校验,避免后续操作因资源创建失败而报错。
修改后的完整代码
// pages/api/azure-create-instances.ts import { ComputeManagementClient, VirtualMachine } from '@azure/arm-compute'; import { NetworkManagementClient } from '@azure/arm-network'; import { DefaultAzureCredential } from '@azure/identity'; import { NextApiRequest, NextApiResponse } from 'next'; function generateVMname(): string { const myDatetime = new Date(); const generalName = 'c24cVM'; return generalName + myDatetime.toISOString().replace(/[^\d]/g, ''); } function generateNICname(): string { const myDatetime = new Date(); const generalName = 'NIC'; return generalName + myDatetime.toISOString().replace(/[^\d]/g, ''); } function generateIPname(): string { const myDatetime = new Date(); const generalName = 'IP'; return generalName + myDatetime.toISOString().replace(/[^\d]/g, ''); } const subscriptionId = process.env.AZURE_SUBSCRIPTION_ID!; const resourceGroupName = 'Azure-test_group'; const vmName = generateVMname(); const location = 'westeurope'; const vmSize = 'Standard_B1s'; const imageId = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.Compute/galleries/imgs_gallery1/images/test_img/versions/0.0.1`; const createPublicIP = async () => { const creds = new DefaultAzureCredential(); const networkClient = new NetworkManagementClient(creds, subscriptionId); const publicIpParams = { location: location, zones: ['3'], publicIPAddressVersion: 'IPv4', publicIPAllocationMethod: 'Static', sku: { name: 'Standard', }, }; const publicIpName = generateIPname(); console.log('Creating Public IP...'); try { const poller = await networkClient.publicIPAddresses.beginCreateOrUpdate( resourceGroupName, publicIpName, publicIpParams ); const publicIp = await poller.pollUntilDone(); console.log('Public IP created:', publicIp); return publicIp.id; } catch (error) { console.error('Error creating Public IP:', error); return undefined; } }; const createNIC = async () => { const creds = new DefaultAzureCredential(); const networkClient = new NetworkManagementClient(creds, subscriptionId); const publicIpId = await createPublicIP(); if (!publicIpId) { throw new Error('Failed to get public IP ID'); } console.log('Public IP ID:', publicIpId); const nicParams = { location: location, ipConfigurations: [ { name: 'MyIpConfig', subnet: { id: `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.Network/virtualNetworks/Azure-test-vnet/subnets/default`, }, publicIPAddress: { id: publicIpId, properties: { deleteOption: 'Detach', }, }, privateIPAllocationMethod: 'Dynamic', }, ], }; const nicName = generateNICname(); const poller = await networkClient.networkInterfaces.beginCreateOrUpdate(resourceGroupName, nicName, nicParams); const nic = await poller.pollUntilDone(); console.log('NIC created:', nic); return nic.id; }; const createVM = async () => { try { const networkID = await createNIC(); if (!networkID) { throw new Error('Failed to get NIC ID'); } const creds = new DefaultAzureCredential(); const computeClient = new ComputeManagementClient(creds, subscriptionId); const vmParams: VirtualMachine = { location, zones: ['3'], hardwareProfile: { vmSize, }, diagnosticsProfile: { bootDiagnostics: { enabled: true, }, }, networkProfile: { networkInterfaces: [ { id: networkID, primary: true, // 明确标记为主网卡,避免多网卡场景下的问题 }, ], }, storageProfile: { imageReference: { id: imageId, }, }, osProfile: { computerName: vmName, adminUsername: 'testuser', adminPassword: 'myPassword123!', linuxConfiguration: { disablePasswordAuthentication: true, ssh: { publicKeys: [ { keyData: process.env.SSHKEY!, path: '/home/testuser/.ssh/authorized_keys', }, ], }, }, }, }; console.log('Starting VM creation...'); const poller = await computeClient.virtualMachines.beginCreateOrUpdate(resourceGroupName, vmName, vmParams); const vm = await poller.pollUntilDone(); console.log('VM created successfully:', vm); return vm; } catch (err) { console.error('Error creating VM:', err); throw err; } }; const startVM = async () => { try { const computeClient = new ComputeManagementClient(new DefaultAzureCredential(), subscriptionId); const poller = await computeClient.virtualMachines.beginStart(resourceGroupName, vmName); const result = await poller.pollUntilDone(); console.log('VM started successfully:', result); } catch (err) { console.error('Error starting VM:', err); } }; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { await createVM(); await startVM(); res.status(200).send('VM created and started successfully'); } catch (err) { res.status(500).send(`Failed to create VM: ${(err as Error).message}`); } }
关键修改点说明
- NIC配置修正:将
publicIPAddress从ipConfigurations[0].properties移至ipConfigurations[0]的顶级属性,符合Azure SDK的数据模型要求,确保NIC与公网IP正确关联。 - 异步操作轮询:所有资源创建操作均使用
pollUntilDone()替代固定延迟,确保资源完全创建完成后再执行后续步骤,避免因Azure后台处理延迟导致的问题。 - 标记主网卡:在VM的
networkInterfaces配置中添加primary: true,明确指定主网卡,确保公网IP正确关联到VM的默认网络接口。
内容的提问来源于stack exchange,提问作者ArieAI
相关产品推荐
相关产品推荐

