Azure
intermediate
35-45 minutes

Azure Virtual Machines

Deploy applications on Azure VMs with full control over the operating system and configuration.

What you'll accomplish

A configured VM running your application with SSH access and networking

Prerequisites

  • Azure account with subscription
  • Azure CLI installed
  • Basic Linux command knowledge

What are Azure Virtual Machines?

Azure VMs provide full control over the operating system and software:

  • Complete control - Install any software
  • Multiple OS options - Linux distributions, Windows Server
  • Flexible sizing - From 1 vCPU to 416 vCPUs
  • Persistent storage - Managed disks
  • Full networking - VNets, NSGs, load balancers

When to Use VMs

Use CaseWhy VMs
Custom softwareNeed specific versions or configurations
Legacy appsCan't containerize
GPU workloadsMachine learning, rendering
Self-managed databasesPostgreSQL, MongoDB clusters
Background jobsLong-running processes

VMs vs Other Options

FeatureVMsApp ServiceContainer Apps
ControlFullLimitedLimited
MaintenanceYouMicrosoftMicrosoft
ScalingManual/VMSSAutomaticAutomatic
CostWhile runningWhile deployedWhile running

Step 1: Choose a VM Size

Common VM Sizes

SeriesUse CaseExample
BBurstable, dev/testB1s, B2s
DGeneral purposeD2s_v5, D4s_v5
EMemory optimizedE2s_v5, E4s_v5
FCompute optimizedF2s_v2, F4s_v2
NGPUNC6, NV6

Size Examples

SizevCPUsMemoryCost/month*
Standard_B1s11 GB~$8
Standard_B2s24 GB~$30
Standard_D2s_v528 GB~$70
Standard_D4s_v5416 GB~$140

*Approximate East US pricing

List Available Sizes

bash
az vm list-sizes --location eastus --output table

Step 2: Create a Virtual Machine

Basic Linux VM

bash
# Create resource group
az group create --name myapp-rg --location eastus

# Create VM
az vm create \
  --resource-group myapp-rg \
  --name myapp-vm \
  --image Ubuntu2204 \
  --size Standard_B2s \
  --admin-username azureuser \
  --generate-ssh-keys \
  --public-ip-sku Standard

VM with Custom Options

bash
az vm create \
  --resource-group myapp-rg \
  --name myapp-vm \
  --image Ubuntu2204 \
  --size Standard_D2s_v5 \
  --admin-username azureuser \
  --ssh-key-values ~/.ssh/id_rsa.pub \
  --os-disk-size-gb 64 \
  --storage-sku Premium_LRS \
  --public-ip-sku Standard \
  --nsg myapp-nsg \
  --vnet-name myapp-vnet \
  --subnet default \
  --tags Environment=Production App=MyApp

Available Images

bash
# List popular images
az vm image list --output table

# Search for specific images
az vm image list --offer Ubuntu --all --output table
az vm image list --offer Debian --all --output table
az vm image list --offer CentOS --all --output table

# Common image URNs:
# Canonical:0001-com-ubuntu-server-jammy:22_04-lts:latest
# Debian:debian-11:11:latest
# RedHat:RHEL:8-lvm:latest

Step 3: Configure Network Security

Create Network Security Group

bash
# Create NSG
az network nsg create \
  --resource-group myapp-rg \
  --name myapp-nsg

# Allow SSH
az network nsg rule create \
  --resource-group myapp-rg \
  --nsg-name myapp-nsg \
  --name AllowSSH \
  --priority 100 \
  --access Allow \
  --protocol Tcp \
  --direction Inbound \
  --source-address-prefixes '*' \
  --destination-port-ranges 22

# Allow HTTP
az network nsg rule create \
  --resource-group myapp-rg \
  --nsg-name myapp-nsg \
  --name AllowHTTP \
  --priority 110 \
  --access Allow \
  --protocol Tcp \
  --direction Inbound \
  --source-address-prefixes '*' \
  --destination-port-ranges 80

# Allow HTTPS
az network nsg rule create \
  --resource-group myapp-rg \
  --nsg-name myapp-nsg \
  --name AllowHTTPS \
  --priority 120 \
  --access Allow \
  --protocol Tcp \
  --direction Inbound \
  --source-address-prefixes '*' \
  --destination-port-ranges 443

List NSG Rules

bash
az network nsg rule list \
  --resource-group myapp-rg \
  --nsg-name myapp-nsg \
  --output table

Step 4: Connect to Your VM

Get Public IP

bash
az vm show \
  --resource-group myapp-rg \
  --name myapp-vm \
  --show-details \
  --query publicIps \
  --output tsv

