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:
| 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.
┌──────────────────────────────┐ 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.
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:
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):
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:
helm upgrade --install cluster-agent appdynamics-charts/cluster-agent \ -f values.yaml -n appdynamics --create-namespace
Verify the metadata service exists:
oc get svc -n appdynamics | grep cluster-metadata-service# cluster-metadata-service ClusterIP 172.30.x.x <none> 9090/TCP
Three environment variables drive the container-ID lookup:
| 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):
--- 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:
oc exec <app-pod> -n my-java-apps -- \ sh -c 'curl -s http://cluster-metadata-service.appdynamics:9090 || echo unreachable'
oc logs <app-pod> -n my-java-apps | grep -iE "API service Properties|Executing request|container.?id|PKIX"
Expected: