Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Azure Container Apps as a host option #1952

Merged
merged 35 commits into from
Sep 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
d721099
Update bicep for ACA
1yefuwang1 Aug 22, 2024
30f00e5
First working version
1yefuwang1 Aug 23, 2024
72e34d2
Support workload profile
1yefuwang1 Aug 28, 2024
55a97fd
Merge branch 'Azure-Samples:main' into main
1yefuwang1 Aug 29, 2024
7edd2db
Add support for CORS and fix identity for openai
1yefuwang1 Aug 30, 2024
8fc2d5a
Add aca-host
1yefuwang1 Sep 2, 2024
9cadd14
Make acr unique
1yefuwang1 Sep 2, 2024
0623e9b
Add doc for aca host
1yefuwang1 Sep 3, 2024
73b2bb3
Merge branch 'Azure-Samples:main' into yefu/aca
1yefuwang1 Sep 3, 2024
e362545
Update ACA docs
1yefuwang1 Sep 3, 2024
24d668a
Remove unneeded bicep files
1yefuwang1 Sep 3, 2024
fbb4b05
Revert chanes to infra/main.parameters.json
1yefuwang1 Sep 3, 2024
4ced7ce
Fix markdown lint issues
1yefuwang1 Sep 3, 2024
625866f
Run frontend build before building docker image
1yefuwang1 Sep 4, 2024
40287f2
remove symlinks and update scripts with paths relative to its own fol…
1yefuwang1 Sep 5, 2024
a99a6c5
Merge with main.bicep
1yefuwang1 Sep 6, 2024
9dc65ca
output AZURE_CONTAINER_REGISTRY_ENDPOINT
1yefuwang1 Sep 9, 2024
7f523a0
Fix deployment with app service
1yefuwang1 Sep 9, 2024
9e6e145
Improve naming and README
1yefuwang1 Sep 9, 2024
4ec32f7
Fix identity name and cost esitmation for aca
1yefuwang1 Sep 9, 2024
4174fd3
Share env vars in bicep and update docs
1yefuwang1 Sep 10, 2024
7e49c99
Revert "remove symlinks and update scripts with paths relative to its…
1yefuwang1 Sep 13, 2024
259e7a5
Add containerapps as a commented out host option
1yefuwang1 Sep 13, 2024
920e979
Update app/backend/.dockerignore
pamelafox Sep 13, 2024
eb09e46
Apply suggestions from code review
pamelafox Sep 13, 2024
56025eb
Merge branch 'main' into yefu/aca
pamelafox Sep 13, 2024
13021cb
More steps for deployment guide
pamelafox Sep 13, 2024
8b19702
Update azure.yaml
pamelafox Sep 13, 2024
6550960
Merge branch 'main' into yefu/aca
pamelafox Sep 13, 2024
d49f60c
Update comment
pamelafox Sep 13, 2024
11837ba
cleanup bicep files and improve docs
1yefuwang1 Sep 14, 2024
560076b
Update condition for running in production for credential
pamelafox Sep 14, 2024
5682b67
Merge branch 'yefu/aca' of https://github.com/tawalke/azure-search-op…
pamelafox Sep 14, 2024
8d3edb0
Update ManagedIdentityCredential to use UAMI for containerapps
1yefuwang1 Sep 18, 2024
8f08b3f
Merge branch 'main' into yefu/aca
pamelafox Sep 19, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .azdo/pipelines/azure-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ steps:
AZURE_ADLS_GEN2_STORAGE_ACCOUNT: $(AZURE_ADLS_GEN2_STORAGE_ACCOUNT)
AZURE_ADLS_GEN2_FILESYSTEM_PATH: $(AZURE_ADLS_GEN2_FILESYSTEM_PATH)
AZURE_ADLS_GEN2_FILESYSTEM: $(AZURE_ADLS_GEN2_FILESYSTEM)
DEPLOYMENT_TARGET: $(DEPLOYMENT_TARGET)
AZURE_CONTAINER_APPS_WORKLOAD_PROFILE: $(AZURE_CONTAINER_APPS_WORKLOAD_PROFILE)

- task: AzureCLI@2
displayName: Deploy Application
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/azure-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ jobs:
AZURE_ADLS_GEN2_STORAGE_ACCOUNT: ${{ vars.AZURE_ADLS_GEN2_STORAGE_ACCOUNT }}
AZURE_ADLS_GEN2_FILESYSTEM_PATH: ${{ vars.AZURE_ADLS_GEN2_FILESYSTEM_PATH }}
AZURE_ADLS_GEN2_FILESYSTEM: ${{ vars.AZURE_ADLS_GEN2_FILESYSTEM }}
DEPLOYMENT_TARGET: ${{ vars.DEPLOYMENT_TARGET }}
AZURE_CONTAINER_APPS_WORKLOAD_PROFILE: ${{ vars.AZURE_CONTAINER_APPS_WORKLOAD_PROFILE }}

