This page explains how to install the Java Agent in a containerized environment. See Container Platforms to for your options. There are three options to install the Java Agent with containers:

Use Auto-Instrumentation

This scenario applies to containers running in Kubernetes where the Cluster Agent is installed.

This option uses the Auto-Instrumentation feature of the Cluster Agent. It is the recommended option because it offers the highest level of automation and the simplest operational experience for instrumenting Java applications in a Kubernetes cluster.

To get started, see Auto-Instrument Applications with the Cluster Agent.

Use Init Containers

This scenario applies to containers running in Kubernetes.

This option uses Kubernetes init containers to copy the agent binaries into the application container. It is the recommended option when Auto-Instrumentation is not possible. In this use case, it assumes there are two containers:

  • Application image is built without any Java Agent binaries.
  • Second init container image is built and only contains the Java Agent binaries.

The deployment spec for the application is configured to copy the agent binaries to the running application container. To use init containers to copy the agent binaries:

  1. Build the Java Application Image
  2. Build the Java Agent Init Container Image
  3. Redirect Agent Output to Stdout
  4. Add the Init Container to the Deployment Spec
  5. Set the Java Agent Environment Variables
  6. Add the -javaagent Argument to the Deployment Spec
  7. (OpenShift only) Set the APPDYNAMICS_AGENT_UNIQUE_HOST_ID Environment Variable
  8. (On-Premises Controller only) Copy the Controller Certificates to the Container

Build the Java Application Image

When building the Java application image, do not include the Java Agent binaries.

Build the Java Agent Init Container Image

The Java Agent image is built separately from the application image and can be reused across multiple Java application deployments. This Dockerfile is an example of building the Java Agent init container image using a multi-stage build. Alternatively, the init container can reference a pre-built image from AppDynamics on Docker Hub.

Redirect Agent Output to Stdout

It is a best practice to redirect agent output to stdout in containerized environments. If you build your own Java Agent init container image, you can redirect the Java agent output to stdout by updating the ver<version>/conf/logging/log4j2.xml file that is provided in the download package. Add two <AppenderRef ref="Console"/> elements as shown in the example log4j2.xml file. If you use a pre-built image from AppDynamics on Docker Hub, you can replace the default  ver<version>/conf/logging/log4j2.xml file based on the example log4j2.xml file using a ConfigMap. Follow the same procedure shown for Copy the Controller Certificates to the Container.

Add the Init Container to the Deployment Spec

Edit the deployment spec to add the required sections that allow you to copy the agent binaries from the init container to the application image.

The following snippet from a deployment spec shows the required volumesvolumeMounts, and initContainer definitions. The code example assumes the Java application image is published to myrepo/java-app:v1 and the init container image uses the pre-built image docker.io/appdynamics/java-agent:20.6.0:

kind: Deployment
spec:
  containers:
  - name: java-app
    image: myrepo/java-app:v1
    volumeMounts:
    - mountPath: /opt/appdynamics
      name: appd-agent-repo
  initContainers:
  - command:
    - cp
    - -r
    - /opt/appdynamics/.
    - /opt/temp
    name: appd-agent
	image: docker.io/appdynamics/java-agent:20.8.0
    volumeMounts:
    - mountPath: /opt/temp
      name: appd-agent-repo
  volumes:
    - name: appd-agent-repo
      emptyDir: {}
YML

Set the Java Agent Environment Variables

To set all of the required Java Agent environment variables, you must follow these steps as explained in Best Practices to Configure Agents in Kubernetes.

Use ConfigMaps to Configure the App Server Agent

  1. Use a ConfigMap to set the Java Agent environment variables that are shared across applications in a namespace:

    apiVersion: v1
    data:
      APPDYNAMICS_AGENT_APPLICATION_NAME: "eCommerce"
      APPDYNAMICS_AGENT_ACCOUNT_NAME: "<value>"
      APPDYNAMICS_CONTROLLER_HOST_NAME: "<value>"
      APPDYNAMICS_CONTROLLER_PORT: "<value>"
      APPDYNAMICS_CONTROLLER_SSL_ENABLED: "<value>"
      APPDYNAMICS_JAVA_AGENT_REUSE_NODE_NAME: "true"
      APPDYNAMICS_JAVA_AGENT_REUSE_NODE_NAME_PREFIX: "<value>"
    kind: ConfigMap
    metadata:
      name: ecommerce-java-config
    YML
  2. Apply the ConfigMap to the namespace:

    kubectl -n ecommerce apply -f ecommerce-java-config.yaml
    BASH
  3. Update the deployment spec to reference the ConfigMap:

    spec:
     containers:
     - name: java-app
       envFrom:
       - configMapRef:
           name: ecommerce-java-config
     ...
    YML

