AWS LambdaでRESTでデータ取得してSplunkに送信してみる

#はじめに
- RESTで取得してSplunk or Splunk Cloudに取り込みたいデータがある
- Add-onがあればいいんだけど、ないものはない
- Add-on作るのめんどい
- ローカルでREST叩くスクリプト動かすとしても24時間稼働するマシン用意するのもコストがかかる
- うーん、、、AWS Lambda試してみるか　→　**何これすごい！**　というお話です

追記：本記事ではNode.jsを使用します。Pythonでの方法をまとめました。
[AWS LambdaでRESTでデータ取得してSplunkに送信してみる (Python編)](https://qiita.com/symmr/items/074be71bdcc31d3e6079)）

#環境
- Lambdaからアクセス可能するためEC2などでグローバルIPを持つSplunk Enterprise、もしくはSplunk Cloud
- AWS Lambda

#設定手順の概要
1. SplunkでHEC設定
2. AWS Lambdaで関数作成
3. CloudWatchでLambdaを定期実行化

#設定手順
##1. SplunkでHEC設定
まずはAWS Lambdaからデータを受け取るためのHECを設定します。
ここは通常通りです。

1. 設定 > データ入力
2. HTTPイベントコレクターの [+ 新規追加] をクリック
3. 名前を適当に入力して [次へ] をクリック
4. 取り込み先のインデックスを選択　※今回はaws_lambdaを作成しました。
5. 生成されたトークンをメモ

以上です。
念のため、SSLの有効/無効状況やHTTPポート番号をグローバル設定から確認しておきましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1218403/36ee2b16-6bb9-3c96-2d9a-03951c9588f8.png)

##2. AWS Lambdaで関数作成
さて、AWS Lambda初心者にとって何をどうしたらいいか全然分からず途方に暮れていたのですが、[splunk-logging blueprint](https://dev.splunk.com/enterprise/docs/devtools/httpeventcollector/useawshttpcollector/createlambdafunctionnodejs/)という有難いものを見つけました。
本当にSplunkには何でもあるなぁ。

これはAWS Lambdaの関数の設計図（Blueprint）で、Splunk HECへのデータ送信が可能なもののようです。言語はNode.jsです。

まずは上記のページの通り設定してきます。

1. AWS ConsoleからLambdaを探す
2. [関数を作成] をクリック
3. [設計図の使用] を選択し、設計図で「splunk-logging」を検索、選択して [設定] をクリック
（または[こちら](https://console.aws.amazon.com/lambda/home?#/create/configure-triggers?bp=splunk-logging)から直接行けます）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1218403/7789cd7f-10f6-8e34-52ce-678d040efec2.png)
4. 諸々入力します。
    + 関数名：任意
    + 実行ロール：「基本的なLambdaアクセス権限で新しいロールを作成」のまま
    + Lambda関数のコード：今はいじる必要なし
    + 環境変数：HECの情報を入力
        + SPLUNK_HEC_URL：https://splunkのhost名:8088/services/collector
            ※プロトコル、ポート番号はHEC設定に合わせる。/services/collectorを忘れずに（これがないと404エラーになります。しばらく悩みました）。
        + SPLUNK_HEC_TOKEN：HECのトークン値
5. [関数の作成] をクリック
6. これでSplunkへデータ送信するための関数が作成されました。早速テストしてみましょう。
「テスト」タブでテンプレートは「hello-world」のまま [呼び出し] をクリックします。するとサンプルのJSONがSplunkに送られます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1218403/ed7cb8dd-aac1-82d2-bcc3-233f85dcc8d0.png)

Splunk側を見てみましょう。あっさり取り込めていますね。すごい！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1218403/370392b9-d099-fb48-ea87-73dc0017da72.png)

7.さて、Hello Worldできたところで今回の本題である「好きなRESTを叩いてそのデータを送る」に取り掛かりたいと思います。
先ほどスルーしたスクリプト部分を見てみましょう。
「コード」タブのindex.jsをダブルクリックで開きます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1218403/ec5b2bf6-4fa4-2f1f-c077-bc66ebce869a.png)

重要なところはexports.handler内です。コメントで使用方法が書かれています。

```javascript:index.js
exports.handler = (event, context, callback) => {
    console.log('Received event:', JSON.stringify(event, null, 2));

    // Log JSON objects to Splunk
    logger.log(event);

    // Log JSON objects with optional 'context' argument (recommended)
    // This adds valuable Lambda metadata including functionName as source, awsRequestId as field
    logger.log(event, context);

    // Log strings
    logger.log(`value1 = ${event.key1}`, context);

    // Log with user-specified timestamp - useful for forwarding events with embedded
    // timestamps, such as from AWS IoT, AWS Kinesis, AWS CloudWatch Logs
    // Change "Date.now()" below to event timestamp if specified in event payload
    logger.logWithTime(Date.now(), event, context);

    // Advanced:
    // Log event with user-specified request parameters - useful to set input settings per event vs token-level
    // Full list of request parameters available here:
    // http://docs.splunk.com/Documentation/Splunk/latest/RESTREF/RESTinput#services.2Fcollector
    logger.logEvent({
        time: Date.now(),
        host: 'serverless',
        source: `lambda:${context.functionName}`,
        sourcetype: 'httpevent',
        event: event,
    });

    // Send all the events in a single batch to Splunk
    logger.flushAsync((error, response) => {
        if (error) {
            callback(error);
        } else {
            console.log(`Response from Splunk:\n${response}`);
            callback(null, event.key1); // Echo back the first key value
        }
    });
};
```


