要約
- Cloud Runで動くPHPアプリケーションのAPMをDatadogに送信したい
- supervisordを使ってコンテナ内にDatadogAgentとApacheを同居させた
- supervisordのEventListener機能を使って、"Apacheの終了=コンテナの終了"を実現した
経緯
弊社のプラットフォーム事業本部ではDatadogの利用が進められています。
パフォーマンスの可視化を進めるために、私のいるチームが持っているPHPアプリケーションも、
Datadogに対応してAPMを送信できるようにしようとしています。
PHPアプリケーションはApacheモジュールとして動作する形になっていて、
GCPのフルマネージドなCloud Run上で稼働しています。
さて、Cloud Run上で動くアプリケーションのAPMを取るための方法としては、
公式ドキュメントにこのような記述があります。
注: この機能は、Google Cloud Run for Anthos/GKE でサポートされています (Cloud Run Fully Managed ではサポートされていません)。
😇
というわけでDatadogのGoogle Cloud Platform インテグレーションを使ったAPMの収集はできないらしいので、別の方法を考える必要があります。
コンテナの中でDatadogAgentとアプリケーションの両方を動かすことを考えたので、どのように実現したかを書きます。
環境情報
- image: php:x.x-apache
実現方法
ざっくりと以下の三つの点について説明します。
- DatadogAgentインストール
- Dockerコンテナで二つのアプリケーションを同居させる
- コンテナごと終了する
DatadogAgentインストール
datadog-agentとdatadog-php-tracerをインストールします。
datadog-agentがメトリクスをサーバーに送信する役割で、
datadog-php-tracerがアプリケーションのAPMをとってagentに投げる役割です。
基本的にパッケージからインストールするかインストールスクリプトの実行で完了します。
# Install datadog-agent
ARG dd_api_key
ENV DD_API_KEY=$dd_api_key
RUN curl -Lo install_agent.sh https://raw.githubusercontent.com/DataDog/datadog-agent/main/cmd/agent/install_script.sh \
&& bash install_agent.sh
# Install datadog-tracer
ENV DDTRACE_VERSION=0.xx.0
RUN curl -Lo datadog-php-tracer.deb https://github.com/DataDog/dd-trace-php/releases/download/${DDTRACE_VERSION}/datadog-php-tracer_${DDTRACE_VERSION}_amd64.deb \
&& dpkg -i ./datadog-php-tracer.deb \
&& rm datadog-php-tracer.deb
また、datadog以前はNew RelicでAPMを取っていました。
datadog-tracerのPHP拡張モジュールと両方入れると
Found incompatible module: newrelic, disabling conflicting functionality
と言われて競合しました。
Dockerコンテナで二つのアプリケーションを同居させる
Dockerコンテナで複数のアプリケーションを動かす方法については、
公式ドキュメントに記載があります。
ドキュメントには二通りの方法が提示されています。
- shell scriptを使う方法
- supervisordを使う方法
再起動等のプロセス制御をやりたいのでsupervisordを使います。
supervisordの設定ファイル(supervisord.conf)にdatadog-trace-agentとapacheの設定を記述します。
[supervisord]
nodaemon=true
[program:datadog-tracer]
command=/opt/datadog-agent/embedded/bin/trace-agent
user=dd-agent
autorestart=true
[program:apache2]
command=/bin/bash -c "apache2-foreground"
基本的に上記の設定だけで簡単に同居させることができます。
supervisordがdockerコンテナの中でPID1として動くので、nodaemon=true
にする必要があります。
apacheにautorestartが設定されていないですが、これが次のEventListenerの話になります。
EventListener
元々はApacheがPID1プロセスとして動いていたため、Apacheが落ちるとコンテナも一緒に落ちる仕組みになっていました。
supervisordの導入でsupervisordがPID1になります。
これによって上記の仕組みが働かなくなるため、別の仕組みを考える必要があります。
supervisordにはEventsという仕組みがあります。
supervisordがevents notificationを飛ばし、EventListenerと呼ばれるプロセスがこれをハンドリングします。
EventListenerは自分で自由にコードを書いて動かすことができるので、これによって目的を実現できます。
[eventlistener:exit-listener]
command=/usr/local/bin/exit_apache_listener.py
events=PROCESS_STATE_EXITED
command=/usr/local/bin/exit_apache_listener.py
でEventListenerのコードを指定します。
events=PROCESS_STATE_EXITED
でハンドリングするイベントを指定しています。
プロセスが正常に起動したことを知らせるPROCESS_STATE_RUNNINGや、プロセスが停止状態になったことを知らせるPROCESS_STATE_STOPPED等、
いくつかのイベントが定義されています。
今回はsupervisord管理外のプロセス停止に反応したいので、PROCESS_STATE_EXITED指定します。
exit_apache_listener.py
では無限ループでイベントをListenしています。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import signal
import sys
from supervisor import childutils
def main():
while True:
headers, payload = childutils.listener.wait()
# '\n'を末尾に付けないと、プログラムが異常終了する
payload_header, data = childutils.eventdata(payload + '\n')
childutils.listener.ok()
# apacheのマスタープロセスが終了した時に、親プロセス(supervisord)を終了する
if (payload_header['processname'] == 'apache2') and (headers['eventname'] == 'PROCESS_STATE_EXITED'):
os.kill(os.getppid(), signal.SIGTERM)
if __name__ == "__main__":
main()
Apacheが終了したら、Apacheの親プロセス、すなわちsupervisordにSIGTERMを送って終了させています。
supervisordはPID1で動いていたので、supervisordを終了することでコンテナごと終了させることができます。
終わりに
Datadogのインテグレーションが用意されていないフルマネージドなCloud Runで、Datadogに対応する方法を書きました。
実験では実際に動作を確認していて、本番環境への適用はまだなので、コストやパフォーマンス等で面白い話があったらまた書くかもしれないです。
ちなみにdatadog以前はNew RelicでPHPアプリケーションのモニタリングをしていたのですが、
New Relicの場合はagentデーモンが勝手に立ち上がる仕組みがあって、簡単にCloud Runに乗せることができます😇😇😇
プラットフォーム事業本部では発信活動を強化しています。