Azure - create VM instance with IP using API

71 views Asked by At

I would like to spawn new instances of my image by using a nodejs API.

I already create a network interface (NIC) and an IP for this.

But when the VM instance is created, i do not see my public IP.

Here is my typescript code:

// 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'; // Replace with your resource group name
const vmName = generateVMname();
const location = 'westeurope'; // Replace with your desired location
const vmSize = 'Standard_B1s'; // Replace with your desired VM size
const imageId = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.Compute/galleries/imgs_gallery1/images/test_img/versions/0.0.1`;

const createPublicIP = async () => {
    // need a 'network contributor' role in the IAM for this
    const creds = new DefaultAzureCredential();
    const networkClient = new NetworkManagementClient(creds, subscriptionId);

    const publicIpParams = {
        location: location,
        zones: ['3'],
        publicIPAddressVersion: 'IPv4',
        // ipConfiguration: {
        //     id: `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.Network/networkInterfaces/${}/ipConfigurations/ipconfig1`
        // },
        publicIPAllocationMethod: 'Static', // You can use 'Dynamic' if needed
        sku: {
            name: 'Standard',
        },
    };

    const publicIpName = generateIPname(); // Replace with your desired public IP name
    console.log('Creating Public IP...');
    try {
        const result = await networkClient.publicIPAddresses.beginCreateOrUpdate(
            resourceGroupName,
            publicIpName,
            publicIpParams
        );
        console.log('Public IP creation result:', result);
        return result.getResult()?.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 resourceGroupName = 'Azure-test_group';
    const publicIpId = await createPublicIP();
    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`,
                },
                properties: {
                    publicIPAddress: {
                        id: publicIpId,
                        properties: {
                            deleteOption: 'Detach',
                        },
                    },
                    privateIPAllocationMethod: 'Dynamic',
                },
            },
        ],
    };
    const nicName = generateNICname();
    return (
        await networkClient.networkInterfaces.beginCreateOrUpdate(resourceGroupName, nicName, nicParams)
    ).getResult()?.id;
};

const createVM = async () => {
    try {
        const networkID = await createNIC();
        // Authenticate using DefaultAzureCredential
        const creds = new DefaultAzureCredential();
        const computeClient = new ComputeManagementClient(creds, subscriptionId);

        // Define VM parameters
        const vmParams: VirtualMachine = {
            location,
            zones: ['3'],
            hardwareProfile: {
                vmSize,
            },
            diagnosticsProfile: {
                bootDiagnostics: {
                    enabled: true,
                },
            },
            networkProfile: {
                networkInterfaces: [
                    {
                        id: networkID,
                    },
                ],
            },
            storageProfile: {
                imageReference: {
                    id: imageId,
                },
            },
            osProfile: {
                computerName: vmName,
                adminUsername: 'testuser', // Replace with your desired admin username
                adminPassword: 'myPassword123!', // Replace with your desired admin password
                linuxConfiguration: {
                    disablePasswordAuthentication: true,
                    ssh: {
                        publicKeys: [
                            {
                                keyData: process.env.SSHKEY,
                                path: '/home/testuser/.ssh/authorized_keys',
                            },
                        ],
                    },
                },
            },
            // Other VM configurations
        };

        // Create VM
        console.log('try to create VM');
        const result = await computeClient.virtualMachines.beginCreateOrUpdate(resourceGroupName, vmName, vmParams);
        await new Promise((resolve) => setTimeout(resolve, 120000)); // Adjust the delay as needed
        console.log('VM created:', result);
    } catch (err) {
        console.error('Error creating VM:', err);
    }
};

const startVM = async () => {
    try {
        const computeClient = new ComputeManagementClient(new DefaultAzureCredential(), subscriptionId);
        const result = await computeClient.virtualMachines.beginStart(resourceGroupName, vmName); // Use the same vmName
        console.log('VM started:', result);
    } catch (err) {
        console.error('Error starting VM:', err);
    }
};

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
    return createVM().then(() => {
        startVM();
        res.send('success');
    });
}

Although I do not know the cause for this, one thing that I see is that the IP i create (in the Azure portal >> resource group >> >> JSONview) that unlike other IPs that are connected to VMs that have public IPs, I do not have the property ipConfigurations-->id.
The problem is that in order to get this id, I need the NIC first, but to get the NIC I need to create an IP.

It might be that I need to add the id in ipConfigurations AFTER finishing the NIC and IP creations. But I am not sure if this is the correct solution, and how to do it.

Would be glad to get some input on how to solve this, and whether my assumption that the problem lies here is correct.

thank you.

1

There are 1 answers

0
Mucker On

I don't know much about your code and I am not a coder but I don't think i need to in this case. I an Azure architect so I deploy VMs with public IPs and the like all the time.

Since a NIC doesn't need a public IP, you can CAN and should create the NIC first. The way it works using the portal (or Powershell, or ARM), is you create the NIC first and attach it to a vnet, then create the VM and attach the NIC to it.

Then in your case you create the IP - this might be where a lack of Azure experience might be confusing you. In Azure, almost everything is a "resource", so to assign a public IP, you have to first "create" it. Once you do that, it will be a resource, then you just run the commands to "attach" it to your NIC. When you create the PIP (public IP address), the API should return it's resourceID. You use this when you then configure the NIC with the new public IP. You CANNOT create the NIC and PIP at the same time for the reasons you said. You could always create the PIP first (as there is no dependency there), retrieve it's ID, then plug that into your code when you create the NIC.