Skip to main content

azd-patterns

Tips and patterns for Azure Developer CLI (azd) workflows, ACA job deployment, and infrastructure scripting conventions. USE FOR: azd hooks, postdeploy, postprovision, ACA job deployment, container app job image update, publish_aca, deploy_job, azure.yaml hooks, cross-platform deployment scripts, uv run, azd env, azd conventions, infra scripts, MCAPS subscription, SecurityControl Ignore tag, resource group tagging, Defender for Cloud noise, Azure Policy auto-remediation, AZURE_TAGS, pilot posture, demo subscription tagging, AcrPull, ImagePullError, JSON array param, BCP186, FetchingKeyVaultSecretFailed, dependsOn rbac, image-pull credential, ACA placeholder image, azd env set triple-escape. DO NOT USE FOR: az login, tenant switching, subscription isolation (use azure-tenant-isolation), Foundry agents (use microsoft-foundry).

跳到安装

来源信息

仓库
aiappsgbb/awesome-gbb
最近来源活动
2026年9月25日 14:00
检测到的 SKILL.md 语言
英语
星标
6
分支
3

安装方式

默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。

检查来源文件

决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。

文件资源管理器
15 个文件

正在显示 SKILL.md

SKILL.md
来源说明 · 只读预览
name
azd-patterns
description
Tips and patterns for Azure Developer CLI (azd) workflows, ACA job deployment, and infrastructure scripting conventions. USE FOR: azd hooks, postdeploy, postprovision, ACA job deployment, container app job image update, publish_aca, deploy_job, azure.yaml hooks, cross-platform deployment scripts, uv run, azd env, azd conventions, infra scripts, MCAPS subscription, SecurityControl Ignore tag, resource group tagging, Defender for Cloud noise, Azure Policy auto-remediation, AZURE_TAGS, pilot posture, demo subscription tagging, AcrPull, ImagePullError, JSON array param, BCP186, FetchingKeyVaultSecretFailed, dependsOn rbac, image-pull credential, ACA placeholder image, azd env set triple-escape. DO NOT USE FOR: az login, tenant switching, subscription isolation (use azure-tenant-isolation), Foundry agents (use microsoft-foundry).
metadata
{"version":"1.6.1"}
# AZD Tips & Patterns Conventions and best practices for `azd` workflows, hooks, and ACA job deployments gathered from real repos. --- ## ACA Job Deployment `azd` does not natively deploy Container Apps **Jobs** — only Container Apps. Jobs need a separate deployment step. ### Recommended: Python + `postdeploy` hook (cross-platform) Used in newer repos. The pattern: 1. **`azure.yaml`** — wire a `postdeploy` hook that runs after `azd deploy`: ```yaml hooks: postdeploy: shell: pwsh run: 'cd infra/scripts && uv sync --frozen && uv run deploy_job.py' interactive: false continueOnError: false ``` 2. **`infra/scripts/deploy_job.py`** — update the job image via Azure SDK: ```python import asyncio, os, sys, logging from utils import load_azd_env from azure.identity.aio import AzureCliCredential from azure.mgmt.appcontainers.aio import ContainerAppsAPIClient async def main(): load_azd_env() image_name = os.getenv("SERVICE_AGENTS_IMAGE_NAME") job_name = os.getenv("JOB_NAME") resource_group = os.getenv("RESOURCE_GROUP") subscription_id = os.getenv("SUBSCRIPTION_ID") tenant_id = os.getenv("AZURE_TENANT_ID") async with AzureCliCredential(tenant_id=tenant_id) as credential: async with ContainerAppsAPIClient(credential, subscription_id) as client: job = await client.jobs.get(resource_group, job_name) job.template.containers[0].image = image_name # Remove stale sidecars — they cause "Failed" status if they exit non-zero job.template.containers = [job.template.containers[0]] poller = await client.jobs.begin_create_or_update(resource_group, job_name, job) await poller.result() if __name__ == "__main__": try: asyncio.run(main()) except Exception as e: logging.critical("Error deploying job: %s", e) sys.exit(1) ``` 3. **`infra/scripts/utils.py`** — load azd environment variables: ```python import json, os, subprocess from pathlib import Path from dotenv import load_dotenv def _ensure_azd_config_dir(): """Walk up from script dir to find repo-root .azure directory. Needed when azd hooks run from a temp directory.""" if os.environ.get("AZD_CONFIG_DIR"): return search = Path(__file__).resolve().parent for _ in range(10): candidate = search / ".azure" if candidate.is_dir() and any( (candidate / d / ".env").exists() for d in os.listdir(candidate) if (candidate / d).is_dir() ): os.environ["AZD_CONFIG_DIR"] = str(candidate) return if search.parent == search: break search = search.parent def load_azd_env(): """Load the default azd environment's .env file.""" _ensure_azd_config_dir() result = subprocess.run("azd env list -o json", shell=True, capture_output=True, text=True) if result.returncode != 0: raise Exception("Error loading azd env") env_json = json.loads(result.stdout) for entry in env_json: if entry["IsDefault"]: load_dotenv(entry["DotEnvPath"], override=True) return raise Exception("No default azd env file found") ``` 4. **`infra/scripts/pyproject.toml`** — managed by `uv`: ```toml [project] name = "infra-scripts" requires-python = ">=3.11" dependencies = [ "azure-identity", "azure-mgmt-appcontainers", "python-dotenv", ] ``` ### Benefits over the old PowerShell approach | Old (`publish_aca.ps1`) | New (`deploy_job.py`) | |---|---| | Shells out to `az acr build` + `az containerapp job update` | Uses Azure SDK directly | | Windows-only (or requires PS Core) | Cross-platform (Python) | | Depends on `az` CLI subscription state | Uses `AzureCliCredential` (respects `AZURE_CONFIG_DIR`) | | No sidecar cleanup | Removes stale sidecars that cause false "Failed" status | | Manual image tagging | Reads image name from azd env vars (`SERVICE_*_IMAGE_NAME`) | ### Legacy: PowerShell script (`publish_aca.ps1`) Older repos (e.g. `acme-demo-form`) use a `postprovision` hook with a PowerShell script: ```yaml # azure.yaml hooks: postprovision: shell: pwsh run: | $RG = azd env get-value AZURE_RESOURCE_GROUP $APP_NAME = azd env get-value AZURE_CAJOB_NAME .\infra\scripts\publish_aca.ps1 -RG $RG -APP_SOURCE_FOLDER .\src\ca-job -APP_NAME $APP_NAME -TYPE job interactive: true continueOnError: false ``` The script does `az acr build` + `az containerapp job update`. This works but is not cross-platform and depends on `az` CLI subscription state. > **⚠️ Gotcha:** `postprovision` runs only on `azd provision`/`azd up`, NOT on `azd deploy`. If the job needs updating on every deploy, use `postdeploy` instead. --- ## Fetch-Latest-Image Pattern (Bicep + ACR) When Bicep provisions a Container App (or Job), it needs an image reference. On **first deploy** the real image hasn't been built yet, so Bicep uses a placeholder. On **subsequent deploys**, `azd deploy` builds the real image into ACR and patches the running container. This is the expected lifecycle — the placeholder in Bicep is intentional, not a bug. ### Why the placeholder exists ```bicep // infra/main.bicep — first-deploy bootstrap resource mcpApp 'Microsoft.App/containerApps@2024-10-02-preview' = { ... tags: { 'azd-service-name': 'mcp' // REQUIRED — azd deploy uses this tag to find the resource } properties: { template: { containers: [ { name: 'mcp' // Placeholder: azd deploy will swap this to the real ACR image image: 'mcr.microsoft.com/azuredocs/containerapps-helloworld:latest' } ] } } } ``` > ⚠️ **The `azd-service-name` tag is MANDATORY.** `azd deploy` locates the > Container App to patch by searching for a resource tagged with > `azd-service-name: <service>` matching the service name in `azure.yaml`. > Without this tag, `azd deploy` fails with "resource not found: unable to > find a resource tagged with 'azd-service-name: mcp'". This is not > documented prominently in the azd docs — add it to every ACA Bicep. On `azd up`: 1. `azd provision` runs Bicep → Container App created with placeholder image 2. `azd deploy` builds `src/mcp/` → pushes to ACR → patches the Container App with the real image 3. Subsequent `azd deploy` calls repeat step 2 only ### Pattern A — `azure.yaml` service binding (recommended) Let `azd` manage the image lifecycle. Declare each container as a service: ```yaml # azure.yaml services: mcp: host: containerapp project: src/mcp docker: path: src/mcp/Dockerfile context: src/mcp ``` `azd deploy` will: - Build the Dockerfile into ACR (tag = `azd-deploy-{timestamp}`) - Set env var `SERVICE_MCP_IMAGE_NAME` = full ACR image ref - Patch the Container App to use the new image ### `azure.yaml` field traps > ⚠️ **`language: html` and `language: static` are NOT valid `azure.yaml` > values.** They look plausible for static or front-end services but silently > break `azd` build detection. For custom-Dockerfile services, omit > `language` entirely and declare only `project` + `docker`. When the Dockerfile is not in the service root, set `docker.context` to the actual build-context directory: ```yaml services: web: host: containerapp project: . docker: path: ./src/Dockerfile context: ./src ``` Without `docker.context`, `azd deploy` builds from the service root and the Dockerfile's `COPY` lines fail because the expected files are outside the build context. ### Pattern B — `postdeploy` hook with `SERVICE_*_IMAGE_NAME` For Container Apps **Jobs** (not managed by `azd deploy`), use a postdeploy hook that reads the azd-generated image name: ```yaml hooks: postdeploy: shell: pwsh run: 'cd infra/scripts && uv sync --frozen && uv run deploy_job.py' ``` ```python # infra/scripts/deploy_job.py image_name = os.getenv("SERVICE_MCP_IMAGE_NAME") # set by azd deploy # ... patch the Container App Job with this image ``` ### Pattern C — Bicep `containerImage` parameter with default For repos where the image is pre-built (e.g., shared ACR across teams): ```bicep param containerImage string = 'mcr.microsoft.com/azuredocs/containerapps-helloworld:latest' resource app 'Microsoft.App/containerApps@2024-10-02-preview' = { ... properties: { template: { containers: [{ name: 'app', image: containerImage }] } } } ``` Override at deploy time: `azd provision --parameter containerImage=myacr.azurecr.io/app:v2` ### The `SERVICE_*_IMAGE_NAME` convention `azd` auto-generates env vars for each service declared in `azure.yaml`: | azure.yaml service name | Env var | Example value | |---|---|---| | `mcp` | `SERVICE_MCP_IMAGE_NAME` | `acrfoo.azurecr.io/mcp:azd-deploy-1716300000` | | `agent` | `SERVICE_AGENT_IMAGE_NAME` | `acrfoo.azurecr.io/agent:azd-deploy-1716300000` | These are available in `postdeploy` hooks and in `.azure/{env}/.env`. > **⚠️ Don't treat the Bicep placeholder as a bug.** When reviewing Bicep > that uses `containerapps-helloworld:latest`, check whether `azure.yaml` > declares the corresponding service. If it does, `azd deploy` handles the > image swap automatically. The placeholder is a bootstrap artifact, not a > deployment gap. --- ## ACA Job: silent-failure debug playbook **Symptom we keep hitting.** ACA Job execution flips to `Failed` within 60s of the deploy completing, but **zero console + system logs reach Log Analytics Workspace** even though sibling ACA apps (MCP server, agent) in the same env route logs cleanly. `az containerapp job logs show` hangs indefinitely. This has bitten deadline-watcher cron jobs after in-process refactors and is often the biggest end-of-session blocker. The diagnostic ladder below goes **cheap → expensive**. Stop at the first rung that surfaces actionable signal. ### Rung 1 — Verify the basics aren't lying ```powershell $RG = 'rg-<your-process>-poc' $JOB = 'aca-job-deadline-watcher' # Confirm the job exists and what image it's actually running az containerapp job show --resource-group $RG --name $JOB ` --query "{state:properties.runningState, image:properties.template.containers[0].image, env:properties.environmentId}" -o table # Confirm last execution(s) az containerapp job execution list --resource-group $RG --name $JOB ` --query "[].{name:name, status:properties.status, start:properties.startTime, end:properties.endTime}" ` --output table | Select-Object -First 10 ``` Common findings: - Image is `mcr.microsoft.com/azuredocs/containerapps-helloworld` — `azd provision` ran but `azd deploy` / publish_aca didn't (placeholder leak — `threadlight-safe-check` Step 3.5 (image-probe) catches this; check separately). - Image hash matches latest ACR push but jobs still Failed → real runtime crash, advance to Rung 2. ### Rung 2 — Probe LAW for ANY signal at all (including system events) ```powershell $WS = az monitor log-analytics workspace list --resource-group $RG ` --query "[0].customerId" -o tsv $JOB = 'aca-job-deadline-watcher' # Console logs — what stdout/stderr emitted (this is the "no logs" thing if empty) az monitor log-analytics query --workspace $WS --analytics-query @" ContainerAppConsoleLogs_CL | where ContainerJobName_s == '$JOB' | order by TimeGenerated desc | project TimeGenerated, RevisionName_s, Log_s | take 50 "@ -o table # System logs — ACA control plane events (start/stop/pull/probe failures) az monitor log-analytics query --workspace $WS --analytics-query @" ContainerAppSystemLogs_CL | where ContainerAppName_s == '$JOB' | order by TimeGenerated desc | project TimeGenerated, Type_s, Reason_s, Log_s | take 50 "@ -o table ``` What each empty/non-empty combo means: | ConsoleLogs | SystemLogs | Likely cause | |---|---|---| | Empty | Empty | LAW routing not wired to this job (env-level diag setting missing or job created before diag setting), OR ACA Job log-pipeline lag (try again 2-3 min after execution) | | Empty | Non-empty | Image pulls / starts but exits before any `print()` / `logging.info()` reaches LAW. Usually an **import-time crash** — the process exits before `logging.basicConfig()` fires. Move to Rung 3. | | Non-empty | Non-empty | You have signal — read it. | ### Rung 3 — Force first-line logging in the entrypoint When the ConsoleLogs pipe is empty but SystemLogs show the container started + exited, the crash is import-side. Tactic: emit a synchronous write to stderr **before any other import** so even an `ImportError` produces a visible breadcrumb. ```python # main.py — top of file, BEFORE any project imports import sys sys.stderr.write("BOOT: entrypoint reached\n"); sys.stderr.flush() try: import azure.identity # the usual suspects first sys.stderr.write("BOOT: azure.identity OK\n"); sys.stderr.flush() import azure.cosmos sys.stderr.write("BOOT: azure.cosmos OK\n"); sys.stderr.flush() # ... your imports ... except Exception as e: sys.stderr.write(f"BOOT: import failed: {type(e).__name__}: {e}\n") sys.stderr.flush() raise ``` Push, redeploy, kick off a manual execution (`az containerapp job start --resource-group $RG --name $JOB`), wait ~90s, re-run the ConsoleLogs query at Rung 2. The breadcrumbs reveal which import exploded. ### Rung 4 — Activity log probe (orthogonal channel — bypasses LAW entirely) ```powershell
在 GitHub 查看
这个 SKILL.md 很大,SkillsMP 这里只预览前一段内容。 在 GitHub 查看