AppDynamics Knowledge Base

Extracting the cgroupv2 Container ID for Manually Instrumented Java Agents on OpenShift

Extracting the cgroupv2 Container ID for Manually Instrumented Java Agents on OpenShift

Platform: Red Hat OpenShift 4.x (ROSA) / any Kubernetes ≥ 1.25 running cgroupv2 Components: AppDynamics Java Agent (init-container pattern), AppDynamics Cluster Agent ≥ 25.10 Status: Validated on OpenShift 4.18 (ROSA), Cluster Agent + Java Agent 26.x

 

AppDynamics provides two supported mechanisms for the Java Agent to retrieve its container ID at startup:

Method APPDYNAMICS_CONTAINERINFO_FETCH_SERVICE value App pod needs RBAC? TLS handling
Kubernetes API server (kubeapi) kubeapi Yes — ServiceAccount with get on pods Agent must trust the cluster CA (extra truststore work on OpenShift)
Cluster Agent metadata service (this document) cluster-metadata-service.<ca-namespace>:9090 No Plain HTTP inside the cluster — nothing to configure

The metadata-service approach is operationally simpler: the Cluster Agent (which already has RBAC to read pods) runs a small HTTP server on port 9090. Each app agent calls it at startup with its pod name and namespace, and receives the container ID back. The app pod requires no ServiceAccount, no Role/RoleBinding, and no certificate handling.

 

