There will be times when multiple teams in your Organization will want to monitor and instrument their Applications. They would also want to have these applications report to different AppDynamics Backend (Controller)
The latest Cluster agent releases gives you that option. Below has been tested on Cluster Agent 25.5 release.
1. tomcat-sample.yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: tomcat-app
labels:
app: tomcat-app-java-apps
namespace: java-apps
spec:
replicas: 1
selector:
matchLabels:
app: tomcat-app
template:
metadata:
labels:
app: tomcat-app
spec:
containers:
- name: tomcat-app
#image: docker.io/abhimanyubajaj98/tomcat-sample:latest
image: docker.io/abhimanyubajaj98/tomcat-sample
imagePullPolicy: Always
ports:
- containerPort: 8080
env:
- name: JAVA_TOOL_OPTIONS
value: "-Xmx512m"
---
apiVersion: v1
kind: Service
metadata:
name: tomcat-app-service
labels:
app: tomcat-app
namespace: java-apps
spec:
ports:
- port: 8080
targetPort: 8080
selector:
app: tomcat-app
2. tomcat-sample-ces.yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: tomcat-app-1
labels:
app: tomcat-app-java-apps-1
namespace: one-apps
spec:
replicas: 1
selector:
matchLabels:
app: tomcat-app-1
template:
metadata:
labels:
app: tomcat-app-1
spec:
containers:
- name: tomcat-app-1
#image: docker.io/abhimanyubajaj98/tomcat-sample:latest
image: docker.io/abhimanyubajaj98/tomcat-sample
imagePullPolicy: Always
ports:
- containerPort: 8080
env:
- name: JAVA_TOOL_OPTIONS
value: "-Xmx512m"
---
apiVersion: v1
kind: Service
metadata:
name: tomcat-app-service-1
labels:
app: tomcat-app-1
namespace: one-apps
spec:
ports:
- port: 8080
targetPort: 8080
selector:
app: tomcat-app-1
1. kubectl create ns java-apps one-apps
2. kubectl create -f tomcat-sample.yaml
3. kubectl create -f tomcat-sample-ces.yaml
root@ip-172-31-12-116:/opt/appdynamics/java-apps# kubectl -n java-apps get pods
NAME READY STATUS RESTARTS AGE
tomcat-app-76df69dc7b-f74wv 1/1 Running 0 71m
root@ip-172-31-12-116:/opt/appdynamics/java-apps# kubectl -n one-apps get pods
NAME READY STATUS RESTARTS AGE
tomcat-app-1-6b86c4b444-h65sj 1/1 Running 0 20m
For this I will deploy one cluster agent with Helm charts and the other one with normal kubectl command line.
First up is creating the secret. Remember Cluster agent will be deployed in 2 namespaces so we will need to create 2 secrets in different namespace.
Our 1st namespace : appdynamics
Our 2nd namespace : appdynamics-1
Let's create secret:
kubectl -n appdynamics create secret generic cluster-agent-secret --from-literal=controller-key=<access-key>
kubectl -n appdynamics-1 create secret generic cluster-agent-secret --from-literal=controller-key=<access-key>
My helm:
root@ip-172-31-12-116:/opt/appdynamics/cluster-agent/cluster-agent-alpine-arm64-bundled-distribution/helm-charts# cat values.yaml
installClusterAgent: true
installInfraViz: false
installSplunkOtelCollector: false
imageInfo:
agentImage: docker.io/appdynamics/cluster-agent
agentTag: 25.5.0-1126
operatorImage: docker.io/appdynamics/cluster-agent-operator
operatorTag: 25.5.0-1107
imagePullPolicy: Always
machineAgentImage: docker.io/appdynamics/machine-agent
machineAgentTag: latest
machineAgentWinImage: docker.io/appdynamics/machine-agent-analytics
machineAgentWinTag: win-latest
netVizImage: docker.io/appdynamics/machine-agent-netviz
netvizTag: latest
controllerInfo:
url: https://controllerces.saas.appdynamics.com:443
account: controllerces
username: null
password: null
accessKey: null
globalAccount: controllerxxx_xxxxxx3
customSSLCert: null
keyStorePasswordSecret: ''
keyStoreFileSecret: ''
authenticateProxy: false
proxyUrl: null
proxyUser: null
proxyPassword: null
createServiceAccount: true
clusterAgent:
containerProperties:
containerBatchSize: 5
containerParallelRequestLimit: 1
containerRegistrationInterval: 120
logProperties:
logFileSizeMb: 5
logFileBackups: 3
logLevel: DEBUG
metricProperties:
metricsSyncInterval: 30
metricUploadRetryCount: 2
metricUploadRetryIntervalMilliSeconds: 5
podMetricCollectionMaxGoRoutines: 3
podMetricCollectionRequestTimeoutSeconds: 5
nsToMonitorRegex: .*
appName: two-ca
instrumentationConfig:
enabled: true
containerAppCorrelationMethod: proxy
instrumentationMethod: Env
numberOfTaskWorkers: 5
appNameStrategy: label
nsToInstrumentRegex: java-apps|nodejs-apps|dotnet-apps
instrumentationRules:
- namespaceRegex: java-apps
language: java
appNameLabel: app
runAsUser: 999
runAsGroup: 999
imageInfo:
image: docker.io/appdynamics/java-agent:latest
agentMountPath: /opt/appdynamics
imagePullPolicy: Always
helm install -f values.yaml cluster-agent -n appdynamcis
root@ip-172-31-12-116:/opt/appdynamics/cluster-agent/cluster-agent-alpine-arm64-bundled-distribution/helm-charts# kubectl -n appdynamics get pods
NAME READY STATUS RESTARTS AGE
appdynamics-operator-769dcf8f4b-kjm2z 1/1 Running 0 77m
two-ca-appdynamics-54bc66f55c-5qzfk 1/1 Running 0 77m
Before we deploy the second Cluster Agent, we will need to edit our cluster-agent-operator.yaml file.
What we need to make sure is every place where you have
namespace: appdynamics
This gets changed to
namespace: appdynamics-1
I have uploaded cluster-agent-operator.txt file that has this configuration. You can use with 25.5 version of Cluster Agent by changing the extension to yaml
Do->
kubectl create -f cluster-agent-operator.yaml
My cluster-agent.yaml:
root@ip-172-31-12-116:/opt/appdynamics/cluster-agent/cluster-agent-alpine-arm64-bundled-distribution# cat cluster-agent.yaml
apiVersion: cluster.appdynamics.com/v1alpha1
kind: Clusteragent
metadata:
name: k8s-cluster-agent
namespace: appdynamics-1
spec:
appName: "cluster-2"
controllerUrl: "https://ces-controller.saas.appdynamics.com:443"
account: "ces-controller"
# docker image info
image: "docker.io/appdynamics/cluster-agent:latest"
serviceAccountName: appdynamics-cluster-agent
nsToMonitorRegex: appdynamics
resources:
limits:
cpu: 90m
memory: "200Mi"
requests:
cpu: 90m
memory: "100Mi"
instrumentationMethod: Env
nsToInstrumentRegex: one-apps
appNameStrategy: label
instrumentationRules:
- namespaceRegex: one-apps
language: java
appNameLabel: app
runAsUser: 999
runAsGroup: 999
imageInfo:
image: docker.io/appdynamics/java-agent:latest
agentMountPath: /opt/appdynamics
imagePullPolicy: Always
### Uncomment the following line if you need pull secret
#imagePullSecret: "<your-docker-pull-secret-name>"
### Uncomment the following line if you need to enable the profiling
#pprofEnabled: true
#pprofPort: 9991
kubectl create -f cluster-agent.yaml
Once done:
root@ip-172-31-12-116:/opt/appdynamics/cluster-agent/cluster-agent-alpine-arm64-bundled-distribution# kubectl -n appdynamics-1 get pods
NAME READY STATUS RESTARTS AGE
appdynamics-operator-7677764d7c-52nx9 1/1 Running 0 51m
k8s-cluster-agent-6dbdb6b4dc-dkdgs 1/1 Running 0 30m
Now, what should happen is namespace one-apps should be instrumented with second Cluster Agent aka one running in appdynamics-1 namespace and java-apps should be instrumented with first Cluster Agent aka one running in appdynamics namespace
And that is what happened->
root@ip-172-31-12-116:/opt/appdynamics/cluster-agent/cluster-agent-alpine-arm64-bundled-distribution# kubectl -n java-apps describe pod
Name: tomcat-app-76df69dc7b-f74wv
Namespace: java-apps
Priority: 0
Service Account: default
Node: ip-172-31-12-116/172.31.12.116
Start Time: Mon, 30 Jun 2025 19:07:11 +0000
Labels: app=tomcat-app
pod-template-hash=76df69dc7b
Annotations: APPD_DEPLOYMENT_NAME: tomcat-app
APPD_INSTRUMENTED_CONTAINERS: tomcat-app
APPD_POD_INSTRUMENTATION_STATE: Successful
APPD_tomcat-app_APPNAME: tomcat-app-java-apps
APPD_tomcat-app_NODEID: 1588804
APPD_tomcat-app_NODENAME: tomcat-app--12
APPD_tomcat-app_TIERID: 30171
APPD_tomcat-app_TIERNAME: tomcat-app
cni.projectcalico.org/podIP: 10.244.116.136/32
cni.projectcalico.org/podIPs: 10.244.116.136/32
Status: Running
IP: 10.244.116.136
IPs:
IP: 10.244.116.136
Controlled By: ReplicaSet/tomcat-app-76df69dc7b
Init Containers:
appd-agent-attach-java:
Container ID: containerd://2785c3422edd754d96270af5e970312791079bb6dc9840acd3ab4af84e5985a0
Image: docker.io/appdynamics/java-agent:latest
Image ID: docker.io/appdynamics/java-agent@sha256:d237aeb95a7b77d6e3e5b2c868e03cd22077e24424b58fa2380e6de340305e35
Port: <none>
Host Port: <none>
Command:
/bin/sh
-c
cp -r /opt/appdynamics/. /opt/appdynamics-java && chown -R 999:999 /opt/appdynamics-java ; ls -la /opt/appdynamics-java
State: Terminated
Reason: Completed
Exit Code: 0
Started: Mon, 30 Jun 2025 19:07:15 +0000
Finished: Mon, 30 Jun 2025 19:07:20 +0000
Ready: True
Restart Count: 0
Limits:
cpu: 20m
memory: 75M
Requests:
cpu: 10m
memory: 50M
Environment: <none>
Mounts:
/opt/appdynamics-java from appd-agent-repo-java (rw)
/var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-95g58 (ro)
Containers:
tomcat-app:
Container ID: containerd://5a27c1bb0d2f539f8288fc65a99cebabeed69d10d34de85c50d0f1389c4e5701
Image: docker.io/abhimanyubajaj98/tomcat-sample
Image ID: docker.io/abhimanyubajaj98/tomcat-sample@sha256:19558c877ec1fca506c3b7a6696a7a3a8914e855df3af93778323fb909b2fdff
Port: 8080/TCP
Host Port: 0/TCP
State: Running
Started: Mon, 30 Jun 2025 19:07:20 +0000
Ready: True
Restart Count: 0
Environment:
APPDYNAMICS_AGENT_ACCOUNT_ACCESS_KEY: <set to the key 'controller-key' in secret 'cluster-agent-secret'> Optional: false
JAVA_TOOL_OPTIONS: -Xmx512m -Dappdynamics.agent.accountAccessKey=$(APPDYNAMICS_AGENT_ACCOUNT_ACCESS_KEY) -Dappdynamics.socket.collection.bci.enable=true -Dappdynamics.jvm.shutdown.mark.node.as.historical=false -Dappdynamics.agent.reuse.nodeName=true -javaagent:/opt/appdynamics-java/javaagent.jar
APPDYNAMICS_CONTROLLER_HOST_NAME: controllerces.saas.appdynamics.com
APPDYNAMICS_CONTROLLER_PORT: 443
APPDYNAMICS_AGENT_TIER_NAME: tomcat-app
APPDYNAMICS_POD_NAMESPACE: java-apps
APPDYNAMICS_CONTAINER_NAME: tomcat-app
APPDYNAMICS_CONTROLLER_SSL_ENABLED: true
APPDYNAMICS_AGENT_ACCOUNT_NAME: controllerces
APPDYNAMICS_AGENT_APPLICATION_NAME: tomcat-app-java-apps
APPDYNAMICS_JAVA_AGENT_REUSE_NODE_NAME_PREFIX: tomcat-app
APPDYNAMICS_CONTAINERINFO_FETCH_SERVICE: cluster-metadata-service.appdynamics:9090
APPDYNAMICS_NETVIZ_AGENT_HOST: (v1:status.hostIP)
APPDYNAMICS_NETVIZ_AGENT_PORT: 3892
Mounts:
/opt/appdynamics-java from appd-agent-repo-java (rw)
/var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-95g58 (ro)
Conditions:
Type Status
PodReadyToStartContainers True
Initialized True
Ready True
ContainersReady True
PodScheduled True
Volumes:
appd-agent-repo-java:
Type: EmptyDir (a temporary directory that shares a pod's lifetime)
Medium:
SizeLimit: <unset>
kube-api-access-95g58:
Type: Projected (a volume that contains injected data from multiple sources)
TokenExpirationSeconds: 3607
ConfigMapName: kube-root-ca.crt
ConfigMapOptional: <nil>
DownwardAPI: true
QoS Class: Burstable
Node-Selectors: <none>
Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events: <none>
Name: tomcat-app-1-6b86c4b444-h65sj
Namespace: one-apps
Priority: 0
Service Account: default
Node: ip-172-31-12-116/172.31.12.116
Start Time: Mon, 30 Jun 2025 19:58:04 +0000
Labels: app=tomcat-app-1
pod-template-hash=6b86c4b444
Annotations: APPD_DEPLOYMENT_NAME: tomcat-app-1
APPD_INSTRUMENTED_CONTAINERS: tomcat-app-1
APPD_POD_INSTRUMENTATION_STATE: Successful
APPD_tomcat-app-1_APPNAME: tomcat-app-java-apps-1
APPD_tomcat-app-1_NODEID: 1588843
APPD_tomcat-app-1_NODENAME: tomcat-app-1--1
APPD_tomcat-app-1_TIERID: 31497
APPD_tomcat-app-1_TIERNAME: tomcat-app-1
cni.projectcalico.org/podIP: 10.244.116.150/32
cni.projectcalico.org/podIPs: 10.244.116.150/32
Status: Running
IP: 10.244.116.150
IPs:
IP: 10.244.116.150
Controlled By: ReplicaSet/tomcat-app-1-6b86c4b444
Init Containers:
appd-agent-attach-java:
Container ID: containerd://172d5f35af34468b82a4bd22a030495de959a6a71ae79bed17cae8d8d203adff
Image: docker.io/appdynamics/java-agent:latest
Image ID: docker.io/appdynamics/java-agent@sha256:d237aeb95a7b77d6e3e5b2c868e03cd22077e24424b58fa2380e6de340305e35
Port: <none>
Host Port: <none>
Command:
/bin/sh
-c
cp -r /opt/appdynamics/. /opt/appdynamics-java && chown -R 999:999 /opt/appdynamics-java ; ls -la /opt/appdynamics-java
State: Terminated
Reason: Completed
Exit Code: 0
Started: Mon, 30 Jun 2025 19:58:05 +0000
Finished: Mon, 30 Jun 2025 19:58:10 +0000
Ready: True
Restart Count: 0
Limits:
cpu: 20m
memory: 75M
Requests:
cpu: 10m
memory: 50M
Environment: <none>
Mounts:
/opt/appdynamics-java from appd-agent-repo-java (rw)
/var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-59w98 (ro)
Containers:
tomcat-app-1:
Container ID: containerd://3234455f370b9af9c2e906ce0a0a183cf9812c53fd2fec3897a4a364c592031c
Image: docker.io/abhimanyubajaj98/tomcat-sample
Image ID: docker.io/abhimanyubajaj98/tomcat-sample@sha256:19558c877ec1fca506c3b7a6696a7a3a8914e855df3af93778323fb909b2fdff
Port: 8080/TCP
Host Port: 0/TCP
State: Running
Started: Mon, 30 Jun 2025 19:58:11 +0000
Ready: True
Restart Count: 0
Environment:
APPDYNAMICS_AGENT_ACCOUNT_ACCESS_KEY: <set to the key 'controller-key' in secret 'cluster-agent-secret'> Optional: false
JAVA_TOOL_OPTIONS: -Xmx512m -Dappdynamics.agent.accountAccessKey=$(APPDYNAMICS_AGENT_ACCOUNT_ACCESS_KEY) -Dappdynamics.socket.collection.bci.enable=true -Dappdynamics.jvm.shutdown.mark.node.as.historical=false -Dappdynamics.agent.reuse.nodeName=true -javaagent:/opt/appdynamics-java/javaagent.jar
APPDYNAMICS_CONTROLLER_HOST_NAME: ces-controller.saas.appdynamics.com
APPDYNAMICS_AGENT_ACCOUNT_NAME: ces-controller
APPDYNAMICS_AGENT_APPLICATION_NAME: tomcat-app-java-apps-1
APPDYNAMICS_JAVA_AGENT_REUSE_NODE_NAME_PREFIX: tomcat-app-1
APPDYNAMICS_CONTAINERINFO_FETCH_SERVICE: cluster-metadata-service.appdynamics-1:9090
APPDYNAMICS_CONTROLLER_PORT: 443
APPDYNAMICS_CONTROLLER_SSL_ENABLED: true
APPDYNAMICS_AGENT_TIER_NAME: tomcat-app-1
APPDYNAMICS_POD_NAMESPACE: one-apps
APPDYNAMICS_CONTAINER_NAME: tomcat-app-1
APPDYNAMICS_NETVIZ_AGENT_HOST: (v1:status.hostIP)
APPDYNAMICS_NETVIZ_AGENT_PORT: 3892
Mounts:
/opt/appdynamics-java from appd-agent-repo-java (rw)
/var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-59w98 (ro)
Conditions:
Type Status
PodReadyToStartContainers True
Initialized True
Ready True
ContainersReady True
PodScheduled True
Volumes:
appd-agent-repo-java:
Type: EmptyDir (a temporary directory that shares a pod's lifetime)
Medium:
SizeLimit: <unset>
kube-api-access-59w98:
Type: Projected (a volume that contains injected data from multiple sources)
TokenExpirationSeconds: 3607
ConfigMapName: kube-root-ca.crt
ConfigMapOptional: <nil>
DownwardAPI: true
QoS Class: Burstable
Node-Selectors: <none>
Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 32m default-scheduler Successfully assigned one-apps/tomcat-app-1-6b86c4b444-h65sj to ip-172-31-12-116
Normal Pulling 32m kubelet Pulling image "docker.io/appdynamics/java-agent:latest"
Normal Pulled 32m kubelet Successfully pulled image "docker.io/appdynamics/java-agent:latest" in 198ms (198ms including waiting). Image size: 84709497 bytes.
Normal Created 32m kubelet Created container: appd-agent-attach-java
Normal Started 32m kubelet Started container appd-agent-attach-java
Normal Pulling 32m kubelet Pulling image "docker.io/abhimanyubajaj98/tomcat-sample"
Normal Pulled 32m kubelet Successfully pulled image "docker.io/abhimanyubajaj98/tomcat-sample" in 236ms (236ms including waiting). Image size: 227949061 bytes.
Normal Created 32m kubelet Created container: tomcat-app-1
Normal Started 32m kubelet Started container tomcat-app-1
Viola!! This is what we wanted 🙂