y-ohgi's blog

TODO: ここになにかかく

Cloud RunサイドカーでDataDog APMを利用する

TL;DR

  • Cloud Runでサイドカー機能が23/05/16にプレビューが発表されました
  • サイドカーコンテナとしてdatpadog agentを並走させ、APMのトレースを取得を試しました

About

Cloud Runにサイドカーがプレビューとしてリリースされました。
モニタリングやプロキシなど、Cloud Runで対応が難しかったケースへの大きな選択肢になります。
今回はCloud Runのサイドカーとして、datadog agentを立てAPMのトレースを取得を行います。

いままでのトレースの取得方法

Cloud Run(やコンテナ実行環境)でdatadogのAPMは パブリックベータ として使用可能です。
どの様にdatadog agentのプロセスを扱うかと言うと、コンテナ実行時のコマンドを(ランタイムやバイナリではなく)datadogが提供するプロセスに対してキックする形で利用可能です。

例えばgolangであれば以下のようなDockerfileになります。

FROM alpine

# docker hubのdatadog/serverless-initイメージからdatadog-initを取得
COPY --from=datadog/serverless-init /datadog-init /app/datadog-init

 :

# 取得したdatadog-initを使用してバイナリを実行
ENTRYPOINT ["/app/datadog-init"]
CMD ["/path/to/your-go-binary"]

ref: https://docs.datadoghq.com/serverless/google_cloud_run/?code-lang=go#install-agent-with-dockerfile

機能として必要十分ではありますが、これはアンチパターンや最終手段ではないかなと個人的に感じていました。
また、親プロセスとしての実行を要求するサービスが増えてしまうと複雑なイメージになってしまい「1コンテナ1プロセス」もしくは「1コンテナ1つの関心事」の掟を破ってしまうこと(アプリケーションの実行と監視の2(以上)つの関心事を持ったコンテナになってしまうこと)に違和感を感じていました。
このアップデートは最後の1ピースとして埋まってくれ、個人的にCloud Runが技術選定の際の最初の選択肢になりました。

Version

  • datadog agent
    • datadog/agent:7
  • python
    • python:3.12.0b1-slim
  • ddtrace
    • 1.13.4

コード

learn-gcp/cloudrun.yaml at main · y-ohgi/learn-gcp

構築

実行用コードの用意

DataDogの公式を参考にPythonで動かします。
Getting Started with Tracing

from flask import Flask
app = Flask(__name__)

@app.route('/')
def home():
    return 'hello world!'

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5050)
# syntax=docker/dockerfile:1
FROM python:3.12.0b1-slim

WORKDIR /app

RUN <<EOL
apt-get update
apt-get -y install build-essential

pip install flask
pip install ddtrace
EOL

COPY . .

EXPOSE 5050

CMD ["ddtrace-run", "python", "hello.py"]

ローカル環境の構築

同じく公式を参考にdocker-compose.yamlを書きます。
Compose and the Datadog Agent

version: "3.9"

services:
  app:
    build: .
    environment:
      - DD_SERVICE=hello
      - DD_AGENT_HOST=datadog
      - DD_ENV=local
    volumes:
      - .:/app:delegated
    ports:
      - "5050:5050"

  datadog:
    image: datadog/agent:7
    environment:
      - DD_API_KEY=${DD_API_KEY}
      - DD_SITE=ap1.datadoghq.com
      - DD_APM_ENABLED=true
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - /proc/:/host/proc/:ro
      - /sys/fs/cgroup:/host/sys/fs/cgroup:ro

ローカル環境での実行

APIキーを作成し、環境変数へ格納し、docker-composeを起動します。

export DD_API_KEY=<DATADOG_API_KEY>
docker compose up

ローカルでFlaskを叩き、暫く待ってからAPMにトレースが反映されることを確認します。

curl localhost:5050

Cloud Runで実行

前提

GCPの各種APIは有効化されていることを前提とします

Artifact Registryへイメージをpush

Artifact Registryへ認証を行います。

gcloud auth configure-docker \
  asia-northeast1-docker.pkg.dev

Dockerfileのbuildとpushを行います。

docker build -t asia-northeast1-docker.pkg.dev/<PROJECT>/learn/hello-py .
docker push asia-northeast1-docker.pkg.dev/<PROJECT>/learn/hello-py

Secret ManagerへDataDogのAPIキーを追加

Cloud Runで使用するDataDogのAPIキーをSecret Managerへ保管します

echo -n ${DD_API_KEY} | gcloud secrets create dd-api-key2 --data-file=-

