-
Notifications
You must be signed in to change notification settings - Fork 118
Gap-2: Container Apps Develop (C → A) — Templates + Dapr + Jobs + Functions-on-ACA #1636
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
Open
paulyuk
wants to merge
1
commit into
main
Choose a base branch
from
pass-equity-gap-2
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
159 changes: 159 additions & 0 deletions
159
plugin/skills/azure-prepare/references/services/container-apps/templates/api.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,159 @@ | ||
| # API Template — REFERENCE ONLY | ||
|
|
||
| REST and gRPC API services on Azure Container Apps with ingress configuration. | ||
|
|
||
| ## When to Use | ||
|
|
||
| - REST API with OpenAPI/Swagger | ||
| - gRPC service | ||
| - API gateway backend | ||
| - Backend-for-frontend (BFF) pattern | ||
|
|
||
| ## Project Structure | ||
|
|
||
| ``` | ||
| project-root/ | ||
| ├── azure.yaml | ||
| ├── Dockerfile | ||
| ├── src/ | ||
| │ └── (API code) | ||
| └── infra/ | ||
| ├── main.bicep | ||
| └── app/ | ||
| └── api.bicep | ||
| ``` | ||
|
|
||
| ## azure.yaml | ||
|
|
||
| ```yaml | ||
| name: my-api | ||
| metadata: | ||
| template: container-apps-api | ||
| services: | ||
| api: | ||
| host: containerapp | ||
| project: . | ||
| language: <js|ts|python|csharp|java|go> | ||
| ``` | ||
|
|
||
| ## Bicep — API Container App | ||
|
|
||
| ```bicep | ||
| param name string | ||
| param location string = resourceGroup().location | ||
| param tags object = {} | ||
| param envId string | ||
| param containerRegistryName string | ||
| param imageName string | ||
| param userAssignedIdentityId string | ||
| param isGrpc bool = false | ||
|
|
||
| resource api 'Microsoft.App/containerApps@2024-03-01' = { | ||
| name: name | ||
| location: location | ||
| tags: union(tags, { 'azd-service-name': 'api' }) | ||
| identity: { | ||
| type: 'UserAssigned' | ||
| userAssignedIdentities: { '${userAssignedIdentityId}': {} } | ||
| } | ||
| properties: { | ||
| managedEnvironmentId: envId | ||
| configuration: { | ||
| ingress: { | ||
| external: true | ||
| targetPort: 8080 | ||
| transport: isGrpc ? 'http2' : 'auto' | ||
| corsPolicy: { | ||
| allowedOrigins: ['*'] | ||
| allowedMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'] | ||
| allowedHeaders: ['*'] | ||
| } | ||
| } | ||
| registries: [ | ||
| { | ||
| server: '${containerRegistryName}.azurecr.io' | ||
| identity: userAssignedIdentityId | ||
| } | ||
| ] | ||
| } | ||
| template: { | ||
| containers: [ | ||
| { | ||
| name: 'api' | ||
| image: imageName | ||
| resources: { cpu: json('0.5'), memory: '1Gi' } | ||
| env: [ | ||
| { name: 'PORT', value: '8080' } | ||
| ] | ||
| } | ||
| ] | ||
| scale: { | ||
| minReplicas: 1 | ||
| maxReplicas: 20 | ||
| rules: [ | ||
| { | ||
| name: 'http-scale' | ||
| http: { metadata: { concurrentRequests: '50' } } | ||
| } | ||
| ] | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| output fqdn string = api.properties.configuration.ingress.fqdn | ||
| output name string = api.name | ||
| ``` | ||
|
|
||
| ## REST vs gRPC | ||
|
|
||
| | Setting | REST | gRPC | | ||
| |---------|------|------| | ||
| | `transport` | `auto` | `http2` | | ||
| | `targetPort` | 8080 | 8080 | | ||
| | Ingress | External or internal | External or internal | | ||
|
|
||
| > ⚠️ **gRPC requires `transport: 'http2'`** — without it, gRPC calls fail. | ||
|
|
||
| ## Internal API (No External Ingress) | ||
|
|
||
| For backend APIs only consumed by other Container Apps: | ||
|
|
||
| ```bicep | ||
| ingress: { | ||
| external: false // internal only | ||
| targetPort: 8080 | ||
| transport: 'auto' | ||
| } | ||
| ``` | ||
|
|
||
| Internal APIs are reachable at `https://<app-name>.internal.<env-domain>`. | ||
|
|
||
| ## API with Authentication | ||
|
|
||
| Use Easy Auth or integrate with Microsoft Entra ID: | ||
|
|
||
| ```bicep | ||
| configuration: { | ||
| ingress: { | ||
| external: true | ||
| targetPort: 8080 | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| > 💡 **Tip:** For API authentication, consider using the built-in authentication | ||
| > feature of Container Apps (Easy Auth) or validating JWT tokens in your application code. | ||
|
|
||
| ## CORS Configuration | ||
|
|
||
| Restrict `allowedOrigins` for production: | ||
|
|
||
| ```bicep | ||
| corsPolicy: { | ||
| allowedOrigins: ['https://myapp.example.com'] | ||
| allowedMethods: ['GET', 'POST', 'PUT', 'DELETE'] | ||
| allowedHeaders: ['Authorization', 'Content-Type'] | ||
| maxAge: 3600 | ||
| } | ||
| ``` | ||
172 changes: 172 additions & 0 deletions
172
.../azure-prepare/references/services/container-apps/templates/functions-on-aca.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,172 @@ | ||
| # Functions on Container Apps — REFERENCE ONLY | ||
|
|
||
| Azure Functions hosted on Container Apps for event-driven triggers and bindings | ||
| with Container Apps scaling and networking. | ||
|
|
||
| ## When to Use | ||
|
|
||
| - Event-driven processing requiring Functions triggers/bindings | ||
| - Need KEDA-based scaling with Functions programming model | ||
| - Want Container Apps networking (VNet, private endpoints) with Functions | ||
| - Migrating from Functions Consumption/Premium to Container Apps | ||
|
|
||
| ## Why Functions on Container Apps? | ||
|
|
||
| | Feature | Functions (Flex) | Functions on ACA | | ||
| |---------|-----------------|------------------| | ||
| | Programming model | Functions v4 | Functions v4 | | ||
| | Triggers/bindings | ✅ Full support | ✅ Full support | | ||
| | Scaling | Flex Consumption | KEDA (Container Apps) | | ||
| | Networking | VNet integration | Container Apps VNet | | ||
| | Container support | Managed | Full Dockerfile control | | ||
| | Dapr integration | ❌ | ✅ | | ||
| | Side-cars | ❌ | ✅ | | ||
|
|
||
| ## Project Structure | ||
|
|
||
| ``` | ||
| project-root/ | ||
| ├── azure.yaml | ||
| ├── Dockerfile | ||
| ├── host.json | ||
| ├── src/ | ||
| │ └── (Functions code) | ||
| └── infra/ | ||
| ├── main.bicep | ||
| └── app/ | ||
| └── functions-app.bicep | ||
| ``` | ||
|
|
||
| ## Dockerfile | ||
|
|
||
| ```dockerfile | ||
| # Example: Node.js Functions on ACA | ||
| FROM mcr.microsoft.com/azure-functions/node:4-node20 | ||
|
|
||
| ENV AzureWebJobsScriptRoot=/home/site/wwwroot | ||
| COPY . /home/site/wwwroot | ||
| RUN cd /home/site/wwwroot && npm install --production | ||
| ``` | ||
|
|
||
| ## azure.yaml | ||
|
|
||
| ```yaml | ||
| name: my-functions-aca | ||
| services: | ||
| api: | ||
| host: containerapp | ||
| project: . | ||
| language: js | ||
| ``` | ||
|
|
||
| ## Bicep — Functions on Container Apps | ||
|
|
||
| ```bicep | ||
| param name string | ||
| param location string = resourceGroup().location | ||
| param tags object = {} | ||
| param envId string | ||
| param containerRegistryName string | ||
| param imageName string | ||
| param userAssignedIdentityId string | ||
| param storageAccountName string | ||
|
|
||
| resource funcApp 'Microsoft.App/containerApps@2024-03-01' = { | ||
| name: name | ||
| location: location | ||
| tags: union(tags, { 'azd-service-name': 'api' }) | ||
| identity: { | ||
| type: 'UserAssigned' | ||
| userAssignedIdentities: { '${userAssignedIdentityId}': {} } | ||
| } | ||
| properties: { | ||
| managedEnvironmentId: envId | ||
| configuration: { | ||
| ingress: { | ||
| external: true | ||
| targetPort: 80 | ||
| transport: 'auto' | ||
| } | ||
| registries: [ | ||
| { | ||
| server: '${containerRegistryName}.azurecr.io' | ||
| identity: userAssignedIdentityId | ||
| } | ||
| ] | ||
| } | ||
| template: { | ||
| containers: [ | ||
| { | ||
| name: 'functions' | ||
| image: imageName | ||
| resources: { cpu: json('0.5'), memory: '1Gi' } | ||
| env: [ | ||
| { | ||
| name: 'AzureWebJobsStorage__accountName' | ||
| value: storageAccountName | ||
| } | ||
| { | ||
| name: 'AzureWebJobsStorage__credential' | ||
| value: 'managedidentity' | ||
| } | ||
| { | ||
| name: 'FUNCTIONS_EXTENSION_VERSION' | ||
| value: '~4' | ||
| } | ||
| ] | ||
| } | ||
| ] | ||
| scale: { | ||
| minReplicas: 0 | ||
| maxReplicas: 30 | ||
| } | ||
| } | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ## Supported Triggers | ||
|
|
||
| All Functions triggers work on Container Apps: | ||
|
|
||
| | Trigger | KEDA Scaler | Notes | | ||
| |---------|-------------|-------| | ||
| | HTTP | `http` | Built-in HTTP scaling | | ||
| | Timer | `cron` | Cron-based scheduling | | ||
| | Service Bus | `azure-servicebus` | Queue/topic scaling | | ||
| | Event Hubs | `azure-eventhub` | Partition-based scaling | | ||
| | Cosmos DB | `azure-cosmosdb` | Change feed scaling | | ||
| | Blob Storage | `azure-blob` | Blob count scaling | | ||
| | Storage Queue | `azure-queue` | Queue length scaling | | ||
|
|
||
| ## KEDA Scale Rules for Triggers | ||
|
|
||
| ```bicep | ||
| scale: { | ||
| minReplicas: 0 | ||
| maxReplicas: 30 | ||
| rules: [ | ||
| { | ||
| name: 'servicebus-scale' | ||
| custom: { | ||
| type: 'azure-servicebus' | ||
| metadata: { | ||
| queueName: 'myqueue' | ||
| namespace: 'my-sb-namespace' | ||
| messageCount: '5' | ||
| } | ||
| } | ||
| } | ||
| ] | ||
| } | ||
| ``` | ||
|
|
||
| ## Key Differences from Standard Functions | ||
|
|
||
| 1. **You manage the Dockerfile** — base image must be `mcr.microsoft.com/azure-functions/<runtime>` | ||
| 2. **Scaling is KEDA-based** — configure scale rules explicitly | ||
| 3. **Storage is still required** — Functions runtime needs `AzureWebJobsStorage` | ||
| 4. **No Flex Consumption billing** — billed as Container Apps | ||
|
|
||
| > ⚠️ **Always use the official Functions base images** from MCR. | ||
| > Custom base images may break the Functions runtime. |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Defaulting CORS to
allowedOrigins: ['*']is an insecure baseline for an API template and is easy to copy into production unintentionally. Consider omittingcorsPolicyfrom the base template (and documenting it as an opt-in), or parameterizing allowed origins with a safe default (e.g., empty list / explicit origins only).