From 4f075951fb23806641b80a0b8b0799819a6c0b61 Mon Sep 17 00:00:00 2001 From: Dave Rendon Date: Tue, 24 Sep 2024 06:39:10 -0500 Subject: [PATCH] add workload - chat gpt v2 --- .../azuredeploy.parameters.json | 33 ++++ .../chatgpt-base-v2/container-apps.bicep | 54 +++++++ .../chatgpt-base-v2/diagram.png | Bin 0 -> 52374 bytes .../chatgpt-base-v2/main.bicep | 141 ++++++++++++++++++ .../chatgpt-base-v2/main.bicepparam | 20 +++ .../modules/cognitive-services.bicep | 46 ++++++ .../modules/container-app-upsert.bicep | 76 ++++++++++ .../modules/container-app.bicep | 123 +++++++++++++++ .../modules/container-apps-environment.bicep | 15 ++ .../modules/container-apps.bicep | 30 ++++ .../modules/container-registry.bicep | 39 +++++ .../modules/registry-access.bicep | 18 +++ .../chatgpt-base-v2/modules/role.bicep | 20 +++ .../chatgpt-base-v2/readme.MD | 5 + 14 files changed, 620 insertions(+) create mode 100644 application-workloads/chatgpt-base-v2/azuredeploy.parameters.json create mode 100644 application-workloads/chatgpt-base-v2/container-apps.bicep create mode 100644 application-workloads/chatgpt-base-v2/diagram.png create mode 100644 application-workloads/chatgpt-base-v2/main.bicep create mode 100644 application-workloads/chatgpt-base-v2/main.bicepparam create mode 100644 application-workloads/chatgpt-base-v2/modules/cognitive-services.bicep create mode 100644 application-workloads/chatgpt-base-v2/modules/container-app-upsert.bicep create mode 100644 application-workloads/chatgpt-base-v2/modules/container-app.bicep create mode 100644 application-workloads/chatgpt-base-v2/modules/container-apps-environment.bicep create mode 100644 application-workloads/chatgpt-base-v2/modules/container-apps.bicep create mode 100644 application-workloads/chatgpt-base-v2/modules/container-registry.bicep create mode 100644 application-workloads/chatgpt-base-v2/modules/registry-access.bicep create mode 100644 application-workloads/chatgpt-base-v2/modules/role.bicep create mode 100644 application-workloads/chatgpt-base-v2/readme.MD diff --git a/application-workloads/chatgpt-base-v2/azuredeploy.parameters.json b/application-workloads/chatgpt-base-v2/azuredeploy.parameters.json new file mode 100644 index 0000000..0442949 --- /dev/null +++ b/application-workloads/chatgpt-base-v2/azuredeploy.parameters.json @@ -0,0 +1,33 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "name": { + "value": "azinsider-chatgpt" + }, + "location": { + "value": "eastus" + }, + "principalId": { + "value": "e8cace21-41c9-4995-9ef2-aa4694cb3d8a" + }, + "openAiResourceName": { + "value": "azinsider-OpenAI" + }, + "openAiResourceGroupName": { + "value": "openai" + }, + "openAiResourceGroupLocation": { + "value": "eastus" + }, + "openAiSkuName": { + "value": "S0" + }, + "createRoleForUser": { + "value": true + }, + "acaExists": { + "value": false + } + } +} \ No newline at end of file diff --git a/application-workloads/chatgpt-base-v2/container-apps.bicep b/application-workloads/chatgpt-base-v2/container-apps.bicep new file mode 100644 index 0000000..039eeae --- /dev/null +++ b/application-workloads/chatgpt-base-v2/container-apps.bicep @@ -0,0 +1,54 @@ +param name string +param location string = resourceGroup().location +param tags object = {} + +param identityName string +param containerAppsEnvironmentName string +param containerRegistryName string +param serviceName string = 'aca' +param exists bool +param openAiDeploymentName string +param openAiEndpoint string + +resource acaIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { + name: identityName + location: location +} + + +module app 'modules/container-app-upsert.bicep' = { + name: '${serviceName}-container-app-module' + params: { + name: name + location: location + tags: union(tags, { 'azd-service-name': serviceName }) + identityName: acaIdentity.name + exists: exists + containerAppsEnvironmentName: containerAppsEnvironmentName + containerRegistryName: containerRegistryName + env: [ + { + name: 'AZURE_OPENAI_CHATGPT_DEPLOYMENT' + value: openAiDeploymentName + } + { + name: 'AZURE_OPENAI_ENDPOINT' + value: openAiEndpoint + } + { + name: 'RUNNING_IN_PRODUCTION' + value: 'true' + } + { + name: 'AZURE_OPENAI_CLIENT_ID' + value: acaIdentity.properties.clientId + } + ] + targetPort: 50505 + } +} + +output SERVICE_ACA_IDENTITY_PRINCIPAL_ID string = acaIdentity.properties.principalId +output SERVICE_ACA_NAME string = app.outputs.name +output SERVICE_ACA_URI string = app.outputs.uri +output SERVICE_ACA_IMAGE_NAME string = app.outputs.imageName diff --git a/application-workloads/chatgpt-base-v2/diagram.png b/application-workloads/chatgpt-base-v2/diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..3e6b062cf3dda1fcbe7fe11c5ee8a92b230436c5 GIT binary patch literal 52374 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 Udlte)g#Z8m07*qoM6N<$g0i}bF8}}l literal 0 HcmV?d00001 diff --git a/application-workloads/chatgpt-base-v2/main.bicep b/application-workloads/chatgpt-base-v2/main.bicep new file mode 100644 index 0000000..2344e02 --- /dev/null +++ b/application-workloads/chatgpt-base-v2/main.bicep @@ -0,0 +1,141 @@ +targetScope = 'subscription' + +@minLength(1) +@maxLength(64) +@description('Name which is used to generate a short unique hash for each resource') +param name string + +@minLength(1) +@description('Primary location for all resources') +param location string + +param modelName string = 'gpt-35-turbo' + +@description('Id of the user or app to assign application roles') +param principalId string = 'e8cace21-41c9-4995-9ef2-aa4694cb3d8a' + +@description('Flag to decide where to create OpenAI role for current user') +param createRoleForUser bool = false + +param acaExists bool = false + +param openAiResourceName string = '' +param openAiResourceGroupName string = '' +param openAiResourceGroupLocation string = '' +param openAiSkuName string = '' +param openAiDeploymentCapacity int = 30 + +var resourceToken = toLower(uniqueString(subscription().id, name, location)) +var tags = { 'azd-env-name': name } + +resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: '${name}-rg' + location: location + tags: tags +} + +resource openAiResourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' existing = if (!empty(openAiResourceGroupName)) { + name: !empty(openAiResourceGroupName) ? openAiResourceGroupName : resourceGroup.name +} + +var prefix = '${name}-${resourceToken}' + +var openAiDeploymentName = 'chatgpt' +module openAi 'modules/cognitive-services.bicep' = { + name: 'openai' + scope: openAiResourceGroup + params: { + name: !empty(openAiResourceName) ? openAiResourceName : '${resourceToken}-cog' + location: !empty(openAiResourceGroupLocation) ? openAiResourceGroupLocation : location + tags: tags + sku: { + name: !empty(openAiSkuName) ? openAiSkuName : 'S0' + } + deployments: [ + { + name: openAiDeploymentName + model: { + format: 'OpenAI' + name: modelName + version: '0613' + } + sku: { + name: 'Standard' + capacity: 30 + } + } + ] + } +} + + +// Container apps host (including container registry) +module containerApps 'modules/container-apps.bicep' = { + name: 'container-apps' + scope: resourceGroup + params: { + name: 'app' + location: location + tags: tags + containerAppsEnvironmentName: '${prefix}-containerapps-env' + containerRegistryName: '${replace(prefix, '-', '')}registry' + } +} + +// Container app frontend +module aca 'container-apps.bicep' = { + name: 'aca' + scope: resourceGroup + params: { + name: replace('${take(prefix,19)}-ca', '--', '-') + location: location + tags: tags + identityName: '${prefix}-id-aca' + containerAppsEnvironmentName: containerApps.outputs.environmentName + containerRegistryName: containerApps.outputs.registryName + openAiDeploymentName: openAiDeploymentName + openAiEndpoint: openAi.outputs.endpoint + exists: acaExists + } +} + + +module openAiRoleUser 'modules/role.bicep' = if (createRoleForUser) { + scope: openAiResourceGroup + name: 'openai-role-user' + params: { + principalId: principalId + roleDefinitionId: '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd' + principalType: 'ServicePrincipal' + } +} + + +module openAiRoleBackend 'modules/role.bicep' = { + scope: openAiResourceGroup + name: 'openai-role-backend' + params: { + principalId: aca.outputs.SERVICE_ACA_IDENTITY_PRINCIPAL_ID + roleDefinitionId: '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd' + principalType: 'ServicePrincipal' + } +} + +output AZURE_LOCATION string = location + +output AZURE_OPENAI_CHATGPT_DEPLOYMENT string = openAiDeploymentName +output AZURE_OPENAI_ENDPOINT string = openAi.outputs.endpoint +output AZURE_OPENAI_KEY string = openAi.outputs.key +output AZURE_OPENAI_RESOURCE string = openAi.outputs.name +output AZURE_OPENAI_RESOURCE_GROUP string = openAiResourceGroup.name +output AZURE_OPENAI_SKU_NAME string = openAi.outputs.skuName +output AZURE_OPENAI_RESOURCE_GROUP_LOCATION string = openAiResourceGroup.location + +output SERVICE_ACA_IDENTITY_PRINCIPAL_ID string = aca.outputs.SERVICE_ACA_IDENTITY_PRINCIPAL_ID +output SERVICE_ACA_NAME string = aca.outputs.SERVICE_ACA_NAME +output SERVICE_ACA_URI string = aca.outputs.SERVICE_ACA_URI +output SERVICE_ACA_IMAGE_NAME string = aca.outputs.SERVICE_ACA_IMAGE_NAME + +output AZURE_CONTAINER_ENVIRONMENT_NAME string = containerApps.outputs.environmentName +output AZURE_CONTAINER_REGISTRY_ENDPOINT string = containerApps.outputs.registryLoginServer +output AZURE_CONTAINER_REGISTRY_NAME string = containerApps.outputs.registryName diff --git a/application-workloads/chatgpt-base-v2/main.bicepparam b/application-workloads/chatgpt-base-v2/main.bicepparam new file mode 100644 index 0000000..3309931 --- /dev/null +++ b/application-workloads/chatgpt-base-v2/main.bicepparam @@ -0,0 +1,20 @@ +using 'main.bicep' + +param name = 'azinsider-chatgpt' +param modelName = 'gpt-35-turbo' + +param location = 'eastus' + +param principalId = 'e8cace21-41c9-4995-9ef2-aa4694cb3d8a' + +param openAiResourceName = 'azinsider-OpenAI' + +param openAiResourceGroupName = 'openai' + +param openAiResourceGroupLocation = 'eastus' + +param openAiSkuName = 'S0' + +param createRoleForUser = true + +param acaExists = false diff --git a/application-workloads/chatgpt-base-v2/modules/cognitive-services.bicep b/application-workloads/chatgpt-base-v2/modules/cognitive-services.bicep new file mode 100644 index 0000000..749cbde --- /dev/null +++ b/application-workloads/chatgpt-base-v2/modules/cognitive-services.bicep @@ -0,0 +1,46 @@ +param name string +param location string = resourceGroup().location +param tags object = {} + +param customSubDomainName string = name +param deployments array = [] +param kind string = 'OpenAI' +param publicNetworkAccess string = 'Enabled' +param sku object = { + name: 'S0' +} + +resource account 'Microsoft.CognitiveServices/accounts@2023-05-01' = { + name: name + location: location + tags: tags + kind: kind + properties: { + customSubDomainName: customSubDomainName + publicNetworkAccess: publicNetworkAccess + networkAcls: { + defaultAction: 'Allow' + } + } + sku: sku +} + +@batchSize(1) +resource deployment 'Microsoft.CognitiveServices/accounts/deployments@2023-05-01' = [for deployment in deployments: { + parent: account + name: deployment.name + properties: { + model: deployment.model + raiPolicyName: contains(deployment, 'raiPolicyName') ? deployment.raiPolicyName : null + } + sku: contains(deployment, 'sku') ? deployment.sku : { + name: 'Standard' + capacity: 20 + } +}] + +output endpoint string = account.properties.endpoint +output id string = account.id +output name string = account.name +output skuName string = account.sku.name +output key string = account.listKeys().key1 diff --git a/application-workloads/chatgpt-base-v2/modules/container-app-upsert.bicep b/application-workloads/chatgpt-base-v2/modules/container-app-upsert.bicep new file mode 100644 index 0000000..c6398c7 --- /dev/null +++ b/application-workloads/chatgpt-base-v2/modules/container-app-upsert.bicep @@ -0,0 +1,76 @@ +param name string +param location string = resourceGroup().location +param tags object = {} + +param containerAppsEnvironmentName string +param containerName string = 'main' +param containerRegistryName string + +@description('Minimum number of replicas to run') +@minValue(1) +param containerMinReplicas int = 1 +@description('Maximum number of replicas to run') +@minValue(1) +param containerMaxReplicas int = 10 + +param secrets array = [] +param env array = [] +param external bool = true +param targetPort int = 80 +param exists bool + +@description('User assigned identity name') +param identityName string + +@description('Enabled Ingress for container app') +param ingressEnabled bool = true + +// Dapr Options +@description('Enable Dapr') +param daprEnabled bool = false +@description('Dapr app ID') +param daprAppId string = containerName +@allowed([ 'http', 'grpc' ]) +@description('Protocol used by Dapr to connect to the app, e.g. http or grpc') +param daprAppProtocol string = 'http' + +@description('CPU cores allocated to a single container instance, e.g. 0.5') +param containerCpuCoreCount string = '0.5' + +@description('Memory allocated to a single container instance, e.g. 1Gi') +param containerMemory string = '1.0Gi' + +resource existingApp 'Microsoft.App/containerApps@2022-03-01' existing = if (exists) { + name: name +} + +module app 'container-app.bicep' = { + name: '${deployment().name}-update' + params: { + name: name + location: location + tags: tags + identityName: identityName + ingressEnabled: ingressEnabled + containerName: containerName + containerAppsEnvironmentName: containerAppsEnvironmentName + containerRegistryName: containerRegistryName + containerCpuCoreCount: containerCpuCoreCount + containerMemory: containerMemory + containerMinReplicas: containerMinReplicas + containerMaxReplicas: containerMaxReplicas + daprEnabled: daprEnabled + daprAppId: daprAppId + daprAppProtocol: daprAppProtocol + secrets: secrets + external: external + env: env + imageName: exists ? existingApp.properties.template.containers[0].image : '' + targetPort: targetPort + } +} + +output defaultDomain string = app.outputs.defaultDomain +output imageName string = app.outputs.imageName +output name string = app.outputs.name +output uri string = app.outputs.uri diff --git a/application-workloads/chatgpt-base-v2/modules/container-app.bicep b/application-workloads/chatgpt-base-v2/modules/container-app.bicep new file mode 100644 index 0000000..6680eb5 --- /dev/null +++ b/application-workloads/chatgpt-base-v2/modules/container-app.bicep @@ -0,0 +1,123 @@ +param name string +param location string = resourceGroup().location +param tags object = {} + +param containerAppsEnvironmentName string +param containerName string = 'main' +param containerRegistryName string + +@description('Minimum number of replicas to run') +@minValue(1) +param containerMinReplicas int = 1 +@description('Maximum number of replicas to run') +@minValue(1) +param containerMaxReplicas int = 10 + +param secrets array = [] +param env array = [] +param external bool = true +param imageName string +param targetPort int = 80 + +@description('User assigned identity name') +param identityName string + +@description('Enabled Ingress for container app') +param ingressEnabled bool = true + +// Dapr Options +@description('Enable Dapr') +param daprEnabled bool = false +@description('Dapr app ID') +param daprAppId string = containerName +@allowed([ 'http', 'grpc' ]) +@description('Protocol used by Dapr to connect to the app, e.g. http or grpc') +param daprAppProtocol string = 'http' + +@description('CPU cores allocated to a single container instance, e.g. 0.5') +param containerCpuCoreCount string = '0.5' + +@description('Memory allocated to a single container instance, e.g. 1Gi') +param containerMemory string = '1.0Gi' + +resource userIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' existing = { + name: identityName +} + +module containerRegistryAccess 'registry-access.bicep' = { + name: '${deployment().name}-registry-access' + params: { + containerRegistryName: containerRegistryName + principalId: userIdentity.properties.principalId + } +} + +resource app 'Microsoft.App/containerApps@2022-03-01' = { + name: name + location: location + tags: tags + // It is critical that the identity is granted ACR pull access before the app is created + // otherwise the container app will throw a provision error + // This also forces us to use an user assigned managed identity since there would no way to + // provide the system assigned identity with the ACR pull access before the app is created + dependsOn: [ containerRegistryAccess ] + identity: { + type: 'UserAssigned' + userAssignedIdentities: { '${userIdentity.id}': {} } + } + properties: { + managedEnvironmentId: containerAppsEnvironment.id + configuration: { + activeRevisionsMode: 'single' + ingress: ingressEnabled ? { + external: external + targetPort: targetPort + transport: 'auto' + } : null + dapr: daprEnabled ? { + enabled: true + appId: daprAppId + appProtocol: daprAppProtocol + appPort: ingressEnabled ? targetPort : 0 + } : { enabled: false } + secrets: secrets + registries: [ + { + server: '${containerRegistry.name}.azurecr.io' + identity: userIdentity.id + } + ] + } + template: { + containers: [ + { + image: !empty(imageName) ? imageName : 'daverendon/chatgpt-base:latest' + name: containerName + env: env + resources: { + cpu: json(containerCpuCoreCount) + memory: containerMemory + } + } + ] + scale: { + minReplicas: containerMinReplicas + maxReplicas: containerMaxReplicas + } + } + } +} + +resource containerAppsEnvironment 'Microsoft.App/managedEnvironments@2022-03-01' existing = { + name: containerAppsEnvironmentName +} + +// 2022-02-01-preview needed for anonymousPullEnabled +resource containerRegistry 'Microsoft.ContainerRegistry/registries@2022-02-01-preview' existing = { + name: containerRegistryName +} + +output defaultDomain string = containerAppsEnvironment.properties.defaultDomain +output imageName string = imageName +output name string = app.name +output uri string = 'https://${app.properties.configuration.ingress.fqdn}' diff --git a/application-workloads/chatgpt-base-v2/modules/container-apps-environment.bicep b/application-workloads/chatgpt-base-v2/modules/container-apps-environment.bicep new file mode 100644 index 0000000..4bfd397 --- /dev/null +++ b/application-workloads/chatgpt-base-v2/modules/container-apps-environment.bicep @@ -0,0 +1,15 @@ +param name string +param location string = resourceGroup().location +param tags object = {} + +resource containerAppsEnvironment 'Microsoft.App/managedEnvironments@2022-03-01' = { + name: name + location: location + tags: tags + properties: { + + } +} + +output defaultDomain string = containerAppsEnvironment.properties.defaultDomain +output name string = containerAppsEnvironment.name diff --git a/application-workloads/chatgpt-base-v2/modules/container-apps.bicep b/application-workloads/chatgpt-base-v2/modules/container-apps.bicep new file mode 100644 index 0000000..87e45f9 --- /dev/null +++ b/application-workloads/chatgpt-base-v2/modules/container-apps.bicep @@ -0,0 +1,30 @@ +param name string +param location string = resourceGroup().location +param tags object = {} + +param containerAppsEnvironmentName string +param containerRegistryName string +param applicationInsightsName string = '' + +module containerAppsEnvironment 'container-apps-environment.bicep' = { + name: '${name}-container-apps-environment' + params: { + name: containerAppsEnvironmentName + location: location + tags: tags + } +} + +module containerRegistry 'container-registry.bicep' = { + name: '${name}-container-registry' + params: { + name: containerRegistryName + location: location + tags: tags + } +} + +output defaultDomain string = containerAppsEnvironment.outputs.defaultDomain +output environmentName string = containerAppsEnvironment.outputs.name +output registryLoginServer string = containerRegistry.outputs.loginServer +output registryName string = containerRegistry.outputs.name diff --git a/application-workloads/chatgpt-base-v2/modules/container-registry.bicep b/application-workloads/chatgpt-base-v2/modules/container-registry.bicep new file mode 100644 index 0000000..8045eb0 --- /dev/null +++ b/application-workloads/chatgpt-base-v2/modules/container-registry.bicep @@ -0,0 +1,39 @@ +param name string +param location string = resourceGroup().location +param tags object = {} + +param adminUserEnabled bool = true +param anonymousPullEnabled bool = false +param dataEndpointEnabled bool = false +param encryption object = { + status: 'disabled' +} +param networkRuleBypassOptions string = 'AzureServices' +param publicNetworkAccess string = 'Enabled' +param sku object = { + name: 'Basic' +} +param zoneRedundancy string = 'Disabled' + + +// 2022-02-01-preview needed for anonymousPullEnabled +resource containerRegistry 'Microsoft.ContainerRegistry/registries@2022-02-01-preview' = { + name: name + location: location + tags: tags + sku: sku + properties: { + adminUserEnabled: adminUserEnabled + anonymousPullEnabled: anonymousPullEnabled + dataEndpointEnabled: dataEndpointEnabled + encryption: encryption + networkRuleBypassOptions: networkRuleBypassOptions + publicNetworkAccess: publicNetworkAccess + zoneRedundancy: zoneRedundancy + } +} + + + +output loginServer string = containerRegistry.properties.loginServer +output name string = containerRegistry.name diff --git a/application-workloads/chatgpt-base-v2/modules/registry-access.bicep b/application-workloads/chatgpt-base-v2/modules/registry-access.bicep new file mode 100644 index 0000000..e17e404 --- /dev/null +++ b/application-workloads/chatgpt-base-v2/modules/registry-access.bicep @@ -0,0 +1,18 @@ +param containerRegistryName string +param principalId string + +var acrPullRole = subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d') + +resource aksAcrPull 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + scope: containerRegistry // Use when specifying a scope that is different than the deployment scope + name: guid(subscription().id, resourceGroup().id, principalId, acrPullRole) + properties: { + roleDefinitionId: acrPullRole + principalType: 'ServicePrincipal' + principalId: principalId + } +} + +resource containerRegistry 'Microsoft.ContainerRegistry/registries@2022-02-01-preview' existing = { + name: containerRegistryName +} diff --git a/application-workloads/chatgpt-base-v2/modules/role.bicep b/application-workloads/chatgpt-base-v2/modules/role.bicep new file mode 100644 index 0000000..dca01e1 --- /dev/null +++ b/application-workloads/chatgpt-base-v2/modules/role.bicep @@ -0,0 +1,20 @@ +param principalId string + +@allowed([ + 'Device' + 'ForeignGroup' + 'Group' + 'ServicePrincipal' + 'User' +]) +param principalType string = 'ServicePrincipal' +param roleDefinitionId string + +resource role 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(subscription().id, resourceGroup().id, principalId, roleDefinitionId) + properties: { + principalId: principalId + principalType: principalType + roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', roleDefinitionId) + } +} diff --git a/application-workloads/chatgpt-base-v2/readme.MD b/application-workloads/chatgpt-base-v2/readme.MD new file mode 100644 index 0000000..0960260 --- /dev/null +++ b/application-workloads/chatgpt-base-v2/readme.MD @@ -0,0 +1,5 @@ +# Deploy a Chat App with OpenAI and Bicep Language v2.0 + +This will deploy the application directly to Azure Container Apps + +👉 https://blog.azinsider.net/f42823f4d44d?source=friends_link&sk=90e5b0b1bd25c012f53113cdde70fa0a