Cloud RunからSecret Managerへのアクセス権限を追加

プロダクションであれば新しくIAMを追加するべきですが、今回はCloud RunのデフォルトIAMへSecret Managerのアクセスロールを追加します。

gcloud projects add-iam-policy-binding <PROJECT_ID> \
  --member user:<USER_EMAIL> \
  --role roles/secretmanager.secretAccessor

Cloud Runのデプロイ

Cloud Run設定用yamlを書きます。

# cloudrun.yaml
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  name: python-with-datadog
  annotations:
    # 現状サイドカーはプレビューなので、プレビュー機能の有効化
    run.googleapis.com/launch-stage: BETA
spec:
  template:
    metadata:
      annotations:
        run.googleapis.com/execution-environment: gen2
        run.googleapis.com/container-dependencies: '{"app":["datadog"]}'
        run.googleapis.com/cpu-throttling: "false"

    spec:
      containers:
        - image: asia-northeast1-docker.pkg.dev/<PROJECT ID>/learn/hello-py:latest
          name: app
          env:
            - name: DD_SERVICE
              value: hello
            - name: DD_ENV
              value: dev
          ports:
            - containerPort: 5050

        - image: datadog/agent:7
          name: datadog
          env:
            # 各リージョンに合わせてドメインを変更
            - name: DD_SITE
              value: ap1.datadoghq.com
            - name: DD_APM_ENABLED
              value: "true"
            - name: DD_HOSTNAME
              value: cloudrun
            # Secret ManagerへDataDog APIキーを取得
            - name: DD_API_KEY
              valueFrom:
                secretKeyRef:
                  name: dd-api-key
                  key: latest

デプロイ

gcloud run services replace cloudrun.yaml --region=asia-northeast1

Cloud Runはデフォルトでは認証が必要なため、公開状態にします。

# policy.yaml
bindings:
  - members:
      - allUsers
    role: roles/run.invoker
gcloud run services set-iam-policy python-with-datadog policy.yaml

動作確認

以下のコマンドを実行した際に得られるURLへcurlを行います。

gcloud run services replace cloudrun.yaml --region=asia-northeast1
 :
New configuration has been applied to service [python-with-datadog].
URL: https://xxxxxxxxxxx-an.a.run.app
curl https://xxxxxxxxxxx-an.a.run.app

DataDogでAPMのトレースを確認して成功です。

制約

個人的に疑問に思った点のまとめです

コンテナの起動順を決められるか

DataDogのような監視やProxyなど、起動順を定義するケースがあります。
それに対してCloud Runのサイドカーは依存関係を定義することが可能です。

spec:
  template:
    metadata:
      annotations:
        # コンテナ名"app"はコンテナ名"datadog"が起動しているという依存関係を定義
        run.googleapis.com/container-dependencies: '{"app":["datadog"]}'

リソース(CPU/Memory)はどう共有するのか

Cloud Run内でコンテナ同士のCPU/Memoryの割り当てを定義できるかについてです。
1つのコンテナがリソースを食いつぶしてしまう(OOM Killerなど)ケースへの考慮です。
これはCloud Runインスタンスだけでなく、各コンテナのリソースを指定することが可能です(ただ。インスタンス内のコンテナのリソースのメトリクス取得はこれから...?

    spec:
      containers:
      - name: app
        image: <IMAGE URI>
        ports:
        - containerPort: 5050
        resources:
          limits:
            cpu: 1000m
            memory: 512Mi

      - name: datadog
        image: datadog/agent:7
        resources:
          limits:
            cpu: 1000m
            memory: 256Mi            

ヘルスチェックは可能か

k8s同様 startupProbe が使用可能です。
Cloud Runは定義されていない場合、自動的に以下のヘルスチェックを行ってくれます。

      containers:
      - name: app
        image: <IMAGE>
        startupProbe:
          timeoutSeconds: 240
          periodSeconds: 240
          failureThreshold: 1
          tcpSocket:
            port: 5050

所感

素直に「これが欲しかった!」という所感です。喜ばしい!
Cloud Runはカジュアルにコンテナイメージを動かせて、他のGCPサービスとの連携もスムーズで好きなサービスの1つでした。
ただ、サイドカーが必要な場合はAutopilotを選定していました。Autopolotはカジュアルにコンテナイメージを動かせるわけではないため(IngressやDeploymentの定義など。)、腰が重かったところがあります。
この中間点をCloud Run自身が埋めてくれたというイメージが強いです。
二度目になりますが、個人的にCloud Runが技術選定時の最初の選択肢になりました。