# Padrões Azure - MORPH Framework

> **Scope:** blazor-azure
> **Layer:** 2 (on keyword)
> **Keywords:** azure, identity, keyvault, credential, managed identity
> **Load When:** azure keywords detected

## 💰 Filosofia de Custos

**Free tier primeiro. Aprovação explícita para upgrade.**

| Nível | Limite | Requer |
|-------|--------|--------|
| Sem aprovação | Free tier apenas | Nada |
| Com aprovação | Até $10/mês | Confirmação |
| Acima de $10 | Justificativa detalhada | ADR |

---

## 🌐 Hosting: App Service vs Container Apps

### Matriz de Decisão

| Critério | App Service (Free F1) | Container Apps (Consumption) |
|----------|----------------------|------------------------------|
| **Custo** | ✅ $0/mês | ⚠️ ~$5/mês |
| **RAM** | 1GB | Configurável (0.5Gi min) |
| **Storage** | 1GB | Ephemeral |
| **CPU** | ⚠️ 60 min/dia | ✅ Ilimitado |
| **Disponibilidade** | ⚠️ Sleep após 20min | ✅ Scale-to-zero sem sleep |
| **SSL Customizado** | ❌ Não (apenas *.azurewebsites.net) | ✅ Sim, gratuito |
| **Scale Out** | ❌ Não | ✅ Auto-scaling |
| **Blazor Server** | ✅ Suporte nativo | ✅ Via Docker |
| **Deploy** | ✅ Direto (ZIP, Git) | ⚠️ Requer container |

### Quando Usar App Service Free

**✅ Cenários Ideais:**
- Protótipos e MVPs de baixo tráfego
- Aplicações de uso interno (horário comercial)
- Demos e POCs
- Apps que toleram cold start (20 min sleep)
- Orçamento zero absoluto

**❌ Não Usar Quando:**
- Necessita estar sempre disponível (24/7)
- Tráfego imprevisível ou spikes
- Mais de 60 min de CPU/dia
- Precisa de SSL customizado
- Requer auto-scaling

### Quando Usar Container Apps

**✅ Cenários Ideais:**
- Produção com disponibilidade 24/7
- Auto-scaling baseado em demanda
- SSL customizado necessário
- Arquitetura microserviços
- Background jobs com Hangfire (minReplicas: 1)
- Apps que precisam estar sempre "quentes"

**❌ Não Usar Quando:**
- Orçamento zero obrigatório
- Tráfego extremamente baixo (< 100 req/dia)
- MVP simples sem requisitos de SLA

### Estratégia Híbrida

```
Dev/Staging:    App Service Free F1
Production:     Container Apps Consumption
```

**Benefícios:**
- 💰 Economia em ambientes não críticos
- 🚀 Performance garantida em produção
- 🔄 Fácil migração (mesma stack .NET)

---

## 📋 Stack Padrão Aprovado

| Recurso | Tier | Custo | Quando Usar |
|---------|------|-------|-------------|
| **App Service** | Free F1 | $0 | MVP, protótipos, dev/staging |
| **Container Apps** | Consumption | ~$0-5/mês | Produção, auto-scaling |
| **Azure SQL** | Free 32GB | $0 | Database |
| **ACR** | Basic | ~$5/mês | Container registry (apenas com CA) |
| **App Insights** | Free 5GB | $0 | Logs e métricas |
| **Azure OpenAI** | gpt-4o-mini | ~$2-10/mês | Análises AI |

**Custo total típico:**
- **App Service Stack:** $0-2/mês (sem ACR)
- **Container Apps Stack:** $7-20/mês (com ACR)

### ⚠️ Requer Aprovação

| Recurso | Custo | Alternativa Free |
|---------|-------|------------------|
| Azure Functions | ~$0-5/mês | Hangfire |
| Service Bus | ~$10/mês | Queue em SQL |
| Cosmos DB | ~$25/mês | Azure SQL JSON |
| Redis Cache | ~$15/mês | In-memory |

---

## 🌐 App Service Free Tier

### Configuração Obrigatória
```bicep
resource appService 'Microsoft.Web/sites@2022-03-01' = {
  name: 'app-${projectName}-${environment}'
  location: location
  properties: {
    serverFarmId: appServicePlan.id
    httpsOnly: true
    siteConfig: {
      netFrameworkVersion: 'v8.0'
      alwaysOn: false        # ⚠️ OBRIGATÓRIO no Free tier
      minTlsVersion: '1.2'
    }
  }
}

resource appServicePlan 'Microsoft.Web/serverfarms@2022-03-01' = {
  name: 'plan-${projectName}-${environment}'
  location: location
  sku: {
    name: 'F1'               # Free tier
    tier: 'Free'
  }
}
```

