Skip to main content

Workload Configuration

In order to instrument Azure Container App workloads the Upwind Tracer will need to be utilized.

Configuring a workload to be instrumented with the tracer requires embedding the tracer as part of the container image using a sidecar pattern or by building the tracer directly into your application image.

Prerequisites

Before configuring workloads, you need the following information from your cluster manager deployment:

ValueDescriptionHow to Get
container_app_environment_idThe Container App Environment IDAzure Portal → Container App Environment → Properties → Resource ID
container_app_environment_nameThe Container App Environment nameAzure Portal → Container App Environment → Overview → Name
cluster_manager_api_hostCluster manager hostname and portAzure Portal → Container App → Ingress → FQDN, append :443 for external or :80 for internal
acr_login_serverACR login serverAzure Portal → Container Registry → Login server
acr_pull_identity_idManaged identity for ACR pullAzure Portal → Managed Identities → {name}-acr-pull-identity → Resource ID

You can also retrieve these values using the Azure CLI:

# Get Container App Environment ID
az containerapp env show --name {ENV_NAME} --resource-group {RESOURCE_GROUP} --query id -o tsv

# Get Cluster Manager FQDN
az containerapp show --name {CLUSTER_MANAGER_NAME} --resource-group {RESOURCE_GROUP} --query "properties.configuration.ingress.fqdn" -o tsv

# Get ACR Login Server
az acr show --name {ACR_NAME} --resource-group {RESOURCE_GROUP} --query loginServer -o tsv

# Get ACR Pull Identity ID
az identity show --name {NAME}-acr-pull-identity --resource-group {RESOURCE_GROUP} --query id -o tsv

ACR Pull-Through Cache

By default, the cluster manager module creates an Azure Container Registry with a pull-through cache for ECR public images. This helps avoid rate limiting when pulling the tracer image.

Use the cached tracer image:

{ACR_LOGIN_SERVER}/upwindsecurity/images/tracer:0.7.16

You can deploy applications with the Upwind Tracer using an init container pattern. This approach keeps your application image unchanged and ensures the tracer binary is ready before your application starts.

Configure your workload with the managed identity for ACR authentication:

terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 4.0"
}
}
}

provider "azurerm" {
features {}
subscription_id = var.subscription_id
}

variable "subscription_id" {
description = "Azure subscription ID"
type = string
}

variable "resource_group_name" {
description = "Resource group name (same as cluster manager)"
type = string
}

variable "location" {
description = "Azure region"
type = string
}

variable "container_app_environment_id" {
description = "Container App Environment ID from cluster manager"
type = string
}

variable "container_app_environment_name" {
description = "Container App Environment name"
type = string
}

variable "cluster_manager_api_host" {
description = "Cluster Manager API host (e.g., my-cm.internal.env.azurecontainerapps.io:80)"
type = string
}

variable "acr_login_server" {
description = "ACR login server URL"
type = string
}

variable "acr_pull_identity_id" {
description = "Managed identity ID for ACR pull"
type = string
}

resource "azurerm_container_app" "my_app" {
name = "my-traced-app"
resource_group_name = var.resource_group_name
container_app_environment_id = var.container_app_environment_id
revision_mode = "Single"

# User-assigned identity for ACR pull
identity {
type = "UserAssigned"
identity_ids = [var.acr_pull_identity_id]
}

# ACR registry configuration
registry {
server = var.acr_login_server
identity = var.acr_pull_identity_id
}

template {
# Shared volume for the tracer binary
volume {
name = "tracer-shared"
storage_type = "EmptyDir"
}

# Init container - copies tracer binary to shared volume before app starts
init_container {
name = "tracer-init"
image = "${var.acr_login_server}/upwindsecurity/images/tracer:0.7.16"
cpu = 0.25
memory = "0.5Gi"

command = ["/var/lib/upwind/upwind-tracer", "--self-copy-path", "/shared/upwind-tracer"]

volume_mounts {
name = "tracer-shared"
path = "/shared"
}
}

# Your application container wrapped by tracer
container {
name = "application"
image = "docker.io/your-org/your-app:latest"
cpu = 0.5
memory = "1Gi"
command = ["/shared/upwind-tracer", "--", "/path/to/your/app"]

volume_mounts {
name = "tracer-shared"
path = "/shared"
}

env {
name = "UPWIND_TRACER_API_HOST"
value = var.cluster_manager_api_host
}

env {
name = "UPWIND_CLOUD_PROVIDER"
value = "azure"
}

env {
name = "UPWIND_CLOUD_ACCOUNT_ID"
value = var.subscription_id
}

env {
name = "UPWIND_TRACER_ZONE"
value = var.location
}

env {
name = "CONTAINER_APP_RESOURCE_GROUP"
value = var.resource_group_name
}

env {
name = "CONTAINER_APP_ENVIRONMENT_NAME"
value = var.container_app_environment_name
}
}
}
}

