Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deploy your Elastic APM Agent to Kubernetes and instrument your Java applications in a smart way #97

Merged
merged 14 commits into from
Nov 1, 2023
18 changes: 16 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@

## Status

[![Build and deploy to GitHub Pages](https://github.com/alexandre-touret/alexandre-touret.github.io/actions/workflows/gh-pages.yml/badge.svg?branch=main)](https://github.com/alexandre-touret/alexandre-touret.github.io/actions/workflows/gh-pages.yml)

## Setup
Expand All @@ -14,9 +17,20 @@ sudo apt install ./hugo_extended_0.110.0_linux-amd64.deb
hugo serve -D -F
```


## Update the theme

```bash
git submodule update --remote --merge
```
```

## Run Markdown Linter

```bash
docker run -v $PWD:/workdir ghcr.io/igorshubovych/markdownlint-cli:latest "*.md" --disable MD041
```

## Run Vale

```bash
. ./run-vale.sh
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
---
title: "Streamline Java Application Deployment: Pack, Ship, and Unlock Distributed Tracing with Elastic APM on Kubernetes"
date: 2023-11-01T08:00:00+02:00
draft: false

images: ["/assets/images/2023/10/claudio-schwarz-q8kR_ie6WnI-unsplash.webp"]
featuredImagePreview: /assets/images/2023/10/claudio-schwarz-q8kR_ie6WnI-unsplash.webp
featuredImage: /assets/images/2023/10/claudio-schwarz-q8kR_ie6WnI-unsplash.webp
og_image: /assets/images/2023/10/claudio-schwarz-q8kR_ie6WnI-unsplash.webp

tags:
- Distributed_Tracing
- Java
- APM
- Docker
- Elastic
---

In [my last article](https://blog.touret.info/2023/09/05/distributed-tracing-opentelemetry-camel-artemis/), I dug into [Distributed Tracing](https://www.w3.org/TR/trace-context/) and exposed how to enable it in Java applications.
We didn't see yet how to deploy an application on Kubernetes and get distributed tracing insights.
Several strategies can be considered, but the main point is how to minimize the impact of deploying APM agents on the whole delivery process.

In this article, I will expose how to ship APM agents for instrumenting Java applications deployed on top of [Kubernetes](https://kubernetes.io/) through [Docker containers](https://www.docker.com/resources/what-container/).

To make it clearer, I will illustrate this setup by the following use case:

* We have an API _"My wonderful API"_ which is instrumented through an [Elastic APM agent](https://www.elastic.co/guide/en/apm/agent/index.html).
* The data is then sent to the [Elastic APM](https://www.elastic.co/guide/en/apm).

{{< style "text-align:center" >}}
![c4 context diagram](/assets/images/2023/10/architecture_system.svg )
{{</ style >}}

Now, if we dive into the _"Wonderful System"_, we can see the _Wonderful Java application_ and the agent:

{{< style "text-align:center" >}}
![c4 context diagram](/assets/images/2023/10/architecture_container.svg )
{{</ style >}}

{{< admonition tip "Elastic APM vs Grafana/OpenTelemetry" >}}
In this article I delve into how to package an [Elastic APM agent](https://www.elastic.co/guide/en/apm/agent/java/current/configuration.html) and enable Distributed Tracing with the [Elastic APM suite](https://www.elastic.co/guide/en/apm/index.html).

You can do that in the same way with an [OpenTelemetry Agent](https://github.com/open-telemetry/opentelemetry-java-instrumentation).
Furthermore, [Elastic APM is compatible with OpenTelemetry](https://www.elastic.co/fr/blog/native-opentelemetry-support-in-elastic-observability).
{{</ admonition >}}

We can basically implement this architecture in two different ways:

1. Deploying the agent in all of our Docker images
2. Deploying the agent asides from the Docker images and using initContainers to bring the agent at the startup of our applications

We will then see how to lose couple application docker images to the apm agent one.

## Why not bringing APM agents in our Docker images?
It could be really tempting to put the APM agents in the application's Docker image.

Why?
Because you just have to add the following lines of code in our Docker images definition:

```dockerfile
RUN mkdir /opt/agent
COPY ./javaagent.jar /opt/agent/javaagent.jar
```

Nonetheless, if you want to upgrade your agent, you will have to repackage it and redeploy all your Docker images.

For regular upgrades, it will not bother you, but, if you encounter a bug or a vulnerability, it will be tricky and annoying to do that.

What is why I prefer loose coupling the _"business"_ applications Docker images to technical tools such as APM agents.

## Deploy an APM agent through initContainers
While looking around how to achieve this, I came across to the [Kubernetes initContainers](https://kubernetes.io/docs/concepts/workloads/pods/init-containers/).

This kind of container is run only once during the startup of every pod.
A bunch of commands is ran then on top of it.
For our current use case, it will copy the javaagent into a volume such as an [empty directory volume](https://kubernetes.io/docs/concepts/storage/volumes/#emptydir).

### Impacts in the "_Wonderful Java Application_ Docker image
The main impact is to declare a volume in your Docker image:

```dockerfile
VOLUME /opt/agent
```
It will be used by both the Docker container and the initContainer.
We can consider it as a "bridge" between these two ones.

We also have to declare one environment variable: ``JAVA_OPTS``.

For instance:

```dockerfile
ENV JAVA_OPTS=$JAVA_OPTS
[...]
ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} org.springframework.boot.loader.JarLauncher"]
```

Il will be used during the deployment to set up our _Wonderful Java Application_.

Now, let's build our initContainer's Docker image.

### InitContainer Docker Image creation
It is really straightforward.
We can use for example, the following configuration:

```dockerfile
FROM alpine:latest
RUN mkdir -p /opt/agent_setup
RUN mkdir /opt/agent
COPY ./javaagent.jar /opt/agent_setup/javaagent.jar
VOLUME /opt/agent
```

### Kubernetes configuration
We can now set up our Kubernetes Deployment to start the corresponding container and copy the Java agent.

```yaml
kind: Deployment
spec:
containers:
- name: java-app
image: repo/my-wonderful-java-app:v1
volumeMounts:
- mountPath: /opt/agent
name: apm-agent-volume
initContainers:
- command:
- cp
- /opt/agent_setup/javaagent.jar
- /opt/agent
name: apm-agent-init
image: repo/apm-agent:v1
volumeMounts:
- mountPath: /opt/agent
name: appd-agent-volume
volumes:
- name: appd-agent-volume
emptyDir: {}
```

{{< admonition tip "Why not just copying the Java agent directly in the initContainer Docker image execution?" >}}
The copy must be run with a command specified in the initContainer declaration and cannot be done during the initContainer execution (i.e., specified in its Dockerfile).
Why?
The volume is mounted just after the initContainer execution and drops the JAR file copied earlier.
{{</ admonition >}}


## Start the Java Application with the agent
Last but not least, we can now configure the [pods](https://kubernetes.io/docs/concepts/workloads/pods/) where we run our Java applications.

We will use the ``JAVA_OPTS`` environment variable to configure the location of the Java agent, and [the Elastic APM Java system properties](https://www.elastic.co/guide/en/apm/agent/java/current/configuration.html).

For instance:

```jshelllanguage
JAVA_OPTS=-javaagent:/opt/agent/javaagent.jar -Delastic.apm.service_name=my-wonderful-application -Delastic.apm.application_packages=org.mywonderfulapp -Delastic.apm.server_url=http://apm:8200
```

You can then configure your Kubernetes deployment as:

```yaml
spec:
containers:
- name: java-app
env:
- name: JAVA_OPTS
value: -javaagent:/opt/agent/javaagent.jar -Delastic.apm.service_name=my-wonderful-application -Delastic.apm.application_packages=org.mywonderfulapp -Delastic.apm.server_url=http://apm:8200
```

_Et voila!_

## Conclusion

We have seen how to pack and deploy Distributed Tracing java agents and Java Applications built on top of Docker images.
Obviously, my technical choice of using an InitContainer can be challenged regarding your technical context and how you are confortable with your delivery practices.
You probably noticed I use an emptyDir to deploy the Java agent.
_Normally_ it will not be a big deal, but I advise you to check this usage with your Kubernetes SRE/Ops/Administrator first.

Anyway, I think it is worth it and the tradeoffs are more than acceptable because this approach are, in my opinion, more flexible than the first one.

Hope this helps!
2 changes: 1 addition & 1 deletion content/talks.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ A l’issue de cet atelier, nous aurons une vue complète et mis en pratique dif
How to learn architecture ? How to improve in this field ? How do we recognize a good or a bad architecture ?
Plenty of books and training sessions address this subject. The best thing is to practice!
In the same way as CodingDojos, I will present to you architecture katas.
Ted NEWARD (http://blogs.tedneward.com/) created them. His idea came from the following observation :
[Ted NEWARD](http://blogs.tedneward.com/) created them. His idea came from the following observation :
"How are we supposed to get great architects, if they only get the chance to architect fewer than a half-dozen times in their career?"
One solution to this issue could be to practice regularly on several topics to gain experience.
After a brief introduction of how to start designing an architecture, I will present architecture katas, and the conduct.
Expand Down
1 change: 1 addition & 0 deletions static/assets/images/2023/10/architecture_container.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading