Admission Controllerを使ってEKS on FargateにDatadog APMを設定する

# やりたいこと
Datadog APMをアプリに設定する方法として、Library Injectionというやり方があります。
通常Datadog APMを構成するためにはDatadogのトレースライブラリをアプリに組み込む必要がありますが、Library Injectionを使うとアプリケーションのコードに全く触れずにライブラリを挿入することが可能です。

今回はEKS on Fargateで動いているアプリに対してLibrary Injectionを使ってAPMを設定してみようと思います。

ここから先は特に明示がない限りKubernetes環境に対するLibrary Injectionについて記述します。

## Library Injection について

Kubernetes の Admission Controller を使用して Kubernetes API へのリクエストをインターセプトし、指定されたインスツルメンテーションライブラリを挿入する仕組みです。

DeploymentにAnnotationを付与すると、Podが生成される前にinitContainersが挿入され、APMの設定に必要なライブラリが挿入されます。

Javaアプリの場合は起動オプションとして` -javaagent:/datadog-lib/dd-java-agent.jar `が設定されます。

詳細はこちら。
https://docs.datadoghq.com/ja/tracing/trace_collection/library_injection_local/

## Datadog Admission Controller について
Datadog Cluster Agent のコンポーネントで、アプリケーションポッドのコンフィギュレーションを簡略化できる便利なツールです。

Datadog Admission Controller は MutatingAdmissionWebhook 型に属しており、kube-apiserverがオブジェクトの作成/更新等のリクエストを受け取った際にリクエストをインターセプトします。

詳細はこちら。
https://docs.datadoghq.com/ja/containers/cluster_agent/admission_controller/

# クラスターエージェントのデプロイ
ここからが本題です。
まずはEKSクラスターに対しAdmission Controllerを有効するためにクラスターエージェントをデプロイします。
デプロイはHelmで実施します。

```cluster-agent-values.yaml
datadog:
  apiKey: <API Key>
  clusterName: <Cluster Name>
agents:
  enabled: false # No DaemonSet generated
clusterAgent:
  enabled: true
  replicas: 2
  token: "aaaaaaaaaabbbbbbbbbbccccccccccdd"
  admissionController:
    enabled: true
clusterChecksRunner:
  enabled: true
apm:
  socketEnabled: false
  portEnabled: true
  port: 8126
  useSocketVolume: false
  ```

Admission Controllerを使うために以下の設定が有効になっている必要があります。
```yaml
admissionController:
    enabled: true
```
また、APMを有効にしておくことも忘れずに。
```yaml
apm:
  socketEnabled: false
  portEnabled: true
  port: 8126
  useSocketVolume: false
  ```
細かい設定内容が気になる方は[Kubernetes APM - トレース収集](https://docs.datadoghq.com/ja/containers/kubernetes/apm/)を参照ください。

上記yamlファイルを使ってクラスターエージェントをインストールします。
`$ helm install cluster-agent -f cluster-agent-values.yaml datadog/datadog`

# Fargateの設定
FargateにDatadog Agentを設定していきます。
EC2であればエージェントをDaemonsetとして動かしていきますが、Fargateの場合はサイドカーとしてデプロイする必要があります。

### EKS Fargate RBAC の設定
以下yamlに従ってAgent RBACを構成します。

```yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: datadog-agent
rules:
  - apiGroups:
    - ""
    resources:
    - nodes
    - namespaces
    - endpoints
    verbs:
    - get
    - list
  - apiGroups:
      - ""
    resources:
      - nodes/metrics
      - nodes/spec
      - nodes/stats
      - nodes/proxy
      - nodes/pods
      - nodes/healthz
    verbs:
      - get
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: datadog-agent
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: datadog-agent
subjects:
  - kind: ServiceAccount
    name: datadog-agent
    namespace: default
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: datadog-agent
  namespace: default
```

### エージェントをサイドカーとして設定




```deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
 name: "sample-java"
 namespace: default
spec:
 replicas: 1
 selector:
   matchLabels:
     app: "sample-java"
 template:
   metadata:
     labels:
       app: "sample-java"
       admission.datadoghq.com/enabled: "true"
       admission.datadoghq.com/config.mode: hostip
     annotations:
       admission.datadoghq.com/java-lib.version: "latest"
   spec:
     serviceAccountName: datadog-agent
     shareProcessNamespace: true
     containers:
     - name: jpet
       image: arey/springboot-petclinic
       ports:
        - containerPort: 8080
     - image: datadog/agent
       name: datadog-agent
       ports:
        - containerPort: 8126
          name: traceport
          protocol: TCP
       env:
       - name: DD_API_KEY
         value: "<API Key>"
       - name: DD_SITE
         value: "datadoghq.com"
       - name: DD_EKS_FARGATE
         value: "true"
       - name: DD_APM_ENABLED
         value: "true"
       - name: DD_CLUSTER_AGENT_AUTH_TOKEN
         value: "aaaaaaaaaabbbbbbbbbbccccccccccdd"
       - name: DD_CLUSTER_NAME
         value: <Cluseter Name>
       - name: DD_CLUSTER_AGENT_URL
         value: https://cluster-agent-datadog-cluster-agent.default.svc.cluster.local:5005
       - name: DD_ORCHESTRATOR_EXPLORER_ENABLED
         value: "true"
       - name: DD_KUBERNETES_KUBELET_NODENAME
         valueFrom:
           fieldRef:
             apiVersion: v1
             fieldPath: spec.nodeName
```

ポイントとなるのはこの部分です。
```yaml
 template:
   metadata:
     labels:
       app: "sample-java"
       admission.datadoghq.com/enabled: "true"
       admission.datadoghq.com/config.mode: hostip
     annotations:
       admission.datadoghq.com/java-lib.version: "latest"
```

今回はJavaアプリケーションなので `java-lib.version` を指定しましたが、アプリの言語に合わせて修正してください。

あとはこれを普通にkubectl applyするだけで完了です。

Datadog上で見ると`datadog-lib-java-init`というinitContainersが挿入されていることが確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242688/2987d501-7dad-0127-a997-9977343826c4.png)


Datadog上で見なくてもinitContainersが挿入され、Javaアプリに ` -javaagent:/datadog-lib/dd-java-agent.jar` オプションが追加されていることがわかります。
```sh
$ kubectl describe pod sample-java-74b7dfbfc4-9f6z4
Name:                 sample-java-74b7dfbfc4-9f6z4
Namespace:            default
Priority:             2000001000
Priority Class Name:  system-node-critical
Service Account:      datadog-agent
Node:                 fargate-ip-192-168-181-99.ap-northeast-1.compute.internal/192.168.181.99
Start Time:           Tue, 19 Dec 2023 20:43:20 +0900
Labels:               admission.datadoghq.com/config.mode=hostip
                      admission.datadoghq.com/enabled=true
                      app=sample-java
                      eks.amazonaws.com/fargate-profile=fp-default
                      pod-template-hash=74b7dfbfc4
Annotations:          CapacityProvisioned: 0.25vCPU 0.5GB
                      Logging: LoggingDisabled: LOGGING_CONFIGMAP_NOT_FOUND
                      admission.datadoghq.com/java-lib.version: latest
Status:               Running
IP:                   192.168.181.99
IPs:
  IP:           192.168.181.99
Controlled By:  ReplicaSet/sample-java-74b7dfbfc4
Init Containers:
  datadog-lib-java-init:
    Container ID:  containerd://3c6848fb183c44ba60bd7d7d8d5a2372a7eaf101b6d129ac8ce181f5cf9c39de
    Image:         gcr.io/datadoghq/dd-lib-java-init:latest
    Image ID:      gcr.io/datadoghq/dd-lib-java-init@sha256:1c1d7225d251147c387250bbcda0500747fa90e3b1e00f60b1c509dcf2a9eb4f
    Port:          <none>
    Host Port:     <none>
    Command:
      sh
      copy-lib.sh
      /datadog-lib
    State:          Terminated
      Reason:       Completed
      Exit Code:    0
      Started:      Tue, 19 Dec 2023 20:43:33 +0900
      Finished:     Tue, 19 Dec 2023 20:43:33 +0900
    Ready:          True
    Restart Count:  0
    Environment:
      DD_ENTITY_ID:    (v1:metadata.uid)
      DD_AGENT_HOST:   (v1:status.hostIP)
    Mounts:
      /datadog-lib from datadog-auto-instrumentation (rw)
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-jr8qv (ro)
Containers:
  jpet:
    Container ID:   containerd://3de6b47c52f4a8781a9dfd6a1390c9c1d09664d5cc8847c41ac9c5576b9ac33c
    Image:          arey/springboot-petclinic
    Image ID:       docker.io/arey/springboot-petclinic@sha256:b9495bb3152c5e5858ce6952f2c454bce2c3538a788445d1a02028c85aca6b3f
    Port:           8080/TCP
    Host Port:      0/TCP
    State:          Running
      Started:      Tue, 19 Dec 2023 20:48:49 +0900
    Last State:     Terminated
      Reason:       OOMKilled
      Exit Code:    137
      Started:      Tue, 19 Dec 2023 20:43:42 +0900
      Finished:     Tue, 19 Dec 2023 20:48:30 +0900
    Ready:          True
    Restart Count:  1
    Environment:
      DD_ENTITY_ID:        (v1:metadata.uid)
      DD_AGENT_HOST:       (v1:status.hostIP)
      JAVA_TOOL_OPTIONS:   -javaagent:/datadog-lib/dd-java-agent.jar
    Mounts:
      /datadog-lib from datadog-auto-instrumentation (rw)
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-jr8qv (ro)
  datadog-agent:
    Container ID:   containerd://d70a7e274ae363c0ff5748afc8c077ddc71299469c1876912dc414c54dc43a85
    Image:          datadog/agent
    Image ID:       docker.io/datadog/agent@sha256:ef306d23b1fb8be4877b7cf1bc174370d9b66f624bf0e3fb831362b5cb99590c
    Port:           8126/TCP
    Host Port:      0/TCP
    State:          Running
      Started:      Tue, 19 Dec 2023 20:48:45 +0900
    Ready:          True
    Restart Count:  0
    Environment:
      DD_ENTITY_ID:                       (v1:metadata.uid)
      DD_AGENT_HOST:                      (v1:status.hostIP)
      DD_API_KEY:                        <API Key>
      DD_SITE:                           datadoghq.com
      DD_EKS_FARGATE:                    true
      DD_APM_ENABLED:                    true
      DD_CLUSTER_AGENT_AUTH_TOKEN:       aaaaaaaaaabbbbbbbbbbccccccccccdd
      DD_CLUSTER_NAME:                   <Cluster Name>
      DD_CLUSTER_AGENT_URL:              https://cluster-agent-datadog-cluster-agent.default.svc.cluster.local:5005
      DD_ORCHESTRATOR_EXPLORER_ENABLED:  true
      DD_KUBERNETES_KUBELET_NODENAME:     (v1:spec.nodeName)
      JAVA_TOOL_OPTIONS:                  -javaagent:/datadog-lib/dd-java-agent.jar
    Mounts:
      /datadog-lib from datadog-auto-instrumentation (rw)
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-jr8qv (ro)
```

## 参考資料
公式ドキュメント: [Amazon EKS on AWS Fargate](https://docs.datadoghq.com/ja/integrations/eks_fargate/)