The previous lesson ended with an uncomfortable diagnosis: vm-motor-disponibilidad-dev works, but it is a single machine. If the physical host fails, if Azure applies platform maintenance or if traffic multiplies by ten, there is no plan B. And the third of those is going to happen to Contoso Airlines on a date already marked in the calendar.
Every year, in the middle of February, Contoso opens summer season sales. The first day concentrates around 60% of the month's traffic, with a three-hour spike in the morning during which the Availability API receives roughly fifteen times the requests of a normal day. With their own servers in Barcelona and Palma, the historical answer was to buy hardware for the peak and keep it at 6% utilization the other 364 days. That is, literally, the problem the cloud solves.
In this lesson you will learn to scale compute and to give it high availability: what the difference is between growing upwards and growing sideways, how virtual machine scale sets work, how instances are spread across availability zones, which load balancer to choose among the four Azure offers, and what design requirement all of this imposes on your application: being stateless.
Cost warning: a scale set with two instances bills two VMs, and a Standard Load Balancer has an hourly cost plus a cost per rule. Everything in this lesson is removed at the end with a single deletion of the lab resource group.
Contents
- Vertical scaling versus horizontal scaling
- The Contoso case: the season-opening peak
- Virtual machine scale sets (VMSS)
- Manual, metric-based autoscaling and scheduled scaling
- Availability zones and availability sets
- Load balancing in Azure: the four options
- Health probes
- Deploying the Availability API behind a Load Balancer
- Stateless applications: the hidden requirement
- The resulting architecture and cleanup
- Common Mistakes and Tips
- Exercises
- Conclusion
- Vertical scaling versus horizontal scaling
There are two ways to give a system more capacity.
| Aspect | Vertical scaling (scale up) | Horizontal scaling (scale out) |
|---|---|---|
| What you do | You swap the machine for a bigger one | You add more identical machines |
| In Azure | az vm resize to a larger size |
Adding instances to a scale set |
| Interruption | Requires restarting the VM | None: the new instances simply join |
| Limit | The family's maximum size | Practically your quota |
| High availability | None: it is still a single point of failure | Intrinsic: if one instance goes down, the rest remain |
| Reversibility | Manual and with a restart | Automatic and within minutes |
| Application requirement | None | The application cannot store local state |
Vertical scaling is simple and sometimes the right answer: a monolithic relational database usually scales better upwards. But for a web application's compute, the cloud model is the horizontal one, for three reasons:
- Real elasticity: you can go from 2 to 20 instances in minutes and back to 2 when the peak is over. You pay for what you use, when you use it.
- Availability: N spread-out instances survive losing one without the service going down.
- No abrupt ceiling: you do not one day reach the family's maximum size and find yourself with nowhere to go.
The trade-off is the one in the last row of the table, and it is not a small one: if your application stores the user's session in the server's memory or writes files to its local disk, horizontal scaling breaks it. We cover that in section 9.
- The Contoso case: the season-opening peak
The numbers Marta Ríos has measured on the current system:
| Moment | Requests per second to the Availability API | Average CPU of the current server |
|---|---|---|
| Normal day, early morning | 15 | 5% |
| Normal day, rush hour | 120 | 35% |
| Season opening, first hour | 1,800 | saturated (100%, queuing) |
| Season opening, rest of the day | 400 | 90% |
The conclusion is twofold: far higher capacity is needed for a few hours a year and minimal capacity the rest of the time. Sizing for the peak with fixed machines means paying fifteen times what you need for 360 days. Sizing for a normal day means losing sales on the day you sell the most.
The solution we build in this lesson: the Availability API is deployed on a scale set with a minimum of 2 instances spread across two zones, a CPU-based autoscale rule and a scheduled rule that raises the minimum on the morning of the opening, all behind a Load Balancer.
- Virtual machine scale sets (VMSS)
A virtual machine scale set (VMSS) is a resource that manages a group of identical VMs as a single unit: they are created from the same model, they are updated together and they grow or shrink according to rules.
What a VMSS gives you that creating VMs by hand does not:
- A single model: you change the image or the size in the model and it applies to all of them.
- Autoscaling by metric, by schedule or manually.
- Automatic instance repair: if an instance fails the health probe for a while, the set replaces it.
- Automatic distribution across zones and fault domains.
- Batched updates (rolling upgrades) with no service outage.
Orchestration mode: uniform versus flexible
| Aspect | Uniform orchestration | Flexible orchestration |
|---|---|---|
| Instances | Identical, managed as a set | Normal VMs, individually visible |
| Access to each instance | Limited, through the set | The same as any VM (az vm show) |
| Mixing sizes or pay-as-you-go and spot VMs | No | Yes |
| Availability zones | Yes | Yes, with explicit spreading |
| Azure's current recommendation | Very large homogeneous workloads | The default for almost anything new |
Flexible is today the recommended mode by default: it gives you set-level management without losing individual control of each VM, and it allows mixing spot-priced instances with normal instances to make peaks cheaper. It is the one we will use.
- Manual, metric-based autoscaling and scheduled scaling
Manual scaling
# Set the instance count by hand. Useful for testing and one-off responses.
az vmss scale \
--resource-group rg-contoso-reservas-pro \
--name vmss-api-disponibilidad-pro \
--new-capacity 4Metric-based autoscaling
Autoscaling watches a metric and acts. A complete rule always has these pieces:
| Piece | What it defines | Example |
|---|---|---|
| Metric | What is watched | The set's Percentage CPU |
| Aggregation and window | How it is summarized and over how long | The average of the last 5 minutes |
| Threshold and operator | When it fires | Greater than 70% |
| Action | What is done | Increase by 2 instances |
| Cooldown | How long to wait before acting again | 5 minutes |
| Limits | Minimum, maximum and default | 2 / 20 / 2 |
GROUP="rg-contoso-reservas-pro"
VMSS="vmss-api-disponibilidad-pro"
# 1. Autoscale profile: minimum 2, maximum 20, default value 2.
az monitor autoscale create \
--resource-group "${GROUP}" \
--resource "${VMSS}" \
--resource-type Microsoft.Compute/virtualMachineScaleSets \
--name autoescala-api-disponibilidad \
--min-count 2 --max-count 20 --count 2 \
--output none
# 2. Scale-out rule: if average CPU goes above 70% for 5 minutes, +2 instances.
az monitor autoscale rule create \
--resource-group "${GROUP}" \
--autoscale-name autoescala-api-disponibilidad \
--condition "Percentage CPU > 70 avg 5m" \
--scale out 2 \
--cooldown 5 \
--output none
# 3. Scale-in rule: if it drops below 30% for 10 minutes, -1 instance.
az monitor autoscale rule create \
--resource-group "${GROUP}" \
--autoscale-name autoescala-api-disponibilidad \
--condition "Percentage CPU < 30 avg 10m" \
--scale in 1 \
--cooldown 10 \
--output noneNote the deliberate asymmetry between the two rules, which is a good practice and not an oversight:
- You scale out fast and in big blocks (+2 with a 5-minute window): falling short costs sales.
- You scale in slowly and one at a time (−1 with a 10-minute window): reducing too early causes the sawtooth effect, in which the system adds and removes instances non-stop, and each start-up takes minutes and costs money.
If the scale-out and scale-in rules have the same threshold, oscillation is guaranteed. Always leave a wide dead band between the two (here, between 30% and 70%).
Scheduled scaling
When you know when the peak is coming, do not wait for the CPU to prove it: a metric-based reaction always arrives late, because a new instance takes several minutes to boot and be ready.
# The season opening is the morning of 12 February:
# we raise the minimum to 10 instances between 07:00 and 14:00.
az monitor autoscale profile create \
--resource-group "${GROUP}" \
--autoscale-name autoescala-api-disponibilidad \
--name apertura-temporada-verano \
--min-count 10 --max-count 30 --count 12 \
--timezone "W. Europe Standard Time" \
--start 2026-02-12T07:00 \
--end 2026-02-12T14:00 \
--output noneDuring that profile, the set never drops below 10 instances and can reach 30 if the CPU calls for it. Outside the window, it goes back to the normal profile (2–20). This combination — scheduled for the predictable, metric-based for the unpredictable — is the pattern most seasonal sales platforms use.
- Availability zones and availability sets
Having several instances is worth nothing if they are all in the same rack and that rack loses power. Azure offers two spreading mechanisms, and they protect against different things.
Availability set (within a single datacenter)
It spreads the VMs across:
- Fault domains: groups of servers that share power supply and network switch. If the rack fails, one fault domain goes down. Up to 3 per region.
- Update domains: groups that Azure reboots separately during platform maintenance. Up to 20.
graph TB
subgraph AS["Availability set (one datacenter)"]
subgraph FD0["Fault domain 0"]
V1["Instance 1"]
end
subgraph FD1["Fault domain 1"]
V2["Instance 2"]
end
subgraph FD2["Fault domain 2"]
V3["Instance 3"]
end
end
It protects against: rack failure and maintenance reboots. It does not protect against the complete loss of the datacenter.
Availability zones (different datacenters)
As you saw in 01-02, a zone is a physically separate datacenter within the same region, with independent power, cooling and networking. Spreading instances across zones protects against the failure of an entire datacenter.
| Mechanism | Protects against | Availability SLA |
|---|---|---|
| Single instance with Premium disks | Nothing beyond local hardware | 99.9% |
| Availability set (2+ instances) | Rack failure and maintenance | 99.95% |
| Availability zones (2+ instances in 2+ zones) | Loss of a datacenter | 99.99% |
Contoso's decision, consistent with the one from module 1: critical components in at least two West Europe zones, with no multi-region active-active setup (a conscious decision on cost grounds). The Availability API is deployed in zones 1 and 2.
# Scale set with explicit spreading across two zones.
az vmss create \
--resource-group rg-contoso-reservas-pro \
--name vmss-api-disponibilidad-pro \
--orchestration-mode Flexible \
--zones 1 2 \
--instance-count 2 \
--vm-sku Standard_B2s \
--image Canonical:ubuntu-24_04-lts:server:latest \
--admin-username azureuser \
--ssh-key-values ~/.ssh/contoso_motor.pub \
--custom-data init-api.yaml \
--tags entorno=produccion proyecto=contoso-reservas \
centro-coste=CC-1042 [email protected] \
criticidad=alta \
--output noneWith --zones 1 2, Azure spreads the instances evenly across both zones and maintains that balance as it scales. Important: zones are chosen when the set is created and cannot be added afterwards. If you think you will ever need them, create it with zones from the start.
- Load balancing in Azure: the four options
Having several instances requires something in front to distribute traffic. Azure offers four services and the confusion between them is one of the favorite topics of certification exams (and of badly built architectures).
| Service | Layer | Scope | Routes by | Typical cases | Relative cost |
|---|---|---|---|---|---|
| Azure Load Balancer | 4 (TCP/UDP) | Regional | IP and port | Any TCP/UDP protocol, VM and VMSS backends | € |
| Application Gateway | 7 (HTTP/S) | Regional | URL path, header, host | Web and API with path-based routing, TLS termination, WAF | €€€ |
| Traffic Manager | DNS | Global | DNS response by profile (performance, priority, geography) | Failover between regions, any protocol | € |
| Azure Front Door | 7 | Global | Path, host, with an edge network and caching | Global websites, TLS termination at the edge, caching, global WAF | €€€ |
Selection criteria, in the form of questions:
- Is it HTTP/S traffic? If it is not (for example, a protocol of the legacy engine's own), your option is Load Balancer or Traffic Manager.
- Do you need to decide based on the URL (
/api/*to one pool,/to another), rewrite headers or offload TLS? Then you need layer 7: Application Gateway or Front Door. - Does traffic arrive from all over the world and do you want caching and an edge presence? Front Door.
- Do you only want to fail over between regions at the DNS level, with any protocol? Traffic Manager.
Two notes on scope, so as not to invade other lessons:
- The global side — Front Door, Traffic Manager, CDN — is developed in lesson 02-06, with the case of the customer buying from South America.
- The web application firewall (WAF), which attaches to Application Gateway or Front Door, is covered in lesson 04-04.
Here we stay regional and at layer 4: Azure Load Balancer in front of the scale set.
Components of a Load Balancer
| Component | What it is |
|---|---|
| Frontend IP | The public or private IP through which traffic enters |
| Backend pool | The set of instances that receive the traffic |
| Health probe | The check that decides which instances are healthy |
| Load balancing rule | Ties frontend, port, pool and probe together |
The SKUs: Standard (the current one: supports zones, up to 1,000 instances, secure by default) and Basic, now retired. Always use Standard.
- Health probes
A health probe is what separates a load balancer from a blind distributor. Every few seconds, the balancer asks each instance whether it is alive; if it does not respond correctly a certain number of times in a row, it stops sending traffic to it.
# HTTP probe against the API's health endpoint, every 5 seconds.
az network lb probe create \
--resource-group rg-contoso-reservas-pro \
--lb-name lb-api-disponibilidad-pro \
--name sonda-api-salud \
--protocol Http \
--port 80 \
--path /salud.json \
--interval 5 \
--output noneGood practices for the health endpoint, which hold for the whole course:
- Make it meaningful: it should check what is needed to serve (is there a database connection?), not always return 200.
- Make it cheap: it runs every few seconds per instance. No heavy queries.
- Do not require authentication from the internal network, or the probe will always fail.
- Separate liveness from readiness: "the process is alive" is not the same as "I can serve requests now".
A badly built probe causes two classic and opposite failures: broken instances receiving traffic (a probe that is too lenient) or healthy instances being pulled out of service in a cascade (a probe that is too strict or has a short timeout).
- Deploying the Availability API behind a Load Balancer
Let us assemble the whole piece. So as not to touch production while you are learning, use the development group; the example says production because that is the target architecture, but you can replace pro with dev in all the variables.
#!/usr/bin/env bash
set -euo pipefail
GROUP="rg-contoso-reservas-pro"
REGION="westeurope"
VMSS="vmss-api-disponibilidad-pro"
LB="lb-api-disponibilidad-pro"
PUBLIC_IP="ip-lb-api-disponibilidad-pro"
TAGS=(entorno=produccion proyecto=contoso-reservas centro-coste=CC-1042
[email protected] criticidad=alta)
# 1. Standard, zone-redundant public IP.
az network public-ip create \
--resource-group "${GROUP}" --name "${PUBLIC_IP}" \
--sku Standard --zone 1 2 3 --allocation-method Static \
--tags "${TAGS[@]}" --output none
# 2. Load Balancer with a public frontend and an empty backend pool.
az network lb create \
--resource-group "${GROUP}" --name "${LB}" --sku Standard \
--public-ip-address "${PUBLIC_IP}" \
--frontend-ip-name frontal-publico \
--backend-pool-name pool-api-disponibilidad \
--tags "${TAGS[@]}" --output none
# 3. Health probe against the API's health endpoint.
az network lb probe create \
--resource-group "${GROUP}" --lb-name "${LB}" \
--name sonda-api-salud --protocol Http --port 80 --path /salud.json \
--interval 5 --output none
# 4. Load balancing rule: port 80 on the frontend to port 80 on the pool.
az network lb rule create \
--resource-group "${GROUP}" --lb-name "${LB}" \
--name regla-http-api \
--protocol Tcp --frontend-port 80 --backend-port 80 \
--frontend-ip-name frontal-publico \
--backend-pool-name pool-api-disponibilidad \
--probe-name sonda-api-salud \
--idle-timeout 10 --output none
# 5. Scale set across two zones, already connected to the backend pool.
az vmss create \
--resource-group "${GROUP}" --name "${VMSS}" \
--orchestration-mode Flexible \
--zones 1 2 --instance-count 2 \
--vm-sku Standard_B2s \
--image Canonical:ubuntu-24_04-lts:server:latest \
--admin-username azureuser \
--ssh-key-values ~/.ssh/contoso_motor.pub \
--custom-data init-api.yaml \
--lb "${LB}" --backend-pool-name pool-api-disponibilidad \
--upgrade-policy-mode Automatic \
--tags "${TAGS[@]}" --output none
echo "API published at: http://$(az network public-ip show -g "${GROUP}" -n "${PUBLIC_IP}" --query ipAddress -o tsv)/salud.json"The init-api.yaml file that installs the API on each instance (the same cloud-init mechanism from the previous lesson, now serving the instance's identity so that you can watch the balancing work):
#cloud-config
package_update: true
packages:
- nginx
runcmd:
- echo "{\"service\":\"availability-api\",\"status\":\"ok\",\"instance\":\"$(hostname)\"}" > /var/www/html/salud.json
- systemctl enable --now nginxChecking the balancing: several consecutive calls should return different instance names.
IP=$(az network public-ip show -g rg-contoso-reservas-pro -n ip-lb-api-disponibilidad-pro --query ipAddress -o tsv)
for i in {1..10}; do curl -s "http://${IP}/salud.json"; echo; doneIf the same instance always answers, check the rule's session persistence: by default the Load Balancer uses a five-tuple (source and destination IP and port, protocol), and since your source port changes on every request, you should see distribution. If you configure source IP persistence (--load-distribution SourceIP), all your requests will go to the same instance, which is exactly what we do not want, for the reason in the next section.
- Stateless applications: the hidden requirement
Horizontal scaling imposes one condition on the application: any instance must be able to serve any request. That means the instance cannot store anything the others need.
The three kinds of state that break scaling and where they should go instead:
| State stored in the wrong place | Symptom when you scale | Where it should go |
|---|---|---|
| User session in memory | The customer loses their basket of tickets when they jump instances | An external store: Azure Cache for Redis, or a cookie signed by the client |
| Uploaded files on the local disk | The generated boarding pass "disappears" depending on which instance answers | Azure Storage (lesson 02-04): the boarding passes go to sttarjetascontosopro |
| Business data in local files | Each instance has a different truth | A database (module 3): sql-contoso-reservas-pro |
The easy temptation is to turn on session persistence in the balancer so that each client always goes to the same instance. It is a patch with three serious side effects: distribution becomes unbalanced, an instance failure kicks its users out, and scaling in loses active sessions. It works as a temporary solution during a migration, never as a design.
That is why the order of this module is not accidental: first scalable compute, then the storage where the things compute cannot keep must live (02-04), and in module 3, the database. Contoso's Availability API is designed stateless from day one: it reads from the database, writes the boarding passes to Storage and keeps nothing locally.
- The resulting architecture and cleanup
graph TD
CLI["Clients<br/>(Contoso Bookings website)"] --> LB["Azure Load Balancer Standard<br/>lb-api-disponibilidad-pro<br/>static public IP"]
LB -->|"/salud.json probe"| P["Backend pool<br/>pool-api-disponibilidad"]
P --> Z1["Zone 1<br/>instance api-01"]
P --> Z2["Zone 2<br/>instance api-02"]
AE["Autoscale<br/>CPU 70% / scheduled opening"] -.->|"adjusts 2 to 20"| VMSS["vmss-api-disponibilidad-pro<br/>(Flexible)"]
VMSS --- Z1
VMSS --- Z2
Z1 --> SQL["db-reservas<br/>(module 3)"]
Z2 --> SQL
Z1 --> ST["sttarjetascontosopro<br/>(lesson 02-04)"]
Z2 --> ST
Cleanup (remember that the production group has the no-borrar-produccion lock; in the lab, work against the development group):
# Individual deletion, in the reverse order of creation.
az vmss delete --resource-group rg-contoso-reservas-dev --name vmss-api-disponibilidad-dev
az network lb delete --resource-group rg-contoso-reservas-dev --name lb-api-disponibilidad-dev
az network public-ip delete --resource-group rg-contoso-reservas-dev --name ip-lb-api-disponibilidad-dev
# Or, in a dedicated lab group, the complete deletion:
# az group delete --name rg-contoso-laboratorio --yes --no-waitAnd a check you should by now be doing by reflex: az disk list --query "[?diskState=='Unattached']" -o table.
Common Mistakes and Tips
- Setting the same threshold for scaling out and in. It produces the sawtooth effect: the system adds and removes instances non-stop. Leave a wide dead band (30%–70%).
- Scaling in as aggressively as you scale out. Removing two at a time with a 5-minute window leaves the service short exactly when the peak bounces back. Scale out fast, scale in slowly.
- Relying only on metric-based scaling for a known peak. Booting instances takes minutes; the opening peak arrives in seconds. Use a scheduled profile.
- Confusing an availability set with an availability zone. The first protects against rack failure inside a datacenter; the second, against the loss of the whole datacenter.
- Forgetting that zones are fixed when the scale set is created. They cannot be added later. Create it zonal from the start.
- Using Application Gateway where a Load Balancer is enough (or the other way round). If you need neither URL-based decisions nor TLS termination, layer 4 is simpler and much cheaper.
- Probes that return 200 no matter what. A health endpoint that checks nothing guarantees that the balancer sends traffic to broken instances.
- Scaling out an application with state in memory. Intermittent errors appear that are impossible to reproduce. Get the state out before you scale.
- Quota tip: the maximum vCPU count per family and region is a quota, not a physical limit (lesson 01-05). If your rule can reach 20 instances of 4 vCPUs, check that you have 80 vCPUs of quota before opening day.
- Cost tip: for peaks, consider spot-priced instances inside the flexible set. They are much cheaper in exchange for being evictable; they are for extra capacity, never for the minimum.
Exercises
Exercise 1: designing the peak scaling policy
With the figures measured by Marta Ríos (15 requests per second in the early morning, 120 at normal rush hour, 1,800 at the opening peak), and knowing that one Standard_B2s instance of the API comfortably serves about 100 requests per second:
- How many instances are needed as a minimum on a normal day at rush hour, with headroom to lose one?
- How many at the opening peak?
- Define the minimum, maximum and scheduled profile values you would configure.
- Explain why the CPU rule alone is not enough for that day.
Exercise 2: choosing the balancer
For each Contoso need, state which load balancing service you would use and why:
- Distributing the traffic of the legacy availability engine's binary protocol (TCP 8500) across three VMs in West Europe.
- Sending
/api/*to the Availability API and everything else to the bookings website, within the same region, with TLS termination. - Serving the public website to customers in South America with the lowest possible latency and image caching.
- Failing over to North Europe if West Europe stops responding, for a service that is not HTTP.
Exercise 3: building and proving high availability
In a lab group:
- Create a flexible scale set with 2 instances in zones 1 and 2, with cloud-init that publishes the instance name in
/salud.json. - Put it behind a Standard Load Balancer with an HTTP probe to
/salud.json. - Prove with
curlthat traffic is distributed across both instances. - Stop one instance and prove that the service keeps responding and that the probe has removed it from the pool.
Solutions
Solution 1:
- Normal rush hour: 120 ÷ 100 = 1.2 → 2 instances, and with headroom to lose one (the N+1 pattern), 3. Since the minimum must also cover the zone that could go down, 2 is the absolute floor and 3 the prudent value.
- Opening peak: 1,800 ÷ 100 = 18 → 18 instances, plus N+1 headroom → 20.
- Proposed configuration:
# Normal profile.
az monitor autoscale create ... --min-count 2 --max-count 20 --count 2
# Scheduled profile for the opening (a high minimum from before sales open).
az monitor autoscale profile create \
--name apertura-temporada-verano \
--min-count 12 --max-count 30 --count 20 \
--timezone "W. Europe Standard Time" \
--start 2026-02-12T07:00 --end 2026-02-12T14:00- Because metric-based scaling is reactive: it needs 5 minutes of high CPU to fire and several minutes more for the instances to boot and pass the probe. The opening peak arrives in less than a minute, so during the first 10 minutes — the highest-selling minutes of the year — the service would be saturated. The scheduled profile leaves the capacity already warm before sales open.
Solution 2:
| Case | Service | Reason |
|---|---|---|
| 1. TCP 8500 of the legacy engine | Azure Load Balancer | Layer 4: it works with any TCP/UDP protocol; the layer 7 services only understand HTTP/S |
2. /api/* versus everything else, with TLS, in one region |
Application Gateway | Regional layer 7: path-based routing and TLS termination. It also supports WAF (04-04) |
| 3. South American customers with caching | Azure Front Door | Global, with an edge presence, TLS termination close to the customer and caching (detail in 02-06) |
| 4. Failover to North Europe without HTTP | Traffic Manager | It works at the DNS level, is protocol-independent and supports a priority profile for failover |
Solution 3:
#!/usr/bin/env bash
set -euo pipefail
GROUP="rg-contoso-laboratorio"
az group create --name "${GROUP}" --location westeurope \
--tags entorno=pruebas proyecto=contoso-reservas centro-coste=CC-1042 \
[email protected] --output none
# 1 and 2: IP, balancer, probe, rule and scale set.
az network public-ip create -g "${GROUP}" -n ip-lb-lab --sku Standard --allocation-method Static --output none
az network lb create -g "${GROUP}" -n lb-lab --sku Standard \
--public-ip-address ip-lb-lab --frontend-ip-name frontal-publico \
--backend-pool-name pool-lab --output none
az network lb probe create -g "${GROUP}" --lb-name lb-lab -n sonda-lab \
--protocol Http --port 80 --path /salud.json --interval 5 --output none
az network lb rule create -g "${GROUP}" --lb-name lb-lab -n regla-http \
--protocol Tcp --frontend-port 80 --backend-port 80 \
--frontend-ip-name frontal-publico --backend-pool-name pool-lab \
--probe-name sonda-lab --output none
az vmss create -g "${GROUP}" -n vmss-lab --orchestration-mode Flexible \
--zones 1 2 --instance-count 2 --vm-sku Standard_B1s \
--image Canonical:ubuntu-24_04-lts:server:latest \
--admin-username azureuser --ssh-key-values ~/.ssh/contoso_motor.pub \
--custom-data init-api.yaml --lb lb-lab --backend-pool-name pool-lab --output none
# 3. Check the distribution.
IP=$(az network public-ip show -g "${GROUP}" -n ip-lb-lab --query ipAddress -o tsv)
for i in {1..10}; do curl -s "http://${IP}/salud.json"; echo; done
# 4. Stop one instance (Flexible mode: they are normal VMs) and repeat the test.
INSTANCE=$(az vm list -g "${GROUP}" --query "[0].name" -o tsv)
az vm stop -g "${GROUP}" -n "${INSTANCE}"
for i in {1..10}; do curl -s "http://${IP}/salud.json"; echo; done
# Every response should now come from the other instance, with no errors.
# Complete cleanup.
az group delete --name "${GROUP}" --yes --no-waitAfter stopping the instance, the probe fails several times in a row and the balancer removes it from the pool: it can take 15-20 seconds to see the effect (probe interval times the number of tolerated failures). That delay is exactly the partial downtime some customers would suffer in a real failure, and it is the argument for not setting long probe intervals.
Conclusion
You now know how to scale and give high availability to compute in Azure. You can tell vertical scaling from horizontal scaling and you understand why the cloud bets on the latter: real elasticity, intrinsic availability and no ceiling. You know what a virtual machine scale set is, the difference between uniform and flexible orchestration, and how to define manual, metric-based autoscale — with asymmetric thresholds and a dead band to avoid the sawtooth effect — and scheduled scaling, which is the only reasonable answer to a peak with a known date such as Contoso's summer season opening. You know the difference between availability sets (fault and update domains inside a datacenter) and availability zones (independent datacenters), with their direct effect on the SLA: 99.9%, 99.95% and 99.99%. You have compared the four load balancing services with clear criteria and you have deployed a Standard Load Balancer with a health probe over a scale set spread across two zones. And you have internalized the hidden requirement behind all of it: the application cannot hold local state.
Now, look at what it took: a scale set, a balancer, a probe, autoscale rules — and you still have to patch each instance's operating system, update the image, manage TLS certificates by hand and build zero-downtime deployment yourself. For the legacy engine there is no alternative. But for a modern web application such as Contoso Bookings or for the new Availability API, Azure offers a service that does all of that for you.
In the next lesson, Azure App Service, you will see why Contoso publishes its applications there instead of maintaining VMs: plans and tiers and what each one unlocks, runtime stacks, deploying code with ZIP and Git, app settings and connection strings, custom domains with managed certificates, autoscaling with no set to administer and, above all, deployment slots with swap and warm-up so that new versions go live without a single customer being unable to buy their ticket.
Azure Course
Module 1: Introduction to Azure
- What Is Azure?
- Service Models, Regions and Availability Zones
- Creating and Setting Up Your Azure Account
- A Tour of the Azure Portal
- Azure Resource Manager: Subscriptions, Resource Groups and Tags
- Azure CLI, PowerShell and Cloud Shell
Module 2: Core Azure Services
- Azure Virtual Machines
- Compute Scaling and High Availability
- Azure App Service
- Azure Storage: Blobs, Files, Queues and Tables
- Azure Networking: Virtual Networks, Subnets and NSGs
- Hybrid Connectivity and Global Delivery
Module 3: Azure Databases
- Choosing the Right Data Service
- Azure SQL Database
- Azure Cosmos DB
- Azure Database for MySQL
- Azure Database for PostgreSQL
- Data Analytics: Data Lake, Data Factory and Synapse
Module 4: Security in Azure
- Microsoft Entra ID and Identity Management
- RBAC and Managed Identities
- Azure Key Vault
- DDoS Protection and Web Application Firewall
- Microsoft Defender for Cloud
- Governance and Compliance with Azure Policy
Module 5: Azure DevOps
- Introduction to Azure DevOps
- Azure Repos
- Azure Pipelines: Continuous Integration
- Continuous Deployment with Environments and Approvals
- Azure Artifacts
- Infrastructure as Code with Bicep
Module 6: Advanced Azure Services
- Containers in Azure: Container Registry and Container Apps
- Azure Kubernetes Service (AKS)
- Azure Functions
- Azure Logic Apps
- Messaging and Events: Service Bus, Event Grid and Event Hubs
- Azure AI Services
Module 7: Monitoring and Management
- Azure Monitor: Metrics, Alerts and Dashboards
- Log Analytics and KQL Queries
- Application Insights
- Azure Automation and Runbooks
- Backup and Disaster Recovery
Module 8: Cost Management and Optimization
- Pricing Calculator and Cost Estimation
- Azure Cost Management: Analysis, Budgets and Alerts
- Reservations, Savings Plans and Azure Hybrid Benefit
- Azure Advisor
- Optimization Strategies and FinOps Culture