SSH Connection

bash
# Connect via SSH
ssh azureuser@<public-ip>

# Or using Azure CLI
az ssh vm \
  --resource-group myapp-rg \
  --name myapp-vm

File Transfer

bash
# Copy file to VM
scp local-file.txt azureuser@<public-ip>:~/

# Copy file from VM
scp azureuser@<public-ip>:~/remote-file.txt ./

# Copy directory
scp -r ./my-app azureuser@<public-ip>:~/

Step 5: Deploy Your Application

Node.js Application Setup

SSH into your VM and run:

bash
# Update system
sudo apt update && sudo apt upgrade -y

# Install Node.js
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs

# Install nginx
sudo apt install -y nginx

# Clone your application
cd /var/www
sudo git clone https://github.com/your-user/your-app.git
cd your-app
sudo npm ci --production
sudo npm run build

# Create systemd service
sudo tee /etc/systemd/system/myapp.service << 'EOF'
[Unit]
Description=My Node.js Application
After=network.target

[Service]
Type=simple
User=www-data
WorkingDirectory=/var/www/your-app
ExecStart=/usr/bin/node server.js
Restart=on-failure
Environment=NODE_ENV=production
Environment=PORT=3000

[Install]
WantedBy=multi-user.target
EOF

# Start service
sudo systemctl daemon-reload
sudo systemctl enable myapp
sudo systemctl start myapp

# Configure nginx reverse proxy
sudo tee /etc/nginx/sites-available/myapp << 'EOF'
server {
    listen 80;
    server_name _;

    location / {
        proxy_pass http://localhost:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_cache_bypass $http_upgrade;
    }
}
EOF

sudo ln -sf /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t && sudo systemctl restart nginx

Using Custom Script Extension

Automate setup during VM creation:

bash
# Create startup script
cat > startup.sh << 'EOF'
#!/bin/bash
apt-get update
apt-get install -y nginx nodejs npm
# ... rest of setup
EOF

# Create VM with script
az vm create \
  --resource-group myapp-rg \
  --name myapp-vm \
  --image Ubuntu2204 \
  --size Standard_B2s \
  --admin-username azureuser \
  --generate-ssh-keys \
  --custom-data startup.sh

Step 6: Manage Your VM

View VM Details

bash
# Show VM info
az vm show \
  --resource-group myapp-rg \
  --name myapp-vm \
  --output table

# Show with IP addresses
az vm show \
  --resource-group myapp-rg \
  --name myapp-vm \
  --show-details \
  --output table

Start, Stop, Restart

bash
# Stop VM (deallocate - no charges)
az vm deallocate \
  --resource-group myapp-rg \
  --name myapp-vm

# Start VM
az vm start \
  --resource-group myapp-rg \
  --name myapp-vm

# Restart VM
az vm restart \
  --resource-group myapp-rg \
  --name myapp-vm

# Delete VM
az vm delete \
  --resource-group myapp-rg \
  --name myapp-vm \
  --yes

Resize VM

bash
# List available sizes for resize
az vm list-vm-resize-options \
  --resource-group myapp-rg \
  --name myapp-vm \
  --output table

# Resize (may require deallocation)
az vm resize \
  --resource-group myapp-rg \
  --name myapp-vm \
  --size Standard_D4s_v5

Step 7: Managed Disks

Add Data Disk

bash
# Create and attach disk
az vm disk attach \
  --resource-group myapp-rg \
  --vm-name myapp-vm \
  --name myapp-datadisk \
  --size-gb 128 \
  --sku Premium_LRS \
  --new

# SSH and format the disk
ssh azureuser@<public-ip>
sudo fdisk -l  # Find the new disk (e.g., /dev/sdc)
sudo mkfs.ext4 /dev/sdc
sudo mkdir /mnt/data
sudo mount /dev/sdc /mnt/data

# Add to fstab for persistence
echo '/dev/sdc /mnt/data ext4 defaults,nofail 0 2' | sudo tee -a /etc/fstab

Disk Types

TypeUse CaseIOPS
Standard_LRSDev/test, backupsUp to 500
StandardSSD_LRSWeb servers, light workloadsUp to 6000
Premium_LRSProduction databasesUp to 20000
UltraSSD_LRSHighest performanceUp to 160000

Step 8: Static IP Address

bash
# Create static public IP
az network public-ip create \
  --resource-group myapp-rg \
  --name myapp-static-ip \
  --sku Standard \
  --allocation-method Static

# Get IP address
az network public-ip show \
  --resource-group myapp-rg \
  --name myapp-static-ip \
  --query ipAddress \
  --output tsv