Use Secrets for the Controller Access Key

  1. Create a Secret using kubectl:

    kubectl -n ecommerce create secret generic appd-agent-secret --from-literal=access-key=<access-key>
    BASH
  2. Update the deployment spec to reference the Secret:

    spec:
      containers:
      - name: java-app
        env:
        - name: APPDYNAMICS_AGENT_ACCOUNT_ACCESS_KEY
          valueFrom:
            secretKeyRef:
              name: appd-agent-secret
              key: access-key
    YML

Set Application-Specific Configuration in the Deployment Spec

Set the application-specific tier name environment variable APPDYNAMICS_AGENT_TIER_NAME in the deployment spec:

spec:
  containers:
  - name: java-app
    env:
    - name: APPDYNAMICS_AGENT_TIER_NAME
      value: ecommerce-service
YML

Add the -javaagent Argument to the Deployment Spec

Edit the deployment spec to add the -javaagent argument to the application container startup command as shown in the example:

spec:
  containers:
    command: ["/bin/sh"]
    args: ["-c", "java -javaagent:/opt/appdynamics/javaagent.jar -jar /myapp.jar"]
YML

Note that for some Java frameworks such as Spring Boot, you can leverage the standard JAVA_TOOL_OPTIONS environment variable to include the -javaagent argument.

(OpenShift Only) Set the APPDYNAMICS_AGENT_UNIQUE_HOST_ID Environment Variable

For Java applications running in OpenShift, set the APPDYNAMICS_AGENT_UNIQUE_HOST_ID environment variable to enable APM correlation with the Cluster Agent. Since the APPDYNAMICS_AGENT_UNIQUE_HOST_ID value depends on a runtime value, set this environment variable in the container startup command. 

For example, for an OpenShift 3.10 or 3.11 environment, set the environment variable as shown:

spec:
  containers:
    command: ["/bin/sh"]
    args: ["-c", "APPDYNAMICS_AGENT_UNIQUE_HOST_ID=$(sed -rn '1s#.*/##; 1s/docker-(.{12}).*/\\1/p' /proc/self/cgroup) && java -javaagent:/opt/appdynamics/javaagent.jar -jar /myapp.jar"]
YML

For your use case, see values documented in Manually Configure App Agents to Correlate with the Cluster Agent

(On-Premises Controller Only) Copy the Controller Certificates to the Container

If on-premises Controller certificates are required, define a ConfigMap to reference the cert file and use a volume mount in the deployment spec to mount the ConfigMap contents to the container.

$ kubectl create configmap appd-cert --from-file=cacerts.jks
BASH

Add the cert file, in this example, appd-cert, to the container file system using volumes and volumeMounts as shown in the deployment spec snippet:

kind: Deployment
spec:
  containers:
    image: myrepo/java-app:v1
    volumeMounts:
    - name: appd-cert
      subPath: cacerts.jks 
      mountPath: /opt/appdynamics/ver4.5.11.26665/conf/cacerts.jks
  volumes:
    - name: appd-cert
      configMap:
        name: appd-cert
YML

Example Configuration for Using an Init Container

A complete example of a deployment spec that uses an init container to copy the agent binaries can be found on Github: java-app.yaml.

Use a Dockerfile

This scenario applies to containers running in Docker and Kubernetes.

This option uses a Dockerfile to copy the Java Agent to the Docker image at build time. To use the option, create a single image that contains both the application and Java Agent binaries.

To copy the agent into the application image during the Docker image build:

  1. Download and Unzip the Java Agent
  2. Redirect Agent Output to Stdout
  3. Copy the Agent Binaries to the Image 
  4. Set the Java Agent Environment Variables
  5. Add the -javaagent Argument to the Startup Command 
  6. (OpenShift only) Set the APPDYNAMICS_AGENT_UNIQUE_HOST_ID Environment Variable
  7. (On-Premises Controller only) Copy the Controller Certs to the Image

Download and Unzip the Java Agent