steps:
- name: Checkout
Expand Down
8 changes: 8 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additio
- [Running unit tests](#running-unit-tests)
- [Running E2E tests](#running-e2e-tests)
- [Code Style](#code-style)
- [Adding new azd environment variables](#add-new-azd-environment-variables)

## Code of Conduct

Expand Down Expand Up @@ -160,3 +161,10 @@ python -m black <path-to-file>
```

If you followed the steps above to install the pre-commit hooks, then you can just wait for those hooks to run `ruff` and `black` for you.

## Adding new azd environment variables

When adding new azd environment variables, please remember to update:
1. App Service's [azure.yaml](./azure.yaml)
1. [ADO pipeline](.azdo/pipelines/azure-dev.yml).
1. [Github workflows](.github/workflows/azure-dev.yml)
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ Pricing varies per region and usage, so it isn't possible to predict exact costs
However, you can try the [Azure pricing calculator](https://azure.com/e/a87a169b256e43c089015fda8182ca87) for the resources below.

- Azure App Service: Basic Tier with 1 CPU core, 1.75 GB RAM. Pricing per hour. [Pricing](https://azure.microsoft.com/pricing/details/app-service/linux/)
- Azure Container Apps: Only provisioned if you deploy to Azure Container Apps following [the ACA deployment guide](docs/azure_container_apps.md). Consumption plan with 1 CPU core, 2.0 GB RAM. Pricing with Pay-as-You-Go. [Pricing](https://azure.microsoft.com/pricing/details/container-apps/)
- Azure OpenAI: Standard tier, GPT and Ada models. Pricing per 1K tokens used, and at least 1K tokens are used per question. [Pricing](https://azure.microsoft.com/pricing/details/cognitive-services/openai-service/)
- Azure AI Document Intelligence: SO (Standard) tier using pre-built layout. Pricing per document page, sample documents have 261 pages total. [Pricing](https://azure.microsoft.com/pricing/details/form-recognizer/)
- Azure AI Search: Basic tier, 1 replica, free level of semantic search. Pricing per hour. [Pricing](https://azure.microsoft.com/pricing/details/search/)
Expand Down Expand Up @@ -126,17 +127,17 @@ A related option is VS Code Dev Containers, which will open the project in your

## Deploying

Follow these steps to provision Azure resources and deploy the application code:
The steps below will provision Azure resources and deploy the application code to Azure App Service. To deploy to Azure Container Apps instead, follow [the container apps deployment guide](docs/azure_container_apps.md).

1. Login to your Azure account:

```shell
azd auth login
```

For GitHub Codespaces users, if the previous command fails, try:
For GitHub Codespaces users, if the previous command fails, try:
```shell
azd auth login --use-device-code
azd auth login --use-device-code
```

1. Create a new azd environment:
Expand Down
7 changes: 7 additions & 0 deletions app/backend/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.git
__pycache__
*.pyc
*.pyo
*.pyd
.Python
env
11 changes: 11 additions & 0 deletions app/backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
FROM python:3.11-bullseye

WORKDIR /app

COPY ./ /app

RUN python -m pip install -r requirements.txt

RUN python -m pip install gunicorn

CMD ["python3", "-m", "gunicorn", "-b", "0.0.0.0:8000", "main:app"]
16 changes: 14 additions & 2 deletions app/backend/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -440,13 +440,25 @@ async def setup_clients():
USE_SPEECH_OUTPUT_BROWSER = os.getenv("USE_SPEECH_OUTPUT_BROWSER", "").lower() == "true"
USE_SPEECH_OUTPUT_AZURE = os.getenv("USE_SPEECH_OUTPUT_AZURE", "").lower() == "true"

# WEBSITE_HOSTNAME is always set by App Service, RUNNING_IN_PRODUCTION is set in main.bicep
RUNNING_ON_AZURE = os.getenv("WEBSITE_HOSTNAME") is not None or os.getenv("RUNNING_IN_PRODUCTION") is not None

# Use the current user identity for keyless authentication to Azure services.
# This assumes you use 'azd auth login' locally, and managed identity when deployed on Azure.
# The managed identity is setup in the infra/ folder.
azure_credential: Union[AzureDeveloperCliCredential, ManagedIdentityCredential]
if os.getenv("WEBSITE_HOSTNAME"): # Environment variable set on Azure Web Apps
if RUNNING_ON_AZURE:
current_app.logger.info("Setting up Azure credential using ManagedIdentityCredential")
azure_credential = ManagedIdentityCredential()
if AZURE_CLIENT_ID := os.getenv("AZURE_CLIENT_ID"):
# ManagedIdentityCredential should use AZURE_CLIENT_ID if set in env, but its not working for some reason,
# so we explicitly pass it in as the client ID here. This is necessary for user-assigned managed identities.
current_app.logger.info(
"Setting up Azure credential using ManagedIdentityCredential with client_id %s", AZURE_CLIENT_ID
)
azure_credential = ManagedIdentityCredential(client_id=AZURE_CLIENT_ID)
else:
current_app.logger.info("Setting up Azure credential using ManagedIdentityCredential")
azure_credential = ManagedIdentityCredential()
elif AZURE_TENANT_ID:
current_app.logger.info(
"Setting up Azure credential using AzureDeveloperCliCredential with tenant_id %s", AZURE_TENANT_ID
Expand Down
6 changes: 5 additions & 1 deletion azure.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ services:
backend:
project: ./app/backend
language: py
# Please check docs/azure_container_apps.md for more information on how to deploy to Azure Container Apps
# host: containerapp
host: appservice
hooks:
prepackage:
prebuild:
windows:
shell: pwsh
run: cd ../frontend;npm install;npm run build
Expand Down Expand Up @@ -86,6 +88,8 @@ pipeline:
- AZURE_ADLS_GEN2_STORAGE_ACCOUNT
- AZURE_ADLS_GEN2_FILESYSTEM_PATH
- AZURE_ADLS_GEN2_FILESYSTEM
- DEPLOYMENT_TARGET
- AZURE_CONTAINER_APPS_WORKLOAD_PROFILE
secrets:
- AZURE_SERVER_APP_SECRET
- AZURE_CLIENT_APP_SECRET
Expand Down
10 changes: 6 additions & 4 deletions docs/appservice.md
Original file line number Diff line number Diff line change
Expand Up @@ -631,15 +631,17 @@ To see any exceptions and server errors, navigate to the _Investigate -> Failure

## Configuring log levels

By default, the deployed app only logs messages with a level of `WARNING` or higher.
By default, the deployed app only logs messages from packages with a level of `WARNING` or higher,
but logs all messages from the app with a level of `INFO` or higher.

These lines of code in `app/backend/app.py` configure the logging level:

```python
# Set root level to WARNING to avoid seeing overly verbose logs from SDKS
logging.basicConfig(level=logging.WARNING)
# Set the app logger level to INFO by default
default_level = "INFO"
if os.getenv("WEBSITE_HOSTNAME"): # In production, don't log as heavily
default_level = "WARNING"
logging.basicConfig(level=os.getenv("APP_LOG_LEVEL", default_level))
app.logger.setLevel(os.getenv("APP_LOG_LEVEL", default_level))
```

To change the default level, either change `default_level` or set the `APP_LOG_LEVEL` environment variable
Expand Down
55 changes: 55 additions & 0 deletions docs/azure_container_apps.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Deploying on Azure Container Apps
1yefuwang1 marked this conversation as resolved.
Show resolved Hide resolved
1yefuwang1 marked this conversation as resolved.
Show resolved Hide resolved

Due to [a limitation](https://github.com/Azure/azure-dev/issues/2736) of the Azure Developer CLI (`azd`), there can be only one host option in the [azure.yaml](../azure.yaml) file.
By default, `host: appservice` is used and `host: containerapp` is commented out.

To deploy to Azure Container Apps, please follow the following steps:

1. Comment out `host: appservice` and uncomment `host: containerapp` in the [azure.yaml](../azure.yaml) file.

2. Login to your Azure account:

```bash
azd auth login
```

3. Create a new `azd` environment to store the deployment parameters:

```bash
azd env new
```

Enter a name that will be used for the resource group.
This will create a new folder in the `.azure` folder, and set it as the active environment for any calls to `azd` going forward.

4. Set the deployment target to `containerapps`:

```bash
azd env set DEPLOYMENT_TARGET containerapps
```

5. (Optional) This is the point where you can customize the deployment by setting other `azd1 environment variables, in order to [use existing resources](docs/deploy_existing.md), [enable optional features (such as auth or vision)](docs/deploy_features.md), or [deploy to free tiers](docs/deploy_lowcost.md).
6. Provision the resources and deploy the code:

```bash
azd up
```

This will provision Azure resources and deploy this sample to those resources, including building the search index based on the files found in the `./data` folder.

**Important**: Beware that the resources created by this command will incur immediate costs, primarily from the AI Search resource. These resources may accrue costs even if you interrupt the command before it is fully executed. You can run `azd down` or delete the resources manually to avoid unnecessary spending.

## Customizing Workload Profile

The default workload profile is Consumption. If you want to use a dedicated workload profile like D4, please run:

```bash
azd env AZURE_CONTAINER_APPS_WORKLOAD_PROFILE D4
```

For a full list of workload profiles, please check [here](https://learn.microsoft.com/azure/container-apps/workload-profiles-overview#profile-types).
Please note dedicated workload profiles have a different billing model than Consumption plan. Please check [here](https://learn.microsoft.com/azure/container-apps/billing) for details.

## Private endpoints

Private endpoints is still in private preview for Azure Conainer Apps and not supported for now.
1 change: 1 addition & 0 deletions infra/abbreviations.json
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@
"virtualNetworks": "vnet-",
"webServerFarms": "plan-",
"webSitesAppService": "app-",
"webSitesContainerApps": "capps-",
"webSitesAppServiceEnvironment": "ase-",
"webSitesFunctions": "func-",
"webStaticSites": "stapp-"
Expand Down
130 changes: 130 additions & 0 deletions infra/core/host/container-app-upsert.bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
metadata description = 'Creates or updates an existing Azure Container App.'
param name string
param location string = resourceGroup().location
param tags object = {}


@description('The number of CPU cores allocated to a single container instance, e.g., 0.5')
param containerCpuCoreCount string = '0.5'

@description('The maximum number of replicas to run. Must be at least 1.')
@minValue(1)
param containerMaxReplicas int = 10

@description('The amount of memory allocated to a single container instance, e.g., 1Gi')
param containerMemory string = '1.0Gi'

@description('The minimum number of replicas to run. Must be at least 1.')
@minValue(1)
param containerMinReplicas int = 1

@description('The name of the container')
param containerName string = 'main'

@description('The environment name for the container apps')
param containerAppsEnvironmentName string = '${containerName}env'

@description('The name of the container registry')
param containerRegistryName string

@description('Hostname suffix for container registry. Set when deploying to sovereign clouds')
param containerRegistryHostSuffix string = 'azurecr.io'

@allowed(['http', 'grpc'])
@description('The protocol used by Dapr to connect to the app, e.g., HTTP or gRPC')
param daprAppProtocol string = 'http'

@description('Enable or disable Dapr for the container app')
param daprEnabled bool = false

@description('The Dapr app ID')
param daprAppId string = containerName

@description('Specifies if the resource already exists')
param exists bool = false

@description('Specifies if Ingress is enabled for the container app')
param ingressEnabled bool = true

@description('The type of identity for the resource')
@allowed(['None', 'SystemAssigned', 'UserAssigned'])
param identityType string = 'None'

@description('The name of the user-assigned identity')
param identityName string = ''

@description('The name of the container image')
param imageName string = ''

@description('The secrets required for the container')
@secure()
param secrets object = {}

@description('The keyvault identities required for the container')
@secure()
param keyvaultIdentities object = {}

@description('The environment variables for the container in key value pairs')
param env object = {}

@description('Specifies if the resource ingress is exposed externally')
param external bool = true

@description('The service binds associated with the container')
param serviceBinds array = []

@description('The target port for the container')
param targetPort int = 80

@allowed(['Consumption', 'D4', 'D8', 'D16', 'D32', 'E4', 'E8', 'E16', 'E32', 'NC24-A100', 'NC48-A100', 'NC96-A100'])
param workloadProfile string = 'Consumption'

param allowedOrigins array = []

resource existingApp 'Microsoft.App/containerApps@2023-05-02-preview' existing = if (exists) {
name: name
}

module app 'container-app.bicep' = {
name: '${deployment().name}-update'
params: {
name: name
workloadProfile: workloadProfile
location: location
tags: tags
identityType: identityType
identityName: identityName
ingressEnabled: ingressEnabled
containerName: containerName
containerAppsEnvironmentName: containerAppsEnvironmentName
containerRegistryName: containerRegistryName
containerRegistryHostSuffix: containerRegistryHostSuffix
containerCpuCoreCount: containerCpuCoreCount
containerMemory: containerMemory
containerMinReplicas: containerMinReplicas
containerMaxReplicas: containerMaxReplicas
daprEnabled: daprEnabled
daprAppId: daprAppId
daprAppProtocol: daprAppProtocol
secrets: secrets
keyvaultIdentities: keyvaultIdentities
allowedOrigins: allowedOrigins
external: external
env: [
for key in objectKeys(env): {
name: key
value: '${env[key]}'
}
]
imageName: !empty(imageName) ? imageName : exists ? existingApp.properties.template.containers[0].image : ''
targetPort: targetPort
serviceBinds: serviceBinds
}
}

output defaultDomain string = app.outputs.defaultDomain
output imageName string = app.outputs.imageName
output name string = app.outputs.name
output uri string = app.outputs.uri
output id string = app.outputs.id
output identityPrincipalId string = app.outputs.identityPrincipalId
Loading
Loading