# Associate with VM's NIC
az network nic ip-config update \
  --resource-group myapp-rg \
  --nic-name myapp-vmVMNic \
  --name ipconfig1 \
  --public-ip-address myapp-static-ip

Step 9: Availability Sets and Scale Sets

Availability Set

High availability within a datacenter:

bash
# Create availability set
az vm availability-set create \
  --resource-group myapp-rg \
  --name myapp-avset \
  --platform-fault-domain-count 2 \
  --platform-update-domain-count 5

# Create VM in availability set
az vm create \
  --resource-group myapp-rg \
  --name myapp-vm-1 \
  --availability-set myapp-avset \
  --image Ubuntu2204 \
  --size Standard_B2s \
  --admin-username azureuser \
  --generate-ssh-keys

Virtual Machine Scale Set

Auto-scaling group of VMs:

bash
# Create scale set
az vmss create \
  --resource-group myapp-rg \
  --name myapp-vmss \
  --image Ubuntu2204 \
  --vm-sku Standard_B2s \
  --instance-count 2 \
  --admin-username azureuser \
  --generate-ssh-keys \
  --upgrade-policy-mode automatic \
  --custom-data startup.sh

# Enable autoscaling
az monitor autoscale create \
  --resource-group myapp-rg \
  --name myapp-autoscale \
  --resource myapp-vmss \
  --resource-type Microsoft.Compute/virtualMachineScaleSets \
  --min-count 2 \
  --max-count 10 \
  --count 2

# Add scale rule
az monitor autoscale rule create \
  --resource-group myapp-rg \
  --autoscale-name myapp-autoscale \
  --condition "Percentage CPU > 70 avg 5m" \
  --scale out 2

az monitor autoscale rule create \
  --resource-group myapp-rg \
  --autoscale-name myapp-autoscale \
  --condition "Percentage CPU < 30 avg 5m" \
  --scale in 1

Step 10: Monitoring

Enable Monitoring

bash
# Install Azure Monitor agent
az vm extension set \
  --resource-group myapp-rg \
  --vm-name myapp-vm \
  --name AzureMonitorLinuxAgent \
  --publisher Microsoft.Azure.Monitor \
  --version 1.0

# View metrics
az monitor metrics list \
  --resource /subscriptions/.../resourceGroups/myapp-rg/providers/Microsoft.Compute/virtualMachines/myapp-vm \
  --metric "Percentage CPU" \
  --output table

Boot Diagnostics

bash
# Enable boot diagnostics
az vm boot-diagnostics enable \
  --resource-group myapp-rg \
  --name myapp-vm

# Get serial console log
az vm boot-diagnostics get-boot-log \
  --resource-group myapp-rg \
  --name myapp-vm

Cost Optimization

Spot VMs

Up to 90% cheaper, but can be evicted:

bash
az vm create \
  --resource-group myapp-rg \
  --name myapp-spot-vm \
  --image Ubuntu2204 \
  --size Standard_D2s_v5 \
  --priority Spot \
  --eviction-policy Deallocate \
  --max-price 0.05 \
  --admin-username azureuser \
  --generate-ssh-keys

Reserved Instances

1-3 year commitment for up to 72% savings:

  1. Go to Reservations in Azure Portal
  2. Select VM reservation
  3. Choose term and VM series
  4. Discounts apply automatically

Auto-Shutdown

bash
az vm auto-shutdown \
  --resource-group myapp-rg \
  --name myapp-vm \
  --time 1900 \
  --timezone "Pacific Standard Time"

Troubleshooting

Can't SSH to VM

bash
# Check NSG rules
az network nsg rule list \
  --resource-group myapp-rg \
  --nsg-name myapp-nsg \
  --output table

# Check VM is running
az vm show \
  --resource-group myapp-rg \
  --name myapp-vm \
  --query powerState \
  --output tsv

# Reset SSH configuration
az vm user reset-ssh \
  --resource-group myapp-rg \
  --name myapp-vm

VM Won't Start

bash
# Check boot diagnostics
az vm boot-diagnostics get-boot-log \
  --resource-group myapp-rg \
  --name myapp-vm

# Redeploy VM (moves to new host)
az vm redeploy \
  --resource-group myapp-rg \
  --name myapp-vm

Reset Password

bash
az vm user update \
  --resource-group myapp-rg \
  --name myapp-vm \
  --username azureuser \
  --password NewSecurePassword123!
Note

Checkpoint: Create a VM, SSH into it, deploy your application, and configure firewall rules to make it accessible.