How It Works

The init container pattern works as follows:

  1. Init Container: Runs before the application container starts, using --self-copy-path to copy the tracer binary to the shared volume, then exits
  2. Application Container: Starts after init container completes, with the tracer binary already available at /shared/upwind-tracer
  3. Shared Volume: An EmptyDir volume named tracer-shared is mounted at /shared in both containers

The tracer automatically intercepts system calls and sends telemetry to the cluster manager.

Option 2: Build Tracer into Application Image

Alternatively, you can build the tracer directly into your application image.

Add the Upwind Tracer to your Dockerfile

This approach involves:

  1. Adding a build stage to copy the Upwind Tracer binary
  2. Setting up the entrypoint to run the tracer and then execute your application

Below is an example Dockerfile with these additions:

# syntax=docker/dockerfile:1

# (1) Add the Upwind Tracer image as a build stage.
# Use ACR cache URL: {ACR_LOGIN_SERVER}/upwindsecurity/images/tracer:0.7.16
FROM {ACR_LOGIN_SERVER}/upwindsecurity/images/tracer:0.7.16 AS upwind-tracer

# (2) Copy the Upwind Tracer binary from the build stage into your workload image.
FROM your-workload-image

COPY --from=upwind-tracer /var/lib/upwind /var/lib/upwind

# (3) Set the default entrypoint to the Upwind Tracer, and pass the path
# to your application so the tracer can invoke it.
ENTRYPOINT ["/var/lib/upwind/upwind-tracer", "--", "/path/to/your/app"]

Build and Push Image

After defining and creating your image, you need to push it to Azure Container Registry or another supported registry and deploy it as a Container App.

Configure Tracer to Cluster Manager Communication

The tracer pushes telemetry to the Upwind Cluster Manager via a gRPC connection. You need to set environment variables on your container app to configure this communication.

terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 4.0"
}
}
}

provider "azurerm" {
features {}
subscription_id = var.subscription_id
}

resource "azurerm_container_app" "workload" {
name = "my-traced-app"
resource_group_name = var.resource_group_name
container_app_environment_id = var.container_app_environment_id
revision_mode = "Single"

template {
container {
name = "application"
image = "your-registry/your-app:latest"
cpu = 0.5
memory = "1Gi"

env {
name = "UPWIND_TRACER_API_HOST"
value = var.cluster_manager_api_host
}

env {
name = "UPWIND_CLOUD_PROVIDER"
value = "azure"
}

env {
name = "UPWIND_CLOUD_ACCOUNT_ID"
value = var.subscription_id
}

env {
name = "UPWIND_TRACER_ZONE"
value = var.location
}

env {
name = "CONTAINER_APP_RESOURCE_GROUP"
value = var.resource_group_name
}

env {
name = "CONTAINER_APP_ENVIRONMENT_NAME"
value = var.container_app_environment_name
}
}
}
}

Environment Variable Details

VariableDescriptionExample
UPWIND_TRACER_API_HOSTThe hostname and port of the cluster managermy-cm.internal.env.azurecontainerapps.io:80
UPWIND_CLOUD_PROVIDERCloud providerazure
UPWIND_CLOUD_ACCOUNT_IDAzure subscription IDYour subscription ID
UPWIND_TRACER_ZONEAzure regioneastus
CONTAINER_APP_RESOURCE_GROUPResource group nameYour resource group name
CONTAINER_APP_ENVIRONMENT_NAMEContainer App Environment nameYour environment name

App Service Workloads

If you have Azure App Service workloads and want them to send traces to the cluster manager, follow these steps. This requires the cluster manager to be installed with VNet integration.

Prerequisites

  1. Cluster manager deployed with VNet integration (infrastructure_subnet_id, internal_load_balancer_enabled, and create_private_dns_zone set)
  2. App Service with VNet integration to the same VNet
  3. App Service subnet delegated to Microsoft.Web/serverFarms

Option A: Embedded Tracer

Build the tracer into your App Service container image (see Option 2: Build Tracer into Application Image) and configure environment variables:

terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 4.0"
}
}
}

provider "azurerm" {
features {}
subscription_id = var.subscription_id
}