要は`logger.log()`、`logger.logWithTime()`もしくは`logger.logEvent()`で送信データを整え、`logger.flushAsync()`で送信するようです。
また、サンプルでは`event`を引数として渡していますが、これはLambdaをAPI GatewayやKinesisなどで起動するときに渡せるデータのようです。
つまりAWS内で発生したデータもKinesisがLambdaに渡し、Lambdaで処理を加えてからSplunkに送信できるということですが、今回は自力でデータを取ってもらうので無視します。

それでは適当なREST APIでデータを取ってみましょう。

ところで国際宇宙ステーション（ISS）が今どこを飛んでいるか気になっていませんか？
ちょうど良かったです。そんなあなたのためにREST APIが用意されています！
[International Space Station Current Location](http://open-notify.org/Open-Notify-API/ISS-Location-Now/)

これを使ってデータ取得と送信のスクリプトを作ってみましょう。
できたものがこちらです。

```javascript

exports.handler = (event, context, callback) => {
    
    let http = require('http');
    const url = 'http://api.open-notify.org/iss-now.json';

    // REST APIでデータ取得
    http.get(url, (res) => {
        let body = '';
        res.setEncoding('utf8');
        
        // データをbodyに蓄積していく
        res.on('data', (chunk) => {
           body += chunk; 
        });
        
        // 蓄積完了したら処理
        res.on('end', (res) => {
            // bodyをJSON Objectに変換
            res = JSON.parse(body);
            console.log('Received event:', res);
            
            // 送信データを組み立て
            logger.logEvent({
                time: res.timestamp, // イベントの時刻。もしデータ内に時刻がなければDate.now()で現在時刻を渡す。
                host: 'serverless', // お好みのhost
                source: `lambda:${context.functionName}`, // お好みのsource
                sourcetype: 'iss', // お好みのsourcetype
                event: res, // 取得したデータをObject化したresを渡す。
                index: aws_lambda, // お好みのindex。HEC側で指定されていれば不要。
            });
            
            // HECにデータ送信
            logger.flushAsync((error, response) => {
                if (error) {
                    callback(error);
                } else {
                    console.log(`Response from Splunk:\n${response}`);
                    callback(null, event.key1); // Echo back the first key value
                }
            });
        });
    }).on('error', (e) => {
        console.log(e.message);
    });
};
```

注意する点としては`logger.logEvent()`でしょうか。
お手軽に`logger.log()`でもいいのですが、今回はタイムスタンプやsourcetypeを指定するためこちらを使用してみました。

```javascript
            // 送信データを組み立て。
            logger.logEvent({
                time: res.timestamp, // イベントの時刻。もしデータ内に時刻がなければDate.now()で現在時刻を渡す。
                host: 'serverless', // お好みのhost
                source: `lambda:${context.functionName}`, // お好みのsource
                sourcetype: 'iss', // お好みのsourcetype
                event: res, // 取得したデータをObject化したresを渡す。
                index: aws_lambda, // お好みのindex。HEC側で指定されていれば不要。
            });
```

さて、これを実行してみます。[Test] ボタンからテストできます。 [Deploy] で更新内容を反映してからテストしてみましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1218403/3346f47e-14f8-0f65-5d60-e037fe9ce5e7.png)

Splunkで見てみましょう。無事取り込めました。すごい！
いい感じに動いたので、次はこのスクリプトを定期実行したいと思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1218403/e103d06e-5ee3-8059-21e6-eb4b71d562a8.png)

##3. CloudWatchでLambdaを定期実行化
Lambdaの定期実行ってどうするんだろと思って調べたところ、CloudWatchでcron実行できるみたいです。
早速設定してみましょう。
1. 設定 > トリガーから [トリガーを追加] をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1218403/68b7f490-09f5-a135-cb32-c06651f382c2.png)
2. EventBridge (CloudWatch Events) を選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1218403/a1b29819-a956-e6ae-ff42-431228082429.png)
3. ルールタイプで「スケジュール式」を選択。cronでもいいですが、rate式というのがすごく分かりやすい。とりあえず5分ごとに実行するように設定。※ISSのRESTは最短5秒ごとの制限。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1218403/ceefd9a6-5f96-d4cd-e038-93180226ad88.png)
4. [追加] をクリックすれば定期実行化が完了

これで5分ごとにISSの位置情報を取得してSplunkに送ってくれるようになりました。
ちょっと待って、Splunkを見てみましょう・・・きっちり5分ごとに取れていますね。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1218403/7662799f-a120-d019-24fe-902c858c6f2b.png)


せっかくなので[Realtime Location Tracker](https://splunkbase.splunk.com/app/3164/)を使って移動履歴をトラッキングしてみましょう。

```conf
index="aws_lambda"
| eval id = 1
| table _time, iss_position.latitude, iss_position.longitude, id
```

ええ、、、速すぎない？何か間違えた、、、？
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1218403/c859ac83-590f-50d1-1f38-8334cdc26aa3.png)

と思ったら多分正しそうです。
[地上から約400kmの上空を秒速約7.7km（時速約27,700km）で飛行していて、地球を約90分で1周、1日で約16周します。](https://fanfun.jaxa.jp/faq/detail/49.html)

#まとめ

- ロジックだけ気にすればいいのでサーバーレスは便利。Lambdaの実行状況はCloudTrailのログ取り込みで可視化できるようです（今回は割愛）。
- RESTでデータを取りたいけどAdd-onが無い場合、AWS Lambdaも有力な選択肢になりますね。