Download the Java Agent from the AppDynamics Downloads center or programmatically download the agent. The agent is packaged as a zip file. In a shell, unzip the AppServer Agent to the AppServerAgent directory:

$ unzip AppServerAgent-<version>.zip  -d AppServerAgent
BASH

Redirect Agent Output to Stdout

It is a best practice to redirect agent output to stdout in containerized environments. To redirect the output of the Java Agent to stdout, edit the ver<version>/conf/logging/log4j2.xml file and add two <AppenderRef ref="Console"/> elements as shown in the example log4j2.xml file.

Copy the Agent Binaries to the Image 

Edit the Dockerfile to copy the unpackaged agent binaries to the target folder:

COPY AppServerAgent/ /opt/appdynamics/
BASH

Set the Java Agent Environment Variables

If you are running the application in a non-Kubernetes environment (using docker run for example), set the agent environment variables in the Dockerfile. For example:

ENV APPDYNAMICS_AGENT_APPLICATION_NAME=<value>
ENV APPDYNAMICS_AGENT_TIER_NAME=<value>
ENV APPDYNAMICS_AGENT_ACCOUNT_NAME=<value>
ENV APPDYNAMICS_AGENT_ACCOUNT_ACCESS_KEY=<value>
ENV APPDYNAMICS_CONTROLLER_HOST_NAME=<value>
ENV APPDYNAMICS_CONTROLLER_PORT=<value>
ENV APPDYNAMICS_CONTROLLER_SSL_ENABLED=<value>
ENV APPDYNAMICS_JAVA_AGENT_REUSE_NODE_NAME=true
ENV APPDYNAMICS_JAVA_AGENT_REUSE_NODE_NAME_PREFIX=<value>
YML

For Kubernetes applications, omit these environment variables from the Dockerfile and set them using ConfigMaps and Secrets as described in Best Practices to Configure Agents in Kubernetes

The reuse node name and prefix environment variables are required to support unique naming for multiple container instances for the same application image. See Reuse Node Name.

Add the -javaagent Argument to the Startup Command 

Edit the Dockerfile to include the -javaagent argument in the image start command based on the location where the agent binaries were copied. For example:

ENV JAVA_OPTS -javaagent:/opt/appdynamics/javaagent.jar
CMD ["java", "$JAVA_OPTS", "-jar", "/myapp.jar"]
BASH

Note that for some Java frameworks such as Spring Boot, the existing JAVA_TOOL_OPTIONS environment variable can be leveraged to include the -javaagent argument.

(OpenShift Only) Set the APPDYNAMICS_AGENT_UNIQUE_HOST_ID Environment Variable

For Java applications running in OpenShift, set the APPDYNAMICS_AGENT_UNIQUE_HOST_ID environment variable to enable APM correlation with the Cluster Agent. Since the value depends on a runtime value, set this environment variable in the image startup script using the values documented in Manually Configure App Agents to Correlate with the Cluster Agent.  For example, for an OpenShift 3.10 or 3.11 environment, add this startup script startup.sh to the Docker image:

#!/bin/bash
# OpenShift 3.10 or 3.11:
APPDYNAMICS_AGENT_UNIQUE_HOST_ID=$(sed -rn '1s#.*/##; 1s/docker-(.{12}).*/\\1/p' /proc/self/cgroup)
exec java $JAVA_OPTS -jar /myapp.jar
BASH

and update the startup command in the Dockerfile to reference it:

CMD ["/startup.sh"]
BASH

(On-Premises Controller Only) Copy the Controller Certs to the Image

For Java Agents communicating with an on-premises Controller, edit the Dockerfile to copy the cert file containing the on-premises certs to the agent conf folder. For example:

COPY ./onprem-cacerts.jks /opt/appdynamics/ver4.5.11.26665/conf/cacerts.jks
BASH

See Enable SSL for the Java Agent

Example Configuration for Using a Dockerfile

This Dockerfile builds a single image with the application and agent binaries included and this Kubernetes deployment spec references that Docker image. This Dockerfile uses a multi-stage build and downloads and unzips the agent.

Example Log4j Configuration File 

