DatadogでlaradocのphpアプリonECSのAPM設定した備忘録

laradocもecsもdockerもわりと初心者でしつこく右往左往していたらなんかできたけど即効なんだっけとなりかねないので備忘録しておきます。
具体的なのは環境依存の手順なので、ちょっとでも構成違うなら[マニュアル](https://docs.datadoghq.com/tracing/)よく見るといいと思います。

以下リンク先に対象として載ってて、その先の言語毎の頁に載ってる対象バージョンとフレームワーク等が合致するとトレースパッケージ入れたり設定したりできそうです。つっこんだ内容を追跡したい場合は追加の設定とかソースコードに追記が要りそうというかそもそも色々取れすぎるので本番というよりはステージングやリリース前環境で等あるようです。
https://docs.datadoghq.com/tracing/setup/


#####datadog-agentのコンテナにした設定

- タスク定義

環境変数とポートmappingなど以下を追加。

```json:datadog-agentのECSタスク定義抜粋
～略～
      "portMappings": [
        {
          "hostPort": 8126,
          "protocol": "tcp",
          "containerPort": 8126
        },
        {
          "hostPort": 8125,
          "protocol": "udp",
          "containerPort": 8125
        }
      ],
～略～
      "environment": [
        {
          "name": "DD_AC_EXCLUDE",
          "value": "name:datadog-agent"
        },
        {
          "name": "DD_API_KEY",
          "value": "<apikey>"
        },
        {
          "name": "DD_APM_ENABLED",
          "value": "true"
        },
        {
          "name": "DD_APM_NON_LOCAL_TRAFFIC",
          "value": "true"
        },
        {
          "name": "DD_APM_RECEIVER_PORT",
          "value": "8126"
        },
        {
          "name": "DD_DOGSTATSD_NON_LOCAL_TRAFFIC",
          "value": "true"
        },
        {
          "name": "DD_LOG_LEVEL",
          "value": "info"
        },
        {
          "name": "DD_LOGS_CONFIG_CONTAINER_COLLECT_ALL",
          "value": "true"
        },
        {
          "name": "DD_LOGS_ENABLED",
          "value": "true"
        },
        {
          "name": "DD_PROCESS_AGENT_ENABLED",
          "value": "true"
        },
        {
          "name": "SD_BACKEND",
          "value": "docker"
        }
      ],
～略～
```



#####アプリコンテナにした設定

ざっくりいうと拡張トレースパッケージ入れるのと環境変数指定するかんじです。

- 前置き

ECSがコンテナをうごかしてくれるわけですが、
ECSのclusterのなかにtaskというk8sでいうとPod相当のサイドカーコンテナなど一緒にしといて同じホストで動かしといたほうがいい通信制限がない複数コンテナのまとまりを定義してるもんがあります。

こんなかんじ。
ECSCluster(service1(task1(container1,container2),service2(task2(container3)))

同じtaskの中に複数コンテナをサイドカーっぽく設定するのであればホスト名とか定義しなくても通信し放題だけどtaskが異なる場合task間の通信にホストのアドレス（載ってるインスタンスの）が要るようでした。
アプリのコンテナ側でトレース用のエージェントを入れてそいつが通信しにいくdatadog-agentのポートとホストを設定するのに重要なのが、環境変数でした。コンテナに（インストールにあたるイメージ生成のあとに）動的な設定情報を与える方法てのが、環境変数つかうかマウントさすかどっちかになる感じです。

で、環境変数てのはtask定義でもCodebuildでも設定できるんですが、
[マニュアルによると](https://docs.datadoghq.com/integrations/amazon_ecs/?tab=python#trace-collection)、ホスト情報というのはコンテナ起動時に情報取得しにいかせて定義するものらしく、それをする方法が、コンテナ起動コマンドのCMDの手前で読みこませて変数を丸っと渡す動きをさせるエントリポイントのスクリプトを仕込んでおくというものでした。ここらへんはDockerの定石な模様。
EntrypointとCMDを両方Dockerファイル内に書くとEntrypointの引数がCMDになるので、以下リンク先で説明されてるようにEntrypointで変数展開された状態を引き継いでCMDを実行するみたいなことにすると、コンテナ起動時に動的に変数が読まれるというような動きをする模様。
https://stackoverflow.com/questions/39082768/what-does-set-e-and-exec-do-for-docker-entrypoint-scripts
https://docs.docker.com/engine/reference/builder/#entrypoint

で、ECSのタスク内は通信は同じホスト内のように自由だけどタスクが分かれるとEC2インスタンスというかコンテナが載ってるVMのホストのプライベートIPとポート番号を指定して目指さないと通信ができないっぽいですね。トレースエージェントからデータドックエージェントにデータを投げるそのために設定が要るみたい。
ホストのIPアドレスはEC2メタデータからとってこれます。

- 具体的な設定内容

```shell-session:既存コンテナをinspectでみてEntrypointを確認
$ docker inspect <container-id>
～略～
            "Entrypoint": [
                "docker-php-entrypoint"
            ],
～略～
```


```shell:phpコンテナのdockerfile抜粋
～略～
# install for datadog amqp
RUN curl -L -o /tmp/datadog-php-tracer.deb "https://github.com/DataDog/dd-trace-php/releases/download/0.22.0/datadog-php-tracer_0.22.0_amd64.deb" \
&& dpkg -i /tmp/datadog-php-tracer.deb \
&& rm /tmp/datadog-php-tracer.deb
～略～
COPY docker-php-entrypoint /usr/local/bin/
RUN chmod -R 777 /usr/local/bin/
CMD php artisan key:generate && php artisan migrate && php artisan config:cache && php artisan storage:link && docker-php-entrypoint && php-fpm
～略～
```

```shell:Entrypointのスクリプト(docker-php-entrypoint)内容
#!/bin/sh
set -e

# first arg is `-f` or `--some-option`
if [ "${1#-}" != "$1" ]; then
        set -- php-fpm "$@"
fi
export DD_AGENT_HOST=`curl -s http://169.254.169.254/latest/meta-data/local-ipv4`
export DD_TRACE_AGENT_HOSTNAME=`curl -s http://169.254.169.254/latest/meta-data/local-ipv4`

exec "$@"
```

```shell:laravel的な.envファイルの追記内容
DD_AGENT_HOST=${DD_AGENT_HOST}
DD_TRACE_AGENT_HOSTNAME=${DD_TRACE_AGENT_HOSTNAME}
```
これをするとそとから渡された環境変数読んでくれるかんじの動きに。
具体的な値はCodebuildの環境変数などから渡すような。
以下のサービス名などの動的でないような変数はCodebuildから渡しました。

```text
DD_SERVICE_NAME	<application-servicename>
DD_ENV	<service-environment(prod,stg,dev)>
DD_TRACE_AGENT_PORT	8126
DD_TRACE_CLI_ENABLED	true
```


これ以外にはとくにアプリケーションに何か設定を埋め込むようなことはしてないけどAPM表示できたなーというところですね。サービスマップとか細かく見るための連携が可能だったりはするようですが特に何もしていないけどけっこう見られるようです。リクエスト数とかどの処理にどれだけ時間かかってるかなど。

#####そのたlogまわりなど

APMは関係ないlaradocのログ集約まわりの備忘録ですが、dockerなので標準出力'php://stdout'にださないとあかんようでした。
それをもともと設定されていたfluentdコンテナがキャッチしたのをdatadogが拾ってくるみたいなことにしました（なんかよくみたらアプリのログは既存のfluentdコンテナというかタスクで標準出力とファイルに吐きつつkinesisFirehouseに流す設定になっており、kinesisからS3に飛ばされていて、datadogは最大15か月で維持したかったらS3に流す感じなので既存設定を維持した）。

```php:bootstrap/app.php一部
$app->configureMonologUsing(function ($monolog) {
    $monolog->pushHandler(new \Monolog\Handler\StreamHandler('php://stdout'));
    $monolog->pushHandler(new \Monolog\Handler\RotatingFileHandler(
        storage_path() . '/logs/laravel.log',
        $maxFiles = 0,
        config('app.log_level')
    ));
});
```

あとさらに関係ないですがログ設定ということでELBのヘルスチェックログをnginxコンテナでUserAgentで除外する設定

```config:nginx.conf一部
  access_log /dev/stdout combined if=$log_ua;
  error_log /dev/stderr;

  map $http_user_agent $log_ua {
      ~ELB-HealthChecker 0;
      default 1;
  }
```

fluentdコンテナ側にインテグレーション用なパッケージいれるのが要る感じでした
（パッケージなしstdout拾わすだけだとdatadog上で後工程パースができなさそう）
https://docs.datadoghq.com/integrations/fluentd/#log-collection

```shell:github.com/fluent/fluentd-docker-image/blob/master/v1.5/alpine/Dockerfile参考一部
#～略～
 && gem install fluent-plugin-kinesis-firehose \
 && gem install fluent-plugin-datadog \
#～略～
```

```xml:fluentd.conf一部
<filter **>
  @type stdout
</filter>

<match **>
  @type copy
  <store>
    @type datadog
    api_key <apikey>
    include_tag_key true
    tag_key 'tag'
    dd_source 'test_souce'
    dd_tags 'test'
    dd_sourcecategory 'tests-cate'
    host 'intake.logs.datadoghq.com'
    ssl_port '10516'
  </store>
</match>
```
ECSのタスクのコンテナ定義からログドライバ指定する例↓

```json:fluentdをログドライバに設定
      "logConfiguration": {
        "logDriver": "fluentd",
        "secretOptions": null,
        "options": {
          "fluentd-async-connect": "true",
          "fluentd-retry-wait": "1000ms",
          "fluentd-buffer-limit": "8MB",
          "fluentd-max-retries": "1073741824"
        }
```

#####datadogでseverityパースしてみた

参考
https://docs.datadoghq.com/logs/processing/parsing/?tab=matcher#key-value
https://developers.cyberagent.co.jp/blog/archives/12565/

適当なnginxコンテナをたてて適当なfluentdコンテナ(datadogプラグイン入りでdatadogに投げつける設定入れてあるやつ)にむかってlogを投げつけたあとにdatadogのlogmanagementの画面上でなにするかって内容図↓です。ステータスでフィルタしたい的なアレです。

![datadog-log-severity1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25031/b5f40638-e6c9-bb90-8699-4009f631cc04.png)

![datadog-log-severity2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25031/02d5995b-6c15-fd69-85ff-f0cc1ca7b776.png)

GrokParserがmessageにあたるやつを解析しようとするらしくmessageRemapperとかが先に要るようでした。（AdvancedSettingsを開けると気づくかんじ）
サンプルソースを貼りさえすれば正規表現があってるかその場でチェックしてくれるのホスピタリティ高すぎやしないかなと思いました（ほめてる）。

あと詳細ログから`create facet`するとひだりのチェックボックスが増えるのも便利な感じでした。
https://qiita.com/14kw/items/f489cdea3de0a34d847b

laravelのログのGlogperserこんなかんじという参考

`serverity_map .*\.%{regex("OFF|ERROR|WARNING|INFO|DEBUG|CRITICAL|ALERT|EMERGENCY"):severity}\:.*`

https://laravel.com/docs/5.5/errors#log-severity-levels 

#####laravel参考
https://qiita.com/tdkn/items/b8aa24cca0ba2904f8e7
https://gomiba.co.in/blog/archives/923
https://blog.pinkumohikan.com/entry/stdout-permission-denied-in-docker-container