ECSのログをSplunkに送る

# はじめに

この記事ではEC2 HostingのECSにデプロイしたアプリケーションのログをSplunkにインデックスする方法についての記事を書きます
Fargateについては別の記事に書きます

# 概要

ECSのログはコンテナごとにLog Driverを選択することでログの出力先を指定できます
Splunkにはre:Invent 2019で発表された [FireLens](https://aws.amazon.com/about-aws/whats-new/2019/11/aws-launches-firelens-log-router-for-amazon-ecs-and-aws-fargate/) を使ってログを送ります
log_routerはFluentdです（Fluent Bitじゃないよ）

Log DriverにSplunkを指定することもできますが、あえてFireLensのfluentdを使います
理由は↓

- fluentdを使うことでSplunkが障害やメンテナンスで落ちてるときに再送してくれる
- JavaのStack Traceのような複数行のログイベントも1イベントとしてまとめてくれる

Splunk log driverはこのようなメリットの裏返しのデメリットがあります

HTTP Event Collectorに直接HTTP POSTするだけなので、再送処理はしてくれません

また、デフォルトでログがJSON形式になってしまいます
[Dockerコンテナ内のログをSplunkに入れてみた](https://qiita.com/kikeyama/items/515d65906537239e04d2#%E8%A3%9C%E8%B6%B3%E3%83%AD%E3%82%B0%E3%81%AE%E3%83%95%E3%82%A9%E3%83%BC%E3%83%9E%E3%83%83%E3%83%88%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6) を参考にログを強引にいい感じの形式にしても、複数行のログをまとめることはできないのです
Splunkで `props.conf` や `transforms.conf` でなんとかしようとしても、HTTP POSTで1行ずつ送ってくるっぽいので、どうしようもありません
うーん、過去の自分の記事を否定してるみたいでやだな

ということで、この [ドキュメント（いちおう公式）](https://github.com/splunk/splunk-connect-for-kubernetes/blob/develop/firelens/README.md) を参照しながら設定しました

# 設定方法

以下の手順で設定します

## 1. SplunkでHTTP Event Collectorを有効化

設定方法は [ここらへんの記事](https://qiita.com/kikeyama/items/bb609cb90269019962cb#http-event-collector%E8%A8%AD%E5%AE%9A) を参考にしてください

## 2. log_routerコンテナ追加

ログを取りたいサービスが入っているタスク定義に `log_router` コンテナを追加して登録します

```yaml:kikeyama-services-task.yaml
- cpu: 0
  environment: []
  essential: true
  firelensConfiguration:
    type: fluentd
    options:
      config-file-type: s3
      config-file-value: arn:aws:s3:::mybucket/fluent.conf
  image: splunk/fluentd-hec:1.2.0
  logConfiguration:
    logDriver: awslogs
    options:
      awslogs-group: /ecs/kikeyama-services
      awslogs-region: us-west-2
      awslogs-stream-prefix: ecs
  memoryReservation: 100
  mountPoints: []
  name: log_router
  portMappings: []
  user: '0'
  volumesFrom: []
```

```bash
# タスク定義を登録
aws ecs register-task-definition --cli-input-yaml file://./kikeyama-services-task.yaml
```

ポイントは↓

- コンテナイメージは `splunk/fluentd-hec:1.2.0` を指定
- `firelensConfiguration` でS3からfluentdの設定ファイルをとってきているところ（以下抜粋）

```yaml:抜粋
  firelensConfiguration:
    type: fluentd
    options:
      config-file-type: s3
      config-file-value: arn:aws:s3:::mybucket/fluent.conf
```

オプションとして、log_routerそのもののログをCloudWatch Logsに送ってます
うまくいかなかったとき用のトラブルシューティングとしてね

## 3. Fluentd設定ファイルを作成してS3に保存

以下のファイルを作成してタスク定義に指定しているS3バケット、パスにアップロードします

```conf:fluent.conf
<system>
  log_level info
</system>

<filter **spring-sfx-demo**>
  @type concat
  key log
  stream_identity_key stream
  multiline_start_regexp /^\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}\.\d{3}/
  flush_interval 5s
  timeout_label @SPLUNK
  use_first_timestamp true
</filter>

<match **>
  @type relabel
  @label @SPLUNK
</match>

<label @SPLUNK>
  <match **>
    @type splunk_hec
    protocol <SPLUNK_HEC_SCHEME (http|https)>
    hec_host <SPLUNK_HEC_HOST>
    hec_port <SPLUNK_HEC_PORT>
    hec_token <SPLUNK_HEC_TOKEN>
    index <SPLUNK_INDEX>
    host_key ec2_instance_id
    source_key ecs_cluster
    sourcetype_key ecs_task_definition
    insecure_ssl true
    <fields>
      container_id
      container_name
      ecs_task_arn
      source
    </fields>
    <format>
      @type single_value
      message_key log
      add_newline false
    </format>
  </match>
</label>

<label @ERROR>
  <match **>
    @type relabel
    @label @SPLUNK
  </match>
</label>
```

ポイントは↓

- ラベル `@SPLUNK` で `splunk_hec` プラグインを利用
- `<fiter xxxxx>` でコンテナごとに `concat` を使って `multiline_start_regexp` でログイベントの最初の文字を正規表現で指定して複数行ログも取り込み可能に

といったところです
今回はJava Spring Bootで作った `spring-sfx-demo` という名前のコンテナのアプリケーションのログを取得します（`filter` で指定してる名前ね）

`@SPLUNK` ラベル内の `<SPLUNK_XXXX>` については適宜自分の環境の値を入れてください（`<` `>` は要らないよ）

では、このファイルをS3にアップロードしましょう

## 4. サービスを更新

ECSのサービスを更新して上記ステップ2.で登録したタスク定義を指定しましょう
これでログはSplunkに送信されます

# Splunkでログを確認

ちゃんと複数行のStack Traceも1ログイベントにまとまってますね

<img width="1541" alt="ecs_log_firelens_fluentd.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/174837/a5576445-f7ed-c6f6-da26-e57e9212e680.png">

今気づいたけどデフォルトでは日付時刻の抽出がちゃんとできてないですね
fluent.conf でちゃんとparseする必要がありますが忘れてました

# さいごに

fluentdを使ったのはほぼ初めてなのでけっこう苦戦しました
もう少し設定をちゃんとする余地はあるものの、とりあえずログは取れてるのでヨシとしましょう

タイムスタンプのparseについては、気が向いたら対応します

Fargateは若干クセがあったので、ECSと全く同じというわけにはいかなかったです
それについては後日別の記事に書きます乞うご期待