resource "azurerm_linux_web_app" "app" {
name = "my-traced-app"
resource_group_name = var.resource_group_name
location = var.location
service_plan_id = azurerm_service_plan.this.id

site_config {
application_stack {
docker_image_name = "your-traced-image:latest"
docker_registry_url = "https://your-registry.azurecr.io"
}
}

app_settings = {
UPWIND_TRACER_API_HOST = var.cluster_manager_api_host
UPWIND_CLOUD_PROVIDER = "azure"
UPWIND_CLOUD_ACCOUNT_ID = var.subscription_id
UPWIND_TRACER_ZONE = var.location
}

# VNet integration to reach the internal cluster manager
virtual_network_subnet_id = azurerm_subnet.app_service.id
}

Option B: Sidecar Pattern (Premium Plan)

If you have a Premium plan (P1v2 or higher), you can use the sidecar pattern instead of embedding the tracer. This uses the AzAPI provider to configure sidecar containers.

terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 4.0"
}
azapi = {
source = "azure/azapi"
version = "~> 2.0"
}
}
}

provider "azurerm" {
features {}
subscription_id = var.subscription_id
}

provider "azapi" {}

resource "azurerm_service_plan" "this" {
name = "my-app-service-plan"
resource_group_name = var.resource_group_name
location = var.location
os_type = "Linux"
sku_name = "P1v2" # Premium plan required for sidecars
}

resource "azurerm_linux_web_app" "app" {
name = "my-traced-app"
resource_group_name = var.resource_group_name
location = var.location
service_plan_id = azurerm_service_plan.this.id

site_config {}

app_settings = {
UPWIND_TRACER_API_HOST = var.cluster_manager_api_host
UPWIND_CLOUD_PROVIDER = "azure"
UPWIND_CLOUD_ACCOUNT_ID = var.subscription_id
UPWIND_TRACER_ZONE = var.location
WEBSITES_ENABLE_APP_SERVICE_STORAGE = "true"
}

virtual_network_subnet_id = azurerm_subnet.app_service.id
}

# Enable sidecar mode
resource "azapi_update_resource" "sidecar_mode" {
resource_id = azurerm_linux_web_app.app.id
type = "Microsoft.Web/sites@2024-04-01"

body = {
properties = {
siteConfig = {
linuxFxVersion = "SITECONTAINERS"
}
}
}
}

# Main application container - uses tracer from /home
resource "azapi_resource" "main_container" {
type = "Microsoft.Web/sites/sitecontainers@2024-04-01"
name = "main"
parent_id = azurerm_linux_web_app.app.id

body = {
properties = {
image = "nginx:latest"
isMain = true
targetPort = "80"
authType = "Anonymous"
startUpCommand = "/home/upwind-tracer -- /docker-entrypoint.sh nginx"
}
}

depends_on = [azapi_update_resource.sidecar_mode]
}

# Tracer sidecar - copies binary to /home
resource "azapi_resource" "tracer_sidecar" {
type = "Microsoft.Web/sites/sitecontainers@2024-04-01"
name = "upwind-tracer"
parent_id = azurerm_linux_web_app.app.id

body = {
properties = {
image = "{ACR_LOGIN_SERVER}/upwindsecurity/images/tracer:0.7.16"
isMain = false
authType = "Anonymous"
startUpCommand = "/var/lib/upwind/upwind-tracer --self-copy-path /home/upwind-tracer --self-copy-keep-running"
}
}

depends_on = [azapi_resource.main_container]
}

The sidecar pattern uses the shared /home directory (instead of EmptyDir) to share the tracer binary between containers.

Option C: Instrument an Existing Container App

To instrument an existing Container app, run the following command:

  upwindctl azure instrument-appservice \
--subscription-id=<subscription-id> \
--resource-group=<resource-group> \
--app-service-plan=<app-service-plan> \
--client-id=<client-id> \
--client-secret=<client-secret>

Verifying the Installation

After deploying your traced application:

  1. Navigate to the Azure Portal
  2. Go to your Container App
  3. Check the logs to verify the tracer is running:
    az containerapp logs show --name <app-name> --resource-group <resource-group> --follow
  4. Look for log entries indicating the tracer has started and is sending data to the cluster manager

Troubleshooting

Issue: Tracer fails to connect to cluster manager

  • Verify the UPWIND_TRACER_API_HOST is set correctly
  • Ensure both the application and cluster manager are in the same Container App Environment
  • Check that the cluster manager is running and accessible

Issue: Application fails to start with tracer

  • Verify the tracer binary is executable
  • Check that the application entrypoint path is correct
  • Review container logs for specific error messages

Issue: No telemetry data visible in Upwind console

  • Ensure the cluster manager has valid Upwind credentials
  • Verify network connectivity between the application and cluster manager
  • Check that the tracer environment variables are set correctly