Project 3: Multi-Tier Web Application β
Overview β
Deploy a highly available, secure multi-tier web application with load balancing, web application firewall, and Azure Bastion for secure management. This architecture is common for enterprise web applications.
Difficulty: Intermediate to Advanced
Duration: 4-6 hours
Cost: ~$100-150/month (VMs, Load Balancer, App Gateway)
Architecture Diagram β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β INTERNET β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
β HTTPS (443)
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β APPLICATION GATEWAY + WAF v2 β
β (Public IP) β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β - SSL Termination - URL-based routing - Autoscaling β β
β β - WAF Protection - Health probes - Session affinity β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
β HTTP (80)
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β VNet: vnet-webapp (10.0.0.0/16) β
β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Subnet: snet-appgw (10.0.0.0/24) - App Gateway β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β βββββββββββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββ β
β β WEB TIER - Subnet: snet-web (10.0.1.0/24) β β
β β β β β
β β ββββββββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββ β β
β β β INTERNAL LOAD BALANCER (10.0.1.100) β β β
β β ββββββββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββ β β
β β βββββββββββββββββΌββββββββββββββββ β β
β β β β β β β
β β βΌ βΌ βΌ β β
β β βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ β β
β β β vm-web-01 β β vm-web-02 β β vm-web-03 β β β
β β β Zone 1 β β Zone 2 β β Zone 3 β β β
β β β Nginx/IIS β β Nginx/IIS β β Nginx/IIS β β β
β β β 10.0.1.4 β β 10.0.1.5 β β 10.0.1.6 β β β
β β βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ β β
β β β β β
β β NSG: nsg-web β β
β βββββββββββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββ β
β β β
β βββββββββββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββ β
β β APP TIER - Subnet: snet-app (10.0.2.0/24) β β
β β β β β
β β ββββββββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββ β β
β β β INTERNAL LOAD BALANCER (10.0.2.100) β β β
β β ββββββββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββ β β
β β βββββββββββββββββΌββββββββββββββββ β β
β β β β β β β
β β βΌ βΌ βΌ β β
β β βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ β β
β β β vm-app-01 β β vm-app-02 β β vm-app-03 β β β
β β β Zone 1 β β Zone 2 β β Zone 3 β β β
β β β App Server β β App Server β β App Server β β β
β β β 10.0.2.4 β β 10.0.2.5 β β 10.0.2.6 β β β
β β βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ β β
β β β β β
β β NSG: nsg-app β β
β βββββββββββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββ β
β β β
β βββββββββββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββ β
β β DATA TIER - Subnet: snet-data (10.0.3.0/24) β β
β β β β β
β β βββββββββββββββββββ βββββββββββββββββββ β β
β β β vm-sql-01 β β vm-sql-02 β β β
β β β Primary β β Secondary β β β
β β β SQL Server ββββββββΆβ SQL Server β Always On AG β β
β β β 10.0.3.4 β β 10.0.3.5 β β β
β β βββββββββββββββββββ βββββββββββββββββββ β β
β β NSG: nsg-data β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β MANAGEMENT - Subnet: AzureBastionSubnet (10.0.4.0/27) β β
β β Azure Bastion β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββWhat You'll Learn β
- Deploy VMs across Availability Zones
- Configure Azure Load Balancer (internal)
- Deploy Application Gateway with WAF
- Implement NSG rules for defense in depth
- Configure Azure Bastion for secure management
- Set up health probes and backend pools
Prerequisites β
- Azure subscription
- Azure CLI installed
- Basic understanding of load balancing concepts
Phase 1: Create Network Infrastructure β
Step 1.1: Create Resource Group and VNet β
bash
# Set variables
LOCATION="eastus"
RG_NAME="rg-webapp-lab-eastus"
# Create resource group
az group create \
--name $RG_NAME \
--location $LOCATION \
--tags Project=MultiTierWebApp Environment=Lab
# Create VNet
az network vnet create \
--resource-group $RG_NAME \
--name vnet-webapp \
--address-prefix 10.0.0.0/16 \
--location $LOCATION
echo "VNet created"Step 1.2: Create Subnets β
bash
# Application Gateway subnet
az network vnet subnet create \
--resource-group $RG_NAME \
--vnet-name vnet-webapp \
--name snet-appgw \
--address-prefix 10.0.0.0/24
# Web tier subnet
az network vnet subnet create \
--resource-group $RG_NAME \
--vnet-name vnet-webapp \
--name snet-web \
--address-prefix 10.0.1.0/24
# App tier subnet
az network vnet subnet create \
--resource-group $RG_NAME \
--vnet-name vnet-webapp \
--name snet-app \
--address-prefix 10.0.2.0/24
# Data tier subnet
az network vnet subnet create \
--resource-group $RG_NAME \
--vnet-name vnet-webapp \
--name snet-data \
--address-prefix 10.0.3.0/24
# Azure Bastion subnet
az network vnet subnet create \
--resource-group $RG_NAME \
--vnet-name vnet-webapp \
--name AzureBastionSubnet \
--address-prefix 10.0.4.0/27
echo "All subnets created"Phase 2: Create Network Security Groups β
Step 2.1: Create Web Tier NSG β
bash
# Create NSG
az network nsg create \
--resource-group $RG_NAME \
--name nsg-web \
--location $LOCATION
# Allow HTTP from App Gateway subnet
az network nsg rule create \
--resource-group $RG_NAME \
--nsg-name nsg-web \
--name AllowHTTPFromAppGW \
--priority 100 \
--direction Inbound \
--access Allow \
--protocol Tcp \
--source-address-prefixes 10.0.0.0/24 \
--destination-port-ranges 80 443
# Allow health probes from Azure
az network nsg rule create \
--resource-group $RG_NAME \
--nsg-name nsg-web \
--name AllowAzureHealthProbe \
--priority 110 \
--direction Inbound \
--access Allow \
--protocol "*" \
--source-address-prefixes AzureLoadBalancer \
--destination-port-ranges "*"
# Allow RDP from Bastion
az network nsg rule create \
--resource-group $RG_NAME \
--nsg-name nsg-web \
--name AllowRDPFromBastion \
--priority 200 \
--direction Inbound \
--access Allow \
--protocol Tcp \
--source-address-prefixes 10.0.4.0/27 \
--destination-port-ranges 3389 22
# Deny all other inbound
az network nsg rule create \
--resource-group $RG_NAME \
--nsg-name nsg-web \
--name DenyAllInbound \
--priority 4096 \
--direction Inbound \
--access Deny \
--protocol "*" \
--source-address-prefixes "*" \
--destination-port-ranges "*"
# Associate NSG
az network vnet subnet update \
--resource-group $RG_NAME \
--vnet-name vnet-webapp \
--name snet-web \
--network-security-group nsg-webStep 2.2: Create App Tier NSG β
bash
# Create NSG
az network nsg create \
--resource-group $RG_NAME \
--name nsg-app \
--location $LOCATION
# Allow from Web tier only
az network nsg rule create \
--resource-group $RG_NAME \
--nsg-name nsg-app \
--name AllowFromWebTier \
--priority 100 \
--direction Inbound \
--access Allow \
--protocol Tcp \
--source-address-prefixes 10.0.1.0/24 \
--destination-port-ranges 8080 8443
# Allow health probes
az network nsg rule create \
--resource-group $RG_NAME \
--nsg-name nsg-app \
--name AllowHealthProbe \
--priority 110 \
--direction Inbound \
--access Allow \
--protocol "*" \
--source-address-prefixes AzureLoadBalancer \
--destination-port-ranges "*"
# Allow RDP from Bastion
az network nsg rule create \
--resource-group $RG_NAME \
--nsg-name nsg-app \
--name AllowRDPFromBastion \
--priority 200 \
--direction Inbound \
--access Allow \
--protocol Tcp \
--source-address-prefixes 10.0.4.0/27 \
--destination-port-ranges 3389 22
# Associate NSG
az network vnet subnet update \
--resource-group $RG_NAME \
--vnet-name vnet-webapp \
--name snet-app \
--network-security-group nsg-appStep 2.3: Create Data Tier NSG β
bash
# Create NSG
az network nsg create \
--resource-group $RG_NAME \
--name nsg-data \
--location $LOCATION
# Allow SQL from App tier only
az network nsg rule create \
--resource-group $RG_NAME \
--nsg-name nsg-data \
--name AllowSQLFromAppTier \
--priority 100 \
--direction Inbound \
--access Allow \
--protocol Tcp \
--source-address-prefixes 10.0.2.0/24 \
--destination-port-ranges 1433
# Allow RDP from Bastion
az network nsg rule create \
--resource-group $RG_NAME \
--nsg-name nsg-data \
--name AllowRDPFromBastion \
--priority 200 \
--direction Inbound \
--access Allow \
--protocol Tcp \
--source-address-prefixes 10.0.4.0/27 \
--destination-port-ranges 3389
# Associate NSG
az network vnet subnet update \
--resource-group $RG_NAME \
--vnet-name vnet-webapp \
--name snet-data \
--network-security-group nsg-dataPhase 3: Deploy Web Tier VMs β
Step 3.1: Create Web VMs Across Zones β
bash
# Set admin credentials
ADMIN_USER="azureadmin"
ADMIN_PASSWORD="P@ssw0rd123!Complex"
# Create web VMs in different zones
for i in 1 2 3; do
az vm create \
--resource-group $RG_NAME \
--name vm-web-0$i \
--vnet-name vnet-webapp \
--subnet snet-web \
--image Win2022Datacenter \
--size Standard_D2s_v3 \
--admin-username $ADMIN_USER \
--admin-password $ADMIN_PASSWORD \
--zone $i \
--public-ip-address "" \
--no-wait
done
echo "Web VMs creation initiated across 3 zones"Step 3.2: Install IIS on Web VMs β
bash
# Wait for VMs to be ready
for i in 1 2 3; do
az vm wait --resource-group $RG_NAME --name vm-web-0$i --created
done
# Install IIS using Custom Script Extension
for i in 1 2 3; do
az vm extension set \
--resource-group $RG_NAME \
--vm-name vm-web-0$i \
--name CustomScriptExtension \
--publisher Microsoft.Compute \
--settings '{"commandToExecute": "powershell -ExecutionPolicy Unrestricted Install-WindowsFeature -Name Web-Server -IncludeManagementTools; Add-Content -Path \"C:\\inetpub\\wwwroot\\Default.htm\" -Value \"Hello from vm-web-0'$i' in Zone '$i'\""}' \
--no-wait
done
echo "IIS installation initiated"Phase 4: Deploy App Tier VMs β
Step 4.1: Create App VMs β
bash
# Create app VMs in different zones
for i in 1 2 3; do
az vm create \
--resource-group $RG_NAME \
--name vm-app-0$i \
--vnet-name vnet-webapp \
--subnet snet-app \
--image Ubuntu2204 \
--size Standard_D2s_v3 \
--admin-username $ADMIN_USER \
--generate-ssh-keys \
--zone $i \
--public-ip-address "" \
--no-wait
done
echo "App VMs creation initiated"Phase 5: Create Internal Load Balancer for App Tier β
Step 5.1: Create Internal Load Balancer β
bash
# Create Internal Load Balancer
az network lb create \
--resource-group $RG_NAME \
--name ilb-app \
--sku Standard \
--vnet-name vnet-webapp \
--subnet snet-app \
--frontend-ip-name FrontEndPool \
--backend-pool-name BackEndPool \
--private-ip-address 10.0.2.100
echo "Internal Load Balancer created"Step 5.2: Create Health Probe β
bash
# Create health probe
az network lb probe create \
--resource-group $RG_NAME \
--lb-name ilb-app \
--name HealthProbe \
--protocol Tcp \
--port 8080 \
--interval 5 \
--threshold 2Step 5.3: Create Load Balancing Rule β
bash
# Create load balancing rule
az network lb rule create \
--resource-group $RG_NAME \
--lb-name ilb-app \
--name HTTPRule \
--protocol Tcp \
--frontend-port 8080 \
--backend-port 8080 \
--frontend-ip-name FrontEndPool \
--backend-pool-name BackEndPool \
--probe-name HealthProbe \
--idle-timeout 15 \
--enable-tcp-reset trueStep 5.4: Add VMs to Backend Pool β
bash
# Wait for App VMs
for i in 1 2 3; do
az vm wait --resource-group $RG_NAME --name vm-app-0$i --created
done
# Get NIC IDs and add to backend pool
for i in 1 2 3; do
NIC_ID=$(az vm show \
--resource-group $RG_NAME \
--name vm-app-0$i \
--query "networkProfile.networkInterfaces[0].id" -o tsv)
# Get IP config name
IP_CONFIG=$(az network nic show --ids $NIC_ID --query "ipConfigurations[0].name" -o tsv)
# Add to backend pool
az network nic ip-config address-pool add \
--resource-group $RG_NAME \
--nic-name $(basename $NIC_ID) \
--ip-config-name $IP_CONFIG \
--lb-name ilb-app \
--address-pool BackEndPool
done
echo "App VMs added to load balancer backend pool"Phase 6: Deploy Application Gateway with WAF β
Step 6.1: Create Public IP for App Gateway β
bash
# Create public IP
az network public-ip create \
--resource-group $RG_NAME \
--name pip-appgw \
--sku Standard \
--allocation-method Static \
--location $LOCATION
# Get the public IP
APPGW_IP=$(az network public-ip show \
--resource-group $RG_NAME \
--name pip-appgw \
--query ipAddress -o tsv)
echo "App Gateway Public IP: $APPGW_IP"Step 6.2: Create Application Gateway β
Long Running Command
Application Gateway deployment takes 15-20 minutes.
bash
# Wait for web VMs to be ready
for i in 1 2 3; do
az vm wait --resource-group $RG_NAME --name vm-web-0$i --created
done
# Get web VM private IPs
WEB_IP1=$(az vm show -g $RG_NAME -n vm-web-01 --show-details --query privateIps -o tsv)
WEB_IP2=$(az vm show -g $RG_NAME -n vm-web-02 --show-details --query privateIps -o tsv)
WEB_IP3=$(az vm show -g $RG_NAME -n vm-web-03 --show-details --query privateIps -o tsv)
# Create Application Gateway with WAF
az network application-gateway create \
--resource-group $RG_NAME \
--name appgw-webapp \
--location $LOCATION \
--sku WAF_v2 \
--capacity 2 \
--vnet-name vnet-webapp \
--subnet snet-appgw \
--public-ip-address pip-appgw \
--http-settings-port 80 \
--http-settings-protocol Http \
--frontend-port 80 \
--servers $WEB_IP1 $WEB_IP2 $WEB_IP3 \
--priority 100
echo "Application Gateway created"Step 6.3: Enable WAF Policy β
bash
# Create WAF policy
az network application-gateway waf-policy create \
--resource-group $RG_NAME \
--name waf-policy-webapp
# Configure WAF policy settings
az network application-gateway waf-policy policy-setting update \
--resource-group $RG_NAME \
--policy-name waf-policy-webapp \
--mode Prevention \
--state Enabled \
--max-request-body-size-kb 128 \
--file-upload-limit-mb 100
# Associate WAF policy with App Gateway
az network application-gateway update \
--resource-group $RG_NAME \
--name appgw-webapp \
--waf-policy waf-policy-webappStep 6.4: Configure Health Probes β
bash
# Create custom health probe
az network application-gateway probe create \
--resource-group $RG_NAME \
--gateway-name appgw-webapp \
--name HealthProbe \
--protocol Http \
--host-name-from-http-settings true \
--path "/" \
--interval 30 \
--timeout 30 \
--threshold 3
# Update backend http settings to use probe
az network application-gateway http-settings update \
--resource-group $RG_NAME \
--gateway-name appgw-webapp \
--name appGatewayBackendHttpSettings \
--probe HealthProbePhase 7: Deploy Azure Bastion β
bash
# Create public IP for Bastion
az network public-ip create \
--resource-group $RG_NAME \
--name pip-bastion \
--sku Standard \
--allocation-method Static
# Create Azure Bastion
az network bastion create \
--resource-group $RG_NAME \
--name bastion-webapp \
--public-ip-address pip-bastion \
--vnet-name vnet-webapp \
--sku Basic \
--location $LOCATION
echo "Azure Bastion deployed"Phase 8: Configure Monitoring β
Step 8.1: Enable Boot Diagnostics β
bash
# Create storage account for diagnostics
STORAGE_NAME="diagwebapp$(date +%s | tail -c 8)"
az storage account create \
--resource-group $RG_NAME \
--name $STORAGE_NAME \
--sku Standard_LRS \
--location $LOCATION
# Enable boot diagnostics on all VMs
for vm in vm-web-01 vm-web-02 vm-web-03 vm-app-01 vm-app-02 vm-app-03; do
az vm boot-diagnostics enable \
--resource-group $RG_NAME \
--name $vm \
--storage $STORAGE_NAME
doneStep 8.2: Configure Azure Monitor Alerts β
bash
# Create action group
az monitor action-group create \
--resource-group $RG_NAME \
--name ag-webapp-alerts \
--short-name WebAppAlrt \
--action email admin admin@contoso.com
# Create CPU alert for VMs
for vm in vm-web-01 vm-web-02 vm-web-03; do
VM_ID=$(az vm show -g $RG_NAME -n $vm --query id -o tsv)
az monitor metrics alert create \
--resource-group $RG_NAME \
--name "High-CPU-$vm" \
--scopes $VM_ID \
--condition "avg Percentage CPU > 80" \
--window-size 5m \
--evaluation-frequency 1m \
--action ag-webapp-alerts \
--severity 2
donePhase 9: Testing and Verification β
Step 9.1: Test Application Gateway β
bash
# Get App Gateway public IP
APPGW_IP=$(az network public-ip show \
--resource-group $RG_NAME \
--name pip-appgw \
--query ipAddress -o tsv)
# Test connectivity
curl http://$APPGW_IP
# Test multiple times to verify load balancing
for i in {1..10}; do
curl -s http://$APPGW_IP
echo ""
doneStep 9.2: Test High Availability β
- Connect to vm-web-01 via Bastion
- Stop IIS service:powershell
Stop-Service W3SVC - Refresh the website - should still work (other VMs serve traffic)
- Verify in App Gateway backend health
Step 9.3: Verify WAF Protection β
bash
# Test WAF with SQL injection attempt (should be blocked)
curl "http://$APPGW_IP/?id=1;DROP TABLE users--"
# Test with XSS attempt (should be blocked)
curl "http://$APPGW_IP/?q=<script>alert('xss')</script>"Step 9.4: Check Backend Health β
bash
# Check App Gateway backend health
az network application-gateway show-backend-health \
--resource-group $RG_NAME \
--name appgw-webapp \
--output tableArchitecture Summary β
| Component | Purpose | Configuration |
|---|---|---|
| Application Gateway | L7 load balancer | WAF_v2, 2 instances |
| Web VMs | Web servers | 3 VMs across zones |
| Internal LB | App tier balancing | Standard SKU |
| App VMs | Application servers | 3 VMs across zones |
| Azure Bastion | Secure management | Basic SKU |
| NSGs | Network security | Per-subnet rules |
Cost Optimization Tips β
- Use Reserved Instances: 40-60% savings for 1-3 year commitment
- Right-size VMs: Start with B-series for dev/test
- Use Spot VMs: For non-critical workloads
- Schedule auto-shutdown: For dev/test environments
- Use App Gateway autoscaling: Pay only for what you use
Cleanup β
bash
# Delete resource group and all resources
az group delete --name $RG_NAME --yes --no-wait
echo "Cleanup initiated"Key Takeaways β
- Defense in Depth: NSGs at each tier
- High Availability: VMs across availability zones
- WAF Protection: L7 security with OWASP rules
- Secure Management: No public IPs on VMs
- Load Balancing: Both L7 (App GW) and L4 (ILB)
Next Steps β
- Add SSL certificates to Application Gateway
- Configure autoscaling for App Gateway
- Implement Azure SQL instead of VM-based SQL
- Add Azure Front Door for global load balancing
- Configure Azure Monitor workbooks