diff --git a/README.md b/README.md index f9e6dba..d0d1ed8 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,8 @@ Follow the deployment guide to deploy this solution to your own Azure subscripti > **Note:** This solution accelerator requires **Azure Developer CLI (azd) version 1.15.0 or higher**. [Download azd here](https://learn.microsoft.com/en-us/azure/developer/azure-developer-cli/install-azd). +> **Note:** This solution accelerator also requires **Bicep CLI version 0.33.0 or higher** for compiling infrastructure templates. [Install Bicep](https://learn.microsoft.com/azure/azure-resource-manager/bicep/install). + [**📘 Click here to launch the Deployment Guide**](./docs/deploymentguide.md)
@@ -130,6 +132,7 @@ Follow the deployment guide to deploy this solution to your own Azure subscripti | **Microsoft Purview** | Existing tenant-level Purview account (or ability to create one) | | **Azure CLI** | Version 2.61.0 or later | | **Azure Developer CLI** | Version 1.15.0 or later | + | **Bicep CLI** | Version 0.33.0 or later | | **Quota** | Sufficient Azure OpenAI quota ([check here](./docs/quota_check.md)) | > **Note:** Fabric automation is optional. To disable all Fabric automation, set `fabricCapacityPreset = 'none'` and `fabricWorkspacePreset = 'none'` in `infra/main.bicepparam`. diff --git a/docs/deploymentguide.md b/docs/deploymentguide.md index e987506..596e479 100644 --- a/docs/deploymentguide.md +++ b/docs/deploymentguide.md @@ -25,6 +25,7 @@ To deploy this solution accelerator, ensure you have access to an [Azure subscri |------|----------------|--------------| | Azure CLI | 2.61.0+ | [Install Azure CLI](https://learn.microsoft.com/cli/azure/install-azure-cli) | | Azure Developer CLI (azd) | 1.15.0+ | [Install azd](https://learn.microsoft.com/azure/developer/azure-developer-cli/install-azd) | +| Bicep CLI | 0.33.0+ | [Install Bicep](https://learn.microsoft.com/azure/azure-resource-manager/bicep/install) | | Git | Latest | [Install Git](https://git-scm.com/downloads) | | PowerShell | 7.0+ | [Install PowerShell](https://learn.microsoft.com/powershell/scripting/install/installing-powershell) | @@ -238,6 +239,7 @@ After setting these variables, run `azd up` normally. The deployment will attach | `postgreSqlNetworkIsolation` | PostgreSQL private networking toggle (defaults to `networkIsolation`) | `networkIsolation` | | `useExistingVNet` | Reuse an existing VNet | `false` | | `existingVnetResourceId` | Existing VNet resource ID (when `useExistingVNet=true`) | `` | +| `existingLogAnalyticsWorkspaceResourceId` | Existing Log Analytics workspace to receive Foundry app + PostgreSQL + Fabric capacity diagnostics. May live in another subscription within the same tenant. | `` | | `vmUserName` | Jump box VM admin username | `VM_ADMIN_USERNAME` env var or `testvmuser` | | `vmAdminPassword` | Jump box VM admin password | `VM_ADMIN_PASSWORD` env var | @@ -267,8 +269,20 @@ To check and adjust quota settings, follow the [Quota Check Guide](./quota_check
Reusing Existing Resources -**Log Analytics Workspace:** -See [Parameter Guide](./parameter_guide.md) for Log Analytics reuse guidance. +**Log Analytics Workspace (BYO observability):** + +By default the wrapper does not create a Log Analytics workspace or Application Insights, so the deployed Foundry application and wrapper-managed resources (PostgreSQL Flexible Server, Fabric capacity) have no telemetry sink. If you already have a centralized workspace, point the deployment at it: + +```powershell +azd env set EXISTING_LOG_ANALYTICS_WORKSPACE_RESOURCE_ID "/subscriptions//resourceGroups//providers/Microsoft.OperationalInsights/workspaces/" +``` + +When set, the deployment will: +1. Create an Application Insights component in the deployment resource group, linked to your existing workspace. +2. Route PostgreSQL diagnostic logs and metrics to your workspace. +3. Route Fabric capacity diagnostic logs and metrics to your workspace. + +The workspace may live in a different resource group or subscription within the same tenant. The identity running `azd up` needs **`Microsoft.Insights/diagnosticSettings/write`** on the workspace itself (covered by the built-in **Log Analytics Contributor** role scoped to the workspace or its resource group — subscription-wide rights are not required). See the **Observability — Bring Your Own Log Analytics Workspace** section in the [Parameter Guide](./parameter_guide.md) for the full output reference (App Insights resource ID, connection string, instrumentation key) and notes on deployment-history exposure of those values.
diff --git a/docs/parameter_guide.md b/docs/parameter_guide.md index b62882f..2220286 100644 --- a/docs/parameter_guide.md +++ b/docs/parameter_guide.md @@ -82,6 +82,55 @@ param fabricCapacitySku = 'F2' // adjust SKU as needed --- +## Observability — Bring Your Own Log Analytics Workspace + +By default the wrapper sets `deployLogAnalytics = false`, so the AI Landing Zone does not create a new Log Analytics workspace and Application Insights is not provisioned. If you already have a centralized Log Analytics workspace (for example one shared across the platform), you can wire the deployed Foundry application and the wrapper-managed resources (PostgreSQL Flexible Server, Fabric capacity) to it. + +### How it works + +When you set `existingLogAnalyticsWorkspaceResourceId`: + +1. An **Application Insights** component is created in the deployment resource group and linked to your existing workspace via `WorkspaceResourceId`. Its name follows the same `appInsightsName` convention (`appi-`). +2. **PostgreSQL Flexible Server** diagnostic settings (all logs + AllMetrics) are routed to your workspace. +3. **Fabric capacity** diagnostic settings (all logs + AllMetrics) are routed to your workspace. +4. The connection string and instrumentation key are exposed as deployment outputs so post-provision automation (or your application configuration) can pick them up. + +> **Note:** This is wrapper-side wiring. The upstream AI Landing Zone submodule does not natively support a BYO Log Analytics workspace, so leave `deployLogAnalytics = false` and `deployAppInsights = true` (the defaults) when using BYO so the LAZ does not create its own workspace + Application Insights pair. + +### Setting it via azd env + +```powershell +azd env set EXISTING_LOG_ANALYTICS_WORKSPACE_RESOURCE_ID "/subscriptions//resourceGroups//providers/Microsoft.OperationalInsights/workspaces/" +``` + +Or set it directly in `infra/main.bicepparam`: + +```bicep +param existingLogAnalyticsWorkspaceResourceId = '/subscriptions//resourceGroups//providers/Microsoft.OperationalInsights/workspaces/' +``` + +### Outputs + +| Output | Description | +|--------|-------------| +| `existingLogAnalyticsWorkspaceResourceIdOut` | Echo of the supplied workspace resource ID | +| `byoApplicationInsightsResourceId` | Resource ID of the App Insights component created against the BYO workspace | +| `byoApplicationInsightsName` | Name of the App Insights component | +| `byoApplicationInsightsConnectionString` | Connection string for app instrumentation | +| `byoApplicationInsightsInstrumentationKey` | Instrumentation key for legacy SDKs | + +> **Sensitive outputs:** The connection string and instrumentation key are bootstrap credentials for sending telemetry to your Application Insights resource. They are emitted as deployment outputs so post-provision scripts and `azd` env can wire them into application configuration. Anyone with read access to the deployment history (subscription/RG `Microsoft.Resources/deployments/read`) can retrieve these values — keep that access scoped appropriately. + +### Permissions + +The identity running the deployment needs permission to attach diagnostic settings to the workspace and to create the Application Insights component: + +- **`Microsoft.Insights/diagnosticSettings/write`** on the BYO Log Analytics workspace (or its resource group). The built-in **Log Analytics Contributor** role on the workspace (or its RG) covers this — there is no need to grant subscription-wide rights. +- **`Microsoft.Insights/components/write`** on the deployment resource group (covered by **Contributor** on the deployment RG, which the deployment identity already needs to provision the rest of the stack). +- The PostgreSQL Flexible Server and Fabric capacity that emit diagnostics are wrapper-managed in the deployment RG, so no additional cross-resource permissions are required. + +--- + ## Table of Contents 1. [Basic Parameters](#basic-parameters) 2. [Deployment Toggles](#deployment-toggles) diff --git a/infra/main.bicep b/infra/main.bicep index cf81bf7..64dc47d 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -111,6 +111,9 @@ param aiFoundryStorageAccountResourceId string = '' param aiFoundryCosmosDBAccountResourceId string = '' param keyVaultResourceId string = '' +@description('Optional. Full ARM resource ID of an existing Log Analytics workspace to use for observability of the deployed Foundry application and wrapper-managed resources (PostgreSQL, Fabric capacity). When provided, an Application Insights component is created in the deployment resource group and linked to this workspace, and diagnostic settings on the wrapper-managed resources are routed to it. Leave empty to skip BYO behavior. Format: /subscriptions/{subId}/resourceGroups/{rg}/providers/Microsoft.OperationalInsights/workspaces/{name}.') +param existingLogAnalyticsWorkspaceResourceId string = '' + @description('Identity options.') param useUAI bool = false param useCAppAPIKey bool = false @@ -405,6 +408,51 @@ var postgreSqlPrivateEndpoints = postgreSqlNetworkIsolation ? [ } ] : [] +// ---------------------------------------------------------------------- +// BYO Log Analytics Workspace (observability for the Foundry application +// and wrapper-managed resources). When existingLogAnalyticsWorkspaceResourceId +// is provided, diagnostic settings on wrapper-managed resources +// (currently PostgreSQL) are routed to that workspace. An +// Application Insights component is created in this resource group and +// linked to that workspace only when BYO Log Analytics is enabled, +// deployAppInsights is true, and deployLogAnalytics is false. +// ---------------------------------------------------------------------- +var byoLogAnalyticsEnabled = !empty(existingLogAnalyticsWorkspaceResourceId) +var byoCreateAppInsights = byoLogAnalyticsEnabled && deployAppInsights && !deployLogAnalytics + +resource byoAppInsights 'Microsoft.Insights/components@2020-02-02' = if (byoCreateAppInsights) { + name: appInsightsName + location: effectiveLocation + kind: 'web' + tags: deploymentTags + properties: { + Application_Type: 'web' + WorkspaceResourceId: existingLogAnalyticsWorkspaceResourceId + DisableIpMasking: false + publicNetworkAccessForIngestion: 'Enabled' + publicNetworkAccessForQuery: 'Enabled' + } +} + +var postgreSqlDiagnosticSettings = (deployPostgreSql && byoLogAnalyticsEnabled) ? [ + { + name: 'send-to-byo-law' + workspaceResourceId: existingLogAnalyticsWorkspaceResourceId + logCategoriesAndGroups: [ + { + categoryGroup: 'allLogs' + enabled: true + } + ] + metricCategories: [ + { + category: 'AllMetrics' + enabled: true + } + ] + } +] : [] + module postgreSqlFlexibleServer 'br/public:avm/res/db-for-postgre-sql/flexible-server:0.15.2' = if (deployPostgreSql) { name: 'postgresql-flexible' params: { @@ -424,6 +472,7 @@ module postgreSqlFlexibleServer 'br/public:avm/res/db-for-postgre-sql/flexible-s version: postgreSqlVersion storageSizeGB: postgreSqlStorageSizeGB privateEndpoints: postgreSqlPrivateEndpoints + diagnosticSettings: postgreSqlDiagnosticSettings tags: deploymentTags } } @@ -532,3 +581,12 @@ output desiredFabricWorkspaceName string = effectiveFabricWorkspaceName // Purview outputs (for post-provision scripts) output purviewAccountResourceId string = purviewAccountResourceId output purviewCollectionName string = !empty(purviewCollectionName) ? purviewCollectionName : (!empty(environmentName) ? 'collection-${environmentName}' : 'collection-${baseName}') + +// Observability outputs (BYO Log Analytics Workspace) +output existingLogAnalyticsWorkspaceResourceIdOut string = existingLogAnalyticsWorkspaceResourceId +output byoApplicationInsightsResourceId string = byoCreateAppInsights ? byoAppInsights.id : '' +output byoApplicationInsightsName string = byoCreateAppInsights ? byoAppInsights.name : '' +#disable-next-line outputs-should-not-contain-secrets +output byoApplicationInsightsConnectionString string = byoCreateAppInsights ? byoAppInsights.properties.ConnectionString : '' +#disable-next-line outputs-should-not-contain-secrets +output byoApplicationInsightsInstrumentationKey string = byoCreateAppInsights ? byoAppInsights.properties.InstrumentationKey : '' diff --git a/infra/main.bicepparam b/infra/main.bicepparam index 88e6bb0..300cd0b 100644 --- a/infra/main.bicepparam +++ b/infra/main.bicepparam @@ -23,6 +23,16 @@ param keyVaultResourceId = '' param useExistingVNet = false param existingVnetResourceId = readEnvironmentVariable('EXISTING_VNET_RESOURCE_ID', '') +// BYO Log Analytics Workspace for observability of the deployed Foundry +// application and wrapper-managed resources (PostgreSQL, Fabric capacity). +// When provided, diagnostic settings on the wrapper-managed resources are +// routed to this workspace. An Application Insights component is also +// created in this RG and linked to the workspace, but only when +// deployAppInsights is true and deployLogAnalytics is false (the wrapper +// defaults). Leave empty to skip BYO behavior. +// Format: /subscriptions/{subId}/resourceGroups/{rg}/providers/Microsoft.OperationalInsights/workspaces/{name} +param existingLogAnalyticsWorkspaceResourceId = readEnvironmentVariable('EXISTING_LOG_ANALYTICS_WORKSPACE_RESOURCE_ID', '') + // Optional additional Entra object IDs to grant Search roles. param aiSearchAdditionalAccessObjectIds = [] diff --git a/infra/main.json b/infra/main.json index ffd08db..6004a18 100644 --- a/infra/main.json +++ b/infra/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.42.1.51946", - "templateHash": "10203842773715246176" + "version": "0.43.8.12551", + "templateHash": "1222454929578026913" }, "description": "Deploys AI Landing Zone with Fabric capacity extension" }, @@ -354,6 +354,13 @@ "type": "string", "defaultValue": "" }, + "existingLogAnalyticsWorkspaceResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Full ARM resource ID of an existing Log Analytics workspace to use for observability of the deployed Foundry application and wrapper-managed resources (PostgreSQL, Fabric capacity). When provided, an Application Insights component is created in the deployment resource group and linked to this workspace, and diagnostic settings on the wrapper-managed resources are routed to it. Leave empty to skip BYO behavior. Format: /subscriptions/{subId}/resourceGroups/{rg}/providers/Microsoft.OperationalInsights/workspaces/{name}." + } + }, "useUAI": { "type": "bool", "defaultValue": false, @@ -825,6 +832,9 @@ "effectiveKeyVaultResourceId": "[if(not(empty(parameters('keyVaultResourceId'))), parameters('keyVaultResourceId'), resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')))]", "effectivePostgreSqlAdminPassword": "[if(equals(parameters('postgreSqlAdminPassword'), '$(secretOrRandomPassword)'), format('{0}!{1}', uniqueString(subscription().id, resourceGroup().id, parameters('postgreSqlServerName')), replace(parameters('generatedPostgreSqlAdminPassword'), '-', '')), parameters('postgreSqlAdminPassword'))]", "postgreSqlPrivateEndpoints": "[if(parameters('postgreSqlNetworkIsolation'), createArray(createObject('name', variables('postgreSqlPrivateEndpointName'), 'subnetResourceId', format('{0}/subnets/{1}', variables('effectiveVnetResourceId'), parameters('peSubnetName')), 'privateDnsZoneGroup', createObject('privateDnsZoneGroupConfigs', createArray(createObject('privateDnsZoneResourceId', resourceId('Microsoft.Network/privateDnsZones', variables('postgreSqlPrivateDnsZoneName'))))))), createArray())]", + "byoLogAnalyticsEnabled": "[not(empty(parameters('existingLogAnalyticsWorkspaceResourceId')))]", + "byoCreateAppInsights": "[and(and(variables('byoLogAnalyticsEnabled'), parameters('deployAppInsights')), not(parameters('deployLogAnalytics')))]", + "postgreSqlDiagnosticSettings": "[if(and(parameters('deployPostgreSql'), variables('byoLogAnalyticsEnabled')), createArray(createObject('name', 'send-to-byo-law', 'workspaceResourceId', parameters('existingLogAnalyticsWorkspaceResourceId'), 'logCategoriesAndGroups', createArray(createObject('categoryGroup', 'allLogs', 'enabled', true())), 'metricCategories', createArray(createObject('category', 'AllMetrics', 'enabled', true())))), createArray())]", "effectiveAiSearchResourceId": "[if(not(empty(parameters('aiSearchResourceId'))), parameters('aiSearchResourceId'), resourceId('Microsoft.Search/searchServices', parameters('searchServiceName')))]", "effectiveStorageAccountResourceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]", "effectiveFabricWorkspaceName": "[if(equals(variables('effectiveFabricWorkspaceMode'), 'byo'), if(not(empty(parameters('fabricWorkspaceName'))), parameters('fabricWorkspaceName'), if(not(empty(parameters('environmentName'))), format('workspace-{0}', parameters('environmentName')), format('workspace-{0}', parameters('baseName')))), if(not(empty(parameters('environmentName'))), format('workspace-{0}', parameters('environmentName')), format('workspace-{0}', parameters('baseName'))))]", @@ -2409,6 +2419,22 @@ "postgreSqlPrivateDnsZone" ] }, + "byoAppInsights": { + "condition": "[variables('byoCreateAppInsights')]", + "type": "Microsoft.Insights/components", + "apiVersion": "2020-02-02", + "name": "[parameters('appInsightsName')]", + "location": "[variables('effectiveLocation')]", + "kind": "web", + "tags": "[parameters('deploymentTags')]", + "properties": { + "Application_Type": "web", + "WorkspaceResourceId": "[parameters('existingLogAnalyticsWorkspaceResourceId')]", + "DisableIpMasking": false, + "publicNetworkAccessForIngestion": "Enabled", + "publicNetworkAccessForQuery": "Enabled" + } + }, "postgreSqlFlexibleServerResource": { "condition": "[parameters('deployPostgreSql')]", "existing": true, @@ -2491,6 +2517,9 @@ "privateEndpoints": { "value": "[variables('postgreSqlPrivateEndpoints')]" }, + "diagnosticSettings": { + "value": "[variables('postgreSqlDiagnosticSettings')]" + }, "tags": { "value": "[parameters('deploymentTags')]" } @@ -5114,8 +5143,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.42.1.51946", - "templateHash": "12325469206187179338" + "version": "0.43.8.12551", + "templateHash": "11293123033508500089" } }, "parameters": { @@ -5353,6 +5382,26 @@ "purviewCollectionName": { "type": "string", "value": "[if(not(empty(parameters('purviewCollectionName'))), parameters('purviewCollectionName'), if(not(empty(parameters('environmentName'))), format('collection-{0}', parameters('environmentName')), format('collection-{0}', parameters('baseName'))))]" + }, + "existingLogAnalyticsWorkspaceResourceIdOut": { + "type": "string", + "value": "[parameters('existingLogAnalyticsWorkspaceResourceId')]" + }, + "byoApplicationInsightsResourceId": { + "type": "string", + "value": "[if(variables('byoCreateAppInsights'), resourceId('Microsoft.Insights/components', parameters('appInsightsName')), '')]" + }, + "byoApplicationInsightsName": { + "type": "string", + "value": "[if(variables('byoCreateAppInsights'), parameters('appInsightsName'), '')]" + }, + "byoApplicationInsightsConnectionString": { + "type": "string", + "value": "[if(variables('byoCreateAppInsights'), reference('byoAppInsights').ConnectionString, '')]" + }, + "byoApplicationInsightsInstrumentationKey": { + "type": "string", + "value": "[if(variables('byoCreateAppInsights'), reference('byoAppInsights').InstrumentationKey, '')]" } } } \ No newline at end of file