2. Architecture

 
┌──────────────────────────────┐        HTTP :9090         ┌──────────────────────────────┐│  App Pod (any namespace)     │  ───────────────────────► │  Cluster Agent namespace     ││  ┌────────────────────────┐  │   GET container metadata  │  ┌────────────────────────┐  ││  │ Java Agent (init-      │  │   (pod name from HOSTNAME │  │ cluster-metadata-      │  ││  │ container pattern)     │  │    + APPDYNAMICS_POD_     │  │ service (started by    │  ││  └────────────────────────┘  │    NAMESPACE)             │  │ Cluster Agent)         │  │└──────────────────────────────┘                           │  └───────────┬────────────┘  │                                                           │              │ K8s API       │                                                           │              ▼ (CA's RBAC)   │                                                           │      kube-apiserver          │                                                           └──────────────────────────────┘

Key behavioral detail: the Java Agent uses the HOSTNAME environment variable as the pod name when querying the metadata service. Do not override the pod hostname (spec.hostname) or unset HOSTNAME, or container-ID extraction will fail.

 

3. Prerequisites

  1. Cluster Agent version 23.9 or higher deployed (Helm or Operator).
  2. Java Agent on a version that supports APPDYNAMICS_CONTAINERINFO_FETCH_SERVICE (any current 25.x/26.x build).
  3. Network path from app namespaces to the Cluster Agent namespace on TCP 9090 (default-allow on a fresh cluster; add a NetworkPolicy exception if you enforce deny-by-default).

 

4. Cluster Agent Configuration (Helm values.yaml)

The metadata server is started by the auto-instrumentation subsystem. For manually instrumented applications, the trick is to enable instrumentation so the metadata server runs, but exclude every namespace so the Cluster Agent never actually mutates any workload:

 
yaml
instrumentationConfig:
  enabled: true                # starts the cluster-metadata-service
  instrumentationMethod: Env  numberOfTaskWorkers: 5
  nsToExcludeRegex: .*         # <-- excludes ALL namespaces from auto-instrumentation;
                               #     only the metadata server remains active

 

""Why nsToExcludeRegex: .* works: auto-instrumentation must be enabled: true for the Cluster Agent to start the container metadata server, but the exclude-all regex guarantees no Deployment/StatefulSet is ever modified. The result is a metadata-server-only mode, suitable when all instrumentation is done manually via init containers. (Setting instrumentationMethod: None with correlation enabled achieves the same effect on Operator-based installs.)""

Minimal sanitized values.yaml (dummy controller values — replace with your own):

 
yaml
deploymentMode: PRIMARYinstallClusterAgent: true

imageInfo:
  agentImage: docker.io/appdynamics/cluster-agent  agentTag: latest  operatorImage: docker.io/appdynamics/cluster-agent-operator  operatorTag: latest  imagePullPolicy: Always
controllerInfo:
  url: https://mycontroller.saas.appdynamics.com:443
  account: mycontroller  accessKey: <controller-access-key>
  globalAccount: mycontroller_xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
createServiceAccount: true
agentServiceAccount: appdynamics-cluster-agentoperatorServiceAccount: appdynamics-operator
clusterAgent:
  nsToMonitorRegex: .*  appName: my-ocp-cluster
instrumentationConfig:
  enabled: true
  instrumentationMethod: Env  numberOfTaskWorkers: 5
  nsToExcludeRegex: .*         # metadata server only — no auto-instrumentation
  defaultAppName: My-Default-App  appNameStrategy: label  resourcesToInstrument:
  - Deployment  - StatefulSet

Deploy/upgrade:

bash
helm upgrade --install cluster-agent appdynamics-charts/cluster-agent \
  -f values.yaml -n appdynamics --create-namespace

Verify the metadata service exists:

bash
oc get svc -n appdynamics | grep cluster-metadata-service# cluster-metadata-service   ClusterIP   172.30.x.x   <none>   9090/TCP

5. Application Deployment (Manual Instrumentation)

Three environment variables drive the container-ID lookup:

Variable Value Purpose
APPDYNAMICS_CONTAINERINFO_FETCH_SERVICE cluster-metadata-service.<ca-namespace>:9090 Where to fetch container metadata
APPDYNAMICS_POD_NAMESPACE namespace of the application pod Lookup key (pod name comes from HOSTNAME)
APPDYNAMICS_CONTAINER_NAME name of the instrumented container Selects the right container in multi-container pods

 

Complete manifest (dummy controller info):

yaml
---
apiVersion: apps/v1kind: Deploymentmetadata:
  name: tomcat-app-manual  labels:
    app: tomcat-app-manual  namespace: my-java-appsspec:
  replicas: 1
  selector:
    matchLabels:
      app: tomcat-app-manual  template:
    metadata:
      labels:
        app: tomcat-app-manual    spec:
      initContainers:
      # Copy the AppD Java Agent into the shared emptyDir.
      # Note: newer appdynamics/java-agent images ship a minimal cp binary
      # that requires the exact form "cp -ra <src> <dst>".
      - name: appd-agent        image: docker.io/appdynamics/java-agent:latest        command: ["cp", "-ra", "/opt/appdynamics/.", "/opt/appdynamics-java"]
        volumeMounts:
        - mountPath: /opt/appdynamics-java          name: appd-agent-repo      containers:
      - name: tomcat-app-manual                      # must match APPDYNAMICS_CONTAINER_NAME
        image: <your-registry>/your-java-app:latest        imagePullPolicy: Always        ports:
        - containerPort: 8080
        volumeMounts:
        - mountPath: /opt/appdynamics-java          name: appd-agent-repo        env:
        # --- cgroupv2 container-ID via Cluster Agent metadata service ---
        - name: APPDYNAMICS_CONTAINERINFO_FETCH_SERVICE          value: "cluster-metadata-service.appdynamics:9090"
        - name: APPDYNAMICS_POD_NAMESPACE          value: "my-java-apps"
        - name: APPDYNAMICS_CONTAINER_NAME          value: "tomcat-app-manual"
        # --- Controller connection (dummy values) ---
        - name: APPDYNAMICS_CONTROLLER_HOST_NAME          value: "mycontroller.saas.appdynamics.com"
        - name: APPDYNAMICS_CONTROLLER_PORT          value: "443"
        - name: APPDYNAMICS_CONTROLLER_SSL_ENABLED          value: "true"
        - name: APPDYNAMICS_AGENT_ACCOUNT_NAME          value: "mycontroller"
        - name: APPDYNAMICS_AGENT_ACCOUNT_ACCESS_KEY          value: "<access-key>"                      # use a Secret in production
        - name: APPDYNAMICS_AGENT_APPLICATION_NAME          value: "My Application"
        - name: APPDYNAMICS_AGENT_TIER_NAME          value: "My Tier"
        - name: APPDYNAMICS_JAVA_AGENT_REUSE_NODE_NAME          value: "true"
        - name: JAVA_TOOL_OPTIONS          value: "-javaagent:/opt/appdynamics-java/javaagent.jar -Dappdynamics.jvm.shutdown.mark.node.as.historical=true"
      volumes:
      - name: appd-agent-repo        emptyDir:
          sizeLimit: "500Mi"
---
apiVersion: v1kind: Servicemetadata:
  name: tomcat-app-service  labels:
    app: tomcat-app-manual  namespace: my-java-appsspec:
  ports:
  - port: 8080
    targetPort: 8080
  selector:
    app: tomcat-app-manual

OpenShift notes:

  • Do not set runAsUser/fsGroup; the restricted-v2 SCC assigns an arbitrary UID to both the init container and the app container (same UID pod-wide), so the copied agent files are readable and the agent can write its logs into the emptyDir.
  • No ServiceAccount, Role, or RoleBinding is required on the application side for this method.

6. Verification

  1. Metadata service reachable (from any pod in the app namespace):
 
   oc exec <app-pod> -n my-java-apps -- \
     sh -c 'curl -s http://cluster-metadata-service.appdynamics:9090 || echo unreachable'
  1. Agent startup log — look for successful container-ID extraction:
 
   oc logs <app-pod> -n my-java-apps | grep -iE "API service Properties|Executing request|container.?id|PKIX"

Expected:

  • API service Properties Successfully Initialized: {HOSTNAME=..., namespace=..., APPDYNAMICS_CONTAINER_NAME=...}
  • Executing request (attempt: 1/15) http://cluster-metadata-service...:9090/... with no exception following
  • A line reporting the resolved container ID (64-char cri-o ID)
  1. Controller UI:
    • The node appears under the configured Application/Tier.
    • Node Dashboard → properties show the Container ID.
    • Kubernetes dashboards (Cluster Agent) show the pod correlated with the APM node (pod detail links to the node).

 

Version history
Last update:
Thursday
Updated by:
Contributors