There's a laundry list.

The resource template format is inhuman gibberish designed for machines. It does idiotic things like manually hard-coding enums in the template to support drop-downs. This means templates go "stale" very quickly and are super fiddly to update to support things like new VM sizes.

You cannot export resource groups bigger than a certain size, so if some idiot in your org makes them too big, there's no backup even in this minimalistic sense.

There is no "backup" for anything but a handful of object types (Web Apps, VM disks, Secret Vaults, and that's about it). If you want to protect your data, you'd better have an on-premise copy and 100% automated deployment capability.

Speaking of automated deployment capability, 99.9% of the Azure docs mention manual deployment or procedural (PowerShell) deployment. Declarative is clearly an afterthought, and mentioned only in passing. Because of this, ~95% of Azure deployments I've seen in the wild were entirely hand built and very fragile. If some malicious admin ran Remove-AzResource on everything, those orgs may as well declare bankruptcy the next day.

Despite this fragility, Azure sales reps are pushing-pushing-pushing for their customers to go "all in" on cloud, be "cloud first", and to basically "lift and shift" their data centres. You gotta break a few eggs to meet your quarterly targets, am I right? Right? Guys?

Speaking of the PowerShell, it's 99% auto-generated from the JSON API binding definitions, and about 5% of it is documented even in the bare minimum sense.

IPv6 "support" is hilarious. They very generously provide a /124 static public subnet prefix. So many addresses! A whole 14 of them! Woo! It's the future now! No need for NAT! A routable address for every endpoint! Let me get right on that, soon as I figure out the fiddly scripting needed to allocate addresses from hundreds of tiny pools. Much fun.

If you delete a DNS Zone by accident, you can't properly recover it within 48 hours because they randomly pick one of 10 name server pools. Hence, your NS bindings at the registrar will point at the wrong servers and even if you update this, there's an inevitable propagation delay. I am aware of workarounds for this like Resource Group Pinning, but only because we jumped up and down and forced support to admit that it's a problem. This little "surprise" is still undocumented.

Speaking of DNS, until we forced Microsoft to fix it, the only way to back up Azure DNS records (az network dns zone export) would corrupt CNAMES and wouldn't round-trip.

Azure DNS uses an idiotic Zone->RecordSet->Record hierarchical structure, which makes small incremental changes hugely fiddly in scripting. You have to download the existing RecordSet, modify it, and then send it back with ETags intact. You can't treat each record as independent rows in a table, even though they effectively are.

The Azure DNS servers don't send "Additional" records (e.g.: the matching A records for the target of a CNAME record), which means that a) it's slower for clients, and b) they can charge you more. They have zero incentive to fix this, because it literally doubles their revenue for alias records of all types.

DNS Metrics are collected every 2 hours, but the graph displays only daily or hourly intervals, so you either get no detail at all, or a sawtooth graph that gives you no useful feedback at all at best, or is panic inducing at worst. Imagine making a small change, glancing at the graph, and seeing it hit zero. Then... staying at zero for an hour. A joyous time, for sure.

I could go on and on.

Azure has some neat stuff, but they're moving fast and breaking things, and half their products are basically MVP garbage authored by the lowest-bidding Indian outsourced teams.