This updated ver<version>/conf/logging/log4j2.xml configuration file redirects the Java Agent logs to stdout. It adds <AppenderRef ref="Console"/> to the Root element.

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO" monitorInterval="2">
   <Appenders>
      <Console name="Console" target="SYSTEM_OUT">
         <PatternLayout pattern="[%t] %d{ABSOLUTE} %5p %c{1} - %m%n" />
      </Console>
      <ADRRAFAppender name="DefaultAppender" fileName="agent.log">
         <PatternLayout pattern="[%t] %d{DATE} %5p %c{1} - %m%n" />
         <SizeBasedTriggeringPolicy size="20 MB" />
         <ADRolloverStrategy max="5" />
         <RegexFilter regex=".*REST.*" onMatch="DENY" onMismatch="ACCEPT" />
      </ADRRAFAppender>
      <ADRRAFAppender name="BCTAppender" fileName="ByteCodeTransformer.log">
         <PatternLayout pattern="[%t] %d{DATE} %5p - %m%n" />
         <SizeBasedTriggeringPolicy size="20 MB" />
         <ADRolloverStrategy max="5" />
      </ADRRAFAppender>
      <ADRRAFAppender name="RESTAppender" fileName="REST.log">
         <PatternLayout pattern="[%t] %d{DATE} %5p %c{1} - %m%n" />
         <RegexFilter regex=".*REST.*" onMatch="ACCEPT" onMismatch="DENY" />
         <SizeBasedTriggeringPolicy size="20 MB" />
         <ADRolloverStrategy max="5" />
      </ADRRAFAppender>
      <ADRRAFAppender name="DynamicServiceAppender" fileName="dynamic-service.log">
         <PatternLayout pattern="[%t] %d{DATE} %5p %c - %m%n" />
         <SizeBasedTriggeringPolicy size="20 MB" />
         <ADRolloverStrategy max="5" />
      </ADRRAFAppender>
      <ADRRAFAppender name="BusinessTransactionsLogger" fileName="BusinessTransactions.log">
         <PatternLayout pattern="[%t] %d{DATE} %5p - %m%n" />
         <SizeBasedTriggeringPolicy size="20 MB" />
         <ADRolloverStrategy max="5" />
      </ADRRAFAppender>
      <ADRRAFAppender name="DataPipelineAppender" fileName="data-pipeline.log">
         <PatternLayout pattern="[%t] %d{DATE} %5p %c - %m%n" />
         <SizeBasedTriggeringPolicy size="20 MB" />
         <ADRolloverStrategy max="5" />
      </ADRRAFAppender>
      <ADRRAFAppender name="APIAppender" fileName="api.log">
         <PatternLayout pattern="[%t] %d{DATE} %5p %c - %m%n" />
         <SizeBasedTriggeringPolicy size="20 MB" />
         <ADRolloverStrategy max="5" />
      </ADRRAFAppender>
   </Appenders>
   <Loggers>
      <!--  to control the logging level of the agent log files, change "level" attribute. level="all|trace|debug|info|warn|error"-->
      <AsyncLogger name="com.singularity" level="info" additivity="false">
         <AppenderRef ref="DefaultAppender" />
         <AppenderRef ref="RESTAppender" />
      </AsyncLogger>
      <AsyncLogger name="com.singularity.BusinessTransactions" level="info" additivity="false">
         <AppenderRef ref="BusinessTransactionsLogger" />
      </AsyncLogger>
      <AsyncLogger name="com.singularity.dynamicservice" level="info" additivity="false">
         <AppenderRef ref="DynamicServiceAppender" />
      </AsyncLogger>
      <AsyncLogger name="com.singularity.ee.service.datapipeline" level="info" additivity="false">
         <AppenderRef ref="DataPipelineAppender" />
      </AsyncLogger>
      <AsyncLogger name="com.singularity.datapipeline" level="info" additivity="false">
         <AppenderRef ref="DataPipelineAppender" />
      </AsyncLogger>
      <AsyncLogger name="com.singularity.BCTLogger" level="info" additivity="false">
         <AppenderRef ref="BCTAppender" />
      </AsyncLogger>
      <AsyncLogger name="com.singularity.api" level="info" additivity="false">
         <AppenderRef ref="APIAppender" />
      </AsyncLogger>
      <AsyncLogger name="com.singularity.segment.TxnTracer" level="info" additivity="false">
         <AppenderRef ref="DefaultAppender" />
      </AsyncLogger>
      <Root level="error">
         <AppenderRef ref="DefaultAppender" />
         <AppenderRef ref="Console" />
      </Root>
   </Loggers>
</Configuration>
CODE