### Limitações Importantes
- ⚠️ **CPU**: Apenas 60 minutos/dia (não contínuos)
- ⚠️ **Sleep**: App dorme após 20 minutos de inatividade
- ⚠️ **SSL**: Apenas `*.azurewebsites.net` (sem domínio customizado)
- ⚠️ **Escala**: Sem scale-out (apenas 1 instância)
- ✅ **Memória**: 1GB RAM
- ✅ **Storage**: 1GB disco

### Deploy
```bash
# Via Azure CLI
az webapp up --name app-myproject-dev --runtime "DOTNET:8.0"

# Via GitHub Actions
- task: AzureWebApp@1
  inputs:
    azureSubscription: 'Azure-Connection'
    appName: 'app-myproject-dev'
    package: '$(Build.ArtifactStagingDirectory)/**/*.zip'
```

### Quando Migrar para Container Apps
Se você observar:
- 🔴 CPU quota esgotada frequentemente
- 🔴 Cold starts afetando UX
- 🔴 Necessidade de SSL customizado
- 🔴 Tráfego crescendo (>1000 req/dia)

**→ Considere migrar para Container Apps Consumption**

---

## 🐳 Container Apps

### Configuração Obrigatória
```yaml
properties:
  template:
    scale:
      minReplicas: 0      # ⚠️ OBRIGATÓRIO: scale-to-zero
      maxReplicas: 2
    containers:
      - name: app
        resources:
          cpu: 0.25       # Mínimo
          memory: 0.5Gi   # Mínimo
```

### Scale-to-Zero
- ✅ **OBRIGATÓRIO** para dev/staging
- ⚠️ Em prod com Hangfire: `minReplicas: 1`
- 💰 Economia: ~80% vs always-on

---

## 🗄️ Azure SQL Free Tier

```
- 32 GB storage
- 100,000 vCore seconds/month
- Serverless compute
- Auto-pause after 1 hour idle
```

### Práticas
- ✅ Usar Managed Identity
- ✅ TDE habilitado (default)
- ❌ Não criar índices em excesso

---

## 🤖 Azure OpenAI

### Modelo Padrão: gpt-4o-mini

| Modelo | Custo Input | Custo Output | Usar |
|--------|-------------|--------------|------|
| **gpt-4o-mini** | $0.15/1M | $0.60/1M | ✅ PADRÃO |
| gpt-4o | $2.50/1M | $10/1M | Com aprovação |
| gpt-4 | $30/1M | $60/1M | ❌ NUNCA |

### Otimização
```csharp
var settings = new OpenAIPromptExecutionSettings
{
    MaxTokens = 500,  // Limitar resposta
    Temperature = 0.3 // Mais determinístico
};
```

---

## 🔐 Segurança

### Managed Identity (Preferido)
```csharp
// Para Azure SQL
"Authentication=Active Directory Managed Identity;"

// Para Azure OpenAI
var credential = new DefaultAzureCredential();
```

### Key Vault
Usar apenas para:
- Secrets de serviços externos
- API keys de terceiros

---

## 📛 Naming Conventions

```
{tipo}-{projeto}-{ambiente}

Exemplos:
- rg-myproject-dev          # Resource Group
- app-myproject-dev         # App Service
- plan-myproject-dev        # App Service Plan
- ca-myproject-dev          # Container App
- sql-myproject-dev         # SQL Server
- sqldb-myproject-dev       # SQL Database
- acr-myproject             # Container Registry
- appi-myproject-dev        # App Insights
- kv-myproject-dev          # Key Vault
```

| Ambiente | Sufixo | Características |
|----------|--------|-----------------|
| Development | -dev | Scale-to-zero, free tier |
| Staging | -stg | Scale-to-zero, free tier |
| Production | -prod | Min 1 replica |

---

## 🚀 Azure DevOps Pipelines

### Estratégia de Ambientes

**2 ambientes:**
- **Staging**: Desenvolvimento + QA (branch: `staging`)
  - Developers rodam projeto LOCAL
  - Acessam recursos REMOTOS staging
  - Deploy automático via pipeline

- **Produção**: Ambiente crítico (branch: `main`/`master`)
  - Deploy via pipeline com aprovação manual
  - Always-on, monitoramento 24/7

### Estrutura de Pipelines

**Stack Configuration** (`.azure/pipelines/`):
```
stacks/blazor-azure/.azure/pipelines/
├── staging-pipeline.yml      # Branch: staging
├── prod-pipeline.yml         # Branch: main/master
└── pipeline-variables.yml    # Variáveis compartilhadas
```

