Skip to content

Add Azure VM and AKS integration tests for the default OTel config#730

Open
movence wants to merge 21 commits into
mainfrom
hsookim/azurevm-integ-test
Open

Add Azure VM and AKS integration tests for the default OTel config#730
movence wants to merge 21 commits into
mainfrom
hsookim/azurevm-integ-test

Conversation

@movence

@movence movence commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Description of the issue

The agent supports running on non-AWS hosts (Azure VM and AKS) with the default OTel config, authenticating to AWS via web-identity federation — but there was no integration test coverage proving the credential chain and end-to-end telemetry delivery (metrics, logs, traces) to CloudWatch from either environment.

Description of changes

Adds two new test suites, each provisioned by terraform and dispatched from the companion workflow PR (aws/amazon-cloudwatch-agent#2216):

Azure VM (terraform/azure/vm, test/azure/vm)

  • Provisions an Azure VM, installs the built .deb, and starts the agent with the default OTel config.
  • Agent authenticates via the Azure IMDS web-identity chain (AssumeRoleWithWebIdentity).
  • Go test runs on the VM and validates metrics (label-filtered), logs, and traces (Transaction Search aws/spans via Logs Insights — the OTLP endpoint requires xray:PutSpans and X-Ray trace IDs with Unix seconds in the first 4 bytes).

AKS (terraform/azure/aks, test/azure/aks)

  • Provisions an AKS cluster, creates an AWS IAM OIDC provider trusting the cluster issuer (sub/aud conditions), and deploys the agent as a DaemonSet from the integration-test ECR image using a projected service-account token (audience sts.amazonaws.com).
  • A load-generator Job sends OTLP metrics/logs/traces to the agent; the Go test runs on the runner and validates all three signals in CloudWatch, scoped per-cluster to avoid cross-run contamination.
  • Registers the new AZUREVM and AKS compute types in environment/computetype.

License

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

Tests

  • Green end-to-end run: 30113003649 (latest, validates the rebased branch HEAD 03d7a2a2). Prior runs: 30048181167, 30029802668, 30018222183. AKS: TestAKS/Metrics, TestAKS/Logs, TestAKS/Traces all PASS; Azure VM passing across 11 consecutive runs.
  • Every assertion demonstrably discriminates: each one both failed (for a verified physical reason) and passed during bring-up. Logs use AssertLogsNotEmpty to prevent vacuous passes.
  • go vet / gofmt clean on new packages; terraform validate clean on both stacks.

Worth noting

  • IAM self-assume trust is a real product requirement, not a test hack: on AKS the agent's sigv4auth chains sts:AssumeRole on the same role that the projected token federates into, which since the 2022 IAM change requires the role to explicitly trust itself (account-root principal + aws:PrincipalArn condition).
  • On AKS, resourcedetection overwrites host.id with the node VMSS UUID, so metrics are scoped by the k8s.cluster.name resource attribute instead.
  • The k8s logs routing produces a 3-segment stream {k8s.namespace.name}/{service.namespace}/{service.name} (the identity transform fills service.namespace from k8s.namespace.name); the test asserts the verified destination.
  • The integration-test ECR image is published to us-west-2 only; the AKS stack pins the registry region and mints the pull-secret token inside terraform (aws_ecr_authorization_token) so no JSON passes through shell quoting.
  • Known coverage follow-ups (intentionally out of scope): k8sattributes pod-mapping enrichment (load-gen uses hostNetwork), and asserting the no-attribute fallback log routing.

movence added 20 commits July 23, 2026 21:23
Adds test/azurevm (TestAzureVM, //go:build integration, TestMain/pre-provisioned
pattern) validating the agent on a real Azure VM: Azure IMDS detection, OTLP
metrics/logs/traces reaching CloudWatch, and the Azure web-identity credential
chain (oidctoken -> AssumeRoleWithWebIdentity -> sigv4auth). Includes the
terraform/azurevm harness (references the AWS OIDC provider by ARN; no role
creation; uploads the runner-built .deb to the VM over SSH), AZUREVM compute
type, and a one-time Azure-side setup.sh.
- Default VM size Standard_D2s_v5 is capacity-restricted in eastus for some
  subscriptions (SkuNotAvailable 409); switch to Standard_D2s_v7.
- Add a network security group with an inbound SSH rule scoped to runner_ip
  and associate it to the NIC; Azure's implicit default denies all inbound, so
  the file/remote-exec provisioners could not reach port 22. null_resource now
  depends on the association so SSH is reachable before provisioning.
Align setup.sh defaults with the shared integ-test resource group and vnet
naming so it reuses existing shared infrastructure instead of creating a
differently-named set.
Azure VMs have no AWS instance metadata, so the agent (with the new env
fallback) resolves region from AWS_REGION at translation time - pass it
on the sudo'd fetch-config invocation.

Replace the 'tee -a etc/env-config' lines: the agent reads
etc/env-config.json (strict JSON) at service startup, so plain KEY=VALUE
lines in etc/env-config were never loaded and the systemd-spawned agent
would miss CWAGENT_ROLE_ARN needed to expand the role_arn placeholder in
the translated YAML. Use the supported '-setenv ... -envconfig' merge
instead. USE_DEFAULT_CONFIG is translation-time only and needs no
persistence.
Namespaced as test/azure/vm and terraform/azure/vm so future Azure
compute targets (e.g. AKS) can live alongside under the same prefix.

Assertion design:
- Use lowercase "azure" marker (OTel structured logs are lowercase)
- No negative log-marker checks in the CredentialChain subtest:
  AssumeRoleWithWebIdentity is the successful API call name, and
  transient retries leave permanent markers even on recovery. The
  Metrics/Logs/Traces subtests already prove delivery end-to-end.
… retry window

X-Ray's OTLP endpoint silently drops traces whose first 4 bytes are not
a valid Unix epoch timestamp (seconds). Our previous format used
nanoseconds which overflowed the 4-byte field. Generate compliant IDs
(first 4 bytes = unix seconds, remaining 12 bytes = sequence padding).

Also revert the t.Skip() workaround — Traces now hard-fails — and extend
the retry window to 5 attempts × 90s to accommodate X-Ray OTLP indexing
latency (typically 3-5 minutes).
GetTraceSummaries depends on X-Ray's indexing pipeline which can lag
5-10+ minutes for OTLP-ingested traces, causing persistent test failures.

Switch to BatchGetTraces with the exact trace IDs we generated — this
queries the raw trace store directly and bypasses indexing entirely.
Record generated OTLP trace IDs during the load window, convert to
X-Ray format (1-{8hex}-{24hex}), and fetch them directly.
The X-Ray OTLP endpoint (xray.{region}.amazonaws.com/v1/traces) requires
xray:PutSpans — a separate IAM action from the legacy PutTraceSegments.
Without it, the agent's trace exports are silently rejected with 403,
which is why traces never appeared in X-Ray despite the endpoint
accepting connections.

Also add xray:BatchGetTraces which the test binary needs for the
direct trace-ID-based validation (bypasses GetTraceSummaries indexing).
Dump agent log lines containing error/warn/xray/traces keywords after
the load window to diagnose why the otlphttp/traces exporter silently
fails to deliver spans to X-Ray. The IAM policy is now correct
(PutSpans granted) but traces still don't arrive — need visibility
into agent-side export errors.
The X-Ray OTLP endpoint requires Transaction Search (trace segment
destination = CloudWatchLogs). Spans ingested this way are stored in the
aws/spans log group in full; GetTraceSummaries and BatchGetTraces only
see the indexed subset (1% by default), so they return empty for
low-volume test traffic. Query aws/spans for the exact generated trace
IDs instead and require all of them to be present.
Provisions an AKS cluster with OIDC issuer, creates an AWS IAM role
trusting the cluster's service account token, deploys the CWA DaemonSet
from the pre-built ECR image with projected STS token auth, runs a
hostNetwork load-generator Job pushing OTLP for 3 minutes, then validates
all three signals (metrics/logs/traces) reach CloudWatch.

Traces validated via aws/spans (Transaction Search) same as the VM test.
The agent entrypoint checks IsRunningInContainer() to decide
whether to use the container config path (--input-dir). Without
this env var set, translateConfig falls back to host paths and
fails inside the container.
…sform

The agent's transform_identity_k8s fills service.namespace from
k8s.namespace.name, so the routed stream is
{k8s.namespace.name}/{service.namespace}/{service.name} — verified live
in CloudWatch for the previous run's cluster.
- Protect generatedTraceIDs/traceSeq with sync.Mutex; snapshot under
  lock after sendTelemetry stops so the read has a happens-before.
- Add AssertLogsNotEmpty to validateLogs (matches the AKS path).
- Close resp.Body in post() to avoid leaking connections across the
  ~54 POST calls per test run.
@movence
movence force-pushed the hsookim/azurevm-integ-test branch from ba81e53 to 03d7a2a Compare July 24, 2026 15:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant