feat(ms-ai-architect): add plugin to open marketplace (v1.5.0 baseline)

Initial addition of ms-ai-architect plugin to the open-source marketplace.
Private content excluded: orchestrator/ (Linear tooling), docs/utredning/
(client investigation), generated test reports and PDF export script.
skill-gen tooling moved from orchestrator/ to scripts/skill-gen/.

Security scan: WARNING (risk 20/100) — no secrets, no injection found.
False positive fixed: added gitleaks:allow to Python variable reference
in output-validation-grounding-verification.md line 109.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-04-07 17:17:17 +02:00
commit 6a7632146e
490 changed files with 213249 additions and 2 deletions

View file

@ -0,0 +1,498 @@
# AI Foundry Disaster Recovery Planning
**Last updated:** 2026-02
**Status:** GA
**Category:** Business Continuity & Disaster Recovery
---
## Introduksjon
Azure AI Foundry (tidligere Azure AI Studio / Azure Machine Learning) er Microsofts sentrale plattform for utvikling, evaluering og deployering av AI-modeller og agenter. Plattformen tilbyr imidlertid ikke automatisk failover eller disaster recovery ut av boksen -- dette er eksplisitt dokumentert av Microsoft. Det betyr at organisasjoner i norsk offentlig sektor som bygger forretningskritiske AI-loesninger pa AI Foundry, ma planlegge og implementere sin egen DR-strategi.
Disaster recovery for AI Foundry-prosjekter er mer kompleks enn for tradisjonelle webapplikasjoner. Et AI-prosjekt bestar av mange sammenkoblede komponenter: modelldeployeringer, datasett, pipeline-konfigurasjoner, agentdefinisjoner, tilkoblinger til eksterne tjenester, og tilhoerende infrastruktur som Azure Cosmos DB, Azure AI Search og Azure Storage. Tap av en enkelt komponent kan gjore hele AI-loesningen uoperativ. Saerlig for Foundry Agent Service er tilstandsdata (samtalehistorikk, agent-definisjoner, trad-kontekst) fordelt pa tvers av flere lagringstjenester, og det finnes per i dag ingen innebygd en-klikks eksport/import-funksjon for komplett gjenoppretting.
Denne referansen dekker prosjektdata-backup og replikering, modellversjonskontroll og gjenoppretting, configuration as code for reproduserbarhet, RTO/RPO-definisjoner for AI-prosjekter, og testing og validering av DR-prosedyrer. Alt er forankret i Microsofts offisielle veiledning for high availability og disaster recovery for AI Foundry.
## Prosjektdata-backup og replikering
### Komponentoversikt for AI Foundry-prosjekter
| Komponent | Lagringssted | Backup-ansvar | Replikeringsmetode |
|-----------|-------------|---------------|---------------------|
| Prosjektkonfigurasjon | AI Foundry control plane | Kunde (IaC) | Bicep/Terraform redeploy |
| Modelldeployeringer | Azure OpenAI / AI Foundry | Kunde (IaC) | Redeploy fra kildekontroll |
| Agentdefinisjoner | Cosmos DB (Standard mode) | Kunde | Cosmos DB continuous backup |
| Samtalehistorikk (traader) | Cosmos DB (`enterprise_memory`) | Kunde | Cosmos DB PITR |
| Kunnskapsfiler (agent) | Azure Storage | Kunde | GRS/GZRS replikering |
| Soekeindekser (agent) | Azure AI Search | Kunde | Manuell gjenskapning |
| Datasett og artefakter | Azure Storage (prosjekt) | Kunde | GRS/GZRS replikering |
| Notebook-filer og kode | Azure Storage | Kunde | Git + Azure Storage |
| Tilkoblinger og secrets | Azure Key Vault | Microsoft | Auto-failover til sekundaer region |
| Container images | Azure Container Registry | Microsoft* | Geo-replikering (konfigurer) |
| Application Insights | Log Analytics workspace | Kunde | Opprett i begge regioner |
> *Azure Container Registry ma konfigureres for geo-replikering av kunden, men Microsoft haandterer selve replikeringsmekanismen.
### Ressurskonfigurering for gjenoppretting
Microsoft anbefaler foelgende konfigurasjon **foer** en hendelse inntreffer:
```
+------------------------------------------------------------------+
| Ressurs | Anbefalt konfigurasjon |
+------------------------------------------------------------------+
| Foundry account | Purview-integrasjon for compliance |
| Foundry project | User-assigned managed identity |
| Agent Service | Standard deployment mode |
| Cosmos DB | Continuous backup med PITR |
| | Service-managed failover |
| | Read replication til failover-reg. |
| AI Search | Unikt navn (unnga kollisjon) |
| Storage account | GZRS (geo-zone-redundant) |
+------------------------------------------------------------------+
```
### Cosmos DB-konfigurasjon for agentdata
Cosmos DB er kritisk for Foundry Agent Service da all agent-tilstand lagres her:
```bash
# Aktiver continuous backup med 30-dagers PITR
az cosmosdb create \
--name svv-ai-cosmos-prod \
--resource-group rg-ai-foundry-prod \
--locations regionName="norwayeast" failoverPriority=0 \
--locations regionName="swedencentral" failoverPriority=1 \
--backup-policy-type Continuous \
--continuous-tier Continuous30Days \
--enable-automatic-failover true \
--default-consistency-level Session
```
> **Viktig:** Aktiver `Service-Managed Failover` slik at Cosmos DB automatisk kan bytte skriveregion fra primaerregion til sekundaerregion ved et langvarig regionalt utfall.
### Azure Storage-konfigurasjon
```bash
# Opprett GZRS storage account for prosjektdata
az storage account create \
--name svvaiprodstorage \
--resource-group rg-ai-foundry-prod \
--location norwayeast \
--sku Standard_GZRS \
--kind StorageV2 \
--min-tls-version TLS1_2 \
--allow-blob-public-access false
```
| Redundanstype | Beskrivelse | Anbefaling |
|---------------|-------------|------------|
| LRS | 3 kopier i en region | Kun for utvikling |
| ZRS | 3 kopier pa tvers av soner | Produksjon uten DR-krav |
| GRS | LRS + asynkron kopi til sekundaer region | Standard DR |
| GZRS | ZRS + asynkron kopi til sekundaer region | **Anbefalt for produksjon** |
| RA-GZRS | GZRS + lesetilgang til sekundaer region | Hoeyest tilgjengelighet |
## Modellversjonskontroll og gjenoppretting
### Versjonskontroll-strategi
AI-modeller gjennomgar kontinuerlig endring -- nye versjoner, fine-tuning, evaluering og deployering. En robust DR-plan krever sporbarhet og reproduserbarhet for alle modellversjoner.
```
Git Repository (kildekontroll)
|
+-- /models/
| +-- model-config.yaml # Modellkonfigurasjon
| +-- deployment-params.json # Deployment-parametere
| +-- evaluation-results/ # Evalueringsresultater per versjon
|
+-- /agents/
| +-- agent-definitions/ # JSON-definisjoner for agenter
| +-- knowledge-sources/ # Referanser til kunnskapsfiler
| +-- tool-bindings/ # Tool-konfigurasjoner
|
+-- /infrastructure/
| +-- bicep/ # IaC for alle ressurser
| +-- pipelines/ # CI/CD pipeline-definisjoner
|
+-- /prompts/
+-- system-prompts/ # System-prompter per agent/modell
+-- evaluation-datasets/ # Testdata for evaluering
```
### Modellregistrering og sporing
```yaml
# model-config.yaml -- Eksempel
model:
name: gpt-4o
version: "2024-11-20"
deployment_type: data_zone_standard
regions:
primary: norwayeast
secondary: swedencentral
quota:
primary_tpm: 120000
secondary_tpm: 120000
fine_tuning:
enabled: false
base_model: null
training_data: null
evaluation:
last_evaluated: "2026-01-15"
accuracy_score: 0.94
dataset: "eval-dataset-v3"
```
### Fine-tuned modeller
For fine-tuned modeller er det spesielt viktig med backup:
| Artefakt | Lagringssted | Backup-metode |
|----------|-------------|---------------|
| Treningsdata | Azure Storage | GZRS + versjonering |
| Modellvekter | AI Foundry model registry | Eksporter + lagre i Storage |
| Hyperparametere | Git (kildekontroll) | Standard Git-backup |
| Evalueringsresultater | AI Foundry + Git | Eksporter til Git |
| Deployment-konfig | Git (Bicep/Terraform) | Standard Git-backup |
> **Merk:** Global training (Public Preview) tilbyr rimeligere fine-tuning, men gir ikke datasuverenitet. For norsk offentlig sektor med strenge krav, bruk regional training i Norway East eller Sweden Central.
## Configuration as Code for reproduserbarhet
### Infrastruktur som kode (IaC)
Microsoft anbefaler eksplisitt a definere account, projects, capability host og avhengigheter i IaC (Bicep eller Terraform). IaC er kilden til sannhet for raskt a reprodusere konfigurasjon og rolletildelinger.
```bicep
// main.bicep -- AI Foundry prosjekt med DR-konfigurasjon
param primaryLocation string = 'norwayeast'
param secondaryLocation string = 'swedencentral'
param projectName string = 'svv-ai-project'
// Cosmos DB med continuous backup og failover
resource cosmosAccount 'Microsoft.DocumentDB/databaseAccounts@2024-05-15' = {
name: '${projectName}-cosmos'
location: primaryLocation
properties: {
databaseAccountOfferType: 'Standard'
consistencyPolicy: {
defaultConsistencyLevel: 'Session'
}
locations: [
{
locationName: primaryLocation
failoverPriority: 0
isZoneRedundant: true
}
{
locationName: secondaryLocation
failoverPriority: 1
isZoneRedundant: true
}
]
backupPolicy: {
type: 'Continuous'
continuousModeProperties: {
tier: 'Continuous30Days'
}
}
enableAutomaticFailover: true
}
}
// Storage med GZRS
resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = {
name: '${projectName}storage'
location: primaryLocation
sku: {
name: 'Standard_GZRS'
}
kind: 'StorageV2'
properties: {
minimumTlsVersion: 'TLS1_2'
allowBlobPublicAccess: false
supportsHttpsTrafficOnly: true
}
}
// AI Foundry project (primary region)
resource aiProject 'Microsoft.MachineLearningServices/workspaces@2024-04-01' = {
name: projectName
location: primaryLocation
identity: {
type: 'UserAssigned'
userAssignedIdentities: {
'${managedIdentity.id}': {}
}
}
properties: {
friendlyName: 'SVV AI Project'
storageAccount: storageAccount.id
keyVault: keyVault.id
applicationInsights: appInsights.id
}
}
```
### CI/CD Pipeline for dual-region deployment
```yaml
# azure-pipelines.yml
trigger:
branches:
include:
- main
stages:
- stage: DeployPrimary
displayName: 'Deploy to Norway East'
jobs:
- job: DeployInfra
steps:
- task: AzureCLI@2
inputs:
azureSubscription: 'svv-ai-prod'
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
az deployment group create \
--resource-group rg-ai-foundry-norwayeast \
--template-file infrastructure/bicep/main.bicep \
--parameters location=norwayeast
- stage: DeploySecondary
displayName: 'Deploy to Sweden Central'
dependsOn: DeployPrimary
jobs:
- job: DeployInfra
steps:
- task: AzureCLI@2
inputs:
azureSubscription: 'svv-ai-prod'
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
az deployment group create \
--resource-group rg-ai-foundry-swedencentral \
--template-file infrastructure/bicep/main.bicep \
--parameters location=swedencentral
- stage: DeployAgents
displayName: 'Deploy Agent Definitions'
dependsOn:
- DeployPrimary
- DeploySecondary
jobs:
- job: DeployAgentDefs
steps:
- script: |
python scripts/deploy-agents.py \
--config agents/agent-definitions/ \
--regions norwayeast swedencentral
```
### Viktige IaC-prinsipper for DR
1. **Bruk user-assigned managed identity** -- ved gjenskapning av ressurser forblir rolletildelinger gyldige
2. **Unnga usporede endringer i portalen** -- alle endringer gjennom IaC/pipeline
3. **Bygg IaC modulaert** -- uavhengig deployment per prosjekt
4. **Opprett rolletildelinger i IaC** -- ikke manuelt i portalen
5. **Deploy til begge regioner i samme pipeline** -- unnga drift
## RTO og RPO-definisjoner for AI-prosjekter
### Begrepsforklaring
| Begrep | Definisjon | Relevans for AI |
|--------|-----------|-----------------|
| **RTO** | Recovery Time Objective -- maks akseptabel tid for a gjenopprette | Hvor lenge kan AI-tjenesten vaere nede? |
| **RPO** | Recovery Point Objective -- maks akseptabelt datatap malt i tid | Hvor mye samtalehistorikk/data kan vi miste? |
| **MTTR** | Mean Time To Recovery -- gjennomsnittlig gjenopprettingstid | Faktisk maalt gjenopprettingstid |
| **MTBF** | Mean Time Between Failures -- gjennomsnittlig tid mellom feil | Paalitelighetsmal for AI-tjenesten |
### Anbefalte RTO/RPO per komponent
| Komponent | RTO-mal | RPO-mal | Gjenopprettingsmetode |
|-----------|---------|---------|----------------------|
| **Azure OpenAI inference** | < 5 min | N/A (stateless) | Automatisk failover via gateway |
| **Agent Service** | < 30 min | < 1 time | Redeploy fra IaC + Cosmos PITR |
| **Samtalehistorikk** | < 2 timer | < 5 min | Cosmos DB continuous backup |
| **Kunnskapsbaser (RAG)** | < 1 time | < 24 timer | Reindeksering fra kilde |
| **Fine-tuned modeller** | < 4 timer | < 24 timer | Redeploy fra model registry |
| **Pipeline/evaluering** | < 8 timer | < 24 timer | Redeploy fra Git |
### Tier-basert DR-strategi
```
Tier 1 -- Virksomhetskritisk (RTO < 5 min, RPO ~0)
- Azure OpenAI inference med multi-region gateway
- Automatisk failover via APIM backend pool
- Eksempel: Innbyggertjenester, sanntids beslutningsstotte
Tier 2 -- Forretningsviktig (RTO < 30 min, RPO < 1 time)
- Agent Service med Cosmos DB failover
- Forhands-deployert sekundaer region (warm standby)
- Eksempel: Intern chatbot, saksbehandlingsassistent
Tier 3 -- Stottende (RTO < 4 timer, RPO < 24 timer)
- Manuell gjenskapning fra IaC
- Cold standby i sekundaer region
- Eksempel: Batch-analysejobber, treningspipelines
```
## Testing og validering av DR-prosedyrer
### DR-testrammeverk
| Testtype | Frekvens | Omfang | Ansvarlig |
|----------|----------|--------|-----------|
| **Tabletop exercise** | Kvartalsvis | Gjennomgang av prosedyrer | Arkitekturteam |
| **Komponent-failover** | Manedlig | Enkeltkomponent (f.eks. Cosmos DB) | Driftsteam |
| **Full DR-drill** | Halvaarlig | Komplett failover til sekundaer region | Hele teamet |
| **Chaos engineering** | Lopende | Automatisert feilinjeksjon | CI/CD pipeline |
### DR-testprosedyre
```
Fase 1: Forberedelse (1 dag foer)
[ ] Verifiser at IaC er oppdatert og synkronisert
[ ] Bekreft at Cosmos DB backup er aktiv og fungerer
[ ] Sjekk at sekundaer region har tilstrekkelig kvote
[ ] Varsle relevante interessenter
Fase 2: Simulert utfall (testdag)
[ ] Deaktiver primaer region i gateway (APIM policy-endring)
[ ] Verifiser at trafikk rutes til sekundaer region
[ ] Kjoer funksjonelle tester mot sekundaer region
[ ] Mal faktisk RTO og RPO
Fase 3: Validering (under test)
[ ] Verifiser AI-inferens fungerer korrekt
[ ] Sjekk at agentsamtaler kan fortsette
[ ] Kontroller at data-konsistens er ivaretatt
[ ] Verifiser overvaking og varsling
Fase 4: Tilbakeforing (etter test)
[ ] Reaktiver primaer region
[ ] Verifiser at trafikk returnerer til normalt moenster
[ ] Dokumenter resultater og avvik
[ ] Oppdater DR-plan basert pa laerdommer
```
### Azure Chaos Studio-integrasjon
Bruk Azure Chaos Studio for automatisert feilinjeksjon:
```json
{
"type": "Microsoft.Chaos/experiments",
"name": "ai-foundry-dr-test",
"properties": {
"steps": [
{
"name": "CosmosDB-failover",
"branches": [
{
"name": "main",
"actions": [
{
"type": "continuous",
"name": "urn:csci:microsoft:cosmosDB:failover/1.0",
"duration": "PT10M",
"parameters": [
{
"key": "readRegion",
"value": "Norway East"
}
],
"selectorId": "cosmos-selector"
}
]
}
]
}
],
"selectors": [
{
"id": "cosmos-selector",
"type": "List",
"targets": [
{
"id": "/subscriptions/.../cosmosdb-account",
"type": "ChaosTarget"
}
]
}
]
}
}
```
### Dokumentasjon av DR-tester
| Felt | Beskrivelse |
|------|-------------|
| **Testdato** | Dato og tidspunkt for testen |
| **Testtype** | Tabletop / Komponent / Full DR |
| **Deltakere** | Navn og roller |
| **Scenario** | Hva ble simulert |
| **Faktisk RTO** | Malt gjenopprettingstid |
| **Faktisk RPO** | Malt datatap |
| **Avvik fra mal** | Var det gap mellom mal og resultat? |
| **Funn og laerdommer** | Hva fungerte, hva ma forbedres? |
| **Tiltak** | Konkrete forbedringspunkter med eier og frist |
## Gjenopprettingsprosedyre ved regionalt utfall
### Steg-for-steg gjenoppretting
```
1. DETEKSJON (automatisk)
- Azure Monitor-varsling om regionalt utfall
- Health check-feil fra APIM gateway
2. VURDERING (5-10 min)
- Er utfallet midlertidig eller vedvarende?
- Hvilke tjenester er pavirket?
- Utloes DR-plan hvis utfall > 15 min
3. FAILOVER (15-30 min)
- Oppdater APIM til a rute all trafikk til sekundaer region
- Verifiser Cosmos DB automatisk failover
- Deploy manglende agentdefinisjoner fra kildekontroll
- Oppdater DNS hvis relevant
4. VALIDERING (15 min)
- Kjoer smoke tests mot sekundaer region
- Verifiser at alle endepunkter responderer
- Kontroller data-tilgjengelighet
5. KOMMUNIKASJON
- Varsle interne brukere om situasjonen
- Oppdater statusside
6. FAILBACK (nar primaer region er tilbake)
- Verifiser primaer region er stabil
- Gradvis ruter trafikk tilbake
- Synkroniser eventuelle endringer fra sekundaer region
```
## Referanser
- [High availability and resiliency for Microsoft Foundry projects and Agent Services](https://learn.microsoft.com/en-us/azure/ai-foundry/how-to/high-availability-resiliency)
- [Foundry Agent Service disaster recovery](https://learn.microsoft.com/en-us/azure/ai-foundry/how-to/agent-service-disaster-recovery)
- [Foundry Agent Service resource and data loss recovery](https://learn.microsoft.com/en-us/azure/ai-foundry/how-to/agent-service-operator-disaster-recovery)
- [High availability and disaster recovery for hub projects](https://learn.microsoft.com/en-us/azure/ai-foundry/how-to/hub-disaster-recovery)
- [Azure security baseline for Azure AI Foundry - Backup and recovery](https://learn.microsoft.com/en-us/security/benchmark/azure/baselines/azure-ai-foundry-security-baseline#backup-and-recovery)
- [Continuous backup with point-in-time restore in Azure Cosmos DB](https://learn.microsoft.com/en-us/azure/cosmos-db/continuous-backup-restore-introduction)
## For Cosmo
- **Bruk denne referansen** nar kunden planlegger disaster recovery for AI Foundry-prosjekter, spesielt nar det gjelder Agent Service, fine-tuned modeller, eller komplekse AI-pipelines som ma overleve regionalt utfall.
- **Fremhev at AI Foundry IKKE tilbyr automatisk failover** -- dette er kundens ansvar. Krav til IaC, dual-region deployment og Cosmos DB continuous backup ma kommuniseres tydelig.
- **Anbefal user-assigned managed identity** som standard -- dette forenkler gjenoppretting dramatisk ved a eliminere behovet for a gjenskape rolletildelinger.
- **Tilpass RTO/RPO-maler til organisasjonens faktiske behov** -- ikke alle AI-tjenester er virksomhetskritiske. Bruk tier-modellen for a differensiere innsats og kostnad.
- **Undersstrek viktigheten av regelmessig DR-testing** -- en DR-plan som ikke er testet er ingen plan. Anbefal kvartalsvis tabletop og halvaarlig full DR-drill som minimum.

View file

@ -0,0 +1,477 @@
# Backup and Recovery Strategies for AI Workloads
**Last updated:** 2026-02
**Status:** GA
**Category:** Business Continuity & Disaster Recovery
---
## Introduksjon
Backup- og gjenopprettingsstrategier for AI-arbeidsbelastninger skiller seg vesentlig fra tradisjonelle applikasjoner. En AI-loesning bestar ikke bare av applikasjonskode og databaser, men ogsaa av trenede modeller, datasett, fine-tuning-artefakter, embedding-indekser, agentdefinisjoner, samtalelogger og pipeline-konfigurasjoner. Hvert av disse elementene har ulike krav til backup-frekvens, retensjonstid og gjenopprettingsmetode. Et tap av treningsdata kan bety uker med tapt arbeid, mens et tap av embedding-indekser kan gjenopprettes ved reindeksering fra kildedata.
For norsk offentlig sektor er backup-strategien ogsaa underlagt regulatoriske krav. Arkivloven, Forvaltningsloven og GDPR stiller krav til datalagring, personvern og etterproevbarhet. AI-systemer som prosesserer personopplysninger ma ha backup-rutiner som baade ivaretar gjenopprettingsbehovet og dataminimeringsprinsippet -- man skal ikke oppbevare mer data enn noedvendig, men man ma kunne gjenopprette det som er paakrevd. Azure Backup, Azure Storage-redundans og tjenestespesifikke backup-mekanismer gir et robust verktoeysett for dette.
Denne referansen dekker inkrementell versus full backup, point-in-time recovery for datasett, snapshot-administrasjon og retensjonsregler, off-region backup-lagring, og automatisering og planlegging av backups. Fokus er pa Azure-tjenester som er relevante for AI-arbeidsbelastninger, med konkrete konfigurasjonseksempler og kostnadsoverveielser.
## Inkrementell versus full backup
### Backup-typer for AI-arbeidsbelastninger
| Backup-type | Beskrivelse | Fordeler | Ulemper | Best for |
|-------------|-------------|---------|---------|----------|
| **Full backup** | Komplett kopi av alle data | Enklest gjenoppretting | Stoerst lagringsbehov, lengst tid | Ukentlig baseline |
| **Inkrementell** | Kun endringer siden forrige backup | Minst lagring, raskest | Krever alle inkrementelle + siste fulle | Daglig / flere ganger daglig |
| **Differensiell** | Endringer siden siste fulle backup | Raskere gjenoppretting enn inkrementell | Stoerre enn inkrementell | Daglig supplement til ukentlig full |
| **Continuous** | Lopende replikering av endringer | Lavest RPO (naer sanntid) | Hoeyest kostnad | Virksomhetskritiske data |
### Anbefalt backup-strategi per AI-komponent
| Komponent | Backup-type | Frekvens | Begrunnelse |
|-----------|------------|----------|-------------|
| **Azure OpenAI konfig** | IaC (Git) | Ved endring | Stateless tjeneste, konfig er alt |
| **Cosmos DB (agentdata)** | Continuous | Lopende | Forretningskritisk tilstandsdata |
| **Azure Storage (datasett)** | Inkrementell | Daglig | Store datamengder, lavt endringsvolum |
| **Azure SQL (strukturerte data)** | Full + diff | Full ukentlig, diff daglig | Relasjonelle data med transaksjonslogg |
| **Azure AI Search indekser** | Ingen backup* | Ved behov | Gjenskap fra kildedata |
| **Fine-tuned modellvekter** | Full | Ved ny versjon | Ikke inkrementelt mulig |
| **Treningsdata** | Inkrementell + versjonering | Daglig | Storrelse og endringshastighet |
| **System-prompter** | Git | Ved endring | Tekst, versjonskontroll er nok |
| **Evalueringsresultater** | Full | Etter hver evaluering | Relativt sma data |
> *Azure AI Search-indekser kan ikke backes opp direkte. Gjenopprett ved reindeksering fra originale kildedata i Azure Storage eller Cosmos DB.
### Azure Backup for AI-relaterte ressurser
Azure Backup stoetter foelgende ressurser relevant for AI-arbeidsbelastninger:
```
+------------------------------------------+------------------+------------------+
| Ressurs | Azure Backup | Nativ backup |
+------------------------------------------+------------------+------------------+
| Azure Virtual Machines (GPU/compute) | Ja | Nei |
| Azure Managed Disks | Ja | Snapshots |
| Azure Files (SMB/NFS) | Ja | Snapshots |
| Azure Blob Storage | Ja (operational) | Versjonering |
| Azure SQL Database | Ja | Auto-backup |
| Azure Database for PostgreSQL | Ja | Auto-backup |
| Azure Cosmos DB | Nei* | Continuous/PITR |
| Azure AI Foundry | Nei | Nei |
| Azure AI Search | Nei | Nei |
+------------------------------------------+------------------+------------------+
```
> *Cosmos DB har sin egen continuous backup-mekanisme og bruker ikke Azure Backup.
## Point-in-time Recovery for datasett
### Azure Blob Storage -- Versjonering og Soft Delete
For datasett lagret i Azure Blob Storage er versjonering og soft delete de viktigste mekanismene for point-in-time recovery:
```bash
# Aktiver blob-versjonering pa storage account
az storage account blob-service-properties update \
--account-name svvaistorage \
--resource-group rg-ai-prod \
--enable-versioning true
# Aktiver soft delete for blobs (30 dagers retensjonstid)
az storage account blob-service-properties update \
--account-name svvaistorage \
--resource-group rg-ai-prod \
--delete-retention-days 30 \
--enable-delete-retention true
# Aktiver soft delete for containere
az storage account blob-service-properties update \
--account-name svvaistorage \
--resource-group rg-ai-prod \
--container-delete-retention-days 30 \
--enable-container-delete-retention true
```
### Azure Blob -- Operational Backup med Azure Backup
Operational backup for Azure Blobs gir point-in-time restore:
```bash
# Opprett backup vault
az dataprotection backup-vault create \
--vault-name svv-ai-backup-vault \
--resource-group rg-ai-prod \
--location norwayeast \
--type SystemAssigned \
--storage-setting "DataStoreType=VaultStore;Type=LocallyRedundant"
# Opprett backup-policy for blobs (30 dagers retensjon)
az dataprotection backup-policy create \
--vault-name svv-ai-backup-vault \
--resource-group rg-ai-prod \
--name blob-backup-policy-30d \
--policy '{
"policyRules": [{
"name": "Default",
"objectType": "AzureRetentionRule",
"lifecycles": [{
"deleteAfter": {
"objectType": "AbsoluteDeleteOption",
"duration": "P30D"
},
"sourceDataStore": {
"objectType": "DataStoreInfoBase",
"dataStoreType": "OperationalStore"
}
}],
"isDefault": true
}],
"datasourceTypes": ["Microsoft.Storage/storageAccounts/blobServices"]
}'
```
### Cosmos DB -- Continuous Backup med PITR
Cosmos DB tilbyr to nivaaer av continuous backup:
| Egenskap | Continuous 7-day | Continuous 30-day |
|----------|-----------------|-------------------|
| Retensjonsperiode | 7 dager | 30 dager |
| Backup-lagringskostnad | Gratis | $0.20/GB * antall regioner |
| Restore-kostnad | $0.15/GB | $0.15/GB |
| Granularitet | Vilkaarlig tidspunkt innenfor retensjon | Vilkaarlig tidspunkt innenfor retensjon |
| Restore-mal | Ny konto eller eksisterende konto | Ny konto eller eksisterende konto |
```bash
# Gjenopprett Cosmos DB til et bestemt tidspunkt
az cosmosdb restore \
--account-name svv-ai-cosmos-prod \
--resource-group rg-ai-prod \
--target-database-account-name svv-ai-cosmos-restored \
--restore-timestamp "2026-02-10T14:30:00Z" \
--location norwayeast
```
> **Viktig:** Ved gjenoppretting opprettes alltid en ny konto. Foelgende konfigurasjoner gjenopprettes IKKE automatisk og ma rekonfigureres: brannmurregler, VNet-innstillinger, RBAC-tildelinger, private endpoints, lagrede prosedyrer, triggere og UDF-er.
### Azure SQL Database -- Point-in-time Restore
For AI-loesninger som bruker Azure SQL for strukturerte data:
```bash
# Gjenopprett Azure SQL til et bestemt tidspunkt
az sql db restore \
--resource-group rg-ai-prod \
--server svv-ai-sqlserver \
--name ai-metadata-db \
--dest-name ai-metadata-db-restored \
--time "2026-02-10T14:30:00Z"
```
| Retensjonsperiode | Standard | Konfigurerbar |
|-------------------|----------|---------------|
| Korttidsretensjon (PITR) | 7 dager | 1-35 dager |
| Langtidsretensjon (LTR) | Ikke aktivert | Opptil 10 aar |
## Snapshot-administrasjon og retensjon
### Snapshot-strategi for AI-infrastruktur
Snapshots er raske, kostnadseffektive kopier av data pa et bestemt tidspunkt. For AI-arbeidsbelastninger er de spesielt nyttige for VM-baserte compute-noder og managed disks.
| Ressurs | Snapshot-type | Maks snapshots | Anbefalt retensjon |
|---------|--------------|----------------|-------------------|
| Azure Managed Disks | Inkrementell | 500 per disk | 30-90 dager |
| Azure Files | Share snapshot | 200 per share | 30 dager |
| Azure Blob | Blob versjon | Ubegrenset* | 30-365 dager |
| VM (via Azure Backup) | App-consistent | Avhenger av policy | 30-90 dager |
> *Ubegrenset antall versjoner, men lagringskostnader oeker. Bruk lifecycle management for a haandtere retensjon.
### Azure Managed Disk Backup
For GPU-VM-er og compute-intensive AI-arbeidsbelastninger:
```bash
# Opprett backup-policy for managed disks
# Daglig backup med 30 dagers retensjon
az dataprotection backup-policy create \
--vault-name svv-ai-backup-vault \
--resource-group rg-ai-prod \
--name disk-backup-daily-30d \
--policy '{
"policyRules": [
{
"name": "BackupDaily",
"objectType": "AzureBackupRule",
"trigger": {
"objectType": "ScheduleBasedTriggerContext",
"schedule": {
"repeatingTimeIntervals": ["R/2026-01-01T02:00:00+00:00/P1D"]
}
},
"dataStore": {
"objectType": "DataStoreInfoBase",
"dataStoreType": "OperationalStore"
}
},
{
"name": "Default",
"objectType": "AzureRetentionRule",
"lifecycles": [{
"deleteAfter": {
"objectType": "AbsoluteDeleteOption",
"duration": "P30D"
},
"sourceDataStore": {
"objectType": "DataStoreInfoBase",
"dataStoreType": "OperationalStore"
}
}],
"isDefault": true
}
],
"datasourceTypes": ["Microsoft.Compute/disks"]
}'
```
> **Merk:** Azure Disk Backup bruker inkrementelle snapshots som er begrenset til 500 per disk. Med daglig backup betyr dette maks ~450 dagers retensjon (50 reservert for on-demand backups).
### Lifecycle Management for Azure Blob Storage
Automatisk haandtering av eldre datasett og backup-data:
```json
{
"rules": [
{
"name": "dataset-lifecycle",
"type": "Lifecycle",
"definition": {
"filters": {
"blobTypes": ["blockBlob"],
"prefixMatch": ["datasets/", "training-data/"]
},
"actions": {
"baseBlob": {
"tierToCool": {
"daysAfterModificationGreaterThan": 30
},
"tierToArchive": {
"daysAfterModificationGreaterThan": 90
},
"delete": {
"daysAfterModificationGreaterThan": 365
}
},
"snapshot": {
"tierToCool": {
"daysAfterCreationGreaterThan": 30
},
"delete": {
"daysAfterCreationGreaterThan": 90
}
},
"version": {
"tierToCool": {
"daysAfterCreationGreaterThan": 30
},
"delete": {
"daysAfterCreationGreaterThan": 90
}
}
}
}
}
]
}
```
## Off-region backup-lagring
### Azure Storage-redundans for backup
| Redundanstype | Regioner | Tilgjengelighet | Kostnad (relativ) | Anbefaling |
|---------------|---------|-----------------|-------------------|------------|
| **LRS** | 1 region, 3 kopier | 99.999999999% (11 niere) | 1x | Kun utvikling |
| **ZRS** | 1 region, 3 soner | 99.9999999999% (12 niere) | ~1.25x | Produksjon uten DR |
| **GRS** | 2 regioner, 6 kopier | 99.99999999999999% (16 niere) | ~2x | Standard DR |
| **GZRS** | 2 regioner, 6 kopier (3 soner + 3) | Hoeyest | ~2.5x | **Anbefalt for AI prod** |
| **RA-GRS/RA-GZRS** | Som GRS/GZRS + lesetilgang | Hoeyest + lestilgang | ~2.5-3x | Lese-intensiv DR |
### Konfigurering av geo-redundant backup
```bash
# Opprett Recovery Services vault med GRS for VM-backup
az backup vault create \
--name svv-ai-recovery-vault \
--resource-group rg-ai-prod \
--location norwayeast
# Sett storage-redundans til geo-redundant
az backup vault backup-properties set \
--name svv-ai-recovery-vault \
--resource-group rg-ai-prod \
--backup-storage-redundancy GeoRedundant
# Aktiver Cross Region Restore
az backup vault backup-properties set \
--name svv-ai-recovery-vault \
--resource-group rg-ai-prod \
--cross-region-restore-flag Enabled
```
### Off-region backup-arkitektur for AI-data
```
Norway East (primaer) Sweden Central (sekundaer)
+---------------------------+ +---------------------------+
| AI Foundry Project | | (Replikert data) |
| +---------------------+ | async | +---------------------+ |
| | Storage (GZRS) |------copy--->| | Storage (read) | |
| +---------------------+ | | +---------------------+ |
| +---------------------+ | auto | +---------------------+ |
| | Cosmos DB |---failover->| | Cosmos DB (replica) | |
| +---------------------+ | | +---------------------+ |
| +---------------------+ | geo-rep | +---------------------+ |
| | Container Registry |------copy--->| | Container Registry | |
| +---------------------+ | | +---------------------+ |
| +---------------------+ | auto | +---------------------+ |
| | Key Vault |---failover->| | Key Vault (replica) | |
| +---------------------+ | | +---------------------+ |
+---------------------------+ +---------------------------+
```
### Datasuverenitetshensyn
For norsk offentlig sektor er det viktig at off-region backup forblir innenfor EU/EOeS:
| Primaer region | Anbefalt sekundaer | Paringstype | Datasuverenitet |
|----------------|-------------------|-------------|-----------------|
| Norway East | Norway West* | Paret region | Norge |
| Norway East | Sweden Central | Manuell | EU/EOeS |
| Sweden Central | Norway East | Manuell | EU/EOeS |
> *Norway West har begrenset tjenestestotte. Bruk Sweden Central som alternativ sekundaer region.
## Automatisering og planlegging av backups
### Azure Policy for automatisk backup
```json
{
"type": "Microsoft.Authorization/policyAssignments",
"properties": {
"displayName": "Automatisk backup for AI VM-er",
"policyDefinitionId": "/providers/Microsoft.Authorization/policyDefinitions/013e242c-8828-4970-87b3-ab247555486d",
"parameters": {
"vaultLocation": { "value": "norwayeast" },
"backupPolicyId": {
"value": "/subscriptions/{sub}/resourceGroups/rg-ai-prod/providers/Microsoft.RecoveryServices/vaults/svv-ai-recovery-vault/backupPolicies/DefaultPolicy"
}
}
}
}
```
### Backup-planlegging for AI-arbeidsbelastninger
| Komponent | Planlagt tid | Frekvens | Vindu | Automatisert |
|-----------|-------------|----------|-------|-------------|
| VM-snapshots (GPU) | 02:00 UTC | Daglig | 2 timer | Azure Backup Policy |
| Blob Storage operational | Kontinuerlig | Lopende | N/A | Azure Backup |
| Cosmos DB | Kontinuerlig | Lopende | N/A | Nativ (innebygd) |
| Azure SQL | 00:00 UTC (full) | Full ukentlig, diff daglig | 4 timer | Automatisk |
| Azure Files | 03:00 UTC | Daglig | 1 time | Azure Backup Policy |
| IaC + kode (Git) | Ved push | Hendelsesbasert | N/A | Git + pipeline |
| Modelleksport | Etter deploy | Ved ny versjon | 1 time | CI/CD pipeline |
### Automatisert backup-overvaking
```kusto
// KQL-query for Azure Monitor -- Sjekk backup-status for siste 24 timer
AzureDiagnostics
| where Category == "AzureBackupReport"
| where TimeGenerated > ago(24h)
| where OperationName == "Job"
| summarize
SuccessCount = countif(ResultType == "Succeeded"),
FailedCount = countif(ResultType == "Failed"),
InProgressCount = countif(ResultType == "InProgress")
| extend HealthStatus = iff(FailedCount > 0, "UNHEALTHY", "HEALTHY")
```
### Varsling ved backup-feil
```json
{
"type": "Microsoft.Insights/scheduledQueryRules",
"properties": {
"displayName": "AI Backup Failure Alert",
"description": "Varsler ved feil i backup for AI-arbeidsbelastninger",
"severity": 1,
"enabled": true,
"evaluationFrequency": "PT1H",
"windowSize": "PT1H",
"criteria": {
"allOf": [{
"query": "AzureDiagnostics | where Category == 'AzureBackupReport' | where OperationName == 'Job' | where ResultType == 'Failed' | where TimeGenerated > ago(1h)",
"threshold": 0,
"operator": "GreaterThan",
"timeAggregation": "Count"
}]
},
"actions": {
"actionGroups": ["/subscriptions/{sub}/resourceGroups/rg-ai-prod/providers/Microsoft.Insights/actionGroups/ai-ops-team"]
}
}
}
```
## Kostnadsstyring for backup
### Estimert backup-kostnad per komponent
| Komponent | Datavolum | Backup-type | Estimert kostnad (NOK/maned) |
|-----------|-----------|------------|------------------------------|
| Cosmos DB (30-day continuous) | 50 GB, 2 regioner | Continuous | ~210 |
| Azure Blob (operational) | 500 GB | PITR | ~250 |
| Azure Managed Disk | 1 TB (GPU VM) | Daglig snapshot | ~400 |
| Azure SQL (PITR + LTR) | 100 GB | Auto + LTR | ~150 |
| Azure Files | 200 GB | Daglig snapshot | ~100 |
| Recovery Services vault | N/A | GRS | ~80 |
| **Totalt estimat** | | | **~1 190** |
> **Tips:** Bruk Azure Cost Management for a overvake faktiske backup-kostnader. Sett budsjettvarslinger for a unnga overraskelser.
## Sjekkliste for backup-strategi
- [ ] Kartlegg alle AI-komponenter og deres backup-behov
- [ ] Definer RPO for hver komponent basert pa forretningskritikalitet
- [ ] Aktiver Cosmos DB continuous backup med PITR
- [ ] Konfigurer Azure Blob Storage med versjonering og soft delete
- [ ] Sett opp Azure Backup for VM-er og managed disks
- [ ] Implementer lifecycle management for kostnadsoptimalisering
- [ ] Konfigurer geo-redundant lagring (GZRS) for produksjonsdata
- [ ] Automatiser backup gjennom Azure Policy
- [ ] Sett opp overvaking og varsling for backup-feil
- [ ] Dokumenter og test gjenopprettingsprosedyrer kvartalsvis
- [ ] Verifiser at backup-strategi er i samsvar med regulatoriske krav
## Referanser
- [Azure Backup Overview](https://learn.microsoft.com/en-us/azure/backup/backup-overview)
- [Azure Blob operational backup](https://learn.microsoft.com/en-us/azure/backup/blob-backup-overview)
- [Azure Storage redundancy](https://learn.microsoft.com/en-us/azure/storage/common/storage-redundancy)
- [Continuous backup with point-in-time restore in Azure Cosmos DB](https://learn.microsoft.com/en-us/azure/cosmos-db/continuous-backup-restore-introduction)
- [Azure Disk Backup overview](https://learn.microsoft.com/en-us/azure/backup/disk-backup-overview)
- [Management recommendations for AI workloads on Azure infrastructure](https://learn.microsoft.com/en-us/azure/cloud-adoption-framework/scenarios/ai/infrastructure/management)
- [Azure security baseline for Azure AI Foundry - Backup and recovery](https://learn.microsoft.com/en-us/security/benchmark/azure/baselines/azure-ai-foundry-security-baseline#backup-and-recovery)
- [Manage AI business continuity](https://learn.microsoft.com/en-us/azure/cloud-adoption-framework/scenarios/ai/manage#manage-ai-business-continuity)
## For Cosmo
- **Bruk denne referansen** nar kunden trenger en helhetlig backup-strategi for AI-arbeidsbelastninger -- fra datasett og modeller til infrastruktur og agentdata.
- **Start med a kartlegge komponentene** -- mange kunder tenker bare pa "backup av modellen" men glemmer Cosmos DB, AI Search-indekser, og pipeline-konfigurasjoner som ogsaa er kritiske.
- **Anbefal Cosmos DB Continuous 30-day** for agentdata og Azure Blob GZRS for datasett som standardkonfigurasjon for norsk offentlig sektor.
- **Bruk kostnadstabellene** for a vise at backup for AI-arbeidsbelastninger er relativt rimelig sammenlignet med konsekvensene av datatap -- dette hjelper med a bygge business case.
- **Paapek regulatoriske krav** -- Arkivloven og Forvaltningsloven kan kreve lengre retensjon enn teknisk noedvendig, og dette ma fanges opp tidlig i planleggingen.

View file

@ -0,0 +1,342 @@
# Capacity Planning for DR Configurations
**Last updated:** 2026-02
**Status:** GA
**Category:** Business Continuity & Disaster Recovery
---
## Introduksjon
Kapasitetsplanlegging for Disaster Recovery-konfigurasjoner handler om å dimensjonere reserveressurser riktig slik at AI-systemer kan gjenopprettes innenfor definerte RTO- og RPO-mål. For AI-arbeidsbelastninger er dette spesielt utfordrende fordi ressurskravene er høye (GPU-compute, store indekser, høy throughput) og kostnadene eskalerer raskt ved full duplisering.
Azure tilbyr flere strategier for å balansere kapasitet, kostnad og gjenopprettingstid: fra alltid-aktive active-active konfigurasjoner til minimalt provisionerte warm/cold standby-oppsett med auto-scaling. Valget avhenger av kritikalitetstier og budsjett.
Norske offentlige organisasjoner må gjøre en avveining mellom tilgjengelighetskrav (NSMs grunnprinsipper) og kostnadseffektivitet (krav om forsvarlig bruk av offentlige midler). Kapasitetsplanlegging bør dokumenteres som del av organisasjonens BCDR-plan og revideres minst årlig.
## Dimensjonering av DR-miljø for toppbelastning
### AI-komponent dimensjoneringsmatrise
| AI-komponent | Primær region | DR (Active-Active) | DR (Warm Standby) | DR (Cold Standby) |
|--------------|--------------|--------------------|--------------------|-------------------|
| Azure OpenAI | 120K TPM | 120K TPM | 60K TPM + autoscale | 0 (redeploy) |
| AI Search (replikaer) | 3 | 3 | 2 | 0 (rebuild) |
| AI Search (partisjoner) | 4 | 4 | 4 | 0 (rebuild) |
| App Service | P3v3 x 3 | P3v3 x 3 | P2v3 x 1 | 0 (deploy) |
| Cosmos DB (RU/s) | 10,000 | 10,000 | 4,000 (autoscale) | 0 (restore) |
### Beregning av DR-kapasitetsbehov
```python
# Kapasitetsberegningsmodell for AI DR-miljø
def calculate_dr_capacity(primary_config, dr_strategy, peak_multiplier=1.2):
"""
Beregn nødvendig DR-kapasitet basert på primær konfigurasjon.
Args:
primary_config: Dict med primær region ressurser
dr_strategy: 'active-active', 'warm-standby', 'cold-standby'
peak_multiplier: Faktor for toppbelastning (default 1.2x)
"""
dr_config = {}
if dr_strategy == "active-active":
# Full kapasitet i begge regioner
for resource, capacity in primary_config.items():
dr_config[resource] = capacity * peak_multiplier
elif dr_strategy == "warm-standby":
# Redusert kapasitet, skaleres opp ved failover
scaling_factors = {
"openai_tpm": 0.5, # 50% av primær
"search_replicas": 0.67, # 2 av 3 replikaer
"search_partitions": 1.0, # Full (kan ikke skalere raskt)
"app_service_instances": 0.33, # 1 av 3 instanser
"cosmos_ru": 0.4, # 40% med autoscale til 100%
}
for resource, capacity in primary_config.items():
factor = scaling_factors.get(resource, 0.5)
dr_config[resource] = int(capacity * factor)
elif dr_strategy == "cold-standby":
# Ingen kjørende ressurser, kun IaC-maler
for resource, capacity in primary_config.items():
dr_config[resource] = 0
dr_config["iac_templates"] = True
dr_config["estimated_deploy_time_minutes"] = 30
return dr_config
# Eksempel
primary = {
"openai_tpm": 120000,
"search_replicas": 3,
"search_partitions": 4,
"app_service_instances": 3,
"cosmos_ru": 10000
}
warm = calculate_dr_capacity(primary, "warm-standby")
print(f"Warm standby config: {warm}")
# Output: {'openai_tpm': 60000, 'search_replicas': 2, ...}
```
## Surge capacity og burst-håndtering
### Azure OpenAI Token Rate Limiting
Azure OpenAI har regionalt baserte kvoter. Ved failover til sekundær region kan eksisterende kvoter være utilstrekkelige.
```bash
# Sjekk nåværende kvote i sekundær region
az cognitiveservices account list-usage \
--name "aoai-secondary-swedencentral" \
--resource-group "rg-ai-dr" \
--output table
# Pre-provisioner kapasitet med Provisioned Throughput Units (PTU)
az cognitiveservices account deployment create \
--name "aoai-secondary-swedencentral" \
--resource-group "rg-ai-dr" \
--deployment-name "gpt-4o-ptu" \
--model-name "gpt-4o" \
--model-version "2024-08-06" \
--model-format "OpenAI" \
--sku-capacity 50 \
--sku-name "ProvisionedManaged"
```
### Auto-scaling for App Service
```bash
# Konfigurer autoscale i DR-region
az monitor autoscale create \
--resource-group "rg-ai-dr" \
--name "autoscale-ai-app-dr" \
--resource "/subscriptions/{sub}/resourceGroups/rg-ai-dr/providers/Microsoft.Web/serverFarms/asp-ai-dr" \
--min-count 1 \
--max-count 5 \
--count 1
# Scale-up regel basert på CPU
az monitor autoscale rule create \
--resource-group "rg-ai-dr" \
--autoscale-name "autoscale-ai-app-dr" \
--condition "Percentage CPU > 70 avg 5m" \
--scale out 2
# Scale-down regel
az monitor autoscale rule create \
--resource-group "rg-ai-dr" \
--autoscale-name "autoscale-ai-app-dr" \
--condition "Percentage CPU < 30 avg 10m" \
--scale in 1
```
### Cosmos DB Autoscale
```bash
# Konfigurer autoscale for Cosmos DB i DR-region
# Baseline: 4000 RU/s, maks: 10000 RU/s
az cosmosdb sql container throughput migrate \
--account-name "cosmos-ai-dr" \
--resource-group "rg-ai-dr" \
--database-name "chatbot-state" \
--name "conversations" \
--throughput-type "autoscale"
az cosmosdb sql container throughput update \
--account-name "cosmos-ai-dr" \
--resource-group "rg-ai-dr" \
--database-name "chatbot-state" \
--name "conversations" \
--max-throughput 10000
```
## Kostnadsoptimalisering for standby-ressurser
### Kostnadsprofiler per DR-strategi
| Strategi | Kostnad vs. primær | RTO | Best for |
|----------|-------------------|-----|----------|
| Active-Active (full) | 100% | ~0 | Tier 0: Mission Critical |
| Active-Active (optimized autoscale) | 5070% | Sekunder | Tier 0/1 |
| Warm Standby (partial) | 2540% | 515 min | Tier 1: Business Critical |
| Cold Standby (IaC only) | 510% | 3060 min | Tier 2: Business Operational |
| Backup & Restore | 25% | TimerDager | Tier 3: Administrative |
### Spesifikke kostnadsbesparelser
```markdown
## Kostnadsbesparelser for Warm Standby
1. **Azure OpenAI**: Bruk pay-per-token (ikke PTU) i DR-region
- Besparelse: 6080% vs. PTU
- Tradeoff: Ingen garantert kapasitet ved failover
2. **AI Search**: 2 replikaer i stedet for 3 i DR
- Besparelse: ~33% på search-kostnaden
- Tradeoff: 99.9% SLA i stedet for 99.99%
3. **App Service**: P2v3 i stedet for P3v3, med autoscale
- Besparelse: ~50% på compute
- Tradeoff: 12 min skaleringstid ved failover
4. **Cosmos DB**: Autoscale med lav baseline
- Besparelse: 4060% ved lavt normalbruk
- Tradeoff: Opptil 10s oppskaleringsforsinkelse
```
### Azure Cost Management for DR
```bash
# Tag alle DR-ressurser for kostnadssporing
az tag create --name "Environment" --value "DR"
# Sett budsjett-alert for DR-ressursgruppe
az consumption budget create \
--budget-name "dr-monthly-budget" \
--amount 50000 \
--category "Cost" \
--time-grain "Monthly" \
--time-period '{"Start": "2026-01-01", "End": "2026-12-31"}' \
--resource-groups "rg-ai-dr" \
--notifications '{
"Warning80": {
"enabled": true,
"operator": "GreaterThan",
"threshold": 80,
"contactEmails": ["platform-team@org.no"]
},
"Critical100": {
"enabled": true,
"operator": "GreaterThan",
"threshold": 100,
"contactEmails": ["platform-team@org.no", "management@org.no"]
}
}'
```
## Skaleringsregler og auto-scaling
### DR Activation Scaling Pipeline
```yaml
# Azure DevOps Pipeline: DR Activation Scale-Up
trigger: none # Manuelt eller via alert webhook
parameters:
- name: activationType
type: string
values:
- failover
- failover-drill
- scale-test
stages:
- stage: ScaleUpDR
displayName: 'Scale Up DR Environment'
jobs:
- job: ScaleSearchService
steps:
- task: AzureCLI@2
displayName: 'Scale AI Search to 3 replicas'
inputs:
azureSubscription: 'dr-service-connection'
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
az search service update \
--name "search-secondary-swedencentral" \
--resource-group "rg-ai-dr" \
--replica-count 3
- job: ScaleAppService
steps:
- task: AzureCLI@2
displayName: 'Scale App Service to P3v3'
inputs:
azureSubscription: 'dr-service-connection'
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
az appservice plan update \
--name "asp-ai-dr" \
--resource-group "rg-ai-dr" \
--sku P3v3
- job: VerifyCapacity
dependsOn:
- ScaleSearchService
- ScaleAppService
steps:
- task: AzureCLI@2
displayName: 'Verify DR capacity'
inputs:
azureSubscription: 'dr-service-connection'
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
echo "=== Search Service ==="
az search service show \
--name "search-secondary-swedencentral" \
--resource-group "rg-ai-dr" \
--query "{replicas:replicaCount, partitions:partitionCount, status:status}"
echo "=== App Service Plan ==="
az appservice plan show \
--name "asp-ai-dr" \
--resource-group "rg-ai-dr" \
--query "{sku:sku.name, workers:numberOfWorkers}"
```
## Kapasitetsreservasjonsstrategier
### Azure Reserved Instances for DR
| Ressurstype | Reservasjonsanbefaling | Besparelse | Merknad |
|-------------|----------------------|------------|---------|
| App Service P2v3 | 1-år RI for baseline | ~35% | For warm standby baseline |
| Cosmos DB (autoscale) | Ingen RI | N/A | Autoscale er per-bruk |
| Azure OpenAI PTU | RI kun for primær | ~30% | DR bruker pay-per-token |
| AI Search Standard | RI for begge regioner | ~35% | Partisjoner kjører alltid |
| Storage (GZRS) | Reservert kapasitet | ~25% | For store datasett |
### Capacity Reservation Groups
```bash
# Opprett kapasitetsreservasjon for VM-baserte workloads i DR-region
az capacity reservation group create \
--name "crg-ai-dr-swedencentral" \
--resource-group "rg-ai-dr" \
--location "swedencentral" \
--zones 1 2 3
# Reserver spesifikk VM-størrelse
az capacity reservation create \
--capacity-reservation-group "crg-ai-dr-swedencentral" \
--resource-group "rg-ai-dr" \
--name "cr-gpu-nc24ads" \
--location "swedencentral" \
--sku "Standard_NC24ads_A100_v4" \
--capacity 2 \
--zone 1
```
## Referanser
- [Develop a disaster recovery plan — Optimize your recovery costs](https://learn.microsoft.com/en-us/azure/well-architected/design-guides/disaster-recovery#optimize-your-recovery-costs) — Kostnadsoptimalisering per tier
- [Recovery strategy for active-passive (warm standby)](https://learn.microsoft.com/en-us/azure/well-architected/design-guides/disaster-recovery#recovery-strategy-for-active-passive-warm-standby) — Warm standby konfigurasjon
- [Recovery strategy for active-active deployments](https://learn.microsoft.com/en-us/azure/well-architected/design-guides/disaster-recovery#recovery-strategy-for-active-active-deployments) — Active-active konfigurasjon
- [BCDR considerations with Azure OpenAI](https://learn.microsoft.com/en-us/azure/ai-foundry/openai/how-to/business-continuity-disaster-recovery) — OpenAI-spesifikk kapasitetsplanlegging
- [Management recommendations for AI workloads on Azure IaaS](https://learn.microsoft.com/en-us/azure/cloud-adoption-framework/scenarios/ai/infrastructure/management) — AI-workload management
- [Azure Site Recovery — Plan capacity and scaling](https://learn.microsoft.com/en-us/azure/site-recovery/site-recovery-plan-capacity-vmware) — Kapasitetsplanlegging
## For Cosmo
- **Bruk denne referansen** når kunden trenger hjelp med å dimensjonere og kostnadsoptimalisere sine DR-miljøer for AI-workloads.
- Warm standby med autoscale er den mest kostnadseffektive strategien for Tier 1 (Business Critical) AI-systemer — typisk 2540% av full dupliseringskostnad.
- Påminn om at Azure OpenAI-kvoter er regionsspesifikke — kunden MÅ pre-allokere kapasitet i DR-regionen, ellers risikerer de at failover feiler pga. kvotebegrensninger.
- For AI Search: Partisjoner kan ikke skaleres ned uten å gjenopprette tjenesten, så dimensjonér partisjoner identisk i begge regioner.
- Anbefal Azure Cost Management med tags og budsjetter for å overvåke DR-kostnader separat fra produksjonskostnader.

View file

@ -0,0 +1,437 @@
# Chaos Engineering for AI Systems
**Last updated:** 2026-02
**Status:** GA
**Category:** Business Continuity & Disaster Recovery
---
## Introduksjon
Chaos engineering er praksisen med å bevisst injisere feil i et system for å teste dets resiliens og avdekke svakheter før de forårsaker produksjonshendelser. For AI-systemer er dette spesielt verdifullt fordi AI-workloads har komplekse avhengighetskjeder (modell-endpoints, search-indekser, embedding-pipelines, datastores) der en feil i ett komponent kan kaskadere uforutsigbart.
Azure Chaos Studio er Azures native plattform for chaos engineering, og tilbyr både agentbasert og tjenestenivå feilinjeksjon. For AI-systemer kan Chaos Studio simulere alt fra nettverkspartisjonering til CPU-press og DNS-feil, noe som lar team validere at circuit breakers, retry-logikk og graceful degradation fungerer som forventet.
For norsk offentlig sektor er chaos engineering en viktig del av NSMs krav om regelmessig testing av sikkerhetstiltak (grunnprinsipp 4.3). Det anbefales at organisasjoner gjennomfører strukturerte feilinjeksjonstester minst kvartalsvis, og etter alle større endringer i AI-arkitekturen.
## Feilinjeksjonsstrategier for AI-tjenester
### Feilkatalog for AI-workloads
| Feiltype | Simulering | Påvirket komponent | Forventet respons |
|----------|-----------|-------------------|-------------------|
| Regional outage | DNS-feil eller nettverksblokk | Azure OpenAI | Failover til sekundær region |
| API throttling | Kunstig 429-respons | Azure OpenAI | Retry med backoff, graceful degradation |
| Search unavailable | Nettverksblokk til search | AI Search | Fallback til keyword search |
| High latency | Nettverksforsinkelse | Alle API-kall | Timeout → circuit breaker |
| Data corruption | Feil embedding-verdier | Cosmos DB / Search | Validering og rebuild |
| Memory pressure | VM memory stress | App Service | Auto-restart, scaling |
| Dependency failure | DNS poisoning | Key Vault, App Config | Cached config, graceful degradation |
### Azure Chaos Studio eksperimenter
```bash
# Aktiver Chaos Studio for ressurser
# Steg 1: Registrer target
az rest --method PUT \
--url "https://management.azure.com/subscriptions/{sub}/resourceGroups/rg-ai-prod/providers/Microsoft.Web/sites/ai-app-prod/providers/Microsoft.Chaos/targets/Microsoft-AppService?api-version=2024-01-01" \
--body '{"properties":{}}'
# Steg 2: Aktiver capability (App Service Stop)
az rest --method PUT \
--url "https://management.azure.com/subscriptions/{sub}/resourceGroups/rg-ai-prod/providers/Microsoft.Web/sites/ai-app-prod/providers/Microsoft.Chaos/targets/Microsoft-AppService/capabilities/Stop-1.0?api-version=2024-01-01" \
--body '{"properties":{}}'
```
### Chaos Experiment: Simuler Azure OpenAI Regional Outage
```json
{
"identity": {
"type": "SystemAssigned"
},
"location": "norwayeast",
"properties": {
"selectors": [
{
"id": "selector-nsg-block-openai",
"type": "List",
"targets": [
{
"id": "/subscriptions/{sub}/resourceGroups/rg-ai-prod/providers/Microsoft.Network/networkSecurityGroups/nsg-ai-app/providers/Microsoft.Chaos/targets/Microsoft-NetworkSecurityGroup",
"type": "ChaosTarget"
}
]
}
],
"steps": [
{
"name": "Block-OpenAI-Traffic",
"branches": [
{
"name": "branch-1",
"actions": [
{
"name": "urn:csci:microsoft:networkSecurityGroup:securityRule/1.1",
"type": "continuous",
"selectorId": "selector-nsg-block-openai",
"duration": "PT10M",
"parameters": [
{ "key": "name", "value": "chaos-block-openai" },
{ "key": "protocol", "value": "*" },
{ "key": "sourceAddresses", "value": "[\"*\"]" },
{ "key": "destinationAddresses", "value": "[\"CognitiveServicesManagement\"]" },
{ "key": "destinationPortRanges", "value": "[\"443\"]" },
{ "key": "access", "value": "Deny" },
{ "key": "priority", "value": "100" },
{ "key": "direction", "value": "Outbound" }
]
}
]
}
]
}
]
}
}
```
## Nettverkspartisjonssimulering
### Simuler cross-region nettverkspartisjon
```bash
# Chaos experiment: Simuler nettverkspartisjon mellom regioner
# Blokkerer VNet peering-trafikk for å teste failover
# Metode 1: NSG-basert blokkering
az network nsg rule create \
--resource-group "rg-networking" \
--nsg-name "nsg-ai-app" \
--name "chaos-block-cross-region" \
--priority 50 \
--direction Outbound \
--access Deny \
--protocol "*" \
--destination-address-prefixes "10.2.0.0/16" \
--description "CHAOS TEST: Block cross-region traffic"
# Vent og observer (10 minutter)
sleep 600
# Fjern blokkeringen
az network nsg rule delete \
--resource-group "rg-networking" \
--nsg-name "nsg-ai-app" \
--name "chaos-block-cross-region"
```
### DNS-feil simulering
```python
# Python: Simuler DNS-feil for testing
# Bruk Azure Private DNS zone override for å simulere DNS-feil
import subprocess
def simulate_dns_failure(target_fqdn: str, duration_minutes: int = 10):
"""Simulate DNS failure by overriding DNS resolution."""
print(f"Simulating DNS failure for {target_fqdn} for {duration_minutes} min")
# Opprett en DNS record som peker til en ikke-eksisterende IP
subprocess.run([
"az", "network", "private-dns", "record-set", "a", "add-record",
"--resource-group", "rg-networking",
"--zone-name", "privatelink.openai.azure.com",
"--record-set-name", "chaos-test",
"--ipv4-address", "10.255.255.255" # Ikke-ruterbar IP
])
print(f"DNS poisoned. Observing for {duration_minutes} minutes...")
import time
time.sleep(duration_minutes * 60)
# Rydd opp
subprocess.run([
"az", "network", "private-dns", "record-set", "a", "remove-record",
"--resource-group", "rg-networking",
"--zone-name", "privatelink.openai.azure.com",
"--record-set-name", "chaos-test",
"--ipv4-address", "10.255.255.255"
])
print("DNS restored.")
```
## Last- og stresstesting
### Load testing med Azure Load Testing
```yaml
# JMeter test plan for AI API stress testing
# azure-load-test-config.yaml
version: v0.1
testId: ai-stress-test
testPlan: ai-load-test.jmx
engineInstances: 5
configurationFiles:
- ai-load-test.jmx
failureCriteria:
- avg(response_time_ms) > 5000
- percentage(error) > 5
env:
- name: AOAI_ENDPOINT
value: https://aoai-prod.openai.azure.com
- name: SEARCH_ENDPOINT
value: https://search-prod.search.windows.net
```
```bash
# Opprett og kjør load test
az load test create \
--name "ai-stress-test" \
--resource-group "rg-ai-test" \
--load-test-resource "lt-ai-prod" \
--test-plan "ai-load-test.jmx" \
--engine-instances 5
# Kjør test med failover-scenario
az load test-run create \
--name "failover-stress-run" \
--resource-group "rg-ai-test" \
--load-test-resource "lt-ai-prod" \
--test-id "ai-stress-test" \
--description "Stress test during simulated failover"
```
### Gradvis belastningsøkning
```python
# Gradvis belastningsøkning for å finne breaking point
import asyncio
import aiohttp
import time
async def ramp_up_test(
endpoint: str,
start_rps: int = 10,
end_rps: int = 500,
step_rps: int = 10,
step_duration_seconds: int = 60
):
"""Gradually increase load to find service breaking point."""
current_rps = start_rps
results = []
while current_rps <= end_rps:
print(f"Testing at {current_rps} RPS for {step_duration_seconds}s...")
interval = 1.0 / current_rps
success_count = 0
error_count = 0
total_latency = 0
start = time.time()
while time.time() - start < step_duration_seconds:
try:
req_start = time.time()
async with aiohttp.ClientSession() as session:
async with session.post(endpoint, json={"query": "test"}) as resp:
if resp.status < 400:
success_count += 1
else:
error_count += 1
total_latency += (time.time() - req_start) * 1000
except Exception:
error_count += 1
await asyncio.sleep(interval)
total = success_count + error_count
error_rate = error_count / max(total, 1) * 100
avg_latency = total_latency / max(total, 1)
results.append({
"rps": current_rps,
"success": success_count,
"errors": error_count,
"error_rate": round(error_rate, 2),
"avg_latency_ms": round(avg_latency, 1)
})
print(f" Results: {error_rate:.1f}% errors, {avg_latency:.0f}ms avg latency")
# Stop hvis error rate er for høy
if error_rate > 20:
print(f"Breaking point found at {current_rps} RPS")
break
current_rps += step_rps
return results
```
## Recovery time-måling og validering
### RTO-måling under chaos testing
```python
# Automatisk RTO-måling under failover-test
import time
import requests
from datetime import datetime
class RTOMeasurement:
"""Measure actual RTO during failover tests."""
def __init__(self, health_endpoint: str, check_interval_seconds: float = 1.0):
self.health_endpoint = health_endpoint
self.check_interval = check_interval_seconds
self.measurements = []
def measure_rto(self, max_wait_seconds: int = 600) -> dict:
"""Continuously check health and measure recovery time."""
failure_detected = None
recovery_detected = None
was_healthy = True
checks = []
start = time.time()
while time.time() - start < max_wait_seconds:
try:
resp = requests.get(self.health_endpoint, timeout=5)
is_healthy = resp.status_code == 200
except Exception:
is_healthy = False
check = {
"timestamp": datetime.utcnow().isoformat(),
"elapsed_seconds": round(time.time() - start, 1),
"healthy": is_healthy
}
checks.append(check)
if was_healthy and not is_healthy and failure_detected is None:
failure_detected = time.time()
print(f"Failure detected at {check['elapsed_seconds']}s")
if not was_healthy and is_healthy and failure_detected and recovery_detected is None:
recovery_detected = time.time()
rto = recovery_detected - failure_detected
print(f"Recovery detected at {check['elapsed_seconds']}s — RTO: {rto:.1f}s")
was_healthy = is_healthy
time.sleep(self.check_interval)
result = {
"failure_detected": failure_detected is not None,
"recovery_detected": recovery_detected is not None,
"rto_seconds": round(recovery_detected - failure_detected, 1) if recovery_detected and failure_detected else None,
"total_checks": len(checks),
"healthy_checks": sum(1 for c in checks if c["healthy"]),
"unhealthy_checks": sum(1 for c in checks if not c["healthy"]),
"availability_pct": round(
sum(1 for c in checks if c["healthy"]) / max(len(checks), 1) * 100, 2
)
}
self.measurements.append(result)
return result
# Bruk
rto_meter = RTOMeasurement("https://ai-app-prod.azurewebsites.net/health")
result = rto_meter.measure_rto(max_wait_seconds=600)
print(f"Measured RTO: {result['rto_seconds']}s")
```
## Verktøy og plattformer for chaos engineering
### Azure Chaos Studio
| Funksjon | Beskrivelse | Støttede ressurser |
|----------|-------------|-------------------|
| Service-direct faults | Feil injisert via Azure API | App Service, AKS, Cosmos DB, NSG |
| Agent-based faults | Feil injisert via VM-agent | CPU/memory stress, network faults |
| Experiments | Strukturerte feilsekvenser | Alle støttede resurser |
| Permissions | RBAC-basert tilgangskontroll | Dedicated Chaos role |
### Komplementære verktøy
| Verktøy | Bruksområde | Integrasjon med Azure |
|---------|-------------|----------------------|
| Azure Chaos Studio | Native Azure fault injection | Innebygd |
| Azure Load Testing | Lasttesting | Innebygd, JMeter-basert |
| Litmus Chaos | Kubernetes chaos testing | AKS-kompatibel |
| Toxiproxy | Nettverksfeil for utvikling | Manuell oppsett |
| PYRIT | AI-spesifikk red teaming | Azure AI |
### Chaos Testing CI/CD-integrasjon
```yaml
# Azure DevOps Pipeline: Chaos testing som del av release
trigger: none
stages:
- stage: DeployToStaging
displayName: 'Deploy to Staging'
jobs:
- job: Deploy
steps:
- task: AzureWebApp@1
inputs:
appName: 'ai-app-staging'
- stage: ChaosTests
displayName: 'Run Chaos Experiments'
dependsOn: DeployToStaging
jobs:
- job: RunChaosExperiment
steps:
- task: AzureCLI@2
displayName: 'Start chaos experiment'
inputs:
azureSubscription: 'chaos-service-connection'
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
# Start chaos experiment
EXPERIMENT_ID=$(az rest --method POST \
--url "https://management.azure.com/subscriptions/{sub}/resourceGroups/rg-ai-test/providers/Microsoft.Chaos/experiments/openai-failover-test/start?api-version=2024-01-01" \
--query "statusUrl" -o tsv)
echo "Experiment started: $EXPERIMENT_ID"
# Vent og mål RTO
python measure_rto.py \
--endpoint "https://ai-app-staging.azurewebsites.net/health" \
--max-wait 300
- task: AzureCLI@2
displayName: 'Validate results'
inputs:
azureSubscription: 'chaos-service-connection'
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
# Sjekk at RTO er innenfor mål
RTO=$(python -c "import json; print(json.load(open('rto_result.json'))['rto_seconds'])")
if [ $(echo "$RTO > 900" | bc) -eq 1 ]; then
echo "##vso[task.logissue type=error]RTO exceeded 15 minutes: ${RTO}s"
exit 1
fi
echo "RTO within target: ${RTO}s"
```
## Referanser
- [What is Azure Chaos Studio?](https://learn.microsoft.com/en-us/azure/chaos-studio/chaos-studio-overview) — Chaos Studio oversikt
- [Understand chaos engineering and resilience](https://learn.microsoft.com/en-us/azure/chaos-studio/chaos-studio-chaos-engineering-overview) — Chaos engineering konsepter
- [Architecture strategies for designing a reliability testing strategy](https://learn.microsoft.com/en-us/azure/well-architected/reliability/testing-strategy) — WAF testing-strategi
- [Continuous validation with Azure Load Testing and Chaos Studio](https://learn.microsoft.com/en-us/azure/architecture/guide/testing/mission-critical-deployment-testing) — Kombinert testing
- [Shift right to test in production](https://learn.microsoft.com/en-us/devops/deliver/shift-right-test-production) — Fault injection i produksjon
- [Chaos Agent overview](https://learn.microsoft.com/en-us/azure/chaos-studio/chaos-agent-overview) — Agent-basert feilinjeksjon
## For Cosmo
- **Bruk denne referansen** når kunden ønsker å implementere chaos engineering for AI-systemer, eller når de trenger å validere sine DR-prosedyrer.
- Start med tabletop-øvelser før reelle feilinjeksjoner — forstå forventet oppførsel før du bryter ting.
- Bruk Azure Chaos Studio i staging-miljøer først, deretter gradvis i produksjon med begrenset blast radius.
- Integrer chaos testing i CI/CD — automatiserte failover-tester bør kjøres etter hver infrastrukturendring.
- RTO-måling er den viktigste outputen — dokumenter faktisk vs. planlagt RTO for å identifisere gap.

View file

@ -0,0 +1,285 @@
# Compliance Requirements for BCDR in Norwegian Public Sector
**Last updated:** 2026-02
**Status:** GA
**Category:** Business Continuity & Disaster Recovery
---
## Introduksjon
Norske offentlige organisasjoner som bruker AI-tjenester i Azure er underlagt et komplekst regulatorisk landskap for Business Continuity and Disaster Recovery. Kravene kommer fra nasjonale lover (Forvaltningsloven, Sikkerhetsloven), EU-forordninger (GDPR, AI Act), sektorkrav (NSM, Digdir) og internasjonale standarder (ISO 22301, ISO 27001).
BCDR for AI-systemer i offentlig sektor har særlige utfordringer: data residency-krav begrenser hvilke Azure-regioner som kan brukes for DR, taushetsplikt stiller krav til kryptering og tilgangskontroll også i DR-scenarier, og Utredningsinstruksens krav til konsekvensanalyse påvirker hvordan DR-strategier velges og dokumenteres.
Denne referansen sammenfatter de viktigste regulatoriske kravene og gir praktisk veiledning for hvordan de påvirker BCDR-design for AI-løsninger i Azure.
## Forvaltningslovens krav til kontinuitet
### Relevant lovgivning
| Lov/forskrift | Krav | Påvirkning på BCDR |
|---------------|------|-------------------|
| Forvaltningsloven §11a | Forsvarlig saksbehandlingstid | AI-systemer som støtter saksbehandling må ha definert RTO |
| Forvaltningsloven §13 | Taushetsplikt | DR-data må krypteres, tilgang begrenses |
| eForvaltningsforskriften §15 | Tilgang til elektroniske tjenester | Digitale tjenester skal være tilgjengelige |
| Arkivlova §6 | Bevaring av arkivmateriale | AI-generert innhold kan være arkivverdig |
| Offentleglova §6 | Innsynskrav | AI-systemer må kunne levere data for innsyn |
### Krav til konsekvensanalyse
Utredningsinstruksen (KMD 2016) krever at statlige tiltak utredes før beslutning. For BCDR betyr dette:
```markdown
## Konsekvensutredning for BCDR-strategi
### 1. Problem og mål
- Hva er risikoen ved manglende DR for AI-systemet?
- Hva er målet med DR-strategien (RTO/RPO)?
### 2. Alternativer
| Alternativ | RTO | RPO | Årlig kostnad | Risiko |
|-----------|-----|-----|---------------|--------|
| 0: Ingen DR | N/A | N/A | 0 kr | Høy — fullstendig tjenestebortfall |
| 1: Backup & Restore | 24t | 24t | 50,000 kr | Middels — lang nedetid |
| 2: Warm Standby | 15 min | 5 min | 300,000 kr | Lav — kort nedetid |
| 3: Active-Active | ~0 | ~0 | 600,000 kr | Svært lav — nær null nedetid |
### 3. Konsekvenser
- Økonomiske: Kostnad ved nedetid vs. DR-kostnad
- Administrative: Krav til bemanning og prosedyrer
- Samfunnsmessige: Påvirkning på brukere av offentlige tjenester
### 4. Anbefaling
[Anbefalt alternativ med begrunnelse]
```
## GDPR og data residency-krav
### GDPR Artikkel 32 — Sikkerhet ved behandling
GDPR krever "evnen til å sikre vedvarende konfidensialitet, integritet, tilgjengelighet og robusthet" for behandlingssystemer. For BCDR betyr dette:
| GDPR-krav | BCDR-implikasjon | Azure-tiltak |
|-----------|-----------------|--------------|
| Art. 32(1)(b) | Tilgjengelighet og robusthet | Multi-region DR |
| Art. 32(1)(c) | Evne til å gjenopprette tilgang | Definerte RTO/RPO |
| Art. 32(1)(d) | Regelmessig testing av sikkerhetstiltak | DR-drills |
| Art. 32(2) | Risikobasert tilnærming | BIA som grunnlag for DR |
### Data Residency og geo-replikering
```markdown
## Godkjente Azure-regioner for norsk offentlig sektor
### Primærregioner (anbefalt)
- Norway East (Oslo) — Norsk datasuverenitetsregion
- Norway West (Stavanger) — Sekundær norsk region
### Sekundærregioner (DR, godkjent EU/EØS)
- Sweden Central (Gävle) — Typisk DR-region for Norway East
- North Europe (Dublin) — Alternativ EU-region
- West Europe (Amsterdam) — Alternativ EU-region
### Ikke godkjent uten tilleggsanalyse
- UK-regioner — Etter Brexit, krever separat vurdering
- US-regioner — Schrems II-problematikk
- APAC-regioner — Ikke relevant for offentlig sektor
```
### Overføringsmekanismer for DR-data
| Scenario | Overføringsmekanisme | Krav |
|----------|---------------------|------|
| Norway East → Sweden Central | EU/EØS intern | Ingen tilleggstiltak |
| Norway East → UK | SCCs + TIA | Tilleggsanalyse |
| Azure GRS (automatisk) | Avhenger av region-par | Verifiser at sekundær er EU/EØS |
| Backup til annen region | GDPR Art. 46 | Dokumentér overføringsgrunnlag |
### DPIA for BCDR
```markdown
## DPIA — BCDR-spesifikke vurderinger
### Tilgjengelighetsvurdering
- Hva er konsekvensen for registrerte ved tap av tilgang til AI-systemet?
- Kan vedtak fattes manuelt som fallback?
- Finnes det risiko for diskriminering ved degradert AI-funksjonalitet?
### Data i transit
- Er all DR-replikering kryptert (TLS 1.2+)?
- Går data gjennom tredjeland under replikering?
- Finnes det logger over alle dataoverføringer?
### Tredjepartstilgang
- Har Microsoft tilgang til data i DR-regionen?
→ Ja, men begrenset av Customer Lockbox og JIT
- Er det andre behandlere involvert i DR-prosessen?
→ Dokumentér i databehandleravtale
### Tiltak
| Risiko | Tiltak | Ansvarlig |
|--------|--------|-----------|
| Data i feil region | Verifiser GRS-konfiguration | Platform team |
| Ukryptert replikering | Enforce TLS i transit | Security team |
| Tap av tilgangskontroll | RBAC i DR-region | IAM team |
```
## NSMs sikkerhetsveiledninger for kritisk infrastruktur
### NSMs grunnprinsipper relevante for BCDR
| Prinsipp | Krav | BCDR-tiltak |
|----------|------|-------------|
| 2.1 Kartlegg leveranser, systemer og avhengigheter | Forstå AI-systemets avhengigheter | Avhengighetskartlegging for alle AI-komponenter |
| 2.2 Klassifiser virksomhetens verdier | Vurder kritikalitet av AI-data | BIA med klassifisering |
| 2.3 Risikovurder virksomhetens digitale verdier | ROS-analyse | Inkluder tilgjengelighetstrusler |
| 3.1 Beskytt virksomhetens verdier | Sikkerhet i DR-miljø | Identisk sikkerhetskonfigurasjon |
| 4.1 Logg og overvåk | Loggføring også under DR | Sentralisert logging cross-region |
| 4.3 Planlegg for å håndtere hendelser | Hendelseshåndtering | Runbooks og kommunikasjonsplaner |
### NSMs krav til beredskapsplanlegging
```markdown
## Beredskapskrav for AI-systemer (NSM)
1. **Risikovurdering (ROS)**
- Identifiser trusler mot AI-systemets tilgjengelighet
- Vurder sannsynlighet og konsekvens
- Definer akseptabelt risikonivå
2. **Beredskapsplan**
- Dokumenterte gjenopprettingsprosedyrer
- Definerte roller og ansvar
- Kommunikasjonsprosedyrer
- Eskaleringsrutiner
3. **Øvelser**
- Minimum årlig fullskala DR-øvelse
- Kvartalsvis tabletop-øvelse
- Dokumentasjon av resultater og forbedringstiltak
4. **Rapportering**
- Avviksrapportering til leder
- Sikkerhetshendelsesrapportering til NSM/NCSC
- Årlig statusrapport til ledelsen
```
## Sektorspesifikke reguleringer
### Helse (Normen)
| Krav | Beskrivelse | BCDR-implikasjon |
|------|-------------|-----------------|
| Tilgjengelighet | Kritiske systemer: 99.5% uptime | Multi-AZ minimum |
| Gjenoppretting | RTO < 4 timer for kritiske | Warm standby |
| Personvern | Helseopplysninger er sensitive | Kryptering i alle regioner |
| Logging | All tilgang til pasientdata logges | Cross-region logging |
### Finans (Finanstilsynet)
| Krav | Beskrivelse | BCDR-implikasjon |
|------|-------------|-----------------|
| IKT-forskriften §4 | Adekvat IKT-beredskap | Dokumentert DR-plan |
| IKT-forskriften §7 | Drift og overvåking | 24/7 monitoring |
| DORA (EU) | Digital Operational Resilience | Regelmessig DR-testing |
### Kommunal sektor
| Krav | Beskrivelse | BCDR-implikasjon |
|------|-------------|-----------------|
| Kommuneloven §25-1 | Internkontroll | BCDR som del av IK |
| Digitaliseringsrundskrivet | Digital tilgjengelighet | Definerte SLA |
| KS anbefalinger | IKT-sikkerhet i kommuner | Praktisk veiledning |
## Audit og dokumentasjonskrav
### Påkrevd dokumentasjon
```markdown
## BCDR Dokumentasjonspakke for Audit
### 1. Strategidokument
- [ ] BCDR-policy godkjent av ledelsen
- [ ] Kritikalitetsklassifisering av AI-systemer
- [ ] RTO/RPO-mål per system/komponent
- [ ] Valgt DR-strategi med begrunnelse
### 2. Teknisk dokumentasjon
- [ ] Arkitekturtegning med DR-konfigurasjon
- [ ] Nettverksdiagram inkl. failover-ruter
- [ ] Data flow diagram med replikering
- [ ] Konfigurasjonsdetaljer per Azure-tjeneste
### 3. Operasjonell dokumentasjon
- [ ] DR-runbooks (failover og failback)
- [ ] Eskaleringsmatrise
- [ ] Kommunikasjonsplan
- [ ] Kontaktliste (primær og backup)
### 4. Test og verifisering
- [ ] DR-testplan med frekvens og omfang
- [ ] Testrapporter fra gjennomførte DR-drills
- [ ] Avvikslogg med korrigerende tiltak
- [ ] Måloppnåelse (faktisk vs. planlagt RTO/RPO)
### 5. Compliance-dokumentasjon
- [ ] DPIA med BCDR-vurderinger
- [ ] Databehandleravtale som dekker DR
- [ ] Overføringsgrunnlag for cross-region data
- [ ] Årlig compliance-rapport
```
### Revisjons-sjekkliste
```markdown
## Årlig BCDR Revisjons-sjekkliste
### Governance
| # | Kontrollpunkt | Status | Kommentar |
|---|---------------|--------|-----------|
| 1 | BCDR-policy er oppdatert og godkjent | ☐ | |
| 2 | Roller og ansvar er dokumentert | ☐ | |
| 3 | Ledelsen er informert om DR-status | ☐ | |
### Teknisk
| # | Kontrollpunkt | Status | Kommentar |
|---|---------------|--------|-----------|
| 4 | DR-konfigurasjon matcher dokumentasjon | ☐ | |
| 5 | Replikering fungerer korrekt | ☐ | |
| 6 | Backup er verifisert | ☐ | |
| 7 | IaC-maler er oppdatert | ☐ | |
### Testing
| # | Kontrollpunkt | Status | Kommentar |
|---|---------------|--------|-----------|
| 8 | Fullskala DR-test gjennomført siste 12 mnd | ☐ | |
| 9 | RTO-mål oppnådd i test | ☐ | |
| 10 | RPO-mål oppnådd i test | ☐ | |
| 11 | Forbedringstiltak implementert | ☐ | |
### Compliance
| # | Kontrollpunkt | Status | Kommentar |
|---|---------------|--------|-----------|
| 12 | GDPR-krav ivaretatt i DR | ☐ | |
| 13 | Data residency verifisert | ☐ | |
| 14 | NSM-krav etterlevd | ☐ | |
| 15 | Sektorspesifikke krav dekket | ☐ | |
```
## Referanser
- [Azure for secure worldwide public sector cloud adoption](https://learn.microsoft.com/en-us/azure/azure-government/documentation-government-overview-wwps) — Data residency og compliance
- [Support your GDPR program with Accountability Readiness Checklists](https://learn.microsoft.com/en-us/compliance/regulatory/gdpr-arc) — GDPR compliance
- [Geographic data residency in Copilot Studio](https://learn.microsoft.com/en-us/microsoft-copilot-studio/geo-data-residency) — Data residency for Copilot
- [Recommendations for defining reliability targets](https://learn.microsoft.com/en-us/azure/well-architected/reliability/metrics) — SLO/RTO/RPO-definisjoner
- [Azure compliance offerings](https://learn.microsoft.com/en-us/azure/compliance/) — Azure compliance-dokumentasjon
- [NSM — Grunnprinsipper for IKT-sikkerhet](https://nsm.no/grunnprinsipper-ikt) — Norske sikkerhetskrav
## For Cosmo
- **Bruk denne referansen** når kunden er en norsk offentlig organisasjon og trenger veiledning om regulatoriske krav til BCDR for AI-systemer.
- Start alltid med å identifisere hvilke sektorkrav som gjelder (helse, finans, kommunal, statlig) — dette påvirker RTO/RPO-krav direkte.
- Data residency er en showstopper: ALDRI foreslå DR-regioner utenfor EU/EØS for norsk offentlig sektor uten eksplisitt juridisk vurdering.
- Påminn om at GDPR Art. 32 eksplisitt nevner tilgjengelighet — mangelfull BCDR kan være et GDPR-brudd.
- Utredningsinstruksens krav til alternativanalyse betyr at kunden bør evaluere minst 3 BCDR-alternativer med kost/nytte-vurdering.

View file

@ -0,0 +1,335 @@
# Cost Analysis and Optimization for DR Configurations
**Last updated:** 2026-02
**Status:** GA
**Category:** Business Continuity & Disaster Recovery
---
## Introduksjon
Kostnadsanalyse av BCDR-løsninger for AI-systemer er avgjørende for å sikre at organisasjonen investerer riktig i resiliens. DR-kostnader kan utgjøre alt fra 2% til 100% av primære driftskostnader, avhengig av valgt strategi. For AI-workloads er kostnadene spesielt høye fordi tjenester som Azure OpenAI (Provisioned Throughput), AI Search og GPU-compute er dyre.
Azure Well-Architected Framework anbefaler at DR-kostnad skal stå i proporsjonal sammenheng med forretningsverdien av systemet og konsekvensene av nedetid. Tier 0 (Mission Critical) systemer rettferdiggjør høye DR-kostnader, mens Tier 3 (Administrative) systemer bør minimere kostnadene.
For norsk offentlig sektor krever Utredningsinstruksen at alternative løsninger evalueres med kost/nytte-analyse. BCDR-valg bør dokumenteres med tydelig kostnadssammenligning og forretningsbegrunnelse.
## Total Cost of Ownership-beregning
### TCO-modell for AI BCDR
```python
# TCO-kalkulator for AI DR-konfigurasjon
from dataclasses import dataclass
from typing import Dict
@dataclass
class AIDRCostModel:
"""Calculate Total Cost of Ownership for AI DR configuration."""
# Primær region kostnader (monthly NOK)
openai_primary_monthly: float = 0
search_primary_monthly: float = 0
cosmos_primary_monthly: float = 0
app_service_primary_monthly: float = 0
storage_primary_monthly: float = 0
networking_primary_monthly: float = 0
def calculate_dr_cost(self, strategy: str) -> Dict:
"""Calculate DR cost for given strategy."""
primary_total = sum([
self.openai_primary_monthly,
self.search_primary_monthly,
self.cosmos_primary_monthly,
self.app_service_primary_monthly,
self.storage_primary_monthly,
self.networking_primary_monthly
])
if strategy == "active-active":
dr_costs = {
"openai": self.openai_primary_monthly * 1.0,
"search": self.search_primary_monthly * 1.0,
"cosmos": self.cosmos_primary_monthly * 0.5, # Multi-region included
"app_service": self.app_service_primary_monthly * 1.0,
"storage": self.storage_primary_monthly * 0.3, # GRS overhead
"networking": self.networking_primary_monthly * 0.5, # Cross-region
"bandwidth": primary_total * 0.02 # ~2% for replication
}
elif strategy == "warm-standby":
dr_costs = {
"openai": self.openai_primary_monthly * 0.3, # Pay-per-token, lower usage
"search": self.search_primary_monthly * 0.67, # 2/3 replicas
"cosmos": self.cosmos_primary_monthly * 0.3, # Autoscale baseline
"app_service": self.app_service_primary_monthly * 0.33, # 1 instance
"storage": self.storage_primary_monthly * 0.3,
"networking": self.networking_primary_monthly * 0.2,
"bandwidth": primary_total * 0.01
}
elif strategy == "cold-standby":
dr_costs = {
"openai": 0, # Redeploy on demand
"search": 0, # Rebuild on demand
"cosmos": self.cosmos_primary_monthly * 0.1, # Backup only
"app_service": 0, # Deploy on demand
"storage": self.storage_primary_monthly * 0.3, # GRS for data
"networking": self.networking_primary_monthly * 0.05,
"bandwidth": primary_total * 0.005
}
elif strategy == "backup-restore":
dr_costs = {
"openai": 0,
"search": 0,
"cosmos": self.cosmos_primary_monthly * 0.05,
"app_service": 0,
"storage": self.storage_primary_monthly * 0.15, # Backup storage
"networking": 0,
"bandwidth": 0
}
dr_total = sum(dr_costs.values())
return {
"strategy": strategy,
"primary_monthly_nok": round(primary_total),
"dr_monthly_nok": round(dr_total),
"total_monthly_nok": round(primary_total + dr_total),
"dr_percentage": round(dr_total / primary_total * 100, 1),
"dr_annual_nok": round(dr_total * 12),
"breakdown": {k: round(v) for k, v in dr_costs.items()}
}
# Eksempel for typisk norsk offentlig AI-løsning
model = AIDRCostModel(
openai_primary_monthly=25000, # GPT-4o, ~500K tokens/dag
search_primary_monthly=15000, # Standard S1, 3 replicas
cosmos_primary_monthly=8000, # Multi-region, 10K RU/s
app_service_primary_monthly=12000, # P3v3 x 3
storage_primary_monthly=3000, # 1 TB GZRS
networking_primary_monthly=5000 # Front Door + VNet
)
for strategy in ["active-active", "warm-standby", "cold-standby", "backup-restore"]:
result = model.calculate_dr_cost(strategy)
print(f"\n{strategy.upper()}")
print(f" DR kostnad: {result['dr_monthly_nok']:,} NOK/mnd ({result['dr_percentage']}%)")
print(f" Total: {result['total_monthly_nok']:,} NOK/mnd")
print(f" Årlig DR: {result['dr_annual_nok']:,} NOK")
```
### Kostnadsoversikt per strategi
| Komponent | Primær | Active-Active | Warm Standby | Cold Standby | Backup Only |
|-----------|--------|---------------|-------------|-------------|-------------|
| Azure OpenAI | 25,000 | 25,000 | 7,500 | 0 | 0 |
| AI Search | 15,000 | 15,000 | 10,000 | 0 | 0 |
| Cosmos DB | 8,000 | 4,000 | 2,400 | 800 | 400 |
| App Service | 12,000 | 12,000 | 4,000 | 0 | 0 |
| Storage | 3,000 | 900 | 900 | 900 | 450 |
| Networking | 5,000 | 2,500 | 1,000 | 250 | 0 |
| Bandwidth | — | 1,360 | 680 | 340 | 0 |
| **DR total/mnd** | — | **60,760** | **26,480** | **2,290** | **850** |
| **% av primær** | — | **89%** | **39%** | **3%** | **1%** |
| **RTO** | — | ~0 | 515 min | 3060 min | Timer |
| **RPO** | — | ~0 | Minutter | Timer | 24 timer |
*Alle beløp i NOK, estimat for typisk offentlig sektor AI-løsning.*
## RTO/RPO vs. kostnads trade-off analyse
### Beslutningsmatrise
```
Kostnad (NOK/mnd)
│ Active-Active
│ ■ (60K)
│ Warm Standby
│ ■ (26K)
│ Cold Standby
│ ■ (2.3K)
│ Backup/Restore
│ ■ (850)
└─────────────────────────────────────────────────── RTO
0 5min 15min 30min 1h 4h 24h
```
### Break-even analyse
```markdown
## Når er Active-Active verdt det?
Merkostnad Active-Active vs. Warm Standby:
60,760 - 26,480 = 34,280 NOK/mnd = 411,360 NOK/år
For at Active-Active skal være verdt det, må kostnaden
av nedetid overstige denne merkostnaden:
Nedetid-kostnad per hendelse = (RTO_warm - RTO_active) × Kostnad per minutt
Forventet besparelse = Nedetid-kostnad × Antall hendelser per år
Eksempel:
- RTO forskjell: 15 min vs. ~0 = 15 min
- Kostnad per minutt nedetid: 5,000 NOK (tapt produktivitet, omdømme)
- Antall hendelser per år: 2
Besparelse = 15 min × 5,000 NOK × 2 = 150,000 NOK/år
Merkostnad 411,360 > Besparelse 150,000 → Warm Standby er bedre valg
Break-even: 411,360 / (15 × 5,000) = 5.5 hendelser/år
→ Trenger 6+ hendelser/år for at Active-Active lønner seg
```
## Reserved Capacity vs. On-Demand prising
### Besparelser med Reserved Instances
| Tjeneste | On-Demand/mnd | 1-år RI/mnd | 3-år RI/mnd | Besparelse 1-år | Besparelse 3-år |
|----------|-------------|-------------|-------------|----------------|----------------|
| App Service P3v3 | 12,000 | 7,800 | 5,400 | 35% | 55% |
| AI Search S1 (3 rep) | 15,000 | 9,750 | 6,750 | 35% | 55% |
| Azure OpenAI PTU (50) | 50,000 | 35,000 | — | 30% | — |
| Redis Premium P1 | 4,500 | 3,150 | 2,250 | 30% | 50% |
### RI-strategi for DR
```markdown
## Anbefalte reservasjoner for DR
### Active-Active DR
- RI for ALLE tjenester i begge regioner (full besparelse)
- Anbefaling: 1-år RI minimum, 3-år for stabile workloads
### Warm Standby DR
- RI for baseline-kapasitet i DR-region (lavere tier)
- On-demand for burst/scale-up kapasitet
- Anbefaling: 1-år RI for baseline, on-demand for topper
### Cold Standby DR
- INGEN RI for DR-region (ressurser kjører ikke)
- RI kun for primær region
- Anbefaling: Bruk Azure Savings Plans for fleksibilitet
### Savings Plans alternativ
Azure Savings Plans gir 1-år eller 3-år commitment
med fleksibilitet til å bruke kapasiteten i hvilken som helst
region — ideelt for DR der regionen kan endres.
```
## Cross-region bandwidth-kostnader
### Bandwidth-prising mellom Azure-regioner
| Datatype | Volume/mnd | Pris/GB | Kostnad/mnd |
|----------|-----------|---------|-------------|
| Cosmos DB replikering | 50 GB | Inkludert | 0 |
| Blob Storage GRS | 100 GB | ~0.70 NOK | 70 |
| AI Search index sync | 10 GB | ~0.70 NOK | 7 |
| Application data | 200 GB | ~0.70 NOK | 140 |
| **Total bandwidth** | **360 GB** | — | **~217 NOK** |
*Intra-Europa bandwidth er relativt rimelig. Kostnaden øker betydelig for cross-kontinent replikering.*
### Optimalisering av bandwidth-kostnader
```markdown
## Bandwidth-optimaliseringsstrategier
1. **Komprimering**: Aktiver gzip/brotli for all cross-region trafikk
- Typisk besparelse: 6080% på tekstbasert data
2. **Delta-replikering**: Synkroniser kun endringer, ikke full kopi
- Bruk Azure Blob Storage Change Feed
- Event-driven sync i stedet for full re-indeksering
3. **Batch vs. real-time**: Batchvise oppdateringer reduserer overhead
- Samle opp endringer og synkroniser hvert 5. minutt
4. **CDN for statisk innhold**: Bruk Azure CDN for dokumenter
- Reduserer cross-region trafikk for hyppig leste filer
```
## Kostnadsoptimalisering og Reserved Instances
### Azure Cost Management dashboard
```bash
# Opprett kostnadsrapport for DR-ressurser
az costmanagement export create \
--name "dr-cost-report" \
--scope "/subscriptions/{sub}/resourceGroups/rg-ai-dr" \
--type "ActualCost" \
--timeframe "MonthToDate" \
--storage-account "stacostmgmt" \
--storage-container "cost-reports" \
--recurrence "Monthly" \
--recurrence-period '{"from": "2026-01-01", "to": "2026-12-31"}'
# Sett budsjett med varsler
az consumption budget create \
--budget-name "ai-dr-budget-2026" \
--amount 400000 \
--category "Cost" \
--time-grain "Annually" \
--time-period '{"Start": "2026-01-01", "End": "2026-12-31"}' \
--resource-groups "rg-ai-dr" \
--notifications '{
"Warning50": {"enabled": true, "operator": "GreaterThan", "threshold": 50,
"contactEmails": ["platform@org.no"]},
"Warning80": {"enabled": true, "operator": "GreaterThan", "threshold": 80,
"contactEmails": ["platform@org.no", "management@org.no"]},
"Critical100": {"enabled": true, "operator": "GreaterThan", "threshold": 100,
"contactEmails": ["platform@org.no", "management@org.no", "cto@org.no"]}
}'
```
### Kvartalsvis kostnadsrapport-mal
```markdown
## BCDR Kostnadsrapport — Q[X] 2026
### Oppsummering
| Kategori | Budsjett | Faktisk | Avvik |
|----------|---------|--------|-------|
| DR infrastruktur | X NOK | X NOK | X% |
| Bandwidth | X NOK | X NOK | X% |
| DR-testing | X NOK | X NOK | X% |
| **Total** | **X NOK** | **X NOK** | **X%** |
### DR-hendelser dette kvartalet
- Antall failover-initieringer: X
- Gjennomsnittlig RTO oppnådd: X min
- Estimert verdi av DR (unngått nedetid): X NOK
### Optimaliseringsmuligheter
1. [Identifisert mulighet med estimert besparelse]
2. [...]
### Anbefalinger
- [Anbefalte endringer med kostnadspåvirkning]
```
## Referanser
- [Optimize your recovery costs](https://learn.microsoft.com/en-us/azure/well-architected/design-guides/disaster-recovery#optimize-your-recovery-costs) — WAF kostnadsoptimalisering for DR
- [Azure Site Recovery pricing](https://azure.microsoft.com/pricing/details/site-recovery/) — Prising for Site Recovery
- [Azure bandwidth pricing](https://azure.microsoft.com/pricing/details/bandwidth/) — Bandwidth-priser mellom regioner
- [Azure pricing calculator](https://azure.microsoft.com/pricing/calculator/) — Generell priskalkulator
- [Microsoft Cost Management](https://learn.microsoft.com/en-us/azure/cost-management-billing/) — Kostnadsovervåking
- [Azure Savings Plans overview](https://learn.microsoft.com/en-us/azure/cost-management-billing/savings-plan/) — Flexible reservasjoner
## For Cosmo
- **Bruk denne referansen** når kunden trenger kostnadsestimat for BCDR-løsninger, eller når de skal sammenligne DR-strategier.
- Warm standby er sweet spot for de fleste offentlige AI-løsninger — 2540% merkostnad for minutter RTO.
- Vis alltid break-even analyse: sammenlign DR-merkostnaden med estimert kostnad ved nedetid for å rettferdiggjøre investeringen.
- Azure OpenAI: Pay-per-token i DR-region er nesten alltid bedre enn PTU fordi DR-trafikken er lav under normal drift.
- For Utredningsinstruksen: Presenter alltid minimum 3 alternativer (f.eks. cold/warm/active-active) med kost/nytte-vurdering.

View file

@ -0,0 +1,306 @@
# Data Replication Patterns for AI Systems
**Last updated:** 2026-02
**Status:** GA
**Category:** Business Continuity & Disaster Recovery
---
## Introduksjon
Datareplikering er fundamentet for Business Continuity i AI-systemer. AI-arbeidsbelastninger har spesielle krav til datakonsistens, latens og tilgjengelighet som gjør valg av replikasjonsmekanisme særlig viktig. En RAG-løsning må for eksempel replikere både search-indekser, embedding-vektorer, kildedokumenter og konversasjonshistorikk — hver med ulike konsistens- og latensbehov.
Azure tilbyr flere replikasjonsmønstre: synkron replikering innenfor tilgjengelighetssoner (Availability Zones), asynkron geo-replikering til sekundærregioner, og applikasjonsbasert replikering for tjenester som ikke har innebygd DR. Valget mellom disse mønstrene påvirker direkte RPO, ytelse og kostnad.
For norsk offentlig sektor er det spesielt viktig å forstå data residency-implikasjoner av geo-replikering. Replikering til en sekundærregion må skje innenfor godkjente geografiske grenser (EU/EØS), og organisasjonen må dokumentere dataflyter i sine behandlingsprotokoll iht. GDPR artikkel 30.
## Synkron vs. asynkron replikering
### Synkron replikering
Ved synkron replikering bekreftes ikke en skriveoperasjon som fullført før dataene er skrevet til alle replikaer. Dette gir null datatap (RPO = 0), men øker skrivelatens.
| Egenskap | Synkron | Asynkron |
|----------|---------|----------|
| RPO | 0 | > 0 (sekunder til minutter) |
| Skrivelatens | Høyere (avhenger av avstand) | Lavere |
| Leseytelse | Kan lese fra replikaer | Kan lese fra replikaer (eventual consistency) |
| Kostnad | Høyere (alltid aktive replikaer) | Lavere |
| Typisk bruk | Intra-region (AZ), mission critical | Cross-region DR |
### Azure Storage replikeringsalternativer
```
LRS → 3 kopier i samme datasenter
ZRS → 3 kopier på tvers av Availability Zones (synkron)
GRS → LRS + asynkron til sekundær region (LRS der)
GZRS → ZRS + asynkron til sekundær region (LRS der)
RA-GRS/RA-GZRS → Tillegg: lesetilgang til sekundær region
```
### Replikeringsvalg per AI-komponent
| AI-komponent | Anbefalt replikering | Begrunnelse |
|--------------|---------------------|-------------|
| Azure Blob Storage (dokumenter) | GZRS / RA-GZRS | Best balance mellom tilgjengelighet og DR |
| Azure Cosmos DB (state/session) | Multi-region writes | Automatisk geo-replikering med ~0 RPO |
| Azure SQL Database | Active geo-replication | Asynkron med ~5 sek RPO |
| Azure AI Search indekser | Manuell dual-indexing | Ingen innebygd replikering |
| Azure OpenAI (modell-config) | IaC-basert redeploy | Stateless tjeneste |
| Azure Key Vault | Automatisk failover | Microsoft-managed geo-replikering |
## Active-Active og Active-Passive mønstre
### Active-Active pattern
I et Active-Active oppsett er begge regioner aktive og mottar trafikk. Dette krever:
- Identisk infrastruktur i begge regioner
- Load balancer for trafikk-distribusjon
- Konflikthåndtering for samtidige skrivinger
```
┌──────────────┐ ┌────────────────┐ ┌──────────────┐
│ Brukere │────▶│ Azure Front │────▶│ Region A │
│ │ │ Door / TM │ │ (Active) │
│ │ │ Latency-based │────▶│ Region B │
│ │ │ routing │ │ (Active) │
└──────────────┘ └────────────────┘ └──────────────┘
```
**Azure Cosmos DB Active-Active eksempel:**
```bash
# Opprett Cosmos DB konto med multi-region writes
az cosmosdb create \
--name "cosmos-ai-state" \
--resource-group "rg-ai-prod" \
--locations regionName="norwayeast" failoverPriority=0 isZoneRedundant=true \
--locations regionName="swedencentral" failoverPriority=1 isZoneRedundant=true \
--enable-multiple-write-locations true \
--default-consistency-level "Session"
# Verifiser replikering
az cosmosdb show \
--name "cosmos-ai-state" \
--resource-group "rg-ai-prod" \
--query "writeLocations[].{Region:locationName, Status:failoverPriority}"
```
### Active-Passive pattern
Active-Passive er mer kostnadseffektivt og enklere å implementere. Primærregionen håndterer all trafikk; sekundærregionen overtar kun ved failover.
**Warm Standby varianter:**
| Variant | Sekundær region | RTO | Kostnad |
|---------|----------------|-----|---------|
| Hot Standby | Full kapasitet, mottar replikert data | Sekunder | Høyest |
| Warm Standby | Minimal kapasitet, auto-scales ved failover | Minutter | Middels |
| Cold Standby | Kun IaC-templates, ingen kjørende ressurser | Timer | Lavest |
```python
# Active-Passive med Azure Traffic Manager health probes
# Bicep template for Traffic Manager profil
"""
resource trafficManagerProfile 'Microsoft.Network/trafficmanagerprofiles@2022-04-01' = {
name: 'tm-ai-service'
location: 'global'
properties: {
profileStatus: 'Enabled'
trafficRoutingMethod: 'Priority'
monitorConfig: {
protocol: 'HTTPS'
port: 443
path: '/health'
intervalInSeconds: 10
timeoutInSeconds: 5
toleratedNumberOfFailures: 3
}
endpoints: [
{
name: 'primary-norwayeast'
type: 'Microsoft.Network/trafficmanagerprofiles/azureEndpoints'
properties: {
targetResourceId: primaryAppService.id
priority: 1
weight: 1
}
}
{
name: 'secondary-swedencentral'
type: 'Microsoft.Network/trafficmanagerprofiles/azureEndpoints'
properties: {
targetResourceId: secondaryAppService.id
priority: 2
weight: 1
}
}
]
}
}
"""
```
## Konsistensmodeller og eventual consistency
### CAP-teoremet og AI-systemer
AI-systemer må velge mellom konsistens (C), tilgjengelighet (A) og partisjontoleranse (P). For de fleste AI-workloads er eventual consistency akseptabelt.
### Cosmos DB konsistensmodeller
| Modell | Garanti | Latens | Anbefalt for |
|--------|---------|--------|-------------|
| Strong | Lineariserbar | Høyest | Finansielle transaksjoner |
| Bounded Staleness | K versjoner eller T tid | Høy | Leaderboard, tellere |
| Session | Konsistent innen sesjon | Middels | Chatbot state (anbefalt) |
| Consistent Prefix | Aldri out-of-order | Lav | Aktivitetslogg |
| Eventual | Ingen garanti om rekkefølge | Lavest | Analytics, rapportering |
```csharp
// C# eksempel: Session consistency for AI chatbot state
using Microsoft.Azure.Cosmos;
var cosmosClient = new CosmosClient(
connectionString,
new CosmosClientOptions
{
ConsistencyLevel = ConsistencyLevel.Session,
ApplicationPreferredRegions = new List<string>
{
Regions.NorwayEast,
Regions.SwedenCentral
}
});
// Hent session token fra response
var response = await container.ReadItemAsync<ConversationState>(
id: sessionId,
partitionKey: new PartitionKey(userId));
string sessionToken = response.Headers.Session;
// Bruk session token for konsistent lesing i neste request
var options = new ItemRequestOptions { SessionToken = sessionToken };
```
## Konfliktløsningsstrategier
### Last-Writer-Wins (LWW)
Standard konflikthåndtering i Cosmos DB. Basert på `_ts` (timestamp) feltet — siste skriving vinner.
### Custom conflict resolution
```javascript
// Cosmos DB custom conflict resolution stored procedure
function resolveConflict(incomingRecord, existingRecord, isTombstone, conflictingRecords) {
// For AI chatbot: merge conversation history
if (incomingRecord.messageHistory && existingRecord.messageHistory) {
// Kombiner meldingshistorikk fra begge regioner
var merged = existingRecord.messageHistory.concat(
incomingRecord.messageHistory.filter(
m => !existingRecord.messageHistory.some(e => e.id === m.id)
)
);
// Sorter kronologisk
merged.sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp));
existingRecord.messageHistory = merged;
existingRecord._ts = Math.max(incomingRecord._ts, existingRecord._ts);
}
var context = getContext();
var collection = context.getCollection();
collection.replaceDocument(existingRecord._self, existingRecord);
}
```
### Konfliktmønstre for AI-data
| Datatype | Konfliktrisiko | Anbefalt strategi |
|----------|---------------|-------------------|
| Brukerpreferanser | Lav | Last-Writer-Wins |
| Konversasjonshistorikk | Middels | Merge med dedup |
| Feedback/ratings | Lav | Append-only |
| Search indeks-oppdateringer | Høy | Source-of-truth rebuild |
| Model config | Lav | Version-basert (IaC) |
## Monitoring av replikasjonsforsinkelse og helse
### Azure Monitor for replication health
```kusto
// KQL: Overvåk Cosmos DB replication lag
AzureDiagnostics
| where ResourceProvider == "MICROSOFT.DOCUMENTDB"
| where Category == "DataPlaneRequests"
| summarize
AvgLatencyMs = avg(duration_s * 1000),
MaxLatencyMs = max(duration_s * 1000),
P99LatencyMs = percentile(duration_s * 1000, 99)
by bin(TimeGenerated, 5m), regionName_s
| order by TimeGenerated desc
```
```kusto
// KQL: Azure Storage Last Sync Time for GRS
StorageBlobLogs
| where OperationName == "GetBlobServiceProperties"
| extend lastSyncTime = tostring(parse_json(ResponseBody).GeoReplication.LastSyncTime)
| project TimeGenerated, lastSyncTime, StatusCode
| order by TimeGenerated desc
```
### Alert-regler for replication health
```bash
# Azure Monitor alert: Cosmos DB replication lag > 5 sekunder
az monitor metrics alert create \
--name "cosmosdb-replication-lag-alert" \
--resource-group "rg-ai-prod" \
--scopes "/subscriptions/{sub}/resourceGroups/rg-ai-prod/providers/Microsoft.DocumentDB/databaseAccounts/cosmos-ai-state" \
--condition "avg ReplicationLatency > 5000" \
--window-size 5m \
--evaluation-frequency 1m \
--severity 2 \
--action-group "ag-oncall-team"
# Azure Storage: Last Sync Time alert
az monitor metrics alert create \
--name "storage-geo-lag-alert" \
--resource-group "rg-ai-prod" \
--scopes "/subscriptions/{sub}/resourceGroups/rg-ai-prod/providers/Microsoft.Storage/storageAccounts/staiprod" \
--condition "avg GeoReplicationLag > 900" \
--window-size 15m \
--evaluation-frequency 5m \
--severity 2 \
--action-group "ag-oncall-team"
```
### Dashboard-metrikker
| Metrikk | Terskel (Warning) | Terskel (Critical) | Tjeneste |
|---------|-------------------|-------------------|----------|
| Replication Latency | > 2 sek | > 10 sek | Cosmos DB |
| Geo Replication Lag | > 5 min | > 15 min | Azure Storage |
| Last Sync Time age | > 10 min | > 30 min | Azure Storage GRS |
| Active Geo-Repl lag | > 10 sek | > 60 sek | Azure SQL |
| Search index sync delta | > 100 docs | > 1000 docs | AI Search (custom) |
## Referanser
- [Azure Storage redundancy](https://learn.microsoft.com/en-us/azure/storage/common/storage-redundancy) — LRS, ZRS, GRS, GZRS-oversikt
- [Azure Storage Geo Priority Replication](https://learn.microsoft.com/en-us/azure/storage/common/storage-redundancy-priority-replication) — SLA-backed RPO
- [Active geo-replication for Azure SQL Database](https://learn.microsoft.com/en-us/azure/azure-sql/database/active-geo-replication-overview) — SQL Database replikering
- [Azure Cosmos DB global distribution](https://learn.microsoft.com/en-us/azure/cosmos-db/distribute-data-globally) — Multi-region writes og consistency
- [What are redundancy, replication, and backup?](https://learn.microsoft.com/en-us/azure/reliability/concept-redundancy-replication-backup) — Grunnleggende konsepter
- [Use geo-redundancy to design highly available applications](https://learn.microsoft.com/en-us/azure/storage/common/geo-redundant-design) — RA-GRS/RA-GZRS designmønstre
- [Multi-region deployments in Azure AI Search](https://learn.microsoft.com/en-us/azure/search/search-multi-region) — AI Search multi-region
## For Cosmo
- **Bruk denne referansen** når kunden trenger hjelp med å velge replikasjonsmekanismer for AI-løsninger, eller når de designer multi-region arkitekturer.
- Anbefal alltid GZRS (ikke GRS) for AI-workloads der Availability Zones er tilgjengelig — det gir best kombinasjon av intra-region HA og cross-region DR.
- For Cosmos DB: Session consistency er nesten alltid riktig valg for AI chatbots — det gir "read-your-own-writes" uten unødvendig latenskostnad.
- Påminn om at Azure AI Search IKKE har innebygd replikering — multi-region krever manuell dual-indexing eller rebuild fra kilde.
- For data residency: Verifiser alltid at sekundærregionen er innenfor godkjente geografiske grenser (Norway East ↔ Sweden Central er typisk godkjent for norske organisasjoner).

View file

@ -0,0 +1,611 @@
# Failover Testing for AI Services
**Last updated:** 2026-02
**Status:** GA
**Category:** Business Continuity & Disaster Recovery
---
## Introduksjon
Failover-testing er en kritisk men ofte forsoemmt del av disaster recovery for AI-tjenester. En DR-plan som ikke er testet er i praksis ingen plan -- den gir en falsk trygghet som kan forsterke konsekvensene av et reelt utfall. Microsoft anbefaler eksplisitt a gjennomfoere regelmessige failover-drills for a validere at resiliens-mekanismer fungerer som forventet, og at teamet er i stand til a haandtere en krise effektivt.
For AI-tjenester er failover-testing spesielt utfordrende av flere grunner. For det foerste er AI-inferens ofte tilstandsloest (stateless), men konteksten rundt -- samtalehistorikk, agent-tilstand, RAG-indekser -- er hoyst tilstandsfull. En vellykket failover av selve inferens-endepunktet betyr lite hvis samtalehistorikken gaar tapt eller kunnskapsbasen ikke er tilgjengelig i failover-regionen. For det andre har AI-tjenester kvotebegrensninger per region, saa en failover kan resultere i lavere kapasitet hvis sekundaerregionen har mindre kvote. For det tredje bruker mange AI-loesninger asynkrone pipelines (batch-prosessering, evaluering, fine-tuning) som har andre failover-moenstre enn sanntids-inferens.
Denne referansen dekker planlagte failover-testscenarier, validering og overvaking under failover, suksesskriterier og akseptanseterskel, dokumentasjon og laerdommer, samt regelmessig testplanlegging og frekvens. Alt er forankret i Microsofts veiledning for chaos engineering, Azure Chaos Studio, og Well-Architected Framework for reliability testing.
## Planlagte failover-testscenarier
### Scenariokatalog for AI-tjenester
Failover-tester boer dekke flere niva -- fra enkeltkomponent til fullstendig regional failover:
| Nivaa | Scenario | Beskrivelse | Kompleksitet | Risiko |
|-------|---------|-------------|-------------|--------|
| **L1** | Enkelt AOAI-endepunkt utilgjengelig | Simuler 429/503 fra ett Azure OpenAI-endepunkt | Lav | Lav |
| **L2** | Cosmos DB regional failover | Bytt skriveregion for agentdata | Middels | Middels |
| **L3** | Full gateway-failover | Ruter all trafikk via sekundaer APIM | Middels | Middels |
| **L4** | AI Search utilgjengelig | RAG-indeks nede, test fallback | Middels | Lav |
| **L5** | Komplett regional failover | All AI-infrastruktur bytter region | Hoey | Hoey |
| **L6** | Korrupt data / utilsiktet sletting | Gjenopprett fra backup | Hoey | Middels |
### Scenario L1: Azure OpenAI endepunkt-failover
**Formal:** Verifisere at APIM-gatewayen korrekt ruter trafikk til sekundaert endepunkt nar primaert returnerer feil.
**Fremgangsmate:**
```
1. Forutsetninger:
- APIM konfigurert med backend pool (Norway East primaer, Sweden Central sekundaer)
- Overvaking aktiv (Application Insights, Azure Monitor)
- Lastetest kjoerer for a generere trafikk
2. Feilinjeksjon:
- Metode A: APIM policy-endring (fjern primaer backend fra pool)
- Metode B: Azure Chaos Studio eksperiment
- Metode C: Nettverksregel som blokkerer trafikk til primaer
3. Forventet oppfoersel:
- Gateway returnerer 429/503 fra primaer backend
- Circuit breaker trigger innen < 5 sekunder
- Trafikk rutes automatisk til sekundaer backend
- Sluttbrukere opplever < 10 sekunder forsinkelse
4. Validering:
- Alle API-kall returnerer 200 innen 30 sekunder
- Latens stabiliserer seg innen 60 sekunder
- Ingen tapt kontekst for pagaende samtaler (med session affinity)
```
**APIM policy for simulert feil:**
```xml
<!-- Midlertidig policy for failover-test: simuler 503 fra primaer -->
<policies>
<inbound>
<base />
<choose>
<when condition="@(context.Request.Headers.GetValueOrDefault("X-Failover-Test","") == "active")">
<!-- Under test: fjern primaer fra backend pool -->
<set-backend-service backend-id="openai-secondary-only" />
</when>
<otherwise>
<set-backend-service backend-id="openai-backend-pool" />
</otherwise>
</choose>
</inbound>
</policies>
```
### Scenario L2: Cosmos DB failover
**Formal:** Verifisere at agentdata er tilgjengelig etter Cosmos DB regional failover.
**Fremgangsmate med Azure Chaos Studio:**
```json
{
"type": "Microsoft.Chaos/experiments",
"name": "cosmos-failover-test",
"location": "norwayeast",
"properties": {
"steps": [
{
"name": "Failover-Cosmos-DB",
"branches": [
{
"name": "cosmos-branch",
"actions": [
{
"type": "continuous",
"name": "urn:csci:microsoft:cosmosDB:failover/1.0",
"duration": "PT10M",
"parameters": [
{
"key": "readRegion",
"value": "Norway East"
}
],
"selectorId": "cosmos-target"
}
]
}
]
}
],
"selectors": [
{
"id": "cosmos-target",
"type": "List",
"targets": [
{
"id": "/subscriptions/{sub-id}/resourceGroups/rg-ai-prod/providers/Microsoft.DocumentDB/databaseAccounts/svv-ai-cosmos/providers/Microsoft.Chaos/targets/Microsoft-CosmosDB",
"type": "ChaosTarget"
}
]
}
]
}
}
```
**Forventet oppfoersel:**
- Cosmos DB bytter skriveregion fra Norway East til Sweden Central
- Lesning kan ha kortvarig hoeyre latens under failover
- Agentsamtaler kan fortsette uten datatap
- Failover fullfores innen < 5 minutter
### Scenario L3: Full gateway-failover
**Formal:** Verifisere at all AI-trafikk kan betjenes fra sekundaer gateway-region.
```
Testoppfoersel:
1. Start med normal trafikk gjennom APIM i Norway East
2. Simuler at APIM i Norway East er utilgjengelig:
- DNS-endring: Pek gateway-FQDN til Sweden Central
- Eller: Azure Front Door helsesjekk feiler for Norway East
3. Verifiser:
- Trafikk rutes til APIM i Sweden Central
- APIM i Sweden Central nar Azure OpenAI i Sweden Central
- Responstid er innenfor akseptabel terskel
- Alle funksjoner (chat, RAG, agent) fungerer
4. Failback:
- Gjenopprett APIM i Norway East
- Verifiser at trafikk returnerer til primaer
```
### Scenario L5: Komplett regional failover
**Formal:** Validere full DR-prosedyre med alle komponenter.
```
Tidsplan for full DR-drill (estimert 4-6 timer):
T+0:00 - Annonsering: "DR-drill starter"
T+0:05 - Simulert utfall av Norway East (DNS/nettverk)
T+0:10 - Deteksjon: Varsling utloeses automatisk
T+0:15 - Vurdering: Driftsteam bekrefter utfall
T+0:20 - Beslutning: Iverksett DR-plan
T+0:25 - Gateway failover: APIM rutes til Sweden Central
T+0:30 - Data failover: Verifiser Cosmos DB og Storage
T+0:45 - Agent redeploy: Deploy agentdefinisjoner i sekundaer region
T+1:00 - Validering: Funksjonelle tester
T+1:30 - Stabilisering: Overvak i 30 minutter
T+2:00 - Normal drift fra sekundaer region bekreftet
T+3:00 - Failback paabegynnes
T+3:30 - Primaer region gjenopprettet
T+4:00 - Normal drift fra primaer region bekreftet
T+4:30 - Retrospektiv og dokumentasjon
```
## Validering og overvaking under failover
### Helsesjekk-endepunkter
Implementer dedikerte helsesjekk-endepunkter for AI-tjenestene:
```python
# health_check.py -- Eksempel pa helsesjekk for AI-stack
import asyncio
from datetime import datetime
from openai import AzureOpenAI
async def check_openai_health(endpoint: str, api_key: str) -> dict:
"""Sjekk Azure OpenAI tilgjengelighet og latens."""
start = datetime.now()
try:
client = AzureOpenAI(
azure_endpoint=endpoint,
api_key=api_key,
api_version="2024-06-01"
)
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "ping"}],
max_tokens=5,
temperature=0
)
latency_ms = (datetime.now() - start).total_seconds() * 1000
return {
"status": "healthy",
"latency_ms": round(latency_ms),
"endpoint": endpoint,
"model": "gpt-4o",
"timestamp": datetime.now().isoformat()
}
except Exception as e:
return {
"status": "unhealthy",
"error": str(e),
"endpoint": endpoint,
"timestamp": datetime.now().isoformat()
}
async def check_cosmos_health(endpoint: str) -> dict:
"""Sjekk Cosmos DB tilgjengelighet."""
# Implementer med Azure Cosmos DB SDK
pass
async def full_health_check() -> dict:
"""Komplett helsesjekk for alle AI-komponenter."""
results = await asyncio.gather(
check_openai_health("https://svv-aoai-ne.openai.azure.com", "***"),
check_openai_health("https://svv-aoai-sc.openai.azure.com", "***"),
check_cosmos_health("https://svv-ai-cosmos.documents.azure.com"),
)
overall = "healthy" if all(r["status"] == "healthy" for r in results) else "degraded"
return {"overall": overall, "components": results}
```
### KQL-queries for failover-overvaking
```kusto
// Overvaak feilrate under failover-test
AppRequests
| where TimeGenerated > ago(1h)
| where AppRoleName == "ai-gateway"
| summarize
TotalRequests = count(),
FailedRequests = countif(ResultCode >= 500),
AvgDuration = avg(DurationMs),
P95Duration = percentile(DurationMs, 95),
P99Duration = percentile(DurationMs, 99)
by bin(TimeGenerated, 1m)
| extend FailureRate = round(todouble(FailedRequests) / TotalRequests * 100, 2)
| order by TimeGenerated desc
```
```kusto
// Sporr backend-skifte under failover
AppDependencies
| where TimeGenerated > ago(1h)
| where DependencyType == "HTTP"
| where Target contains "openai.azure.com"
| summarize
RequestCount = count(),
AvgDuration = avg(DurationMs),
FailCount = countif(ResultCode >= 400)
by bin(TimeGenerated, 1m), Target
| order by TimeGenerated desc
```
```kusto
// Cosmos DB failover-hendelser
AzureDiagnostics
| where ResourceProvider == "MICROSOFT.DOCUMENTDB"
| where Category == "DataPlaneRequests"
| where TimeGenerated > ago(1h)
| summarize
RequestCount = count(),
AvgLatency = avg(duration_s * 1000),
ErrorCount = countif(statusCode_s >= "400")
by bin(TimeGenerated, 1m), regionName_s
| order by TimeGenerated desc
```
### Azure Monitor Dashboard for failover
Opprett et dedikert dashboard for failover-overvaking:
| Panel | Metrikk | Terskel (groen) | Terskel (roed) |
|-------|---------|-----------------|----------------|
| AOAI feilrate | % 4xx/5xx | < 1% | > 5% |
| AOAI latens (P95) | Millisekunder | < 2000 ms | > 5000 ms |
| APIM throughput | Requests/min | > 80% av baseline | < 50% av baseline |
| Cosmos DB latens | Millisekunder | < 50 ms | > 200 ms |
| Cosmos DB tilgjengelighet | % | > 99.9% | < 99% |
| Aktiv region | Region-label | Primaer | Sekundaer |
## Suksesskriterier og akseptanseterskel
### Definerte suksesskriterier per testnivaa
| Testnivaa | Kriterie | Maal | Akseptabelt | Feil |
|-----------|---------|------|-------------|------|
| **L1: Endepunkt-failover** | Gjenopprettingstid | < 10 sek | < 30 sek | > 30 sek |
| | Feilrate under failover | 0% | < 2% | > 5% |
| | Latensoekning | < 50% | < 100% | > 200% |
| **L2: Cosmos DB failover** | Gjenopprettingstid | < 2 min | < 5 min | > 10 min |
| | Datatap (RPO) | 0 sek | < 10 sek | > 60 sek |
| | Agentfunksjonalitet | Full | Degradert | Utilgjengelig |
| **L3: Gateway-failover** | Gjenopprettingstid | < 5 min | < 15 min | > 30 min |
| | Feilrate for sluttbrukere | < 1% | < 5% | > 10% |
| | Funksjonell dekning | 100% | > 90% | < 80% |
| **L5: Full regional** | Gjenopprettingstid (RTO) | < 30 min | < 60 min | > 120 min |
| | Datatap (RPO) | < 5 min | < 30 min | > 60 min |
| | Alle tjenester operative | 100% | > 95% | < 90% |
### Akseptanseprotokoll
```
FAILOVER-TEST AKSEPTANSEPROTOKOLL
==================================
Testdato: _______________
Testnivaa: [ ] L1 [ ] L2 [ ] L3 [ ] L4 [ ] L5 [ ] L6
Testleder: _______________
Deltakere: _______________
RESULTATER:
-----------
Malt RTO: _____ min/sek
Malt RPO: _____ min/sek
Maks feilrate: _____ %
Maks latensoekning: _____ %
Funksjoner tilgjengelig: _____ %
VURDERING:
----------
[ ] BESTATT -- Alle kriterier innenfor "Maal"
[ ] AKSEPTABELT -- Alle kriterier innenfor "Akseptabelt"
[ ] FEIL -- Ett eller flere kriterier i "Feil"-sonen
AVVIK OG MERKNADER:
____________________________________________________
____________________________________________________
SIGNATUR:
Testleder: _____________ Dato: __________
Godkjenner: ____________ Dato: __________
```
### Baseline-maling
Foer failover-testing ma du etablere en baseline for normal ytelse:
```bash
# Kjoer baseline-lasttest med Azure Load Testing
az load test create \
--name ai-baseline-test \
--resource-group rg-ai-prod \
--location norwayeast \
--test-plan tests/baseline-load-test.jmx \
--engine-instances 2
# Baseline-kriterier (eksempel):
# - Gjennomsnittlig responstid: < 1500 ms
# - P95 responstid: < 3000 ms
# - Feilrate: < 0.5%
# - Throughput: > 50 req/sek
```
## Dokumentasjon og laerdommer
### Testreportmal
```markdown
# Failover Test Report
## Testoversikt
- **Dato:** 2026-02-15
- **Scenario:** L3 -- Full gateway-failover
- **Varighet:** 2 timer 15 minutter
- **Deltakere:** [Navn og roller]
## Sammendrag
[2-3 setninger om hva som ble testet og hovedresultatet]
## Testforloep
| Tid | Hendelse | Status |
|-----|---------|--------|
| 10:00 | Test startet, normal trafikk | OK |
| 10:05 | Primaer gateway deaktivert | OK |
| 10:05:12 | Foerste feil detektert av monitor | OK |
| 10:05:45 | Trafikk rutes til sekundaer | OK |
| 10:06:30 | Alle helsesjekker gronne | OK |
| ... | ... | ... |
## Malte resultater
| Kriterie | Maal | Resultat | Status |
|---------|------|---------|--------|
| RTO | < 5 min | 1 min 30 sek | BESTATT |
| Feilrate | < 5% | 2.1% | BESTATT |
| Latens P95 | < 3000 ms | 2800 ms | BESTATT |
## Funn og observasjoner
1. [Funn 1: Beskrivelse og alvorlighet]
2. [Funn 2: Beskrivelse og alvorlighet]
## Forbedringstiltak
| # | Tiltak | Prioritet | Ansvarlig | Frist |
|---|--------|-----------|-----------|-------|
| 1 | [Tiltak] | Hoey | [Navn] | [Dato] |
| 2 | [Tiltak] | Middels | [Navn] | [Dato] |
## Laerdommer (Lessons Learned)
- **Hva fungerte bra:** [Beskrivelse]
- **Hva kan forbedres:** [Beskrivelse]
- **Uventede funn:** [Beskrivelse]
```
### Kunnskapsbase for failover-laerdommer
Bygg en loepende kunnskapsbase med laerdommer fra failover-tester:
| Dato | Scenario | Laerdom | Tiltak | Status |
|------|---------|---------|--------|--------|
| 2026-01-15 | L1 | Circuit breaker brukte 45 sek (for lang) | Reduser timeout til 10 sek | Implementert |
| 2026-02-01 | L2 | Cosmos DB failover tok 8 min (mal: 5 min) | Aktiver service-managed failover | Planlagt |
| 2026-02-15 | L3 | DNS-propagering tok 10 min | Reduser TTL til 60 sek | Under arbeid |
### Post-incident review-prosess
Etter hver failover-test (og spesielt etter reelle hendelser):
```
1. Samle data (innen 24 timer)
- Loggfiler fra alle komponenter
- Metrikkdata fra Azure Monitor
- Tidslinje for hendelser
- Kommunikasjonslogg
2. Gjennomfoere retrospektiv (innen 1 uke)
- Blameless post-mortem
- Identifiser rotaarsaker
- Dokumenter tidslinje
- Klassifiser funn (kritisk/hoey/middels/lav)
3. Definere tiltak (under retrospektiv)
- Konkrete tiltak med eier og frist
- Oppdater DR-plan
- Oppdater runbooks
- Planlegg oppfoelgingstest
4. Foelg opp (loepende)
- Sporr tiltak i Linear/backlog
- Verifiser implementering
- Test forbedringer i neste planlagte test
```
## Regelmessig testplanlegging og frekvens
### Anbefalt testfrekvens
| Testtype | Frekvens | Deltakere | Estimert tid |
|----------|----------|-----------|-------------|
| **L1: Endepunkt-failover** | Ukentlig (automatisert) | CI/CD pipeline | 5-10 min |
| **L2: Komponent-failover** | Manedlig | Driftsteam (2-3 personer) | 1-2 timer |
| **L3: Gateway-failover** | Kvartalsvis | Drifts- + utviklingsteam | 2-4 timer |
| **L5: Full regional drill** | Halvaarlig | Alle (inkl. ledelse) | 4-8 timer |
| **Tabletop exercise** | Kvartalsvis | Arkitektur + drift + forretning | 2 timer |
| **Chaos engineering** | Lopende i CI/CD | Automatisert | Varierer |
### Arsplan for failover-testing
```
Q1 (jan-mar):
- Uke 2: Tabletop exercise (alle scenarier)
- Uke 4: L2 komponent-test (Cosmos DB failover)
- Uke 8: L3 gateway-failover
- Uke 12: L5 FULL DR-DRILL
Q2 (apr-jun):
- Uke 14: Tabletop exercise (nye scenarier)
- Uke 17: L2 komponent-test (Storage failover)
- Uke 20: L3 gateway-failover
- Uke 24: L2 komponent-test (AI Search recovery)
Q3 (jul-sep):
- Uke 27: Tabletop exercise (oppdatert DR-plan)
- Uke 30: L2 komponent-test (Cosmos DB failover)
- Uke 33: L3 gateway-failover
- Uke 36: L5 FULL DR-DRILL
Q4 (okt-des):
- Uke 40: Tabletop exercise (arsrevisjon)
- Uke 43: L2 komponent-test (valgfritt scenario)
- Uke 46: L3 gateway-failover
- Uke 50: Arlig DR-rapportering og planrevisjon
```
### Automatisert failover-testing i CI/CD
Integrer L1-tester i CI/CD-pipeline:
```yaml
# azure-pipelines.yml -- Failover test stage
- stage: FailoverTest
displayName: 'Automated Failover Validation'
dependsOn: DeployStaging
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
jobs:
- job: RunChaosExperiment
steps:
- task: AzureCLI@2
displayName: 'Start Chaos Experiment'
inputs:
azureSubscription: 'svv-ai-staging'
scriptType: 'bash'
inlineScript: |
# Start chaos experiment
az chaos experiment start \
--name ai-gateway-failover-test \
--resource-group rg-ai-staging
# Vent pa at eksperimentet fullfores
az chaos experiment show \
--name ai-gateway-failover-test \
--resource-group rg-ai-staging \
--query "status" -o tsv
- task: AzureCLI@2
displayName: 'Run Load Test During Chaos'
inputs:
azureSubscription: 'svv-ai-staging'
scriptType: 'bash'
inlineScript: |
# Kjoer lasttest parallelt med chaos experiment
az load test run create \
--test-id ai-failover-load-test \
--resource-group rg-ai-staging \
--load-test-resource svv-ai-load-test
- task: AzureCLI@2
displayName: 'Validate Results'
inputs:
azureSubscription: 'svv-ai-staging'
scriptType: 'bash'
inlineScript: |
# Valider at feilrate er innenfor terskel
ERROR_RATE=$(az monitor metrics list \
--resource "/subscriptions/{sub}/..." \
--metric "FailedRequests" \
--interval PT1M \
--query "value[0].timeseries[0].data[-1].total" -o tsv)
if [ "$ERROR_RATE" -gt "5" ]; then
echo "##vso[task.logissue type=error]Failover test failed: error rate $ERROR_RATE% exceeds 5% threshold"
exit 1
fi
echo "Failover test passed: error rate $ERROR_RATE%"
```
### Azure Chaos Studio + Azure Load Testing-integrasjon
Microsoft anbefaler a kombinere Chaos Studio (feilinjeksjon) med Azure Load Testing (syntetisk last) for realistisk failover-validering:
```
+------------------+ +---------------------+
| Azure Load | | Azure Chaos Studio |
| Testing | | |
| (syntetisk last) | | (feilinjeksjon) |
+--------+---------+ +---------+-----------+
| |
| Parallelkjoering |
+------------+------------+
|
+-------v--------+
| AI Application |
| (staging) |
+-------+--------+
|
+------------+------------+
| |
+--------v---------+ +--------v---------+
| Azure OpenAI | | Azure OpenAI |
| (Norway East) | | (Sweden Central) |
+------------------+ +------------------+
```
**Viktig:** Under kombinert chaos + load testing vil man forvente hoeyere feilrate og latens enn normalt. Definer separate baseline-verdier for "normal" og "chaos"-tilstand.
## Referanser
- [What is Azure Chaos Studio?](https://learn.microsoft.com/en-us/azure/chaos-studio/chaos-studio-overview)
- [Create a chaos experiment - Cosmos DB failover](https://learn.microsoft.com/en-us/azure/chaos-studio/chaos-studio-tutorial-service-direct-portal)
- [Continuous validation with Azure Load Testing and Chaos Studio](https://learn.microsoft.com/en-us/azure/architecture/guide/testing/mission-critical-deployment-testing)
- [Architecture strategies for designing a reliability testing strategy](https://learn.microsoft.com/en-us/azure/well-architected/reliability/testing-strategy)
- [Architecture strategies for disaster recovery](https://learn.microsoft.com/en-us/azure/well-architected/reliability/disaster-recovery)
- [Shift right to test in production - Fault injection](https://learn.microsoft.com/en-us/devops/deliver/shift-right-test-production#fault-injection)
- [Deployment and testing for mission-critical workloads on Azure](https://learn.microsoft.com/en-us/azure/well-architected/mission-critical/mission-critical-deployment-testing)
## For Cosmo
- **Bruk denne referansen** nar kunden har implementert DR-infrastruktur men mangler en testplan -- en DR-loesning uten testing er like risikabel som ingen DR-loesning.
- **Start med L1-tester (automatisert)** for a bygge erfaring og tillit foer man gradvis oeker til L3 og L5 -- dette reduserer risikoen for at tester selv forarsaker utfall.
- **Anbefal Azure Chaos Studio + Azure Load Testing-kombinasjonen** som standard verktoeysett. Chaos Studio er GA og stoetter service-direct faults mot Cosmos DB, som er den mest kritiske AI-komponenten a teste.
- **Fremhev at failover-testing ma inkludere hele AI-stakken** -- ikke bare inferens-endepunktet. Samtalehistorikk (Cosmos DB), kunnskapsbaser (AI Search), og agent-definisjoner ma alle valideres under failover.
- **Bruk akseptanseprotokollen og rapportmalen** for a gi kunden konkrete verktøy de kan ta i bruk umiddelbart. Dokumentasjon av tester er like viktig som selve testingen for kontinuerlig forbedring.

View file

@ -0,0 +1,396 @@
# Geo-Redundancy for Azure AI Search
**Last updated:** 2026-02
**Status:** GA
**Category:** Business Continuity & Disaster Recovery
---
## Introduksjon
Azure AI Search er en regional tjeneste uten innebygd geo-replikering eller automatisk failover. Hvis regionen blir utilgjengelig, blir også search-tjenesten utilgjengelig. For AI-løsninger med RAG-arkitektur (Retrieval-Augmented Generation) er dette en kritisk svakhet fordi search-indeksen er hjørnesteinen i hele kunnskapsgjenfinningen.
For å oppnå geo-redundans for Azure AI Search må organisasjoner implementere egne løsninger: identiske search-tjenester i flere regioner, synkroniserte indekser, og load balancing med failover-logikk. Dette krever nøye planlegging av indekseringsstrategier, konsistensgarantier og trafikkstyring.
For norsk offentlig sektor med strenge tilgjengelighetskrav er multi-region AI Search en viktig komponent i BCDR-strategien. Typisk oppsett er primær i Norway East med sekundær i Sweden Central, noe som sikrer data residency innenfor EU/EØS samtidig som det gir regional redundans.
## Indeksreplikering på tvers av regioner
### Arkitekturoversikt
Azure AI Search har ingen innebygd mekanisme for indeksreplikering mellom regioner. Du må implementere en av følgende strategier:
```
Strategi 1: Dual Push Indexing
┌──────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ Datakilde │────▶│ Indexer Pipeline │────▶│ Search Region A │
│ (Blob/SQL) │ │ (Azure Functions) │────▶│ Search Region B │
└──────────────┘ └──────────────────┘ └──────────────────┘
Strategi 2: Pull from Replicated Source
┌──────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ Datakilde A │◀───▶│ GRS / GZRS │◀───▶│ Datakilde B │
│ (Region A) │ │ Replikering │ │ (Region B) │
└──────┬───────┘ └──────────────────┘ └──────┬───────────┘
│ │
▼ ▼
┌──────────────┐ ┌──────────────────┐
│ AI Search A │ │ AI Search B │
│ (Indexer) │ │ (Indexer) │
└──────────────┘ └──────────────────┘
```
### Dual Push Indexing med Azure Functions
```python
# Azure Function: Push-based dual-region indexing
import azure.functions as func
from azure.search.documents import SearchClient
from azure.core.credentials import AzureKeyCredential
import json
# Konfigurer klienter for begge regioner
primary_client = SearchClient(
endpoint="https://search-primary-norwayeast.search.windows.net",
index_name="knowledge-base",
credential=AzureKeyCredential("<primary-key>")
)
secondary_client = SearchClient(
endpoint="https://search-secondary-swedencentral.search.windows.net",
index_name="knowledge-base",
credential=AzureKeyCredential("<secondary-key>")
)
def main(msg: func.QueueMessage) -> None:
"""Process document and index to both regions."""
document = json.loads(msg.get_body().decode('utf-8'))
# Indekser til primær region
try:
primary_result = primary_client.upload_documents(documents=[document])
logging.info(f"Primary indexed: {primary_result[0].key}")
except Exception as e:
logging.error(f"Primary indexing failed: {e}")
# Send til dead-letter queue for retry
raise
# Indekser til sekundær region (asynkront er OK)
try:
secondary_result = secondary_client.upload_documents(documents=[document])
logging.info(f"Secondary indexed: {secondary_result[0].key}")
except Exception as e:
logging.warning(f"Secondary indexing failed (will retry): {e}")
# Legg i retry-kø — sekundær er ikke kritisk
send_to_retry_queue(document)
```
### Pull-basert indeksering med Built-in Indexers
```bash
# Opprett identiske indexer i begge regioner
# Primær region — kobler til primær datakilde
az search indexer create \
--service-name "search-primary-norwayeast" \
--resource-group "rg-ai-prod" \
--name "blob-indexer" \
--data-source-name "blob-source-primary" \
--target-index-name "knowledge-base" \
--schedule '{"interval": "PT5M"}'
# Sekundær region — kobler til GRS-replikert datakilde
az search indexer create \
--service-name "search-secondary-swedencentral" \
--resource-group "rg-ai-dr" \
--name "blob-indexer" \
--data-source-name "blob-source-secondary" \
--target-index-name "knowledge-base" \
--schedule '{"interval": "PT5M"}'
```
## Replikatelling og dimensjonering for tilgjengelighet
### Intra-region tilgjengelighet med replikaer
Azure AI Search distribuerer automatisk replikaer på tvers av Availability Zones når du har 2+ replikaer i en region som støtter AZ.
| Replikaer | SLA | Lesbare spørringer | Skriveoperasjoner | Merknader |
|-----------|-----|-------------------|-------------------|-----------|
| 1 | 99.9% | Ja | Ja | Ingen AZ-redundans |
| 2 | 99.9% | Ja | Ja | AZ-distribuert automatisk |
| 3+ | 99.99% | Ja | Ja | Anbefalt for prod (read/write SLA) |
### Dimensjoneringsveiledning for multi-region
```
Per region (produksjon):
├── Replikaer: 3 (for 99.99% SLA og AZ-redundans)
├── Partisjoner: Basert på indeksstørrelse
│ ├── < 25 GB → 1 partisjon
│ ├── 2550 GB → 2 partisjoner
│ ├── 50150 GB → 36 partisjoner
│ └── > 150 GB → 612 partisjoner
└── SKU: Standard eller Standard S2/S3
Sekundær region (DR):
├── Replikaer: 2 (minimum for AZ, scale up ved failover)
├── Partisjoner: Identisk med primær
└── SKU: Identisk med primær
```
### Kostnadsoptimalisering for sekundær region
```bash
# Sekundær region starter med færre replikaer
# Scale up automatisk ved failover via Azure Automation
# Opprett Automation Runbook for scale-up
az automation runbook create \
--automation-account-name "aa-ai-dr" \
--resource-group "rg-ai-dr" \
--name "scale-up-search-dr" \
--type "PowerShell" \
--content '
# Scale sekundær AI Search fra 2 til 3 replikaer
$searchService = Get-AzSearchService `
-ResourceGroupName "rg-ai-dr" `
-Name "search-secondary-swedencentral"
Set-AzSearchService `
-ResourceGroupName "rg-ai-dr" `
-Name "search-secondary-swedencentral" `
-ReplicaCount 3
Write-Output "Scaled to 3 replicas for DR"
'
```
## Failover- og routingstrategier
### Azure Front Door for AI Search failover
```bicep
// Bicep: Azure Front Door med failover for AI Search
resource frontDoor 'Microsoft.Cdn/profiles@2023-05-01' = {
name: 'fd-ai-search'
location: 'global'
sku: {
name: 'Premium_AzureFrontDoor'
}
}
resource originGroup 'Microsoft.Cdn/profiles/originGroups@2023-05-01' = {
parent: frontDoor
name: 'search-origins'
properties: {
loadBalancingSettings: {
sampleSize: 4
successfulSamplesRequired: 3
}
healthProbeSettings: {
probePath: '/indexes/knowledge-base/docs?api-version=2024-07-01&search=*&$top=1'
probeRequestType: 'GET'
probeProtocol: 'Https'
probeIntervalInSeconds: 30
}
}
}
resource primaryOrigin 'Microsoft.Cdn/profiles/originGroups/origins@2023-05-01' = {
parent: originGroup
name: 'primary-norwayeast'
properties: {
hostName: 'search-primary-norwayeast.search.windows.net'
priority: 1
weight: 1000
}
}
resource secondaryOrigin 'Microsoft.Cdn/profiles/originGroups/origins@2023-05-01' = {
parent: originGroup
name: 'secondary-swedencentral'
properties: {
hostName: 'search-secondary-swedencentral.search.windows.net'
priority: 2
weight: 1000
}
}
```
### Application-level failover
```python
# Python: Application-level failover for Azure AI Search
from azure.search.documents import SearchClient
from azure.core.credentials import AzureKeyCredential
from azure.core.exceptions import ServiceResponseError, HttpResponseError
import time
class ResilientSearchClient:
"""AI Search client with automatic failover."""
def __init__(self, primary_endpoint, secondary_endpoint, index_name, api_key):
self.primary = SearchClient(
endpoint=primary_endpoint,
index_name=index_name,
credential=AzureKeyCredential(api_key)
)
self.secondary = SearchClient(
endpoint=secondary_endpoint,
index_name=index_name,
credential=AzureKeyCredential(api_key)
)
self.use_primary = True
self.failover_time = None
self.health_check_interval = 60 # sekunder
def search(self, search_text, **kwargs):
"""Search with automatic failover."""
client = self.primary if self.use_primary else self.secondary
try:
results = client.search(search_text=search_text, **kwargs)
# Sjekk om vi kan falle tilbake til primær
if not self.use_primary and self._should_check_primary():
self._try_failback()
return results
except (ServiceResponseError, HttpResponseError) as e:
if self.use_primary:
print(f"Primary search failed, failing over: {e}")
self.use_primary = False
self.failover_time = time.time()
return self.secondary.search(search_text=search_text, **kwargs)
else:
raise # Begge regioner feiler
def _should_check_primary(self):
"""Check if enough time has passed to try primary again."""
if self.failover_time is None:
return False
return time.time() - self.failover_time > self.health_check_interval
def _try_failback(self):
"""Attempt to fail back to primary region."""
try:
self.primary.search(search_text="*", top=1)
self.use_primary = True
self.failover_time = None
print("Failback to primary successful")
except Exception:
pass # Primær er fortsatt nede
```
## Holde indekser synkroniserte
### Synkroniseringstrategier
| Strategi | Forsinkelse | Kompleksitet | Anbefalt for |
|----------|-----------|-------------|-------------|
| Dual push (samtidige) | ~0 | Middels | Sanntidskritiske data |
| Event-driven sync | Sekunder | Middels | Generelt anbefalt |
| Scheduled indexer | 560 min | Lav | Batch-baserte oppdateringer |
| Full rebuild | Timer | Lav | Sjeldne endringer |
### Event-driven synkronisering med Event Grid
```bash
# Sett opp Event Grid for blob-endringer → trigger dual indexing
az eventgrid event-subscription create \
--name "blob-change-to-search-sync" \
--source-resource-id "/subscriptions/{sub}/resourceGroups/rg-ai-prod/providers/Microsoft.Storage/storageAccounts/staiprod" \
--included-event-types "Microsoft.Storage.BlobCreated" "Microsoft.Storage.BlobDeleted" \
--endpoint-type "azurefunction" \
--endpoint "/subscriptions/{sub}/resourceGroups/rg-ai-prod/providers/Microsoft.Web/sites/func-search-sync/functions/SyncToSecondary"
```
### Indeks-konsistensvalidering
```python
# Periodisk validering av indekskonsistens mellom regioner
import requests
def validate_index_consistency(primary_endpoint, secondary_endpoint, index_name, api_key):
"""Compare document counts and sample documents between regions."""
headers = {"api-key": api_key, "Content-Type": "application/json"}
# Sammenlign dokumenttellinger
primary_count = requests.get(
f"{primary_endpoint}/indexes/{index_name}/docs/$count?api-version=2024-07-01",
headers=headers
).json()
secondary_count = requests.get(
f"{secondary_endpoint}/indexes/{index_name}/docs/$count?api-version=2024-07-01",
headers=headers
).json()
drift = abs(primary_count - secondary_count)
drift_pct = (drift / max(primary_count, 1)) * 100
return {
"primary_count": primary_count,
"secondary_count": secondary_count,
"drift": drift,
"drift_percentage": round(drift_pct, 2),
"in_sync": drift_pct < 1.0 # < 1% avvik er akseptabelt
}
```
## Query-ytelse i multi-region oppsett
### Latensoptimalisering
| Strategi | Latensreduksjon | Merknad |
|----------|----------------|---------|
| Latency-based routing | 2050 ms | Brukere sendes til nærmeste region |
| Semantic caching | 8095% | Cache hyppige spørringer i APIM |
| Read replicas (intra-region) | 1030 ms | Fordel lesninger over replikaer |
| Query optimalisering | Varierer | $select, $top for å redusere payload |
### Azure API Management for caching og routing
```xml
<!-- APIM Policy: Cache og failover for AI Search queries -->
<policies>
<inbound>
<base />
<cache-lookup vary-by-query-parameter="search,filter,top,skip"
caching-type="internal" />
</inbound>
<backend>
<retry condition="@(context.Response.StatusCode >= 500)"
count="1" interval="0" first-fast-retry="true">
<choose>
<when condition="@(context.Variables.GetValueOrDefault<bool>("usePrimary", true))">
<set-backend-service
base-url="https://search-primary-norwayeast.search.windows.net" />
</when>
<otherwise>
<set-backend-service
base-url="https://search-secondary-swedencentral.search.windows.net" />
</otherwise>
</choose>
</retry>
</backend>
<outbound>
<cache-store duration="300" />
<base />
</outbound>
</policies>
```
## Referanser
- [Multi-region deployments in Azure AI Search](https://learn.microsoft.com/en-us/azure/search/search-multi-region) — Offisiell multi-region guide
- [Reliability in Azure AI Search](https://learn.microsoft.com/en-us/azure/reliability/reliability-ai-search) — Tilgjengelighet, AZ og DR
- [Multi-region solutions in nonpaired regions](https://learn.microsoft.com/en-us/azure/reliability/regions-multi-region-nonpaired) — Multi-region uten parede regioner
- [Azure AI Search multi-region Bicep sample](https://github.com/Azure-Samples/azure-search-multiple-regions) — Komplett Bicep-mal
- [Azure Front Door overview](https://learn.microsoft.com/en-us/azure/frontdoor/front-door-overview) — Global load balancing
- [Azure Traffic Manager overview](https://learn.microsoft.com/en-us/azure/traffic-manager/traffic-manager-overview) — DNS-basert trafikkstyring
## For Cosmo
- **Bruk denne referansen** når kunden bygger RAG-løsninger med Azure AI Search og trenger geo-redundans for search-indeksene.
- Azure AI Search har INGEN innebygd geo-replikering — dette er en vanlig misforståelse. Kunden må implementere dual-indexing selv.
- Anbefal minimum 3 replikaer per region for 99.99% SLA og AZ-redundans — 2 replikaer gir kun 99.9%.
- For kostnadsoptimalisering: Sekundær region kan kjøre med 2 replikaer og skalere opp til 3 ved failover via Azure Automation.
- Indekskonsistens bør valideres automatisk — sett opp periodisk sjekk av dokumenttelling og samplingsbasert innholdsvalidering.

View file

@ -0,0 +1,316 @@
# Incident Response for AI Systems
**Last updated:** 2026-02
**Status:** GA
**Category:** Business Continuity & Disaster Recovery
---
## Introduksjon
Incident response for AI-systemer krever spesialiserte prosedyrer som adresserer unike feilmodi som ikke finnes i tradisjonelle IT-systemer. AI-spesifikke hendelser inkluderer modell-degradering, prompt injection-angrep, hallusinasjonsspikes, embedding-drift, og utilgjengelige inference-endepunkter. Disse hendelsene kan ha subtile symptomer som er vanskelige å oppdage med tradisjonell overvåking.
Azure tilbyr flere verktøy for AI-spesifikk overvåking og hendelseshåndtering: Microsoft Defender for AI Services for trusseloppdaging, Azure Monitor for metrikk og alerting, Microsoft Sentinel for SIEM/SOAR-kapabiliteter, og Application Insights for applikasjonslagsovervåking. Disse verktøyene må konfigureres spesifikt for AI-arbeidsbelastninger.
For norsk offentlig sektor er hendelseshåndtering regulert gjennom NSMs grunnprinsipper, og mange organisasjoner har ITIL-baserte prosesser. AI-hendelser krever utvidelse av eksisterende incident management-prosesser med AI-spesifikke klassifiseringer, eskaleringsregler og kommunikasjonsplaner.
## AI-spesifikke hendelsesklassifiseringer
### Hendelseskategorier for AI-systemer
| Kategori | Beskrivelse | Eksempler | Alvorlighetsgrad |
|----------|-------------|-----------|-----------------|
| Modellutilgjengelighet | Inference-endepunkt svarer ikke | Azure OpenAI regional outage, quota exceeded | Kritisk |
| Modelldegraddering | Redusert kvalitet på modellresponser | Økt hallusinasjonsrate, inkonsistente svar | Høy |
| Datapipeline-feil | Indeksering eller dataflyt stoppet | Search indexer failed, embedding pipeline stoppet | Høy |
| Sikkerhetsbrudd | AI-spesifikke angrep | Prompt injection, jailbreak, data exfiltration | Kritisk |
| Ytelsesdegraddering | Økt latens eller redusert throughput | Token rate limiting, høy p99 latens | Middels |
| Kostnadsanomali | Uventet økning i AI-forbruk | Token-forbruk spike, uautoriserte API-kall | Middels |
| Datakvalitetsproblem | Korrupt eller utdatert data | Embedding drift, stale indeks, poison data | Høy |
| Compliance-brudd | Brudd på regulatoriske krav | PII i modellresponser, uautorisert datatilgang | Kritisk |
### Alvorlighetsnivåer og responstider
| Nivå | Beskrivelse | Responstid | Eskalering | Eksempel |
|------|-------------|-----------|------------|---------|
| P0 — Kritisk | Total tjenestebortfall eller sikkerhetshendelse | < 15 min | Umiddelbar til CISO/CTO | Regional outage, aktiv data breach |
| P1 — Høy | Betydelig degradering av funksjonalitet | < 1 time | Til teamlead innen 30 min | Modell gir feil svar konsistent |
| P2 — Middels | Delvis degradering, workaround eksisterer | < 4 timer | Til teamlead innen 2 timer | Økt latens på search-spørringer |
| P3 — Lav | Minimal påvirkning | Neste virkedag | Standard kanal | Ikke-kritisk indexer-feil |
## Deteksjon og alerting-strategier
### Microsoft Defender for AI Services
```bash
# Aktiver Defender for AI Services
az security pricing create \
--name "AI" \
--tier "standard"
# Se aktive sikkerhetsvarsler for AI
az security alert list \
--query "[?contains(alertType, 'AI')]" \
--output table
```
### Azure Monitor alerting for AI-metrikker
```kusto
// KQL: Detect increased error rate for Azure OpenAI
AzureDiagnostics
| where ResourceProvider == "MICROSOFT.COGNITIVESERVICES"
| where Category == "RequestResponse"
| where TimeGenerated > ago(15m)
| summarize
TotalRequests = count(),
FailedRequests = countif(resultCode_d >= 400),
ErrorRate = round(countif(resultCode_d >= 400) * 100.0 / count(), 2)
by bin(TimeGenerated, 5m)
| where ErrorRate > 5.0
| project TimeGenerated, TotalRequests, FailedRequests, ErrorRate
```
```kusto
// KQL: Detect abnormal token consumption (potential abuse)
AzureDiagnostics
| where ResourceProvider == "MICROSOFT.COGNITIVESERVICES"
| where Category == "RequestResponse"
| where TimeGenerated > ago(1h)
| extend
promptTokens = toint(properties_s.promptTokens),
completionTokens = toint(properties_s.completionTokens)
| summarize
TotalTokens = sum(promptTokens + completionTokens),
AvgTokensPerRequest = avg(promptTokens + completionTokens)
by bin(TimeGenerated, 5m), callerIpAddress_s
| where TotalTokens > 100000 // Anomali-terskel
```
```kusto
// KQL: AI Search indexer failure detection
AzureDiagnostics
| where ResourceProvider == "MICROSOFT.SEARCH"
| where OperationName == "Indexers.Status"
| where resultSignature_d != 200
| project TimeGenerated, resource_s, OperationName, resultSignature_d, resultDescription_s
| order by TimeGenerated desc
```
### Alert-konfigurasjoner
```bash
# Azure OpenAI error rate alert
az monitor metrics alert create \
--name "aoai-error-rate-critical" \
--resource-group "rg-ai-prod" \
--scopes "/subscriptions/{sub}/resourceGroups/rg-ai-prod/providers/Microsoft.CognitiveServices/accounts/aoai-prod" \
--condition "avg ServerErrors > 10" \
--window-size 5m \
--evaluation-frequency 1m \
--severity 0 \
--action-group "ag-ai-oncall"
# Azure OpenAI latency alert
az monitor metrics alert create \
--name "aoai-latency-warning" \
--resource-group "rg-ai-prod" \
--scopes "/subscriptions/{sub}/resourceGroups/rg-ai-prod/providers/Microsoft.CognitiveServices/accounts/aoai-prod" \
--condition "avg Latency > 5000" \
--window-size 5m \
--evaluation-frequency 1m \
--severity 2 \
--action-group "ag-ai-team"
# Token consumption anomaly
az monitor metrics alert create \
--name "aoai-token-anomaly" \
--resource-group "rg-ai-prod" \
--scopes "/subscriptions/{sub}/resourceGroups/rg-ai-prod/providers/Microsoft.CognitiveServices/accounts/aoai-prod" \
--condition "total ProcessedPromptTokens > 500000" \
--window-size 1h \
--evaluation-frequency 15m \
--severity 2 \
--action-group "ag-ai-team"
```
## Eskalerings-prosedyrer og runbooks
### Eskalerings-matrise
```markdown
## Eskaleringsmatrise for AI-hendelser
### Nivå 1: AI Platform Team (L1)
- **Ansvar:** Første respons, triage, kjente problemer
- **Verktøy:** Azure Monitor dashboards, Runbook for kjente feil
- **Eskaleringstid:** 15 min (P0), 30 min (P1), 2 timer (P2)
### Nivå 2: AI Engineering Team (L2)
- **Ansvar:** Teknisk feilsøking, workaround-implementering
- **Verktøy:** Log Analytics, Application Insights, Azure CLI
- **Eskaleringstid:** 30 min (P0), 1 time (P1)
### Nivå 3: Architecture/Security Team (L3)
- **Ansvar:** Arkitekturelle beslutninger, sikkerhetsrespons
- **Verktøy:** Microsoft Sentinel, Defender for Cloud
- **Eskaleringstid:** 1 time (P0), involveres alltid for sikkerhet
### Nivå 4: Microsoft Support (L4)
- **Ansvar:** Platform-nivå feil, Azure-tjenestefeil
- **Verktøy:** Azure Support ticket (Severity A for P0)
- **Kontakt:** Azure Support portal, TAM for Enterprise
```
### Runbook: Azure OpenAI Regional Outage
```markdown
## Runbook: Azure OpenAI Regional Outage
### Trigger
- Azure Service Health alert for Cognitive Services i primær region
- Error rate > 50% vedvarende over 5 minutter
- Alle requests returnerer 5xx
### Umiddelbare handlinger (05 min)
1. Verifiser at det er en regional hendelse (sjekk Azure Status)
2. Aktiver incident i PagerDuty/Opsgenie
3. Send umiddelbar varsling til interessenter
### Failover-prosedyre (515 min)
1. Aktiver failover via Traffic Manager/APIM:
```bash
# Oppdater Traffic Manager priority
az network traffic-manager endpoint update \
--resource-group rg-networking \
--profile-name tm-aoai \
--name secondary-swedencentral \
--type azureEndpoints \
--priority 1
```
2. Verifiser at sekundært endpoint responderer
3. Sjekk at applikasjoner bruker ny rute
4. Overvåk error rate i sekundær region
### Kommunikasjon (løpende)
1. Oppdater status-side
2. Varsle forretningsbrukere via Teams/epost
3. Logg alle handlinger i incident management system
### Gjenoppretting
1. Overvåk Azure Service Health for løsning
2. Verifiser at primær region fungerer (test med syntetisk trafikk)
3. Planlegg kontrollert failback i lavtrafikkperiode
4. Utfør failback og verifiser
```
## Kommunikasjonsplaner for interessenter
### Kommunikasjonsmal
| Tidspunkt | Mottaker | Kanal | Innhold |
|-----------|---------|-------|---------|
| T+0 min | Ops-team | PagerDuty | Automatisk alert |
| T+5 min | Teamlead | Teams/Slack | Triage-oppsummering |
| T+15 min | Management | Epost | Statusoppdatering med ETA |
| T+30 min | Alle brukere | Statusside | Offentlig statusmelding |
| T+60 min | Ledelse | Epost | Oppdatert ETA, påvirkning |
| Hver time | Alle | Statusside + epost | Løpende oppdatering |
| Etter løsning | Alle | Epost | Hendelse løst, kort oppsummering |
| T+48 timer | Internt | Møte + doc | Post-mortem rapport |
### Statusmeldings-maler
```markdown
## Statusmelding — Mal
### Hendelse oppdaget
[Tidspunkt]: Vi har identifisert et problem med [tjenestenavn].
Påvirkning: [Beskrivelse av påvirkning for brukere]
Status: Vi undersøker og vil gi oppdatering innen [tid].
### Under arbeid
[Tidspunkt]: Vi har identifisert årsaken som [kort beskrivelse].
Tiltak: [Hva gjøres?]
Forventet løsning: [ETA]
Workaround: [Eventuell midlertidig løsning]
### Løst
[Tidspunkt]: Hendelsen er løst. [Tjenestenavn] fungerer normalt.
Varighet: [Fratil]
Rotårsak: [Kort beskrivelse]
Tiltak: [Hva gjøres for å forhindre gjentakelse]
```
## Post-incident review og forbedring
### Post-mortem prosess
1. **Samle data** (innen 24 timer):
- Tidslinje med alle handlinger
- Alle relevante logger og metrikker
- Kommunikasjonslogg
2. **Gjennomfør blameless post-mortem** (innen 5 virkedager):
- Tidslinje-gjennomgang
- Rotårsaksanalyse (5 Whys eller Fishbone)
- Identifiser forbedringstiltak
- Definer action items med eiere og tidsfrister
3. **Dokumenter og distribuer** (innen 7 virkedager):
- Post-mortem rapport
- Oppdaterte runbooks
- Nye/justerte alerts
- Lessons learned
### Post-mortem mal
```markdown
# Post-Mortem Report — [Hendelsesnavn]
## Oppsummering
- **Dato:** [Dato]
- **Varighet:** [Timer:Minutter]
- **Alvorlighetsgrad:** [P0/P1/P2/P3]
- **Påvirkede tjenester:** [Liste]
- **Påvirkede brukere:** [Antall/beskrivelse]
## Tidslinje
| Tidspunkt | Hendelse | Aksjon |
|-----------|---------|--------|
| HH:MM | [Hva skjedde] | [Hva ble gjort] |
## Rotårsak
[Detaljert beskrivelse av rotårsak]
## Hva gikk bra
- [Punkt 1]
- [Punkt 2]
## Hva kan forbedres
- [Punkt 1]
- [Punkt 2]
## Action Items
| # | Beskrivelse | Eier | Frist | Status |
|---|-------------|------|-------|--------|
| 1 | [Tiltak] | [Navn] | [Dato] | Open |
```
## Referanser
- [Secure AI — Detect AI security threats](https://learn.microsoft.com/en-us/azure/cloud-adoption-framework/scenarios/ai/secure) — CAF AI-sikkerhet
- [AI-6: Establish monitoring and detection](https://learn.microsoft.com/en-us/security/benchmark/azure/mcsb-v2-artificial-intelligence-security) — MCSB AI-overvåking
- [Create an effective incident management plan](https://learn.microsoft.com/en-us/azure/well-architected/design-guides/incident-management) — WAF incident management
- [Microsoft Defender for AI Services](https://learn.microsoft.com/en-us/azure/defender-for-cloud/ai-threat-protection) — AI-spesifikk trusseloppdaging
- [Azure Monitor alerts overview](https://learn.microsoft.com/en-us/azure/azure-monitor/alerts/alerts-overview) — Alert-rammeverk
- [Microsoft Sentinel overview](https://learn.microsoft.com/en-us/azure/sentinel/overview) — SIEM/SOAR for sikkerhetshendelser
- [Monitor Azure OpenAI](https://learn.microsoft.com/en-us/azure/ai-foundry/openai/how-to/monitor-openai) — OpenAI-spesifikk monitoring
## For Cosmo
- **Bruk denne referansen** når kunden etablerer eller forbedrer sine incident response-prosedyrer for AI-systemer.
- AI-hendelser krever utvidelse av eksisterende ITIL/incident-prosesser — ikke separate prosesser, men tilpassede kategorier og runbooks.
- Anbefal alltid blameless post-mortems — fokuser på systemer og prosesser, ikke personer.
- For norsk offentlig sektor: Hendelseshåndtering bør integreres med NSMs rapporteringskrav og organisasjonens ROS-analyse.
- Kritisk: Sørg for at AI-teamet har direkte eskaleringsmulighet til Microsoft Support med Severity A for produksjonskritiske hendelser.

View file

@ -0,0 +1,430 @@
# Monitoring and Alerting for Failover Detection
**Last updated:** 2026-02
**Status:** GA
**Category:** Business Continuity & Disaster Recovery
---
## Introduksjon
Rask og pålitelig deteksjon av feil er avgjørende for å minimere nedetid i AI-systemer. Failover-deteksjon handler om å oppdage at en tjeneste eller region har feilet, og å initiere gjenopprettingsprosessen så raskt som mulig. For AI-workloads er dette spesielt viktig fordi forsinkede svar eller manglende tilgjengelighet direkte påvirker brukeropplevelsen.
Azure Monitor, Application Insights og Azure Service Health gir et robust rammeverk for overvåking og alerting. For AI-spesifikke metrikker som token-forbruk, modellkvalitet og search-indeksvaliditet kreves tilpasset monitoring med custom metrics og KQL-spørringer.
For norsk offentlig sektor som følger ITIL-baserte prosesser, må monitoring integreres med eksisterende incident management-systemer. NSMs grunnprinsipper krever "planlegging for å håndtere hendelser" (prinsipp 4.3), som inkluderer automatisk deteksjon og varsling.
## Health check-endepunkter og heartbeats
### Health check arkitektur
```
┌──────────────────┐
│ Azure Monitor │
│ (Availability │
│ Tests) │
└────────┬─────────┘
│ HTTPS GET /health
┌──────────────────┐ ┌───────────────────┐
│ App Service │────▶│ Deep Health Check │
│ /health │ │ ├─ OpenAI ✓/✗ │
│ (Shallow) │ │ ├─ AI Search ✓/✗ │
│ │ │ ├─ Cosmos DB ✓/✗ │
│ /health/deep │ │ ├─ Redis ✓/✗ │
│ (Deep) │ │ └─ Key Vault ✓/✗ │
└──────────────────┘ └───────────────────┘
```
### Health check implementering
```python
# FastAPI health check endpoints for AI service
from fastapi import FastAPI, Response
from datetime import datetime
import asyncio
app = FastAPI()
class HealthStatus:
def __init__(self):
self.checks = {}
self.overall = "unknown"
async def check_openai():
"""Check Azure OpenAI availability."""
try:
response = await openai_client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "ping"}],
max_tokens=1,
timeout=5
)
return {"status": "healthy", "latency_ms": response.usage.total_tokens}
except Exception as e:
return {"status": "unhealthy", "error": str(e)}
async def check_search():
"""Check Azure AI Search availability."""
try:
results = search_client.search(search_text="*", top=1)
count = 0
async for _ in results:
count += 1
return {"status": "healthy", "documents_accessible": True}
except Exception as e:
return {"status": "unhealthy", "error": str(e)}
async def check_cosmos():
"""Check Cosmos DB availability."""
try:
await cosmos_container.read_item(
item="health-check", partition_key="system"
)
return {"status": "healthy"}
except Exception as e:
return {"status": "unhealthy", "error": str(e)}
@app.get("/health")
async def shallow_health():
"""Shallow health check — is the app running?"""
return {"status": "healthy", "timestamp": datetime.utcnow().isoformat()}
@app.get("/health/deep")
async def deep_health(response: Response):
"""Deep health check — are all dependencies healthy?"""
checks = await asyncio.gather(
check_openai(),
check_search(),
check_cosmos(),
return_exceptions=True
)
result = {
"timestamp": datetime.utcnow().isoformat(),
"checks": {
"openai": checks[0] if not isinstance(checks[0], Exception) else {"status": "error"},
"search": checks[1] if not isinstance(checks[1], Exception) else {"status": "error"},
"cosmos": checks[2] if not isinstance(checks[2], Exception) else {"status": "error"},
}
}
# Bestem overall status
unhealthy = [k for k, v in result["checks"].items()
if v.get("status") != "healthy"]
if not unhealthy:
result["status"] = "healthy"
elif len(unhealthy) == len(result["checks"]):
result["status"] = "unhealthy"
response.status_code = 503
else:
result["status"] = "degraded"
result["degraded_services"] = unhealthy
response.status_code = 200 # Degraded men funksjonell
return result
```
### Azure Monitor Availability Tests
```bash
# Opprett availability test for shallow health check
az monitor app-insights web-test create \
--resource-group "rg-ai-prod" \
--app-insights "ai-app-insights-prod" \
--web-test-name "health-check-shallow" \
--location "norwayeast" \
--defined-web-test-name "ShallowHealthCheck" \
--url "https://ai-app-prod.azurewebsites.net/health" \
--expected-status-code 200 \
--frequency 300 \
--timeout 30 \
--enabled true
# Opprett availability test for deep health check
az monitor app-insights web-test create \
--resource-group "rg-ai-prod" \
--app-insights "ai-app-insights-prod" \
--web-test-name "health-check-deep" \
--location "norwayeast" \
--defined-web-test-name "DeepHealthCheck" \
--url "https://ai-app-prod.azurewebsites.net/health/deep" \
--expected-status-code 200 \
--frequency 300 \
--timeout 60 \
--enabled true
```
## Latens og feilrate-overvåking
### KQL-spørringer for AI-metrikker
```kusto
// Azure OpenAI — Latency tracking per deployment
AzureDiagnostics
| where ResourceProvider == "MICROSOFT.COGNITIVESERVICES"
| where Category == "RequestResponse"
| where TimeGenerated > ago(1h)
| extend
deploymentName = tostring(properties_s.modelDeploymentName),
latencyMs = duration_s * 1000,
statusCode = resultCode_d
| summarize
P50 = percentile(latencyMs, 50),
P95 = percentile(latencyMs, 95),
P99 = percentile(latencyMs, 99),
SuccessRate = round(countif(statusCode < 400) * 100.0 / count(), 2),
TotalRequests = count()
by bin(TimeGenerated, 5m), deploymentName
| order by TimeGenerated desc
```
```kusto
// Azure AI Search — Query performance
AzureDiagnostics
| where ResourceProvider == "MICROSOFT.SEARCH"
| where OperationName == "Query.Search"
| where TimeGenerated > ago(1h)
| extend
queryLatencyMs = DurationMs,
resultCount = toint(Properties.ResultCount)
| summarize
AvgLatency = avg(queryLatencyMs),
P95Latency = percentile(queryLatencyMs, 95),
AvgResults = avg(resultCount),
TotalQueries = count(),
ErrorRate = round(countif(resultSignature_d >= 400) * 100.0 / count(), 2)
by bin(TimeGenerated, 5m)
| order by TimeGenerated desc
```
```kusto
// End-to-end RAG pipeline latency
customMetrics
| where name == "rag_pipeline_duration_ms"
| where timestamp > ago(1h)
| extend
phase = tostring(customDimensions.phase),
region = tostring(customDimensions.region)
| summarize
P50 = percentile(value, 50),
P95 = percentile(value, 95),
P99 = percentile(value, 99)
by bin(timestamp, 5m), phase, region
| order by timestamp desc, phase asc
```
## Custom metrics for AI-tjenestehelse
### Application Insights custom metrics
```python
# Custom metrics for AI service health monitoring
from opencensus.ext.azure.log_exporter import AzureLogHandler
from applicationinsights import TelemetryClient
import time
tc = TelemetryClient(instrumentation_key="<key>")
class AIMetricsCollector:
"""Collect and emit custom AI metrics."""
def track_openai_call(self, deployment, latency_ms, tokens_used, success):
"""Track Azure OpenAI API call metrics."""
tc.track_metric("openai_latency_ms", latency_ms, properties={
"deployment": deployment,
"success": str(success)
})
tc.track_metric("openai_tokens_used", tokens_used, properties={
"deployment": deployment
})
if not success:
tc.track_metric("openai_error_count", 1, properties={
"deployment": deployment
})
def track_search_call(self, index_name, latency_ms, result_count, success):
"""Track Azure AI Search call metrics."""
tc.track_metric("search_latency_ms", latency_ms, properties={
"index": index_name,
"success": str(success)
})
tc.track_metric("search_result_count", result_count, properties={
"index": index_name
})
def track_rag_pipeline(self, total_ms, search_ms, llm_ms, success):
"""Track end-to-end RAG pipeline metrics."""
tc.track_metric("rag_total_latency_ms", total_ms)
tc.track_metric("rag_search_latency_ms", search_ms)
tc.track_metric("rag_llm_latency_ms", llm_ms)
tc.track_metric("rag_pipeline_success", 1 if success else 0)
def track_health_check(self, service_name, is_healthy, latency_ms):
"""Track health check results for dashboards."""
tc.track_metric(f"health_{service_name}", 1 if is_healthy else 0)
tc.track_metric(f"health_{service_name}_latency", latency_ms)
def flush(self):
tc.flush()
```
## Alert-regler og eskaleringspolicyer
### Alerting-strategi
| Metrikk | Warning | Critical | Aksjon |
|---------|---------|----------|--------|
| OpenAI error rate | > 5% i 5 min | > 20% i 5 min | Notify → Auto-failover |
| OpenAI P95 latency | > 5s | > 15s | Notify team |
| Search error rate | > 2% i 5 min | > 10% i 5 min | Notify → Auto-failover |
| Health check failure | 2 consecutive | 3 consecutive | Initiate DR |
| Token consumption | > 80% quota | > 95% quota | Scale/notify |
| Cosmos DB latency | > 50ms P95 | > 200ms P95 | Investigate |
### Alert rules i Azure Monitor
```bash
# Critical: AI service health check failures
az monitor metrics alert create \
--name "ai-health-critical" \
--resource-group "rg-ai-prod" \
--scopes "/subscriptions/{sub}/resourceGroups/rg-ai-prod/providers/Microsoft.Insights/components/ai-app-insights-prod" \
--condition "count availabilityResults/failed > 3" \
--window-size 5m \
--evaluation-frequency 1m \
--severity 0 \
--action-group "ag-ai-oncall" \
--description "3+ health check failures in 5 min — initiate DR assessment"
# Warning: Elevated OpenAI latency
az monitor scheduled-query create \
--name "aoai-latency-warning" \
--resource-group "rg-ai-prod" \
--scopes "/subscriptions/{sub}/resourceGroups/rg-ai-prod/providers/Microsoft.Insights/components/ai-app-insights-prod" \
--condition "count > 0" \
--condition-query "
customMetrics
| where name == 'openai_latency_ms'
| where timestamp > ago(5m)
| summarize P95 = percentile(value, 95)
| where P95 > 5000
" \
--evaluation-frequency 1m \
--window-size 5m \
--severity 2 \
--action-group "ag-ai-team"
```
## Integrasjon med incident management-systemer
### Azure Logic App for eskalering
```json
{
"definition": {
"$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json",
"triggers": {
"alert_webhook": {
"type": "Request",
"kind": "Http",
"inputs": {
"schema": {
"type": "object",
"properties": {
"alertName": {"type": "string"},
"severity": {"type": "integer"},
"affectedResource": {"type": "string"}
}
}
}
}
},
"actions": {
"create_incident": {
"type": "ApiConnection",
"inputs": {
"method": "POST",
"host": "servicenow-connection",
"path": "/api/now/table/incident",
"body": {
"short_description": "@{triggerBody().alertName}",
"urgency": "@{if(equals(triggerBody().severity, 0), '1', '2')}",
"impact": "@{if(equals(triggerBody().severity, 0), '1', '2')}",
"assignment_group": "AI Platform Team",
"category": "AI Service"
}
}
},
"send_teams_notification": {
"type": "ApiConnection",
"inputs": {
"method": "POST",
"host": "teams-connection",
"path": "/v3/conversations/@{variables('teamChannelId')}/activities",
"body": {
"type": "message",
"text": "AI Service Alert: @{triggerBody().alertName} (Sev @{triggerBody().severity})"
}
},
"runAfter": { "create_incident": ["Succeeded"] }
}
}
}
}
```
### Automatisk failover-trigger
```python
# Azure Function triggered by Alert webhook — initiate automated failover
import azure.functions as func
from azure.mgmt.trafficmanager import TrafficManagerManagementClient
from azure.identity import DefaultAzureCredential
def main(req: func.HttpRequest) -> func.HttpResponse:
"""Handle Azure Monitor alert and trigger failover if needed."""
alert_data = req.get_json()
severity = alert_data.get("data", {}).get("essentials", {}).get("severity")
alert_name = alert_data.get("data", {}).get("essentials", {}).get("alertRule")
if severity in ["Sev0", "Sev1"] and "health-critical" in alert_name:
# Initier automatisk failover
credential = DefaultAzureCredential()
tm_client = TrafficManagerManagementClient(credential, subscription_id)
# Oppdater Traffic Manager til å bruke sekundær region
profile = tm_client.profiles.get("rg-networking", "tm-ai-failover")
for endpoint in profile.endpoints:
if "secondary" in endpoint.name:
endpoint.priority = 1
else:
endpoint.priority = 2
tm_client.profiles.create_or_update("rg-networking", "tm-ai-failover", profile)
return func.HttpResponse(
f"Failover initiated for alert: {alert_name}", status_code=200
)
return func.HttpResponse("Alert received, no failover needed", status_code=200)
```
## Referanser
- [Monitor Azure OpenAI](https://learn.microsoft.com/en-us/azure/ai-foundry/openai/how-to/monitor-openai) — OpenAI monitoring og alerting
- [Monitor Azure AI Search](https://learn.microsoft.com/en-us/azure/search/monitor-azure-cognitive-search) — AI Search monitoring
- [Azure Monitor alerts overview](https://learn.microsoft.com/en-us/azure/azure-monitor/alerts/alerts-overview) — Alert-rammeverk
- [Health modeling and observability of mission-critical workloads](https://learn.microsoft.com/en-us/azure/well-architected/mission-critical/mission-critical-health-modeling) — Health modeling
- [Application Insights overview](https://learn.microsoft.com/en-us/azure/azure-monitor/app/app-insights-overview) — APM for applikasjoner
- [Azure Service Health](https://learn.microsoft.com/en-us/azure/service-health/overview) — Azure-tjenestestatus
## For Cosmo
- **Bruk denne referansen** når kunden setter opp monitoring og alerting for failover-deteksjon i AI-systemer.
- Implementer alltid to nivåer av health checks: shallow (er appen oppe?) og deep (er alle avhengigheter friske?).
- Alert-terskler bør baseres på baseline-metrikker — bruk minst 2 ukers normaldata før du setter statiske terskler.
- For automatisk failover: Krev minimum 3 påfølgende health check-feil før failover trigges for å unngå false positives.
- Integrer med eksisterende ITSM-systemer (ServiceNow, Jira Service Management) via Azure Logic Apps eller Azure Functions.

View file

@ -0,0 +1,384 @@
# Multi-Region Azure OpenAI Deployment
**Last updated:** 2026-02
**Status:** GA
**Category:** Business Continuity & Disaster Recovery
---
## Introduksjon
Azure OpenAI-tjenester er tilgjengelige i flere Azure-regioner, og når en ressurs opprettes, knyttes den permanent til den valgte regionen. For virksomhetskritiske AI-applikasjoner i norsk offentlig sektor er det avgjorende a planlegge for regional redundans. Et regionalt utfall -- selv om det er sjeldent -- kan lamme AI-drevne tjenester som chatboter, dokumentanalyse og beslutningsstotte dersom all trafikk er avhengig av et enkelt endepunkt. Multi-region-deployering loeser dette ved a spre arbeidsbelastningen over flere Azure-regioner med intelligent lastbalansering og automatisk failover.
For norske organisasjoner er regionvalg spesielt viktig pa grunn av krav til datasuverenitet og personvern under GDPR og Schrems II. Azure Norway East er den primaere regionen for norsk offentlig sektor, men modellutvalget er begrenset sammenlignet med Sweden Central. En velplanlagt multi-region-arkitektur kombinerer naerhet (lav latens), compliance (data innenfor EU/EOeS), og kapasitet (bredere modelltilgang) pa en balansert mate. Data Zone-deployeringer forenkler dette ved a la Azure optimere ruting innenfor en geografisk sone (f.eks. EU) uten at kunden selv ma administrere lastbalansering mellom individuelle regioner.
Denne referansen dekker regionvalg for Norge og EU, lastbalanseringsmonstre via Azure API Management, latensoptimalisering med proximity routing, kvoteadministrasjon per region, og kostnadsmodeller for multi-region-oppsett. Alt er forankret i Microsofts offisielle BCDR-veiledning for Azure OpenAI og arkitekturmoenstre for generative AI gateways.
## Regionvalg for Norge og EU
### Tilgjengelige regioner med Azure OpenAI
| Region | Primaer bruk | Modellstotte | Latens fra Norge | Datasuverenitet |
|--------|-------------|--------------|------------------|-----------------|
| Norway East | Primaer region | Begrenset (gpt-4o 2024-11-20) | < 10 ms | Norge/EU |
| Sweden Central | Sekundaer/failover | Bred (gpt-4o, o1, gpt-35-turbo) | ~15-25 ms | EU |
| West Europe | Alternativ | Begrenset (gpt-35-turbo) | ~30-40 ms | EU |
| UK South | Alternativ | Moderat (gpt-4o, gpt-35-turbo) | ~35-45 ms | UK (tilstrekkelig for mange bruksomrader) |
| France Central | Alternativ | Bred (gpt-4o, o1) | ~35-45 ms | EU |
### Anbefalte regionkombinasjoner
**For norsk offentlig sektor (strengt EU-krav):**
```
Primaer: Norway East (lavest latens, norsk datasuverenitet)
Sekundaer: Sweden Central (bredest modellstotte i Norden)
Tertiaer: France Central (EU-failover utenfor Norden)
```
**For Data Zone-deployeringer (anbefalt av Microsoft):**
```
Data Zone: EU
Primaer: Norway East endpoint
Sekundaer: Sweden Central endpoint (samme Data Zone-pool)
```
> **Viktig:** Data Zone-deployeringer er mer effektive og enklere enn selvadministrert lastbalansering mellom regionale deployeringer. Azure optimerer ruting og prosessering pa tvers av tilgjengelig compute i datasonen. Bruk Data Zone som standard for Standard-deployeringer.
### Beslutningstre for regionvalg
```
Trenger du datalagring KUN i Norge?
|-- Ja --> Norway East (kun region)
| Merk: Begrenset modellstotte, hoeyre risiko ved utfall
|
|-- Nei --> Aksepterer du EU/EOeS databehandling?
|-- Ja --> Data Zone EU (anbefalt)
| Primaer: Norway East
| Sekundaer: Sweden Central
|
|-- Nei --> Vurder Global Standard
(data kan behandles globalt, hoeyest kapasitet)
```
## Lastbalansering mellom OpenAI-endepunkter
### Arkitekturmonstre
Microsoft anbefaler en **Generative AI Gateway** foran Azure OpenAI-endepunktene. Azure API Management (APIM) er den foretrukne PaaS-loesningen for dette.
#### Moenster 1: APIM single-region med multi-region backends
```
+------------------+
| Klient/App |
+--------+---------+
|
+--------v---------+
| Azure API Mgmt |
| (Norway East) |
+--+------------+--+
| |
+--------v--+ +-----v-------+
| Azure AOAI | | Azure AOAI |
| Norway East| | Sweden Cent.|
| (primaer) | | (sekundaer) |
+------------+ +-------------+
```
**Fordeler:** Enklest a sette opp, sentralisert policy-styring.
**Ulemper:** APIM er single point of failure. Egress-kostnader for cross-region trafikk.
#### Moenster 2: APIM multi-region deployment
```
+------------------+
| Klient/App |
+--------+---------+
|
+---------v----------+
| Azure Front Door / |
| Traffic Manager |
+---------+----------+
|
+--------------+---------------+
| |
+---------v----------+ +-------------v--------+
| APIM Gateway | | APIM Gateway |
| Norway East | | Sweden Central |
+--------+-----------+ +-----------+----------+
| |
+--------v-----------+ +-----------v----------+
| Azure AOAI | | Azure AOAI |
| Norway East | | Sweden Central |
+--------------------+ +----------------------+
```
**Fordeler:** Ingen single point of failure, ytelsesbasert ruting.
**Ulemper:** Hoeyre kostnad, mer kompleks drift.
#### Moenster 3: Data Zone med enkel gateway
```
+------------------+
| Klient/App |
+--------+---------+
|
+--------v---------+
| Azure API Mgmt |
| (Norway East) |
+--------+---------+
|
+--------v---------+
| Azure AOAI |
| Data Zone: EU |
| (Azure ruter |
| automatisk) |
+------------------+
```
**Fordeler:** Enklest, Azure haandterer intern ruting i EU-sonen.
**Ulemper:** Mindre kontroll over noyaktig hvilken region som brukes.
### APIM Backend Pool-konfigurasjon
Azure API Management stoetter backend pools med innebygd lastbalansering:
```json
{
"type": "Microsoft.ApiManagement/service/backends",
"name": "openai-backend-pool",
"properties": {
"type": "Pool",
"pool": {
"services": [
{
"id": "/backends/norway-east-openai",
"priority": 1,
"weight": 3
},
{
"id": "/backends/sweden-central-openai",
"priority": 1,
"weight": 1
},
{
"id": "/backends/france-central-openai",
"priority": 2,
"weight": 1
}
]
}
}
}
```
### Lastbalanseringsalternativer i APIM
| Metode | Beskrivelse | Bruksomrade |
|--------|-------------|-------------|
| **Round-robin** | Fordeler jevnt mellom backends | Standard for lik kapasitet |
| **Vektet** | Basert pa vekt per backend | Ulik kapasitetsallokering |
| **Prioritetsbasert** | Hoeyere prioritet forst, lavere som fallback | PTU primaer, Standard sekundaer |
| **Session affinity** | Samme bruker til samme backend | Chat/agent-scenarier med kontekst |
### Circuit Breaker-policy i APIM
Gatewayen ma respektere throttling-signaler (HTTP 429) og fjerne feilede backends fra poolen:
```xml
<policies>
<inbound>
<base />
<set-backend-service backend-id="openai-backend-pool" />
</inbound>
<backend>
<retry condition="@(context.Response.StatusCode == 429)"
count="3"
interval="0"
first-fast-retry="true">
<set-backend-service backend-id="openai-backend-pool" />
<forward-request buffer-request-body="true" />
</retry>
</backend>
<outbound>
<base />
</outbound>
</policies>
```
> **Beste praksis:** Bruk `Retry-After`-headeren fra Azure OpenAI for a styre circuit breaker-logikken. Ikke proev a forutsi throttling; bruk HTTP-responskoder for a drive rutingbeslutninger.
## Latensoptimalisering og Proximity Routing
### Strategier for lav latens
| Strategi | Implementasjon | Effekt |
|----------|---------------|--------|
| **Co-lokalisering** | Gateway og AOAI i samme region | Eliminerer cross-region latens |
| **Private Endpoints** | Azure Private Link for alle AOAI-instanser | Reduserer nettverkshopp |
| **Azure Front Door** | Performance-based routing til naermeste gateway | Automatisk proximity routing |
| **ExpressRoute** | Dedikert forbindelse fra on-premises | Stabil, lav latens |
### Private Endpoint-arkitektur
```
On-premises nettverk (Statens vegvesen)
|
+-- ExpressRoute --> Azure vNet (Norway East)
|
+-- Private Endpoint --> APIM (Norway East)
|
+-- Private Endpoint --> AOAI (Norway East)
|
+-- vNet Peering --> Azure vNet (Sweden Central)
|
+-- Private Endpoint --> AOAI (Sweden Central)
```
### DNS-konfigurasjon for failover
For privat nettverkstilgang kan en split-brain DNS-tilnaerming brukes:
```
Normaltilstand:
aoai-gateway.intern.vegvesen.no --> APIM Norway East (privat IP)
Ved regional utfall:
aoai-gateway.intern.vegvesen.no --> APIM Sweden Central (privat IP)
(manuell DNS-endring eller Azure Private DNS zones)
```
> **Merk:** Azure har per i dag ikke en native tjeneste for global server load balancer for arbeidsbelastninger som krever privat DNS-opploesning. Organisasjoner kan oppna active/passive-moenster gjennom a endre DNS-posten for gatewayen.
## Kvoteadministrasjon per region
### Kvotesystemet i Azure OpenAI
Kvote tildeles per **abonnement + region + modell** i enheter av **Tokens-per-Minute (TPM)**. Nar en deployment opprettes, trekkes TPM fra tilgjengelig kvote.
| Parameter | Beskrivelse |
|-----------|-------------|
| **TPM (Tokens Per Minute)** | Primaer kvoteenhet, tildelt per deployment |
| **RPM (Requests Per Minute)** | Avledet fra TPM, ratio varierer per modell |
| **Maks ressurser per region** | 30 |
| **Deployeringer per modell** | Ingen begrensning (fjernet med nytt kvotesystem) |
### Eksempel: RPM/TPM-ratio per modell
| Modell | 1 Kapasitetsenhet | RPM | TPM |
|--------|-------------------|-----|-----|
| gpt-4o og eldre chat | 1 | 6 | 1 000 |
| o1, o1-preview | 1 | 1 | 6 000 |
| o3-mini, o1-mini, o3-pro | 1 | 1 | 10 000 |
| o3, o4-mini | 1 | 1 | 1 000 |
### Kvotestrategi for multi-region
```
Abonnement: SVV-AI-Prod
|
+-- Norway East
| +-- gpt-4o Data Zone: 120K TPM (primaer)
| +-- gpt-35-turbo: 60K TPM
|
+-- Sweden Central
| +-- gpt-4o Data Zone: 120K TPM (sekundaer)
| +-- gpt-35-turbo: 120K TPM
| +-- o1-mini Global Standard: 80K TPM
|
+-- France Central (failover)
+-- gpt-4o Standard: 60K TPM
```
> **Tips:** Alloker full tilgjengelig kvote til hvert endepunkt. Siden kvote er per abonnement + region, pavirker ikke deployeringer i forskjellige regioner hverandre. Hvis kvoten er oppbrukt, kan et nytt abonnement deployeres pa samme mate bak gatewayen.
### Overvaking av kvotebruk
```bash
# Sjekk kapasitet per modell/region via REST API
az rest --method get \
--url "https://management.azure.com/subscriptions/{sub-id}/providers/Microsoft.CognitiveServices/modelCapacities?api-version=2024-06-01-preview&modelName=gpt-4o&modelVersion=2024-11-20"
```
Bruk Azure AI Foundry portal (**Management > Quota**) for oversikt over kvoteallokeringer pa tvers av deployeringer i en gitt region.
## Kostnadsmodell for multi-region
### Kostnadskomponenter
| Komponent | Kostnadsdriver | Estimat (NOK/maned) |
|-----------|---------------|---------------------|
| **Azure OpenAI Standard** | Token-forbruk per region | Varierer per bruk |
| **Azure OpenAI PTU** | Fast pris per PTU-enhet | ~170 NOK/PTU/time |
| **APIM Premium** | Per gateway-enhet per region | ~30 000 NOK/enhet/maned |
| **APIM Standard v2** | Per enhet | ~8 000 NOK/enhet/maned |
| **Egress-trafikk** | Cross-region dataoverforing | ~0,70 NOK/GB |
| **Private Endpoints** | Per endepunkt per time | ~80 NOK/endepunkt/maned |
| **Azure Front Door** | Per profil + trafikk | Fra ~3 500 NOK/maned |
### Kostnadsoptimaliseringsstrategi: PTU + Standard spillover
Microsoft anbefaler a kombinere Provisioned Throughput Units (PTU) med Standard-deployeringer:
```
Prioritet 1: Enterprise PTU Pool (Region A)
- Fast pris, garantert kapasitet
- Bruk all kapasitet foerst
Prioritet 2: Enterprise PTU Pool (Region B)
- Beskytter mot regionalt utfall
- Redundans for PTU
Prioritet 3: Standard Data Zone (EU)
- Pay-per-token for trafikktopper
- Spillover fra PTU
```
### Eksempel: Kostnadssammenligning (maned)
| Scenario | Oppsett | Estimert kostnad (NOK) |
|----------|---------|----------------------|
| **Enkel region** | 1x AOAI Standard + APIM Std v2 | 15 000 - 25 000 |
| **Dual region (Data Zone)** | 2x AOAI Data Zone + APIM Std v2 | 20 000 - 35 000 |
| **Enterprise PTU + failover** | PTU (100 enheter) + Standard failover + APIM Premium | 200 000 - 350 000 |
| **Full HA multi-region** | APIM Premium multi-region + 3x AOAI + Front Door | 120 000 - 250 000 |
> **Merk:** Kostnadsestimatene er veiledende og avhenger sterkt av trafikkvolum, modellvalg og PTU-allokering. Bruk Azure Pricing Calculator for noyaktige estimater.
### Kostnadsbesparende tips
1. **Bruk Data Zone-deployeringer** fremfor selvadministrert multi-region -- enklere og mer kostnadseffektivt
2. **Alloker PTU for baseline-trafikk**, Standard for topper (spillover-moenster)
3. **Plasser PTU og Standard i forskjellige regioner** -- unnga a miste begge ved regionalt utfall
4. **Konsolider gjennom felles Enterprise PTU Pool** -- hoeyere utnyttelse nar trafikk fra flere applikasjoner jevnes ut
5. **Unnga APIM Premium med mindre du trenger multi-region gateway** -- Standard v2 er tilstrekkelig for mange scenarier
## Implementeringssjekkliste
- [ ] Velg primaer og sekundaer region basert pa compliance-krav og modellbehov
- [ ] Deploy Azure OpenAI-ressurser i begge regioner med identiske modelldeployeringer
- [ ] Konfigurer APIM med backend pool og prioritetsbasert lastbalansering
- [ ] Implementer circuit breaker-policy som respekterer 429 og Retry-After
- [ ] Sett opp Private Endpoints for alle AOAI-instanser
- [ ] Konfigurer overvaking av kvotebruk og latens per region
- [ ] Dokumenter failover-prosedyre og test regelmessig
- [ ] Etabler varsling ved regionalt utfall eller hoey feilrate
## Referanser
- [Business Continuity and Disaster Recovery (BCDR) considerations with Azure OpenAI](https://learn.microsoft.com/en-us/azure/ai-foundry/openai/how-to/business-continuity-disaster-recovery)
- [Use a gateway in front of multiple Azure OpenAI deployments or instances](https://learn.microsoft.com/en-us/azure/architecture/ai-ml/guide/azure-openai-gateway-multi-backend)
- [Backends in API Management - Load-balanced pool](https://learn.microsoft.com/en-us/azure/api-management/backends#load-balanced-pool)
- [Manage Azure OpenAI quota](https://learn.microsoft.com/en-us/azure/ai-foundry/openai/how-to/quota)
- [Azure OpenAI model availability by region](https://learn.microsoft.com/en-us/azure/ai-foundry/openai/concepts/models)
- [Smart Load Balancing for OpenAI with Azure API Management](https://github.com/Azure-Samples/openai-apim-lb)
## For Cosmo
- **Bruk denne referansen** nar kunden spoer om multi-region-deployering, lastbalansering, eller failover for Azure OpenAI -- spesielt for produksjonsmiljoeer som krever hoey tilgjengelighet.
- **Anbefal Data Zone-deployeringer** som foerstevalg for norsk offentlig sektor. De fleste organisasjoner trenger ikke selvadministrert multi-region lastbalansering -- Data Zone haandterer dette innenfor EU-sonen.
- **Vurder regionkombinasjonen Norway East + Sweden Central** som standard for nordiske organisasjoner med EU-compliance-krav, men paapek at Norway East har begrenset modellstotte.
- **Bruk kostnadstabellene** for a gi konkrete estimater i NOK nar kunden trenger budsjettgrunnlag. Juster basert pa trafikkvolum og modellvalg.
- **Flagg alltid APIM som anbefalt gateway** -- Microsoft anbefaler dette eksplisitt i sin arkitekturveiledning, og det gir circuit breaker, retry, logging og lastbalansering som en PaaS-tjeneste.

View file

@ -0,0 +1,419 @@
# Network Resilience Patterns for AI Workloads
**Last updated:** 2026-02
**Status:** GA
**Category:** Business Continuity & Disaster Recovery
---
## Introduksjon
Nettverksresiliens er en kritisk komponent i BCDR for AI-arbeidsbelastninger. AI-systemer er avhengige av pålitelig nettverkskommunikasjon mellom flere tjenester: applikasjonslaget, Azure OpenAI-endepunkter, AI Search-tjenester, embeddings-APIer og datastores. En nettverksforstyrrelse i ett punkt kan kaskadere og ta ned hele AI-løsningen.
Azure Well-Architected Framework definerer flere resiliensmønstre som er særlig relevante for AI-workloads: Circuit Breaker for å forhindre kaskadefeil, Retry med exponential backoff for transiente feil, Bulkhead for isolering av feildomener, og Throttling for å beskytte mot overbelastning. Disse mønstrene bør implementeres systematisk i alle AI-applikasjoner.
For norsk offentlig sektor er nettverkssikkerhet regulert gjennom NSMs grunnprinsipper, og mange organisasjoner bruker private endepunkter (Private Link) for sine Azure AI-tjenester. BCDR-designet må ta hensyn til disse nettverksrestriksjonene og sikre at failover fungerer også med private nettverkskonfigurasjoner.
## Redundante nettverksstier og tilkoblinger
### Multi-path nettverksarkitektur
```
┌─────────────┐
│ Brukere │
└──────┬──────┘
┌──────▼──────────────────────┐
│ Azure Front Door (Global) │ ← DDoS Protection Standard
└──────┬──────────────────────┘
┌────┴────┐
│ │
┌─▼──┐ ┌─▼──┐
│ R1 │ │ R2 │ ← To Azure-regioner
└─┬──┘ └─┬──┘
│ │
┌─▼──────┐ ┌▼───────┐
│ VNet A │ │ VNet B │ ← Isolerte VNets per region
│ ├─APIM │ │ ├─APIM │
│ ├─App │ │ ├─App │
│ ├─PE │ │ ├─PE │ ← Private Endpoints til AI-tjenester
│ └─NSG │ │ └─NSG │
└────────┘ └────────┘
```
### Azure ExpressRoute redundans
```bash
# Primær ExpressRoute-tilkobling
az network express-route create \
--name "er-primary-norwayeast" \
--resource-group "rg-networking" \
--location "norwayeast" \
--bandwidth-in-mbps 1000 \
--peering-location "Oslo" \
--provider "Telenor"
# Sekundær ExpressRoute (annen provider/lokasjon)
az network express-route create \
--name "er-secondary-norwayeast" \
--resource-group "rg-networking" \
--location "norwayeast" \
--bandwidth-in-mbps 1000 \
--peering-location "Stavanger" \
--provider "GlobalConnect"
# VPN som backup for ExpressRoute
az network vnet-gateway create \
--name "vpn-gw-norwayeast" \
--resource-group "rg-networking" \
--location "norwayeast" \
--vnet "vnet-ai-norwayeast" \
--gateway-type Vpn \
--sku VpnGw2AZ \
--vpn-type RouteBased
```
### DNS-resiliens
```bash
# Azure Private DNS Zones for AI-tjenester med failover
az network private-dns zone create \
--resource-group "rg-networking" \
--name "privatelink.openai.azure.com"
# Link til VNets i begge regioner
az network private-dns link vnet create \
--resource-group "rg-networking" \
--zone-name "privatelink.openai.azure.com" \
--name "link-vnet-norwayeast" \
--virtual-network "vnet-ai-norwayeast" \
--registration-enabled false
az network private-dns link vnet create \
--resource-group "rg-networking" \
--zone-name "privatelink.openai.azure.com" \
--name "link-vnet-swedencentral" \
--virtual-network "vnet-ai-swedencentral" \
--registration-enabled false
```
## Circuit Breaker-mønster for API-kall
### Circuit Breaker implementering
Circuit Breaker-mønsteret forhindrer at en applikasjon gjentatte ganger forsøker å kalle en tjeneste som feiler, noe som kan forårsake kaskadefeil og ressursutmattelse.
```python
# Python Circuit Breaker for Azure OpenAI
import time
from enum import Enum
from threading import Lock
class CircuitState(Enum):
CLOSED = "closed" # Normal drift
OPEN = "open" # Stopp alle kall
HALF_OPEN = "half_open" # Prøv ett kall
class CircuitBreaker:
"""Circuit breaker for Azure AI service calls."""
def __init__(
self,
failure_threshold=5,
recovery_timeout=30,
success_threshold=3
):
self.failure_threshold = failure_threshold
self.recovery_timeout = recovery_timeout
self.success_threshold = success_threshold
self.state = CircuitState.CLOSED
self.failure_count = 0
self.success_count = 0
self.last_failure_time = None
self.lock = Lock()
def can_execute(self):
"""Check if a request can be made."""
with self.lock:
if self.state == CircuitState.CLOSED:
return True
elif self.state == CircuitState.OPEN:
if time.time() - self.last_failure_time > self.recovery_timeout:
self.state = CircuitState.HALF_OPEN
return True
return False
elif self.state == CircuitState.HALF_OPEN:
return True
def record_success(self):
"""Record a successful call."""
with self.lock:
if self.state == CircuitState.HALF_OPEN:
self.success_count += 1
if self.success_count >= self.success_threshold:
self.state = CircuitState.CLOSED
self.failure_count = 0
self.success_count = 0
else:
self.failure_count = 0
def record_failure(self):
"""Record a failed call."""
with self.lock:
self.failure_count += 1
self.last_failure_time = time.time()
if self.state == CircuitState.HALF_OPEN:
self.state = CircuitState.OPEN
self.success_count = 0
elif self.failure_count >= self.failure_threshold:
self.state = CircuitState.OPEN
# Bruk med Azure OpenAI
cb_openai = CircuitBreaker(failure_threshold=3, recovery_timeout=60)
cb_search = CircuitBreaker(failure_threshold=5, recovery_timeout=30)
async def call_openai_with_circuit_breaker(messages):
if not cb_openai.can_execute():
# Fallback: returner cached eller statisk respons
return get_fallback_response(messages)
try:
response = await openai_client.chat.completions.create(
model="gpt-4o",
messages=messages,
timeout=30
)
cb_openai.record_success()
return response
except Exception as e:
cb_openai.record_failure()
raise
```
### Circuit Breaker i .NET med Polly
```csharp
// C# med Polly for resilient Azure AI-kall
using Polly;
using Polly.CircuitBreaker;
var circuitBreakerPolicy = Policy
.Handle<HttpRequestException>()
.Or<TaskCanceledException>()
.CircuitBreakerAsync(
exceptionsAllowedBeforeBreaking: 5,
durationOfBreak: TimeSpan.FromSeconds(30),
onBreak: (ex, breakDuration) =>
logger.LogWarning($"Circuit opened for {breakDuration.TotalSeconds}s: {ex.Message}"),
onReset: () =>
logger.LogInformation("Circuit closed, resuming normal operation"),
onHalfOpen: () =>
logger.LogInformation("Circuit half-open, testing with next request")
);
var retryPolicy = Policy
.Handle<HttpRequestException>()
.WaitAndRetryAsync(
retryCount: 3,
sleepDurationProvider: retryAttempt =>
TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
onRetry: (ex, delay, retryCount, context) =>
logger.LogWarning($"Retry {retryCount} after {delay.TotalSeconds}s: {ex.Message}")
);
// Kombiner retry + circuit breaker
var resilientPolicy = Policy.WrapAsync(retryPolicy, circuitBreakerPolicy);
var result = await resilientPolicy.ExecuteAsync(async () =>
await searchClient.SearchAsync<SearchDocument>(query)
);
```
## Graceful degradation av AI-tjenester
### Degraderingsstrategier
| Feiltilstand | Degraderingsstrategi | Brukeropplevelse |
|-------------|---------------------|------------------|
| Azure OpenAI nede | Returnér cached svar eller statiske meldinger | "Vi opplever tekniske problemer..." |
| AI Search nede | Fall tilbake til enklere tekstsøk | Redusert relevans, men funksjonelt |
| Embedding API nede | Bruk keyword-basert search | Ingen semantisk søk, men resultater |
| Alle AI-tjenester nede | Full graceful degradation | Manuell betjening eller køsystem |
### Implementering
```python
# Graceful degradation for RAG-applikasjon
class ResilientRAGService:
"""RAG service with multiple fallback levels."""
async def get_response(self, user_query: str) -> dict:
"""Try full RAG, then degrade gracefully."""
# Level 1: Full RAG (AI Search + Azure OpenAI)
try:
context = await self._search_with_ai(user_query)
response = await self._generate_with_openai(user_query, context)
return {"level": "full", "response": response}
except ServiceUnavailableError:
pass
# Level 2: Keyword search + Azure OpenAI
try:
context = await self._keyword_search(user_query)
response = await self._generate_with_openai(user_query, context)
return {"level": "degraded_search", "response": response}
except ServiceUnavailableError:
pass
# Level 3: Cached/FAQ responses
try:
response = await self._get_cached_response(user_query)
if response:
return {"level": "cached", "response": response}
except Exception:
pass
# Level 4: Static fallback
return {
"level": "fallback",
"response": "Vi opplever tekniske problemer med vår AI-tjeneste. "
"Vennligst prøv igjen senere eller kontakt oss direkte."
}
```
## Private endepunkter og nettverksisolering
### Private Link for AI-tjenester
```bash
# Opprett Private Endpoints for AI-tjenester i begge regioner
# Azure OpenAI Private Endpoint — Primær region
az network private-endpoint create \
--name "pe-aoai-norwayeast" \
--resource-group "rg-ai-prod" \
--vnet-name "vnet-ai-norwayeast" \
--subnet "snet-private-endpoints" \
--private-connection-resource-id "/subscriptions/{sub}/resourceGroups/rg-ai-prod/providers/Microsoft.CognitiveServices/accounts/aoai-prod" \
--group-ids "account" \
--connection-name "aoai-primary"
# Azure OpenAI Private Endpoint — DR region
az network private-endpoint create \
--name "pe-aoai-swedencentral" \
--resource-group "rg-ai-dr" \
--vnet-name "vnet-ai-swedencentral" \
--subnet "snet-private-endpoints" \
--private-connection-resource-id "/subscriptions/{sub}/resourceGroups/rg-ai-dr/providers/Microsoft.CognitiveServices/accounts/aoai-dr" \
--group-ids "account" \
--connection-name "aoai-secondary"
# AI Search Private Endpoint — Primær region
az network private-endpoint create \
--name "pe-search-norwayeast" \
--resource-group "rg-ai-prod" \
--vnet-name "vnet-ai-norwayeast" \
--subnet "snet-private-endpoints" \
--private-connection-resource-id "/subscriptions/{sub}/resourceGroups/rg-ai-prod/providers/Microsoft.Search/searchServices/search-prod" \
--group-ids "searchService" \
--connection-name "search-primary"
```
### VNet Peering mellom regioner
```bash
# VNet peering for cross-region kommunikasjon
az network vnet peering create \
--name "peer-norwayeast-to-swedencentral" \
--resource-group "rg-networking" \
--vnet-name "vnet-ai-norwayeast" \
--remote-vnet "/subscriptions/{sub}/resourceGroups/rg-networking/providers/Microsoft.Network/virtualNetworks/vnet-ai-swedencentral" \
--allow-vnet-access true \
--allow-forwarded-traffic true
az network vnet peering create \
--name "peer-swedencentral-to-norwayeast" \
--resource-group "rg-networking" \
--vnet-name "vnet-ai-swedencentral" \
--remote-vnet "/subscriptions/{sub}/resourceGroups/rg-networking/providers/Microsoft.Network/virtualNetworks/vnet-ai-norwayeast" \
--allow-vnet-access true \
--allow-forwarded-traffic true
```
## DDoS-beskyttelse og trafikkfiltrering
### Azure DDoS Protection
```bash
# Aktiver DDoS Protection Standard
az network ddos-protection create \
--name "ddos-ai-protection" \
--resource-group "rg-networking" \
--location "norwayeast"
# Koble til VNet
az network vnet update \
--name "vnet-ai-norwayeast" \
--resource-group "rg-networking" \
--ddos-protection-plan "ddos-ai-protection"
```
### NSG-regler for AI-tjenester
```bash
# Network Security Group for AI-subnet
az network nsg rule create \
--resource-group "rg-networking" \
--nsg-name "nsg-ai-app" \
--name "AllowAzureOpenAI" \
--priority 100 \
--direction Outbound \
--access Allow \
--protocol Tcp \
--destination-port-ranges 443 \
--destination-address-prefixes "CognitiveServicesManagement" \
--description "Allow outbound to Azure OpenAI"
az network nsg rule create \
--resource-group "rg-networking" \
--nsg-name "nsg-ai-app" \
--name "AllowAzureSearch" \
--priority 110 \
--direction Outbound \
--access Allow \
--protocol Tcp \
--destination-port-ranges 443 \
--destination-address-prefixes "AzureCognitiveSearch" \
--description "Allow outbound to Azure AI Search"
az network nsg rule create \
--resource-group "rg-networking" \
--nsg-name "nsg-ai-app" \
--name "DenyAllOtherOutbound" \
--priority 4000 \
--direction Outbound \
--access Deny \
--protocol "*" \
--destination-port-ranges "*" \
--destination-address-prefixes "*" \
--description "Deny all other outbound traffic"
```
## Referanser
- [Circuit Breaker pattern](https://learn.microsoft.com/en-us/azure/architecture/patterns/circuit-breaker) — Detaljert mønsterbeskrivelse
- [Retry pattern](https://learn.microsoft.com/en-us/azure/architecture/patterns/retry) — Retry-strategier
- [Architecture design patterns that support reliability](https://learn.microsoft.com/en-us/azure/well-architected/reliability/design-patterns) — WAF resiliensmønstre
- [Transient fault handling](https://learn.microsoft.com/en-us/azure/architecture/best-practices/transient-faults) — Best practices for transiente feil
- [Azure DDoS Protection overview](https://learn.microsoft.com/en-us/azure/ddos-protection/ddos-protection-overview) — DDoS-beskyttelse
- [Azure Private Link overview](https://learn.microsoft.com/en-us/azure/private-link/private-link-overview) — Private Endpoints
## For Cosmo
- **Bruk denne referansen** når kunden designer nettverksarkitektur for resiliente AI-løsninger, eller når de implementerer failover med private endepunkter.
- Circuit Breaker + Retry med exponential backoff er OBLIGATORISK for alle Azure AI API-kall — dette er ikke valgfritt.
- For private endpoints: Husk at failover mellom regioner krever at Private DNS-soner er linket til begge VNets.
- Graceful degradation bør alltid designes i lag — full AI → enklere søk → cached svar → statisk fallback.
- Anbefal Azure Front Door (Premium) for AI-workloads som trenger global load balancing med DDoS-beskyttelse og WAF i ett produkt.

View file

@ -0,0 +1,265 @@
# RTO and RPO Planning for AI Services
**Last updated:** 2026-02
**Status:** GA
**Category:** Business Continuity & Disaster Recovery
---
## Introduksjon
Recovery Time Objective (RTO) og Recovery Point Objective (RPO) er de to mest kritiske metrikkene i enhver BCDR-strategi for AI-systemer. RTO definerer hvor raskt et system må gjenopprettes etter en forstyrrelse, mens RPO definerer hvor mye datatap som er akseptabelt. For AI-tjenester som Azure OpenAI, Azure AI Search og Azure Machine Learning er disse metrikkene spesielt viktige fordi nedetid direkte påvirker brukeropplevelsen og forretningsbeslutninger.
I norsk offentlig sektor er kravene til tilgjengelighet regulert gjennom flere rammeverk, inkludert Utredningsinstruksen, NSMs grunnprinsipper for IKT-sikkerhet og Digitaliseringsdirektoratets arkitekturprinsipper. Organisasjoner må dokumentere RTO og RPO for alle kritiske systemer som del av sin sikkerhetsstyring og internkontroll.
For AI-løsninger bringer disse kravene unike utfordringer: modelldata, treningsdata, embedding-indekser og konfigurasjoner må alle vurderes separat i en Business Impact Analysis (BIA). En chatbot med RAG-arkitektur har for eksempel separate RPO-krav for selve language model-endpointet, search-indeksen og kunnskapsdokumentene.
## Business Impact Analysis for RTO-bestemmelse
En Business Impact Analysis (BIA) er det første steget i å definere RTO for AI-systemer. BIA kartlegger forretningspåvirkningen av nedetid for hvert AI-komponent.
### Kritikalitetstier for AI-systemer
| Tier | Beskrivelse | RTO-mål | RPO-mål | Eksempel AI-bruk |
|------|-------------|---------|---------|------------------|
| Tier 0 — Mission Critical | Nedetid er uakseptabelt | < 1 min | 0 | Sanntids sikkerhetsovervåking med AI |
| Tier 1 — Business Critical | Kort nedetid tolererbar | < 15 min | < 5 min | Kundeservicebot i produksjon |
| Tier 2 — Business Operational | Timer akseptabelt | < 4 timer | < 1 time | Intern rapporteringsplattform med AI |
| Tier 3 — Administrative | Lengre nedetid OK | < 24 timer | < 24 timer | Trenings- og sandbox-miljøer |
### BIA-prosess for AI-komponenter
1. **Identifiser alle AI-avhengigheter**: Kartlegg komponentene i AI-løsningen (modell-endpoints, search-indekser, data-pipelines, embedding-stores)
2. **Vurder forretningspåvirkning per komponent**: Hva skjer hvis Azure OpenAI-endpointet er nede? Hva om AI Search-indeksen er korrupt?
3. **Kvantifiser finansiell påvirkning**: Beregn kostnad per time med nedetid
4. **Kartlegg avhengigheter**: Hvilke systemer avhenger av AI-komponentene?
5. **Definer akseptabel degradering**: Kan systemet tilby begrenset funksjonalitet uten AI?
### BIA-mal for AI-tjenester
```markdown
## Business Impact Analysis — [Tjenestenavn]
### Tjenestebeskrivelse
- **Funksjon:** [Hva gjør AI-tjenesten?]
- **Brukere:** [Antall brukere/systemer som avhenger av tjenesten]
- **Driftstid:** [Forventet tilgjengelighet, f.eks. 24/7 eller kontortid]
### Påvirkningsvurdering
| Nedetid | Finansiell påvirkning | Omdømmepåvirkning | Regulatorisk risiko |
|---------|----------------------|--------------------|--------------------|
| 01 time | [Lav/Middels/Høy] | [Lav/Middels/Høy] | [Lav/Middels/Høy] |
| 14 timer | [...] | [...] | [...] |
| 424 timer | [...] | [...] | [...] |
| > 24 timer | [...] | [...] | [...] |
### Konklusjon
- **Kritikalitetstier:** [0/1/2/3]
- **RTO-krav:** [X minutter/timer]
- **RPO-krav:** [X minutter/timer]
```
## Datatap-toleranse og RPO-beregning
RPO for AI-systemer krever spesiell oppmerksomhet fordi data har forskjellig verdi og regenereringstid.
### RPO-kategorier for AI-data
| Datatype | Typisk RPO | Regenereringstid | Beskyttelsesmekanisme |
|----------|-----------|-------------------|-----------------------|
| Treningsdata (datasett) | 24 timer | Dager til uker | Azure Blob Storage GRS/GZRS |
| Finjusterte modeller | 24 timer | Timer til dager | Model registry backup |
| Search-indekser (embeddings) | 14 timer | Timer | Dual-region indexing |
| Brukerdata/konversasjoner | 015 min | Ikke regenererbar | Cosmos DB multi-region writes |
| Konfigurasjoner og prompts | 0 | Minutter via IaC | Git + IaC deployment |
| Fine-tuning jobb-tilstand | 424 timer | Timer | Checkpoint-basert backup |
### Beregningsmodell for RPO
RPO beregnes basert på tre faktorer:
1. **Data change rate**: Hvor ofte endres dataene?
2. **Replication lag**: Hva er forsinkelsen mellom primær og sekundær region?
3. **Backup frequency**: Hvor ofte tas backup?
```
Effektiv RPO = max(Replication Lag, Backup Interval)
```
For Azure Storage med Geo Priority Replication er RPO for blobs garantert <= 15 minutter (99.0% av faktureringsperioden).
### Azure-tjenester og deres innebygde RPO
| Azure-tjeneste | Innebygd RPO | Konfigurasjon | Merknad |
|----------------|-------------|---------------|---------|
| Azure OpenAI | Ingen innebygd DR | Manuell multi-region | Stateless — redeploy i ny region |
| Azure AI Search | Ingen innebygd repl. | Manuell multi-region | Rebuild indeks fra kilde |
| Azure Cosmos DB | ~0 (multi-region writes) | Konfigurerbar | Automatisk geo-replikering |
| Azure Blob Storage (GRS) | ~15 min | Aktivér GRS/GZRS | Async replikering |
| Azure Blob Storage (GPR) | <= 15 min SLA | Aktivér Geo Priority Repl. | SLA-backed RPO |
| Azure SQL Database | ~5 sek (geo-repl.) | Active geo-replication | Async replikering |
| Azure Machine Learning | Ingen innebygd DR | Manuell multi-region | Separat storage per region |
## Mapping av krav til Azure-kapabiliteter
### Recovery-konfigurasjoner
| Konfigurasjonstype | RTO | RPO | Kostnad | Egnet for |
|--------------------|-----|-----|---------|-----------|
| Active-Active | ~0 | ~0 | Høyest | Tier 0: Mission Critical |
| Active-Passive (Warm) | Minutter | Minutter | Middels-Høy | Tier 1: Business Critical |
| Active-Passive (Cold) | Timer | Timer | Middels | Tier 2: Business Operational |
| Backup & Restore | TimerDager | TimerDager | Lavest | Tier 3: Administrative |
### Azure OpenAI BCDR-konfigurasjon
```python
# Eksempel: Multi-region Azure OpenAI med failover via Azure API Management
import openai
from azure.identity import DefaultAzureCredential
# Primær region
primary_client = openai.AzureOpenAI(
azure_endpoint="https://aoai-primary-norwayeast.openai.azure.com/",
api_version="2024-10-21",
azure_deployment="gpt-4o"
)
# Sekundær region (warm standby)
secondary_client = openai.AzureOpenAI(
azure_endpoint="https://aoai-secondary-swedencentral.openai.azure.com/",
api_version="2024-10-21",
azure_deployment="gpt-4o"
)
def call_with_failover(messages, max_retries=3):
"""Call Azure OpenAI with automatic failover to secondary region."""
try:
response = primary_client.chat.completions.create(
model="gpt-4o",
messages=messages,
timeout=30
)
return response
except Exception as e:
print(f"Primary region failed: {e}")
# Failover to secondary
response = secondary_client.chat.completions.create(
model="gpt-4o",
messages=messages,
timeout=30
)
return response
```
### Azure AI Search multi-region oppsett
```bash
# Deploy identisk search-tjeneste i to regioner
az search service create \
--name "search-primary-norwayeast" \
--resource-group "rg-ai-prod" \
--location "norwayeast" \
--sku "standard" \
--replica-count 3 \
--partition-count 2
az search service create \
--name "search-secondary-swedencentral" \
--resource-group "rg-ai-dr" \
--location "swedencentral" \
--sku "standard" \
--replica-count 2 \
--partition-count 2
# Bruk Azure Traffic Manager for routing
az network traffic-manager profile create \
--name "tm-search-failover" \
--resource-group "rg-networking" \
--routing-method Priority \
--unique-dns-name "ai-search-global"
```
## Norske regulatoriske krav
### Forvaltningsloven og Utredningsinstruksen
Norske offentlige organisasjoner må dokumentere:
- **Konsekvensanalyse**: Vurdering av konsekvenser ved bortfall av AI-tjenester
- **Alternativanalyse**: Evaluering av ulike BCDR-strategier med kost/nytte
- **Risiko- og sårbarhetsanalyse (ROS)**: Identifisering av trusler mot AI-systemers tilgjengelighet
### NSMs grunnprinsipper
NSM (Nasjonal sikkerhetsmyndighet) krever:
- Klassifisering av systemer etter kritikalitet
- Dokumenterte gjenopprettingsplaner
- Regelmessig testing av beredskapsplaner
- Loggføring og rapportering av hendelser
### Data Residency-krav
| Krav | Beskrivelse | Påvirkning på BCDR |
|------|-------------|-------------------|
| Schrems II | Data kan ikke overføres til usikre tredjeland | Begrens DR-regioner til EU/EØS |
| GDPR Art. 32 | Tilstrekkelig sikkerhetsnivå inkl. tilgjengelighet | Dokumentér RTO/RPO i DPIA |
| Forvaltningsloven §13 | Taushetsplikt | Kryptering i alle DR-regioner |
## Dokumentasjons-maler og governance
### RTO/RPO-dokumentasjonsmal
```markdown
# RTO/RPO Dokumentasjon — [Systemnavn]
## Versjon og godkjenning
- **Versjon:** [X.Y]
- **Sist oppdatert:** [Dato]
- **Godkjent av:** [Navn og rolle]
- **Neste revisjon:** [Dato]
## Systembeskrivelse
[Kort beskrivelse av AI-systemet og dets forretningsfunksjon]
## Komponentoversikt med RTO/RPO
| Komponent | Kritikalitet | RTO | RPO | DR-strategi | Ansvarlig |
|-----------|-------------|-----|-----|-------------|-----------|
| Azure OpenAI Endpoint | Høy | 15 min | N/A | Multi-region | Platform team |
| AI Search Index | Høy | 1 time | 4 timer | Dual indexing | Data team |
| Cosmos DB (state) | Kritisk | 0 | 0 | Multi-region writes | Platform team |
| Blob Storage (docs) | Middels | 4 timer | 15 min | GRS | Ops team |
## Testplan
- **Frekvens:** Kvartalsvis
- **Type:** Failover drill + tabletop
- **Suksesskriterier:** [Definer]
## Hendelsesklassifisering
[Ref. til incident response plan]
```
### Governance-prosess
1. **Årlig BIA-revisjon**: Oppdater kritikalitetsvurderinger
2. **Kvartalsvis testing**: Verifiser at RTO/RPO-mål oppnås
3. **Hendelsesdrevet oppdatering**: Revider etter reelle hendelser
4. **Endringsbasert vurdering**: Nye AI-komponenter trigger ny BIA
## Referanser
- [Business continuity and disaster recovery overview](https://learn.microsoft.com/en-us/azure/reliability/concept-business-continuity-high-availability-disaster-recovery) — Grunnleggende BCDR-konsepter og definisjoner
- [Develop a disaster recovery plan for multi-region deployments](https://learn.microsoft.com/en-us/azure/well-architected/design-guides/disaster-recovery) — WAF-veiledning for DR-planlegging
- [Recommendations for defining reliability targets](https://learn.microsoft.com/en-us/azure/well-architected/reliability/metrics) — SLO, RTO og RPO-definisjoner
- [BCDR considerations with Azure OpenAI](https://learn.microsoft.com/en-us/azure/ai-foundry/openai/how-to/business-continuity-disaster-recovery) — Azure OpenAI-spesifikk BCDR
- [Azure Storage redundancy](https://learn.microsoft.com/en-us/azure/storage/common/storage-redundancy) — GRS, GZRS og replikeringsalternativer
- [Azure Storage Geo Priority Replication](https://learn.microsoft.com/en-us/azure/storage/common/storage-redundancy-priority-replication) — SLA-backed RPO for blobs
- [Reliability in Azure AI Search](https://learn.microsoft.com/en-us/azure/reliability/reliability-ai-search) — Tilgjengelighet og DR for AI Search
## For Cosmo
- **Bruk denne referansen** når kunden trenger hjelp med å definere RTO og RPO for sine AI-systemer, eller når de planlegger BCDR-strategi.
- Start alltid med en Business Impact Analysis (BIA) før du foreslår tekniske løsninger — RTO/RPO er forretningsbeslutninger, ikke tekniske.
- Utfordre kunder som sier "alt er kritisk" — differensiert kritikalitet er nøkkelen til kostnadseffektiv BCDR.
- For norsk offentlig sektor: Påpek at DPIA (Data Protection Impact Assessment) bør inkludere tilgjengelighetsvurdering med RTO/RPO.
- Husk at Azure OpenAI er stateless — RTO handler om redeployment og DNS-oppdatering, ikke om datavederlag.

View file

@ -0,0 +1,419 @@
# Service Level Documentation and DR Runbooks
**Last updated:** 2026-02
**Status:** GA
**Category:** Business Continuity & Disaster Recovery
---
## Introduksjon
Service Level Agreements (SLA), runbooks og operasjonelle prosedyrer er bindeleddet mellom BCDR-strategi og faktisk gjenopprettingsevne. Uten presis dokumentasjon av SLA-mål, detaljerte trinn-for-trinn runbooks og tydelig ansvarsfordeling, vil selv den best designede DR-arkitekturen feile under en reell hendelse.
For AI-systemer er dokumentasjon spesielt viktig fordi gjenopprettingsprosedyrer ofte involverer flere Azure-tjenester med ulike oppstartstider og avhengigheter. En RAG-løsning krever for eksempel at Cosmos DB er tilgjengelig før App Service, som igjen må vente på at AI Search-indeksen er synkronisert, før Azure OpenAI-kall kan brukes meningsfullt.
Azure Well-Architected Framework understreker at en DR-plan må inkludere tre essensielle komponenter: en klar runbook, en veldefinert kommunikasjonsplan, og en strukturert eskaleringsvei. For norsk offentlig sektor bør disse dokumentene følge organisasjonens ITIL-rammeverk og NSMs krav til beredskapsplanlegging.
## Service Level Agreement-maler
### SLA-dokument for AI-tjeneste
```markdown
# Service Level Agreement
## [AI-tjeneste navn]
### 1. Tjenestebeskrivelse
| Felt | Verdi |
|------|-------|
| Tjenestenavn | [Navn] |
| Tjenesteeier | [Avdeling/person] |
| Versjon | [X.Y] |
| Gyldig fra | [Dato] |
| Neste revisjon | [Dato] |
### 2. Tjenestenivåmål (SLO)
#### 2.1 Tilgjengelighet
| Mål | Verdi | Måleperiode | Ekskluderinger |
|-----|-------|-------------|----------------|
| Tilgjengelighet | 99.9% | Månedlig | Planlagt vedlikehold |
| Oppetid (beregnet) | ~43.8 min nedetid/mnd | — | — |
#### 2.2 Ytelse
| Mål | Verdi | Målepunkt |
|-----|-------|-----------|
| Chat response time (P95) | < 5 sekunder | End-to-end |
| Search query time (P95) | < 500 ms | API-nivå |
| Throughput | > 100 samtidige brukere | Applikasjonsnivå |
#### 2.3 Gjenoppretting
| Mål | Verdi | Merknad |
|-----|-------|---------|
| RTO | 15 minutter | Fra deteksjon til gjenopprettet |
| RPO | 5 minutter | Maks akseptabelt datatap |
| MTTR | < 30 minutter | Gjennomsnittlig gjenopprettingstid |
### 3. Ansvar og eskalering
| Rolle | Ansvarlig | Telefon | Epost |
|-------|-----------|---------|-------|
| Tjenesteeier | [Navn] | [Tlf] | [Epost] |
| Teknisk ansvarlig | [Navn] | [Tlf] | [Epost] |
| DR-koordinator | [Navn] | [Tlf] | [Epost] |
| Backup kontakt | [Navn] | [Tlf] | [Epost] |
### 4. Vedlikehold og unntak
- Planlagt vedlikehold: Tirsdager 02:0004:00 CET
- Varsling: Minimum 72 timer i forkant
- Nødvedlikehold: Varsling så snart som mulig
### 5. Rapportering
- Månedlig SLA-rapport til tjenesteeier
- Kvartalsvis trendrapport til ledelsen
- Umiddelbar hendelsesrapport ved SLA-brudd
```
## RTO og RPO dokumentasjonsstandarder
### Detaljert RTO/RPO-dokumentasjon
```markdown
# RTO/RPO Spesifikasjon — AI Platform
## Komponentoversikt med gjenopprettingsmål
| ID | Komponent | Kritikalitet | RTO | RPO | DR-strategi | Region |
|----|-----------|-------------|-----|-----|-------------|--------|
| C01 | Azure OpenAI | Høy | 5 min | N/A | Multi-region failover | NE→SC |
| C02 | Azure AI Search | Høy | 15 min | 30 min | Dual-indexing | NE→SC |
| C03 | Cosmos DB | Kritisk | ~0 | ~0 | Multi-region writes | NE+SC |
| C04 | App Service | Høy | 5 min | N/A | Multi-region + TM | NE→SC |
| C05 | Azure Key Vault | Kritisk | Auto | Auto | MS-managed failover | NE→SC |
| C06 | Blob Storage (docs) | Middels | 15 min | 15 min | GZRS | NE→SC |
| C07 | Redis Cache | Middels | 10 min | 5 min | Geo-replication | NE→SC |
| C08 | App Configuration | Lav | 5 min | ~0 | Geo-replication | NE→SC |
## Avhengighetsrekkefølge for gjenoppretting
```mermaid
graph LR
KV[Key Vault C05] --> DB[Cosmos DB C03]
KV --> Redis[Redis C07]
DB --> App[App Service C04]
Redis --> App
Config[App Config C08] --> App
Storage[Blob Storage C06] --> Search[AI Search C02]
Search --> App
App --> AOAI[Azure OpenAI C01]
```
## Gjenopprettingsrekkefølge
1. Key Vault (automatisk failover)
2. Cosmos DB (automatisk multi-region)
3. Redis Cache (geo-replication failover)
4. App Configuration (geo-replication failover)
5. Blob Storage (GRS failover if needed)
6. AI Search (start indexer i sekundær region)
7. App Service (deploy/scale i sekundær region)
8. Azure OpenAI (verifiser sekundært endpoint)
9. Traffic Manager (oppdater routing)
```
## Disaster Recovery Runbooks og Playbooks
### Master DR Runbook
```markdown
# DR Runbook — AI Platform
## Forutsetninger
- Tilgang til Azure Portal med Owner-rolle på rg-ai-dr
- Azure CLI installert og autentisert
- Tilgang til organisasjonens incident management system
- Kontaktliste for eskalering tilgjengelig
## Fase 1: Deteksjon og Vurdering (05 minutter)
### Steg 1.1: Verifiser hendelsen
- [ ] Sjekk Azure Service Health: https://status.azure.com
- [ ] Sjekk intern monitoring: [Dashboard URL]
- [ ] Verifiser med automatisk helsesjekk:
```bash
curl -s https://ai-app-prod.azurewebsites.net/health/deep | jq .
```
### Steg 1.2: Klassifiser hendelsen
| Scenario | Alvorlighetsgrad | Aksjon |
|----------|-----------------|--------|
| Enkelt komponent nede | P2 | Standard feilsøking |
| Regional degradering | P1 | Vurder partial failover |
| Full regional outage | P0 | Initier full DR |
### Steg 1.3: Deklarer hendelse
- [ ] Opprett incident i [ITSM-system]
- [ ] Varsle DR-koordinator
- [ ] Aktiver kommunikasjonsplan
---
## Fase 2: Failover-initiering (510 minutter)
### Steg 2.1: Verifiser DR-region readiness
```bash
# Sjekk at DR-ressurser er tilgjengelige
az resource list \
--resource-group "rg-ai-dr" \
--query "[].{Name:name, Type:type, Location:location}" \
--output table
# Sjekk Cosmos DB replikering
az cosmosdb show \
--name "cosmos-ai-state" \
--resource-group "rg-ai-prod" \
--query "readLocations[].{Region:locationName, State:failoverPriority}" \
--output table
```
### Steg 2.2: Scale up DR-ressurser
```bash
# Scale AI Search til produksjonsnivå
az search service update \
--name "search-secondary-swedencentral" \
--resource-group "rg-ai-dr" \
--replica-count 3
# Scale App Service
az appservice plan update \
--name "asp-ai-dr" \
--resource-group "rg-ai-dr" \
--sku P3v3
# Verifiser OpenAI-endpoint i DR-region
curl -s -H "api-key: $AOAI_DR_KEY" \
"https://aoai-secondary-swedencentral.openai.azure.com/openai/deployments/gpt-4o/chat/completions?api-version=2024-10-21" \
-d '{"messages":[{"role":"user","content":"test"}]}' | jq .status
```
### Steg 2.3: Oppdater Traffic Manager
```bash
# Switch til sekundær region
az network traffic-manager endpoint update \
--resource-group "rg-networking" \
--profile-name "tm-ai-platform" \
--name "primary-norwayeast" \
--type azureEndpoints \
--endpoint-status Disabled
az network traffic-manager endpoint update \
--resource-group "rg-networking" \
--profile-name "tm-ai-platform" \
--name "secondary-swedencentral" \
--type azureEndpoints \
--endpoint-status Enabled
```
---
## Fase 3: Verifikasjon (1015 minutter)
### Steg 3.1: Funksjonell testing
- [ ] Test health endpoint: `curl https://ai-app-dr.azurewebsites.net/health/deep`
- [ ] Test chat-funksjonalitet manuelt
- [ ] Test search-funksjonalitet
- [ ] Verifiser brukerautentisering
### Steg 3.2: Data-integritet
- [ ] Sjekk Cosmos DB datakonsistens
- [ ] Verifiser AI Search indeksstatus
- [ ] Kontroller at siste data er tilgjengelig
### Steg 3.3: Ytelsesverifisering
- [ ] Kjør syntetisk lasttest (lav belastning)
- [ ] Verifiser at responstider er akseptable
- [ ] Sjekk at auto-scaling fungerer
---
## Fase 4: Stabilisering og Kommunikasjon
### Steg 4.1: Oppdater interessenter
- [ ] Send statusoppdatering til alle berørte
- [ ] Oppdater statusside
- [ ] Informer kundeservice
### Steg 4.2: Overvåking
- [ ] Sett opp forsterket overvåking i DR-region
- [ ] Konfigurer alerts med lavere terskler
- [ ] Start kontinuerlig helsesjekk
---
## Fase 5: Failback (når primær region er tilgjengelig)
### Steg 5.1: Verifiser primær region
- [ ] Bekreft at Azure Service Health viser "Resolved"
- [ ] Test primær region infrastruktur
- [ ] Verifiser data-synkronisering
### Steg 5.2: Planlegg failback
- [ ] Velg lavtrafikk-vindu for failback
- [ ] Kommuniser plan til interessenter
- [ ] Forbered failback-runbook
### Steg 5.3: Utfør failback
```bash
# Re-aktiver primær region
az network traffic-manager endpoint update \
--resource-group "rg-networking" \
--profile-name "tm-ai-platform" \
--name "primary-norwayeast" \
--type azureEndpoints \
--endpoint-status Enabled
# Gradvis shift trafikk tilbake (weighted routing)
# eller oppdater priority
```
### Steg 5.4: Nedskaler DR-region
```bash
# Etter verifisert failback, nedskaler DR
az search service update \
--name "search-secondary-swedencentral" \
--resource-group "rg-ai-dr" \
--replica-count 2
az appservice plan update \
--name "asp-ai-dr" \
--resource-group "rg-ai-dr" \
--sku P2v3
```
---
## Fase 6: Post-Incident
- [ ] Gjennomfør post-mortem innen 5 virkedager
- [ ] Oppdater runbooks basert på erfaringer
- [ ] Logg faktisk RTO/RPO vs. mål
- [ ] Oppdater BCDR-dokumentasjon
```
## Trinn-for-trinn gjenopprettingsprosedyrer
### Spesifikk prosedyre: Azure AI Search Index Rebuild
```markdown
# Prosedyre: Rebuild AI Search Index i DR-region
## Når brukes denne?
- AI Search primær region er utilgjengelig
- Search indeks i DR-region er utdatert (> RPO)
- Corrupted index detected
## Forutsetninger
- Kildedokumenter tilgjengelig i DR-region (Blob Storage GZRS)
- Search service i DR-region er kjørende
- Skillset og indexer-definisjoner lagret i IaC (Git)
## Prosedyre
### 1. Verifiser at indeksdefinisjoner er tilgjengelige
```bash
# Hent indeksdefinisjon fra IaC-repo
git clone https://github.com/org/ai-infrastructure.git
cd ai-infrastructure/search-indexes/
cat knowledge-base-index.json | jq .name
```
### 2. Opprett/oppdater indeks i DR-region
```bash
# Opprett indeks
curl -X PUT \
"https://search-secondary-swedencentral.search.windows.net/indexes/knowledge-base?api-version=2024-07-01" \
-H "api-key: $SEARCH_DR_KEY" \
-H "Content-Type: application/json" \
-d @knowledge-base-index.json
# Opprett datasource
curl -X PUT \
"https://search-secondary-swedencentral.search.windows.net/datasources/blob-source?api-version=2024-07-01" \
-H "api-key: $SEARCH_DR_KEY" \
-H "Content-Type: application/json" \
-d @blob-datasource-dr.json
# Opprett skillset (hvis AI enrichment brukes)
curl -X PUT \
"https://search-secondary-swedencentral.search.windows.net/skillsets/embedding-skillset?api-version=2024-07-01" \
-H "api-key: $SEARCH_DR_KEY" \
-H "Content-Type: application/json" \
-d @embedding-skillset.json
```
### 3. Start full re-indeksering
```bash
# Opprett og kjør indexer
curl -X PUT \
"https://search-secondary-swedencentral.search.windows.net/indexers/blob-indexer?api-version=2024-07-01" \
-H "api-key: $SEARCH_DR_KEY" \
-H "Content-Type: application/json" \
-d @blob-indexer.json
# Overvåk indexer-status
watch -n 10 'curl -s \
"https://search-secondary-swedencentral.search.windows.net/indexers/blob-indexer/status?api-version=2024-07-01" \
-H "api-key: $SEARCH_DR_KEY" | jq ".lastResult.status, .lastResult.itemsProcessed"'
```
### 4. Verifiser indekskvalitet
```bash
# Sjekk dokumenttelling
curl -s "https://search-secondary-swedencentral.search.windows.net/indexes/knowledge-base/docs/\$count?api-version=2024-07-01" \
-H "api-key: $SEARCH_DR_KEY"
# Test en søkespørring
curl -s "https://search-secondary-swedencentral.search.windows.net/indexes/knowledge-base/docs/search?api-version=2024-07-01" \
-H "api-key: $SEARCH_DR_KEY" \
-H "Content-Type: application/json" \
-d '{"search": "test query", "top": 5}' | jq '.value | length'
```
### 5. Forventet tidsbruk
| Indeksstørrelse | Estimert tid | Merknad |
|----------------|-------------|---------|
| < 10,000 docs | 1020 min | Inkl. embedding-generering |
| 10,000100,000 | 3060 min | Avhenger av skillset |
| > 100,000 | 14 timer | Vurder parallel indexing |
```
## Eierskap og eskaleringsmatrise
### RACI-matrise for DR
| Aktivitet | Platform Team | AI Team | Security | Management | Microsoft |
|-----------|:------------:|:-------:|:--------:|:----------:|:---------:|
| Deteksjon | R | I | I | I | C |
| Beslutning om failover | A | C | C | I | — |
| Teknisk failover | R | C | I | I | C |
| Kommunikasjon (intern) | I | I | I | R/A | — |
| Kommunikasjon (ekstern) | I | I | I | R/A | — |
| Verifisering | R | R | C | I | — |
| Failback-planlegging | R | C | C | A | C |
| Post-mortem | R | R | C | A | — |
*R = Responsible, A = Accountable, C = Consulted, I = Informed*
## Referanser
- [Document your DR plan](https://learn.microsoft.com/en-us/azure/well-architected/design-guides/disaster-recovery#document-your-dr-plan) — WAF DR-dokumentasjon
- [DR communication plan](https://learn.microsoft.com/en-us/azure/well-architected/design-guides/disaster-recovery#document-your-dr-plan) — Kommunikasjonsplan
- [Test regularly and improve the plan](https://learn.microsoft.com/en-us/azure/well-architected/design-guides/disaster-recovery#test-regularly-and-improve-the-plan) — Testing av DR-plan
- [Create an effective incident management plan](https://learn.microsoft.com/en-us/azure/well-architected/design-guides/incident-management) — Incident management
- [Recommendations for defining reliability targets](https://learn.microsoft.com/en-us/azure/well-architected/reliability/metrics) — SLO-definisjoner
- [Reliability in Azure AI Search](https://learn.microsoft.com/en-us/azure/reliability/reliability-ai-search) — AI Search DR
## For Cosmo
- **Bruk denne referansen** når kunden trenger maler for SLA-dokumentasjon, DR-runbooks eller eskaleringsprosedyrer for AI-systemer.
- DR-runbooks MÅ være executable — hvert steg skal ha konkrete kommandoer, ikke bare beskrivelser.
- Versjonér runbooks i Git som kode — de endres like ofte som infrastrukturen.
- Gjenopprettingsrekkefølge er kritisk — dokumentér avhengigheter eksplisitt og test at rekkefølgen fungerer.
- For norsk offentlig sektor: RACI-matrise bør inkludere personvernombud (DPO) for hendelser som involverer persondata.

View file

@ -0,0 +1,403 @@
# State Management and Consistency During Failover
**Last updated:** 2026-02
**Status:** GA
**Category:** Business Continuity & Disaster Recovery
---
## Introduksjon
Håndtering av applikasjonstilstand (state) under failover-scenarioer er en av de mest utfordrende aspektene ved BCDR for AI-systemer. AI-applikasjoner har typisk flere typer state som må ivaretas: brukersesjoner, konversasjonshistorikk, mellomresultater fra langvarige operasjoner (fine-tuning, batch-indeksering), og applikasjonskonfigurasjon.
Under en failover kan in-flight requests gå tapt, sesjonsstilstand kan bli inkonsistent mellom regioner, og operasjoner som var halvveis fullført kan etterlate systemet i en ukjent tilstand. For å håndtere dette kreves distribuerte state management-mønstre, idempotente operasjoner, og robust request-retry logikk.
For norsk offentlig sektor er tap av state spesielt problematisk når AI-systemet støtter saksbehandling eller vedtaksfatting. Forvaltningsloven krever sporbarhet og etterrettelighet, noe som betyr at konversasjonshistorikk og AI-anbefalinger må bevares konsistent gjennom failover.
## Distribuerte state management-mønstre
### State-kategorier for AI-systemer
| State-type | Eksempel | Varighet | Kritikalitet | Lagring |
|-----------|---------|----------|-------------|---------|
| Session state | Autentiseringstoken, brukerpreferanser | Timer | Middels | Redis Cache / Cosmos DB |
| Conversation state | Chat-historikk, kontekstvindu | Dager | Høy | Cosmos DB |
| Operation state | Fine-tuning progress, batch-status | TimerDager | Middels | Queue + Cosmos DB |
| Configuration state | Model deployments, system prompts | Permanent | Kritisk | App Configuration / Git |
| Cache state | Søkeresultater, embeddings | MinutterTimer | Lav | Redis Cache |
### Distribuert state med Azure Cosmos DB
```python
# Distribuert state management for AI chatbot
from azure.cosmos.aio import CosmosClient
from azure.identity.aio import DefaultAzureCredential
import json
from datetime import datetime, timedelta
class DistributedStateManager:
"""Manage AI application state across regions with Cosmos DB."""
def __init__(self, connection_string, database_name="ai-state"):
self.client = CosmosClient.from_connection_string(connection_string)
self.database = self.client.get_database_client(database_name)
self.sessions = self.database.get_container_client("sessions")
self.conversations = self.database.get_container_client("conversations")
async def save_session(self, session_id: str, user_id: str, data: dict):
"""Save session state with TTL and version tracking."""
document = {
"id": session_id,
"userId": user_id,
"data": data,
"version": data.get("version", 0) + 1,
"lastUpdated": datetime.utcnow().isoformat(),
"ttl": 3600 * 24, # 24 timer TTL
"region": self._get_current_region()
}
await self.sessions.upsert_item(document)
return document["version"]
async def get_session(self, session_id: str, user_id: str):
"""Get session with partition key optimization."""
try:
response = await self.sessions.read_item(
item=session_id,
partition_key=user_id
)
return response
except Exception:
return None # Session not found
async def save_conversation_turn(
self, conversation_id: str, user_id: str, turn: dict
):
"""Append a conversation turn atomically."""
# Bruk conditional update for å unngå konflikter
conversation = await self._get_or_create_conversation(
conversation_id, user_id
)
# Legg til turn med unik ID for idempotens
turn["turnId"] = f"{conversation_id}-{len(conversation['turns'])}"
turn["timestamp"] = datetime.utcnow().isoformat()
conversation["turns"].append(turn)
conversation["lastUpdated"] = datetime.utcnow().isoformat()
# Conditional update med ETag for optimistisk locking
await self.conversations.replace_item(
item=conversation_id,
body=conversation,
match_condition=conversation.get("_etag")
)
def _get_current_region(self):
import os
return os.environ.get("AZURE_REGION", "unknown")
```
### Redis Cache for Session State
```bash
# Azure Cache for Redis med geo-replikering
# Primær region
az redis create \
--name "redis-ai-norwayeast" \
--resource-group "rg-ai-prod" \
--location "norwayeast" \
--sku "Premium" \
--vm-size "P1" \
--enable-non-ssl-port false \
--minimum-tls-version "1.2"
# Sekundær region (geo-replica)
az redis create \
--name "redis-ai-swedencentral" \
--resource-group "rg-ai-dr" \
--location "swedencentral" \
--sku "Premium" \
--vm-size "P1" \
--enable-non-ssl-port false
# Opprett geo-replikering
az redis server-link create \
--name "redis-ai-norwayeast" \
--resource-group "rg-ai-prod" \
--server-to-link "/subscriptions/{sub}/resourceGroups/rg-ai-dr/providers/Microsoft.Cache/Redis/redis-ai-swedencentral" \
--replication-role Secondary
```
## Sesjonsstilstandsreplikering og synkronisering
### Session Affinity vs. Shared State
| Tilnærming | Fordel | Ulempe | Anbefalt for |
|-----------|--------|--------|-------------|
| Session affinity (sticky) | Enkel, ingen replikering | Session tapt ved node-feil | Dev/test |
| Shared state (Redis) | Rask failover | Replikeringsforsinkelse | Produksjon |
| Shared state (Cosmos DB) | Global replikering | Høyere latens enn Redis | Multi-region |
| Stateless (JWT) | Ingen server-state | Begrenset datamengde | API-first design |
### Session migration under failover
```csharp
// C# Session migration strategy
public class ResilientSessionStore : ISessionStore
{
private readonly IDistributedCache _primaryCache;
private readonly IDistributedCache _secondaryCache;
private readonly CosmosClient _cosmosClient;
private bool _usingPrimary = true;
public async Task<SessionData?> GetSessionAsync(string sessionId)
{
var cache = _usingPrimary ? _primaryCache : _secondaryCache;
try
{
var data = await cache.GetStringAsync(sessionId);
if (data != null)
return JsonSerializer.Deserialize<SessionData>(data);
}
catch (RedisConnectionException)
{
// Redis failover
_usingPrimary = !_usingPrimary;
cache = _usingPrimary ? _primaryCache : _secondaryCache;
try
{
var data = await cache.GetStringAsync(sessionId);
if (data != null)
return JsonSerializer.Deserialize<SessionData>(data);
}
catch
{
// Begge Redis nede — fall tilbake til Cosmos DB
}
}
// Fallback: hent fra Cosmos DB (persistent store)
return await GetFromCosmosAsync(sessionId);
}
public async Task SaveSessionAsync(string sessionId, SessionData data)
{
// Skriv til Redis OG Cosmos DB (write-through)
var json = JsonSerializer.Serialize(data);
var options = new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(24)
};
// Redis (rask, men kan feile)
try
{
var cache = _usingPrimary ? _primaryCache : _secondaryCache;
await cache.SetStringAsync(sessionId, json, options);
}
catch { /* Redis-feil er ikke kritisk */ }
// Cosmos DB (persistent, geo-replikert)
await SaveToCosmosAsync(sessionId, data);
}
}
```
## Håndtering av in-flight requests under failover
### Request draining
```python
# Graceful request draining under failover
import asyncio
from contextlib import asynccontextmanager
class GracefulFailoverManager:
"""Manage in-flight requests during failover."""
def __init__(self, drain_timeout_seconds=30):
self.drain_timeout = drain_timeout_seconds
self.active_requests = 0
self.accepting_requests = True
self._lock = asyncio.Lock()
@asynccontextmanager
async def track_request(self):
"""Context manager to track active requests."""
async with self._lock:
if not self.accepting_requests:
raise ServiceUnavailableError(
"Service is draining for failover. "
"Please retry against the new endpoint."
)
self.active_requests += 1
try:
yield
finally:
async with self._lock:
self.active_requests -= 1
async def initiate_drain(self):
"""Stop accepting new requests and wait for in-flight to complete."""
async with self._lock:
self.accepting_requests = False
# Vent på at aktive requests fullføres
start = asyncio.get_event_loop().time()
while self.active_requests > 0:
elapsed = asyncio.get_event_loop().time() - start
if elapsed > self.drain_timeout:
print(f"Drain timeout! {self.active_requests} requests still active")
break
await asyncio.sleep(0.5)
return self.active_requests == 0
# Bruk i applikasjon
failover_mgr = GracefulFailoverManager(drain_timeout_seconds=30)
async def handle_chat_request(request):
async with failover_mgr.track_request():
response = await process_ai_request(request)
return response
```
## Idempotens og request retry-strategier
### Idempotent design for AI-operasjoner
```python
# Idempotent AI operations with deduplication
import hashlib
import json
class IdempotentAIService:
"""Ensure AI operations are idempotent using request IDs."""
def __init__(self, state_store, cache_ttl_seconds=3600):
self.state_store = state_store
self.cache_ttl = cache_ttl_seconds
def generate_idempotency_key(self, operation: str, params: dict) -> str:
"""Generate deterministic key for deduplication."""
canonical = json.dumps(params, sort_keys=True)
return hashlib.sha256(f"{operation}:{canonical}".encode()).hexdigest()
async def execute_idempotent(
self, operation: str, params: dict, execute_fn
):
"""Execute operation with idempotency guarantee."""
key = self.generate_idempotency_key(operation, params)
# Sjekk om operasjonen allerede er utført
existing = await self.state_store.get(f"idempotent:{key}")
if existing:
return json.loads(existing) # Returner cached resultat
# Utfør operasjonen
result = await execute_fn(params)
# Lagre resultat for deduplisering
await self.state_store.set(
f"idempotent:{key}",
json.dumps(result),
ttl=self.cache_ttl
)
return result
# Eksempel: Idempotent embedding-generering
service = IdempotentAIService(redis_store)
async def generate_embedding(text):
return await service.execute_idempotent(
operation="embed",
params={"text": text, "model": "text-embedding-3-large"},
execute_fn=lambda p: openai_client.embeddings.create(
input=p["text"], model=p["model"]
)
)
```
### Retry-strategi med idempotens
| Operasjonstype | Idempotent? | Retry-strategi | Max retries |
|---------------|-------------|---------------|-------------|
| Chat completion | Ja (med seed) | Exponential backoff | 3 |
| Embedding generation | Ja (deterministisk) | Fast retry | 3 |
| Search query | Ja (read-only) | Fast retry | 5 |
| Index update | Ja (upsert) | Exponential backoff | 3 |
| Fine-tuning start | Nei | Ingen retry | 0 |
| Conversation save | Conditional (ETag) | Exponential backoff | 3 |
## State validering og verifikasjonsprosedyrer
### Post-failover validering
```python
# Post-failover state validation checklist
async def validate_state_after_failover(primary_region, dr_region):
"""Validate state consistency after failover."""
results = {}
# 1. Verifiser session state
sample_sessions = await get_recent_sessions(limit=100)
session_ok = 0
for session in sample_sessions:
dr_session = await dr_state_store.get_session(session["id"])
if dr_session and dr_session["version"] >= session["version"] - 1:
session_ok += 1
results["sessions"] = {
"total": len(sample_sessions),
"consistent": session_ok,
"pct": round(session_ok / max(len(sample_sessions), 1) * 100, 1)
}
# 2. Verifiser conversation state
sample_convs = await get_recent_conversations(limit=50)
conv_ok = 0
for conv in sample_convs:
dr_conv = await dr_state_store.get_conversation(conv["id"])
if dr_conv and len(dr_conv["turns"]) >= len(conv["turns"]) - 1:
conv_ok += 1
results["conversations"] = {
"total": len(sample_convs),
"consistent": conv_ok,
"pct": round(conv_ok / max(len(sample_convs), 1) * 100, 1)
}
# 3. Verifiser configuration state
primary_config = await get_app_configuration(primary_region)
dr_config = await get_app_configuration(dr_region)
config_match = primary_config == dr_config
results["configuration"] = {"consistent": config_match}
# 4. Samlet vurdering
all_ok = (
results["sessions"]["pct"] > 95 and
results["conversations"]["pct"] > 95 and
results["configuration"]["consistent"]
)
results["overall"] = "PASS" if all_ok else "FAIL"
return results
```
## Referanser
- [Recommendations for handling transient faults](https://learn.microsoft.com/en-us/azure/well-architected/design-guides/handle-transient-faults) — Retry og idempotens
- [Retry pattern](https://learn.microsoft.com/en-us/azure/architecture/patterns/retry) — Retry-mønster
- [Designing Azure Functions for identical input](https://learn.microsoft.com/en-us/azure/azure-functions/functions-idempotent) — Idempotent design
- [Compensating Transaction pattern](https://learn.microsoft.com/en-us/azure/architecture/patterns/compensating-transaction) — Kompenserende transaksjoner
- [Azure Cosmos DB consistency levels](https://learn.microsoft.com/en-us/azure/cosmos-db/consistency-levels) — Konsistensmodeller
- [Azure Cache for Redis geo-replication](https://learn.microsoft.com/en-us/azure/azure-cache-for-redis/cache-how-to-geo-replication) — Redis geo-replikering
## For Cosmo
- **Bruk denne referansen** når kunden trenger hjelp med state management under failover for AI-applikasjoner.
- Anbefal alltid write-through til Cosmos DB selv om Redis brukes som primær session store — Redis-data kan gå tapt ved failover.
- Idempotens er OBLIGATORISK for alle AI-operasjoner som kan retries — bruk request IDs og conditional updates.
- For konversasjonshistorikk: Bruk append-only mønster med unik turnId for å unngå duplikater ved retry.
- Graceful request draining bør implementeres i alle produksjonsapplikasjoner — brå terminering av in-flight requests gir dårlig brukeropplevelse.

View file

@ -0,0 +1,385 @@
# Azure Arc for AI Management
**Last updated:** 2026-02
**Status:** GA
**Category:** Hybrid Cloud & Edge AI
---
## Introduksjon
Azure Arc er Microsofts svar på utfordringen med å administrere AI-arbeidsbelastninger på tvers av hybride og multicloud-miljøer. For norsk offentlig sektor, der data kan befinne seg i egne datasentre, på Azure Local-klynger eller hos tredjeparts skyleverandorer, gir Arc en sentralisert kontrollflate som gjor det mulig å behandle alle Kubernetes-klynger som forsteklasses Azure-ressurser.
Med Azure Arc-enabled Kubernetes kan organisasjoner koble sammen klynger som kjorer lokalt, i Azure eller hos andre skyleverandorer, og administrere dem fra Azure Portal med ensartede policyer, overvaking og sikkerhetskontroller. Dette er spesielt verdifullt for AI-arbeidsbelastninger som krever GPU-akselerasjon, modellversjonering og sentralisert governance.
For offentlige virksomheter i Norge betyr dette at man kan overholde krav til datasuverenitet og plassering av data, samtidig som man drar nytte av Azures ML-plattform for trening og inferens pa tvers av distribuerte miljoer.
---
## Arkitekturoversikt
Azure Arc for AI Management bygger pa tre lag:
```
┌─────────────────────────────────────────────────┐
│ Azure Control Plane │
│ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │
│ │ Azure │ │ Azure │ │ Azure Machine │ │
│ │ Policy │ │ Monitor │ │ Learning │ │
│ └────┬─────┘ └────┬─────┘ └───────┬──────────┘ │
│ │ │ │ │
│ └─────────────┼───────────────┘ │
│ │ Azure Arc │
└─────────────────────┼───────────────────────────-┘
┌─────────────┼──────────────┐
│ │ │
┌────▼───┐ ┌────▼───┐ ┌─────▼──────┐
│ On-prem│ │ Azure │ │ Multi-cloud│
│ K8s │ │ AKS │ │ K8s │
│ Cluster│ │ Cluster│ │ Cluster │
└────────┘ └────────┘ └────────────┘
```
---
## Arc-enabled Kubernetes for AI
### Tilkobling av klynger
Azure Arc-enabled Kubernetes lar deg koble eksisterende Kubernetes-klynger til Azure for sentralisert administrasjon. Klynger kan kjore pa:
| Plattform | Stotte | Beskrivelse |
|-----------|--------|-------------|
| AKS i Azure | Innebygd | Fullt administrert Kubernetes i skyen |
| AKS pa Azure Local | GA | Kubernetes pa egne servere med Azure-integrasjon |
| Arc-enabled K8s (on-prem) | GA | Alle CNCF-sertifiserte klynger lokalt |
| Arc-enabled K8s (multicloud) | GA | AWS EKS, Google GKE, etc. |
| Edge-enheter | GA | Azure Stack Edge, IoT Edge-enheter |
### Tilkobling med Azure CLI
```bash
# Koble en on-premises klynge til Azure Arc
az connectedk8s connect \
--name my-ai-cluster \
--resource-group ai-rg \
--location norwayeast
# Verifiser tilkobling
az connectedk8s show \
--name my-ai-cluster \
--resource-group ai-rg
```
### Arc-agenter
Nar en klynge kobles til Arc, installeres flere agenter:
| Agent | Funksjon |
|-------|----------|
| `clusteridentityoperator` | Administrerer managed identity for klyngen |
| `clusterconnectoperator` | Hndterer klynge-tilkobling |
| `configoperator` | Overvaker konfigurasjonsendringer |
| `controlleroperator` | Orkestrerer andre agenter |
| `fluxoperator` | GitOps-basert konfigurasjonsadministrasjon |
| `extensionoperator` | Installerer og administrerer klynge-extensions |
---
## Sentralisert ML-modellforvaltning
### Azure Machine Learning Extension
Azure Machine Learning-extensionen er kjernen i AI-forvaltning pa Arc-enabled klynger. Den lar deg bruke Arc-klynger som compute targets for bade trening og inferens.
**Installasjon:**
```bash
# Installer ML-extension pa Arc-enabled klynge
az k8s-extension create \
--name aml-extension \
--extension-type Microsoft.AzureML.Kubernetes \
--cluster-type connectedClusters \
--cluster-name my-ai-cluster \
--resource-group ai-rg \
--scope cluster \
--configuration-settings \
enableTraining=True \
enableInference=True \
inferenceRouterServiceType=LoadBalancer \
allowInsecureConnections=True
```
**Koble til ML workspace:**
```bash
# Attach klynge til Azure ML workspace
az ml compute attach \
--resource-group ai-workspace-rg \
--workspace-name ai-workspace \
--type Kubernetes \
--name arc-compute \
--resource-id "/subscriptions/<sub>/resourceGroups/ai-rg/providers/Microsoft.Kubernetes/connectedClusters/my-ai-cluster" \
--namespace ai-workloads
```
### Bruksmonster for Kubernetes Compute
| Monster | Data | Trening | Inferens | Bruksomrade |
|---------|------|---------|----------|-------------|
| Sky-forst | Sky | Azure | Azure | Standard ML-pipeline |
| Hybrid trening | Lokalt | Lokalt | Sky | Datasuverenitiet, global tilgang |
| Hybrid inferens | Sky | Sky | Lokalt | Latens, compliance |
| Full lokal | Lokalt | Lokalt | Lokalt | Strengt regulert |
| Multi-sky | Distribuert | Begge | Begge | Elastisitet + kontroll |
### KAITO - Kubernetes AI Toolchain Operator
KAITO forenkler deployment av open-source LLM-er pa Arc-enabled Kubernetes:
```yaml
# workspace-phi4.yaml - Deploy Phi-4-mini pa Arc-klynge
apiVersion: kaito.sh/v1alpha1
kind: Workspace
metadata:
name: workspace-phi-4-mini
spec:
resource:
instanceType: Standard_NC4_A2
labelSelector:
matchLabels:
apps: llm-inference
inference:
preset:
name: phi-4-mini-instruct
```
**Stottede GPU-modeller for KAITO pa Azure Local:**
| GPU | VM SKU | Stottede modeller |
|-----|--------|-------------------|
| NVIDIA T4 | Standard_NK6 | Phi-3-mini-4k |
| NVIDIA A2 | Standard_NC4, NC8 | Phi-3-mini, Phi-3.5-mini |
| NVIDIA A16 | Standard_NC16, NC32 | Phi-4-mini, Mistral-7B, Qwen2.5 |
---
## Policy og Compliance Enforcement
### Azure Policy for Kubernetes
Azure Policy kan handheve governance-regler pa tvers av alle Arc-enabled klynger. For AI-arbeidsbelastninger er dette kritisk for a sikre:
- Konsistente sikkerhetsinnstillinger pa tvers av klynger
- Modell-deployment kun til godkjente noder
- Overholdelse av dataklassifisering og suverenitetskrav
- Standardiserte konfigurasjoner for GPU-ressurser
**Installasjon av Policy-extension:**
```bash
# Installer Azure Policy pa Arc-klynge
az k8s-extension create \
--cluster-type connectedClusters \
--cluster-name my-ai-cluster \
--resource-group ai-rg \
--extension-type Microsoft.PolicyInsights \
--name azurepolicy
```
### Innebygde policyer for AI-governance
| Policy | Kategori | Effekt |
|--------|----------|--------|
| Kubernetes-klynger bor ikke tillate privilegerte containere | Sikkerhet | Deny |
| Kubernetes-klynger bor bruke interne lastbalanserere | Nettverk | Deny |
| Kubernetes-klynger bor ha Azure Policy-addon | Compliance | Audit |
| Kubernetes-klynger bor kun bruke godkjente container images | Supply chain | Deny |
| Kubernetes-klynger bor ha resursgrenser | Ressurs | Audit |
### Tilpassede policyer for AI
```json
{
"if": {
"allOf": [
{
"field": "type",
"equals": "Microsoft.Kubernetes/connectedClusters"
},
{
"field": "tags['ai-workload']",
"exists": true
}
]
},
"then": {
"effect": "auditIfNotExists",
"details": {
"type": "Microsoft.KubernetesConfiguration/extensions",
"existenceCondition": {
"field": "Microsoft.KubernetesConfiguration/extensions/extensionType",
"equals": "Microsoft.AzureML.Kubernetes"
}
}
}
}
```
---
## Multi-cluster AI Governance
### Azure Kubernetes Fleet Manager
For organisasjoner med mange AI-klynger gir Fleet Manager sentralisert koordinering:
| Funksjon | Beskrivelse |
|----------|-------------|
| Cluster grouping | Grupper klynger etter formål (trening, inferens, edge) |
| Update orchestration | Koordinerte oppdateringer pa tvers av klynger |
| Configuration propagation | Distribuer GitOps-konfigurasjoner til mange klynger |
| Multi-cluster networking | Service discovery pa tvers av klynger |
### GitOps-basert AI-modell-distribusjon
Bruk Flux v2 for a distribuere AI-modeller og konfigurasjoner:
```bash
# Konfigurer GitOps med Flux v2 for modell-deployment
az k8s-configuration flux create \
--name ai-model-config \
--cluster-name my-ai-cluster \
--resource-group ai-rg \
--cluster-type connectedClusters \
--namespace ai-models \
--scope namespace \
--url https://github.com/org/ai-model-configs \
--branch main \
--kustomization name=models path=./models prune=true
```
### Overvaking med Azure Monitor
```bash
# Aktiver Container Insights pa Arc-klynge
az k8s-extension create \
--name azuremonitor-containers \
--cluster-name my-ai-cluster \
--resource-group ai-rg \
--cluster-type connectedClusters \
--extension-type Microsoft.AzureMonitor.Containers
```
**Viktige metrikker a overvake for AI-klynger:**
| Metrikk | Beskrivelse | Terskel |
|---------|-------------|---------|
| GPU-utnyttelse | Prosent GPU-bruk per node | >80% = skaler opp |
| GPU-minne | VRAM-forbruk | >90% = advarsel |
| Inferens-latens | P95 responstid | <500ms for real-time |
| Modell-versjon | Aktiv modellversjon | Match med registeret |
| Pod-restarts | Antall omstarter | >3 = undersok |
---
## Sikkerhetsarkitektur for Arc AI
### Defense in Depth
```
┌────────────────────────────────────────┐
│ 1. Azure RBAC │
│ ┌──────────────────────────────────┐ │
│ │ 2. Kubernetes RBAC │ │
│ │ ┌────────────────────────────┐ │ │
│ │ │ 3. Network Policy │ │ │
│ │ │ ┌──────────────────────┐ │ │ │
│ │ │ │ 4. Pod Security │ │ │ │
│ │ │ │ ┌────────────────┐ │ │ │ │
│ │ │ │ │ 5. Container │ │ │ │ │
│ │ │ │ │ Security │ │ │ │ │
│ │ │ │ └────────────────┘ │ │ │ │
│ │ │ └──────────────────────┘ │ │ │
│ │ └────────────────────────────┘ │ │
│ └──────────────────────────────────┘ │
└────────────────────────────────────────┘
```
### Microsoft Defender for Kubernetes
Defender gir trusselbeskyttelse for alle Arc-enabled klynger:
- Runtime-trusselbeskyttelse
- Sarbarhetsskanning av container images
- Sikkerhetskonfigurasjonskontroller
- Integrasjon med Microsoft Sentinel for SIEM
### Hemmelighetshaandtering
```bash
# Installer Azure Key Vault Secrets Provider
az k8s-extension create \
--cluster-type connectedClusters \
--cluster-name my-ai-cluster \
--resource-group ai-rg \
--extension-type Microsoft.AzureKeyVaultSecretsProvider \
--name akvsecretsprovider
```
---
## Relevans for norsk offentlig sektor
### Datasuverenitetshensyn
| Krav | Arc-losning |
|------|-------------|
| Data ma forbli i Norge | On-prem klynge med Arc management |
| Sentralisert policy | Azure Policy handheves fra Norway East |
| Auditlog | Azure Monitor med lokal lagring |
| Kryptering | Key Vault med CMK i Norway East |
| Tilgangskontroll | Azure RBAC + Kubernetes RBAC |
### Anbefalte Azure-regioner
| Region | Bruk | Data residency |
|--------|------|----------------|
| Norway East | Primaer kontrollflate | Norge |
| Norway West | DR/backup | Norge |
| West Europe | Fallback, utvidede tjenester | EU/EFTA |
### NSM-krav og Arc
Nasjonal sikkerhetsmyndighet (NSM) sine grunnprinsipper for IKT-sikkerhet kan mappes mot Arc-kapabiliteter:
| NSM-prinsipp | Arc-kontroll |
|--------------|--------------|
| Kartlegg enheter og programvare | Arc inventory og tagging |
| Ha kontroll pa nettverk og systemer | Azure Policy, Network Policy |
| Beskytt data | Kryptering, Key Vault |
| Overlapp/overvak | Azure Monitor, Defender |
| Styring og kontroll | RBAC, governance hierarki |
---
## Begrensninger og hensyn
| Begrensning | Beskrivelse | Workaround |
|-------------|-------------|------------|
| Outbound connectivity | Arc krever utgaende HTTPS | Proxy-stotte tilgjengelig |
| Extension-kompatibilitet | Ikke alle extensions stotter alle distribusjoner | Sjekk kompatibilitetsmatrise |
| GPU-stotte | KAITO pa Arc kun for Azure Local (preview) | Bruk Azure ML extension for andre |
| Skalering | Ingen auto-scaling for Kubernetes compute i ML | Manuell skalering |
| Modellkatalog | Model Catalog ikke stottet pa Kubernetes endpoints | Bruk custom modeller |
---
## For Cosmo
- **Azure Arc er brokken mellom lokale AI-klynger og Azures skybaserte administrasjon** — alle Kubernetes-klynger blir forsteklasses Azure-ressurser med policy, overvaking og ML-integrasjon.
- **KAITO (Kubernetes AI Toolchain Operator) forenkler LLM-deployment** pa Arc-enabled klynger, spesielt pa Azure Local med GPU-stotte for Phi-4, Mistral og Qwen-modeller.
- **Azure Policy for Kubernetes handhever governance pa tvers av alle klynger** — fra on-prem til multicloud — med innebygde og tilpassede policyer for AI-arbeidsbelastninger.
- **For norsk offentlig sektor er Arc losningen for "data forblir lokalt, styring fra skyen"** — kontrollflaten i Norway East, data pa egne servere, med full auditlog og kryptering.
- **Multi-cluster governance med Fleet Manager og GitOps** gir skalerbar, deklarativ modell- og konfigurasjonsstyring for distribuerte AI-miljoer.

View file

@ -0,0 +1,366 @@
# Azure Confidential Computing for AI
**Last updated:** 2026-02
**Status:** GA
**Category:** Hybrid Cloud & Edge AI
---
## Introduksjon
Azure Confidential Computing (ACC) beskytter data under prosessering (data-in-use) ved hjelp av hardware-baserte Trusted Execution Environments (TEE). For AI-arbeidsbelastninger betyr dette at modeller og inferensdata kan beskyttes mot uautorisert tilgang — inkludert fra skyoperatoren selv. Dette er en gamechanger for organisasjoner som prosesserer sensitive data med AI.
For norsk offentlig sektor losner ACC en fundamental utfordring: hvordan bruke sky-basert AI-kraftig hardware (GPU-er, akseleratorer) for sensitive data uten a kompromittere datasikkerheten. NSM Grunnprinsipper og Schrems II-krav kan ivaretas ved at data aldri eksisteres i klartekst utenfor TEE — selv Microsoft som skyoperator kan ikke se dataene.
Microsoft tilbyr flere ACC-tilbud for AI: Confidential VMs basert pa AMD SEV-SNP for CPU-baserte arbeidsbelastninger, Confidential GPU VMs med NVIDIA H100 for GPU-akselerert AI, Confidential Containers pa ACI og AKS, og Azure Attestation for verifisering av TEE-integritet.
---
## Kjernekomponenter
| Komponent | Formal | Teknologi |
|-----------|--------|-----------|
| AMD SEV-SNP | Minnesikring for hele VM | CPU-basert TEE |
| Intel TDX | Trust Domain Extensions for VM-isolasjon | CPU-basert TEE (Preview) |
| Intel SGX | Application-level enclaves | Enclave-basert TEE |
| NVIDIA H100 TEE | GPU-basert confidential computing | Confidential GPU VM |
| Azure Attestation | Verifisering av TEE-tilstand | PaaS-tjeneste |
| Confidential VMs | Kryptert VM-minne | DCasv5, ECasv5, NCCadsH100v5 |
| Confidential Containers | Container-isolasjon i TEE | ACI, AKS |
| Azure Key Vault mHSM | Nokkelhandtering i HSM | FIPS 140-2 Level 3 |
---
## TEE-Enabled Model Execution
### Confidential VM for AI-inferens
```bash
# Opprett Confidential VM for AI-arbeidslast (AMD SEV-SNP)
az vm create \
--resource-group rg-confidential-ai \
--name vm-confidential-inference \
--image "Canonical:0001-com-ubuntu-confidential-vm-jammy:22_04-lts-cvm:latest" \
--size Standard_DC4as_v5 \
--security-type ConfidentialVM \
--os-disk-security-encryption-type VMGuestStateOnly \
--enable-vtpm true \
--enable-secure-boot true \
--admin-username azureuser \
--generate-ssh-keys
# Installer AI runtime
az vm run-command invoke \
--resource-group rg-confidential-ai \
--name vm-confidential-inference \
--command-id RunShellScript \
--scripts "
pip install onnxruntime torch transformers
# Modell og data er kryptert i minnet av AMD SEV-SNP
"
```
### Confidential GPU VM for AI (NVIDIA H100)
```bash
# Opprett Confidential GPU VM med NVIDIA H100 TEE
az vm create \
--resource-group rg-confidential-ai \
--name vm-confidential-gpu \
--image "microsoft-dsvm:ubuntu-hpc:2204:latest" \
--size Standard_NCCads_H100_v5 \
--security-type ConfidentialVM \
--os-disk-security-encryption-type DiskWithVMGuestState \
--enable-vtpm true \
--admin-username azureuser \
--generate-ssh-keys
```
### Linked CPU-GPU TEE-arkitektur
```
┌─────────────────────────────────────────┐
│ Confidential GPU VM │
│ │
│ ┌──────────────┐ ┌────────────────┐ │
│ │ CPU TEE │ │ GPU TEE │ │
│ │ (AMD SNP) │←→│ (NVIDIA H100) │ │
│ │ │ │ │ │
│ │ - Datainntak │ │ - Inferens │ │
│ │ - Pre/post │ │ - Training │ │
│ │ - Orkestrering│ │ - Tensor ops │ │
│ └──────────────┘ └────────────────┘ │
│ ↑ ↑ │
│ Kryptert minne Kryptert VRAM │
│ (aldri i klartekst utenfor TEE) │
└─────────────────────────────────────────┘
```
---
## Encrypted Inference Pipelines
### End-to-end kryptert inferens
```python
# Confidential inferens med attestation-basert nokkelfrigivelse
from azure.identity import DefaultAzureCredential
from azure.keyvault.keys import KeyClient
from azure.attestation import AttestationClient
import onnxruntime as ort
class ConfidentialInferencePipeline:
def __init__(self):
self.credential = DefaultAzureCredential()
self.attestation_client = AttestationClient(
endpoint="https://sharedeus.eus.attest.azure.net",
credential=self.credential
)
async def run_confidential_inference(self, encrypted_input: bytes) -> bytes:
"""Kjor inferens med end-to-end kryptering"""
# Steg 1: Generer TEE-attestasjonsrapport
attestation_report = self._generate_attestation()
# Steg 2: Hent dekrypteringsnokkel via Secure Key Release (SKR)
decryption_key = await self._secure_key_release(attestation_report)
# Steg 3: Dekrypter input innenfor TEE
# (Data er kun i klartekst innenfor TEE-minnet)
plaintext_input = self._decrypt_in_tee(encrypted_input, decryption_key)
# Steg 4: Kjor inferens
result = self._run_model(plaintext_input)
# Steg 5: Krypter output for returnerning
encrypted_output = self._encrypt_in_tee(result, decryption_key)
return encrypted_output
def _generate_attestation(self) -> dict:
"""Generer hardware-attestasjonsrapport fra AMD SEV-SNP"""
# Hent SNP attestation report fra /dev/sev-guest
# Rapporten inkluderer:
# - Platform-versjon og firmware
# - VM measurement (hash av VM-konfigurasjon)
# - Runtime measurement
import subprocess
report = subprocess.run(
["snp-report", "--format", "json"],
capture_output=True, text=True
)
return {
"snp_report": report.stdout,
"runtime_data": self._get_runtime_claims()
}
async def _secure_key_release(self, attestation: dict) -> bytes:
"""Frigivelse av nokkel basert pa attestasjon"""
# Azure Attestation verifiserer TEE-tilstand
result = self.attestation_client.attest_snp_vm(
report=attestation["snp_report"],
runtime_data=attestation["runtime_data"]
)
# Kun hvis attestasjon er gyldig, frigir Key Vault nokkelen
key_client = KeyClient(
vault_url="https://myvault.vault.azure.net",
credential=self.credential
)
return key_client.release_key(
name="inference-key",
target_attestation_token=result.token
)
```
### Confidential Containers for AI
```yaml
# Confidential container deployment pa Azure Container Instances
# Container-gruppen kjorer i AMD SEV-SNP TEE
apiVersion: 2023-05-01
name: confidential-inference
location: norwayeast
properties:
confidentialComputeProperties:
ccePolicy: "<base64-encoded-rego-policy>"
containers:
- name: inference-engine
properties:
image: myregistry.azurecr.io/confidential-inference:v1
resources:
requests:
cpu: 4
memoryInGB: 16
environmentVariables:
- name: MODEL_PATH
value: /models/encrypted_model.enc
- name: ATTESTATION_ENDPOINT
value: "https://sharedneu.neu.attest.azure.net"
volumeMounts:
- name: model-volume
mountPath: /models
osType: Linux
sku: Confidential
volumes:
- name: model-volume
azureFile:
shareName: encrypted-models
storageAccountName: mystorageaccount
```
---
## Attestation for Compliance
### Azure Attestation-flyten
```
┌──────────┐ ┌───────────────┐ ┌──────────────┐
│ TEE/CVM │────→│ Azure │────→│ Relying │
│ │ │ Attestation │ │ Party │
│ Generer │ │ │ │ │
│ Evidence │ │ Verifiser │ │ Frigir data/ │
│ │ │ Evaluer policy│ │ noekler │
└──────────┘ └───────────────┘ └──────────────┘
```
### Attestasjonspolicy for AI-arbeidsbelastninger
```json
// SKR-policy for Confidential AI VM
{
"version": "1.0.0",
"anyOf": [
{
"authority": "https://sharedneu.neu.attest.azure.net",
"allOf": [
{
"claim": "x-ms-compliance-status",
"equals": "azure-compliant-cvm"
},
{
"claim": "x-ms-sevsnpvm-is-debuggable",
"equals": "false"
},
{
"claim": "x-ms-sevsnpvm-vmpl",
"equals": "0"
},
{
"claim": "x-ms-isolation-tee.x-ms-attestation-type",
"equals": "sevsnpvm"
}
]
}
]
}
```
### Compliance-rapportering
```python
# Generer compliance-rapport for confidential AI
class ConfidentialAIComplianceReport:
def generate_report(self) -> dict:
return {
"platform": {
"type": "Azure Confidential VM",
"tee": "AMD SEV-SNP",
"firmware_version": self._get_firmware_version(),
"attestation_status": "verified"
},
"data_protection": {
"encryption_at_rest": "AES-256 (Customer-managed key)",
"encryption_in_transit": "TLS 1.3",
"encryption_in_use": "AMD SEV-SNP memory encryption",
"key_management": "Azure Key Vault Managed HSM"
},
"access_control": {
"operator_access": "Denied (TEE-enforced)",
"attestation_required": True,
"secure_key_release": True
},
"compliance_frameworks": [
"GDPR Art. 32 (data-in-use protection)",
"Schrems II (operator cannot access data)",
"NSM Grunnprinsipper (kryptering ved bruk)",
"ISO 27001 A.10 (cryptographic controls)"
],
"audit_trail": {
"attestation_logs": "Azure Monitor",
"key_release_logs": "Key Vault audit log",
"inference_metadata": "Application Insights"
}
}
```
---
## Performance Trade-offs
### Ytelsespavirkning av Confidential Computing
| Arbeidsbelastning | Uten CC | Med CC (CPU TEE) | Med CC (GPU TEE) | Overhead |
|-------------------|---------|-------------------|-------------------|----------|
| ONNX inferens (CPU) | 10 ms | 11-12 ms | N/A | 10-20% |
| PyTorch inferens (GPU) | 5 ms | N/A | 5.5-6 ms | 10-20% |
| LLM generering (GPU) | 30 tok/s | N/A | 25-28 tok/s | 7-17% |
| Embedding-generering | 50 ms/batch | 55-60 ms/batch | 52-55 ms/batch | 4-20% |
| Modell-lasting | 5 s | 7-8 s | 6-7 s | 20-40% |
### Optimalisering for lavere overhead
| Optimalisering | Beskrivelse | Forventet forbedring |
|----------------|-------------|---------------------|
| Batching | Samle flere inferensforesp. | Amortiser TEE-overhead |
| Model caching | Hold modell i TEE-minne | Unnga re-dekryptering |
| Forhands-attestasjon | Cache attestasjonstoken | Reduser latens per kall |
| NUMA-optimalisering | Pin til korrekt NUMA-node | Bedre minneytelse |
| Hugepage-allokering | Bruk store minnesider | Reduser TLB-misser |
---
## Norsk offentlig sektor
### Hvorfor Confidential Computing for norsk offentlig AI?
- **Schrems II-kompatibilitet**: Data er kryptert under prosessering — selv Microsoft kan ikke se innholdet, noe som adresserer europeisk personvernlovgivning
- **NSM-krav**: Grunnprinsipper for IKT-sikkerhet krever kryptering, og CC utvider dette til data-in-use
- **Flerpartisanalyse**: Kommuner og etater kan analysere data sammen uten a eksponere radata for hverandre
- **AI pa sensitive data**: Helsedata (pasientjournaler), persondata (NAV), og justisdata kan prosesseres med AI uten a kompromittere personvern
### Bruksscenarier
| Etat | Scenario | CC-komponent |
|------|----------|--------------|
| NAV | AI-analyse av soknader med persondata | Confidential VM |
| Helsedirektoratet | Prediktiv analyse pa helsejournaler | Confidential GPU VM |
| Politiet | Biometrisk matching | SGX enclaves |
| Skatteetaten | Fraud detection pa skattedata | Confidential Containers |
| Kommuner | Flerparts-analyse av velferdsdata | Confidential ACI |
---
## Beslutningsrammeverk
| Scenario | Anbefaling | Begrunnelse |
|----------|------------|-------------|
| CPU-basert AI, enkel migrasjon | Confidential VM (SEV-SNP) | Ingen kodeendringer |
| GPU-akselerert AI, hoy ytelse | NCCadsH100v5 Confidential GPU VM | Linked CPU+GPU TEE |
| Container-baserte mikrotjenester | Confidential Containers pa ACI/AKS | Container-policy-basert sikkerhet |
| Application-level isolasjon | Intel SGX enclaves | Minst mulig TCB |
| Flerpartssanalyse | Confidential Containers + Attestation | Verifiserbar isolasjon |
| Strengeste compliance-krav | Confidential GPU + mHSM + Attestation | Full stack confidentiality |
---
## For Cosmo
- **Confidential Computing losner data-in-use-problemet for sky-AI** — anbefal Confidential VMs (AMD SEV-SNP) som forste steg for organisasjoner som noler med sky-AI pa grunn av datasikkerhetshensyn
- **Ytelsesoverhead er typisk 10-20%** — dette er akseptabelt for de fleste arbeidsbelastninger og kan optimaliseres med batching og modell-caching innenfor TEE
- **Azure Attestation + Secure Key Release er pabudt for compliance** — modeller og data bor kun dekrypteres etter vellykket attestasjon som beviser at arbeidsmiljoet er integert
- **For norsk offentlig sektor: Confidential GPU VMs (H100) er den mest lovende losningen** for a kjore avansert AI pa sensitive data i skyen — den kombinerer GPU-ytelse med TEE-beskyttelse
- **Dokumenter alltid TEE-stack, attestasjonspolicy og nokkelhandtering** i sikkerhetsarkitekturen — dette er konkret bevis for compliance i DPIA og sikkerhetsrevisjoner

View file

@ -0,0 +1,451 @@
# Azure IoT Hub and AI Pipeline
**Last updated:** 2026-02
**Status:** GA
**Category:** Hybrid Cloud & Edge AI
---
## Introduksjon
Azure IoT Hub er Microsofts sentrale PaaS-tjeneste for toveiskommunikasjon mellom IoT-enheter og skyen. Kombinert med Azure Stream Analytics for sanntidsanalyse og Azure Machine Learning for modelltrening og -scoring, danner IoT Hub kjernen i en enhetlig AI-pipeline fra enhet til innsikt.
For norsk offentlig sektor er denne arkitekturen relevant for scenarioer som smart veginfrastruktur (sanntidsmaling av trafikk og veiforhold), bygg-automatisering (energistyring i offentlige bygninger), miljooverkaking (luft- og vannkvalitet), og prediktiv vedlikehold av kritisk infrastruktur. IoT Hub gir sikker enhetstilkobling, mens Stream Analytics prosesserer data i sanntid, og Azure ML scorer modeller for prediktive innsikter.
Arkitekturen skalerer fra hundrevis til millioner av enheter, med innebygd stoette for meldingsruting, device twins for konfigurasjonstyring, og enkel integrasjon med Azure-dataplatformen (Fabric, Event Hub, Cosmos DB) for langsiktig analyse.
---
## Kjernekomponenter
| Komponent | Formal | Teknologi |
|-----------|--------|-----------|
| Azure IoT Hub | Sentral enhetskommunikasjon og -styring | PaaS |
| Azure Stream Analytics | Sanntids stromprosessering | SQL-basert |
| Azure Machine Learning | Modelltrening og online scoring | ML Platform |
| Event Hub | Hoyvolum meldingsinntak | Event streaming |
| Azure Cosmos DB | Sanntids operasjonell database | NoSQL |
| Azure Data Lake / Fabric | Langsiktig dataanalyse | Analytics |
| Power BI | Sanntids dashboards | Visualisering |
| IoT Edge | Lokal prosessering pa enheter | Container runtime |
---
## Device-to-Hub Data Flow
### Arkitektur for enhet-til-sky-dataflyt
```
┌──────────┐ ┌──────────┐ ┌──────────────┐
│ Sensorer │────→│ IoT Edge │────→│ IoT Hub │
│ (MQTT) │ │ Gateway │ │ │
└──────────┘ └──────────┘ │ - Routing │
│ - Enrichment│
┌──────────┐ │ - Twin mgmt │
│ Direkte │─────────────────────→│ │
│ enheter │ └──────┬───────┘
│ (AMQP) │ │
└──────────┘ ┌─────────┼─────────┐
↓ ↓ ↓
┌──────────┐ ┌────────┐ ┌────────┐
│ Stream │ │ Event │ │ Cosmos │
│ Analytics│ │ Hub │ │ DB │
└──────────┘ └────────┘ └────────┘
↓ ↓ ↓
┌──────────┐ ┌────────┐ ┌────────┐
│ Azure ML │ │ Fabric │ │ Power │
│ Scoring │ │ │ │ BI │
└──────────┘ └────────┘ └────────┘
```
### IoT Hub meldingsruting
```json
// IoT Hub meldingsruting for AI pipeline
{
"routes": [
{
"name": "realtime-to-stream-analytics",
"source": "DeviceMessages",
"condition": "temperature > 0 OR vibration > 0",
"endpointNames": ["stream-analytics-endpoint"],
"isEnabled": true
},
{
"name": "anomalies-to-event-hub",
"source": "DeviceMessages",
"condition": "$body.alert = 'anomaly'",
"endpointNames": ["anomaly-event-hub"],
"isEnabled": true
},
{
"name": "all-data-to-storage",
"source": "DeviceMessages",
"condition": "true",
"endpointNames": ["datalake-storage"],
"isEnabled": true
},
{
"name": "device-lifecycle-to-cosmos",
"source": "DeviceLifecycleEvents",
"condition": "true",
"endpointNames": ["cosmos-db-endpoint"],
"isEnabled": true
}
]
}
```
### Enhetstilkobling med Python SDK
```python
# IoT-enhet sender sensordata til IoT Hub
from azure.iot.device import IoTHubDeviceClient, Message
import json
import time
class SensorDevice:
def __init__(self, connection_string: str):
self.client = IoTHubDeviceClient.create_from_connection_string(
connection_string
)
self.client.connect()
def send_telemetry(self, sensor_data: dict):
"""Send sensordata med metadata for ruting"""
message = Message(
json.dumps(sensor_data),
content_encoding="utf-8",
content_type="application/json"
)
# Egendefinerte properties for meldingsruting
message.custom_properties["sensorType"] = sensor_data.get("type", "unknown")
message.custom_properties["location"] = sensor_data.get("location", "unknown")
# Sett prioritet for anomalier
if sensor_data.get("alert"):
message.custom_properties["priority"] = "high"
self.client.send_message(message)
def start_continuous_telemetry(self, interval_seconds: int = 10):
"""Kontinuerlig sending av sensordata"""
while True:
data = self.read_sensors()
self.send_telemetry(data)
time.sleep(interval_seconds)
def read_sensors(self) -> dict:
"""Les sensorverdier (simulert)"""
import random
return {
"timestamp": time.time(),
"temperature": random.uniform(18.0, 25.0),
"humidity": random.uniform(30.0, 70.0),
"vibration": random.uniform(0.0, 5.0),
"type": "environment",
"location": "building-A-floor-2"
}
```
---
## Stream Processing for AI
### Azure Stream Analytics for IoT AI
```sql
-- Sanntids anomalideteksjon med Stream Analytics
-- Kombinerer sensordata med ML-scoring
-- Query 1: Glidende statistikk per enhet
WITH DeviceStats AS (
SELECT
IoTHub.ConnectionDeviceId AS DeviceId,
System.Timestamp() AS WindowEnd,
AVG(temperature) AS AvgTemp,
STDEV(temperature) AS StdTemp,
MIN(temperature) AS MinTemp,
MAX(temperature) AS MaxTemp,
COUNT(*) AS ReadingCount
FROM
IoTHubInput TIMESTAMP BY EventProcessedUtcTime
GROUP BY
IoTHub.ConnectionDeviceId,
SlidingWindow(minute, 10)
)
-- Query 2: Anomalideteksjon med statistisk terskel
SELECT
ds.DeviceId,
ds.WindowEnd,
ds.AvgTemp,
ds.StdTemp,
CASE
WHEN ds.AvgTemp > (ref.NormalAvg + 3 * ref.NormalStd) THEN 'HIGH_ANOMALY'
WHEN ds.AvgTemp > (ref.NormalAvg + 2 * ref.NormalStd) THEN 'WARNING'
WHEN ds.AvgTemp < (ref.NormalAvg - 3 * ref.NormalStd) THEN 'LOW_ANOMALY'
ELSE 'NORMAL'
END AS Status,
ref.DeviceName,
ref.Location
INTO
AnomalyOutput
FROM
DeviceStats ds
JOIN ReferenceData ref ON ds.DeviceId = ref.DeviceId
WHERE
ds.ReadingCount >= 5 -- Minst 5 malinger for palitelighet
-- Query 3: Dataaggregering for ML-trening
SELECT
IoTHub.ConnectionDeviceId AS DeviceId,
System.Timestamp() AS WindowEnd,
AVG(temperature) AS AvgTemp,
AVG(humidity) AS AvgHumidity,
AVG(vibration) AS AvgVibration,
STDEV(vibration) AS StdVibration,
MAX(vibration) AS PeakVibration,
COUNT(*) AS SampleCount
INTO
MLTrainingOutput
FROM
IoTHubInput TIMESTAMP BY EventProcessedUtcTime
GROUP BY
IoTHub.ConnectionDeviceId,
TumblingWindow(hour, 1)
```
### Stream Analytics med innebygd anomalideteksjon
```sql
-- Bruk innebygd AnomalyDetection-funksjon
SELECT
IoTHub.ConnectionDeviceId AS DeviceId,
temperature,
AnomalyDetection_SpikeAndDip(
temperature,
95, -- Konfidensniaa
120, -- Vindusstoorrelse
'spikesanddips'
) OVER (
PARTITION BY IoTHub.ConnectionDeviceId
LIMIT DURATION(minute, 120)
) AS AnomalyResult
INTO
AnomalyAlertOutput
FROM
IoTHubInput TIMESTAMP BY EventProcessedUtcTime
```
---
## Real-Time Model Scoring
### Azure ML Online Endpoint for IoT-scoring
```python
# Azure ML endpoint for sanntids IoT-scoring
from azure.ai.ml import MLClient
from azure.ai.ml.entities import (
ManagedOnlineEndpoint,
ManagedOnlineDeployment,
Model
)
from azure.identity import DefaultAzureCredential
def deploy_iot_scoring_endpoint(ml_client: MLClient):
"""Deploy sanntids scoring-endpoint for IoT-data"""
# Opprett endpoint
endpoint = ManagedOnlineEndpoint(
name="iot-anomaly-scoring",
auth_mode="key",
description="Anomalideteksjon for IoT-sensordata"
)
ml_client.online_endpoints.begin_create_or_update(endpoint).result()
# Deploy modell
deployment = ManagedOnlineDeployment(
name="anomaly-v1",
endpoint_name="iot-anomaly-scoring",
model=Model(path="./models/anomaly_model.pkl"),
code_configuration={
"code": "./scoring",
"scoring_script": "score.py"
},
instance_type="Standard_DS3_v2",
instance_count=2, # Redundans for palitelighet
environment="azureml:sklearn-1.0:1"
)
ml_client.online_deployments.begin_create_or_update(deployment).result()
```
### Scoring-script for IoT-data
```python
# score.py — Azure ML scoring-script for IoT
import json
import joblib
import numpy as np
def init():
global model
model = joblib.load("model/anomaly_model.pkl")
def run(raw_data):
"""Score IoT-sensordata mot prediktiv modell"""
data = json.loads(raw_data)
features = np.array([[
data["avg_temperature"],
data["avg_humidity"],
data["avg_vibration"],
data["std_vibration"],
data["peak_vibration"],
data["sample_count"]
]])
prediction = model.predict(features)[0]
probability = model.predict_proba(features)[0]
return json.dumps({
"device_id": data["device_id"],
"prediction": int(prediction),
"failure_probability": float(max(probability)),
"recommendation": (
"SCHEDULE_MAINTENANCE" if prediction == 1
else "NORMAL_OPERATION"
),
"scored_at": data.get("window_end")
})
```
### Stream Analytics integrert med Azure ML
```sql
-- Kall Azure ML endpoint fra Stream Analytics
WITH ScoringInput AS (
SELECT
IoTHub.ConnectionDeviceId AS device_id,
System.Timestamp() AS window_end,
AVG(temperature) AS avg_temperature,
AVG(humidity) AS avg_humidity,
AVG(vibration) AS avg_vibration,
STDEV(vibration) AS std_vibration,
MAX(vibration) AS peak_vibration,
COUNT(*) AS sample_count
FROM IoTHubInput
TIMESTAMP BY EventProcessedUtcTime
GROUP BY
IoTHub.ConnectionDeviceId,
TumblingWindow(minute, 15)
)
SELECT
si.*,
ml.prediction,
ml.failure_probability,
ml.recommendation
INTO MaintenanceOutput
FROM ScoringInput si
CROSS APPLY AzureMLEndpoint(si) AS ml
WHERE ml.failure_probability > 0.5
```
---
## Scaling Hybrid Ingestion
### Skaleringsarkitektur
| Skala | Enheter | IoT Hub SKU | Stream Analytics SU | Anbefaling |
|-------|---------|-------------|--------------------|----|
| Liten | < 1 000 | S1 (1 enhet) | 6 SU | Standard oppsett |
| Medium | 1 000 - 100 000 | S2 (2 enheter) | 12-24 SU | Partisjonering |
| Stor | 100 000 - 1M | S3 (10 enheter) | 48+ SU | Event Hub routing |
| Enterprise | > 1M | S3 + Event Hub | Dedikert klynge | Multi-hub-arkitektur |
### Hybrid skalering med edge-forbehandling
```python
# Hybrid skaleringsmonster: Edge reduserer skylast
class HybridScalingConfig:
"""Konfigurasjon for hybrid edge-sky skalering"""
@staticmethod
def calculate_cloud_load(
total_devices: int,
messages_per_device_per_hour: int,
edge_aggregation_ratio: float = 0.1 # 10% av data sendes til sky
) -> dict:
"""Beregn skylast med edge-forbehandling"""
raw_messages = total_devices * messages_per_device_per_hour
cloud_messages = int(raw_messages * edge_aggregation_ratio)
bandwidth_reduction = 1 - edge_aggregation_ratio
# IoT Hub dimensjonering
messages_per_day = cloud_messages * 24
if messages_per_day < 400_000:
iot_hub_sku = "S1 (1 enhet)"
elif messages_per_day < 6_000_000:
iot_hub_sku = "S2 (1 enhet)"
else:
units = (messages_per_day // 6_000_000) + 1
iot_hub_sku = f"S2 ({units} enheter)"
return {
"total_devices": total_devices,
"raw_messages_per_hour": raw_messages,
"cloud_messages_per_hour": cloud_messages,
"bandwidth_reduction": f"{bandwidth_reduction*100:.0f}%",
"iot_hub_sku": iot_hub_sku,
"estimated_monthly_cost_nok": cloud_messages * 24 * 30 * 0.001
}
```
---
## Norsk offentlig sektor
### Relevante bruksomrader
| Sektor | Use Case | Enheter | AI-modell |
|--------|----------|---------|-----------|
| Samferdsel | Veisensor-nettverket | ~5 000 | Trafikk-prediksjon, vintervedlikehold |
| Energi | Smart bygg-styring | ~10 000/bygg | Energi-optimalisering |
| Miljoe | Luft/vann-kvalitet | ~500 stasjoner | Forurensnings-varsling |
| Helse | Utstyrsovervaking | ~1 000/sykehus | Prediktiv vedlikehold |
| Kyst | Maritime sensorer | ~2 000 | Vaer-prediksjon, sikkerhet |
### Sikkerhetskrav
- IoT Hub-endepunkt i Norway East
- TLS 1.2+ for all enhetskommunikasjon
- X.509-sertifikater for enhetsautentisering
- DPS (Device Provisioning Service) for automatisk registrering
- NSM-kompatibel nettverkssegmentering
---
## Beslutningsrammeverk
| Scenario | Anbefaling | Begrunnelse |
|----------|------------|-------------|
| < 1 000 enheter, enkel analyse | IoT Hub S1 + Stream Analytics | Lavest kostnad og kompleksitet |
| Sanntids ML-scoring | Stream Analytics + Azure ML endpoint | Integrert ML-scoring i strom |
| Hoeyvolum med edge-forbehandling | IoT Edge + IoT Hub S2/S3 | Redusert skylast og kostnad |
| Langsiktig analyse | IoT Hub + Event Hub + Fabric | Skalerbar historisk analyse |
| Prediktiv vedlikehold | Full pipeline med retraining loop | Kontinuerlig modellforbedring |
| Anomalideteksjon | Stream Analytics innebygd anomali | Raskest a implementere |
---
## For Cosmo
- **IoT Hub + Stream Analytics + Azure ML er den kanoniske AI-pipeline for IoT** — anbefal denne treledds-arkitekturen som standard for alle IoT-AI-scenarier i offentlig sektor
- **Edge-forbehandling reduserer skylast med 90%+** — la IoT Edge aggregere og filtrere data for sensordata sendes til sky, noe som dramatisk reduserer baade kostnader og bandbreddekrav
- **Stream Analytics innebygde anomalideteksjon er raskest a implementere** — bruk AnomalyDetection_SpikeAndDip-funksjonen for rask oppstart for du bygger egne ML-modeller
- **Azure ML Online Endpoints gir sanntids scoring fra Stream Analytics** — bruk CROSS APPLY med AzureMLEndpoint-funksjonen for a integrere avansert ML direkte i strom-prosessering
- **For norsk offentlig sektor: Dimensjoner IoT Hub-kapasitet basert pa cloud-meldinger etter edge-aggregering** — med 90% edge-reduksjon kan selv store sensornettverk klare seg med S1/S2-tieren

View file

@ -0,0 +1,399 @@
# Azure Local for Edge AI Workloads
**Last updated:** 2026-02
**Status:** GA
**Category:** Hybrid Cloud & Edge AI
---
## Introduksjon
Azure Local (tidligere Azure Stack HCI) er Microsofts hyperkonvergerte infrastrukturlosning for a kjore Azure-tjenester lokalt. For AI-arbeidsbelastninger tilbyr Azure Local GPU-akselerasjon, Kubernetes-stotte via AKS enabled by Arc, og lokal tilgang til Azure-tjenester — alt administrert fra Azure Portal.
For norsk offentlig sektor representerer Azure Local en unik mulighet: organisasjoner kan plassere AI-infrastruktur i egne datarom eller hos godkjente driftspartnere, samtidig som de far tilgang til Azures ML-plattform, overvaking og governance-verktoy. Dette gir data residency i Norge uten a ga pa kompromiss med moderne AI-kapabiliteter.
Azure Local stotter et bredt utvalg av NVIDIA GPU-er for AI-inferens og trening, inkludert T4, A2, A16, L4, L40 og L40S. Sammen med AKS enabled by Arc og KAITO (Kubernetes AI Toolchain Operator) kan organisasjoner kjore open-source LLM-er som Phi-4, Mistral og Llama direkte pa egen infrastruktur.
---
## Arkitekturoversikt
```
┌───────────────────────────────────────────────┐
│ Azure Control Plane │
│ Azure Portal │ ML Workspace │ Azure Monitor │
└───────────────────────┬───────────────────────┘
│ Azure Arc
┌───────────────────────▼───────────────────────┐
│ Azure Local Cluster │
│ ┌─────────────────────────────────────────┐ │
│ │ AKS enabled by Arc │ │
│ │ ┌──────────┐ ┌──────────┐ │ │
│ │ │ CPU Node │ │ GPU Node │ │ │
│ │ │ Pool │ │ Pool │ │ │
│ │ │ │ │ NVIDIA │ │ │
│ │ │ Services │ │ T4/A2/L4 │ │ │
│ │ └──────────┘ └──────────┘ │ │
│ └─────────────────────────────────────────┘ │
│ ┌───────────────┐ ┌─────────────────────┐ │
│ │ Azure Arc VMs │ │ Storage Spaces │ │
│ │ (DDA/GPU-P) │ │ Direct (S2D) │ │
│ └───────────────┘ └─────────────────────┘ │
└───────────────────────────────────────────────┘
```
---
## GPU-stotte i Azure Local
### GPU-tilordningsmetoder
Azure Local stotter to metoder for GPU-bruk:
| Egenskap | Discrete Device Assignment (DDA) | GPU Partitioning (GPU-P) |
|----------|----------------------------------|--------------------------|
| Ressursmodell | Hel GPU til en VM | Delt GPU mellom flere VM-er |
| VM-tetthet | Lav (1 GPU = 1 VM) | Hoy (1 GPU = mange VM-er) |
| Appkompatibilitet | Full (DX12, OpenGL, CUDA) | Full (DX12, OpenGL, CUDA) |
| VRAM | Opp til full GPU VRAM | Per partisjon |
| Driver i gjest | Leverandor-driver (NVIDIA) | Leverandor-driver (NVIDIA) |
| AKS-stotte | Ja | Nei (kun VM-er) |
| Best for | AI-trening, stor inferens | Lettere inferens, delt bruk |
### Stottede NVIDIA GPU-er
| GPU-modell | DDA (Arc VMs) | DDA (AKS) | GPU-P (VMs) | Typisk bruk |
|------------|---------------|-----------|-------------|-------------|
| NVIDIA T4 | Ja | Ja | Nei | Inferens, lette modeller |
| NVIDIA A2 | Ja | Ja | Ja | Inferens, mellomstore modeller |
| NVIDIA A10 | Ja (unmanaged) | Nei | Ja | Trening og inferens |
| NVIDIA A16 | Ja | Ja | Ja | Multi-bruker inferens |
| NVIDIA A40 | Ja (unmanaged) | Nei | Ja | Tung trening |
| NVIDIA L4 | Ja | Ja | Ja | Moderne inferens |
| NVIDIA L40 | Ja | Ja | Ja | Stor modell-inferens |
| NVIDIA L40S | Ja | Ja | Ja | High-end AI workloads |
### GPU-klargjoring
```powershell
# Sjekk GPU-status pa Azure Local-noder
Get-PnpDevice | Select-Object Status, Class, FriendlyName, InstanceId |
Where-Object { $_.FriendlyName -match "Nvidia" }
# Installer mitigation driver for DDA
# (Kreves for Azure Local 23H2+)
pnputil /add-driver oem_mitigation.inf /install
# Verifiser GPU er dismounted og klar for DDA
Get-VMHostAssignableDevice
```
---
## Cluster-felles ML Stack
### AKS enabled by Azure Arc pa Azure Local
AKS pa Azure Local gir en fullt administrert Kubernetes-opplevelse med GPU-stotte:
**Oppretting av klynge med GPU:**
```bash
# Opprett AKS-klynge pa Azure Local
az aksarc create \
--resource-group ai-edge-rg \
--name ai-edge-cluster \
--custom-location my-custom-location \
--vnet-ids /subscriptions/.../virtualNetworks/ai-vnet
# Legg til GPU node pool
az aksarc nodepool add \
--cluster-name ai-edge-cluster \
--name gpu-pool \
--resource-group ai-edge-rg \
--node-count 2 \
--node-vm-size Standard_NC4_A2 \
--os-type Linux
```
**GPU VM SKU-er tilgjengelige:**
| VM SKU | GPU | vCPU | Minne (GB) | GPU-minne |
|--------|-----|------|------------|-----------|
| Standard_NK6 | T4 (1x) | 6 | 56 | 16 GB |
| Standard_NC4 | A2 (1x) | 4 | 28 | 16 GB |
| Standard_NC8 | A2 (1x) | 8 | 56 | 16 GB |
| Standard_NC16 | A16 (1x) | 16 | 112 | 16 GB |
| Standard_NC32 | A16 (2x) | 32 | 224 | 32 GB |
### KAITO pa Azure Local
Kubernetes AI Toolchain Operator (KAITO) kjorer som en cluster extension:
```bash
# Opprett klynge med KAITO aktivert
az aksarc create \
--resource-group ai-edge-rg \
--name ai-kaito-cluster \
--custom-location my-custom-location \
--vnet-ids /subscriptions/.../virtualNetworks/ai-vnet \
--enable-ai-toolchain-operator
# Aktiver KAITO pa eksisterende klynge
az aksarc update \
--resource-group ai-edge-rg \
--name ai-edge-cluster \
--enable-ai-toolchain-operator
```
**Deploy LLM med KAITO:**
```yaml
# workspace-phi4-mini.yaml
apiVersion: kaito.sh/v1alpha1
kind: Workspace
metadata:
name: workspace-phi-4-mini
spec:
resource:
instanceType: Standard_NC8
labelSelector:
matchLabels:
apps: llm-inference
inference:
preset:
name: phi-4-mini-instruct
```
```bash
# Deploy modellen
kubectl apply -f workspace-phi4-mini.yaml
# Sjekk status
kubectl get workspace -w
# Test inferens
export SERVICE_IP=$(kubectl get svc workspace-phi-4-mini \
-o jsonpath='{.spec.clusterIP}')
kubectl run -it --rm curl --image=curlimages/curl -- \
curl -X POST http://$SERVICE_IP/v1/completions \
-H "Content-Type: application/json" \
-d '{
"model": "phi-4-mini-instruct",
"prompt": "Forklar fordelene med edge AI for offentlig sektor",
"max_tokens": 200
}'
```
---
## Local Azure Services
### Azure-tjenester som kjorer lokalt
Azure Local gir tilgang til et voksende utvalg Azure-tjenester direkte pa lokale servere:
| Tjeneste | Tilgjengelighet | AI-relevans |
|----------|-----------------|-------------|
| AKS (Arc) | GA | Container-basert ML og inferens |
| Azure Arc VMs | GA | GPU-akselererte VM-er for AI |
| Azure Monitor | GA | Overvaking av AI-arbeidsbelastninger |
| Azure Policy | GA | Governance for AI-deployments |
| Azure Key Vault | GA | Hemmelighetshaandtering for modeller |
| Azure Container Registry | Preview | Lokal container-lagring (disconnected) |
| Azure Arc Data Services | GA | SQL og PostgreSQL for AI-data |
### Lokal Container Registry for disconnected drift
```bash
# Opprett lokal ACR for disconnected miljoer
# (Azure Local med autonome operasjoner)
az acr create \
--name myedgeregistry \
--resource-group ai-edge-rg \
--location autonomous \
--sku Standard
```
---
## Storage-optimalisert inferens
### Storage Spaces Direct (S2D)
Azure Local bruker S2D for distribuert lagring med hoy ytelse:
| Lagringstype | Best for | Ytelse |
|-------------|----------|--------|
| NVMe + SSD tiered | Modell-lasting | <100ms load for 7B modell |
| All-flash NVMe | Real-time inferens | Sub-ms I/O |
| SSD + HDD tiered | Modell-arkiv, batch | Kostnadsoptimalt |
### Persistent Volume for ML-modeller
```yaml
# persistent-volume-claim.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: ai-model-storage
namespace: ai-workloads
spec:
accessModes:
- ReadWriteMany
storageClassName: azure-local-ssd
resources:
requests:
storage: 100Gi
---
# Pod med modell-lagring
apiVersion: v1
kind: Pod
metadata:
name: model-server
namespace: ai-workloads
spec:
containers:
- name: inference
image: myregistry/model-server:v1
volumeMounts:
- name: model-data
mountPath: /models
resources:
limits:
nvidia.com/gpu: 1
volumes:
- name: model-data
persistentVolumeClaim:
claimName: ai-model-storage
```
### Caching-strategier for modeller
| Strategi | Beskrivelse | Fordel |
|----------|-------------|--------|
| Pre-load til NVMe | Last modeller ved oppstart | Raskest cold start |
| Shared PVC | ReadWriteMany for flere pods | Effektiv lagring |
| InitContainer | Last modell for main container starter | Paalitelig sekvensering |
| Model sidecar | Egen container for modell-lasting | Separasjon av ansvar |
---
## Hybrid Resilience Patterns
### High Availability for AI pa Azure Local
```
┌─────────────────────────────────────────┐
│ Azure Local HA Cluster │
│ │
│ Node 1 ────────── Node 2 │
│ GPU: L4 GPU: L4 │
│ AI Workload AI Workload │
│ (Active) (Standby) │
│ │ │ │
│ └──── S2D ───────┘ │
│ Replicated Storage │
└─────────────────────────────────────────┘
```
| Resilience-moenster | Beskrivelse | RTO |
|---------------------|-------------|-----|
| Active-Passive GPU | Standby node med GPU ready | 2-5 min |
| Active-Active load balanced | Inferens fordelt pa noder | 0 (graceful) |
| N+1 redundancy | Ekstra node for failover | 1-3 min |
| Stretched cluster | Klynge over to lokasjoner | Automatisk |
### Failover-konfigurasjon
```powershell
# Konfigurer VM failover med GPU re-assignment
# Krever at GPU-er er tilgjengelige pa failover-noden
# Sett foretrukket eier for AI VM
Set-ClusterGroup -Name "AI-Inference-VM" `
-PreferredOwner "Node1", "Node2"
# Aktiver automatisk failback
(Get-ClusterGroup "AI-Inference-VM").AutoFailbackType = 1
```
### Cloud-fallback for Azure Local
Nar lokal kapasitet er utilstrekkelig:
```yaml
# Hybrid inference med sky-fallback
apiVersion: v1
kind: ConfigMap
metadata:
name: inference-config
data:
routing: |
primary:
endpoint: http://local-model-svc:8080/v1/completions
timeout: 5s
fallback:
endpoint: https://my-foundry.openai.azure.com/v1/completions
condition: local_gpu_util > 95% OR local_unavailable
```
---
## Nettverksarkitektur
### Anbefalte nettverkstopologier
| Topologi | Bruk | Krav |
|----------|------|------|
| Converged | Enkle deployments | Min 10 Gbps |
| Hyper-converged | Standard AI-klynge | 25 Gbps + RDMA |
| Switched | Stor skala, mange noder | 25-100 Gbps fabric |
### Minimum nettverkskrav for AI
| Trafikk | Minimum | Anbefalt |
|---------|---------|----------|
| Management (Arc) | 1 Gbps + internett | 10 Gbps |
| Storage (S2D) | 10 Gbps RDMA | 25 Gbps RDMA |
| Compute (GPU) | 10 Gbps | 25 Gbps |
| Client (inferens) | 1 Gbps | 10 Gbps |
---
## Sizing-guide for AI-arbeidsbelastninger
### Anbefalte konfigurasjoner
| Arbeidsbelastning | Noder | GPU per node | Minne per node | Lagring |
|-------------------|-------|-------------|----------------|---------|
| Liten inferens (Phi-3) | 2 | 1x A2 | 64 GB | 1 TB NVMe |
| Medium inferens (Phi-4, Mistral-7B) | 3 | 1x A16 | 128 GB | 2 TB NVMe |
| Stor inferens (Llama-70B) | 4 | 2x L40S | 256 GB | 4 TB NVMe |
| Trening + inferens | 4+ | 2x A40/L40S | 512 GB | 8 TB NVMe |
### Kostnadsestimat (Azure Local)
| Komponent | Estimert kost (NOK) |
|-----------|---------------------|
| 3-node Azure Local cluster | 300,000 - 600,000 |
| NVIDIA A2 GPU (per stk) | 15,000 - 25,000 |
| NVIDIA L4 GPU (per stk) | 25,000 - 40,000 |
| NVIDIA L40S GPU (per stk) | 80,000 - 120,000 |
| Azure Local lisens | Inkludert i Azure-abonnement |
| Azure Arc management | Inkludert (basis) |
| Azure ML extension | Inkludert |
**Merk:** Priser er estimater og varierer med leverandor og konfigurasjon.
---
## For Cosmo
- **Azure Local er den primaere plattformen for on-premises AI i Microsoft-okosystemet** — fullt integrert med Azure Arc for sentralisert styring, men med all databehandling pa egen infrastruktur.
- **GPU-stotten er bred med NVIDIA T4/A2/A16/L4/L40/L40S** — bade DDA (hel GPU) og GPU-P (delt GPU) er tilgjengelig, noe som gir fleksibilitet fra lette inferensoppgaver til tung trening.
- **KAITO pa AKS Arc forenkler LLM-deployment drastisk** — fra GPU-klargjoring til modell-serving med OpenAI-kompatibelt API pa noen fa kubectl-kommandoer.
- **For norsk offentlig sektor gir Azure Local data residency i Norge** med full Azure-administrasjon fra Norway East-regionen — data forlater aldri lokale servere.
- **Hybrid resilience med sky-fallback** sikrer at AI-tjenester forblir tilgjengelige selv ved lokal kapasitetsmangel, med automatisk routing til Azure-endepunkter.

View file

@ -0,0 +1,403 @@
# Data Sovereignty for Norwegian Public Sector
**Last updated:** 2026-02
**Status:** GA
**Category:** Hybrid Cloud & Edge AI
---
## Introduksjon
Datasuverenitet er et av de viktigste temaene nar norsk offentlig sektor vurderer skybaserte AI-losninger. Etter Schrems II-dommen (2020), EUs AI Act (2024), og okt fokus pa digital autonomi i Europa, ma organisasjoner navigere et komplekst landskap av juridiske krav, tekniske kontroller og politiske forventninger.
For AI-arbeidsbelastninger er utfordringene spesielt store: AI-modeller kan inneholde implisitt persondata i sine vekter, treningsdata kan vare sensitivt, og inferensresultater kan avslore informasjon om underlaget. Samtidig er mange av de kraftigste AI-tjenestene kun tilgjengelige fra bestemte Azure-regioner, og noen krever global databehandling.
Denne referansen gir en strukturert oversikt over regulatoriske krav, Microsofts tilbud for datasuverenitet, og praktiske arkitekturmoenstre for norsk offentlig sektor som vil ta i bruk AI pa en trygg og lovlig mate.
---
## Regulatorisk landskap
### Schrems II og konsekvenser
Schrems II-dommen (juli 2020) ugyldiggjorde EU-US Privacy Shield og stilte strengere krav til overforing av persondata til tredjeland:
| Aspekt | Konsekvens for AI i skyen |
|--------|--------------------------|
| Ugyldiggjoring av Privacy Shield | Kan ikke basere dataoverforing til USA pa Privacy Shield |
| Strengere SCC-krav | Standard Contractual Clauses krever tilleggstiltak |
| Risikovurdering pakreves | Ma vurdere om mottakerlandets lovgivning gir tilstrekkelig vern |
| Supplementary measures | Tekniske, organisatoriske og kontraktuelle tiltak ma iverksettes |
**Post-Schrems II tiltak fra Microsoft:**
- EU Data Boundary (EUDB) implementert for a holde data i EU/EFTA
- Standard Contractual Clauses (SCCs) oppdatert
- Data Protection Addendum (DPA) styrket
- Transparensrapporter publisert
### EU-US Data Privacy Framework (2023)
I juli 2023 vedtok EU-kommisjonen EU-US Data Privacy Framework som ny mekanisme for lovlig overforing av persondata til USA:
| Aspekt | Status |
|--------|--------|
| Adequacy decision | Vedtatt juli 2023 |
| Microsoft-sertifisering | Ja, sertifisert under DPF |
| Norsk aksept | Norge (via EOS-avtalen) folger EU-beslutninger |
| Stabilitet | Utfordret av NOYB, men gyldig per 2026 |
| Anbefalinger | Bruk DPF + tekniske tiltak (defense in depth) |
### GDPR/Personvernforordningen
| Krav | Relevans for AI |
|------|-----------------|
| Art. 5 (formaalsbegrensning) | AI-modeller ma brukes til angitt formal |
| Art. 6 (behandlingsgrunnlag) | Samtykke, avtale eller berettiget interesse |
| Art. 22 (automatiserte beslutninger) | Rett til menneskelig inngripen |
| Art. 25 (privacy by design) | Innebygd personvern i AI-systemer |
| Art. 35 (DPIA) | Pakrevd for AI med hoy risiko |
| Art. 44-49 (tredjelands overforing) | Relevant for sky-AI-tjenester |
### EUs AI Act
| Risikokategori | Krav | Eksempler |
|----------------|------|-----------|
| Uakseptabel risiko | Forbudt | Sosial scoring, manipulering |
| Hoy risiko | Strenge krav | Biometrisk ID, kredittscoring |
| Begrenset risiko | Transparenskrav | Chatbots (merking) |
| Minimal risiko | Ingen sarlige krav | Spamfiltre, anbefalinger |
**Norsk implementering:** AI Act folges opp gjennom EOS-avtalen. Datatilsynet er ansvarlig for haandheving.
---
## Norske krav til data residency
### Utredningsinstruksen
Statlige tiltak (inkludert AI-prosjekter) ma folge utredningsinstruksen:
| Krav | Konsekvens for AI-prosjekter |
|------|------------------------------|
| Problemdefinisjon | Klar definisjon av hva AI skal lose |
| Behovsanalyse | Dokumenter hvorfor AI er nodvendig |
| Alternativvurdering | Sammenlign sky/hybrid/lokalt |
| Konsekvensutredning | Personvern, sikkerhet, okonomi |
| Forholdsmessighet | Balanse mellom nytte og risiko |
| Horing | Involver berorte parter |
### Sikkerhetsloven og NSMs krav
For virksomheter underlagt sikkerhetsloven:
| Krav | Implikasjon |
|------|------------|
| Informasjonssikkerhet | AI-systemer som behandler sikkerhetsgradert info |
| Forebyggende sikkerhet | Risikovurdering av AI-leverandorer |
| Personellsikkerhet | Klarering for tilgang til AI-systemer |
| Objektsikkerhet | Fysisk sikring av AI-infrastruktur |
| IKT-sikkerhet | NSMs grunnprinsipper for AI-systemer |
### Digitaliseringsrundskrivet
Regjeringens retningslinjer for offentlig sektors digitalisering:
| Prinsipp | AI-relevans |
|----------|-------------|
| Skyforst-strategi | Sky er forstevalg, men med unntak for sensitiv data |
| Apne data | AI-modeller bor benytte apne datakilder der mulig |
| Deling av data | Samarbeid mellom etater om AI-treningsdata |
| Personvern | DPIA for alle AI-systemer med persondata |
| Tilgjengelighet | AI-tjenester ma vaere universelt utformet |
---
## Azure Data Residency for Norge
### Azure-regioner i Norge
| Region | Tjenester | Formaal |
|--------|-----------|---------|
| Norway East (Oslo) | Fullt tjenesteomfang | Primaerregion |
| Norway West (Stavanger) | Begrenset | DR/backup |
### Azure-tjenester tilgjengelig i Norway East
| Tjenestekategori | Tilgjengelighet | Merknader |
|-----------------|-----------------|-----------|
| Compute (VMs) | GA | Inkl. GPU (NC, ND-serier) |
| Azure Kubernetes Service | GA | Primaer container-plattform |
| Azure Storage | GA | Alle lagringstyper |
| Azure SQL/Cosmos DB | GA | Regional data residency |
| Azure AI Foundry | Begrenset | Ikke alle modeller |
| Azure OpenAI | GA | GPT-4o, GPT-4o-mini |
| Azure AI Services | GA | Vision, Speech, Language |
| Azure Machine Learning | GA | Trening og inferens |
| Azure Key Vault | GA | Hemmelighetshaandtering |
| Azure Monitor | GA | Overvaking |
### Tjenester som IKKE er tilgjengelige i Norway East
| Tjeneste | Naermeste region | Alternativ |
|----------|-----------------|------------|
| Azure OpenAI (GPT-5) | Sweden Central | Bruk EUDB-region |
| Copilot Studio | EU-regioner | Sett tenant til EU |
| Noen AI Foundry-modeller | Sweden/West Europe | Vurder EUDB-scope |
| Azure AI Search (semantic) | West Europe | Kan kreve EU-plassering |
---
## EU Data Boundary (EUDB)
### Hva er EUDB?
EU Data Boundary er Microsofts forpliktelse til a lagre og behandle kundedata og persondata innenfor EU/EFTA for sine enterprise online services:
| Tjeneste | EUDB-stottet | Betingelse |
|----------|-------------|------------|
| Azure (regionale) | Ja | Deploy i EU/EFTA-region |
| Azure (ikke-regionale) | Delvis | Krever konfigurasjon |
| Dynamics 365 | Ja | Tenant i EU geo |
| Power Platform | Ja | Miljo i EU geo |
| Microsoft 365 | Ja | Tenant i EU geo |
### EUDB-land
EU Data Boundary dekker:
- **EU:** Osterrike, Belgia, Bulgaria, Kroatia, Kypros, Tsjekkia, Danmark, Estland, Finland, Frankrike, Tyskland, Hellas, Ungarn, Irland, Italia, Latvia, Litauen, Luxembourg, Malta, Nederland, Polen, Portugal, Romania, Slovakia, Slovenia, Spania, Sverige
- **EFTA:** Liechtenstein, Island, **Norge**, Sveits
### Konfigurering av EUDB for Azure
```bash
# Konfigurer Azure Data Boundary for tenant
az data-boundary create --data-boundary EU --default default
```
```json
// Azure Policy: Tving ressurser til Norway East
{
"if": {
"not": {
"field": "location",
"in": ["norwayeast", "norwaywest", "swedencentral",
"westeurope", "northeurope"]
}
},
"then": {
"effect": "deny"
}
}
```
---
## Microsoft Sovereign Cloud
### Sovereign deployment-modeller
Microsoft tilbyr tre nivaer av suverenitet:
| Modell | Beskrivelse | Kontrollniva | Tilgjengelighet |
|--------|-------------|-------------|-----------------|
| Sovereign Public Cloud | Azure med EUDB + sovereign controls | Hoy | GA (EU/EFTA) |
| Sovereign Private Cloud | Azure Local/M365 Local i eget datasenter | Hoyest | GA |
| National Partner Clouds | Partnerdrevet lokal sky | Variabel | Utvalgte land |
### Sovereign Landing Zone (SLZ)
SLZ er en variant av Azure Landing Zone med innebygde suverenitetskontroller:
| Kontrollniva | Policyer | Bruksomrade |
|-------------|----------|-------------|
| L1 (Basis) | Data residency, godkjente regioner | Standard offentlig sektor |
| L2 (Styrket) | L1 + kryptering med CMK | Sensitiv data |
| L3 (Konfidensielt) | L2 + confidential computing | Sikkerhetsgradert |
**SLZ Policy-kontroller:**
| Policy-ID | Kontroll | Effekt |
|-----------|---------|--------|
| SO.1 | Data residency — godkjente regioner | Deny |
| SO.2 | Kryptering med kundestyrt nokkel (CMK) | Audit/Deny |
| SO.3 | Confidential computing for utvalgte tjenester | Audit |
| SO.4 | Private endpoints for datatilgang | Deny |
### Implementering av SLZ
```bash
# Deploy Sovereign Landing Zone med Bicep
az deployment sub create \
--location norwayeast \
--template-file sovereign-landing-zone.bicep \
--parameters \
allowedLocations='["norwayeast","norwaywest"]' \
requireCMK=true \
enforcePrivateEndpoints=true \
dataClassification="sensitive"
```
---
## Azure Data Classification for AI
### Dataklassifiseringsmatrise
| Klassifisering | Beskrivelse | Sky-tillatelse | Azure-krav |
|---------------|-------------|----------------|------------|
| Apen | Offentlig tilgjengelig | Alle regioner | Standard |
| Intern | Ikke-sensitiv intern data | EU/EFTA | EUDB |
| Fortrolig | Sensitiv, persondata | Norway East/West | CMK + RBAC |
| Strengt fortrolig | Hoy sensitivitet | Norway + spesialtiltak | SLZ L2+ |
| Sikkerhetsgradert | Underlagt sikkerhetsloven | Lokalt / godkjent sky | Azure Local |
### AI-spesifikke datakategorier
| Datakategori | Eksempel | Klassifisering | Behandlingssted |
|-------------|----------|---------------|-----------------|
| Treningsdata | Dokumenter, bilder | Fortrolig+ | Norway East |
| Modellvekter | Fine-tuned modeller | Intern/Fortrolig | Norway East |
| Inferens-input | Brukerforesporsler | Fortrolig | Norway East |
| Inferens-output | AI-svar | Fortrolig | Norway East |
| Systemlogger | Telemetri, metrikker | Intern | EU/EFTA |
| Prompt-logger | Bruker-prompts | Fortrolig | Norway East |
---
## Praktiske arkitekturmoenstre
### Moenster 1: Full sky i Norway East
```
┌─────────────────────────────────────┐
│ Norway East Region │
│ ┌───────────┐ ┌───────────────┐ │
│ │ Azure │ │ Azure AI │ │
│ │ OpenAI │ │ Services │ │
│ │ (GPT-4o) │ │ (Vision,Speech│ │
│ └─────┬─────┘ └──────┬────────┘ │
│ │ │ │
│ ┌─────▼───────────────▼────────┐ │
│ │ Azure ML Workspace │ │
│ │ + Private Endpoints │ │
│ └──────────────────────────────┘ │
│ ┌──────────────────────────────┐ │
│ │ Azure Key Vault (CMK) │ │
│ └──────────────────────────────┘ │
└─────────────────────────────────────┘
```
**Best for:** Standard AI-prosjekter uten krav utover GDPR/EUDB.
### Moenster 2: Hybrid med Azure Local
```
┌──────────────────────┐ ┌────────────────────┐
│ Norway East │ │ Eget datasenter │
│ ┌────────────────┐ │ │ ┌──────────────┐ │
│ │ Azure ML │ │ │ │ Azure Local │ │
│ │ (Orchestration)│◄─┼──┼─►│ (AI Inference)│ │
│ └────────────────┘ │ │ │ GPU + Data │ │
│ ┌────────────────┐ │ │ └──────────────┘ │
│ │ Azure Monitor │ │ │ ┌──────────────┐ │
│ │ (Overvaking) │◄─┼──┼──│ Arc Agent │ │
│ └────────────────┘ │ │ └──────────────┘ │
└──────────────────────┘ └────────────────────┘
```
**Best for:** Sensitiv data som ikke kan forlate egne lokaler, men trenger sky-administrasjon.
### Moenster 3: Fullstendig lokal (sovereign private)
```
┌─────────────────────────────────────┐
│ Eget datasenter │
│ ┌──────────────────────────────┐ │
│ │ Azure Local Cluster │ │
│ │ ┌────────┐ ┌───────────┐ │ │
│ │ │ AKS │ │ ONNX │ │ │
│ │ │ (KAITO)│ │ Runtime │ │ │
│ │ └────────┘ └───────────┘ │ │
│ │ ┌────────────────────────┐ │ │
│ │ │ Disconnected │ │ │
│ │ │ AI Containers │ │ │
│ │ └────────────────────────┘ │ │
│ └──────────────────────────────┘ │
│ Ingen ekstern tilkobling │
└─────────────────────────────────────┘
```
**Best for:** Sikkerhetsgradert data, forsvarssektor, kritisk infrastruktur.
---
## Beslutningstre for datasuverenitet
```
Er dataene sikkerhetsgraderte (Sikkerhetsloven)?
├── Ja → Moenster 3: Azure Local, helt lokalt
│ Ingen sky-tilkobling
│ ONNX Runtime + Disconnected containers
└── Nei → Inneholder dataene personopplysninger?
├── Ja → Er det saerlige kategorier (helse, biometri)?
│ ├── Ja → Moenster 2: Hybrid
│ │ Data lokalt, styring fra Norway East
│ │ DPIA pakrevd, CMK-kryptering
│ │
│ └── Nei → Moenster 1 eller 2
│ Norway East med EUDB
│ Standard GDPR-tiltak
└── Nei → Moenster 1: Full sky
Norway East / EU region
Standard sikkerhetstiltak
```
---
## Compliance-sjekkliste for AI-prosjekter
| # | Kontroll | Ansvarlig | Status |
|---|---------|-----------|--------|
| 1 | DPIA gjennomfort | Personvernombud | |
| 2 | Behandlingsgrunnlag dokumentert | Juridisk | |
| 3 | Dataklassifisering gjennomfort | Informasjonseier | |
| 4 | Azure-region valgt (Norway East) | IT-arkitekt | |
| 5 | EUDB konfigurert | Sky-administrator | |
| 6 | CMK aktivert for sensitiv data | Sikkerhetsansvarlig | |
| 7 | Private endpoints konfigurert | Nettverksansvarlig | |
| 8 | RBAC implementert | IAM-ansvarlig | |
| 9 | Logging og overvaking aktivert | Driftsansvarlig | |
| 10 | AI Act risikoklassifisering | AI-ansvarlig | |
| 11 | Utredningsinstruksen fulgt | Prosjektleder | |
| 12 | ROS-analyse gjennomfort | Sikkerhetsansvarlig | |
| 13 | Leverandorvurdering gjennomfort | Innkjopsansvarlig | |
| 14 | Databehandleravtale inngatt | Juridisk | |
| 15 | Exitstrategi dokumentert | IT-arkitekt | |
---
## Sammenligning: Sovereign Cloud-alternativer
| Egenskap | Azure Sovereign Public | Azure Local (Private) | National Partner Cloud |
|----------|----------------------|----------------------|----------------------|
| Data residency | EU/EFTA (konfiguerbar) | Fullt lokalt | Varierer |
| Kontroll over data | Microsoft-driftet | Kunde-driftet | Partnerdriftet |
| AI-tjenester | Fullt omfang | Begrensede (ONNX, containers) | Varierer |
| Skalerbarhet | Hoy | Begrenset av hardware | Varierer |
| Kostnad | Pay-as-you-go | CAPEX + OPEX | Varierer |
| Compliance (GDPR) | Ja | Ja | Varierer |
| Compliance (NSM) | Delvis | Ja (med tiltak) | Varierer |
| Sikkerhetsgradert | Nei | Mulig | Varierer |
| AI Act compliance | Verktoy tilgjengelig | Kunde-ansvar | Varierer |
---
## For Cosmo
- **Schrems II er ikke lenger den eneste utfordringen** — EU-US Data Privacy Framework (2023), EU Data Boundary, og Sovereign Landing Zone gir et nyansert verktoyskrin for lovlig bruk av Azure AI fra Norge.
- **Norway East-regionen er forstevalg for norsk offentlig sektor** — de fleste AI-tjenester (Azure OpenAI GPT-4o, AI Services, ML) er tilgjengelig der, men noen nyere modeller krever Sweden Central eller West Europe.
- **Tre arkitekturmoenstre dekker hele spekteret** — full sky for standard data, hybrid for sensitiv data, og helt lokalt (Azure Local) for sikkerhetsgradert — alltid med DPIA og risikovurdering.
- **Sovereign Landing Zone med L1-L3 policyer** gir mekanisk haandheving av data residency, kryptering og tilgangskontroll — ikke bare dokumentbaserte lovnader.
- **AI Act-klassifisering ma gjores for hvert AI-prosjekt** — norsk offentlig sektor ma identifisere risikokategori (minimal/begrenset/hoy/uakseptabel) og implementere tilsvarende tiltak for AI-systemer.

View file

@ -0,0 +1,513 @@
# Disconnected AI Scenarios
**Last updated:** 2026-02
**Status:** GA
**Category:** Hybrid Cloud & Edge AI
---
## Introduksjon
Frakoblede (disconnected) AI-scenarioer er situasjoner der AI-arbeidsbelastninger ma kjore uten internettilkobling — enten permanent, periodisk eller i beredskapssituasjoner. For norsk offentlig sektor er dette svart relevant: Forsvaret opererer i omrader uten nettdekning, helsesektoren trenger AI-stotte i ambulanser og utposter, og kritisk infrastruktur (energi, transport) ma fungere uavhengig av skytjenester.
Microsoft tilbyr flere losninger for frakoblet AI: Azure AI Foundry Tools disconnected containers for tradisjonelle AI-tjenester (tale, tekst, bilde), Azure Stack Edge for hardware-basert edge-inferens, Azure Local med disconnected operations for storre Kubernetes-miljoer, og ONNX Runtime for helt lokale modellkjoringer uten skyavhengigheter.
Denne referansen dekker de viktigste moensterne for offline modell-deployment, datarekonsiliering, lokal caching/synkronisering og fallback-strategier — alle med fokus pa palit drift nar nettverkstilkoblingen er ustabil eller fravarende.
---
## Spekter av tilkobling
AI-scenarioer fordeler seg langs et tilkoblingsspektrum:
```
┌─────────────────────────────────────────────────────┐
│ Alltid Sporadisk Periodisk Helt │
│ tilkoblet tilkoblet tilkoblet frakoblet │
│ ●──────────●────────────●────────────● │
│ | | | | │
│ Standard Connected Batch sync Air-gapped │
│ Azure containers + lokale │
│ services (billing) offline ops modeller │
└─────────────────────────────────────────────────────┘
```
| Modus | Nettverkskrav | Azure-tjenester | Billing |
|-------|---------------|-----------------|---------|
| Alltid tilkoblet | Stabilt internett | Alle | Pay-as-you-go |
| Connected containers | Periodisk (billing) | Begrensede | Bruksbasert |
| Periodisk synk | Timer/dager mellom tilkoblinger | Batch-synk | Commitment tier |
| Helt frakoblet | Ingen | Kun lokale | Forhndsbetalt lisens |
---
## Offline Model Deployment
### Azure AI Foundry Tools Disconnected Containers
Microsofts primaere losning for AI-tjenester uten nettverkstilkobling:
| Tjeneste | Container | Disconnected | Status |
|----------|-----------|--------------|--------|
| Speech to Text | speech-to-text | Ja | GA |
| Custom Speech to Text | custom-speech-to-text | Ja | GA |
| Neural Text to Speech | neural-text-to-speech | Ja | GA |
| Translator | text-translation | Ja | GA |
| Language Detection | text-language-detection | Ja | GA |
| Key Phrase Extraction | text-keyphrase | Ja | GA |
| Named Entity Recognition | text-ner | Ja | GA |
| PII Detection | text-pii | Ja | GA |
| Sentiment Analysis | text-sentiment | Ja | GA |
| CLU | clu | Ja | GA |
| Summarization | text-summarization | Ja | Preview |
| Read OCR | vision-read | Ja | GA |
| Document Intelligence | document-intelligence | Ja | GA |
| Content Safety (Text) | contentsafety-text | Ja | Preview |
| Content Safety (Image) | contentsafety-image | Ja | Preview |
| Prompt Shields | contentsafety-promptshields | Ja | Preview |
### Prosess for disconnected deployment
```
┌─────────────────────────────────────────┐
│ 1. Soknad og godkjenning │
│ ├── Enterprise Agreement kreves │
│ ├── Gyldig business case │
│ └── Godkjenning innen 10 dager │
│ │
│ 2. Lisensnedlasting │
│ ├── Kjop commitment tier │
│ ├── Last ned lisensfil │
│ └── Lisensfil har utlopsdato │
│ │
│ 3. Container-nedlasting │
│ ├── Pull fra MCR (online) │
│ ├── Eksporter til tar │
│ └── Overfar til offline-miljo │
│ │
│ 4. Offline deployment │
│ ├── Importer container │
│ ├── Mount lisensfil │
│ └── Kjor uten nettverkstilkobling │
└─────────────────────────────────────────┘
```
### Lisensnedlasting og container-oppsett
```bash
# Steg 1: Last ned lisens (online maskin)
docker run --rm -it \
-v /host/license:/license \
mcr.microsoft.com/azure-cognitive-services/speechservices/speech-to-text \
eula=accept \
billing=https://my-resource.cognitiveservices.azure.com \
apikey=<API_KEY> \
DownloadLicense=True \
Mounts:License=/license
# Steg 2: Eksporter container image
docker save \
mcr.microsoft.com/azure-cognitive-services/speechservices/speech-to-text \
-o speech-to-text.tar
# Steg 3: Overfar til offline-miljo (USB, etc.)
# Kopier speech-to-text.tar og lisensfil
# Steg 4: Importer pa offline-maskin
docker load -i speech-to-text.tar
# Steg 5: Kjor uten nettverkstilkobling
docker run --rm -it -p 5000:5000 \
-v /host/license:/license \
mcr.microsoft.com/azure-cognitive-services/speechservices/speech-to-text \
eula=accept \
Mounts:License=/license \
Mounts:Output=/output
```
### ONNX Runtime — helt lokale modeller
For scenarioer uten Docker- eller lisensbehov:
```python
# Helt lokal inferens med ONNX Runtime
# Ingen skyavhengighet, ingen lisens, ingen Docker
import onnxruntime as ort
import numpy as np
# Last modell fra lokal disk
session = ort.InferenceSession(
"/models/document-classifier.onnx",
providers=['CPUExecutionProvider']
)
# Kjor inferens
input_name = session.get_inputs()[0].name
result = session.run(None, {
input_name: np.array(preprocessed_data)
})
```
---
## Azure Stack Edge i disconnected modus
### Nkkelforskjeller offline vs online
| Funksjon | Online | Disconnected |
|----------|--------|-------------|
| Azure Portal management | Ja | Nei — kun lokal UI |
| Kubernetes workloads | Full Arc-stotte | Lokal kubectl |
| Container registry | Azure Container Registry | Edge Container Registry |
| Overvaking | Azure Monitor | Lokalt Kubernetes dashboard |
| Azure Arc | Full integrasjon | Ikke tilgjengelig |
| VM-styring | Arc-enabled VMs | Lokalt PowerShell/UI |
| GPU workloads | Full stotte | Full stotte (forklargjort) |
### Forberedelse for disconnected drift
```powershell
# Forbered Azure Stack Edge for offline-bruk
# (Gjores mens enheten fortsatt er online)
# 1. Aktiver enhet via Azure Portal
# 2. Enable Kubernetes
Set-AzureDataBoxEdgeRole -Name "Kubernetes" -Activated
# 3. Deploy alle nodvendige container workloads
kubectl apply -f ai-inference-deployment.yaml
# 4. Push container images til Edge Container Registry
docker tag my-model:v1 ecr.edgehostname:31001/my-model:v1
docker push ecr.edgehostname:31001/my-model:v1
# 5. Verifiser at alt kjorer
kubectl get pods -A
# 6. Koble fra nettverket
```
---
## Data Reconciliation Strategies
### Utfordringer med frakoblet data
Nar AI-systemer kjorer offline, oppstar det utfordringer med:
- Data som genereres lokalt ma synkroniseres nar tilkobling gjenopprettes
- Modellresultater fra offline-perioden ma valideres
- Konflikter mellom lokale og sentrale data
- Versjonshaandtering av modeller og konfigurasjoner
### Rekonsilieringsmoenstre
| Moenster | Beskrivelse | Bruksomrade |
|---------|-------------|-------------|
| Store-and-Forward | Buffer lokalt, send nar tilkoblet | IoT-data, loggfiler |
| Event Sourcing | Registrer alle hendelser, replay sentralt | Audit, compliance |
| Last-Write-Wins | Siste endring vinner ved konflikt | Enkle konfigurasjoner |
| Merge/CRDTs | Konfliktfri sammenslaing | Distribuerte datasett |
| Manual Review | Menneske loeser konflikter | Kritiske beslutninger |
### Store-and-Forward med IoT Hub
```python
# Lokal buffering og batch-synkronisering
import json
import os
from datetime import datetime
from pathlib import Path
class OfflineBuffer:
def __init__(self, buffer_dir="/data/offline-buffer"):
self.buffer_dir = Path(buffer_dir)
self.buffer_dir.mkdir(parents=True, exist_ok=True)
def store_result(self, inference_result, metadata):
"""Lagre inferensresultat lokalt."""
entry = {
"timestamp": datetime.utcnow().isoformat(),
"result": inference_result,
"metadata": metadata,
"synced": False
}
filepath = self.buffer_dir / f"{entry['timestamp']}.json"
filepath.write_text(json.dumps(entry))
return filepath
def get_unsynced(self):
"""Hent alle usynkroniserte resultater."""
results = []
for f in sorted(self.buffer_dir.glob("*.json")):
entry = json.loads(f.read_text())
if not entry.get("synced"):
results.append((f, entry))
return results
async def sync_to_cloud(self, iot_client):
"""Synkroniser bufferede resultater til sky."""
unsynced = self.get_unsynced()
for filepath, entry in unsynced:
try:
await iot_client.send_message(
json.dumps(entry)
)
entry["synced"] = True
entry["synced_at"] = datetime.utcnow().isoformat()
filepath.write_text(json.dumps(entry))
except Exception as e:
# Fortsett med neste — proev igjen senere
print(f"Sync feilet for {filepath}: {e}")
break
```
### Modellversjon-rekonsiliering
```yaml
# model-sync-config.yaml
sync:
strategy: "check-on-connect"
model_registry:
cloud: "https://ml-workspace.azureml.net/models"
local: "/models/registry.json"
conflict_resolution: "cloud-wins"
validation:
enabled: true
test_dataset: "/data/validation/standard-test.json"
min_accuracy: 0.95
rollback:
enabled: true
keep_previous: 3
```
---
## Local Cache and Sync
### Flerlags cache-arkitektur
```
┌──────────────────────────────────────────┐
│ Lag 1: In-Memory Cache (Redis) │
│ TTL: 1 time │ Storrelse: 2 GB │
│ Hoyest prioritet, raskest tilgang │
├──────────────────────────────────────────┤
│ Lag 2: Lokal Disk Cache (SSD) │
│ TTL: 7 dager │ Storrelse: 100 GB │
│ Modellvekter, embeddings, resultater │
├──────────────────────────────────────────┤
│ Lag 3: Persistent Storage (S2D/NAS) │
│ Ingen TTL │ Storrelse: 1 TB+ │
│ Full modellhistorikk, treningsdata │
├──────────────────────────────────────────┤
│ Lag 4: Cloud Sync (Azure Blob) │
│ Synk ved tilkobling │
│ Master-kopi av modeller og data │
└──────────────────────────────────────────┘
```
### Synkroniseringslogikk
```python
# Intelligent sync-manager
class SyncManager:
def __init__(self, config):
self.local_store = LocalModelStore(config.local_path)
self.cloud_store = AzureBlobStore(config.connection_string)
self.sync_log = SyncLog(config.log_path)
async def check_connectivity(self):
"""Sjekk om skytilkobling er tilgjengelig."""
try:
await self.cloud_store.ping()
return True
except Exception:
return False
async def sync_models(self):
"""Synkroniser modeller mellom lokal og sky."""
if not await self.check_connectivity():
return SyncResult(status="offline", synced=0)
# Hent manifest fra sky
cloud_manifest = await self.cloud_store.get_manifest()
local_manifest = self.local_store.get_manifest()
updates = []
for model_id, cloud_info in cloud_manifest.items():
local_info = local_manifest.get(model_id)
if not local_info:
# Ny modell — last ned
updates.append(("download", model_id, cloud_info))
elif cloud_info['version'] > local_info['version']:
# Oppdatert modell — last ned ny versjon
updates.append(("update", model_id, cloud_info))
# Utfor oppdateringer med prioritering
for action, model_id, info in sorted(
updates, key=lambda x: x[2].get('priority', 99)
):
try:
await self._download_model(model_id, info)
self.sync_log.record(action, model_id, "success")
except Exception as e:
self.sync_log.record(action, model_id, f"failed: {e}")
# Last opp lokale resultater
await self._upload_offline_results()
return SyncResult(
status="synced",
synced=len(updates)
)
```
---
## Fallback Inference Patterns
### Degraderingsstrategier
| Strategi | Nar | Implementasjon |
|----------|-----|---------------|
| Full model → Lite model | GPU svikter | Fall tilbake til CPU-modell |
| Cloud model → Edge model | Nettverk nede | Bruk lokal kvantisert modell |
| ML-modell → Regler | Modell korrupt | Regelbasert fallback |
| Real-time → Batch | Overbelastning | Buffer foresporsler |
| AI → Manuell | Alt feiler | Eskalering til menneske |
### Implementasjon av fallback-kaskade
```python
class ResilientInferenceEngine:
def __init__(self):
self.engines = [
CloudInference(endpoint="https://foundry.azure.com"),
LocalGPUInference(model_path="/models/full-model.onnx"),
LocalCPUInference(model_path="/models/quantized-int8.onnx"),
RuleBasedFallback(rules_path="/config/rules.json")
]
async def infer(self, input_data, timeout=5.0):
"""Prover inferensmotorer i prioritetsrekkefoolge."""
last_error = None
for engine in self.engines:
try:
result = await asyncio.wait_for(
engine.predict(input_data),
timeout=timeout
)
return InferenceResult(
prediction=result,
engine=engine.name,
confidence=engine.confidence_level,
degraded=(engine != self.engines[0])
)
except asyncio.TimeoutError:
last_error = f"{engine.name}: timeout"
timeout = min(timeout * 2, 30) # Okt timeout for neste
except Exception as e:
last_error = f"{engine.name}: {e}"
continue
# Alle motorer feilet — returner fallback
return InferenceResult(
prediction=None,
engine="none",
confidence=0,
degraded=True,
error=last_error
)
```
### Health monitoring for offline-systemer
```yaml
# health-check-config.yaml
health_checks:
model_health:
interval: 60s
checks:
- name: model_loaded
type: inference_test
input: "test_input.json"
expected_output_shape: [1, 10]
- name: gpu_available
type: nvidia_smi
min_free_memory_mb: 1024
- name: disk_space
type: disk
min_free_gb: 10
degradation_rules:
- condition: "gpu_available == false"
action: "switch_to_cpu_model"
- condition: "disk_space < 5GB"
action: "cleanup_old_models"
- condition: "model_loaded == false"
action: "reload_from_cache"
max_retries: 3
```
---
## Scenarioer for norsk offentlig sektor
### Forsvar og beredskap
| Scenario | Tilkoblingsstatus | Losning |
|----------|-------------------|---------|
| Feltoperasjoner | Helt frakoblet | ONNX Runtime + kvantiserte modeller |
| Kjoretoy/fartoy | Periodisk | Store-and-forward + modellsynk |
| Kommandoplass | Begrenset | Azure Stack Edge disconnected |
| Sambandssystemer | Ustabil | Fallback-kaskade med degradering |
### Helse
| Scenario | Tilkoblingsstatus | Losning |
|----------|-------------------|---------|
| Ambulanse | Ustabil | Lokal bildeanalyse (ONNX) |
| Distriktslege | Periodisk | Disconnected containers (tale, tekst) |
| Sykehus DR | Beredskap | Azure Local med offline-kapasitet |
| Feltsykehus | Frakoblet | Forhndslastede modeller |
### Transport og infrastruktur
| Scenario | Tilkoblingsstatus | Losning |
|----------|-------------------|---------|
| Tunneler | Frakoblet | Edge-inferens med kamerasystem |
| Fartsoyvervaking | Ustabil | Lokal objektdeteksjon |
| Trafikkanalyse | Periodisk | Batch-analyse med synk |
| Fergedrift | Variabel | Hybrid med sky-fallback |
---
## Lisens- og kostnadshensyn
### Disconnected containers prismodell
| Prismodell | Beskrivelse | Krav |
|-----------|-------------|------|
| Commitment tier | Arlig forpliktelse | Enterprise Agreement |
| Per-tjeneste | Betal per container-tjeneste | Godkjent soknad |
| Kalenderars-binding | 12 mnd minimum | Automatisk fornyelse |
### Viktige begrensninger
- Lisensfil har utlopsdato — krever periodisk fornyelse
- Enterprise Agreement eller tilsvarende er obligatorisk
- Godkjenningsprosess tar opptil 10 virkedager
- Ingen SLA for disconnected containers (kunde eier infrastruktur)
- Ikke tilgjengelig i sovereign clouds (kun public cloud for opprettelse)
---
## For Cosmo
- **Azure tilbyr et komplett spekter for frakoblet AI** — fra Foundry Tools disconnected containers (tale, tekst, bilde) til helt lokale ONNX Runtime-modeller uten skyavhengighet.
- **Disconnected containers krever Enterprise Agreement og godkjenning** — lisensfiler har utlopsdato og ma fornyes, men gir tilgang til de samme API-ene som sky-tjenestene.
- **Fallback-kaskader er essensielt for paalitelig edge-AI** — design alltid med degraderingsstrategi: sky → lokal GPU → lokal CPU → regler → manuell.
- **Store-and-forward med rekonsilieringslogikk** loser utfordringen med data som genereres offline — buffer lokalt, synkroniser ved tilkobling, hndter konflikter.
- **For norsk offentlig sektor er frakoblet AI kritisk for beredskap, forsvar og helse** — Azure Stack Edge og ONNX Runtime gir funksjonskapasitet uten internett-avhengighet.

View file

@ -0,0 +1,482 @@
# Edge AI Inferencing Patterns
**Last updated:** 2026-02
**Status:** GA
**Category:** Hybrid Cloud & Edge AI
---
## Introduksjon
Edge AI-inferens handler om a kjore maskinlaeringsmodeller naermest mulig der data oppstar — pa enheter, gateways, lokale servere eller Azure Local-klynger. For norsk offentlig sektor er dette relevant i scenarioer som sanntids videoanalyse, dokumentbehandling i felt, naturspraakbehandling offline, og autonome systemer i omrader med begrenset nettverkstilkobling.
Microsoft tilbyr et bredt spekter av verktoy for edge-inferens: ONNX Runtime som universell inferensmotor, Azure IoT Edge for container-baserte modeller, Azure Stack Edge for hardware-akselerert inferens, og KAITO for LLM-deployment pa Kubernetes. Valget av moenster avhenger av modellstorrelse, latenskrav, tilgjengelig hardware og tilkoblingsstatus.
Denne referansen dekker de viktigste moensterne for modelloptimalisering, akselerasjon, caching og batching/streaming — alle med fokus pa Microsoft Azure-okosystemet og relevans for offentlig sektor.
---
## Model Quantization og Compression
### Hva er kvantisering?
Kvantisering reduserer presisjonen til modellvekter fra hoyere til lavere bit-representasjoner, noe som reduserer modellstorrelse og oker inferenshastighet med minimalt noyaktighetstap.
| Presisjon | Bits | Storrelse (7B modell) | Hastighet | Noyaktighet |
|-----------|------|----------------------|-----------|-------------|
| FP32 | 32 | ~28 GB | Baseline | 100% |
| FP16 | 16 | ~14 GB | 2x | ~99.9% |
| BF16 | 16 | ~14 GB | 2x | ~99.9% |
| INT8 | 8 | ~7 GB | 3-4x | ~99% |
| INT4 | 4 | ~3.5 GB | 5-8x | ~97% |
### Kvantiseringsmetoder i Azure-okosystemet
| Metode | Verktoy | Best for | Presisjon |
|--------|---------|----------|-----------|
| Post-Training Quantization (PTQ) | ONNX Runtime, Olive | Rask konvertering | INT8/INT4 |
| Quantization-Aware Training (QAT) | PyTorch + Azure ML | Hoyest noyaktighet | INT8 |
| GPTQ | HuggingFace + KAITO | LLM-kvantisering | INT4 |
| AWQ | HuggingFace + KAITO | LLM-kvantisering | INT4 |
| Dynamic Quantization | ONNX Runtime | CPU-inferens | INT8 |
### ONNX Runtime-kvantisering
```python
# Kvantiser ONNX-modell til INT8
from onnxruntime.quantization import quantize_dynamic, QuantType
quantize_dynamic(
model_input="model_fp32.onnx",
model_output="model_int8.onnx",
weight_type=QuantType.QInt8,
optimize_model=True
)
```
### Olive — Microsofts modelloptimalisering
Olive er Microsofts verktoy for helhetlig modelloptimalisering:
```python
# olive_config.json
{
"input_model": {
"type": "OnnxModel",
"model_path": "model.onnx"
},
"systems": {
"local_system": {
"type": "LocalSystem",
"accelerators": [{"device": "npu"}]
}
},
"passes": {
"quantization": {
"type": "OnnxQuantization",
"config": {
"quant_mode": "static",
"quant_format": "QDQ",
"calibration_data_reader": "CalibrationDataReader"
}
},
"perf_tuning": {
"type": "OrtPerfTuning",
"config": {
"data_dir": "./calibration_data"
}
}
},
"engine": {
"search_strategy": {
"execution_order": "joint",
"search_algorithm": "exhaustive"
},
"output_dir": "./optimized"
}
}
```
### Modellkomprimering
| Teknikk | Beskrivelse | Storrelsereduksjon | Noyaktighetstap |
|---------|-------------|-------------------|-----------------|
| Pruning | Fjerner uvesentlige vekter | 30-70% | 1-3% |
| Knowledge Distillation | Laerer liten modell fra stor | 50-90% | 2-5% |
| Weight Sharing | Deler vekter mellom lag | 20-40% | <1% |
| Low-Rank Factorization | Dekomponerer vektmatriser | 30-50% | 1-2% |
---
## Real-time Inference Acceleration
### ONNX Runtime Execution Providers
ONNX Runtime stotter flere hardware-akseleratorer gjennom Execution Providers (EP):
| Execution Provider | Hardware | Best for | Latens |
|--------------------|----------|----------|--------|
| CPU EP | x86/ARM CPU | Universell | Basis |
| CUDA EP | NVIDIA GPU | GPU-inferens | 5-50x raskere |
| TensorRT EP | NVIDIA GPU | Optimalisert GPU | 10-100x raskere |
| OpenVINO EP | Intel CPU/GPU/VPU | Intel-optimalisert | 3-20x raskere |
| DirectML EP | Windows GPU/NPU | Windows-enheter | 5-30x raskere |
| QNN EP | Qualcomm NPU | Snapdragon-enheter | 10-50x raskere |
### GPU-akselerert inferens med ONNX Runtime
```python
import onnxruntime as ort
# Konfigurasjon for NVIDIA GPU (TensorRT)
session_options = ort.SessionOptions()
session_options.graph_optimization_level = \
ort.GraphOptimizationLevel.ORT_ENABLE_ALL
providers = [
('TensorrtExecutionProvider', {
'trt_max_workspace_size': 2147483648, # 2 GB
'trt_fp16_enable': True,
'trt_engine_cache_enable': True,
'trt_engine_cache_path': './trt_cache'
}),
('CUDAExecutionProvider', {
'device_id': 0,
'arena_extend_strategy': 'kSameAsRequested',
'gpu_mem_limit': 4 * 1024 * 1024 * 1024, # 4 GB
'cudnn_conv_algo_search': 'DEFAULT'
}),
'CPUExecutionProvider'
]
session = ort.InferenceSession(
"model_fp16.onnx",
sess_options=session_options,
providers=providers
)
# Inferens
result = session.run(None, {"input": input_data})
```
### OpenVINO Model Server (OVMS) pa Edge
For Azure Arc/Azure Local-miljoer der Intel-hardware brukes:
```yaml
# ovms-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: ovms-inference
spec:
replicas: 2
template:
spec:
containers:
- name: ovms
image: openvino/model_server:latest
ports:
- containerPort: 9000 # gRPC
- containerPort: 8000 # REST
volumeMounts:
- name: model-store
mountPath: /models
env:
- name: MODELS_CONFIG
value: "/models/config.json"
resources:
limits:
cpu: "4"
memory: "8Gi"
volumes:
- name: model-store
persistentVolumeClaim:
claimName: model-pvc
```
### vLLM for LLM-inferens
KAITO bruker vLLM som standard inferensmotor for LLM-er:
| Funksjon | Beskrivelse | Fordel |
|----------|-------------|--------|
| PagedAttention | Effektiv KV-cache-haandtering | 2-4x gjennomstromning |
| Continuous batching | Dynamisk batching av foresporsler | Redusert latens |
| Tensor parallelism | Fordel modell over GPUer | Storre modeller |
| Quantization support | AWQ, GPTQ, SqueezeLLM | Lavere minnebruk |
| OpenAI-compatible API | Standard API-format | Enkel integrasjon |
---
## Caching Patterns for Edge
### Modellcaching-strategier
| Strategi | Implementasjon | Bruksomrade |
|----------|---------------|-------------|
| Model preloading | Last modell i minne ved oppstart | Sanntids inferens |
| TensorRT engine cache | Cach kompilerte TRT-motorer | GPU-inferens |
| ONNX session cache | Gjenbruk ORT-sesjoner | Repetitive inferenser |
| Result caching | Redis/memcached for resultater | Identiske inputs |
| Embedding cache | Cach vektorrepresentasjoner | RAG pa edge |
### Resultatchaching med Redis pa Edge
```yaml
# redis-cache-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: inference-cache
spec:
template:
spec:
containers:
- name: redis
image: redis:7-alpine
ports:
- containerPort: 6379
resources:
limits:
memory: "2Gi"
args:
- "--maxmemory"
- "1.5gb"
- "--maxmemory-policy"
- "allkeys-lru"
```
```python
# Inferens med caching
import redis
import hashlib
import json
cache = redis.Redis(host='inference-cache', port=6379)
def cached_inference(model_session, input_data, ttl=3600):
# Generer cache-nokkel fra input
cache_key = hashlib.sha256(
json.dumps(input_data, sort_keys=True).encode()
).hexdigest()
# Sjekk cache
cached = cache.get(cache_key)
if cached:
return json.loads(cached)
# Kjor inferens
result = model_session.run(None, input_data)
# Lagre i cache
cache.setex(cache_key, ttl, json.dumps(result.tolist()))
return result
```
### KV-cache for LLM-er
For LLM-inferens pa edge er KV-cache (key-value cache) kritisk:
| Teknikk | Beskrivelse | Minnebesparelse |
|---------|-------------|-----------------|
| Standard KV-cache | Full cache for alle tokens | Baseline |
| Sliding window | Begrens cache til siste N tokens | 50-80% |
| Grouped-query attention | Faerre KV-hoder | 4-8x |
| PagedAttention (vLLM) | Sidert minnehaandtering | Dynamisk |
---
## Batching vs. Streaming Inference
### Sammenligning
| Egenskap | Batch Inference | Streaming Inference |
|----------|----------------|---------------------|
| Latens | Hoyere (venter pa batch) | Lavere (umiddelbar) |
| Gjennomstromning | Hoyere (GPU-utnyttelse) | Lavere (per request) |
| GPU-utnyttelse | Optimal (fyller batch) | Variabel |
| Bruksomrade | Dokumentanalyse, batch-scoring | Chat, real-time |
| Skalering | Horisontal (flere workers) | Vertikal (GPU-kapasitet) |
### Beslutningstre
```
Trenger du sanntidssvar (<100ms)?
├── Ja → Streaming inference
│ ├── Enkelt request → Single request pipeline
│ └── Flere samtidige → Continuous batching (vLLM)
└── Nei → Batch inference
├── <1000 elementer → Micro-batch pa GPU
└── >1000 elementer → Azure ML Batch Endpoints
```
### Batch Inference pa Edge
```python
import numpy as np
from collections import deque
import threading
import time
class EdgeBatchInferencer:
def __init__(self, session, batch_size=8, max_wait_ms=50):
self.session = session
self.batch_size = batch_size
self.max_wait = max_wait_ms / 1000
self.queue = deque()
self.lock = threading.Lock()
def infer(self, input_data):
"""Legg til i koe og vent pa batch-resultat."""
event = threading.Event()
result_container = {}
with self.lock:
self.queue.append((input_data, event, result_container))
# Trigger batch hvis full
if len(self.queue) >= self.batch_size:
self._process_batch()
# Vent pa resultat (med timeout)
event.wait(timeout=self.max_wait * 2)
return result_container.get('result')
def _process_batch(self):
"""Prosesser akkumulerte inputs som en batch."""
with self.lock:
items = []
while self.queue and len(items) < self.batch_size:
items.append(self.queue.popleft())
if not items:
return
# Kombiner inputs til batch
batch_input = np.stack([item[0] for item in items])
# Kjor batch-inferens
batch_results = self.session.run(
None, {"input": batch_input}
)
# Distribuer resultater
for i, (_, event, container) in enumerate(items):
container['result'] = batch_results[0][i]
event.set()
```
### Streaming Inference for LLM pa Edge
```python
# Streaming med vLLM-kompatibelt API (KAITO)
import requests
import json
def stream_inference(prompt, service_ip, max_tokens=500):
"""Stream tokens fra lokal LLM pa edge."""
response = requests.post(
f"http://{service_ip}/v1/completions",
json={
"model": "phi-4-mini-instruct",
"prompt": prompt,
"max_tokens": max_tokens,
"stream": True
},
stream=True
)
for line in response.iter_lines():
if line:
data = line.decode('utf-8')
if data.startswith('data: '):
chunk = json.loads(data[6:])
if chunk.get('choices'):
token = chunk['choices'][0].get('text', '')
yield token
```
---
## IoT Edge ML Inference Pattern
### Azure IoT Edge med dynamisk modellasting
```
┌─────────────────────────────────────────┐
│ Azure Cloud │
│ ┌──────────┐ ┌──────────────────┐ │
│ │ Blob │ │ IoT Hub │ │
│ │ Storage │ │ (Device Twins) │ │
│ │ (Models) │ │ │ │
│ └────┬─────┘ └────────┬─────────┘ │
└───────┼───────────────────┼─────────────┘
│ │
│ ┌───────────────▼──────────┐
│ │ IoT Edge Runtime │
│ │ ┌─────────────────────┐ │
└───┼─►│ Model Loader Module │ │
│ └──────────┬──────────┘ │
│ ┌──────────▼──────────┐ │
│ │ Inference Module │ │
│ │ (ONNX/LiteRT) │ │
│ └──────────┬──────────┘ │
│ ┌──────────▼──────────┐ │
│ │ Local Storage │ │
│ │ (Model Cache) │ │
│ └─────────────────────┘ │
└──────────────────────────┘
```
### Device Twin-basert modellstyring
```python
# Motta modelloppdatering via IoT Edge Device Twin
from azure.iot.device import IoTHubModuleClient
async def twin_update_handler(patch):
if 'model_version' in patch:
model_url = patch['model_url']
model_version = patch['model_version']
checksum = patch['model_checksum']
# Last ned ny modell
await download_model(model_url, checksum)
# Hot-swap modell uten nedetid
await reload_model(model_version)
# Rapporter tilbake
reported = {
"active_model_version": model_version,
"model_loaded_at": datetime.utcnow().isoformat()
}
client.patch_twin_reported_properties(reported)
```
---
## Ytelsesreferanser
### Typiske inferenstider pa Microsoft edge-hardware
| Hardware | Modell | Oppgave | Latens (ms) |
|----------|--------|---------|-------------|
| Azure Stack Edge Pro GPU (T4) | YOLOv8 | Objektdeteksjon | 8-15 |
| Azure Stack Edge Pro GPU (T4) | ResNet-50 | Bildeklassifisering | 3-5 |
| Azure Local (A2) | Phi-3-mini-4k | Tekst (128 tokens) | 200-400 |
| Azure Local (L4) | Phi-4-mini | Tekst (128 tokens) | 80-150 |
| Azure Local (L40S) | Mistral-7B | Tekst (128 tokens) | 50-100 |
| Intel CPU (Xeon) + OpenVINO | BERT-base | NER | 5-10 |
| CPU (ONNX Runtime) | DistilBERT | Sentiment | 3-8 |
---
## For Cosmo
- **ONNX Runtime er universalmotoren for edge-inferens** — stotter CPU, GPU, NPU via Execution Providers, med TensorRT for NVIDIA og OpenVINO for Intel som de viktigste akseleratorene.
- **Kvantisering (INT8/INT4) er den enkleste og mest effektive optimaliseringen** — reduserer modellstorrelse 2-8x med minimalt noyaktighetstap, spesielt viktig for edge-enheter med begrenset minne.
- **Velg batching for gjennomstromning, streaming for latens** — continuous batching (vLLM/KAITO) gir det beste av begge for LLM-inferens pa edge Kubernetes-klynger.
- **Caching pa flere nivaer er essensielt for edge-ytelse** — TensorRT engine cache, resultat-cache (Redis), og KV-cache for LLM-er reduserer bade latens og GPU-belastning.
- **IoT Edge med Device Twins gir skalerbar modellstyring** for distribuerte edge-enheter — modellversjonering, inkrementell oppdatering og hot-swap uten nedetid.

View file

@ -0,0 +1,443 @@
# Edge-to-Cloud Data Synchronization
**Last updated:** 2026-02
**Status:** GA
**Category:** Hybrid Cloud & Edge AI
---
## Introduksjon
Palitelig datasynkronisering mellom edge og sky er en av de mest komplekse utfordringene i hybrid AI-arkitekturer. Data ma flyte i begge retninger — sensordata og inferensresultater fra edge til sky for langsiktig analyse og modelltrening, og oppdaterte modeller og konfigurasjoner fra sky tilbake til edge. Alt dette ma handtere nettverksavbrudd, konflikter og dataintegritet.
For norsk offentlig sektor er palitelig synkronisering kritisk: inspeksjonsdata fra felt ma garantert na sentrale systemer, AI-modellsoppdateringer ma distribueres til alle edge-stasjoner uten manuell intervensjon, og logging for compliance-formal ma vaere komplett — selv etter langvarige offline-perioder.
Microsoft tilbyr flere synkroniseringsmekanismer: Azure IoT Edge med utvidet offline-stoette (ubegrenset offline-tid med lokal buffring), Azure Container Storage med automatisk sky-sync, Azure Cosmos DB med multi-region replikering, og Azure IoT Hub device twins for konfigurasjonssynkronisering. Valget avhenger av datamengde, konsistenskrav og tilkoblingsprofil.
---
## Kjernekomponenter
| Komponent | Formal | Teknologi |
|-----------|--------|-----------|
| Azure IoT Edge | Utvidet offline med meldingsbuffring | Container runtime |
| Azure Container Storage | Lokal lagring med automatisk sky-sync | Arc-enabled |
| Azure Cosmos DB | Multi-region database med konflikthondtering | NoSQL / SQL API |
| IoT Hub Device Twins | Konfigurasjonssynkronisering enhet-sky | PaaS |
| Event Hub | Hoeyvolum event-inntak | Event streaming |
| Azure Data Lake | Langsiktig datalagring | Storage Gen2 |
| Delta Lake | ACID-transaksjoner pa datalake | Open source |
---
## Eventual Consistency Patterns
### Konsistensmodeller for edge-sky
| Modell | Beskrivelse | Latens | Brukstilfelle |
|--------|-------------|--------|---------------|
| Sterk konsistens | Alle noder ser samme data samtidig | Hoey | Kritiske transaksjoner |
| Bounded staleness | Data er konsistent innen et tidsvindu | Medium | Nesten-sanntid dashboards |
| Session konsistens | Konsistens innen en enhet-session | Lav | Brukerinteraksjon |
| Eventuell konsistens | Data konvergerer over tid | Lavest | Sensordata, logger |
### Azure Cosmos DB for edge-sky-synkronisering
```python
# Azure Cosmos DB med konfigurerbar konsistens for edge-sky sync
from azure.cosmos import CosmosClient, PartitionKey
from azure.cosmos.documents import ConsistencyLevel
class EdgeCloudSyncStore:
def __init__(self, endpoint: str, key: str):
self.client = CosmosClient(
endpoint, key,
consistency_level=ConsistencyLevel.Session # Bra for edge-sky
)
self.database = self.client.get_database_client("edge-ai-data")
def setup_containers(self):
"""Opprett containere for synkronisert data"""
# Sensordata: Eventuell konsistens, hoey throughput
self.sensor_container = self.database.create_container_if_not_exists(
id="sensor-data",
partition_key=PartitionKey(path="/deviceId"),
default_ttl=86400 * 30 # 30 dagers retention
)
# AI-resultater: Session konsistens
self.ai_results = self.database.create_container_if_not_exists(
id="ai-results",
partition_key=PartitionKey(path="/deviceId"),
default_ttl=86400 * 365 # 1 aars retention
)
# Modellkonfigurasjon: Sterkere konsistens
self.model_config = self.database.create_container_if_not_exists(
id="model-config",
partition_key=PartitionKey(path="/region")
)
def upsert_sensor_data(self, device_id: str, readings: list[dict]):
"""Skriv sensordata med idempotensnokkel for a haandtere re-sync"""
for reading in readings:
reading["id"] = f"{device_id}_{reading['timestamp']}"
reading["deviceId"] = device_id
reading["_etag"] = None # Cosmos DB haandterer versjonering
self.sensor_container.upsert_item(
body=reading,
pre_trigger_include=None,
post_trigger_include=None
)
def get_latest_model_config(self, region: str) -> dict:
"""Hent siste modellkonfigurasjon for en region"""
query = """
SELECT TOP 1 *
FROM c
WHERE c.region = @region
ORDER BY c.updatedAt DESC
"""
items = list(self.model_config.query_items(
query=query,
parameters=[{"name": "@region", "value": region}],
enable_cross_partition_query=False
))
return items[0] if items else None
```
### Event-basert synkronisering
```python
# Event-basert edge-to-cloud synkronisering
import asyncio
import json
import gzip
from datetime import datetime, timedelta
class EventBasedSync:
def __init__(self, local_store, cloud_endpoint: str):
self.local_store = local_store
self.cloud_endpoint = cloud_endpoint
self.sync_log = []
self.last_sync = None
async def sync_outbound(self, max_batch_size: int = 100) -> dict:
"""Synkroniser lokale hendelser til sky"""
# Hent usynkroniserte hendelser
pending = self.local_store.get_unsynced_events(limit=max_batch_size)
if not pending:
return {"status": "up_to_date", "synced": 0}
# Komprimer for overfoering
payload = gzip.compress(
json.dumps([e.__dict__ for e in pending]).encode()
)
try:
# Send til sky-endpoint
response = await self._send_to_cloud(payload)
if response["status"] == "accepted":
# Marker som synkronisert
event_ids = [e.id for e in pending]
self.local_store.mark_synced(event_ids)
self.last_sync = datetime.utcnow()
self.sync_log.append({
"direction": "outbound",
"events": len(event_ids),
"size_bytes": len(payload),
"timestamp": self.last_sync.isoformat()
})
return {
"status": "synced",
"synced": len(event_ids),
"remaining": self.local_store.count_unsynced(),
"compressed_size": len(payload)
}
except ConnectionError:
return {
"status": "offline",
"pending": len(pending),
"retry_after": "next_connectivity"
}
async def sync_inbound(self) -> dict:
"""Hent oppdateringer fra sky (modeller, konfigurasjon)"""
try:
since = self.last_sync or datetime.utcnow() - timedelta(days=7)
updates = await self._fetch_from_cloud(since)
applied = 0
for update in updates:
if update["type"] == "model_update":
await self._apply_model_update(update)
applied += 1
elif update["type"] == "config_change":
await self._apply_config_change(update)
applied += 1
return {"status": "updated", "applied": applied}
except ConnectionError:
return {"status": "offline", "using_cached": True}
```
---
## Delta Sync Optimization
### Inkrementell synkronisering
```python
# Delta-synkronisering for effektiv dataoverfoering
import hashlib
import json
from typing import Optional
class DeltaSyncEngine:
def __init__(self):
self.local_checksums: dict[str, str] = {}
self.sync_watermark: Optional[str] = None
def calculate_delta(self, current_data: dict,
last_synced_data: dict) -> dict:
"""Beregn delta mellom navaerende og sist synkronisert tilstand"""
delta = {
"added": {},
"modified": {},
"deleted": []
}
# Finn nye og endrede elementer
for key, value in current_data.items():
current_hash = self._hash_value(value)
if key not in last_synced_data:
delta["added"][key] = value
elif self._hash_value(last_synced_data[key]) != current_hash:
delta["modified"][key] = value
# Finn slettede elementer
for key in last_synced_data:
if key not in current_data:
delta["deleted"].append(key)
return delta
def apply_delta(self, base_data: dict, delta: dict) -> dict:
"""Anvend delta pa basisdatasettet"""
result = dict(base_data)
# Legg til nye
result.update(delta.get("added", {}))
# Oppdater endrede
result.update(delta.get("modified", {}))
# Fjern slettede
for key in delta.get("deleted", []):
result.pop(key, None)
return result
def compress_delta(self, delta: dict) -> bytes:
"""Komprimer delta for overfoering"""
import gzip
json_bytes = json.dumps(delta, separators=(',', ':')).encode()
compressed = gzip.compress(json_bytes, compresslevel=9)
return compressed
def get_sync_stats(self, delta: dict, compressed: bytes) -> dict:
"""Beregn synkroniseringsstatistikk"""
full_size = len(json.dumps(delta).encode())
return {
"items_changed": (
len(delta.get("added", {})) +
len(delta.get("modified", {})) +
len(delta.get("deleted", []))
),
"full_size_bytes": full_size,
"compressed_size_bytes": len(compressed),
"compression_ratio": f"{(1 - len(compressed)/full_size)*100:.1f}%"
if full_size > 0 else "N/A"
}
def _hash_value(self, value) -> str:
return hashlib.sha256(json.dumps(value, sort_keys=True).encode()).hexdigest()[:16]
```
---
## Conflict Resolution Strategies
### Konflikttyper i edge-sky-synkronisering
| Konflikttype | Arsak | Losningsstrategi |
|-------------|-------|-----------------|
| Write-Write | Samme data endret pa bade edge og sky | LWW eller custom merge |
| Delete-Update | Data slettet pa en side, oppdatert pa annen | Policy-basert (behold eller slett) |
| Schema-conflict | Modellversjon ulik pa edge og sky | Versjonert schema med migrasjon |
| Ordering-conflict | Hendelser mottat i feil rekkefolge | Timestamp-basert reordering |
### Cosmos DB konflikthondtering
```python
# Cosmos DB konflikthondterings-policy
from azure.cosmos import ContainerProxy
class CosmosConflictHandler:
def __init__(self, container: ContainerProxy):
self.container = container
def setup_lww_policy(self):
"""Last-Write-Wins basert pa egendefinert felt"""
# Konfigureres ved container-oppretting
# Cosmos DB bruker _ts (timestamp) som default
pass
def setup_custom_resolution(self):
"""Custom konflikthondtering med stored procedure"""
sproc_body = """
function resolve(incomingItem, existingItem, isTombstone, conflictingItems) {
// For AI-resultater: Behold den med hoeyest confidence
if (incomingItem.ai_confidence > existingItem.ai_confidence) {
return incomingItem;
}
return existingItem;
}
"""
self.container.scripts.create_stored_procedure({
"id": "resolveConflict",
"body": sproc_body
})
def read_conflict_feed(self) -> list[dict]:
"""Les konflikter som krever manuell losning"""
conflicts = list(self.container.list_conflicts())
return [{
"id": c["id"],
"resource_id": c.get("resourceId"),
"conflict_type": c.get("operationType"),
"source_region": c.get("sourceResourceId")
} for c in conflicts]
```
---
## Data Deduplication at Scale
### Dedupliseringsstrategier
```python
# Skalerbar deduplisering for edge-sky-data
import hashlib
from bloom_filter2 import BloomFilter
class EdgeDeduplication:
def __init__(self, expected_items: int = 1_000_000):
# Bloom-filter for hurtig duplikat-sjekk (minneeffektivt)
self.bloom = BloomFilter(
max_elements=expected_items,
error_rate=0.01 # 1% falsk-positiv rate
)
# Eksakt sjekk for bloom-positive
self.recent_hashes: set = set()
self.max_recent = 100_000
def is_duplicate(self, data: dict) -> bool:
"""Sjekk om dataelementet allerede er prosessert"""
data_hash = self._compute_hash(data)
# Hurtig bloom-filter-sjekk
if data_hash not in self.bloom:
return False
# Eksakt sjekk for bekreftelse
return data_hash in self.recent_hashes
def mark_processed(self, data: dict):
"""Marker dataelement som prosessert"""
data_hash = self._compute_hash(data)
self.bloom.add(data_hash)
self.recent_hashes.add(data_hash)
# Begrens minnebruk
if len(self.recent_hashes) > self.max_recent:
# Fjern eldste 20%
to_remove = len(self.recent_hashes) - int(self.max_recent * 0.8)
for _ in range(to_remove):
self.recent_hashes.pop()
def _compute_hash(self, data: dict) -> str:
"""Beregn deterministisk hash av dataelementet"""
# Bruk innholds-hash (ekskluder metadata som timestamp)
content_keys = sorted(k for k in data.keys()
if k not in ("_ts", "synced_at", "sync_id"))
content = {k: data[k] for k in content_keys}
return hashlib.sha256(
json.dumps(content, sort_keys=True).encode()
).hexdigest()
def get_stats(self) -> dict:
return {
"bloom_filter_items": len(self.bloom),
"recent_exact_items": len(self.recent_hashes),
"estimated_memory_mb": (
self.bloom.bitarray.nbytes / 1024 / 1024 +
len(self.recent_hashes) * 64 / 1024 / 1024
)
}
```
---
## Norsk offentlig sektor
### Synkroniseringskrav for offentlig sektor
| Krav | Beskrivelse | Losning |
|------|-------------|---------|
| Dataintegritet | Ingen datatap ved offline/sync | Event sourcing + idempotens |
| Sporbarhet | All synkronisering ma logges | Sync audit log |
| Personvern | Sensitive data ma krypteres i transit | TLS 1.3 + end-to-end |
| Compliance | 7 ars retention for visse datatyper | Immutable storage |
| Konflikthondtering | Sporbar og deterministisk | Policy-basert med audit trail |
### Anbefalte Azure-tjenester per scenario
| Scenario | Primaer-tjeneste | Sekundaer | Konsistens |
|----------|-----------------|-----------|------------|
| IoT-sensordata | IoT Hub + Event Hub | Data Lake | Eventuell |
| AI-resultater | Cosmos DB | Data Lake backup | Session |
| Modellkonfig | IoT Hub Device Twin | Git (GitOps) | Sterk |
| Inspeksjonsdata | Cosmos DB | Blob Storage | Bounded staleness |
---
## Beslutningsrammeverk
| Scenario | Anbefaling | Begrunnelse |
|----------|------------|-------------|
| Hoeyvolum sensorer, enveis | IoT Hub → Event Hub → Data Lake | Skalerbart, rimelig |
| Toveis med konfliktfare | Cosmos DB med session-konsistens | Innebygd konflikthondtering |
| Kritisk data, null tap | Event sourcing + Cosmos DB | Idempotent, sporbar |
| Periodisk bulk-sync | Delta sync + Azure Blob | Minimal bandwidth |
| Multi-edge koordinering | Cosmos DB multi-write | Automatisk konflikthondtering |
| Modellpush til edge | IoT Hub Device Twin + Blob SAS | Etablert monster |
---
## For Cosmo
- **Event sourcing med idempotens er gullstandarden** for edge-sky-synkronisering — alle dataelementer faar en unik ID og kan trygt re-sendes uten duplikater
- **Delta-synkronisering reduserer datavolum med 80-95%** sammenlignet med full sync — beregn kun endringer og komprimer med gzip for minimal bandbreddebruk
- **Cosmos DB med session-konsistens er den beste balansen** mellom ytelse og dataintegritet for de fleste edge-sky-scenarier i offentlig sektor
- **Bloom-filter gir O(1) dedupliseringssjekk** med minimal minnebruk — implementer dette pa bade edge og sky-siden for a hindre duplikat-inntak
- **For norsk offentlig sektor: Krav til sporbarhet og retention betyr at ALL synkronisering ma logges** — implementer sync audit log med 7 ars immutable retention for compliance med arkivloven

View file

@ -0,0 +1,448 @@
# Hybrid RAG Architecture
**Last updated:** 2026-02
**Status:** GA
**Category:** Hybrid Cloud & Edge AI
---
## Introduksjon
Hybrid RAG (Retrieval-Augmented Generation) refererer til RAG-arkitekturer der retrieval og generering fordeles mellom lokale (on-premises/edge) og sky-baserte ressurser. Dette moensteret er relevant nar organisasjoner har data som ikke kan eller bor forlate det lokale miljoet, men onsker a kombinere lokale dokumenter med sky-basert kunnskap for bedre svar.
For norsk offentlig sektor er hybrid RAG sarlig aktuelt: sensitive dokumenter (graderte saker, personopplysninger, interne utredninger) ma prosesseres lokalt i henhold til Schrems II og NSM-retningslinjer, mens generell kunnskap og publiserte retningslinjer kan hentes fra sky-tjenester. Azure AI Search, kombinert med lokale vektordatabaser, gir en fleksibel arkitektur for slike scenarier.
Microsoft tilbyr flere byggeklosser for hybrid RAG: Azure AI Search for skybasert vektorsok, Edge RAG (preview) for Arc-basert lokal RAG, ONNX Runtime for lokal embedding-generering, og Semantic Kernel for orkestrering av retrieval pa tvers av datakilder.
---
## Kjernekomponenter
| Komponent | Formal | Teknologi |
|-----------|--------|-----------|
| Azure AI Search | Skybasert vektorsok og hybrid search | PaaS (GA) |
| Edge RAG | Lokal RAG pa Arc-enabled Kubernetes | Azure Arc (Preview) |
| Local Vector DB | Lokal vektorlagring for sensitive data | ChromaDB, Qdrant, pgvector |
| Embedding Model | Generering av vektorrepresentasjoner | Azure OpenAI, Phi-3/4, ONNX |
| Semantic Kernel | Orkestrering av hybrid retrieval | .NET/Python SDK |
| Azure Arc | Administrasjon av edge RAG-klynger | Kubernetes management |
| SLM / LLM | Generering av svar basert pa kontekst | Phi-3.5/Phi-4, GPT-4o |
---
## Local Embedding og Retrieval
### Lokal embedding med ONNX Runtime
For sensitive data som ikke kan sendes til sky-tjenester, kan embeddings genereres lokalt:
```python
# Lokal embedding-generering med ONNX Runtime
import onnxruntime as ort
import numpy as np
from transformers import AutoTokenizer
class LocalEmbeddingService:
def __init__(self, model_path: str, tokenizer_name: str):
self.session = ort.InferenceSession(
model_path,
providers=['CUDAExecutionProvider', 'CPUExecutionProvider']
)
self.tokenizer = AutoTokenizer.from_pretrained(tokenizer_name)
def embed(self, texts: list[str]) -> np.ndarray:
"""Generer embeddings lokalt uten sky-avhengighet"""
encoded = self.tokenizer(
texts,
padding=True,
truncation=True,
max_length=512,
return_tensors="np"
)
outputs = self.session.run(
None,
{
"input_ids": encoded["input_ids"].astype(np.int64),
"attention_mask": encoded["attention_mask"].astype(np.int64)
}
)
# Mean pooling
embeddings = outputs[0]
mask = encoded["attention_mask"][:, :, np.newaxis]
pooled = (embeddings * mask).sum(axis=1) / mask.sum(axis=1)
# Normalisering
norms = np.linalg.norm(pooled, axis=1, keepdims=True)
return pooled / norms
def embed_single(self, text: str) -> np.ndarray:
return self.embed([text])[0]
```
### Lokal vektordatabase med ChromaDB
```python
# Lokal vektordatabase for sensitive dokumenter
import chromadb
from chromadb.config import Settings
class LocalVectorStore:
def __init__(self, persist_directory: str):
self.client = chromadb.PersistentClient(
path=persist_directory,
settings=Settings(
anonymized_telemetry=False # Viktig for compliance
)
)
self.collection = self.client.get_or_create_collection(
name="sensitive_documents",
metadata={"hnsw:space": "cosine"}
)
def add_documents(self, documents: list[dict], embeddings: np.ndarray):
"""Indekser dokumenter med pre-beregnede embeddings"""
self.collection.add(
ids=[doc["id"] for doc in documents],
embeddings=embeddings.tolist(),
documents=[doc["content"] for doc in documents],
metadatas=[{
"source": doc["source"],
"classification": doc["classification"],
"timestamp": doc["timestamp"]
} for doc in documents]
)
def search(self, query_embedding: np.ndarray, n_results: int = 5,
classification_filter: str = None) -> list[dict]:
"""Sok med valgfri klassifiseringsfiltrering"""
where_filter = None
if classification_filter:
where_filter = {"classification": classification_filter}
results = self.collection.query(
query_embeddings=[query_embedding.tolist()],
n_results=n_results,
where=where_filter,
include=["documents", "metadatas", "distances"]
)
return [{
"content": doc,
"metadata": meta,
"score": 1 - dist # Konverter avstand til likhetsscore
} for doc, meta, dist in zip(
results["documents"][0],
results["metadatas"][0],
results["distances"][0]
)]
```
---
## Federated Vector Search
### Arkitektur for foderasjon
Federated vector search kombinerer resultater fra flere vektordatabaser — bade lokale og skybaserte — uten a flytte sensitive data:
```
[Bruker-query]
[Embedding Service (lokal)]
[Federated Search Router]
├── [Lokal VektorDB] → Sensitive dokumenter
├── [Azure AI Search] → Publiserte retningslinjer
└── [Edge RAG Cluster] → Avdelingsdata
[Result Merger & Ranker]
[LLM/SLM Generering]
[Svar til bruker]
```
### Implementering med Semantic Kernel
```csharp
// Federated RAG med Semantic Kernel
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Memory;
public class FederatedRagService
{
private readonly IMemoryStore _localStore;
private readonly IMemoryStore _cloudStore;
private readonly ITextEmbeddingGenerationService _localEmbedding;
private readonly Kernel _kernel;
public FederatedRagService(
IMemoryStore localStore,
IMemoryStore cloudStore,
ITextEmbeddingGenerationService localEmbedding,
Kernel kernel)
{
_localStore = localStore;
_cloudStore = cloudStore;
_localEmbedding = localEmbedding;
_kernel = kernel;
}
public async Task<string> QueryAsync(string question, SearchOptions options)
{
// Generer embedding lokalt
var queryEmbedding = await _localEmbedding
.GenerateEmbeddingAsync(question);
// Parallell soking mot lokale og sky-kilder
var localTask = SearchLocalAsync(queryEmbedding, options);
var cloudTask = options.AllowCloudSearch
? SearchCloudAsync(question, options)
: Task.FromResult(new List<SearchResult>());
await Task.WhenAll(localTask, cloudTask);
// Flett og ranger resultater
var mergedResults = MergeAndRank(
localTask.Result,
cloudTask.Result,
options.MaxResults
);
// Generer svar med lokal SLM eller sky-LLM
return await GenerateResponseAsync(question, mergedResults, options);
}
private List<SearchResult> MergeAndRank(
List<SearchResult> localResults,
List<SearchResult> cloudResults,
int maxResults)
{
// Reciprocal Rank Fusion for a kombinere resultater
var fusedScores = new Dictionary<string, double>();
int rank = 1;
foreach (var result in localResults.OrderByDescending(r => r.Score))
{
fusedScores[result.Id] = 1.0 / (60 + rank);
rank++;
}
rank = 1;
foreach (var result in cloudResults.OrderByDescending(r => r.Score))
{
var id = result.Id;
fusedScores[id] = fusedScores.GetValueOrDefault(id, 0)
+ 1.0 / (60 + rank);
rank++;
}
return fusedScores
.OrderByDescending(kv => kv.Value)
.Take(maxResults)
.Select(kv => /* map back to SearchResult */)
.ToList();
}
}
```
---
## Chunking Strategies for Split Data
### Tilpasset chunking for hybrid miljoer
Nar data er fordelt mellom lokale og skybaserte lagre, ma chunking-strategien ivareta kontekstuell sammenheng pa tvers av tier:
| Strategi | Lokale data | Skydata | Brukstilfelle |
|----------|-------------|---------|---------------|
| Fixed-size chunks | 512 tokens | 1024 tokens | Generell bruk |
| Semantic chunking | Avsnitt/seksjon | Avsnitt/seksjon | Strukturerte dokumenter |
| Hierarchical chunking | Dokument → Seksjon → Avsnitt | Artikkel → Paragraf | Regelverk, utredninger |
| Sliding window | 256 tokens, 64 overlap | 512 tokens, 128 overlap | Teknisk dokumentasjon |
| Parent-child | Lagre parent lokal, child i vektor | Lagre parent i blob, child i Search | Lange dokumenter |
### Implementering av hierarkisk chunking
```python
# Hierarkisk chunking for norske offentlige dokumenter
from dataclasses import dataclass
from typing import Optional
import re
@dataclass
class Chunk:
id: str
content: str
level: str # "document", "section", "paragraph"
parent_id: Optional[str]
metadata: dict
class HierarchicalChunker:
def __init__(self, max_chunk_tokens: int = 512):
self.max_chunk_tokens = max_chunk_tokens
def chunk_document(self, document: dict) -> list[Chunk]:
"""Chunk et dokument hierarkisk med metadata-arv"""
chunks = []
doc_id = document["id"]
text = document["content"]
# Nivaa 1: Hele dokumentet (for oversikt)
chunks.append(Chunk(
id=f"{doc_id}_doc",
content=self._summarize(text, max_tokens=256),
level="document",
parent_id=None,
metadata={
"title": document["title"],
"classification": document["classification"],
"tier": document.get("tier", "local")
}
))
# Nivaa 2: Seksjoner
sections = self._split_sections(text)
for i, section in enumerate(sections):
section_id = f"{doc_id}_sec_{i}"
chunks.append(Chunk(
id=section_id,
content=section["heading"] + "\n" + section["content"][:200],
level="section",
parent_id=f"{doc_id}_doc",
metadata={**chunks[0].metadata, "section": section["heading"]}
))
# Nivaa 3: Avsnitt
paragraphs = self._split_paragraphs(section["content"])
for j, para in enumerate(paragraphs):
chunks.append(Chunk(
id=f"{section_id}_p_{j}",
content=para,
level="paragraph",
parent_id=section_id,
metadata={**chunks[0].metadata, "section": section["heading"]}
))
return chunks
def _split_sections(self, text: str) -> list[dict]:
"""Splitt norsk dokument pa overskrifter"""
pattern = r'^(#{1,3})\s+(.+)$'
sections = []
current = {"heading": "Innledning", "content": ""}
for line in text.split('\n'):
match = re.match(pattern, line)
if match:
if current["content"].strip():
sections.append(current)
current = {"heading": match.group(2), "content": ""}
else:
current["content"] += line + "\n"
if current["content"].strip():
sections.append(current)
return sections
```
---
## Context Optimization Across Tiers
### Kontekstvindu-optimalisering
Nar data hentes fra flere kilder, er det kritisk a optimalisere hvordan kontekst presenteres til LLM/SLM:
```python
# Kontekstoptimalisering for hybrid RAG
class ContextOptimizer:
def __init__(self, max_context_tokens: int = 4096):
self.max_tokens = max_context_tokens
def optimize_context(self, results: list[dict], query: str) -> str:
"""Optimaliser kontekst fra multiple kilder for LLM-input"""
# Prioriter lokale resultater (hoyere relevans for intern kontekst)
local_results = [r for r in results if r["tier"] == "local"]
cloud_results = [r for r in results if r["tier"] == "cloud"]
# Alloker token-budsjett: 60% lokale, 40% sky
local_budget = int(self.max_tokens * 0.6)
cloud_budget = int(self.max_tokens * 0.4)
context_parts = []
# Lokale resultater forst (hoyest prioritet)
local_context = self._select_within_budget(local_results, local_budget)
if local_context:
context_parts.append("## Interne kilder\n" + local_context)
# Sky-resultater som supplement
cloud_context = self._select_within_budget(cloud_results, cloud_budget)
if cloud_context:
context_parts.append("## Offentlige kilder\n" + cloud_context)
return "\n\n".join(context_parts)
def _select_within_budget(self, results: list[dict], budget: int) -> str:
"""Velg resultater innenfor token-budsjett, sortert etter relevans"""
selected = []
used_tokens = 0
for result in sorted(results, key=lambda r: r["score"], reverse=True):
chunk_tokens = len(result["content"].split()) * 1.3 # Estimert
if used_tokens + chunk_tokens > budget:
break
selected.append(
f"[{result['metadata'].get('title', 'Ukjent')}]\n{result['content']}"
)
used_tokens += chunk_tokens
return "\n---\n".join(selected)
```
---
## Norsk offentlig sektor
### Dataklassifisering for hybrid RAG
| Klassifisering | Lagring | Embedding | LLM | Eksempel |
|----------------|---------|-----------|-----|----------|
| Ugradert offentlig | Azure AI Search | Azure OpenAI | GPT-4o | Publiserte retningslinjer |
| Intern | Lokal vektorDB | Lokal ONNX | Phi-3.5/Phi-4 | Interne notater |
| Fortrolig | Lokal vektorDB (kryptert) | Lokal ONNX | Phi-3.5 lokal | Personopplysninger |
| Strengt fortrolig | Air-gapped lokal | Lokal ONNX | Lokal SLM | Graderte dokumenter |
### Schrems II-kompatibel arkitektur
- Sensitive persondata embeddes og lagres kun lokalt
- Kun aggregerte, anonymiserte metadata kan deles med sky-tjenester
- Azure AI Search brukes for offentlig tilgjengelig informasjon
- Edge RAG (Azure Arc) gir sky-management uten a eksponere innhold
---
## Beslutningsrammeverk
| Scenario | Anbefaling | Begrunnelse |
|----------|------------|-------------|
| Alle data kan i sky | Azure AI Search (agentic retrieval) | Enklest, best ytelse |
| Mix av sensitiv + offentlig | Federated RAG med Semantic Kernel | Balanserer sikkerhet og kvalitet |
| Alle data ma lokalt | Edge RAG med Phi-3.5 + ChromaDB | Full datakontroll |
| Lavt volum, hoy sensitivitet | Lokal RAG med Phi-4 + pgvector | Minimal attack surface |
| Hoy skala, lav sensitivitet | Azure AI Search + GPT-4o | Best kvalitet og skalerbarhet |
| Periodisk tilkobling | Edge RAG med synkronisert referansedata | Offline-forst-tilnaerming |
---
## For Cosmo
- **Hybrid RAG er den riktige arkitekturen nar data har ulik sensitivitet** — bruk federated search med Reciprocal Rank Fusion for a kombinere resultater fra lokale og skybaserte vektordatabaser uten a flytte sensitive data
- **Edge RAG (Azure Arc) er Microsofts foretrukne losning** for on-premises RAG med sky-administrasjon — anbefal dette for organisasjoner som onsker hybrid RAG med sentral management
- **Lokal embedding er nodvendig for sensitive data** — bruk ONNX Runtime med en liten embedding-modell (f.eks. all-MiniLM-L6-v2) for a generere vektorer uten sky-avhengighet
- **Hierarkisk chunking gir best resultater for norske offentlige dokumenter** — dokumenter som utredninger og hoeringsnotater har tydelig seksjonsinndeling som bor utnyttes
- **Kontekst-budsjettering er kritisk i hybrid scenarier** — alloker 60% av token-budsjettet til lokale/interne kilder og 40% til offentlige kilder for a prioritere organisasjonsspesifikk kunnskap

View file

@ -0,0 +1,378 @@
# IoT Operations and AI Integration
**Last updated:** 2026-02
**Status:** GA
**Category:** Hybrid Cloud & Edge AI
---
## Introduksjon
Azure IoT Operations er Microsofts edge runtime-plattform for industrielle IoT-scenarier, bygget pa Azure Arc-enabled Kubernetes. Den kombinerer datainnsamling fra sensorer og utstyr med AI-inferens direkte pa edge, noe som muliggjor sanntidsanalyse uten avhengighet av skytilkobling for tidskritiske beslutninger.
For norsk offentlig sektor er IoT-integrasjon med AI relevant i scenarier som smart infrastruktur (broer, tunneler, veier), miljooverkaking, energistyring i offentlige bygg, og transportlogistikk. Azure IoT Operations gir en standardisert plattform for a samle sensordata, normalisere dem, og kjore AI-modeller lokalt for prediktiv vedlikehold og anomalideteksjon.
Plattformen bygger pa MQTT-protokollen for enhetskommunikasjon, Data Flows for datatransformasjon og kontekstualisering, og Azure Arc for sentralisert administrasjon. AI-modeller kan deployes som containere pa edge-klyngen, med Azure ML for modelltrenings- og oppdateringspipeliner mellom sky og edge.
---
## Kjernekomponenter
| Komponent | Formal | Teknologi |
|-----------|--------|-----------|
| Azure IoT Operations | Edge runtime for IoT-datainnsamling og -prosessering | Arc-enabled Kubernetes |
| MQTT Broker | Meldingsinfrastruktur for enhets-til-edge-kommunikasjon | MQTT v3.1.1/v5 |
| Data Flows | Datatransformasjon, kontekstualisering og ruting | Pipelinekonfigurasjon |
| OPC UA Connector | Industriprotokoll for tilkobling til PLC-er og SCADA | OPC UA standard |
| Azure IoT Hub | Sky-endepunkt for telemetri og device management | PaaS |
| Azure Stream Analytics | Sanntids stromprosessering av IoT-data | SQL-basert query |
| Azure ML on Arc | Edge AI-modelltrenings- og inferenspipeline | Kubernetes ML |
---
## Sensor Data Normalization
### Utfordringer med sensordata
Sensordata fra industrielle miljoer er ofte heterogene — ulike protokoller (Modbus, OPC UA, MQTT), forskjellige dataformater, inkonsistente tidsserier, og varierende kvalitet. Normalisering er kritisk for at AI-modeller skal fungere palitelig.
### Normaliseringsarkitektur
```
Sensorer → OPC UA / MQTT → Azure IoT Operations → Data Flows → Normalisert output
AI-inferensmodul
Azure IoT Hub (sky)
```
### Data Flow-konfigurasjon for normalisering
```yaml
# Eksempel: Data Flow for temperatursensor-normalisering
apiVersion: connectivity.iotoperations.azure.com/v1
kind: DataFlow
metadata:
name: temperature-normalization
spec:
sources:
- type: mqtt
topic: "sensors/temperature/#"
transformations:
- type: compute
expression: |
{
"deviceId": $.topic.split('/')[2],
"timestamp": $.systemProperties.enqueuedTime,
"temperature_celsius": $.payload.value * ($.payload.unit == 'F' ? 5/9 - 32*5/9 : 1),
"quality": $.payload.quality ?? 'unknown',
"location": $.payload.metadata.location
}
- type: filter
expression: "$.temperature_celsius >= -50 AND $.temperature_celsius <= 100"
destinations:
- type: mqtt
topic: "normalized/temperature"
- type: dataLakeStorage
endpoint: "edge-datalake"
```
### Strategier for datakvalitet
| Strategi | Beskrivelse | Implementering |
|----------|-------------|----------------|
| Range-validering | Filtrer verdier utenfor forventet omrade | Data Flow filter-transformasjon |
| Interpolering | Fyll manglende verdier i tidsserier | Edge-modul med pandas/numpy |
| Deduplisering | Fjern duplikate meldinger | MQTT broker QoS + dedup-logikk |
| Tidsstempelsynkronisering | Juster klokkeforskjeller mellom enheter | NTP + Data Flow timestamp-mapping |
| Enhetskonvertering | Standardiser til SI-enheter | Data Flow compute-transformasjon |
---
## Edge Gateway AI Preprocessing
### Gateway-arkitektur
Edge gateways fungerer som intelligente mellomledd mellom sensorer og sky. De utforer forbehandling, filtrering, aggregering og initial AI-inferens for a redusere datavolum og latens.
```python
# Eksempel: Edge gateway AI-forbehandling med Azure IoT Edge
import asyncio
from azure.iot.device.aio import IoTHubModuleClient
import numpy as np
import onnxruntime as ort
class AIPreprocessingGateway:
def __init__(self):
self.module_client = None
self.anomaly_model = ort.InferenceSession("anomaly_detector.onnx")
self.buffer = []
self.buffer_size = 100
async def initialize(self):
self.module_client = IoTHubModuleClient.create_from_edge_environment()
await self.module_client.connect()
self.module_client.on_message_received = self.process_message
async def process_message(self, message):
"""Forbehandling pipeline: normalisering → anomalideteksjon → aggregering"""
data = message.data
# Trinn 1: Normalisering
normalized = self.normalize(data)
# Trinn 2: Anomalideteksjon (lokal inferens)
is_anomaly = self.detect_anomaly(normalized)
if is_anomaly:
# Send anomalier umiddelbart til sky
await self.module_client.send_message_to_output(
{"type": "anomaly", "data": normalized, "confidence": 0.95},
"alertOutput"
)
# Trinn 3: Buffer og aggreger normaldata
self.buffer.append(normalized)
if len(self.buffer) >= self.buffer_size:
aggregated = self.aggregate(self.buffer)
await self.module_client.send_message_to_output(
{"type": "aggregated", "data": aggregated},
"telemetryOutput"
)
self.buffer.clear()
def detect_anomaly(self, data):
"""ONNX-basert anomalideteksjon"""
input_array = np.array([data["values"]], dtype=np.float32)
result = self.anomaly_model.run(None, {"input": input_array})
return result[0][0] > 0.8 # Anomali-terskel
def aggregate(self, buffer):
"""Aggreger buffer til statistisk sammendrag"""
values = [item["value"] for item in buffer]
return {
"mean": np.mean(values),
"std": np.std(values),
"min": np.min(values),
"max": np.max(values),
"count": len(values),
"period_start": buffer[0]["timestamp"],
"period_end": buffer[-1]["timestamp"]
}
```
### Fordeler med gateway-forbehandling
| Fordel | Beskrivelse | Effekt |
|--------|-------------|--------|
| Redusert bandwidth | Aggregering reduserer datamengde 10-100x | Lavere kostnader |
| Lavere latens | Anomalideteksjon pa millisekunder lokalt | Raskere respons |
| Offline-kapabilitet | Fortsetter drift uten skytilkobling | Hoyere tilgjengelighet |
| Datakvalitet | Validering og rensing for sky-inntak | Bedre analyser |
---
## Time-Series Analytics at Edge
### Azure Stream Analytics pa edge
Azure Stream Analytics kan deployes som IoT Edge-modul for sanntids tidsserieanalyse:
```sql
-- Stream Analytics edge-query: Glidende gjennomsnitt med anomalideteksjon
SELECT
IoTHub.ConnectionDeviceId AS DeviceId,
System.Timestamp() AS WindowEnd,
AVG(temperature) AS AvgTemperature,
STDEV(temperature) AS StdDevTemperature,
COUNT(*) AS ReadingCount,
CASE
WHEN AVG(temperature) >
LAG(AVG(temperature), 1) OVER (PARTITION BY IoTHub.ConnectionDeviceId LIMIT DURATION(minute, 30))
+ 3 * STDEV(temperature)
THEN 'ANOMALY'
ELSE 'NORMAL'
END AS Status
INTO
alertOutput
FROM
sensorInput TIMESTAMP BY EventProcessedUtcTime
GROUP BY
IoTHub.ConnectionDeviceId,
TumblingWindow(minute, 5)
HAVING
COUNT(*) > 10
```
### Edge-basert prediktiv vedlikehold
```python
# Prediktiv vedlikehold med tidsserieanalyse pa edge
import onnxruntime as ort
import numpy as np
from collections import deque
class PredictiveMaintenanceEdge:
def __init__(self, model_path: str, window_size: int = 100):
self.session = ort.InferenceSession(model_path)
self.window = deque(maxlen=window_size)
self.feature_names = ["vibration", "temperature", "pressure", "rpm"]
def add_reading(self, reading: dict) -> dict:
"""Legg til ny maling og returner prediksjon om bufferen er full"""
features = [reading.get(f, 0.0) for f in self.feature_names]
self.window.append(features)
if len(self.window) == self.window.maxlen:
return self.predict()
return {"status": "collecting", "readings": len(self.window)}
def predict(self) -> dict:
"""Kjor RUL-prediksjon (Remaining Useful Life)"""
input_data = np.array([list(self.window)], dtype=np.float32)
# Modell-inferens
rul_prediction = self.session.run(
["remaining_useful_life", "failure_probability"],
{"sensor_sequence": input_data}
)
rul_hours = float(rul_prediction[0][0])
failure_prob = float(rul_prediction[1][0])
return {
"remaining_useful_life_hours": rul_hours,
"failure_probability": failure_prob,
"recommendation": self._get_recommendation(rul_hours, failure_prob),
"confidence": self._calculate_confidence()
}
def _get_recommendation(self, rul: float, prob: float) -> str:
if prob > 0.8 or rul < 24:
return "IMMEDIATE_MAINTENANCE"
elif prob > 0.5 or rul < 168:
return "SCHEDULE_MAINTENANCE"
return "NORMAL_OPERATION"
```
---
## Device-to-Cloud AI Pipelines
### Pipeline-arkitektur
```
[Sensorer] → [Edge Gateway] → [Azure IoT Operations] → [IoT Hub] → [Stream Analytics]
↓ ↓ ↓
[Lokal inferens] [Edge AI-modell] [Azure ML / Fabric]
↓ ↓ ↓
[Sanntidsvarsler] [Kontekstualisert data] [Modelloppdatering]
↓ ↓
[Cloud feedback] ←←←←←←←← [Ny modellversjon]
```
### Implementering med Azure ML og IoT Hub
```python
# Device-to-Cloud AI Pipeline med modelloppdatering
from azure.iot.hub import IoTHubRegistryManager
from azure.ai.ml import MLClient
from azure.identity import DefaultAzureCredential
class AIEdgePipeline:
def __init__(self, hub_connection_string: str, ml_workspace: str):
self.registry_manager = IoTHubRegistryManager(hub_connection_string)
self.ml_client = MLClient(
DefaultAzureCredential(),
subscription_id="<sub-id>",
resource_group_name="<rg>",
workspace_name=ml_workspace
)
def deploy_model_to_edge(self, device_id: str, model_name: str, model_version: str):
"""Deploy oppdatert AI-modell til edge-enhet via IoT Hub device twin"""
twin = self.registry_manager.get_twin(device_id)
# Oppdater desired properties med ny modellinfo
twin_patch = {
"properties": {
"desired": {
"ai_model": {
"name": model_name,
"version": model_version,
"download_url": self._get_model_sas_url(model_name, model_version),
"checksum": self._get_model_checksum(model_name, model_version),
"updated_at": datetime.utcnow().isoformat()
}
}
}
}
self.registry_manager.update_twin(device_id, twin_patch)
def collect_edge_metrics(self, device_id: str) -> dict:
"""Hent ytelsesmetrikker fra edge AI-modul"""
twin = self.registry_manager.get_twin(device_id)
reported = twin.properties.reported.get("ai_metrics", {})
return {
"inference_count": reported.get("total_inferences", 0),
"avg_latency_ms": reported.get("avg_latency_ms", 0),
"model_version": reported.get("current_model_version", "unknown"),
"accuracy_drift": reported.get("accuracy_drift", 0),
"last_updated": reported.get("last_report_time")
}
```
### Modell-feedback-loop
| Fase | Lokasjon | Handling | Verktoy |
|------|----------|----------|---------|
| Datainnsamling | Edge | Samle inferensresultater og ground truth | IoT Edge modul |
| Dataaggregering | Edge | Komprimere og batche data | Data Flows |
| Dataoverfoering | Edge → Sky | Sende treningsdata til sky | IoT Hub / Event Hub |
| Modelltrening | Sky | Retrain/fine-tune modell | Azure ML |
| Modellvalidering | Sky | Evaluere ny modell mot baseline | Azure ML Endpoints |
| Modelldistribusjon | Sky → Edge | Pushe ny modell til edge | IoT Hub device twin |
| A/B-testing | Edge | Sammenligne modellversjoner | Edge-modul |
---
## Norsk offentlig sektor
### Relevante use cases
- **Statens vegvesen**: Sanntids verkontrollovervaking med AI-basert analyse av vaerdata, trafikkmonstre og veiforhold fra veistasjonssensorer
- **Kystverket**: Autonome sensorsystemer langs kysten for miljooverkaking og sikkerhet, med begrenset tilkobling
- **Energisektoren**: Smart styring av offentlige bygg med prediktiv vedlikeholdsanalyse av HVAC-systemer
- **Helsesektoren**: IoT-basert pasientovervaking pa sykehus med lokal AI for tidlig varsling
### Regulatoriske hensyn
- Data fra sensorer i offentlig infrastruktur kan inneholde personopplysninger (kameradata, lokasjon)
- Schrems II krever at persondata prosesseres innenfor EOS
- NSM Grunnprinsipper gjelder for kritisk infrastruktur-systemer
- Personvernkonsekvensvurdering (DPIA) pakrevd for AI-basert overvaking
---
## Beslutningsrammeverk
| Scenario | Anbefaling | Begrunnelse |
|----------|------------|-------------|
| < 100 sensorer, stabil tilkobling | Azure IoT Hub direkte | Enklest, lavest kostnad |
| 100-10 000 sensorer, variabel tilkobling | Azure IoT Operations pa AKS Edge | Lokal buffring og forbehandling |
| Kritisk sanntidsanalyse | Edge AI med Stream Analytics | Sub-sekund latens |
| Prediktiv vedlikehold | ONNX-modell pa edge gateway | Offline-kapabel, lav latens |
| Regulert miljoo (helse, forsvar) | Azure IoT Operations + Confidential Computing | Dataskydd i prosessering |
| Store datamengder, periodisk tilkobling | Edge-aggregering + batch-upload | Bandbreddesparing |
---
## For Cosmo
- **Azure IoT Operations er den foretrukne plattformen** for industrielle IoT-AI-scenarier pa edge, med MQTT-basert kommunikasjon og Data Flows for datatransformasjon — anbefal dette fremfor eldre IoT Edge-moenstre
- **Sensor data normalization er fundamentalt** — uten standardisert datakvalitet og enhetskonvertering vil AI-modeller gi upanalitelige resultater, sa invester i Data Flow-transformasjoner for normalisering
- **Gateway AI-forbehandling reduserer skyavhengighet dramatisk** — anomalideteksjon og aggregering pa edge kan kutte bandwidth med 90%+ og gi sub-sekund responstid for kritiske hendelser
- **Modelloppdatering via device twin** er en etablert pattern for a holde edge AI-modeller oppdatert uten manuell intervensjon — bruk IoT Hub device twin for versjonsstyring og SAS-basert nedlasting
- **For norsk offentlig sektor**: Vurder alltid DPIA for sensor-AI-losninger som kan prosessere persondata, og sorg for at edge-prosessering begrenser hvilke data som forlater det lokale nettverket

View file

@ -0,0 +1,407 @@
# Kubernetes-Based AI at the Edge (AKS Edge)
**Last updated:** 2026-02
**Status:** GA
**Category:** Hybrid Cloud & Edge AI
---
## Introduksjon
AKS Edge Essentials er Microsofts lettvekts Kubernetes-distribusjon for edge-scenarier, designet for a kjore containeriserte arbeidsbelastninger pa PC-klasse hardware. I motsetning til AKS i skyen eller AKS pa Azure Local, er AKS Edge Essentials optimalisert for statiske, forhands-definerte konfigurasjoner pa enheter med begrenset kapasitet — fra industrielle PC-er til gateway-enheter.
For AI-arbeidsbelastninger pa edge muliggjor AKS Edge Essentials deployment av ML-modeller, inferensservere, og AI-pipelines som Kubernetes-pods med GPU-akselerasjon (via GPU-PV). Tilkoblet Azure Arc gir sentralisert administrasjon, GitOps-basert deployment, og integrasjon med Azure ML, Azure Monitor og Azure Policy.
For norsk offentlig sektor er AKS Edge Essentials relevant for distribuert AI pa lokale stasjoner (veisensorer, helseutstyr, energimalere) der Kubernetes-basert orkestrering gir standardisert deployment og oppdatering av AI-modeller pa tvers av geografisk spredte enheter.
---
## Kjernekomponenter
| Komponent | Formal | Teknologi |
|-----------|--------|-----------|
| AKS Edge Essentials | Lettvekts Kubernetes pa edge | K8s/K3s |
| CBL-Mariner Linux VM | Managed Linux VM for containere | Microsoft Mariner |
| Azure Arc | Sentralisert administrasjon fra sky | Kubernetes management |
| GitOps (Flux) | Deklarativ applikasjons-deployment | Git-basert CD |
| GPU-PV | GPU-deling mellom host og VM | Paravirtualisering |
| KAITO | AI-modell deployment operator | Kubernetes operator |
| Helm | Pakkehandtering for Kubernetes | Chart-basert |
---
## AKS Edge Essentials Deployment
### Systemkrav
| Komponent | Minimum | Anbefalt for AI |
|-----------|---------|-----------------|
| OS | Windows 10/11 IoT Enterprise | Windows 11 IoT Enterprise |
| CPU | 4 kjerner | 8+ kjerner |
| RAM | 4 GB (K3s) / 8 GB (K8s) | 16+ GB for AI-workloads |
| Disk | 40 GB | 100+ GB SSD |
| GPU | Ikke pakrevd | NVIDIA T4/A2 for inferens |
| Nettverk | 1 Gbps | 10 Gbps for modell-nedlasting |
### Installasjon og klynge-oppsett
```powershell
# Steg 1: Installer AKS Edge Essentials
# Last ned MSI fra Microsoft
Start-BitsTransfer -Source "https://aka.ms/aks-edge/k8s-msi" `
-Destination "AksEdge-K8s.msi"
msiexec /i AksEdge-K8s.msi /passive
# Steg 2: Importer PowerShell-moduler
Import-Module AksEdge
# Steg 3: Generer konfigurasjonsfil
New-AksEdgeConfig -DeploymentType SingleMachineCluster `
-NodeType Linux `
-outFile .\aksedge-config.json
```
```json
// aksedge-config.json — Konfigurert for AI-workloads
{
"SchemaVersion": "1.14",
"Version": "1.0",
"DeploymentType": "SingleMachineCluster",
"Init": {
"ServiceIPRangeSize": 10
},
"Network": {
"NetworkPlugin": "flannel",
"InternetDisabled": false
},
"User": {
"AcceptEula": true,
"AcceptOptionalTelemetry": false
},
"Machines": [
{
"LinuxNode": {
"CpuCount": 8,
"MemoryInMB": 12288,
"DataSizeInGB": 40,
"Mtu": 1500
}
}
]
}
```
```powershell
# Steg 4: Valider og deploy klynge
Test-AksEdgeNetworkParameters -JsonConfigFilePath .\aksedge-config.json
New-AksEdgeDeployment -JsonConfigFilePath .\aksedge-config.json
# Steg 5: Verifiser deployment
kubectl get nodes -o wide
kubectl get pods --all-namespaces -o wide
```
### Tilkobling til Azure Arc
```powershell
# Koble AKS Edge Essentials til Azure Arc
$arcParams = @{
ClusterName = "edge-ai-station-01"
ResourceGroupName = "rg-edge-ai-norway"
Location = "norwayeast"
SubscriptionId = "<subscription-id>"
TenantId = "<tenant-id>"
}
# Installer Arc-agenter pa klyngen
Install-AksEdgeArc @arcParams
# Verifiser Arc-tilkobling
kubectl get pods -n azure-arc
az connectedk8s show --name edge-ai-station-01 --resource-group rg-edge-ai-norway
```
---
## Container Orchestration at Edge
### AI-inferens deployment med Kubernetes
```yaml
# ONNX Runtime inferensserver pa AKS Edge
apiVersion: apps/v1
kind: Deployment
metadata:
name: onnx-inference-server
namespace: ai-workloads
spec:
replicas: 1
selector:
matchLabels:
app: onnx-inference
template:
metadata:
labels:
app: onnx-inference
spec:
containers:
- name: inference
image: mcr.microsoft.com/onnxruntime/server:latest
args:
- "--model_path"
- "/models/anomaly_detector.onnx"
- "--http_port"
- "8001"
ports:
- containerPort: 8001
name: http
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "2Gi"
cpu: "2"
volumeMounts:
- name: model-storage
mountPath: /models
livenessProbe:
httpGet:
path: /health
port: 8001
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8001
initialDelaySeconds: 10
periodSeconds: 5
volumes:
- name: model-storage
persistentVolumeClaim:
claimName: ai-models-pvc
---
apiVersion: v1
kind: Service
metadata:
name: onnx-inference-svc
namespace: ai-workloads
spec:
selector:
app: onnx-inference
ports:
- port: 8001
targetPort: 8001
type: ClusterIP
```
### GitOps-basert modelloppdatering med Flux
```yaml
# Flux Kustomization for AI-modell deployment
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: ai-models
namespace: flux-system
spec:
interval: 5m
path: ./edge-ai/models
prune: true
sourceRef:
kind: GitRepository
name: edge-ai-config
healthChecks:
- apiVersion: apps/v1
kind: Deployment
name: onnx-inference-server
namespace: ai-workloads
timeout: 10m
---
# Git-repository som kilde for konfigurasjon
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: edge-ai-config
namespace: flux-system
spec:
interval: 1m
url: https://dev.azure.com/org/project/_git/edge-ai-config
branch: main
secretRef:
name: git-credentials
```
---
## Multi-Node Edge Clusters
### Skalerbar klynge pa tvers av maskiner
```powershell
# Steg 1: Generer multi-node konfigurasjon
New-AksEdgeConfig -DeploymentType ScalableCluster `
-NodeType Linux `
-outFile .\multinode-config.json
# Steg 2: Deploy primaer node
New-AksEdgeDeployment -JsonConfigFilePath .\multinode-config.json
# Steg 3: Hent join-token for ekstra noder
$token = Get-AksEdgeNodeJoinToken
# Steg 4: Pa sekundaer maskin — join klyngen
New-AksEdgeScaleConfig -ScaleType AddNode `
-NodeType Linux `
-LinuxNodeIp "192.168.1.102" `
-outFile .\scale-config.json
Add-AksEdgeNode -JsonConfigFilePath .\scale-config.json
```
### Multi-node arkitektur for AI
```
┌─────────────────────────────────────┐
│ Edge AI Cluster │
│ │
│ ┌───────────┐ ┌───────────┐ │
│ │ Node 1 │ │ Node 2 │ │
│ │ (Control │ │ (Worker) │ │
│ │ + Worker) │ │ │ │
│ │ │ │ - AI │ │
│ │ - API │ │ inferens│ │
│ │ server │ │ - GPU │ │
│ │ - etcd │ │ workload│ │
│ │ - Scheduler│ │ │ │
│ └───────────┘ └───────────┘ │
│ ↕ ↕ │
│ [Flannel/Calico networking] │
│ │
│ ┌───────────┐ │
│ │ Node 3 │ Azure Arc ←→ Sky │
│ │ (Worker) │ │
│ │ │ │
│ │ - Data │ │
│ │ pipeline│ │
│ │ - Storage │ │
│ └───────────┘ │
└─────────────────────────────────────┘
```
---
## Service Mesh for Edge
### Lettvekts service mesh pa edge
For AI-arbeidsbelastninger pa edge med flere mikrotjenester (inferens, datainntak, API-gateway) kan en lettvekts service mesh gi observabilitet og trafikkstyring:
```yaml
# Envoy-basert sidecar for AI-inferens (lettvekts alternativ)
apiVersion: apps/v1
kind: Deployment
metadata:
name: ai-inference-with-proxy
namespace: ai-workloads
spec:
template:
spec:
containers:
# AI-inferens container
- name: inference
image: myregistry/anomaly-model:v2
ports:
- containerPort: 8080
# Envoy sidecar for observabilitet
- name: envoy-proxy
image: envoyproxy/envoy:v1.28-latest
ports:
- containerPort: 9901 # Admin
- containerPort: 10000 # Ingress
volumeMounts:
- name: envoy-config
mountPath: /etc/envoy
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "128Mi"
cpu: "100m"
volumes:
- name: envoy-config
configMap:
name: envoy-edge-config
```
### Canary deployment for modellversjoner
```yaml
# Canary deployment: Gradvis utrulling av ny AI-modell
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ai-inference-canary
annotations:
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-weight: "20" # 20% til ny modell
spec:
rules:
- host: inference.edge.local
http:
paths:
- path: /predict
pathType: Prefix
backend:
service:
name: inference-v2 # Ny modellversjon
port:
number: 8080
```
---
## Norsk offentlig sektor
### Distribusjonsstrategi for norske edge-stasjoner
| Stasjon | Antall | Hardware | AKS Edge-konfig | AI-workload |
|---------|--------|----------|-----------------|-------------|
| Veisensorer | ~200 | Industrial PC | Single-node K3s | Trafikk-analyse |
| Tunnelverkaking | ~50 | Rack-server | Multi-node K8s | Brann/ventilasjon |
| Ferjekaier | ~30 | Rugged PC | Single-node K3s | Bildetelling |
| Ladestajoner | ~500 | IoT gateway | K3s minimal | Energi-prediksjon |
### Sikkerhets- og administrasjonskrav
- Azure Arc for sentralisert forvaltning fra Norway East
- GitOps for sporbar og audit-bar deployment
- Network policies for nettverkssegmentering
- Pod security policies/standards for container-isolasjon
- KMS-plugin for kryptering av secrets i etcd
---
## Beslutningsrammeverk
| Scenario | Anbefaling | Begrunnelse |
|----------|------------|-------------|
| Enkelt-enhet AI | AKS Edge Essentials single-node (K3s) | Lavest ressursbruk |
| Multi-workload edge | AKS Edge Essentials single-node (K8s) | Full K8s-kompatibilitet |
| Redundant edge-klynge | AKS Edge multi-node (K8s) | Hoy tilgjengelighet |
| GPU-akselerert AI | AKS Edge + GPU-PV + NVIDIA plugin | Container-basert GPU-inferens |
| Skalerbar fleet-management | AKS Edge + Azure Arc + GitOps | Sentralisert administrasjon |
| Windows + Linux workloads | AKS Edge med bade Linux og Windows VM | Interop-scenarier |
---
## For Cosmo
- **AKS Edge Essentials er den foretrukne loesningen for container-basert AI pa edge** — det gir Kubernetes-standarder pa PC-klasse hardware med minimal fotavtrykk (4 GB RAM for K3s)
- **Azure Arc + GitOps gir sentralisert forvaltning** — anbefal dette for organisasjoner med mange edge-stasjoner som trenger sporbar, automatisert deployment av AI-modeller
- **K3s vs K8s: Velg K3s for enkle AI-scenarier** med 1-3 containere, og K8s nar du trenger full Kubernetes-funksjonalitet som network policies og Pod security standards
- **GPU-PV muliggjor delt GPU-tilgang** mellom Windows-host og Linux VM — bruk dette for edge-servere med NVIDIA GPU som kjorer bade tradisjonelle Windows-applikasjoner og AI-containere
- **For norsk offentlig sektor: AKS Edge + Arc + GitOps gir en standardisert plattform** for AI-deployment pa tvers av etater og lokasjoner — definer felles Helm charts og Flux-konfigurasjoner for a sikre konsistent og revisjonsvennlig deployment

View file

@ -0,0 +1,470 @@
# Network-Constrained AI Deployment
**Last updated:** 2026-02
**Status:** GA
**Category:** Hybrid Cloud & Edge AI
---
## Introduksjon
Nettverksbegrensede miljoer — med lav bandwidth, hoey latens, eller intermitterende tilkobling — krever spesialtilpassede AI-deployments. Standard sky-baserte AI-arkitekturer som sender data frem og tilbake til cloud endpoints feiler i slike miljoer, enten pa grunn av uakseptabel latens eller fordi tilkoblingen simpelthen ikke er palitelig nok.
For norsk offentlig sektor er dette relevant i mange scenarier: rurale omrader med begrenset mobildekning, maritime miljoer med satellittkommunikasjon, tunneler og underjordiske anlegg, feltoperasjoner i krisesituasjoner, og industrielle miljoer med nettverksisolasjon av sikkerhetsgrunner. AI-losninger for slike miljoer ma optimaliseres for minimal nettverksbruk.
Microsoft tilbyr flere teknologier for nettverksbegrensede deployments: modellkvantisering og -komprimering med Olive/ONNX Runtime for mindre modeller, Azure IoT Edge med utvidet offline-stoette for edge-prosessering, delta-synkronisering for effektiv dataoverfoering, og bandwidth-bevisst batching for a maksimere utnyttelsen av tilgjengelig tilkobling.
---
## Kjernekomponenter
| Komponent | Formal | Teknologi |
|-----------|--------|-----------|
| ONNX Runtime | Optimalisert lokal inferens | Cross-platform |
| Olive | Modellkomprimering og kvantisering | Python |
| Azure IoT Edge | Edge-prosessering med buffring | Container runtime |
| Delta Sync | Inkrementell datasynkronisering | Protokoll |
| MQTT | Lettvekts meldingsprotokoll | IoT-standard |
| gRPC | Effektiv binart API-protokoll | Google RPC |
| Protocol Buffers | Kompakt serialisering | Google |
---
## Model Size Reduction
### Kvantiseringsstrategier for nettverksbegrensede miljoer
| Teknikk | Stoerrelses-reduksjon | Kvalitetstap | Nedlastningstid (1 Mbps) |
|---------|---------------------|-------------|--------------------------|
| FP32 (original) | Baseline (7 GB for 3.8B) | Ingen | 15+ timer |
| FP16 | 2x (3.5 GB) | Minimalt | 7+ timer |
| INT8 | 4x (1.75 GB) | < 1% | 3.5 timer |
| INT4 | 8x (875 MB) | 1-3% | 1.7 timer |
| INT4 + Pruning | 10-12x (600 MB) | 2-5% | 1.2 timer |
| Distillation | 20-50x (140-350 MB) | 5-15% | 15-40 min |
### Olive-basert komprimering
```python
# Olive pipeline for maksimal modellkomprimering
import json
olive_config = {
"input_model": {
"type": "HfModel",
"model_path": "microsoft/Phi-3-mini-4k-instruct"
},
"passes": {
# Steg 1: Konverter til ONNX
"conversion": {
"type": "OnnxConversion",
"target_opset": 17
},
# Steg 2: Grafoptimalisering
"optimization": {
"type": "OrtTransformersOptimization",
"model_type": "gpt2",
"opt_level": 2,
"only_onnxruntime": True
},
# Steg 3: Kvantisering til INT4
"quantization": {
"type": "OnnxMatMul4Quantizer",
"block_size": 32,
"is_symmetric": True,
"accuracy_level": 4
},
# Steg 4: Strukturell pruning (fjern unodvendige vekter)
"pruning": {
"type": "SlicGPT",
"sparsity": 0.25, # 25% reduksjon
"calibration_data_config": {
"name": "c4",
"split": "validation",
"num_samples": 128
}
}
},
"engine": {
"evaluator": {
"metrics": [
{
"name": "model_size",
"type": "custom_metric",
"priority": 1
},
{
"name": "latency",
"type": "latency",
"priority": 2
}
]
}
}
}
# Kjor Olive-pipeline
# olive run --config olive_compress.json
```
### Kunnskapsdesstillasjon for ultra-sma modeller
```python
# Destiller fra Phi-3 Medium (14B) til en 1B custom-modell
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
class ModelDistillation:
def __init__(self, teacher_model: str, student_config: dict):
self.teacher = AutoModelForCausalLM.from_pretrained(teacher_model)
self.student = self._create_student(student_config)
self.tokenizer = AutoTokenizer.from_pretrained(teacher_model)
def distill(self, dataset, epochs: int = 5, temperature: float = 2.0):
"""Destiller laerer-modell til elev-modell"""
optimizer = torch.optim.AdamW(self.student.parameters(), lr=1e-4)
for epoch in range(epochs):
for batch in dataset:
inputs = self.tokenizer(batch["text"], return_tensors="pt",
padding=True, truncation=True)
# Laerer-prediksjoner (soft targets)
with torch.no_grad():
teacher_logits = self.teacher(**inputs).logits
# Elev-prediksjoner
student_logits = self.student(**inputs).logits
# Destillasjonsloss: KL-divergens med temperatur
loss = self._distillation_loss(
student_logits, teacher_logits, temperature
)
loss.backward()
optimizer.step()
optimizer.zero_grad()
def _distillation_loss(self, student_logits, teacher_logits, temperature):
"""KL-divergens mellom student og teacher"""
import torch.nn.functional as F
soft_student = F.log_softmax(student_logits / temperature, dim=-1)
soft_teacher = F.softmax(teacher_logits / temperature, dim=-1)
return F.kl_div(soft_student, soft_teacher, reduction='batchmean') * (temperature ** 2)
```
---
## Partial Model Loading
### Modell-segmentering for inkrementell nedlasting
```python
# Delvis modellnedlasting for nettverksbegrensede miljoer
import os
import hashlib
from typing import Optional
class IncrementalModelLoader:
def __init__(self, model_dir: str, remote_url: str):
self.model_dir = model_dir
self.remote_url = remote_url
self.manifest_path = os.path.join(model_dir, "manifest.json")
def check_and_download(self, bandwidth_kbps: float) -> dict:
"""Sjekk modellstatus og last ned manglende deler"""
manifest = self._get_remote_manifest()
local_state = self._get_local_state()
missing_segments = []
total_download_bytes = 0
for segment in manifest["segments"]:
local_hash = local_state.get(segment["name"])
if local_hash != segment["hash"]:
missing_segments.append(segment)
total_download_bytes += segment["size"]
# Estimer nedlastningstid
download_time_seconds = (total_download_bytes * 8) / (bandwidth_kbps * 1000)
return {
"model_version": manifest["version"],
"total_segments": len(manifest["segments"]),
"missing_segments": len(missing_segments),
"download_size_mb": total_download_bytes / 1024 / 1024,
"estimated_time_minutes": download_time_seconds / 60,
"can_use_partial": manifest.get("supports_partial_inference", False),
"minimum_segments_for_inference": manifest.get("min_segments", 1)
}
def download_prioritized(self, bandwidth_kbps: float,
time_budget_minutes: float) -> str:
"""Last ned modellsegmenter prioritert innenfor tidsbudsjett"""
check = self.check_and_download(bandwidth_kbps)
if check["estimated_time_minutes"] <= time_budget_minutes:
# Full nedlasting mulig
return self._download_all_segments()
else:
# Prioritert delvis nedlasting
# Last ned kritiske segmenter foerst (embedding, attention heads)
return self._download_critical_first(time_budget_minutes, bandwidth_kbps)
def _download_critical_first(self, time_budget: float, bw: float) -> str:
"""Last ned de viktigste modelldelene foerst"""
priority_order = [
"embeddings", # Nodvendig for all inferens
"attention_layers", # Kjernekapabilitet
"ffn_layers", # Detaljert prosessering
"output_head" # Siste lag
]
# Download i prioritert rekkefoolge innenfor tidsbudsjett
downloaded = []
remaining_seconds = time_budget * 60
for priority in priority_order:
segment_size = self._get_segment_size(priority)
download_time = (segment_size * 8) / (bw * 1000)
if download_time <= remaining_seconds:
self._download_segment(priority)
downloaded.append(priority)
remaining_seconds -= download_time
else:
break
return f"Lastet ned {len(downloaded)}/{len(priority_order)} segmenter"
```
---
## Bandwidth-Aware Batching
### Adaptiv batchstoerrelser basert pa tilgjengelig bandwidth
```python
# Bandwidth-bevisst batch-synkronisering
import asyncio
import time
from dataclasses import dataclass
@dataclass
class BandwidthProfile:
estimated_kbps: float
latency_ms: float
reliability: float # 0-1, andel vellykkede overforinger
class AdaptiveBatchSync:
def __init__(self):
self.bandwidth_history: list[BandwidthProfile] = []
self.compression_enabled = True
async def measure_bandwidth(self) -> BandwidthProfile:
"""Mal tilgjengelig bandwidth med minimal data"""
test_data = b"x" * 1024 # 1 KB testpakke
start = time.time()
try:
# Send testpakke og mal round-trip
success = await self._send_probe(test_data)
elapsed = time.time() - start
profile = BandwidthProfile(
estimated_kbps=(len(test_data) * 8) / (elapsed * 1000),
latency_ms=elapsed * 1000,
reliability=1.0 if success else 0.0
)
except Exception:
profile = BandwidthProfile(
estimated_kbps=0, latency_ms=float('inf'), reliability=0.0
)
self.bandwidth_history.append(profile)
return profile
def calculate_optimal_batch(self, pending_items: int,
avg_item_size_kb: float) -> dict:
"""Beregn optimal batchstoorrelse basert pa nettverksforhold"""
if not self.bandwidth_history:
return {"batch_size": 1, "reason": "Ingen maalinger"}
recent = self.bandwidth_history[-5:] # Siste 5 maalinger
avg_bw = sum(p.estimated_kbps for p in recent) / len(recent)
avg_reliability = sum(p.reliability for p in recent) / len(recent)
if avg_reliability < 0.3:
# Svart upaalitelig — minimale batche
return {"batch_size": 1, "compress": True, "priority_only": True}
if avg_bw < 10: # < 10 kbps
# Ekstremt lav bandwidth
batch_size = min(5, pending_items)
return {
"batch_size": batch_size,
"compress": True,
"format": "protobuf",
"priority_only": True,
"estimated_time_s": (batch_size * avg_item_size_kb) / avg_bw
}
elif avg_bw < 100: # 10-100 kbps
# Lav bandwidth
batch_size = min(20, pending_items)
return {
"batch_size": batch_size,
"compress": True,
"format": "protobuf",
"priority_only": False
}
elif avg_bw < 1000: # 100 kbps - 1 Mbps
# Medium bandwidth
batch_size = min(100, pending_items)
return {
"batch_size": batch_size,
"compress": True,
"format": "json_gzip"
}
else: # > 1 Mbps
# God bandwidth
return {
"batch_size": min(500, pending_items),
"compress": False,
"format": "json"
}
```
---
## Latency Compensation Patterns
### Strategier for latenskompensering
| Monster | Beskrivelse | Implementering |
|---------|-------------|----------------|
| Optimistisk UI | Vis resultat umiddelbart, korriger senere | Lokal prediksjon + sky-validering |
| Prefetching | Forhaandslast sannsynlige data | Prediktiv caching |
| Stale-while-revalidate | Vis cachet data mens ny hentes | Cache-lag med TTL |
| Lokal buffer | Buffer resultater lokalt | SQLite + event queue |
| Priority queue | Prioriter kritiske data | Vektet synk-koe |
| Komprimering | Reduser datamengde | gzip, protobuf, CBOR |
### Implementering av latenskompensering
```python
# Latenskompenserende AI-proxy
import asyncio
import gzip
import json
from collections import OrderedDict
class LatencyCompensatingProxy:
def __init__(self, local_model, cache_size: int = 1000):
self.local_model = local_model
self.cache = OrderedDict()
self.cache_size = cache_size
self.pending_validations = asyncio.Queue()
async def predict(self, input_data: dict) -> dict:
"""Prediksjon med latenskompensering"""
cache_key = self._hash_input(input_data)
# Sjekk cache forst
if cache_key in self.cache:
cached = self.cache[cache_key]
self.cache.move_to_end(cache_key)
return {**cached, "source": "cache"}
# Lokal prediksjon (umiddelbar)
local_result = self.local_model.predict(input_data)
# Cache resultatet
self._cache_result(cache_key, local_result)
# Koe for sky-validering i bakgrunnen
await self.pending_validations.put({
"cache_key": cache_key,
"input": input_data,
"local_result": local_result
})
return {**local_result, "source": "local", "validated": False}
async def background_validator(self):
"""Bakgrunnsvalidering mot sky-modell"""
while True:
item = await self.pending_validations.get()
try:
cloud_result = await self._cloud_predict(item["input"])
# Oppdater cache med validert resultat
self._cache_result(item["cache_key"], {
**cloud_result,
"validated": True
})
# Varsle om avvik
if self._significant_difference(
item["local_result"], cloud_result
):
await self._notify_correction(item, cloud_result)
except Exception:
pass # Sky utilgjengelig — behold lokal prediksjon
def _significant_difference(self, local: dict, cloud: dict) -> bool:
"""Sjekk om sky-resultat avviker vesentlig fra lokalt"""
if local.get("label") != cloud.get("label"):
return True
if abs(local.get("confidence", 0) - cloud.get("confidence", 0)) > 0.2:
return True
return False
```
---
## Norsk offentlig sektor
### Nettverksbegrensede scenarier i Norge
| Scenario | Typisk bandwidth | Latens | Tilgjengelighet |
|----------|-----------------|--------|-----------------|
| Rural mobildekning | 1-10 Mbps | 50-200 ms | 70-90% |
| Maritim (kyst) | 0.5-5 Mbps | 200-600 ms | 60-80% |
| Tunnel/underjordisk | 0 Mbps (isolert) | N/A | 0% |
| Svalbart | 1-50 Mbps | 500+ ms | 80% |
| Beredskap (krise) | 0.1-1 Mbps | Variable | 30-70% |
| Felt (skog/fjell) | 0-5 Mbps | 100-500 ms | 40-80% |
### Anbefalinger
- Dimensjoner alltid for verste-tilfelle tilkobling
- Bruk INT4-kvantiserte modeller som standard for edge-deployment
- Implementer bandwidth-maling for a tilpasse sync-strategi dynamisk
- Protobuf/CBOR for serialisering i stedet for JSON i lav-bandwidth-scenarier
- Prioriter anomalier og kritiske resultater i sync-koeen
---
## Beslutningsrammeverk
| Scenario | Anbefaling | Begrunnelse |
|----------|------------|-------------|
| < 100 kbps | Full lokal inferens + minimal sync | Ikke nok bandwidth for sky-AI |
| 100 kbps - 1 Mbps | Lokal inferens + delta-sync | Synkroniser resultater, ikke radata |
| 1-10 Mbps | Hybrid med progressiv enhancement | Sky-validering av lokale resultater |
| > 10 Mbps | Standard sky-AI med lokal fallback | Normal drift med offline-buffer |
| Intermitterende | Event sourcing + prioritert batch-sync | Palitelig leveranse over tid |
| Satelitt (hoey latens) | Full lokal med periodisk bulk-sync | Latens for hoey for interaktiv sky-AI |
---
## For Cosmo
- **Modellstoerrelse er den viktigste faktoren for nettverksbegrensede deployments** — bruk INT4-kvantisering som standard, og vurder destillasjon for ultralette modeller under 500 MB
- **Bandwidth-bevisst batching er pabudt** — maal tilgjengelig bandwidth kontinuerlig og tilpass batchstoerrelser og kompresjonsformat dynamisk
- **Protobuf/CBOR sparer 60-80% bandwidth** sammenlignet med JSON — bruk binaere serialiseringsformater for all edge-til-sky-kommunikasjon i lav-bandwidth-miljoer
- **Inkrementell modellnedlasting er kritisk** for modelloppdatering over lav bandwidth — last ned kun endrede lag/segmenter, og stoeett delvis modellbruk under nedlasting
- **For norsk offentlig sektor: Design for 100 kbps som worst case** — mange felt-scenarier i rural Norge har begrenset 4G-dekning, og maritime/beredskapsscenarier kan ha enda lavere bandwidth

View file

@ -0,0 +1,491 @@
# Offline-First AI Application Patterns
**Last updated:** 2026-02
**Status:** GA
**Category:** Hybrid Cloud & Edge AI
---
## Introduksjon
Offline-first AI-applikasjoner er designet for a fungere primaert lokalt og synkronisere med skyen nar tilkobling er tilgjengelig. Dette monsteret snur den tradisjonelle sky-forst-tilnaermingen pa hodet: i stedet for a feile nar nettverket er nede, er applikasjonen designet for a operere uavhengig med lokal AI-inferens og datalagring.
For norsk offentlig sektor er offline-first sarlig relevant i felt-scenarioer: vegarbeidere som inspiserer infrastruktur i omrader uten dekning, ambulansepersonell som trenger AI-stoette i rurale omrader, beredskapspersonell under krisesituasjoner der kommunikasjonsinfrastruktur kan vaere nede, og maritime inspeksjoner langs kysten.
Microsoft tilbyr flere byggeklosser for offline-first AI: ONNX Runtime for lokal inferens, Azure IoT Edge for container-basert edge-prosessering med utvidet offline-stoette, Azure Container Storage for lokal persistens med automatisk sky-synkronisering, og Phi-modeller for lokale SLM-kapabiliteter.
---
## Kjernekomponenter
| Komponent | Formal | Teknologi |
|-----------|--------|-----------|
| ONNX Runtime | Lokal AI-inferens uten sky | Cross-platform |
| Azure IoT Edge | Utvidet offline-kapabilitet | Container runtime |
| Azure Container Storage | Lokal lagring med sky-sync | Arc-enabled |
| Phi-3/4 SLM | Lokal sprakmodell | MIT-lisens |
| SQLite/LiteDB | Lokal database for offline-data | Embedded DB |
| CRDTs | Konfliktfri replikert datatype | Datastruktur |
| Azure Cosmos DB | Sky-database med offline SDK | Multi-model DB |
---
## Local-First Data Models
### Arkitektur for lokal-forst AI
```
┌─────────────────────────────────────────────┐
│ Offline-First App │
│ │
│ ┌──────────┐ ┌───────────┐ ┌───────────┐ │
│ │ UI Layer │ │ AI Engine │ │ Data Layer│ │
│ │ │ │ │ │ │ │
│ │ - Input │←→│ - ONNX RT │←→│ - SQLite │ │
│ │ - Output │ │ - Phi SLM │ │ - VectorDB│ │
│ │ - Status │ │ - Scoring │ │ - File │ │
│ └──────────┘ └───────────┘ └───────────┘ │
│ ↕ │
│ ┌────────────┐ │
│ │ Sync Engine│ │
│ │ │ │
│ │ - Queue │ │
│ │ - Delta │ │
│ │ - Conflict │ │
│ └──────┬─────┘ │
└─────────────────────────────────────┼────────┘
[Sky (nar tilgjengelig)]
```
### Event-sourcing for offline data
```python
# Event-sourced datamodell for offline-first AI
from dataclasses import dataclass, field
from datetime import datetime
from typing import Optional
import json
import sqlite3
import uuid
@dataclass
class Event:
id: str = field(default_factory=lambda: str(uuid.uuid4()))
timestamp: str = field(default_factory=lambda: datetime.utcnow().isoformat())
type: str = ""
entity_id: str = ""
data: dict = field(default_factory=dict)
synced: bool = False
device_id: str = ""
class OfflineEventStore:
def __init__(self, db_path: str, device_id: str):
self.device_id = device_id
self.conn = sqlite3.connect(db_path)
self._init_schema()
def _init_schema(self):
self.conn.executescript("""
CREATE TABLE IF NOT EXISTS events (
id TEXT PRIMARY KEY,
timestamp TEXT NOT NULL,
type TEXT NOT NULL,
entity_id TEXT NOT NULL,
data TEXT NOT NULL,
synced INTEGER DEFAULT 0,
device_id TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS ai_results (
id TEXT PRIMARY KEY,
event_id TEXT REFERENCES events(id),
model_version TEXT,
result TEXT,
confidence REAL,
created_at TEXT,
synced INTEGER DEFAULT 0
);
CREATE INDEX IF NOT EXISTS idx_events_synced ON events(synced);
CREATE INDEX IF NOT EXISTS idx_events_entity ON events(entity_id);
""")
def append_event(self, event_type: str, entity_id: str, data: dict) -> Event:
"""Legg til hendelse i lokal event store"""
event = Event(
type=event_type,
entity_id=entity_id,
data=data,
device_id=self.device_id
)
self.conn.execute(
"INSERT INTO events VALUES (?, ?, ?, ?, ?, ?, ?)",
(event.id, event.timestamp, event.type, event.entity_id,
json.dumps(event.data), 0, event.device_id)
)
self.conn.commit()
return event
def store_ai_result(self, event_id: str, model_version: str,
result: dict, confidence: float):
"""Lagre AI-inferensresultat lokalt"""
self.conn.execute(
"INSERT INTO ai_results VALUES (?, ?, ?, ?, ?, ?, ?)",
(str(uuid.uuid4()), event_id, model_version,
json.dumps(result), confidence,
datetime.utcnow().isoformat(), 0)
)
self.conn.commit()
def get_unsynced_events(self, limit: int = 100) -> list[Event]:
"""Hent hendelser som ikke er synkronisert"""
cursor = self.conn.execute(
"SELECT * FROM events WHERE synced = 0 ORDER BY timestamp LIMIT ?",
(limit,)
)
return [Event(*row) for row in cursor.fetchall()]
def mark_synced(self, event_ids: list[str]):
"""Marker hendelser som synkronisert"""
placeholders = ",".join("?" * len(event_ids))
self.conn.execute(
f"UPDATE events SET synced = 1 WHERE id IN ({placeholders})",
event_ids
)
self.conn.commit()
```
---
## Conflict Resolution on Sync
### Konflikthondteringsstrategier
| Strategi | Beskrivelse | Best for |
|----------|-------------|----------|
| Last-Write-Wins (LWW) | Siste endring vinner | Enkle data, lav risiko |
| First-Write-Wins | Forste endring vinner | Uforanderlige hendelser |
| Merge | Kombiner endringer automatisk | Komplementaere felt |
| CRDT | Konfliktfri replikert datatype | Tallere, sett, tekst |
| Custom Resolution | Applikasjonsspesifikk logikk | Komplekse forretningsregler |
### Implementering av konflikthondtering
```python
# Konflikthondtering for offline-first AI-applikasjon
from enum import Enum
from typing import Callable
class ConflictStrategy(Enum):
LAST_WRITE_WINS = "lww"
FIRST_WRITE_WINS = "fww"
MERGE = "merge"
MANUAL = "manual"
class SyncConflictResolver:
def __init__(self, strategy: ConflictStrategy = ConflictStrategy.LAST_WRITE_WINS):
self.strategy = strategy
self.custom_resolvers: dict[str, Callable] = {}
def register_resolver(self, entity_type: str, resolver: Callable):
"""Registrer egendefinert konfliktloeser for en entitetstype"""
self.custom_resolvers[entity_type] = resolver
def resolve(self, local_event: dict, remote_event: dict) -> dict:
"""Los konflikt mellom lokal og fjern hendelse"""
entity_type = local_event.get("type", "")
# Egendefinert resolver har forrang
if entity_type in self.custom_resolvers:
return self.custom_resolvers[entity_type](local_event, remote_event)
if self.strategy == ConflictStrategy.LAST_WRITE_WINS:
return self._last_write_wins(local_event, remote_event)
elif self.strategy == ConflictStrategy.FIRST_WRITE_WINS:
return self._first_write_wins(local_event, remote_event)
elif self.strategy == ConflictStrategy.MERGE:
return self._merge(local_event, remote_event)
else:
return {"conflict": True, "local": local_event, "remote": remote_event}
def _last_write_wins(self, local: dict, remote: dict) -> dict:
local_ts = local.get("timestamp", "")
remote_ts = remote.get("timestamp", "")
return local if local_ts >= remote_ts else remote
def _merge(self, local: dict, remote: dict) -> dict:
"""Merge ved a kombinere ikke-overlappende felt"""
merged = {**remote.get("data", {})}
for key, value in local.get("data", {}).items():
if key not in merged or merged[key] is None:
merged[key] = value
elif key in merged and value != merged[key]:
# Begge har endret — behold begge med suffix
merged[f"{key}_local"] = value
merged[f"{key}_remote"] = merged[key]
return {"data": merged, "merge_status": "auto_merged"}
# Eksempel: Konflikthondtering for AI-inspeksjonsresultater
resolver = SyncConflictResolver(ConflictStrategy.MERGE)
def resolve_inspection(local, remote):
"""Inspeksjoner: Behold den med hoeyest AI-confidence"""
local_conf = local.get("data", {}).get("ai_confidence", 0)
remote_conf = remote.get("data", {}).get("ai_confidence", 0)
winner = local if local_conf >= remote_conf else remote
winner["data"]["conflict_resolved"] = True
winner["data"]["alternative_confidence"] = min(local_conf, remote_conf)
return winner
resolver.register_resolver("inspection_result", resolve_inspection)
```
---
## Progressive Enhancement
### Progressiv AI-kapabilitet
```python
# Progressiv enhancement: Eskalerer AI-kapabilitet basert pa tilkobling
from enum import Enum
import asyncio
class ConnectivityLevel(Enum):
OFFLINE = 0 # Ingen tilkobling
LOW_BANDWIDTH = 1 # < 1 Mbps
CONNECTED = 2 # Normal tilkobling
HIGH_BANDWIDTH = 3 # > 10 Mbps
class ProgressiveAIService:
def __init__(self):
self.local_model = None # Phi-3 Mini INT4 (alltid tilgjengelig)
self.medium_model = None # Phi-3 Small (krever > 16 GB RAM)
self.cloud_client = None # Azure OpenAI (krever tilkobling)
async def classify_document(self, text: str) -> dict:
"""Klassifiser dokument med best tilgjengelig AI"""
connectivity = await self.check_connectivity()
if connectivity >= ConnectivityLevel.HIGH_BANDWIDTH:
# Nivaa 3: Full sky-AI med GPT-4o
return await self._classify_cloud(text, model="gpt-4o")
elif connectivity >= ConnectivityLevel.CONNECTED:
# Nivaa 2: Sky-AI med lettere modell
return await self._classify_cloud(text, model="gpt-4o-mini")
elif connectivity >= ConnectivityLevel.LOW_BANDWIDTH:
# Nivaa 1: Lokal medium modell med sky-validering
local_result = self._classify_local(text, self.medium_model)
# Asynkron validering i bakgrunn nar mulig
asyncio.create_task(self._validate_in_background(text, local_result))
return local_result
else:
# Nivaa 0: Full offline med lokal mini-modell
return self._classify_local(text, self.local_model)
def _classify_local(self, text: str, model) -> dict:
"""Lokal klassifisering med ONNX-modell"""
result = model.predict(text)
return {
"classification": result["label"],
"confidence": result["score"],
"model": "local",
"connectivity": "offline",
"note": "Resultat fra lokal modell — verifiseres ved tilkobling"
}
async def check_connectivity(self) -> ConnectivityLevel:
"""Sjekk navaerende tilkoblingsniva"""
try:
import aiohttp
async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=3)) as session:
async with session.get("https://management.azure.com/health") as resp:
if resp.status == 200:
# Estimer bandwidth
return ConnectivityLevel.HIGH_BANDWIDTH
except Exception:
pass
try:
# Proeving med minimal data
import socket
socket.create_connection(("8.8.8.8", 53), timeout=2)
return ConnectivityLevel.LOW_BANDWIDTH
except Exception:
return ConnectivityLevel.OFFLINE
```
### UI-indikasjon av AI-nivaa
```python
# Statusindikator for progressive AI
AI_LEVEL_INFO = {
ConnectivityLevel.OFFLINE: {
"label": "Offline-modus",
"description": "Bruker lokal AI-modell. Resultater synkroniseres ved tilkobling.",
"icon": "offline",
"accuracy": "God (lokal modell)",
"features": ["Klassifisering", "Oppsummering", "Uttrekking"]
},
ConnectivityLevel.LOW_BANDWIDTH: {
"label": "Begrenset tilkobling",
"description": "Lokal AI med bakgrunns-validering.",
"icon": "low_signal",
"accuracy": "God+ (validert i bakgrunn)",
"features": ["Klassifisering", "Oppsummering", "Uttrekking", "Bakgrunns-validering"]
},
ConnectivityLevel.CONNECTED: {
"label": "Tilkoblet",
"description": "Sky-AI med standard modell.",
"icon": "connected",
"accuracy": "Hoey",
"features": ["Alle funksjoner", "RAG", "Avansert analyse"]
},
ConnectivityLevel.HIGH_BANDWIDTH: {
"label": "Full tilkobling",
"description": "Sky-AI med avansert modell.",
"icon": "full_signal",
"accuracy": "Hoeyest",
"features": ["Alle funksjoner", "RAG", "Avansert analyse", "Bildeanalyse"]
}
}
```
---
## Offline Capability Testing
### Testrammeverk for offline AI
```python
# Testrammeverk for offline-first AI-applikasjon
import pytest
from unittest.mock import patch, AsyncMock
class TestOfflineAI:
"""Tester for offline-first AI-funksjonalitet"""
@pytest.fixture
def ai_service(self):
return ProgressiveAIService()
@pytest.fixture
def event_store(self, tmp_path):
return OfflineEventStore(str(tmp_path / "test.db"), "test-device")
def test_offline_classification(self, ai_service):
"""AI-klassifisering skal fungere uten nettverkstilkobling"""
with patch.object(ai_service, 'check_connectivity',
return_value=ConnectivityLevel.OFFLINE):
result = asyncio.run(ai_service.classify_document(
"Vedtak om avslag pa soeknad om byggetillatelse"
))
assert result["classification"] is not None
assert result["connectivity"] == "offline"
assert result["confidence"] > 0.5
def test_event_persistence_offline(self, event_store):
"""Hendelser skal lagres lokalt ved offline"""
event = event_store.append_event(
"inspection", "bridge-001",
{"status": "ok", "notes": "Ingen synlige skader"}
)
assert event.synced is False
assert event.device_id == "test-device"
# Hent usynkroniserte hendelser
unsynced = event_store.get_unsynced_events()
assert len(unsynced) == 1
def test_sync_after_reconnection(self, event_store):
"""Usynkroniserte hendelser skal koes for synkronisering"""
# Simuler 10 offline-hendelser
for i in range(10):
event_store.append_event(
"sensor_reading", f"sensor-{i}",
{"value": i * 1.5}
)
unsynced = event_store.get_unsynced_events()
assert len(unsynced) == 10
# Simuler synkronisering
synced_ids = [e.id for e in unsynced[:5]]
event_store.mark_synced(synced_ids)
remaining = event_store.get_unsynced_events()
assert len(remaining) == 5
def test_conflict_resolution(self):
"""Konflikter ved sync skal loses deterministisk"""
resolver = SyncConflictResolver(ConflictStrategy.LAST_WRITE_WINS)
local = {"timestamp": "2026-02-12T10:00:00", "data": {"status": "ok"}}
remote = {"timestamp": "2026-02-12T09:00:00", "data": {"status": "warning"}}
result = resolver.resolve(local, remote)
assert result["data"]["status"] == "ok" # Nyeste vinner
def test_progressive_enhancement(self, ai_service):
"""AI-kvalitet skal oeke med bedre tilkobling"""
results = {}
for level in ConnectivityLevel:
with patch.object(ai_service, 'check_connectivity',
return_value=level):
result = asyncio.run(ai_service.classify_document("test"))
results[level] = result
# Verifiser at sky-resultat har hoeyere konfidensangivelse
assert results[ConnectivityLevel.OFFLINE]["model"] == "local"
```
---
## Norsk offentlig sektor
### Felt-scenarier som krever offline-first
| Scenario | Etat | Offline-varighet | AI-funksjon |
|----------|------|-----------------|-------------|
| Vegfinspeksjon | SVV | Timer | Skadeklassifisering |
| Ambulanse | Helseetaten | Minutter-timer | Triagering |
| Beredskap | DSB | Dager | Situasjonsanalyse |
| Maritime inspeksjoner | Sjoefartsdir. | Timer-dager | Rapport-generering |
| Grensekontroll | Politiet | Minutter | Dokumentverifisering |
| Skogsbrannberedskap | 110-sentraler | Timer | Risikoanalyse |
### Krav til offline-first i offentlig sektor
- Applikasjonen MA fungere uten nettverkstilkobling
- Lokale AI-resultater MA vaere tydelig merket som "ikke-validert"
- Synkronisering MA skje automatisk ved tilkobling
- Konflikthondtering MA vaere deterministisk og sporbar
- Data MA vaere kryptert lokalt (BitLocker/LUKS)
---
## Beslutningsrammeverk
| Scenario | Anbefaling | Begrunnelse |
|----------|------------|-------------|
| Felt-app med periodisk tilkobling | Full offline-first med event sourcing | Data bevares alltid lokalt |
| Sanntids-AI med fallback | Progressiv enhancement | Best mulig kvalitet per tilstand |
| Multi-enhet med sync | CRDTs + event store | Konfliktfri synkronisering |
| Kritisk infrastruktur | Azure IoT Edge extended offline | Uavhengig drift i uker |
| Klient-app pa PC | SQLite + ONNX RT + bakgrunns-sync | Enkel, palitelig arkitektur |
| Beredskapsapplikasjon | Full offline med manuell sync | Ingen skyavhengighet |
---
## For Cosmo
- **Offline-first er et designprinsipp, ikke en feilhaandterings-strategi** — applikasjonen MA designes for a fungere lokalt foerst, med sky som en berikelse nar tilgjengelig
- **Event sourcing er det foretrukne datamoensteeret** for offline-first AI — alle hendelser og AI-resultater lagres lokalt som uforanderlige events og synkroniseres inkrementelt
- **Progressiv enhancement gir graceful degradation** — definer tydelige AI-kapabilitetsnivaaer (offline/begrenset/tilkoblet/full) og kommuniser dette til brukeren
- **Konflikthondtering maa vaere deterministisk og sporbar** — bruk Last-Write-Wins som standard, med custom resolvers for doemenespesifikke regler (f.eks. hoeyest AI-confidence vinner)
- **For norsk offentlig sektor: Test offline-scenarioer som foersteklasses testcase** — ikke anta tilkobling, og sooerg for at felt-personell kan fullfoere sine oppgaver uavhengig av nettverksstatus

View file

@ -0,0 +1,453 @@
# On-Premises SLM and Phi Model Deployment
**Last updated:** 2026-02
**Status:** GA
**Category:** Hybrid Cloud & Edge AI
---
## Introduksjon
Small Language Models (SLM) er kompakte AI-modeller med faerre enn 10 milliarder parametere som kan kjores effektivt pa lokal hardware uten skyavhengighet. Microsofts Phi-modellserie — fra Phi-2 (2.7B) til Phi-4 (14B) — representerer state-of-the-art for SLM, med ytelse som konkurrerer med modeller mange ganger storre.
For norsk offentlig sektor er lokal SLM-deployment sarlig attraktivt: full datakontroll uten at data forlater organisasjonens nettverk, forutsigbare kostnader uten per-token-prising, og mulighet for drift i miljoer med begrenset eller ingen internettilkobling. Phi-modellene er spesielt godt egnet fordi de er optimalisert for oppgaver som klassifisering, oppsummering, enhetstuttrekking og enkel sporsmalsbesvaring.
Microsoft tilbyr flere deploymentsveier for lokale SLM: Azure App Service sidecar, AKS Edge Essentials med KAITO, ONNX Runtime pa Windows/Linux, og Windows ML pa Copilot+ PC-er. Valget avhenger av skaleringsbehovet, tilgjengelig hardware og integrasjonskrav.
---
## Kjernekomponenter
| Komponent | Formal | Teknologi |
|-----------|--------|-----------|
| Phi-3 Mini | 3.8B parameter SLM for generelle oppgaver | MIT-lisens |
| Phi-3 Small | 7B parameter SLM for hoeyere kvalitet | MIT-lisens |
| Phi-3 Medium | 14B parameter SLM for komplekse oppgaver | MIT-lisens |
| Phi-3.5 Mini | Forbedret 3.8B med multilingual stoette | MIT-lisens |
| Phi-4 Mini | Nyeste 3.8B med forbedret resonnering | MIT-lisens |
| ONNX Runtime | Cross-platform inferensmotor | Open source |
| KAITO | Kubernetes AI Toolchain Operator | Azure Arc |
| Olive | Modelloptimalisering for deployment | Microsoft |
| Windows ML | Lokal inferens pa Windows | Windows SDK |
---
## Phi-3/Phi-4 Deployment
### Modelloversikt
| Modell | Parametere | Kontekst | RAM-krav | GPU-krav | Styrker |
|--------|-----------|----------|----------|----------|---------|
| Phi-3 Mini 4K | 3.8B | 4K tokens | 8 GB | Valgfritt | Enkel Q&A, klassifisering |
| Phi-3 Mini 128K | 3.8B | 128K tokens | 8 GB | Anbefalt | Lange dokumenter |
| Phi-3 Small | 7B | 128K tokens | 16 GB | Anbefalt | Flerspraklig, koding |
| Phi-3 Medium | 14B | 128K tokens | 32 GB | Pakrevd | Kompleks resonnering |
| Phi-3.5 Mini | 3.8B | 128K tokens | 8 GB | Valgfritt | Multilingual, forbedret |
| Phi-4 Mini | 3.8B | 128K tokens | 8 GB | Valgfritt | Beste resonnering i klassen |
### Deployment med Azure App Service Sidecar
```yaml
# App Service sidecar-konfigurasjon for Phi-3.5 Mini
apiVersion: apps/v1
kind: Deployment
metadata:
name: phi-slm-app
spec:
template:
spec:
containers:
# Hoved-applikasjon
- name: web-app
image: myregistry.azurecr.io/myapp:latest
ports:
- containerPort: 8080
env:
- name: SLM_ENDPOINT
value: "http://localhost:11434"
# SLM sidecar
- name: phi-sidecar
image: mcr.microsoft.com/oss/ollama/ollama:latest
ports:
- containerPort: 11434
resources:
requests:
memory: "8Gi"
cpu: "4"
limits:
memory: "16Gi"
cpu: "8"
command: ["ollama", "serve"]
lifecycle:
postStart:
exec:
command: ["ollama", "pull", "phi3.5"]
```
### Deployment med KAITO pa AKS Edge
```yaml
# KAITO Workspace for Phi-3 Mini pa edge
apiVersion: kaito.sh/v1alpha1
kind: Workspace
metadata:
name: phi3-edge
annotations:
kaito.sh/enablelb: "false" # Ikke ekstern lastbalansering pa edge
spec:
resource:
instanceType: "Standard_NC4as_T4_v3" # GPU-node
labelSelector:
matchLabels:
apps: phi3-edge
inference:
preset:
name: "phi-3-mini-128k-instruct"
adapters:
- source:
name: "custom-norwegian-adapter"
image: "myregistry/phi3-no-adapter:v1"
```
### ONNX Runtime deployment (CPU)
```python
# Phi-3 Mini deployment med ONNX Runtime (ingen GPU nodvendig)
import onnxruntime_genai as og
class PhiLocalDeployment:
def __init__(self, model_path: str):
"""
Initialiser Phi-3/4 lokal deployment.
model_path: Sti til ONNX-optimalisert Phi-modell
"""
self.model = og.Model(model_path)
self.tokenizer = og.Tokenizer(self.model)
self.search_options = {
"max_length": 2048,
"temperature": 0.7,
"top_p": 0.9,
"do_sample": True
}
def generate(self, prompt: str, system_message: str = None,
max_tokens: int = 1024) -> str:
"""Generer svar fra lokal Phi-modell"""
if system_message:
full_prompt = (
f"<|system|>\n{system_message}<|end|>\n"
f"<|user|>\n{prompt}<|end|>\n"
f"<|assistant|>\n"
)
else:
full_prompt = (
f"<|user|>\n{prompt}<|end|>\n"
f"<|assistant|>\n"
)
input_tokens = self.tokenizer.encode(full_prompt)
params = og.GeneratorParams(self.model)
params.set_search_options(**{
**self.search_options,
"max_length": max_tokens
})
params.input_ids = input_tokens
generator = og.Generator(self.model, params)
output_tokens = []
while not generator.is_done():
generator.compute_logits()
generator.generate_next_token()
new_token = generator.get_next_tokens()[0]
output_tokens.append(new_token)
return self.tokenizer.decode(output_tokens)
def generate_streaming(self, prompt: str, system_message: str = None):
"""Streaming-generering for lavere opplevd latens"""
full_prompt = self._format_prompt(prompt, system_message)
input_tokens = self.tokenizer.encode(full_prompt)
params = og.GeneratorParams(self.model)
params.set_search_options(**self.search_options)
params.input_ids = input_tokens
generator = og.Generator(self.model, params)
tokenizer_stream = self.tokenizer.create_stream()
while not generator.is_done():
generator.compute_logits()
generator.generate_next_token()
token = generator.get_next_tokens()[0]
yield tokenizer_stream.decode(token)
```
---
## Resource-Constrained Sizing
### Hardware-dimensjonering
| Scenario | Modell | CPU | RAM | GPU | Disk | Inferens-hastighet |
|----------|--------|-----|-----|-----|------|-------------------|
| Minimal (PC) | Phi-3 Mini INT4 | 4 kjerner | 8 GB | Ingen | 5 GB | ~10 tokens/s |
| Standard (server) | Phi-3 Mini FP16 | 8 kjerner | 16 GB | T4 16GB | 10 GB | ~40 tokens/s |
| Ytelse (GPU) | Phi-3 Small FP16 | 8 kjerner | 32 GB | A10G 24GB | 20 GB | ~50 tokens/s |
| Enterprise | Phi-3 Medium FP16 | 16 kjerner | 64 GB | A100 40GB | 40 GB | ~60 tokens/s |
| Edge (NPU) | Phi-3 Mini INT4 | Snapdragon X | 16 GB | NPU | 5 GB | ~20 tokens/s |
### Minnesoptimalisering
```python
# Konfigurasjon for ressursbegrensede miljoer
import onnxruntime as ort
def create_optimized_session(model_path: str, max_memory_gb: float = 4.0):
"""Opprett ONNX-session optimalisert for begrenset minne"""
session_options = ort.SessionOptions()
# Reduser minnebruk
session_options.enable_mem_pattern = True
session_options.enable_mem_reuse = True
# Begrens trader basert pa tilgjengelige kjerner
import os
available_cores = os.cpu_count() or 4
session_options.intra_op_num_threads = max(1, available_cores // 2)
session_options.inter_op_num_threads = max(1, available_cores // 4)
# Velg execution provider basert pa tilgjengelig hardware
providers = []
if ort.get_device() == "GPU":
providers.append(('CUDAExecutionProvider', {
'device_id': 0,
'arena_extend_strategy': 'kSameAsRequested',
'gpu_mem_limit': int(max_memory_gb * 1024 * 1024 * 1024),
'cudnn_conv_algo_search': 'HEURISTIC'
}))
providers.append('CPUExecutionProvider')
return ort.InferenceSession(
model_path,
sess_options=session_options,
providers=providers
)
```
---
## Prompt Optimization for SLM
### SLM-spesifikke prompt-teknikker
SLM-er har begrenset kontekstvindu og resonneringskapasitet sammenlignet med LLM-er. Prompt-optimalisering er kritisk:
| Teknikk | Beskrivelse | Effekt |
|---------|-------------|--------|
| Konsist system-melding | Kort, presis rolledefinisjon (< 100 tokens) | Bedre oppgavefokus |
| Strukturert output | Be om JSON/tabell-format | Mer palitelig parsing |
| Few-shot eksempler | 1-3 konkrete eksempler | Hoyere noyaktighet |
| Decomposition | Del opp komplekse oppgaver | Bedre resultater |
| Constraint-basert | Eksplisitte begrensninger | Unnga hallusinasjoner |
### Prompt-maler for norsk offentlig sektor
```python
# Optimaliserte prompt-maler for SLM i offentlig sektor
SLM_PROMPTS = {
"klassifisering": """<|system|>
Du klassifiserer dokumenter. Svar KUN med en av kategoriene.
Kategorier: {categories}
<|end|>
<|user|>
Klassifiser folgende tekst:
"{text}"
Kategori:<|end|>
<|assistant|>""",
"oppsummering": """<|system|>
Du oppsummerer tekst pa norsk. Maks {max_words} ord.
<|end|>
<|user|>
Oppsummer folgende:
"{text}"
<|end|>
<|assistant|>
Oppsummering:""",
"uttrekking": """<|system|>
Du trekker ut strukturert informasjon. Svar i JSON-format.
<|end|>
<|user|>
Trekk ut folgende felter fra teksten: {fields}
Tekst: "{text}"
JSON:<|end|>
<|assistant|>
{{""",
"qa_med_kontekst": """<|system|>
Du svarer pa sporsmal basert pa konteksten. Svar KUN basert pa informasjonen gitt.
Hvis svaret ikke finnes i konteksten, si "Ikke tilstrekkelig informasjon."
<|end|>
<|user|>
Kontekst:
{context}
Sporsmal: {question}
<|end|>
<|assistant|>"""
}
```
---
## Fine-Tuning at Edge
### Lokal fine-tuning av Phi-modeller
```python
# LoRA fine-tuning av Phi-3 for norsk offentlig sektor
from transformers import (
AutoModelForCausalLM, AutoTokenizer,
TrainingArguments, Trainer
)
from peft import LoraConfig, get_peft_model
import torch
def finetune_phi_lora(
base_model: str = "microsoft/phi-3-mini-4k-instruct",
dataset_path: str = "training_data.jsonl",
output_dir: str = "./phi3-finetuned"
):
"""Fine-tune Phi-3 med LoRA for norsk offentlig sektor"""
# Last modell med 4-bit kvantisering for a spare minne
model = AutoModelForCausalLM.from_pretrained(
base_model,
torch_dtype=torch.bfloat16,
load_in_4bit=True,
device_map="auto"
)
tokenizer = AutoTokenizer.from_pretrained(base_model)
# LoRA-konfigurasjon (minimal for edge)
lora_config = LoraConfig(
r=16, # Lav rank for edge-deployment
lora_alpha=32,
target_modules=["q_proj", "v_proj", "k_proj", "o_proj"],
lora_dropout=0.05,
bias="none",
task_type="CAUSAL_LM"
)
model = get_peft_model(model, lora_config)
# Treningsargumenter optimalisert for begrenset hardware
training_args = TrainingArguments(
output_dir=output_dir,
num_train_epochs=3,
per_device_train_batch_size=2,
gradient_accumulation_steps=8,
learning_rate=2e-4,
fp16=True,
logging_steps=10,
save_strategy="epoch",
optim="paged_adamw_8bit", # Minneeffektiv optimizer
max_grad_norm=0.3,
warmup_ratio=0.03
)
trainer = Trainer(
model=model,
args=training_args,
train_dataset=load_dataset(dataset_path),
tokenizer=tokenizer
)
trainer.train()
# Lagre kun LoRA-adapteret (liten filstorrelse)
model.save_pretrained(output_dir)
# Adapter-storrelse: typisk 20-50 MB vs 7+ GB for full modell
```
### ONNX-eksport for deployment
```bash
# Konverter fine-tuned Phi-3 til ONNX for deployment
python -m olive run \
--config olive-config.json \
--model-id ./phi3-finetuned \
--output-dir ./phi3-onnx-optimized \
--precision int4 \
--target-device cpu
```
```json
// olive-config.json
{
"input_model": {
"type": "HfModel",
"model_path": "./phi3-finetuned"
},
"passes": [
{"type": "OnnxConversion"},
{"type": "OnnxQuantization", "quant_mode": "dynamic", "weight_type": "QInt4"},
{"type": "OrtTransformersOptimization", "model_type": "gpt2"}
],
"engine": {
"target": "onnxruntime",
"execution_providers": ["CPUExecutionProvider"]
}
}
```
---
## Norsk offentlig sektor
### Hvorfor lokal SLM for offentlig sektor?
- **Datakontroll**: Ingen data forlater organisasjonens nettverk — viktig for personopplysninger og gradert informasjon
- **Kostnadskontroll**: Fast infrastrukturkostnad uten per-token-prising — enklere budsjettforvaltning
- **Tilgjengelighet**: Fungerer uten internettilkobling — relevant for felt, krisesituasjoner, og isolerte miljoer
- **Etterlevelse**: Enklere a demonstrere compliance med Schrems II, GDPR, og NSM-krav
### Anbefalte bruksomrader for SLM
| Bruksomrade | Modell | Beskrivelse |
|-------------|--------|-------------|
| Dokumentklassifisering | Phi-3 Mini INT4 | Klassifiser innkommende post/henvendelser |
| Oppsummering | Phi-3.5 Mini | Oppsummer lange utredninger og hoeringsnotater |
| Informasjonsuttrekking | Phi-3 Mini | Trekk ut nokkeldata fra skjemaer |
| Intern Q&A | Phi-4 Mini + RAG | Svar pa sporsmal fra regelverk |
| Tekstgenerering | Phi-3 Small | Utkast til brev og standardsvar |
---
## Beslutningsrammeverk
| Scenario | Anbefaling | Begrunnelse |
|----------|------------|-------------|
| Enkel klassifisering/uttrekking | Phi-3 Mini INT4 pa CPU | Minimal hardware, rask inferens |
| Norsk sprakbehandling | Phi-3.5 Mini eller Phi-4 Mini | Bedre multilingual stoette |
| Kompleks resonnering | Phi-3 Medium pa GPU | Nodvendig kapasitet |
| Edge-deployment (IoT) | Phi-3 Mini INT4 ONNX | Minst fotavtrykk |
| Windows-klient | Windows ML + Phi-4 Mini | Automatisk hardware-optimalisering |
| Copilot+ PC | Windows ML med NPU | Best ytelse/watt |
| Server-deployment | KAITO pa AKS Edge | Skalerbart, Kubernetes-managed |
---
## For Cosmo
- **Phi-3 Mini INT4 er det naturlige startpunktet** for de fleste offentlige sektors SLM-bruk — 3.8B parametere gir overraskende god kvalitet for klassifisering, uttrekking og enkel Q&A, og krever kun 8 GB RAM uten GPU
- **Fine-tuning med LoRA er nodvendig for doenmespesifikke oppgaver** — en LoRA-adapter pa 20-50 MB er mye enklere a distribuere til edge enn en full modell, og gir dramatisk forbedring for norsk fagsprak
- **Prompt-optimalisering er viktigere for SLM enn for LLM** — korte, strukturerte prompts med eksplisitte output-formater og 1-3 few-shot-eksempler oker kvaliteten betydelig
- **ONNX Runtime + Olive er den foretrukne deployment-pipeline** — konverter til ONNX, kvantiser til INT4, og deploy pa CPU for maksimal portabilitet og ytelse
- **For norsk offentlig sektor: Lokal SLM-deployment eliminerer de fleste Schrems II-utfordringer** — data forlater aldri nettverket, noe som forenkler personvernkonsekvensvurdering og compliance-dokumentasjon

View file

@ -0,0 +1,412 @@
# ONNX Runtime for Edge Deployment
**Last updated:** 2026-02
**Status:** GA
**Category:** Hybrid Cloud & Edge AI
---
## Introduksjon
ONNX Runtime er Microsofts open-source, hoyytelses inferensmotor for kjoring av maskinlaeringsmodeller i ONNX-format (Open Neural Network Exchange). Den er optimalisert for bade sky og edge, og stotter Linux, Windows og macOS pa tvers av CPU, GPU og NPU-akseleratorer. ONNX Runtime er innebygd i Windows som del av Windows ML og driver inferens i hoyskala Microsoft-tjenester som Bing, Office og Azure AI.
For edge-deployment er ONNX Runtime sarlig verdifull fordi den gir en enhetlig inferensmotor pa tvers av hardware-plattformer — fra kraftige edge-servere med GPU til ressursbegrensede IoT-enheter med kun CPU. Modeller fra PyTorch, TensorFlow, scikit-learn og andre rammeverk kan konverteres til ONNX-format og deretter optimaliseres for spesifikk target-hardware med Olive-verktoysettet.
For norsk offentlig sektor betyr ONNX Runtime at AI-modeller kan deployes lokalt uten skyavhengighet, noe som er viktig for datasuverenitetsscenarier, offline-drift, og miljoer med begrenset nettverkstilkobling.
---
## Kjernekomponenter
| Komponent | Formal | Teknologi |
|-----------|--------|-----------|
| ONNX Runtime | Inferensmotor for ONNX-modeller | C++/Python/C#/JS |
| ONNX Runtime GenAI | Generativ AI-inferens (LLM) | Python/C# |
| Olive | Modelloptimalisering og kompilering | Python CLI |
| Windows ML | ONNX Runtime integrert i Windows | Windows SDK |
| DirectML | Hardware-akselerasjon pa Windows | GPU EP |
| Execution Providers | Hardware-spesifikke akseleratorer | CPU/GPU/NPU |
---
## ONNX Model Conversion
### Konvertering fra populaere rammeverk
```python
# PyTorch til ONNX-konvertering
import torch
import onnx
def convert_pytorch_to_onnx(model, sample_input, output_path: str):
"""Konverter PyTorch-modell til ONNX-format"""
model.eval()
torch.onnx.export(
model,
sample_input,
output_path,
export_params=True,
opset_version=17,
do_constant_folding=True,
input_names=['input'],
output_names=['output'],
dynamic_axes={
'input': {0: 'batch_size'},
'output': {0: 'batch_size'}
}
)
# Valider ONNX-modell
onnx_model = onnx.load(output_path)
onnx.checker.check_model(onnx_model)
print(f"ONNX-modell lagret: {output_path}")
print(f"Modellstorrelse: {os.path.getsize(output_path) / 1024 / 1024:.1f} MB")
```
```python
# TensorFlow/Keras til ONNX med tf2onnx
import tf2onnx
import tensorflow as tf
def convert_tensorflow_to_onnx(saved_model_path: str, output_path: str):
"""Konverter TensorFlow SavedModel til ONNX"""
model_proto, _ = tf2onnx.convert.from_saved_model(
saved_model_path,
output_path=output_path,
opset=17
)
print(f"Konvertert TensorFlow-modell til {output_path}")
```
```python
# scikit-learn til ONNX med skl2onnx
from skl2onnx import convert_sklearn
from skl2onnx.common.data_types import FloatTensorType
def convert_sklearn_to_onnx(model, n_features: int, output_path: str):
"""Konverter scikit-learn-modell til ONNX"""
initial_type = [('input', FloatTensorType([None, n_features]))]
onnx_model = convert_sklearn(model, initial_types=initial_type)
with open(output_path, "wb") as f:
f.write(onnx_model.SerializeToString())
```
### Olive-basert optimalisering
```bash
# Olive: Automatisk modelloptimalisering for target-hardware
pip install olive-ai
# Konverter og optimaliser Phi-3 for CPU edge deployment
olive run \
--model microsoft/Phi-3-mini-4k-instruct \
--output-dir ./optimized-model \
--device cpu \
--precision int4 \
--passes onnx_conversion,onnx_quantization,ort_optimization
```
```json
// olive-config.json for edge-optimalisering
{
"input_model": {
"type": "HfModel",
"model_path": "microsoft/Phi-3-mini-4k-instruct"
},
"systems": {
"local": {
"type": "LocalSystem",
"accelerators": [{"device": "cpu"}]
}
},
"passes": {
"conversion": {
"type": "OnnxConversion",
"target_opset": 17
},
"quantization": {
"type": "OnnxQuantization",
"quant_mode": "dynamic",
"weight_type": "QInt4",
"calibration_data_config": {
"name": "my_calibration_dataset"
}
},
"optimization": {
"type": "OrtTransformersOptimization",
"model_type": "gpt2",
"opt_level": 2,
"float16": false
}
},
"engine": {
"evaluator": {
"metrics": [
{"name": "latency", "type": "latency", "priority": 1},
{"name": "accuracy", "type": "accuracy", "priority": 2}
]
}
}
}
```
---
## Hardware Acceleration (GPU/NPU)
### Execution Provider-oversikt
| Execution Provider | Hardware | Plattform | Brukstilfelle |
|-------------------|----------|-----------|---------------|
| CPUExecutionProvider | Alle CPU-er | Alle | Baseline, alltid tilgjengelig |
| CUDAExecutionProvider | NVIDIA GPU | Linux/Windows | Hoy-ytelse GPU-inferens |
| TensorrtExecutionProvider | NVIDIA GPU | Linux | Lavest latens GPU-inferens |
| DirectMLExecutionProvider | GPU/NPU | Windows | Windows-universal akselerasjon |
| OpenVINOExecutionProvider | Intel CPU/GPU/NPU | Linux/Windows | Intel-optimalisert |
| QNNExecutionProvider | Qualcomm NPU | Windows ARM64 | Snapdragon AI akselerasjon |
| AzureExecutionProvider | Azure AI | Cloud | Sky-basert inferens |
### GPU-akselerert inferens
```python
# NVIDIA GPU-akselerert inferens med ONNX Runtime
import onnxruntime as ort
def create_gpu_session(model_path: str) -> ort.InferenceSession:
"""Opprett GPU-akselerert ONNX Runtime-session"""
session_options = ort.SessionOptions()
session_options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
session_options.enable_mem_pattern = True
# Prioriter GPU, fall tilbake til CPU
providers = [
('CUDAExecutionProvider', {
'device_id': 0,
'arena_extend_strategy': 'kNextPowerOfTwo',
'gpu_mem_limit': 4 * 1024 * 1024 * 1024, # 4 GB
'cudnn_conv_algo_search': 'EXHAUSTIVE',
'do_copy_in_default_stream': True
}),
'CPUExecutionProvider'
]
session = ort.InferenceSession(
model_path,
sess_options=session_options,
providers=providers
)
# Verifiser at GPU brukes
active_provider = session.get_providers()[0]
print(f"Aktiv provider: {active_provider}")
return session
```
### Windows ML med automatisk EP-discovery
```csharp
// Windows ML med automatisk hardware-deteksjon
using Microsoft.ML.OnnxRuntime;
public class WindowsMLInference
{
private InferenceSession _session;
public async Task InitializeAsync(string modelPath)
{
var sessionOptions = new SessionOptions();
// Windows ML velger automatisk beste EP
// Qualcomm NPU → Intel OpenVINO → DirectML GPU → CPU
sessionOptions.AppendExecutionProvider_WindowsML();
_session = new InferenceSession(modelPath, sessionOptions);
// Logg valgt EP
var providers = _session.GetAvailableProviders();
Console.WriteLine($"Tilgjengelige providers: {string.Join(", ", providers)}");
}
public float[] RunInference(float[] input, int[] dimensions)
{
var inputTensor = new DenseTensor<float>(input, dimensions);
var inputs = new List<NamedOnnxValue>
{
NamedOnnxValue.CreateFromTensor("input", inputTensor)
};
using var results = _session.Run(inputs);
return results.First().AsTensor<float>().ToArray();
}
}
```
---
## Cross-Platform Compatibility
### Deployment-matrise
| Plattform | OS | Arkitektur | Stoettede EP-er | Brukstilfelle |
|-----------|-----|------------|-----------------|---------------|
| Edge server | Linux | x64 | CUDA, TensorRT, CPU | Hoyytelse-inferens |
| Edge server | Windows | x64 | DirectML, CUDA, CPU | Windows-basert edge |
| IoT Gateway | Linux | ARM64 | CPU, OpenVINO | Lettvekt-inferens |
| Copilot+ PC | Windows | ARM64 | QNN (NPU), DirectML | Klient-AI |
| Azure Local | Linux | x64 | CUDA, CPU | On-premises |
| Raspberry Pi | Linux | ARM64 | CPU | Prototype/test |
### Cross-platform deployment med Docker
```dockerfile
# Multi-platform ONNX Runtime edge container
FROM --platform=$TARGETPLATFORM mcr.microsoft.com/onnxruntime/server:latest
# Kopier optimalisert modell
COPY ./models/optimized_model.onnx /models/model.onnx
# Konfigurasjon
ENV MODEL_PATH=/models/model.onnx
ENV HTTP_PORT=8001
# Helsesjekkek
HEALTHCHECK --interval=30s --timeout=5s \
CMD curl -f http://localhost:${HTTP_PORT}/health || exit 1
EXPOSE ${HTTP_PORT}
CMD ["--model_path", "/models/model.onnx", "--http_port", "8001"]
```
```yaml
# Multi-arch build for edge deployment
# docker buildx build --platform linux/amd64,linux/arm64 -t myregistry/inference:v1 .
apiVersion: apps/v1
kind: Deployment
metadata:
name: onnx-inference-edge
spec:
replicas: 1
template:
spec:
containers:
- name: inference
image: myregistry/inference:v1
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "2Gi"
cpu: "2"
nvidia.com/gpu: "1" # Valgfritt, kun pa GPU-noder
ports:
- containerPort: 8001
```
---
## Performance Profiling
### Ytelsesanalyse-verktoy
| Verktoy | Formal | Plattform |
|---------|--------|-----------|
| ONNX Runtime Profiler | Session-level profilering | Alle |
| Windows Performance Analyzer | System-wide AI-analyse | Windows |
| Netron | Modellvisualisering og inspeksjon | Alle |
| Olive Benchmarking | Automatisert ytelses-benchmarking | Alle |
| NVIDIA Nsight | GPU-profilering | NVIDIA |
### Profilering med ONNX Runtime
```python
# Ytelsesprofilering av ONNX-modell
import onnxruntime as ort
import numpy as np
import time
class ONNXProfiler:
def __init__(self, model_path: str):
self.options = ort.SessionOptions()
self.options.enable_profiling = True
self.options.profile_file_prefix = "onnx_profile"
self.session = ort.InferenceSession(
model_path,
sess_options=self.options
)
def benchmark(self, input_data: dict, iterations: int = 100) -> dict:
"""Kjor benchmark med detaljerte tidsmalinger"""
# Warmup
for _ in range(10):
self.session.run(None, input_data)
# Benchmark
latencies = []
for _ in range(iterations):
start = time.perf_counter_ns()
self.session.run(None, input_data)
end = time.perf_counter_ns()
latencies.append((end - start) / 1e6) # ms
# Stopp profilering og lagre rapport
profile_path = self.session.end_profiling()
return {
"mean_latency_ms": np.mean(latencies),
"p50_latency_ms": np.percentile(latencies, 50),
"p95_latency_ms": np.percentile(latencies, 95),
"p99_latency_ms": np.percentile(latencies, 99),
"throughput_qps": 1000 / np.mean(latencies),
"iterations": iterations,
"profile_file": profile_path,
"providers": self.session.get_providers()
}
```
---
## Norsk offentlig sektor
### Fordeler med ONNX Runtime for offentlig sektor
- **Leverandoruavhengighet**: ONNX er et apent format — modeller er portable mellom plattformer og leverandorer
- **Lokal deployment**: Kjor modeller lokalt uten skyavhengighet, viktig for datasuverenitet
- **Cross-platform**: Samme modell kan kjores pa server, edge-gateway, og klientenhet
- **Kostnadseffektivt**: Ingen per-inferens-kostnader, kun infrastrukturkostnader
### Anbefalt deployment-pipeline
1. Tren modell i Azure ML (sky)
2. Konverter til ONNX med Olive
3. Kvantiser til INT4/INT8 for target-hardware
4. Valider ytelse med benchmarking
5. Deploy via container til AKS Edge eller direkte til device
6. Overvak med Application Insights
---
## Beslutningsrammeverk
| Scenario | Anbefaling | Begrunnelse |
|----------|------------|-------------|
| Windows-klient med NPU | Windows ML (automatisk EP) | Enklest, best integrasjon |
| NVIDIA GPU edge server | CUDA EP + TensorRT | Lavest latens |
| Intel-basert edge | OpenVINO EP | Intel-optimalisert |
| ARM64 IoT gateway | CPU EP med INT4-kvantisering | Minst ressursbruk |
| Cross-platform deployment | Docker + CPU EP | Maksimal portabilitet |
| Azure Local | CUDA EP i Kubernetes | Enterprise-skalerbart |
---
## For Cosmo
- **ONNX Runtime er den universelle inferensmotoren** for Microsoft-okosystemet — anbefal det som standard for alle edge AI-deployments pa grunn av cross-platform-stoette og hardware-akselerasjon
- **Olive er det foretrukne verktoeyet for modelloptimalisering** — det automatiserer konvertering, kvantisering og optimalisering i en pipeline og sikrer at modellen er optimalisert for spesifikk target-hardware
- **Windows ML erstatter DirectML** som anbefalt tilnaerming pa Windows — det abstraherer EP-management og velger automatisk beste akselerator (NPU, GPU, CPU)
- **INT4-kvantisering via Olive gir 5-8x stoerrelses-reduksjon** med minimalt noyaktighetstap — dette er kritisk for edge-deployment der minne og lagring er begrenset
- **For norsk offentlig sektor: ONNX-format sikrer leverandoeruavhengighet** som kreves av Digdirs arkitekturprinsipper — modeller kan flyttes mellom Azure, on-premises, og andre skyleverandoerer uten endring

View file

@ -0,0 +1,551 @@
# Regulatory Compliance for Edge AI
**Last updated:** 2026-02
**Status:** GA
**Category:** Hybrid Cloud & Edge AI
---
## Introduksjon
AI-systemer deployed pa edge — pa lokale servere, gateways, enheter eller on-premises Kubernetes-klynger — ma oppfylle de samme regulatoriske kravene som skybaserte AI-systemer, men med tilleggsutfordringer knyttet til fysisk tilgangskontroll, distribuert logging, og vedlikehold av mange enheter. Norsk offentlig sektor opererer under et komplekst regulatorisk landskap: GDPR, EU AI Act, NSM Grunnprinsipper, Utredningsinstruksen, og sektorspesifikke krav.
For edge AI er utfordringen todelt: For det forste ma selve AI-systemet vaere compliant (ansvarlig AI, dataminimering, transparens). For det andre ma den distribuerte arkitekturen — med data og modeller pa mange fysiske lokasjoner — administreres slik at alle noder er oppdaterte, logget, og revisjonsvennlige. Manglende sentralisert kontroll gir okt risiko for konfigurasjonsavvik og compliance-brudd.
Microsoft tilbyr verktoy for a adressere dette: Azure Arc for sentralisert policy-haandheving, Microsoft Purview for dataklassifisering og -styring, Microsoft Defender for Cloud for sikkerhetsvurdering, og Compliance Manager for regulatorisk kartlegging. Disse verktoyene kan utvides til edge-miljoer gjennom Arc-integrasjon.
---
## Kjernekomponenter
| Komponent | Formal | Teknologi |
|-----------|--------|-----------|
| Azure Arc | Sentralisert policy-haandheving pa edge | Kubernetes/Server |
| Microsoft Purview | Dataklassifisering og -styring | Data governance |
| Compliance Manager | Regulatorisk kartlegging og kontroller | Assessment |
| Microsoft Defender for Cloud | Sikkerhetsvurdering | CSPM/CWPP |
| Azure Policy | Automatisert policy-haandheving | Policy engine |
| Azure Monitor | Sentralisert logging og overvaking | Observability |
| Microsoft Priva | Personvernkonsekvensvurdering | Privacy |
---
## Data Protection Impact Assessment (DPIA)
### Nar er DPIA pakrevd for edge AI?
Ifoolge GDPR Art. 35 er DPIA pakrevd nar databehandling sannsynligvis medforer hoey risiko for fysiske personers rettigheter. For edge AI gjelder dette sarlig:
| Trigger | Edge AI-eksempel | DPIA-krav |
|---------|-----------------|-----------|
| Automatiserte beslutninger | AI-basert triage pa sykehus | Pakrevd |
| Systematisk overvaking | Kamerabasert trafikkanalyse | Pakrevd |
| Sensitive data i stor skala | Helsedata-analyse pa lokale servere | Pakrevd |
| Ny teknologi | AI-modeller pa edge-enheter | Vurderes |
| Saerbare grupper | AI i barnevern/NAV | Pakrevd |
### DPIA-mal for edge AI-system
```markdown
## DPIA for Edge AI-system
### 1. Systembeskrivelse
- **Navn**: [System-navn]
- **Formal**: [Formal med AI-behandling]
- **Datatyper**: [Persondata som behandles]
- **Datasubjekter**: [Hvem gjelder det]
- **Edge-lokasjon**: [Hvor AI kjorer]
- **Modelltype**: [SLM/ONNX/Custom]
- **Dataminimering**: [Hvordan begrenses datainnsamling]
### 2. Nodvendighet og proporsjonalitet
- [ ] Er AI-behandling nodvendig for formalet?
- [ ] Kan formalet oppnas med mindre inngripende midler?
- [ ] Er datamengden begrenset til det nodvendige?
- [ ] Er lagringstid fastsatt og begrunnet?
### 3. Risikovurdering
| Risiko | Sannsynlighet | Konsekvens | Tiltak |
|--------|--------------|------------|--------|
| Data pa avveie fra edge-enhet | Medium | Hoey | Kryptering, fysisk sikring |
| Feilaktig AI-beslutning | Medium | Avhengig av kontekst | Menneskelig overstyring |
| Modell-bias | Lav-Medium | Hoey | Bias-testing, overvaking |
| Manglende logging | Lav | Hoey | Sentralisert audit via Arc |
| Uautorisert tilgang | Medium | Hoey | Tilgangskontroll, attestasjon |
### 4. Tiltak for a redusere risiko
- [ ] Kryptering av data pa edge-enhet (at rest, in transit, in use)
- [ ] Automatisert logging til sentralt system
- [ ] Menneskelig overstyring for kritiske beslutninger
- [ ] Regelmessig bias-evaluering av AI-modell
- [ ] Sletterutiner for persondata pa edge
- [ ] Fysisk sikring av edge-enheter
- [ ] Sentralisert policy-haandheving via Azure Arc
### 5. Konsultasjon
- [ ] Personvernombud konsultert
- [ ] Datatilsynet konsultert (om pakrevd)
- [ ] Beroorte parter informert
```
### Implementering av DPIA-kontroller
```python
# Automatisert DPIA-kontrollsjekk for edge AI
from dataclasses import dataclass
from typing import Optional
from datetime import datetime
@dataclass
class DPIAControl:
id: str
name: str
description: str
status: str # "implemented", "partial", "missing"
evidence: Optional[str] = None
last_verified: Optional[datetime] = None
class EdgeAIDPIAChecker:
def __init__(self):
self.controls = self._define_controls()
def _define_controls(self) -> list[DPIAControl]:
return [
DPIAControl(
id="DPIA-01",
name="Data minimering",
description="Kun nodvendige persondata samles inn pa edge",
status="missing"
),
DPIAControl(
id="DPIA-02",
name="Kryptering at rest",
description="All data pa edge-enhet er kryptert",
status="missing"
),
DPIAControl(
id="DPIA-03",
name="Kryptering in transit",
description="TLS 1.3 for all kommunikasjon",
status="missing"
),
DPIAControl(
id="DPIA-04",
name="Tilgangskontroll",
description="RBAC implementert pa edge-klynge",
status="missing"
),
DPIAControl(
id="DPIA-05",
name="Audit logging",
description="All AI-inferens og datatilgang logges",
status="missing"
),
DPIAControl(
id="DPIA-06",
name="Menneskelig overstyring",
description="AI-beslutninger kan overstyres av menneske",
status="missing"
),
DPIAControl(
id="DPIA-07",
name="Slettemekanisme",
description="Persondata kan slettes fra edge-enhet",
status="missing"
),
DPIAControl(
id="DPIA-08",
name="Bias-evaluering",
description="Modellen er testet for bias og diskriminering",
status="missing"
),
DPIAControl(
id="DPIA-09",
name="Transparens",
description="Bruker informeres om AI-behandling",
status="missing"
),
DPIAControl(
id="DPIA-10",
name="Policy-haandheving",
description="Azure Policy haandheves pa edge via Arc",
status="missing"
)
]
def assess(self) -> dict:
"""Vurder compliance-status"""
implemented = sum(1 for c in self.controls if c.status == "implemented")
total = len(self.controls)
return {
"score": f"{implemented}/{total}",
"percentage": f"{(implemented/total)*100:.0f}%",
"status": "COMPLIANT" if implemented == total else "NON_COMPLIANT",
"missing": [c.name for c in self.controls if c.status == "missing"],
"partial": [c.name for c in self.controls if c.status == "partial"]
}
```
---
## Risk Assessment Frameworks
### NSM Grunnprinsipper for edge AI
```python
# NSM Grunnprinsipper-basert risikovurdering for edge AI
class NSMRiskAssessment:
"""Risikovurdering basert pa NSMs grunnprinsipper for IKT-sikkerhet"""
CATEGORIES = {
"identifisere": [
"Kartlegge enheter, systemer og tjenester",
"Klassifisere informasjon og data",
"Identifisere saarbarheter",
"Vurdere risiko"
],
"beskytte": [
"Haandtere identiteter og tilganger",
"Beskytte data (kryptering)",
"Sikre plattformer og applikasjoner",
"Beskytte nettverk"
],
"oppdage": [
"Overvake sikkerhetstilstand",
"Logge og analysere hendelser",
"Oppdage uonsket aktivitet"
],
"haandtere": [
"Haandtere sikkerhetshendelser",
"Gjenopprette etter hendelser",
"Forbedre basert pa erfaring"
]
}
def assess_edge_ai_system(self, system_config: dict) -> dict:
"""Vurder et edge AI-system mot NSM Grunnprinsipper"""
results = {}
for category, principles in self.CATEGORIES.items():
category_results = []
for principle in principles:
score = self._evaluate_principle(principle, system_config)
category_results.append({
"principle": principle,
"score": score, # 1-5
"status": "OK" if score >= 3 else "MANGELFULL"
})
results[category] = {
"principles": category_results,
"avg_score": sum(r["score"] for r in category_results) / len(category_results)
}
overall = sum(r["avg_score"] for r in results.values()) / len(results)
return {
"categories": results,
"overall_score": round(overall, 1),
"overall_status": "AKSEPTABEL" if overall >= 3.0 else "UTILSTREKKELIG",
"recommendation": self._generate_recommendations(results)
}
```
### EU AI Act risikoklassifisering for edge AI
| Risikoniva | Eksempel edge AI | Krav | Konsekvens |
|------------|-----------------|------|------------|
| Uakseptabel | Sosial scoring pa edge | Forbudt | Kan ikke deployes |
| Hoey risiko | Biometrisk ID pa edge | Full compliance | DPIA + CE-merking + audit |
| Begrenset risiko | Chatbot pa klientenhet | Transparens | Bruker ma informeres |
| Minimal risiko | Spam-filter lokalt | Frivillig | Anbefalt god praksis |
---
## Audit Logging at Edge
### Sentralisert audit-logging arkitektur
```
┌─────────────┐ ┌──────────────┐ ┌───────────────┐
│ Edge Node 1 │ │ Edge Node 2 │ │ Edge Node N │
│ │ │ │ │ │
│ [AI-inferens]│ │ [AI-inferens] │ │ [AI-inferens] │
│ [Audit log] │ │ [Audit log] │ │ [Audit log] │
│ ↓ │ │ ↓ │ │ ↓ │
│ [OMS Agent] │ │ [OMS Agent] │ │ [OMS Agent] │
└──────┬──────┘ └──────┬───────┘ └──────┬────────┘
│ │ │
└───────────────────┼─────────────────────┘
┌────────────────────────┐
│ Log Analytics │
│ Workspace │
│ (Norway East) │
│ │
│ ┌────────────────┐ │
│ │ KQL-queries │ │
│ │ for compliance │ │
│ └────────────────┘ │
└────────────┬───────────┘
┌────────────────────────┐
│ Azure Sentinel / SIEM │
│ (Sikkerhetshendelser) │
└────────────────────────┘
```
### Implementering av edge audit logging
```python
# Strukturert audit logging for edge AI
import json
import logging
from datetime import datetime
from typing import Optional
class EdgeAIAuditLogger:
"""Audit logger for AI-inferens pa edge, kompatibel med Azure Monitor"""
def __init__(self, device_id: str, log_path: str):
self.device_id = device_id
self.logger = logging.getLogger("edge-ai-audit")
# Filbasert logging (lokal buffer)
handler = logging.FileHandler(log_path)
handler.setFormatter(logging.Formatter('%(message)s'))
self.logger.addHandler(handler)
self.logger.setLevel(logging.INFO)
def log_inference(self, model_name: str, model_version: str,
input_summary: str, output_summary: str,
confidence: float, latency_ms: float,
user_id: Optional[str] = None,
contains_pii: bool = False):
"""Logg AI-inferens for audit"""
audit_entry = {
"timestamp": datetime.utcnow().isoformat() + "Z",
"event_type": "AI_INFERENCE",
"device_id": self.device_id,
"model": {
"name": model_name,
"version": model_version
},
"inference": {
"input_summary": input_summary if not contains_pii else "[PII_REDACTED]",
"output_summary": output_summary,
"confidence": confidence,
"latency_ms": latency_ms
},
"context": {
"user_id": user_id,
"contains_pii": contains_pii,
"processing_location": "edge",
"data_residency": "Norway"
}
}
self.logger.info(json.dumps(audit_entry))
def log_data_access(self, data_type: str, purpose: str,
legal_basis: str, user_id: Optional[str] = None):
"""Logg datatilgang for GDPR Art. 30"""
audit_entry = {
"timestamp": datetime.utcnow().isoformat() + "Z",
"event_type": "DATA_ACCESS",
"device_id": self.device_id,
"data_access": {
"data_type": data_type,
"purpose": purpose,
"legal_basis": legal_basis,
"user_id": user_id
}
}
self.logger.info(json.dumps(audit_entry))
def log_model_update(self, old_version: str, new_version: str,
update_source: str, integrity_check: str):
"""Logg modell-oppdatering"""
audit_entry = {
"timestamp": datetime.utcnow().isoformat() + "Z",
"event_type": "MODEL_UPDATE",
"device_id": self.device_id,
"model_update": {
"old_version": old_version,
"new_version": new_version,
"source": update_source,
"integrity_verified": integrity_check == "valid"
}
}
self.logger.info(json.dumps(audit_entry))
```
### KQL-queries for compliance-rapportering
```kusto
// KQL: AI-inferens audit-rapport siste 30 dager
EdgeAIAudit_CL
| where TimeGenerated > ago(30d)
| where event_type_s == "AI_INFERENCE"
| summarize
TotalInferences = count(),
AvgConfidence = avg(inference_confidence_d),
AvgLatency = avg(inference_latency_ms_d),
PIIInferences = countif(context_contains_pii_b == true),
UniqueModels = dcount(model_name_s),
UniqueDevices = dcount(device_id_s)
by bin(TimeGenerated, 1d)
| order by TimeGenerated desc
// KQL: Sjekk at alle edge-noder har oppdatert modell
EdgeAIAudit_CL
| where event_type_s == "MODEL_UPDATE"
| summarize LastUpdate = max(TimeGenerated), CurrentVersion = arg_max(TimeGenerated, model_update_new_version_s)
by device_id_s
| extend DaysSinceUpdate = datetime_diff('day', now(), LastUpdate)
| where DaysSinceUpdate > 7
| project device_id_s, CurrentVersion, DaysSinceUpdate
| order by DaysSinceUpdate desc
// KQL: PII-tilgangsrapport for GDPR Art. 30
EdgeAIAudit_CL
| where event_type_s == "DATA_ACCESS"
| summarize
AccessCount = count(),
UniqueUsers = dcount(data_access_user_id_s),
Purposes = make_set(data_access_purpose_s)
by data_access_data_type_s, data_access_legal_basis_s
| order by AccessCount desc
```
---
## Transparency and Explainability
### Forklarbarhets-krav for edge AI
| Krav | Kilde | Implementering |
|------|-------|----------------|
| Rett til forklaring | GDPR Art. 22 | Modell-forklaringsrapporter |
| Transparens | EU AI Act | Brukerinformasjon om AI-bruk |
| Dokumentasjon | EU AI Act (hoey-risiko) | Teknisk dokumentasjon |
| Menneskelig tilsyn | EU AI Act | Override-mekanisme |
| Etterproovbarhet | Forvaltningsloven | Audit trail + begrunnelse |
### Implementering av forklarbarhet pa edge
```python
# SHAP-basert forklarbarhet for edge AI-modeller
import shap
import numpy as np
class EdgeAIExplainer:
"""Lettvekts forklarbarhet for edge-deployde modeller"""
def __init__(self, model, feature_names: list[str]):
self.model = model
self.feature_names = feature_names
# Bruk pre-beregnet bakgrunnsdata for effektivitet
self.explainer = None
def initialize_with_background(self, background_data: np.ndarray):
"""Initialiser forklarer med representativt datasett"""
# Bruk kun 100 eksempler for minneeffektivitet pa edge
sample = background_data[:100] if len(background_data) > 100 else background_data
self.explainer = shap.KernelExplainer(
self.model.predict,
sample,
link="logit"
)
def explain_prediction(self, input_data: np.ndarray) -> dict:
"""Generer forklaring for en enkelt prediksjon"""
if self.explainer is None:
return {"error": "Forklarer ikke initialisert"}
shap_values = self.explainer.shap_values(input_data, nsamples=50)
# Sorter features etter viktighet
feature_importance = []
for i, name in enumerate(self.feature_names):
importance = abs(float(shap_values[0][i]))
direction = "oker" if shap_values[0][i] > 0 else "reduserer"
feature_importance.append({
"feature": name,
"importance": importance,
"direction": direction,
"value": float(input_data[0][i])
})
feature_importance.sort(key=lambda x: x["importance"], reverse=True)
# Generer menneskelesbar forklaring
top_features = feature_importance[:3]
explanation = self._generate_norwegian_explanation(top_features)
return {
"prediction": self.model.predict(input_data)[0],
"feature_importance": feature_importance,
"explanation_no": explanation,
"model_type": type(self.model).__name__,
"explainability_method": "SHAP (KernelExplainer)"
}
def _generate_norwegian_explanation(self, top_features: list[dict]) -> str:
"""Generer forklaring pa norsk"""
parts = []
for f in top_features:
parts.append(
f"'{f['feature']}' (verdi: {f['value']:.2f}) "
f"{f['direction']} sannsynligheten"
)
return "Beslutningen er hovedsakelig basert pa: " + ", ".join(parts) + "."
```
---
## Norsk offentlig sektor
### Regulatorisk sjekkliste for edge AI
| Regulering | Krav | Edge AI-implementering |
|-----------|------|----------------------|
| GDPR Art. 5 | Dataminimering | Prosesser pa edge, send kun aggregert |
| GDPR Art. 22 | Automatiserte beslutninger | Menneskelig overstyring pakrevd |
| GDPR Art. 30 | Behandlingsprotokoll | Audit logging pa alle noder |
| GDPR Art. 32 | Tekniske tiltak | Kryptering, tilgangskontroll |
| GDPR Art. 35 | DPIA | Dokumentert vurdering |
| EU AI Act | Risikoklassifisering | Kategoriser edge AI-systemer |
| NSM Grunnprinsipper | IKT-sikkerhet | Policy-haandheving via Arc |
| Forvaltningsloven | Begrunnelse | Forklarbarhetsmekanisme |
| Offentleglova | Innsyn | Tilgjengelig dokumentasjon |
| Arkivlova | Bevaring | Langsiktig audit-lagring |
### Datatilsynets anbefalinger for AI
- Gjennomfoer DPIA for alle AI-systemer som prosesserer persondata
- Implementer privacy by design og privacy by default
- Sikre rett til forklaring ved automatiserte beslutninger
- Dokumenter rettslig grunnlag for AI-behandling
- Gjennomfoer jevnlige revisjoner av AI-systemets funksjon
---
## Beslutningsrammeverk
| Scenario | Anbefaling | Begrunnelse |
|----------|------------|-------------|
| Hoey-risiko AI (helse, justis) | Full DPIA + EU AI Act compliance + audit | Pakrevd, strengeste krav |
| Begrenset risiko (chatbot, hjelp) | Transparenserklaring + logging | Informasjonsplikt |
| Minimal risiko (spam, klassifisering) | God praksis + logging | Frivillig, men anbefalt |
| Multi-edge deployment | Azure Arc + Policy + sentralisert logging | Konsistent haandheving |
| Sensitive persondata | DPIA + kryptering + forklarbarhet | GDPR-krav |
| Offentlig forvaltning | Full compliance-stack + begrunnelse | Forvaltningsloven |
---
## For Cosmo
- **DPIA er pakrevd for de fleste edge AI-systemer** i offentlig sektor som prosesserer persondata — bruk den strukturerte malen og implementer automatiserte kontrollsjekker
- **Audit logging pa edge ma synkroniseres sentralt** — bruk Azure Monitor Agent (OMS) pa alle edge-noder og lagre logger i Log Analytics Workspace i Norway East med 7 ars retention
- **EU AI Act krever risikoklassifisering** — kategoriser edge AI-systemer tidlig i prosjektet og implementer kravene for riktig risikoniva for du deployer
- **Forklarbarhets er et lovkrav ved automatiserte beslutninger** — implementer lettvekts SHAP-basert forklarbarhet pa edge og generer norskspraklige forklaringer for brukere og saksbehandlere
- **Azure Arc + Policy er den eneste skalerbare maaten** a handheve compliance pa tvers av mange edge-noder — definer policies sentralt og la Arc sikre at alle noder er compliant

View file

@ -0,0 +1,375 @@
# Sovereign Cloud for Norwegian AI
**Last updated:** 2026-02
**Status:** GA
**Category:** Hybrid Cloud & Edge AI
---
## Introduksjon
Microsoft Sovereign Cloud er en suite av kapabiliteter og deploymentmodeller designet for a hjelpe myndigheter og regulerte industrier med a oppfylle krav til dataresidency, compliance og operasjonell suverenitet — uten a gi avkall pa fordelene ved hyperscale sky-innovasjon. For norsk offentlig sektor er dette sarlig relevant gitt strenge krav fra NSM, Datatilsynet, og EU-regulering.
Sovereign Cloud tilbyr tre deploymentmodeller: Sovereign Public Cloud i Microsoft-drevne datasentre innenfor definerte geopolitiske grenser (f.eks. Norway East/West), Sovereign Private Cloud via Azure Local for customer-kontrollerte miljoer, og National Partner Clouds for lokaliserte suverene skyimplementeringer. Hver modell balanserer skyverdi mot suverenitetskontroller.
For AI-arbeidsbelastninger i norsk offentlig sektor kombinerer Sovereign Cloud dataresendens med konfidensielle beregningsteknologier, kundestyrte krypteringsnoekler, og policy-baserte guardrails — alt for a muliggjore avansert AI-bruk uten a kompromittere suverenitet.
---
## Kjernekomponenter
| Komponent | Formal | Teknologi |
|-----------|--------|-----------|
| Sovereign Landing Zone | Infrastruktur-as-code for suverene miljoer | Bicep/Terraform |
| Sovereignty Baseline Policies | Azure Policy for dataresidency og konfidensialitet | Azure Policy |
| Azure Confidential Computing | Beskyttelse av data under prosessering | AMD SEV-SNP, Intel TDX |
| Customer-Managed Keys (CMK) | Kundekontrollert kryptering | Azure Key Vault mHSM |
| Data Guardian | Datastyring for suverene arbeidsbelastninger | Preview |
| External Key Management | Kundekontrollert nokkelhandtering utenfor Azure | Integration |
| Transparency Logs | Innsyn i operatoerens aktiviteter | Audit |
| Azure Local | On-premises sky-infrastruktur | Sovereign Private Cloud |
---
## Data Sovereignty Architecture
### Sovereign Landing Zone (SLZ)
SLZ er en variant av Azure Landing Zone spesielt designet for digital suverenitet:
```
┌─────────────────────────────────────────────────┐
│ Sovereign Landing Zone │
│ │
│ ┌─────────────────────────────────────────────┐ │
│ │ Management Group Hierarchy │ │
│ │ Root → Sovereign → Production → AI │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ ┌──────────────┐ ┌───────────────────────────┐ │
│ │ Sovereignty │ │ Workload Templates │ │
│ │ Baseline │ │ │ │
│ │ Policies │ │ - AI Foundry template │ │
│ │ │ │ - AKS template │ │
│ │ - Data │ │ - App Service template │ │
│ │ residency │ │ - Storage template │ │
│ │ - CMK │ │ │ │
│ │ - Network │ │ │ │
│ └──────────────┘ └───────────────────────────┘ │
│ │
│ ┌──────────────┐ ┌───────────────────────────┐ │
│ │ Confidential │ │ Monitoring & Audit │ │
│ │ Computing │ │ │ │
│ │ Layer │ │ - Azure Monitor │ │
│ │ │ │ - Transparency Logs │ │
│ │ - CVMs │ │ - Compliance Manager │ │
│ │ - mHSM │ │ - Defender for Cloud │ │
│ └──────────────┘ └───────────────────────────┘ │
└─────────────────────────────────────────────────┘
```
### Deployment med Bicep
```bicep
// Sovereign Landing Zone for norsk offentlig AI
targetScope = 'managementGroup'
@description('Sovereignty Baseline Policy Assignment')
resource sovereigntyBaseline 'Microsoft.Authorization/policyAssignments@2024-04-01' = {
name: 'sovereignty-baseline-norway'
properties: {
displayName: 'Sovereignty Baseline - Norway AI'
policyDefinitionId: '/providers/Microsoft.Authorization/policySetDefinitions/sovereignty-baseline'
parameters: {
allowedLocations: {
value: ['norwayeast', 'norwaywest']
}
requireConfidentialComputing: {
value: true
}
requireCustomerManagedKeys: {
value: true
}
requirePrivateEndpoints: {
value: true
}
}
}
}
// Nektelsespolicy: Hindre data fra a forlate Norge
resource dataResidencyPolicy 'Microsoft.Authorization/policyAssignments@2024-04-01' = {
name: 'data-residency-norway'
properties: {
displayName: 'Enforce Norway Data Residency'
policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/e56962a6-4747-49cd-b67b-bf8b01975c4c'
parameters: {
listOfAllowedLocations: {
value: ['norwayeast', 'norwaywest']
}
}
enforcementMode: 'Default'
}
}
// Customer-Managed Key krav for AI-lagring
resource cmkPolicy 'Microsoft.Authorization/policyAssignments@2024-04-01' = {
name: 'cmk-encryption-ai'
properties: {
displayName: 'Require CMK for AI Storage'
policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/6fac406b-40ca-413b-bf8e-0bf964659c25'
enforcementMode: 'Default'
}
}
```
---
## Regional Deployment Constraints
### Azure-regioner i Norge
| Region | Lokasjon | Tjenester | GA-status |
|--------|----------|-----------|-----------|
| Norway East | Oslo | Fullt AI-spekter | GA |
| Norway West | Stavanger | DR og backup | GA |
### AI-tjenester tilgjengelig i Norway East
| Tjeneste | Tilgjengelig | Sovereign-kompatibel |
|----------|-------------|---------------------|
| Azure OpenAI | Ja | Ja (med CMK) |
| Azure AI Search | Ja | Ja |
| Azure ML | Ja | Ja |
| Azure AI Services | Ja | Ja |
| Azure AI Foundry | Ja | Ja |
| Confidential VMs | Ja | Ja |
| AKS | Ja | Ja |
### Deployment-begrensninger
```python
# Verifiser at AI-ressurser deployes i tillatte regioner
from azure.mgmt.resource import ResourceManagementClient
from azure.identity import DefaultAzureCredential
class SovereigntyChecker:
ALLOWED_REGIONS = ["norwayeast", "norwaywest"]
def __init__(self):
self.credential = DefaultAzureCredential()
def verify_resource_locations(self, subscription_id: str) -> list[dict]:
"""Verifiser at alle AI-ressurser er i tillatte regioner"""
client = ResourceManagementClient(self.credential, subscription_id)
violations = []
ai_resource_types = [
"Microsoft.CognitiveServices/accounts",
"Microsoft.MachineLearningServices/workspaces",
"Microsoft.Search/searchServices",
"Microsoft.OpenAI/accounts"
]
for resource in client.resources.list():
if resource.type in ai_resource_types:
if resource.location not in self.ALLOWED_REGIONS:
violations.append({
"resource_name": resource.name,
"resource_type": resource.type,
"location": resource.location,
"severity": "CRITICAL",
"remediation": f"Flytt til {self.ALLOWED_REGIONS}"
})
return violations
def verify_data_residency(self, subscription_id: str) -> dict:
"""Generer dataresidency-rapport"""
violations = self.verify_resource_locations(subscription_id)
return {
"status": "COMPLIANT" if not violations else "NON_COMPLIANT",
"allowed_regions": self.ALLOWED_REGIONS,
"total_ai_resources": self._count_ai_resources(subscription_id),
"violations": violations,
"recommendation": (
"Alle AI-ressurser er innenfor tillatte regioner"
if not violations
else f"{len(violations)} ressurser krever flytting"
)
}
```
---
## Compliance Audit Trails
### Logging-arkitektur for sovereign AI
```
┌─────────────────────────────────────────┐
│ AI-arbeidsbelastning │
│ │
│ [Inferens] → [Azure Monitor] │
│ [Trening] → [Log Analytics] │
│ [Datatilgang] → [Diagnostic Settings] │
└────────────────┬────────────────────────┘
┌─────────────────────────────────────────┐
│ Compliance Audit Layer │
│ │
│ [Transparency Logs] │
│ → Microsoft operator-aktiviteter │
│ │
│ [Azure Activity Log] │
│ → Ressurs-operasjoner │
│ │
│ [Key Vault Audit Log] │
│ → Nokkel-tilgang og -bruk │
│ │
│ [Purview Audit] │
│ → Data-tilgang og -klassifisering │
└────────────────┬────────────────────────┘
┌─────────────────────────────────────────┐
│ Long-term Retention (Norway) │
│ │
│ [Immutable Blob Storage] │
│ → 7 ars retention for compliance │
│ → WORM-policy (Write Once Read Many) │
│ → Norway East lokasjon │
└─────────────────────────────────────────┘
```
### Implementering av audit trail
```python
# Sovereign AI audit trail konfigurasjon
from azure.mgmt.monitor import MonitorManagementClient
class SovereignAuditConfiguration:
def configure_ai_diagnostics(self, resource_id: str, workspace_id: str):
"""Konfigurer diagnostikk for sovereign AI-ressurs"""
monitor_client = MonitorManagementClient(
self.credential, self.subscription_id
)
diagnostic_settings = {
"logs": [
{
"category": "RequestResponse",
"enabled": True,
"retentionPolicy": {"enabled": True, "days": 2555} # 7 ar
},
{
"category": "Audit",
"enabled": True,
"retentionPolicy": {"enabled": True, "days": 2555}
},
{
"category": "AllMetrics",
"enabled": True,
"retentionPolicy": {"enabled": True, "days": 365}
}
],
"workspaceId": workspace_id,
"storageAccountId": self.immutable_storage_id
}
monitor_client.diagnostic_settings.create_or_update(
resource_uri=resource_id,
name="sovereign-ai-diagnostics",
diagnostic_settings_resource=diagnostic_settings
)
```
---
## Vendor Lock-in Mitigation
### Strategier for a unnga avhengighet
| Strategi | Implementering | Effekt |
|----------|----------------|--------|
| ONNX-format | Bruk ONNX for alle modeller | Portabilitet mellom plattformer |
| Open-source SLM | Phi-modeller med MIT-lisens | Ingen leverandor-avhengighet |
| IaC (Bicep/Terraform) | Infrastruktur som kode | Reproduserbar deployment |
| Standard API-er | OpenAI-kompatible API-er | Bytt leverandor uten kodeendring |
| Multi-cloud exit plan | Dokumentert migrasjonsplan | Redusert risiko |
| Container-basert | Docker/Kubernetes | Platform-uavhengig |
### Exit-strategi-sjekkliste
```markdown
## Exit-strategi for sovereign AI-plattform
### Data
- [ ] Alle data kan eksporteres i standardformater (JSON, Parquet, CSV)
- [ ] Vektordatabase kan eksporteres (HNSW-indekser)
- [ ] Krypteringsnoekler lagret i customer-controlled HSM
- [ ] Backup-kopier i kundens kontroll
### Modeller
- [ ] Alle modeller i ONNX-format
- [ ] Fine-tuning-data og adaptere eksporterbare
- [ ] Evalueringsmetriker dokumentert for sammenligning
- [ ] Ingen proprietaere modellformater
### Infrastruktur
- [ ] All infrastruktur definert i Bicep/Terraform
- [ ] Kubernetes-arbeidsbelastninger med standard Helm charts
- [ ] Ingen Azure-spesifikke SDK-avhengigheter i forretningslogikk
- [ ] CI/CD-pipelines platform-uavhengige
### Kontrakt
- [ ] Dataportabilitet klausul i avtale
- [ ] Migrasjonsbistand klausul
- [ ] Oppsigelsesperiode tilstrekkelig for migrasjon
- [ ] Eierskap til data og modeller tydelig definert
```
---
## Norsk offentlig sektor
### Relevante krav og rammeverk
| Krav | Kilde | Sovereign Cloud-losning |
|------|-------|------------------------|
| Data ma lagres i Norge/EOS | Schrems II / Datatilsynet | Norway East/West regioner |
| Kryptering av data ved hvile og transport | NSM Grunnprinsipper | CMK + TLS 1.3 |
| Kryptering av data under prosessering | NSM / okt sikkerhet | Confidential Computing |
| Innsyn i operatoerens handlinger | Offentleglova / transparens | Transparency Logs |
| Dokumentert risikovurdering | Utredningsinstruksen | Compliance Manager |
| Leverandoruavhengighet | Arkitekturprinsippene (Digdir) | ONNX + open source + IaC |
| Universell utforming | Likestillingsloven | N/A (applikasjonsniva) |
### Anskaffelseshensyn
- **SSA-L/SSA-T**: Sovereign Cloud-kapabiliteter bor spesifiseres i kravspesifikasjonen
- **Databehandleravtale**: Ma dekke dataresidency, kryptering, og audit-rettigheter
- **Exit-klausul**: Kontrakten ma sikre rett til dataeksport og migrasjonsbistand
- **Gevinstrealisering**: Dokumenter besparelser vs. on-premises drift
---
## Beslutningsrammeverk
| Scenario | Anbefaling | Begrunnelse |
|----------|------------|-------------|
| Standard AI-arbeidsbelastning | Sovereign Public Cloud (Norway East) | Enklest, fullt spekter av tjenester |
| Sensitive data med hoy sikkerhet | SPC + Confidential Computing + CMK | Maksimal beskyttelse |
| Forsvar/nasjonal sikkerhet | Sovereign Private Cloud (Azure Local) | Full kontroll, air-gapped |
| Flerparts-analyse mellom etater | Confidential Computing pa SPC | Verifiserbar isolasjon |
| Compliance-kritisk AI | SPC + Compliance Manager + Audit trails | Dokumenterbar etterlevelse |
| Hybrid sky + on-prem | Azure Arc + SLZ | Enhetlig forvaltning |
---
## For Cosmo
- **Sovereign Landing Zone er den korrekte startarkitekturen** for alle AI-prosjekter i norsk offentlig sektor — deploy SLZ med Sovereignty Baseline Policies som forste steg
- **Norway East er primary-regionen for AI** — alle Azure AI-tjenester inkludert OpenAI og Confidential VMs er tilgjengelig der, med Norway West for DR
- **Customer-Managed Keys (CMK) via Managed HSM er et minimum** for sovereign AI — dette gir kundekontrollert kryptering og tilfredsstiller NSM-krav
- **Vendor lock-in-mitigering er pabudt** ifoolge Digdirs arkitekturprinsipper — bruk ONNX, open-source SLM (Phi), og Infrastructure-as-Code for a sikre portabilitet
- **Transparency Logs og Compliance Manager er kritiske for revisjon** — norsk offentlig sektor ma kunne dokumentere operatoertilgang og compliance-status for Riksrevisjonen og Datatilsynet

View file

@ -0,0 +1,356 @@
# Windows AI and AI PC Capabilities
**Last updated:** 2026-02
**Status:** GA
**Category:** Hybrid Cloud & Edge AI
---
## Introduksjon
Windows AI-plattformen representerer Microsofts satsing pa lokal AI-inferens direkte pa klientenheter. Med Windows ML (Machine Learning), ONNX Runtime integrert i OS, og Neural Processing Units (NPU) i Copilot+ PC-er, kan AI-modeller kjores lokalt med full datakontroll, ingen nettverkslatens, og forutsigbar ytelse.
For norsk offentlig sektor er Windows AI relevant for klientbaserte AI-funksjoner som dokumentklassifisering, oppsummering, og informasjonsuttrekking — alt uten at data forlater enheten. Ansatte kan bruke AI-stoettede verktoy for daglige oppgaver uten bekymring for at sensitive data sendes til skytjenester. Phi-4 Mini, innebygd i Microsoft Edge som lokal SLM, demonstrerer denne tilnaermingen.
Windows ML er den anbefalte veien for a deploye ONNX-modeller pa Windows, med automatisk Execution Provider-discovery som velger beste tilgjengelige akselerator — NPU, GPU eller CPU — uten at utviklere trenger a kode for spesifikk hardware.
---
## Kjernekomponenter
| Komponent | Formal | Teknologi |
|-----------|--------|-----------|
| Windows ML | ONNX Runtime integrert i Windows | Windows App SDK |
| ONNX Runtime | Inferensmotor for AI-modeller | Open source |
| DirectML | GPU/NPU-akselerasjon (legacy) | Windows |
| Execution Providers | Hardware-spesifikke akseleratorer | QNN, OpenVINO, DML |
| Phi-4 Mini | Innebygd SLM i Microsoft Edge | Lokal inferens |
| AI Dev Gallery | Eksempler og modellkatalog | Open source |
| Foundry Local | Klare-til-bruk AI-modeller | Microsoft |
| Windows AI APIs | Innebygde AI-funksjoner | Windows SDK |
---
## Windows ML og ONNX Runtime
### Hvordan Windows ML fungerer
Windows ML inkluderer en kopi av ONNX Runtime og muliggjor dynamisk nedlasting av leverandorspesifikke Execution Providers (EP):
```
[ONNX-modell] → [Windows ML] → [EP Discovery] → [Inferens]
┌─────────────┼─────────────┐
↓ ↓ ↓
[Qualcomm QNN] [Intel OpenVINO] [DirectML]
(Snapdragon NPU) (Intel NPU) (GPU/CPU)
```
### Kodeeksempel: Windows ML-inferens i C#
```csharp
// Windows ML-inferens med automatisk EP-discovery
using Microsoft.ML.OnnxRuntime;
public class WindowsMLService
{
private InferenceSession _session;
public async Task<bool> InitializeAsync(string modelPath)
{
try
{
var options = new SessionOptions();
// Windows ML velger automatisk beste EP:
// 1. NPU (Qualcomm QNN / Intel OpenVINO) - lavest energibruk
// 2. GPU (DirectML) - hoeyest ytelse
// 3. CPU - alltid tilgjengelig fallback
// EP-er lastes ned automatisk via Windows Update
options.AppendExecutionProvider_WindowsML();
_session = new InferenceSession(modelPath, options);
return true;
}
catch (Exception ex)
{
Console.WriteLine($"Kunne ikke initialisere modell: {ex.Message}");
return false;
}
}
public float[] Classify(float[] input, int[] shape)
{
var tensor = new DenseTensor<float>(input, shape);
var inputs = new List<NamedOnnxValue>
{
NamedOnnxValue.CreateFromTensor("input", tensor)
};
using var results = _session.Run(inputs);
return results.First().AsTensor<float>().ToArray();
}
}
```
### Kodeeksempel: Python med Windows ML
```python
# Windows ML-inferens i Python
import onnxruntime as ort
import numpy as np
# Opprett session med Windows ML EP
session_options = ort.SessionOptions()
session = ort.InferenceSession(
"model.onnx",
sess_options=session_options,
providers=["WindowsMLExecutionProvider", "CPUExecutionProvider"]
)
# Kjor inferens
input_data = np.random.randn(1, 3, 224, 224).astype(np.float32)
result = session.run(None, {"input": input_data})
print(f"Output shape: {result[0].shape}")
```
### Modellkompilering for optimal ytelse
```csharp
// Kompiler modell for optimal EP-ytelse (forhands-optimalisering)
using Microsoft.ML.OnnxRuntime;
// Kompilering kan ta flere minutter — gjor dette i bakgrunnen
var compileOptions = new OrtModelCompilationOptions(sessionOptions);
compileOptions.SetInputModelPath(modelPath);
compileOptions.SetOutputModelPath(compiledModelPath);
// Kompiler modellen (optimal for enhetens hardware)
await Task.Run(() => compileOptions.CompileModel());
// Bruk kompilert modell for raskere inferens
var session = new InferenceSession(compiledModelPath, sessionOptions);
```
---
## Neural Processing Unit (NPU)
### Hva er en NPU?
En Neural Processing Unit er en dedikert AI-brikke designet spesifikt for a utfore AI-oppgaver som moenstergjenkjenning, klassifisering og naturlig sprakbehandling. I motsetning til CPU (generelle beregninger) og GPU (parallellprosessering for grafikk), er NPU-er optimalisert for nevrale nettverksoperasjoner med lavt energiforbruk.
### NPU-landskap i Copilot+ PC-er
| Leverandoer | Chip | TOPS | Prosess | Plattform |
|-------------|------|------|---------|-----------|
| Qualcomm | Snapdragon X Elite | 45 TOPS | 4nm | ARM64 |
| Qualcomm | Snapdragon X Plus | 45 TOPS | 4nm | ARM64 |
| Intel | Core Ultra 200V | 48 TOPS | Intel 4 | x64 |
| AMD | Ryzen AI 300 | 50 TOPS | 4nm | x64 |
### NPU vs GPU vs CPU for AI
| Aspekt | NPU | GPU | CPU |
|--------|-----|-----|-----|
| Energiforbruk | Lavest | Hoeyest | Medium |
| AI-ytelse | Hoey (spesialisert) | Hoeyest (generell) | Lavest |
| Tilgjengelighet | Nye PC-er | Diskret/integrert | Alle |
| Modellstoette | INT4/INT8 | FP16/FP32 | Alle formater |
| Best for | Alltid-pa AI | Tunge oppgaver | Fallback |
### Tilgang til NPU via Windows ML
```csharp
// Automatisk NPU-bruk via Windows ML
// Ingen eksplisitt NPU-koding nodvendig — Windows ML velger beste EP
// For avansert kontroll: Sjekk tilgjengelig hardware
public void CheckAICapabilities()
{
var session = new InferenceSession("model.onnx");
var providers = session.GetAvailableProviders();
foreach (var provider in providers)
{
Console.WriteLine($"Tilgjengelig EP: {provider}");
// Eksempel output:
// QNNExecutionProvider (Qualcomm NPU)
// OpenVINOExecutionProvider (Intel NPU)
// DmlExecutionProvider (GPU)
// CPUExecutionProvider (CPU)
}
}
```
---
## Copilot+ PC Specifications
### Minimumskrav for Copilot+ PC
| Krav | Spesifikasjon |
|------|---------------|
| NPU | Minimum 40 TOPS |
| RAM | 16 GB eller mer |
| Lagring | 256 GB SSD eller mer |
| OS | Windows 11 24H2 eller nyere |
### Windows AI APIs (innebygde funksjoner)
| API | Funksjon | Krav | Status |
|-----|----------|------|--------|
| OCR | Tekstgjenkjenning i bilder | Copilot+ PC | GA |
| Image Description | Bildebeskrivelese med AI | Copilot+ PC | GA |
| Text Summarization | Oppsummering av tekst | Copilot+ PC | GA |
| Object Erase | Fjern objekter fra bilder | Copilot+ PC | GA |
| Image Segmentation | Segmentering av bilder | Copilot+ PC | GA |
| Phi Silica | Innebygd SLM i Windows | Copilot+ PC | GA |
### Bruk av Windows AI APIs
```csharp
// Windows AI API: Tekstoppsummering
using Windows.AI;
public async Task<string> SummarizeText(string text)
{
var summarizer = await TextSummarizer.CreateAsync();
var result = await summarizer.SummarizeAsync(text, new SummarizerOptions
{
MaxSentences = 3,
Language = "no" // Norsk stoette
});
return result.Summary;
}
```
---
## Local LLM Inference on Device
### Phi-4 Mini i Microsoft Edge
Microsoft Edge inkluderer Phi-4 Mini som lokal SLM, tilgjengelig via Web AI API-er:
```javascript
// Prompt API i Microsoft Edge (Phi-4 Mini lokal inferens)
// Ingen nettverkskall — alt skjer pa enheten
async function localAIClassification(text) {
// Sjekk tilgjengelighet
const availability = await ai.languageModel.capabilities();
if (availability.available === 'no') {
console.log('Lokal AI ikke tilgjengelig pa denne enheten');
return null;
}
// Opprett session med system-prompt
const session = await ai.languageModel.create({
systemPrompt: `Du er en dokumentklassifiserer for norsk offentlig sektor.
Klassifiser dokumenter i en av disse kategoriene:
- Vedtak
- Klage
- Henvendelse
- Intern notat
- Hoeringssvar
Svar KUN med kategorinavnet.`
});
// Kjor lokal inferens
const result = await session.prompt(
`Klassifiser dette dokumentet: "${text.substring(0, 500)}"`
);
session.destroy();
return result.trim();
}
```
```javascript
// Writing Assistance API: Oppsummering i Edge
async function summarizeDocument(text) {
const summarizer = await ai.summarizer.create({
type: 'key-points',
length: 'short',
format: 'markdown'
});
const summary = await summarizer.summarize(text);
summarizer.destroy();
return summary;
}
```
### Foundry Local for rikere modeller
```bash
# Installer Foundry Local for lokale AI-modeller
# Gir tilgang til storre modeller enn de innebygde
# List tilgjengelige modeller
foundry model list
# Last ned Phi-3.5 for lokal bruk
foundry model download phi-3.5-mini
# Start inferens-server
foundry model serve phi-3.5-mini --port 11434
# Bruk via OpenAI-kompatibelt API
curl http://localhost:11434/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "phi-3.5-mini",
"messages": [
{"role": "system", "content": "Du er en hjelpsom assistent for norsk offentlig sektor."},
{"role": "user", "content": "Oppsummer folgende utredning..."}
]
}'
```
---
## Norsk offentlig sektor
### Brukstilfeller for Windows AI i offentlig sektor
| Brukstilfelle | Windows AI-losning | Fordel |
|---------------|-------------------|--------|
| E-post-triage | Phi-4 Mini (Edge Prompt API) | Klassifiser innkommende post uten sky |
| Dokumentoppsummering | Windows AI Summarizer API | Rask oversikt over lange dokumenter |
| Skjema-lesing | Windows AI OCR | Digitalisering av papirskjemaer |
| Intern Q&A | Foundry Local + Phi-3.5 | Svar basert pa lokale dokumenter |
| Referat-skriving | Edge Writing Assistance | Utkast til moetereferater |
### Sikkerhetshensyn
- Alle data forblir pa enheten — ingen nettverkskall for AI-inferens
- Phi-4 Mini-modellen er innebygd i Edge, ikke nedlastet fra sky per session
- Windows ML-modeller lagres lokalt og krever ingen sky-autentisering
- IT-administratorer kan kontrollere AI-API-tilgjengelighet via Group Policy
---
## Beslutningsrammeverk
| Scenario | Anbefaling | Begrunnelse |
|----------|------------|-------------|
| Enkel tekst-AI pa klient | Edge Prompt API (Phi-4 Mini) | Innebygd, ingen oppsett |
| Oppsummering/skriving | Edge Writing Assistance APIs | Spesialisert, hoey kvalitet |
| Custom ONNX-modell | Windows ML med automatisk EP | Best hardware-utnyttelse |
| Storre SLM lokalt | Foundry Local | OpenAI-kompatibelt API |
| Enterprise-utrulling | Windows ML + Intune-administrasjon | Sentralisert styring |
| NPU-optimalisert | Copilot+ PC med Windows ML | Best ytelse/watt |
---
## For Cosmo
- **Windows ML er den anbefalte veien for lokal AI pa Windows** — det erstatter DirectML og gir automatisk hardware-deteksjon og EP-nedlasting, noe som forenkler deployment dramatisk
- **Copilot+ PC-er med NPU muliggjor always-on AI** med lavt energiforbruk — anbefal dette for klientbaserte AI-oppgaver som dokumentklassifisering og oppsummering
- **Edge Prompt API (Phi-4 Mini) er den laveste terskelen for lokal AI** — utviklere kan bruke JavaScript-API-er for a integrere AI uten modellnedlasting eller kompleks oppsett
- **For norsk offentlig sektor: Lokal AI pa klientenheter eliminerer behovet for a sende sensitive data til sky** — dette forenkler DPIA og Schrems II-compliance for enklere AI-brukstilfeller
- **Modellkompilering er viktig for produksjonsytelse** — kompiler ONNX-modeller for target-hardware for a oppna opptil 2-3x forbedring i inferenshastighet etter forste kjoring