**Framework Templates** (`framework/templates/infrastructure/azure/pipelines/`):
```
framework/templates/infrastructure/azure/pipelines/templates/
├── build-dotnet.yml           # Build .NET projects
├── deploy-container-app.yml   # Deploy to Azure Container Apps
├── deploy-app-service.yml     # Deploy to Azure App Service
└── infra-deploy.yml           # Deploy Bicep infrastructure
```

**Usage in Pipelines:**
```yaml
# Reference framework templates with relative path
- template: ../../../../framework/templates/infrastructure/azure/pipelines/templates/build-dotnet.yml
  parameters:
    dotnetVersion: '8.x'
```

### Pipeline Staging

```yaml
# staging-pipeline.yml
trigger:
  branches:
    include: [staging]

variables:
  - template: pipeline-variables.yml
  - name: environment
    value: 'staging'
  - name: hostingType
    value: 'containerapp'

stages:
  - stage: Build
  - stage: DeployInfra
  - stage: BuildContainer
  - stage: DeployApp
    jobs:
      - deployment: DeployAppJob
        environment: 'staging'  # No approval
```

### Pipeline Production

```yaml
# prod-pipeline.yml
trigger:
  branches:
    include: [main, master]

variables:
  - template: pipeline-variables.yml
  - name: environment
    value: 'prod'
  - name: hostingType
    value: 'containerapp'

stages:
  - stage: Build
  - stage: SecurityScan
  - stage: DeployInfra
  - stage: BuildContainer
  - stage: DeployApp
    jobs:
      - deployment: DeployAppJob
        environment: 'production'  # Approval required
```

### Workload Identity (Sem Secrets)

Ao invés de Service Principals com secrets, use Workload Identity Federation:

```bash
# Criar App Registration com Federated Credential
az ad app create --display-name "myapp-prod-pipeline"

# Configurar federated credential
az ad app federated-credential create \
  --id <APP_ID> \
  --parameters @federated-credential.json
```

**Vantagens:**
- ✅ Sem secrets para gerenciar
- ✅ Rotação automática de tokens
- ✅ Mais seguro
- ✅ Auditoria melhorada

**Documentação completa:** `.azure/docs/azure-devops-setup.md`

---

## ✅ Checklist de Deploy

### Antes
- [ ] Testes passando
- [ ] Migrations aplicadas
- [ ] Secrets no Key Vault
- [ ] Managed Identity configurada

### Após
- [ ] Health check OK
- [ ] Logs no App Insights
- [ ] Funcionalidade testada
- [ ] Custos verificados

---

## ⚠️ Package Version Conflicts

### Azure.Identity Downgrade Error

**Error:**
```
NU1605: Detected package downgrade: Azure.Identity from 1.14.2 to 1.13.2
```

**Cause:** `Microsoft.Data.SqlClient` or other packages require a newer version.

**Solution:** Always specify `Azure.Identity` explicitly in your `.csproj`:

```xml
<!-- Prevent version conflicts -->
<PackageReference Include="Azure.Identity" Version="1.14.2" />
```

**Why this happens:**
- Transitive dependencies pull different versions
- NuGet picks the lowest common version
- This causes runtime failures with newer APIs

---

## 🔑 Key Vault Configuration

### Critical Rule

**NEVER condition Key Vault loading on environment. Load whenever URI is configured.**

**❌ WRONG:**
```csharp
// This breaks DI in development!
if (!string.IsNullOrEmpty(keyVaultUri) && !builder.Environment.IsDevelopment())
{
    builder.Configuration.AddAzureKeyVault(...);
}
```

**✅ CORRECT:**
```csharp
// Load Key Vault in ALL environments where URI exists
if (!string.IsNullOrEmpty(keyVaultUri))
{
    builder.Configuration.AddAzureKeyVault(
        new Uri(keyVaultUri),
        new DefaultAzureCredential());
}
```

**Why:**
- Services registered via DI may depend on Key Vault secrets
- `IBlobStorageService`, `IPaymentService`, etc. fail if secrets not loaded
- Development can use Azure Key Vault with developer credentials
- Or use `appsettings.Development.json` to override secrets locally

---

## 🚀 DefaultAzureCredential Optimization

### Problem

`DefaultAzureCredential` is **slow in containers** because it tries multiple authentication methods sequentially (environment, workload identity, managed identity, Visual Studio, CLI, etc.).

**Symptom:** Startup takes 30+ seconds, or times out.

### Solution: Disable Unused Credential Types

