Microsoft Azure: сервисы, интеграция с .NET-миром, AKS, сравнение с AWS
В https://courses.digitable.life/post/devops/11-aws/ мы разобрали AWS как модель: аккаунт — граница взрыва, регион — независимая плоскость управления, сеть — источник неожиданных счетов. Azure устроен по тем же физическим законам, но принимает другие решения почти на каждой развилке. И эти решения — не косметические переименования: иерархия ресурсов, модель доступа и экономика лицензий отличаются настолько, что перенос архитектурных привычек один в один даёт плохую систему.
Azure выбирают по трём причинам, и почти всегда только по ним. Первая: организация уже платит Microsoft — Windows Server, SQL Server, Active Directory, Microsoft 365, и лицензионная экономика в Azure радикально другая. Вторая: команда пишет на .NET, и интеграция инструментов действительно бесшовная — от dotnet publish до трассировок в Application Insights без строчки конфигурации. Третья: требования по резидентности данных, комплаенс, а иногда — прямой корпоративный контракт (EA/MCA) с процентной скидкой, которой нет у конкурента.
Если ни одна из трёх не про вас — это ещё не аргумент против Azure, но повод честно сравнить, а не идти за модой. Ниже — попытка дать материал для такого сравнения.
Оговорка про цены: все цифры — порядок величины для регионов вроде westeurope/eastus на момент написания. Microsoft меняет прайс, вводит новые SKU и периодически переносит сроки объявленных изменений. Важны не центы, а соотношения.
Каркас: тенант, management group, подписка, resource group
В AWS два уровня — аккаунт и регион. В Azure их четыре, и путаница в них — источник половины проблем с доступами.
Тенант (Entra ID) — каталог удостоверений. Это про то, кто вы: пользователи, группы, приложения, service principal. Один тенант может держать много подписок; подписку можно перенести в другой тенант. Тенант — не граница биллинга и не граница ресурсов, это граница идентичности.
Management group — узел дерева для политик и назначений ролей. Дерево до шести уровней, корень — Tenant Root Group. Аналог AWS Organizations OU, но с важным отличием: назначение роли на management group наследуется вниз на все подписки, и в Azure нет прямого аналога SCP как «потолка разрешений». Роль ограничения играет Azure Policy — см. ниже.
Подписка (subscription) — граница биллинга и квот. Ближайший родственник аккаунта AWS, но заметно менее жёсткая: подписки живут в одном тенанте, и администратор тенанта при желании дотягивается до всех. Квоты (vCPU по семействам, публичные IP, число сетевых интерфейсов) — именно на уровне подписки и региона.
Resource group (RG) — обязательный контейнер жизненного цикла. Каждый ресурс лежит ровно в одной RG. У RG есть регион — но только для хранения метаданных, ресурсы внутри могут быть где угодно. Главное свойство: удаление RG каскадно удаляет всё внутри. Это одновременно лучший инструмент уборки за собой и лучший способ уничтожить прод одной командой.
границa идентичности
пользователи, группы, приложения"] T --> MGROOT["Tenant Root Group"] MGROOT --> PLAT["MG: Platform"] MGROOT --> LZ["MG: Landing Zones"] MGROOT --> SBX["MG: Sandbox"] PLAT --> CONN["Подписка: connectivity
hub VNet, firewall, DNS"] PLAT --> MGMT["Подписка: management
Log Analytics, бэкапы"] PLAT --> IDN["Подписка: identity
domain controllers"] LZ --> PROD["Подписка: prod"] LZ --> STG["Подписка: staging"] SBX --> DEV["Подписка: dev-*
с жёстким бюджетом"] PROD --> RG1["RG: rg-payments-prod-we
AKS, Key Vault, SQL"] PROD --> RG2["RG: rg-network-prod-we
spoke VNet, private endpoints"] POL["Azure Policy
назначена на MG"] -. "наследуется вниз:
deny / audit / deployIfNotExists" .-> LZ RBAC["Назначения RBAC
на MG или подписке"] -. "наследуются вниз" .-> PROD style T fill:#3d8bcd,color:#fff style POL fill:#e9a23b,color:#3a3f4b style RBAC fill:#2a9d8f,color:#fff
Практический вывод: изоляция в Azure строится подписками, а не resource group. RG — единица развёртывания и уборки, не единица безопасности. Прод и стейдж в одной подписке разделены только ролями, а роли ошибаются.
Именование стоит зафиксировать в первый день, потому что Azure не даёт переименовывать ресурсы. Стандартная схема Microsoft: <тип>-<нагрузка>-<среда>-<регион>-<номер>, например aks-payments-prod-weu-01, kv-payments-prod-weu (у Key Vault лимит 24 символа), stpaymentsprodweu (у storage account — 3–24 символа, только строчные буквы и цифры, глобально уникально).
Entra ID, RBAC и managed identity: где ломаются мозги после AWS
Здесь самое существенное расхождение с AWS, и оно концептуальное.
Две системы ролей, а не одна. Роли Entra ID (Global Administrator, User Administrator, Application Administrator) управляют каталогом: пользователями, приложениями, MFA. Роли Azure RBAC (Owner, Contributor, Reader и сотни встроенных) управляют ресурсами: виртуалками, кластерами, хранилищами. Это разные плоскости. Global Administrator по умолчанию не имеет доступа к ресурсам вообще — пока не нажмёт «Elevate access», получив User Access Administrator на корне дерева. Инженеры, пришедшие из AWS, ищут «главную роль» и не находят.
Модель вычисления разрешений проще, чем в IAM, и от этого опаснее. В Azure RBAC назначения аддитивны: объединение всех Actions минус NotActions. Явного Deny в обычном назначении роли нет — deny assignments существуют, но создаются только платформой (Managed Applications, Blueprints), вручную их не пишут. Значит, привычный приём AWS «повесим Deny поверх всего» здесь не работает.
Роль запрета играет Azure Policy — отдельная система governance с эффектами Deny, Audit, Modify, DeployIfNotExists, AuditIfNotExists. Она перехватывает запросы к Azure Resource Manager до создания ресурса.
{
"properties": {
"displayName": "Запретить публичный сетевой доступ к PaaS-сервисам",
"mode": "Indexed",
"policyRule": {
"if": {
"allOf": [
{ "field": "type", "in": [
"Microsoft.Storage/storageAccounts",
"Microsoft.KeyVault/vaults",
"Microsoft.Sql/servers"
]},
{ "field": "Microsoft.Storage/storageAccounts/publicNetworkAccess",
"notEquals": "Disabled" }
]
},
"then": { "effect": "Deny" }
}
}
}
Это ровно то место, где Azure строже AWS: политика может не только запретить, но и починить ресурс (Modify добавит тег, DeployIfNotExists создаст диагностическую настройку). SCP так не умеет.
Managed identity — лучшее, что есть в Azure. Идея: ресурс (VM, App Service, Container App, под в AKS) получает собственное удостоверение в Entra ID, и токен для доступа к другому сервису берётся из локального endpoint без единого секрета. Никаких ключей, никакой ротации, никакого «а где лежит строка подключения».
Два вида. System-assigned — создаётся вместе с ресурсом, умирает вместе с ним, один к одному. User-assigned — самостоятельный объект, назначается многим ресурсам сразу; переживает пересоздание ресурса. Практическое правило: для одиночных сервисов берите system-assigned (нечего чистить), для групп одинаковых нагрузок и для всего, что пересоздаётся terraform-ом, — user-assigned, иначе при каждом replace придётся заново раздавать роли.
Как это выглядит из кода на .NET — весь секрет-менеджмент сводится к одному классу:
// Program.cs — приложение ASP.NET Core, ноль секретов в конфигурации.
// DefaultAzureCredential пробует по очереди: переменные окружения,
// workload identity, managed identity, Azure CLI разработчика.
// Один и тот же код работает на ноутбуке и в проде.
using Azure.Identity;
using Microsoft.Data.SqlClient;
var builder = WebApplication.CreateBuilder(args);
var credential = new DefaultAzureCredential(new DefaultAzureCredentialOptions
{
// В проде явно указываем client id user-assigned identity:
// без этого на машине с несколькими identity выбор неоднозначен.
ManagedIdentityClientId = builder.Configuration["AZURE_CLIENT_ID"]
});
// Секреты из Key Vault подмешиваются прямо в IConfiguration:
// в коде остаётся обычный builder.Configuration["Payments:ApiKey"].
builder.Configuration.AddAzureKeyVault(
new Uri($"https://{builder.Configuration["KeyVaultName"]}.vault.azure.net/"),
credential);
// Подключение к Azure SQL вообще без пароля: токен Entra ID вместо строки с паролем.
builder.Services.AddScoped(_ =>
{
var conn = new SqlConnection(builder.Configuration.GetConnectionString("Db"));
// Connection string вида:
// "Server=sql-payments-prod.database.windows.net;Database=payments;Encrypt=True;"
conn.AccessToken = credential
.GetToken(new Azure.Core.TokenRequestContext(
new[] { "https://database.windows.net/.default" }))
.Token;
return conn;
});
// Телеметрия: одна строка — и метрики, зависимости, исключения и распределённые
// трассировки уходят в Application Insights без ручной инструментации.
builder.Services.AddApplicationInsightsTelemetry();
var app = builder.Build();
app.MapGet("/healthz", () => Results.Ok("ok"));
app.Run();
Со стороны инфраструктуры остаётся выдать роли. Обратите внимание: доступ к данным в Azure — тоже RBAC, а не отдельный ACL, как политика бакета в S3.
# Даём managed identity право читать секреты Key Vault.
# Data-plane роли ("Key Vault Secrets User") — новая модель;
# старые access policies живут параллельно и до сих пор путают людей.
$ az role assignment create \
--assignee-object-id "$MI_PRINCIPAL_ID" --assignee-principal-type ServicePrincipal \
--role "Key Vault Secrets User" \
--scope "/subscriptions/$SUB/resourceGroups/rg-payments-prod-weu/providers/Microsoft.KeyVault/vaults/kv-payments-prod-weu"
# И право на базу — уже средствами самой SQL, а не ARM:
$ sqlcmd -S sql-payments-prod.database.windows.net -d payments -G -Q "
CREATE USER [mi-payments-prod] FROM EXTERNAL PROVIDER;
ALTER ROLE db_datareader ADD MEMBER [mi-payments-prod];
ALTER ROLE db_datawriter ADD MEMBER [mi-payments-prod];"
Проверка, что identity вообще работает, — прямой запрос к IMDS изнутри машины:
$ curl -s -H "Metadata: true" \
"http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https%3A%2F%2Fvault.azure.net" \
| jq -r '.access_token' | cut -c1-40
eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1
Если здесь пусто — identity не назначена ресурсу; если токен есть, а вызов сервиса даёт 403 — не хватает назначения роли. Это два разных класса ошибок, и различать их надо с первой минуты.
AKS с рабочей нагрузкой: workload identity вместо секретов
В Kubernetes managed identity не вешается на под напрямую — под не является ресурсом Azure. Раньше это решал aad-pod-identity с перехватом IMDS-трафика (снят с поддержки). Сейчас — Workload Identity: кластер публикует OIDC-issuer, ServiceAccount получает проецируемый токен, Entra ID меняет этот токен на свой по федеративному доверию.
projected token participant K as AKS OIDC issuer participant E as Entra ID participant KV as Key Vault Note over P,SA: kubelet монтирует токен в /var/run/secrets/azure/tokens/ P->>SA: читает проецируемый JWT P->>E: POST /oauth2/v2.0/token
client_assertion = этот JWT
client_id = user-assigned MI E->>K: запрашивает JWKS по OIDC issuer URL K-->>E: публичные ключи E->>E: проверяет подпись, issuer,
subject = system:serviceaccount:ns:sa E-->>P: access token на audience vault.azure.net P->>KV: GET /secrets/db-password (Bearer token) KV->>KV: проверяет RBAC-назначение для MI KV-->>P: значение секрета Note over P,KV: ни одного долгоживущего секрета
ни в манифесте, ни в образе, ни в git
Настройка целиком — три команды и две аннотации:
# 1. Включаем OIDC issuer и надстройку workload identity на существующем кластере
$ az aks update -g rg-payments-prod-weu -n aks-payments-prod \
--enable-oidc-issuer --enable-workload-identity
$ ISSUER=$(az aks show -g rg-payments-prod-weu -n aks-payments-prod \
--query oidcIssuerProfile.issuerUrl -o tsv)
$ echo "$ISSUER"
https://westeurope.oic.prod-aks.azure.com/8f3c.../4b21.../
# 2. Создаём user-assigned identity и федеративное доверие к конкретному ServiceAccount
$ az identity create -g rg-payments-prod-weu -n mi-payments-prod
$ az identity federated-credential create \
--identity-name mi-payments-prod -g rg-payments-prod-weu \
--name fc-payments \
--issuer "$ISSUER" \
--subject "system:serviceaccount:payments:payments-api" \
--audience api://AzureADTokenExchange
# 3. Манифесты: annotation связывает SA с identity, label включает вебхук инжекции
apiVersion: v1
kind: ServiceAccount
metadata:
name: payments-api
namespace: payments
annotations:
azure.workload.identity/client-id: "8b1e0f2a-4c9d-4f1a-9e77-2b6c5d3a1f04"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: payments-api
namespace: payments
spec:
replicas: 3
selector: { matchLabels: { app: payments-api } }
template:
metadata:
labels:
app: payments-api
azure.workload.identity/use: "true" # без этого лейбла токен не проецируется
spec:
serviceAccountName: payments-api
securityContext:
runAsNonRoot: true
seccompProfile: { type: RuntimeDefault }
containers:
- name: api
image: crpayments.azurecr.io/payments-api:1.42.0
ports: [{ containerPort: 8080 }]
env:
- name: KeyVaultName
value: kv-payments-prod-weu
resources:
requests: { cpu: "250m", memory: "256Mi" }
limits: { memory: "512Mi" } # limit по CPU обычно вреден
readinessProbe:
httpGet: { path: /healthz, port: 8080 }
periodSeconds: 5
Типичная ошибка: аннотация есть, лейбла нет — под стартует, токен не смонтирован, приложение молча падает в DefaultAzureCredential с таймаутом на IMDS. Проверка в одну строку:
$ kubectl exec -n payments deploy/payments-api -- ls /var/run/secrets/azure/tokens/
azure-identity-token
Подробнее про сам Kubernetes — https://courses.digitable.life/post/devops/06-kubernetes/, про доставку манифестов через GitOps — https://courses.digitable.life/post/devops/08-helm-and-gitops/.
Сеть: VNet и её счёт
VNet — прямой аналог VPC, но с несколькими отличиями, которые бьют по рукам.
Подсети не привязаны к зоне доступности. В AWS подсеть живёт в одной AZ, и мультизональность выражается через несколько подсетей. В Azure подсеть региональная, а зона выбирается у самого ресурса. Это упрощает планирование адресов и убирает целый класс ошибок «забыли подсеть в третьей AZ».
NSG вместо двух сущностей. Network Security Group — это одновременно и security group, и NACL: stateful-правила с приоритетами, применяются к подсети или к сетевому интерфейсу. Отдельного stateless-уровня нет. Плюс к NSG есть Application Security Group — способ группировать интерфейсы по роли и писать правила «web → app», не оперируя CIDR.
Исходящий трафик стал явным. Исторически виртуалка без публичного IP всё равно получала выход в интернет через неявный SNAT платформы. Этот default outbound access отключается для новых развёртываний, и теперь выход нужно задать явно: NAT Gateway, публичный IP или outbound-правило балансировщика. Не заметив этого, команды получают «внезапно перестал резолвиться nuget.org» на свежесозданной подсети.
Цена трафика. Внутри региона и одной VNet — включая обмен между зонами доступности — трафик сейчас не тарифицируется, и это заметное отличие от AWS с её 0,01 $/ГБ в каждую сторону за cross-AZ. Зато VNet peering платный в обе стороны, примерно по 0,01 $/ГБ, и hub-and-spoke архитектура с прогоном всего трафика через центральный firewall превращает эту строку в заметную. Egress в интернет: первые 100 ГБ в месяц бесплатно, дальше ступенчато от ~0,087 $/ГБ.
Сравнение сетевых примитивов и их цены — то место, где выбор облака действительно решается:
| Задача | AWS | Azure | Что стоит знать |
|---|---|---|---|
| Изолированная сеть | VPC | Virtual Network | В Azure подсеть региональная, не зональная |
| Фильтрация | Security Group + NACL | NSG + ASG | NSG stateful; NACL-аналога нет |
| Выход в интернет | NAT Gateway ~0,045 $/ч + 0,045 $/ГБ | NAT Gateway ~0,045 $/ч + 0,045 $/ГБ | Паритет; в Azure ещё есть outbound-правила LB |
| Приватный доступ к PaaS | VPC Endpoint / PrivateLink | Private Endpoint ~0,01 $/ч + 0,01 $/ГБ | Endpoint на каждый сервис; при 40 сервисах это заметно |
| Балансировка L7 | ALB | Application Gateway / Front Door | AppGW дороже ALB; Front Door — глобальный, ближе к CloudFront+Global Accelerator |
| Cross-AZ трафик | 0,01 $/ГБ в каждую сторону | не тарифицируется внутри VNet | Реальная экономия для чатливых кластеров |
| Соединение сетей | VPC Peering, Transit Gateway | VNet Peering, Virtual WAN | Peering платный с обеих сторон |
| Egress бесплатно | 100 ГБ/мес | 100 ГБ/мес | Дальше похожие ступени |
Отдельный сюжет — планирование IP-адресов под AKS. Это самая частая непоправимая ошибка в Azure: модель сети выбирается при создании кластера и не меняется.
Практика: берите Azure CNI Overlay по умолчанию, а классический CNI — только когда к поду обязаны обращаться напрямую из VNet (некоторые legacy-интеграции, специфические appliance). Overlay снимает потолок масштабирования и позволяет переиспользовать один и тот же pod CIDR во всех кластерах.
Вычисления: что брать под свой масштаб
У Azure пять способов запустить код, и разница между ними — это разница в эксплуатационной нагрузке, а не в производительности.
| Вариант | Цена (порядок) | Порог входа | Эксплуатационная нагрузка | Когда брать |
|---|---|---|---|---|
| App Service (B1 ~13 $/мес, P1v3 ~120 $/мес) | средняя | час | почти ноль: платформа обновляет ОС и рантайм | Классическое веб-приложение .NET, 1–10 сервисов, команда без выделенного DevOps |
| Azure Functions (Consumption: 1 млн вызовов бесплатно) | около нуля при простое | час | ноль, но холодный старт и лимиты | Обработчики событий, webhooks, cron, склейка сервисов |
| Container Apps (~0,000024 $/vCPU-с, есть бесплатный грант) | низкая при неровной нагрузке | день | низкая: Kubernetes внутри, но скрыт | Микросервисы, где нужен масштаб до нуля и KEDA, но не нужен API Kubernetes |
| AKS (control plane Free 0 $ / Standard ~0,10 $/ч, узлы отдельно) | зависит от узлов | недели | высокая: обновления, CNI, автоскейлинг, дежурства | Много команд, много сервисов, нужен собственный API-слой платформы |
| Virtual Machines / VMSS | самая низкая за vCPU | день | максимальная: ОС, патчи, образы | Легаси, лицензированный софт, специфические требования к ядру |
Ключевое, что часто понимают поздно: App Service и Container Apps закрывают потребности подавляющего большинства команд, а AKS оправдан числом команд, а не числом подов. Три сервиса в AKS — это отрицательная ценность: вы платите инженерным временем за гибкость, которой не пользуетесь. Тот же вывод мы делали про EKS в https://courses.digitable.life/post/devops/11-aws/, и он не зависит от облака.
Формула, по которой стоит принимать решение: сравните стоимость платформенной команды (в Европе — от 150 000 € в год на человека, и меньше двух человек в дежурстве по Kubernetes не бывает) со стоимостью надбавки управляемого сервиса. Container Apps дороже голых VM примерно вдвое за тот же vCPU-час; при счёте в 3000 $/мес это переплата 1500 $/мес, то есть 18 000 $ в год — заметно дешевле одного инженера.
AKS вглубь: то, что отличает его от EKS
Control plane бесплатен в Free-тарифе — редкая щедрость, у AWS EKS всегда 0,10 $/ч. Но Free-тариф не имеет финансово подкреплённого SLA и ограничен по числу узлов и по производительности плоскости управления. Для прода берите Standard (~0,10 $/ч, SLA 99,95 % с зонами) — это те же деньги, что EKS, но с честным обещанием. Premium (~0,60 $/ч) добавляет long-term support на версию Kubernetes до двух лет; берут те, кто физически не успевает обновляться раз в квартал.
Node pools вместо групп узлов. Ровно один системный пул (несёт CoreDNS, metrics-server, konnectivity) и сколько угодно пользовательских. Системный пул стоит держать отдельным и запрещать на нём прикладные поды:
$ az aks nodepool add -g rg-payments-prod-weu --cluster-name aks-payments-prod \
--name userpool1 --node-count 3 \
--node-vm-size Standard_D4s_v5 \
--zones 1 2 3 \
--enable-cluster-autoscaler --min-count 3 --max-count 20 \
--max-pods 110 \
--node-taints "workload=general:NoSchedule" \
--os-sku AzureLinux
# Проверяем разделение ролей
$ kubectl get nodes -L agentpool,topology.kubernetes.io/zone
NAME STATUS ROLES AGE VERSION AGENTPOOL ZONE
aks-systempool-2938471-vmss000000 Ready <none> 31d v1.31.3 systempool westeurope-1
aks-systempool-2938471-vmss000001 Ready <none> 31d v1.31.3 systempool westeurope-2
aks-userpool1-1847263-vmss000000 Ready <none> 6d v1.31.3 userpool1 westeurope-1
aks-userpool1-1847263-vmss000002 Ready <none> 6d v1.31.3 userpool1 westeurope-3
Обновление узлов — место, где ломаются кластеры. AKS обновляет пул по одному узлу, создавая surge-узлы. По умолчанию max-surge=1, что для пула из 20 узлов означает часы. Ставьте 33 % и обязательно настраивайте PodDisruptionBudget, иначе drain выселит все реплики сразу.
Автоскейлинг трёх уровней. Cluster Autoscaler на пуле (описан выше), HPA/KEDA на подах, и Node Autoprovisioning — реализация Karpenter под Azure, которая подбирает тип VM под ожидающие поды вместо масштабирования фиксированного пула. Плюс Virtual Nodes: поды уезжают в Azure Container Instances, когда узлов не хватает, — способ пережить всплеск без ожидания загрузки VM, но с ограничениями по сети и хранилищу.
Чего в AKS нет по сравнению с EKS: нет прямого аналога IRSA-простоты (workload identity делает то же, но настраивается многословнее), нет такого зрелого экосистемного слоя вокруг Spot, и Azure CNI исторически сложнее в отладке, чем VPC CNI. Чего нет в EKS: бесплатного control plane, встроенного контроллера GitOps (Flux ставится как надстройка кластера одной командой) и настолько же плотной интеграции с корпоративным каталогом — вход в кластер по учётке Entra ID с Conditional Access и MFA настраивается штатно.
# Вход в кластер через Entra ID: kubectl уходит за токеном в браузер,
# доступ управляется группами каталога, а не отдельным файлом aws-auth
$ az aks get-credentials -g rg-payments-prod-weu -n aks-payments-prod
$ kubectl get pods -n payments
To sign in, use a web browser to open https://microsoft.com/devicelogin
and enter the code F7K2QW9XZ to authenticate.
Инфраструктура как код: Bicep против Terraform
В Azure два законных пути, и выбор между ними — не вопрос вкуса.
Bicep — DSL Microsoft, транспилируется в ARM-шаблоны. Главное свойство: нет файла состояния. Состоянием является сам Azure Resource Manager, а what-if показывает разницу, опрашивая API. Это снимает целый класс проблем — блокировки стейта, дрейф, «терраформ хочет пересоздать всё». Взамен: Bicep умеет только Azure.
// main.bicep — AKS с workload identity и правами на ACR, без единого секрета
targetScope = 'resourceGroup'
param location string = resourceGroup().location
param clusterName string
param nodeCount int = 3
@allowed(['Free', 'Standard', 'Premium'])
param skuTier string = 'Standard'
resource acr 'Microsoft.ContainerRegistry/registries@2023-07-01' = {
name: 'cr${uniqueString(resourceGroup().id)}'
location: location
sku: { name: 'Premium' } // Premium нужен для private endpoint и гео-репликации
properties: { adminUserEnabled: false }
}
resource aks 'Microsoft.ContainerService/managedClusters@2024-05-01' = {
name: clusterName
location: location
identity: { type: 'SystemAssigned' }
sku: { name: 'Base', tier: skuTier }
properties: {
dnsPrefix: clusterName
oidcIssuerProfile: { enabled: true }
securityProfile: { workloadIdentity: { enabled: true } }
networkProfile: {
networkPlugin: 'azure'
networkPluginMode: 'overlay' // тот самый выбор, который потом не поменять
networkPolicy: 'cilium'
networkDataplane: 'cilium'
podCidr: '10.244.0.0/16'
serviceCidr: '10.0.0.0/16'
dnsServiceIP: '10.0.0.10'
}
agentPoolProfiles: [
{
name: 'systempool'
mode: 'System'
count: nodeCount
vmSize: 'Standard_D4s_v5'
availabilityZones: ['1', '2', '3']
osSKU: 'AzureLinux'
maxPods: 110
}
]
autoUpgradeProfile: { upgradeChannel: 'patch' } // патчи прилетают сами
}
}
// Право тянуть образы — назначение роли, а не пароль реестра в secret
resource acrPull 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
scope: acr
name: guid(acr.id, aks.id, 'AcrPull')
properties: {
principalId: aks.properties.identityProfile.kubeletidentity.objectId
principalType: 'ServicePrincipal'
roleDefinitionId: subscriptionResourceId(
'Microsoft.Authorization/roleDefinitions',
'7f951dda-4ed3-4680-a7ca-43fe172d538d') // AcrPull
}
}
output clusterFqdn string = aks.properties.fqdn
# Сухой прогон: ARM сам считает разницу с текущим состоянием
$ az deployment group what-if -g rg-payments-prod-weu \
--template-file main.bicep --parameters clusterName=aks-payments-prod
Resource and property changes are indicated with these symbols:
+ Create
~ Modify
= NoChange
The deployment will update the following scope:
Scope: /subscriptions/8f3c.../resourceGroups/rg-payments-prod-weu
~ Microsoft.ContainerService/managedClusters/aks-payments-prod
~ properties.agentPoolProfiles[0].count: 3 -> 5
+ Microsoft.ContainerRegistry/registries/crx7k2m9
Resource changes: 1 to create, 1 to modify, 12 no change.
Terraform с провайдером azurerm — то же самое, но с состоянием, модулями и, главное, с возможностью описать в одном плане Azure, Cloudflare, Datadog и GitHub. Подробнее про сам Terraform — https://courses.digitable.life/post/devops/09-terraform-and-iac/.
# backend.tf — состояние в Blob Storage с блокировкой через lease
terraform {
required_version = ">= 1.9"
required_providers {
azurerm = { source = "hashicorp/azurerm", version = "~> 4.0" }
}
backend "azurerm" {
resource_group_name = "rg-tfstate-shared-weu"
storage_account_name = "sttfstateshared"
container_name = "tfstate"
key = "payments/prod.tfstate"
use_azuread_auth = true # без ключей доступа к storage account
}
}
provider "azurerm" {
features {
key_vault { purge_soft_delete_on_destroy = false } # не даём terraform стирать секреты навсегда
}
subscription_id = var.subscription_id
}
resource "azurerm_kubernetes_cluster" "this" {
name = "aks-payments-prod"
location = var.location
resource_group_name = azurerm_resource_group.this.name
dns_prefix = "aks-payments-prod"
sku_tier = "Standard"
oidc_issuer_enabled = true
workload_identity_enabled = true
automatic_upgrade_channel = "patch"
local_account_disabled = true # только вход через Entra ID
default_node_pool {
name = "systempool"
vm_size = "Standard_D4s_v5"
zones = [1, 2, 3]
auto_scaling_enabled = true
min_count = 3
max_count = 6
os_sku = "AzureLinux"
only_critical_addons_enabled = true # taint CriticalAddonsOnly
upgrade_settings { max_surge = "33%" }
}
identity { type = "SystemAssigned" }
network_profile {
network_plugin = "azure"
network_plugin_mode = "overlay"
network_policy = "cilium"
network_data_plane = "cilium"
pod_cidr = "10.244.0.0/16"
service_cidr = "10.0.0.0/16"
dns_service_ip = "10.0.0.10"
}
lifecycle {
ignore_changes = [
# Автоскейлер меняет count вне terraform — не воюем с ним
default_node_pool[0].node_count,
# Патч-версию поднимает канал автообновления
kubernetes_version,
]
}
}
| Критерий | Bicep | Terraform (azurerm) |
|---|---|---|
| Состояние | нет, состояние — сам ARM | файл state, нужен backend и блокировки |
| Мультиоблачность | только Azure | любой провайдер в одном плане |
| Скорость появления новых API | день в день с релизом Azure | отставание от недель до месяцев |
| Порог входа | ниже, если знаком ARM | ниже, если знаком Terraform |
| Дрейф | не проблема: what-if спрашивает реальность |
требует refresh, конфликтует с ручными правками |
| Удаление ресурсов | сложнее: complete mode рискован | штатный destroy |
| Экосистема модулей | Azure Verified Modules | Registry, огромный |
| Кому | чистый Azure, команды внутри Microsoft-стека | смешанный ландшафт, уже есть Terraform |
Практический выбор: если весь ландшафт — Azure и вы не хотите заводить процесс вокруг стейта, Bicep честно лучше. Во всех остальных случаях Terraform.
CI/CD: GitHub Actions с OIDC и что стало с Azure DevOps
Долгоживущий service principal с секретом в переменной пайплайна — устаревшая и опасная практика. Правильный путь — federated credential: GitHub выдаёт OIDC-токен, Entra ID меняет его на свой.
# Доверяем ровно ветке main ровно одного репозитория
$ az ad app federated-credential create --id "$APP_ID" --parameters '{
"name": "gh-payments-main",
"issuer": "https://token.actions.githubusercontent.com",
"subject": "repo:contoso/payments:ref:refs/heads/main",
"audiences": ["api://AzureADTokenExchange"]
}'
# .github/workflows/deploy.yml
name: build-and-deploy
on:
push:
branches: [main]
permissions:
id-token: write # без этого OIDC-токен не выдадут
contents: read
env:
ACR: crpayments.azurecr.io
IMAGE: payments-api
jobs:
build:
runs-on: ubuntu-latest
outputs:
digest: ${{ steps.push.outputs.digest }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with: { dotnet-version: '9.0.x' }
- name: Тесты
run: dotnet test --configuration Release --logger trx
- uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }} # это идентификаторы, не секреты
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
# Логин в ACR тоже без пароля — по токену той же федерации
- run: az acr login --name crpayments
- id: push
run: |
TAG="${GITHUB_SHA::12}"
docker build -t "$ACR/$IMAGE:$TAG" .
docker push "$ACR/$IMAGE:$TAG"
echo "digest=$(docker inspect --format='{{index .RepoDigests 0}}' "$ACR/$IMAGE:$TAG")" >> "$GITHUB_OUTPUT"
deploy:
needs: build
runs-on: ubuntu-latest
environment: production # ручное подтверждение и защита ветки
steps:
- uses: actions/checkout@v4
- uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- uses: azure/aks-set-context@v4
with:
resource-group: rg-payments-prod-weu
cluster-name: aks-payments-prod
- name: Развёртывание по digest, а не по тегу
run: |
kubectl -n payments set image deploy/payments-api \
api="${{ needs.build.outputs.digest }}"
kubectl -n payments rollout status deploy/payments-api --timeout=5m
Развёртывание по digest, а не по тегу, — важная деталь: тег изменяем, digest нет, и только он даёт воспроизводимость. Разбор этой темы — в https://courses.digitable.life/post/devops/05-containers-and-registries/, а стратегии выкатки (canary, blue-green) — в https://courses.digitable.life/post/devops/04-cd-and-release-strategies/.
Про Azure DevOps: он жив, поддерживается и у многих корпоративных клиентов остаётся основным — особенно из-за Azure Boards и жёстких требований к аудиту. Но вектор развития Microsoft явно направлен в GitHub: новые возможности появляются там первыми. Для нового проекта в 2026 году брать Azure Pipelines стоит только при наличии конкретной причины — например, уже настроенных self-hosted агентов в закрытом контуре и лицензий. Сравнение платформ CI — https://courses.digitable.life/post/devops/03-modern-ci-platforms/.
Данные и наблюдаемость
Azure SQL Database — не «SQL Server в облаке», а отдельная PaaS-реализация: своя модель ресурсов (vCore или DTU), автоматические бэкапы с point-in-time restore, встроенная гео-репликация. Serverless-уровень умеет засыпать при простое и тарифицироваться посекундно — идеально для dev/test, опасно для прода из-за холодного старта в десятки секунд. Для переноса легаси, которому нужны SQL Agent, cross-database queries и CLR, существует Managed Instance — дороже, но совместимо почти полностью.
Ближайшая аналогия из мира AWS: Azure SQL DB ≈ Aurora по духу (переписанный движок под облако), Managed Instance ≈ RDS for SQL Server. Про моделирование данных и выбор СУБД — https://courses.digitable.life/post/databases/00-overview/.
Application Insights и Log Analytics — здесь Azure выигрывает у AWS в удобстве и проигрывает в цене. Автоматическая инструментация .NET даёт карту зависимостей, распределённые трассировки и профилирование без единой строки кода. Запросы пишутся на KQL, который по выразительности заметно превосходит CloudWatch Logs Insights:
// Топ медленных зависимостей за сутки с разбивкой по типу и перцентилями
dependencies
| where timestamp > ago(1d) and cloud_RoleName == "payments-api"
| summarize
calls = count(),
p50 = percentile(duration, 50),
p95 = percentile(duration, 95),
p99 = percentile(duration, 99),
fails = countif(success == false)
by type, target
| extend fail_rate = round(100.0 * fails / calls, 2)
| where calls > 100
| order by p95 desc
| take 20
Цена — примерно 2,30 $ за гигабайт приёма в Log Analytics. Приложение, пишущее debug-логи из тридцати подов, легко даёт 100 ГБ в сутки, то есть счёт за наблюдаемость больше счёта за вычисления. Меры: sampling в Application Insights (адаптивный включён по умолчанию, но его выключают «чтобы видеть всё»), Basic Logs-тариф для объёмных таблиц, retention и экспорт холодного в Storage. Подробно про метрики, SLO и дежурства — https://courses.digitable.life/post/devops/16-observability-and-oncall/.
Стоимость: реальный расчёт и главный рычаг Microsoft
Azure Hybrid Benefit — единственный аргумент, который делает Azure объективно дешевле AWS, и он работает только для Windows Server и SQL Server с активной Software Assurance. Разница не косметическая: лицензия Windows добавляет примерно 0,046 $ на ядро в час, то есть на четырёхъядерной машине — около 134 $/мес поверх цены железа. Для парка из пятидесяти Windows-машин это ~80 000 $ в год, которые Hybrid Benefit просто убирает. Для SQL Server экономия ещё выше.
Если ваш ландшафт — Linux и .NET на кроссплатформенном рантайме, этот рычаг не работает вовсе, и ценовое сравнение с AWS сводится к торгу по контракту.
Остальные механизмы скидок:
| Механизм | Скидка | Гибкость | Обязательство | Кому |
|---|---|---|---|---|
| Pay-as-you-go | 0 % | полная | нет | всплески, эксперименты |
| Spot VM | до 90 % | вытеснение с уведомлением ~30 с | нет | CI-агенты, батчи, stateless-пулы AKS |
| Savings plan for compute | до ~65 % | любые серии VM и регионы, App Service, Container Apps | $/час на 1 или 3 года | дефолт для смешанной нагрузки |
| Reserved VM Instances | до ~72 % | привязка к серии и региону, обмен возможен | 1 или 3 года | стабильный крупный парк одного типа |
| Azure Hybrid Benefit | до 40 % на Windows, до 55 % на SQL | полная, включается флагом | наличие Software Assurance | любой Windows/SQL-парк |
| Dev/Test подписка | Windows и SQL без лицензионной платы | только для непрода | подписка Visual Studio | стейджи, песочницы, CI |
Складываются они не аддитивно, а мультипликативно: reservation действует на вычислительную часть, Hybrid Benefit убирает лицензионную. Отсюда маркетинговые «до 80 %» — арифметика честная, но требует и SA, и трёхлетнего обязательства.
Разбор счёта из CLI — первое, что стоит настроить:
# Что растёт: разбивка за месяц по сервисам
$ az costmanagement query --type ActualCost --timeframe MonthToDate \
--scope "/subscriptions/$SUB" \
--dataset-aggregation '{"total":{"name":"Cost","function":"Sum"}}' \
--dataset-grouping name="ServiceName" type="Dimension" \
--query "properties.rows" -o tsv | sort -k1 -rn | head -8
1849.77 Virtual Machines
760.12 Azure SQL Database
512.40 Log Analytics <- почти как база данных, стоит проверить sampling
411.05 Azure Kubernetes Service
268.30 Bandwidth
154.90 Private Link
96.44 Storage
61.20 Key Vault
# Кто владеет: разбивка по тегу, который вы обязаны навязать политикой
$ az costmanagement query --type ActualCost --timeframe MonthToDate \
--scope "/subscriptions/$SUB" \
--dataset-grouping name="team" type="TagKey" --query "properties.rows" -o tsv
Теги в Azure не наследуются автоматически от подписки или RG к ресурсам. Это ровно та же проблема, что в AWS: без Modify-политики, дописывающей теги, отчёт получается на треть «ничьим». Политика с эффектом Modify и inheritTagFromResourceGroup решает вопрос централизованно, не трогая шаблоны команд.
Про сравнение экономики облака и своего железа целиком — https://courses.digitable.life/post/devops/15-cloud-cost-and-tradeoffs/.
Честное сравнение с AWS
Сначала карта соответствий — она нужна, чтобы читать чужую документацию:
| Функция | AWS | Azure |
|---|---|---|
| Изоляция биллинга | Account | Subscription |
| Группировка для политик | Organizations OU + SCP | Management Group + Azure Policy |
| Каталог пользователей | IAM Identity Center | Entra ID (сам тенант) |
| Роль без секретов | IAM Role + IRSA | Managed Identity + Workload Identity |
| Секреты | Secrets Manager / Parameter Store | Key Vault |
| Объектное хранилище | S3 | Blob Storage |
| Управляемый Kubernetes | EKS (0,10 $/ч) | AKS (Free 0 $ / Standard 0,10 $/ч) |
| Контейнеры без узлов | Fargate, App Runner | Container Apps, Container Instances |
| PaaS для веб-приложений | Elastic Beanstalk, App Runner | App Service |
| Функции | Lambda | Functions |
| Реляционные СУБД | RDS, Aurora | Azure SQL DB, MI, Flexible Server |
| NoSQL | DynamoDB | Cosmos DB |
| Реестр образов | ECR | ACR |
| CDN | CloudFront | Front Door / CDN |
| Метрики и логи | CloudWatch | Azure Monitor + Log Analytics |
| Аудит API | CloudTrail | Activity Log + Entra audit logs |
| IaC от вендора | CloudFormation, CDK | ARM, Bicep |
Теперь по существу — где реальная разница, а не разные слова:
Где Azure сильнее. Экономика Windows и SQL Server через Hybrid Benefit — не догоняется ничем. Единый корпоративный каталог: одна учётка с Conditional Access, MFA и группами покрывает и Microsoft 365, и вход в AKS, и доступ к базе. Managed identity удобнее и повсеместнее, чем IRSA. Application Insights даёт трассировки .NET-приложения буквально бесплатно по усилиям. Cross-AZ трафик внутри VNet не тарифицируется. Azure Policy умеет чинить, а не только запрещать. Регионов больше, и в ряде стран Azure присутствует там, где AWS нет.
Где AWS сильнее. Глубина и предсказуемость IAM: явный Deny, permission boundaries, симулятор политик — в Azure аналогичный контроль собирается из RBAC и Policy и получается многословнее. Зрелость и стабильность API: провайдеры и SDK ломаются реже, документация точнее. Ассортимент инстансов и глубина спот-рынка. Экосистема сторонних инструментов почти всегда сначала поддерживает AWS. Наконец, у Azure заметно чаще встречаются региональные сбои плоскости управления и «превью, которое живёт три года».
Где паритет. Цена базового вычисления и хранения различается на единицы процентов и определяется вашим контрактом, а не прайсом. Оба облака одинаково плохо прощают отсутствие тегов, отсутствие бюджетных алертов и отсутствие учений по восстановлению.
Стратегически важный вывод: мультиоблако как страховка от вендора почти всегда обходится дороже, чем риск, от которого страхует. Разумная версия — переносимость на уровне контейнеров и Kubernetes плюс IaC, чтобы миграция была проектом на квартал, а не на два года. Про другие облака и их ниши — следующая статья.
Типичные ошибки
- Удаление resource group «для порядка». Каскад необратим. Ставьте
CanNotDeletelock на прод-RG иReadOnly— на сетевые. - Путаница ролей Entra ID и Azure RBAC. Global Administrator без Owner не может ничего с ресурсами, Owner без ролей каталога не заведёт приложение. Раздавайте обе плоскости осознанно и через PIM с активацией по запросу.
- Free-тариф AKS в проде. Нет SLA на плоскость управления — при её деградации вы не сможете деплоить и масштабироваться, а обращение в поддержку не будет иметь опоры.
- Классический Azure CNI без расчёта адресов. Кластер упирается в подсеть, модель сети не меняется, лечится пересозданием. Считайте IP до создания.
publicNetworkAccess: Enabledна PaaS. Storage account, Key Vault и SQL по умолчанию доступны из интернета с аутентификацией. Закрывайте политикой и Private Endpoint.- Остановка VM из ОС вместо деаллокации.
shutdown -h nowвнутри гостя оставляет машину в состоянии Stopped и продолжает тарифицировать вычисления. Толькоaz vm deallocateостанавливает счёт (диски всё равно оплачиваются). - Диски и публичные IP от удалённых машин. Не удаляются автоматически; managed disk уровня Premium SSD на 1 ТБ — это ~135 $/мес за ничто.
- Игнорирование квот vCPU. Квоты выданы на семейство и регион; масштабирование в час пик упрётся в лимит, а не в деньги. Запрашивайте повышение заранее.
- Log Analytics без sampling и retention. Самая частая строка «почему счёт вырос» после виртуалок.
- Key Vault в горячем пути. Лимит порядка 2000 операций на 10 секунд на хранилище; приложение, читающее секрет на каждый запрос, получает 429. Кэшируйте и обновляйте по TTL.
- Секрет service principal в переменной пайплайна. Живёт год, утекает навсегда. Только federated credentials.
- Один тенант, одна подписка, всё в ней. Прод, стейдж и эксперименты, разделённые только именами RG, — то же самое, что один аккаунт AWS на всё.
- Ставка на превью-сервис. В Azure статус Preview держится долго, SLA не действует, а API меняется. Для прода — только GA.
- Отсутствие плана на региональный сбой. Paired regions — это про порядок восстановления Microsoft, а не про вашу репликацию. Гео-избыточность включается явно и стоит денег.
Практика продакшена: как это обычно устроено
Зрелая установка почти всегда сходится к схеме Cloud Adoption Framework: платформенные подписки отдельно, нагрузки отдельно, сеть — hub-and-spoke, доступы — через группы Entra ID с активацией по запросу.
Минимальный набор, который надо сделать в первую неделю и который потом окупается:
- Дерево management group и назначение Azure Policy на уровне Landing Zones: запрет публичного доступа к PaaS, обязательные теги через
Modify, разрешённые регионы, обязательные диагностические настройки черезDeployIfNotExists. - Отдельные подписки под прод, непрод и песочницу; на песочнице — бюджет с автоматическим действием при превышении.
- Log Analytics workspace в платформенной подписке, все диагностические логи стекаются туда политикой, а не руками.
- Federated credentials для CI вместо секретов, отдельное приложение на каждый репозиторий и каждую среду.
- PIM (Privileged Identity Management): постоянных Owner нет, роль активируется на четыре часа с обоснованием и MFA.
- Бюджеты и алерты на прогноз — до того, как счёт удивит, а не после.
- Учения по восстановлению раз в квартал: развернуть Bicep/Terraform в чистой подписке, восстановить базу из point-in-time, проверить целостность.
Схема сети, к которой приходят почти все:
или NVA"] BAS["Azure Bastion
доступ к VM без публичных IP"] DNS["Private DNS zones
privatelink.*"] VPN["VPN / ExpressRoute
к корпоративной сети"] end subgraph SPOKE1["Spoke: prod — подписка payments"] AKSN["AKS node subnet"] PE1["Private endpoints:
SQL, Key Vault, Storage"] end subgraph SPOKE2["Spoke: shared services"] ACRP["ACR с private endpoint"] MON["Log Analytics"] end ONPREM["Дата-центр компании
AD, легаси"] --- VPN SPOKE1 <-->|"peering, платный в обе стороны"| HUB SPOKE2 <--> HUB AKSN -->|"весь egress через firewall"| FW AKSN --> PE1 DNS -.->|"резолвинг приватных имён"| SPOKE1 DNS -.-> SPOKE2 style FW fill:#e76f51,color:#fff style HUB fill:#3d8bcd,color:#fff
Две вещи, которые в этой схеме дороже, чем кажется: peering-трафик (он платный с обеих сторон, а весь egress прогоняется через hub — то есть тарифицируется дважды) и Azure Firewall (около 1,25 $/час за Standard плюс обработка данных — под 1000 $/мес до единого пакета полезной нагрузки). Для команды из десяти человек это часто избыточно: NSG плюс NAT Gateway плюс Private Endpoint закрывают ту же модель угроз за десятки долларов. Firewall берут, когда есть требование централизованной инспекции от службы безопасности, а не потому что «так на референсной архитектуре».
Про безопасность конвейера, подпись образов и сканирование — https://courses.digitable.life/post/devops/17-security-in-pipeline/. Про сам язык, ради которого чаще всего приходят в Azure, — https://courses.digitable.life/post/csharp/00-overview/.
Мини-итог
- Иерархия Azure — четыре уровня: тенант (идентичность), management group (политики), подписка (биллинг и квоты), resource group (жизненный цикл). Изоляция строится подписками, RG — не граница безопасности.
- Две независимые системы ролей: Entra ID управляет каталогом, Azure RBAC — ресурсами. Явного
Denyв RBAC нет; роль запрета и починки играет Azure Policy. - Managed identity и workload identity — сильнейшая сторона платформы: приложение на .NET работает вообще без секретов, один и тот же код на ноутбуке и в проде.
- Сеть: подсети региональные, cross-AZ внутри VNet бесплатен, зато peering платный в обе стороны, а исходящий доступ теперь надо задавать явно. Модель CNI для AKS выбирается один раз и навсегда — считайте IP заранее.
- AKS даёт бесплатный control plane, но прод требует Standard. Выбирайте overlay, разделяйте системный и пользовательский пулы, настраивайте PDB до первого обновления.
- Порог входа важнее моды: App Service и Container Apps закрывают большинство задач, AKS оправдан числом команд, а не числом подов.
- Bicep выигрывает у Terraform в чистом Azure-ландшафте (нет стейта, API день в день), Terraform — везде, где облако не одно.
- Единственный объективный ценовой аргумент в пользу Azure — Azure Hybrid Benefit для Windows и SQL Server. Без Windows-лицензий сравнение с AWS сводится к вашему контракту.
Источники
- Azure Architecture Center — эталонные архитектуры и решения, включая раздел AWS to Azure services comparison.
- Cloud Adoption Framework: Azure landing zones — каноническая структура подписок, сети и политик.
- Azure Well-Architected Framework — пять столпов, включая Cost Optimization и Operational Excellence.
- AKS Baseline architecture — самый полный разбор продакшн-кластера с обоснованием каждого решения.
- Azure CNI Overlay networking — первоисточник по планированию адресов.
- Microsoft Entra Workload ID — федерация токенов для AKS и GitHub Actions.
- Azure RBAC documentation и Azure Policy effects — две половины модели доступа, читать вместе.
- Bicep documentation и Azure Verified Modules — официальный IaC-путь с проверенными модулями.
- Terraform AzureRM Provider — справочник по ресурсам и их аргументам.
- Azure Pricing Calculator и Azure Hybrid Benefit — расчёт до и главный рычаг экономии.
- Kusto Query Language reference — язык запросов Log Analytics; освоение окупается в первый же инцидент.
Что дальше
Azure и AWS — два крупнейших, но далеко не единственных варианта. У Google Cloud другая философия сети и лучший в отрасли управляемый Kubernetes, у Yandex Cloud и региональных провайдеров — своя ниша, определяемая законодательством и близостью поддержки. Об этом — следующая статья: Google Cloud, Yandex Cloud и другие облака: чем отличаются и когда их берут.