```csharp
// For Container Apps / AKS with Managed Identity ONLY
var credential = new DefaultAzureCredential(new DefaultAzureCredentialOptions
{
    // Disable all except Managed Identity
    ExcludeEnvironmentCredential = true,
    ExcludeWorkloadIdentityCredential = true,
    ExcludeSharedTokenCacheCredential = true,
    ExcludeVisualStudioCredential = true,
    ExcludeVisualStudioCodeCredential = true,
    ExcludeAzureCliCredential = true,
    ExcludeAzurePowerShellCredential = true,
    ExcludeAzureDeveloperCliCredential = true,
    ExcludeInteractiveBrowserCredential = true,
    ExcludeManagedIdentityCredential = false  // Keep this one!
});
```

### Environment-Specific Configuration

```csharp
// Program.cs - Smart credential selection
DefaultAzureCredential CreateCredential(IHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        // Development: Allow CLI, VS, VS Code
        return new DefaultAzureCredential();
    }

    // Production: Only Managed Identity (fast!)
    return new DefaultAzureCredential(new DefaultAzureCredentialOptions
    {
        ExcludeEnvironmentCredential = true,
        ExcludeWorkloadIdentityCredential = true,
        ExcludeSharedTokenCacheCredential = true,
        ExcludeVisualStudioCredential = true,
        ExcludeVisualStudioCodeCredential = true,
        ExcludeAzureCliCredential = true,
        ExcludeAzurePowerShellCredential = true,
        ExcludeAzureDeveloperCliCredential = true,
        ExcludeInteractiveBrowserCredential = true,
        ExcludeManagedIdentityCredential = false
    });
}
```

---

## 🐳 Container App Deployment Issues

### Problem: Container App Not Updating

After pushing a new image, Container App continues running the old version.

**Cause:** Same image digest doesn't trigger a new revision.

**Solution:** Force a new revision with a timestamp:

```bash
# Force new revision with environment variable
az containerapp update \
  --name ca-myapp-prod \
  --resource-group rg-myapp-prod \
  --set-env-vars "DEPLOY_TIMESTAMP=$(date +%s)"
```

### Complete Deploy Script

```bash
#!/bin/bash
# deploy-container-app.sh

APP_NAME="ca-myapp-prod"
RG_NAME="rg-myapp-prod"
ACR_NAME="acrmyapp"
IMAGE_TAG="latest"

# 1. Build and push
docker build --no-cache -t $ACR_NAME.azurecr.io/myapp:$IMAGE_TAG .
docker push $ACR_NAME.azurecr.io/myapp:$IMAGE_TAG

# 2. Update with timestamp (forces new revision)
az containerapp update \
  --name $APP_NAME \
  --resource-group $RG_NAME \
  --set-env-vars "DEPLOY_TIMESTAMP=$(date +%s)"

# 3. Verify
az containerapp show \
  --name $APP_NAME \
  --resource-group $RG_NAME \
  --query "properties.runningStatus"

# 4. Check logs
az containerapp logs show \
  --name $APP_NAME \
  --resource-group $RG_NAME \
  --follow
```

---

## 🔧 Troubleshooting Azure

Quick reference for common Azure issues:

### Key Vault access denied
→ Verify RBAC role assignment (Key Vault Secrets User)
→ Check Managed Identity is enabled on the resource
→ Verify Key Vault firewall allows the resource's IP/VNet

### Container App 404
→ Check ingress configuration (external/internal)
→ Verify health probe endpoint exists and returns 200
→ Check container is actually running (logs)

### Managed Identity not working
→ Verify identity is assigned to the resource
→ Check RBAC scope (subscription vs resource group vs resource)
→ Allow 5-10 minutes for propagation after assignment

### blazor.web.js 404 (.NET 10)
→ Add to `.csproj`:
```xml
<RequiresAspNetWebAssets>true</RequiresAspNetWebAssets>
```

### DefaultAzureCredential slow/timeout
→ Disable unused credential types (see section above)
→ Check network connectivity to Azure AD

### Container App not updating
→ Use `DEPLOY_TIMESTAMP` to force new revision (see section above)
→ Verify image was actually pushed to ACR
→ Check ACR webhook/event subscription

### EF Core migrations not applied
→ Add auto-migration to startup (dev/staging only)
→ Or run migration in pipeline before deploy

---

## 📚 Lessons Learned - Deploy

Key insights from production deployments:

1. **`docker build --no-cache`** is essential when debugging image issues
2. **`DEPLOY_TIMESTAMP`** forces new revision in Container Apps
3. **.NET 10 Preview** has undocumented breaking changes - check GitHub Issues
4. **GitHub Issues** are more effective than official docs for edge cases
5. **Auto-migration** simplifies deploy but has risks in production
6. **Key Vault in dev** needs developer credentials, not just prod Managed Identity
7. **Document while solving** - saves time later (hence this document!)
