ちょっと話題の記事

CloudWatch Logs Subscriptionsを利用したZabbixへのログ転送

2015.06.15

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

先日、CloudWatch Logs Subscriptionsという新機能の発表がありました。

CloudWatch Logsに記録されたログイベントをリアルタイムにKinesisへ投入する機能です。

これまでは、ログイベントを取得する場合、CloudWatch LogsのGetLogEvents APIにアクセスし一括でログを取得するしかありませんでした。 そして、このGetLogEvents APIには1アカウントあたり秒間10リクエストまで(上限緩和可)という制限があり、多くの種類のログを常に取得し続けたい場合は注意が必要でした。

今回、CloudWatch LogsからKinesisにログイベントを投入可能になったことにより、Kinesisストリームをポーリングするだけで常に最新のデータを取得できるようになりました。

また、KinesisはLambdaのイベントソースとしても利用できるので、CloudWatch Logsのログをリアルタイムに処理することも可能です。

Fluentdを利用されている方は、CloudWatch LogsのLog Agentをin_tailプラグインとして、そしてLambdaをアウトプットプラグインとして利用できると考えたらわかりやすいかもしれません。

今回は、ApacheのアクセスログをLog AgentでCloudWatch Logsにアップロードし、それをKinesis、Lambda経由でZabbixに転送してみました。

cwlogs-to-zabbix_outline

CloudWatch Logsの設定

上図の左部分、Log Agentを利用したログアップロードを設定します。

なお、CloudWatch Logsについて詳しく知りたい方はこちらの記事を参照ください。 Amazon CloudWatch Logsでログファイルを監視する Amazon CloudWatch Logsによるログの収集とフィルタとアラーム設定 CloudWatch LogsでAmazonLinux上のApacheエラーログを監視する

EC2上にウェブサーバを構築します。CloudWatch Logsにログをアップロードするため適切なIAM Roleを付与してください。(上記記事の1つ目を参照ください。)。 なお、今回の例ではオレゴンリージョン(us-west-2)を利用しています。

ログストリーム名にサーバホスト名を利用するので、ホスト名を指定します。

[ec2-user@web01 ~]$ cat /etc/sysconfig/network
NETWORKING=yes
HOSTNAME=web01
NOZEROCONF=yes
NETWORKING_IPV6=no
IPV6INIT=no
IPV6_ROUTER=no
IPV6_AUTOCONF=no
IPV6FORWARDING=no
IPV6TO4INIT=no
IPV6_CONTROL_RADVD=no

[ec2-user@web01 ~]$ sudo hostname web01

ApacheウェブサーバとLog Agentをインストールし、Log Agentの設定を行います。

# インストール
[ec2-user@web01 ~]$ sudo yum install -y httpd awslogs
(省略)

# Log Agent設定
[ec2-user@web01 ~]$ sudo cat /etc/awslogs/awscli.conf
[plugins]
cwlogs = cwlogs
[default]
region = us-west-2

[ec2-user@web01 ~]$ sudo cat /etc/awslogs/awslogs.conf
[general]
# Path to the CloudWatch Logs agent's state file. The agent uses this file to maintain
# client side state across its executions.
state_file = /var/lib/awslogs/agent-state

[/var/log/httpd/access_log]
datetime_format = %d/%b/%Y:%H:%M:%S %z
timezone = UTC
file = /var/log/httpd/access_log
buffer_duration = 5000
log_stream_name = {hostname}_{instance_id}
initial_position = start_of_file
log_group_name = httpd_access_log

# ApacheとLog Agent起動
[ec2-user@web01 ~]$ sudo chkconfig httpd on
[ec2-user@web01 ~]$ sudo chkconfig awslogs on
[ec2-user@web01 ~]$ sudo service httpd start
Starting httpd: httpd: apr_sockaddr_info_get() failed for web01
httpd: Could not reliably determine the server's fully qualified domain name, using 127.0.0.1 for ServerName
                                                           [  OK  ]
[ec2-user@web01 ~]$ sudo service awslogs start
Starting awslogs:                                          [  OK  ]

# 動作確認
[ec2-user@web01 ~]$ curl localhost > /dev/null
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  3839  100  3839    0     0   447k      0 --:--:-- --:--:-- --:--:--  468k
[ec2-user@web01 ~]$ cat /var/log/awslogs.log
2015-06-14 19:20:54,028 - cwlogs.push - INFO - 2667 - MainThread - Using default logging configuration.
2015-06-14 19:21:19,035 - cwlogs.push.stream - INFO - 2667 - Thread-1 - Starting publisher for [bb5ddbfd22a823067fbf1d8b231f40fc, /var/log/httpd/access_log]
2015-06-14 19:21:19,043 - cwlogs.push.stream - INFO - 2667 - Thread-1 - Starting reader for [bb5ddbfd22a823067fbf1d8b231f40fc, /var/log/httpd/access_log]
2015-06-14 19:21:19,044 - cwlogs.push.reader - INFO - 2667 - Thread-4 - Start reading file from 0.
2015-06-14 19:21:25,100 - cwlogs.push.publisher - WARNING - 2667 - Thread-3 - Caught exception: An error occurred (ResourceNotFoundException) when calling the PutLogEvents operation: The specified log group does not exist.
2015-06-14 19:21:25,101 - cwlogs.push.batch - INFO - 2667 - Thread-3 - Creating log group httpd_access_log.
2015-06-14 19:21:25,150 - cwlogs.push.batch - INFO - 2667 - Thread-3 - Creating log stream web01_i-e3f6082b.
2015-06-14 19:21:25,274 - cwlogs.push.publisher - INFO - 2667 - Thread-3 - Log group: httpd_access_log, log stream: web01_i-e3f6082b, queue size: 0, Publish batch: {'skipped_events_count': 0, 'first_event': {'timestamp': 1434309676000, 'start_position': 0L, 'end_position': 87L}, 'fallback_events_count': 0, 'last_event': {'timestamp': 1434309676000, 'start_position': 0L, 'end_position': 87L}, 'source_id': 'bb5ddbfd22a823067fbf1d8b231f40fc', 'num_of_events': 1, 'batch_size_in_bytes': 112}
2015-06-14 19:21:52,360 - cwlogs.push.publisher - INFO - 2667 - Thread-3 - Log group: httpd_access_log, log stream: web01_i-e3f6082b, queue size: 0, Publish batch: {'skipped_events_count': 0, 'first_event': {'timestamp': 1434309706000, 'start_position': 87L, 'end_position': 174L}, 'fallback_events_count': 0, 'last_event': {'timestamp': 1434309706000, 'start_position': 87L, 'end_position': 174L}, 'source_id': 'bb5ddbfd22a823067fbf1d8b231f40fc', 'num_of_events': 1, 'batch_size_in_bytes': 112}

Agent Logのログを見る限りちゃんと動いていそうです。 マネジメントコンソールでCloudWatch Logsのログイベントを確認してみましょう。

スクリーンショット 2015-06-14 12.25.48

正常にログイベントがアップロードされています。

Agent Logの設定は下記のようにしました。

  • log_stream_name:{hostname}_{instance_id}
  • log_group_name:httpd_access_log

ログストリームは、同じソースを共有する一連のログイベントです。 そして、ロググループは、保持、監視、アクセス制御について同じ設定を共有するログストリームのグループです。各ログストリームは、1 つのロググループに属している必要があります。

CloudWatch Logs Subscriptionsによるデータ投入もロググループ単位で行われます。

ログストリーム名は各ウェブサーバで異なる値を指定し、ロググループ名は共通の値httpd_access_logを指定します。 後半で、Zabbixにログデータを転送するのですが、その時、Zabbixホスト名にはログストリーム名を、Zabbixアイテムキーにはロググループ名を対応させています。

Kinesisストリーム作成

CloudWatch Logsの設定はできたのでログの投入先であるKinesisストリームを作成しましょう。 aws-cliが利用でき、適切な権限があるマシン上で実行してください。 今回は先ほどのweb01に付与したIAM RoleにKinesisやIAMの操作権限を追加して作業しました。

[localhost ~]$ aws kinesis create-stream --stream-name "AccessLogStream" --shard-count 1 --region us-west-2
[ec2-user@web01 ~]$ aws kinesis create-stream --stream-name "AccessLogStream" --shard-count 1 --region us-west-2
[ec2-user@web01 ~]$ aws kinesis describe-stream --stream-name "AccessLogStream" --region us-west-2
{
    "StreamDescription": {
        "StreamStatus": "ACTIVE",
        "StreamName": "AccessLogStream",
        "StreamARN": "arn:aws:kinesis:us-west-2:{your-account-id}:stream/AccessLogStream",
        "Shards": [
            {
                "ShardId": "shardId-000000000000",
                "HashKeyRange": {
                    "EndingHashKey": "340282366920938463463374607431768211455",
                    "StartingHashKey": "0"
                },
                "SequenceNumberRange": {
                    "StartingSequenceNumber": "49551636815970121886867452509971134500694293938498961410"
                }
            }
        ]
    }
}

AccessLogStreamという名前のストリームを作成しました。 StreamARNは後ほど利用するので控えておきます。

CloudWatch Logs Subscriptions設定

それではCloudWatch LogsのログイベントがKinesisストリームに投入されるよう設定していきます。

まず、CloudWatch Logs用のIAM Roleを作成します。Kinesisへのデータ投入権限を付与するためです。

# Assume Role可能なCloudWatch Logs用Roleを作成
[ec2-user@logtest_i-a53fc96d ~]$ cat ~/TrustPolicyForCWL.json
[ec2-user@web01 ~]$ cat TrustPolicyForCWL.json
{
  "Statement": {
    "Effect": "Allow",
    "Principal": { "Service": "logs.us-west-2.amazonaws.com" },
    "Action": "sts:AssumeRole"
  }
}
[ec2-user@web01 ~]$ aws iam create-role \
--role-name CWLtoKinesisRole \
--assume-role-policy-document file://~/TrustPolicyForCWL.json
{
    "Role": {
        "AssumeRolePolicyDocument": {
            "Statement": {
                "Action": "sts:AssumeRole",
                "Effect": "Allow",
                "Principal": {
                    "Service": "logs.us-west-2.amazonaws.com"
                }
            }
        },
        "RoleId": "AROAIRHSRC6Q6ANXELY3U",
        "CreateDate": "2015-06-14T20:03:42.258Z",
        "RoleName": "CWLtoKinesisRole",
        "Path": "/",
        "Arn": "arn:aws:iam::{your-account-id}:role/CWLtoKinesisRole"
    }
}

# 作成したRoleにKinesisストリームへのレコード投入権限を付与
[ec2-user@web01 ~]$ cat PermissionsForCWL.json
{
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "kinesis:PutRecord",
      "Resource": "arn:aws:kinesis:us-west-2:{your-account-id}:stream/AccessLogStream"
    },
    {
      "Effect": "Allow",
      "Action": "iam:PassRole",
      "Resource": "arn:aws:iam::{your-account-id}:role/CWLtoKinesisRole"
    }
  ]
}
[ec2-user@web01 ~]$ aws iam put-role-policy \
--role-name CWLtoKinesisRole \
--policy-name Permissions-Policy-For-CWL \
--policy-document file://~/PermissionsForCWL.json

ResourceやArnの値は実際のものに書き換えてください。

では、CloudWatch Logs Subscriptionsの設定を行います。 aws-cliのバージョンが1.7.32以上である必要があるので、バージョンが低い場合はアップデートしてください。

# aws-cliバージョンアップ
[ec2-user@web01 ~]$ aws --version
aws-cli/1.7.31 Python/2.7.9 Linux/3.14.35-28.38.amzn1.x86_64
[ec2-user@web01 ~]$ sudo pip install --upgrade awscli
(省略)
[ec2-user@web01 ~]$ aws --version
aws-cli/1.7.34 Python/2.7.9 Linux/3.14.35-28.38.amzn1.x86_64

# CloudWatch Logs Subscriptions設定
[ec2-user@web01 ~]$ aws logs put-subscription-filter \
--log-group-name "httpd_access_log" \
--filter-name "AccessLogFilter" \
--filter-pattern "" \
--destination-arn "arn:aws:kinesis:us-west-2:{your-account-id}:stream/AccessLogStream" \
--role-arn "arn:aws:iam::{your-account-id}:role/CWLtoKinesisRole" \
--region us-west-2
[ec2-user@web01 ~]$ aws logs describe-subscription-filters --log-group-name 'httpd_access_log' --region us-west-2
{
    "subscriptionFilters": [
        {
            "filterPattern": "",
            "filterName": "AccessLogFilter",
            "roleArn": "arn:aws:iam::{your-account-id}:role/CWLtoKinesisRole",
            "creationTime": 1434313096456,
            "logGroupName": "httpd_access_log",
            "destinationArn": "arn:aws:kinesis:us-west-2:{your-account-id}:stream/AccessLogStream"
        }
    ]
}

今回の例ではログイベントのフィルタリングは行っていません。 特定の条件に合うログイベントのみストリームに投入したい場合はフィルタリングパターンを指定してください。 フィルタとパターンの構文 動作確認してみましょう。

[ec2-user@web01 ~]$ SHARD_ITERATOR=$(aws kinesis get-shard-iterator --shard-id shardId-000000000000 --shard-iterator-type LATEST --stream-name AccessLogStream --query 'ShardIterator' --region us-west-2);
[ec2-user@web01 ~]$ aws kinesis get-records --shard-iterator $SHARD_ITERATOR  --region us-west-2
{
    "Records": [],
    "NextShardIterator": "AAAAAAAAAAHIVuAIrsZWrJDsc6is3SeNfY8t+mjt5nS3wvbbvoydBUoIsi8q5NeJPPjLIDsYvzLFgLk4R58ewka9v4yapQ2q/3GNG/nDnf1I0qSg+7yLNF9vYa4rj0YYNrNs6b2lanT3oCrtc7QMaRjD0XbXxHtEgqjnDvEYnCdB41uQAH331F1EIOZlLRsx/grhipZbeuAHJNL5yqorOoxV/5jitaOf",
    "MillisBehindLatest": 0
}
[ec2-user@web01 ~]$ curl localhost/subscription-test > /dev/null 2>&1
[ec2-user@web01 ~]$ sleep 10
[ec2-user@web01 ~]$ aws kinesis get-records --shard-iterator $SHARD_ITERATOR  --region us-west-2
{
    "Records": [
        {
            "PartitionKey": "{your-account-id}:httpd_access_log:web01_i-e3f6082b",
            "Data": "H4sIAAAAAAAAAE2QUWuDMBSF/0rI62rNvbma6Jsw1zE2GNS3WsTarBNaFY0ro/S/765lMEIecr7knHNzkSc3TfXBFd+Dk6l8zIqsesvX62yVy4Xsz50bWdYmIgOYJLE2LB/7w2rs54HJp/fDvqqbhm0q1u907UdXnxif3U5B1QZOf8TK4o7xNO+mZmwH3/bdU3v0bpxkupHZzeK1P9w1ub0Z5V+u87/8Itv9bxFIbIwmJtQQk6VIkUVCC9ZGHAARb42RUgk31kQYEZcmy7m+5VF9feLWwEgDIYFSavH3BWwPaJaKF4iA1wYofJm7EBVEKapUmxRBPPAbtRWlXOWFCP9PE3gOEM9F8R7CEkopSJHARPHdgE+lbObxGJolcUQp5XV7/QGm9mytfwEAAA==",
            "SequenceNumber": "49551636815970121886867453709884412130096584687576154114"
        }
    ],
    "NextShardIterator": "AAAAAAAAAAGfpEC1o+Hhi2PBIhdoK+bxqOQA8oGTxtai3kyxJbM6JS5Qe9e6o5ufx4PYR4e/TaPNWRdfqoLDOlSu+vuW4Ar9idEBNqFzqVQWcIwfFwG0UfqXwmUcNEraJ3MXjwqBbKkjSUn1GqR7HvzZtlgGXI3+MyV5x/9jFjHJd+fJdUhOspky82qk47QJOaj+BDRhkCSZihmydvj1QUTP7OcBv/V6",
    "MillisBehindLatest": 6000
}

AccessLogStreamにデータが投入されていることが確認できます。 データはgzip圧縮されたのちBase64エンコードされているので元に戻して内容を確認してみましょう。

[ec2-user@web01 ~]$ echo "H4sIAAAAAAAAAE2QUWuDMBSF/0rI62rNvbma6Jsw1zE2GNS3WsTarBNaFY0ro/S/765lMEIecr7knHNzkSc3TfXBFd+Dk6l8zIqsesvX62yVy4Xsz50bWdYmIgOYJLE2LB/7w2rs54HJp/fDvqqbhm0q1u907UdXnxif3U5B1QZOf8TK4o7xNO+mZmwH3/bdU3v0bpxkupHZzeK1P9w1ub0Z5V+u87/8Itv9bxFIbIwmJtQQk6VIkUVCC9ZGHAARb42RUgk31kQYEZcmy7m+5VF9feLWwEgDIYFSavH3BWwPaJaKF4iA1wYofJm7EBVEKapUmxRBPPAbtRWlXOWFCP9PE3gOEM9F8R7CEkopSJHARPHdgE+lbObxGJolcUQp5XV7/QGm9mytfwEAAA==" | base64 -d | zcat | jq .
{
  "messageType": "DATA_MESSAGE",
  "owner": "{your-account-id}",
  "logGroup": "httpd_access_log",
  "logStream": "web01_i-e3f6082b",
  "subscriptionFilters": [
    "AccessLogFilter"
  ],
  "logEvents": [
    {
      "id": "31986276423164845048242818856081508132500975434425499648",
      "timestamp": 1434314241000,
      "message": "127.0.0.1 - - [14/Jun/2015:20:37:21 +0000] \"GET /subscription-test HTTP/1.1\" 404 290 \"-\" \"curl/7.40.0\""
    }
  ]
}

問題ないようです。

このログイベントをZabbixに送るのが今回の目的です。 この例だと、

  • Zabbixホスト名: web01_i-e3f6082b(ログストリーム名)
  • Zabbixアイテムキー: httpd_access_log(ロググループ名)
  • Zabbixアイテム値: 127.0.0.1 - - [14/Jun/2015:20:37:21 +0000] \"GET /subscription-test HTTP/1.1\" 404 290 \"-\" \"curl/7.40.0\"

で送る想定です。

下図の赤枠で囲まれたところまでが設定済みですので、次にZabbix側の設定を行いましょう。

cwlogs-to-zabbix_until-kinesis

Zabbix設定

web01_i-e3f6082bという名前のホストを作成し、下記のようなアイテムを登録します。

スクリーンショット 2015-06-14 14.14.55

  • タイプ: Zabbixトラッパー
  • アイテムキー: httpd_access_log
  • データ型: テキスト

これでログイベントのデータソースと転送先の準備ができたので、最後にLambdaで繋ぎましょう。

Lambda作成

Lambdaが受け取るイベントのフォーマットは下記のようになっています。

{
    "Records": [
        {
            "kinesis": {
                "kinesisSchemaVersion": "1.0",
                "PartitionKey": "{your-account-id}:httpd_access_log:web01_i-e3f6082b",
                "SequenceNumber": "49551636815970121886867453709884412130096584687576154114",
                "Data": "H4sIAAAAAAAAAE2QUWuDMBSF/0rI62rNvbma6Jsw1zE2GNS3WsTarBNaFY0ro/S/765lMEIecr7knHNzkSc3TfXBFd+Dk6l8zIqsesvX62yVy4Xsz50bWdYmIgOYJLE2LB/7w2rs54HJp/fDvqqbhm0q1u907UdXnxif3U5B1QZOf8TK4o7xNO+mZmwH3/bdU3v0bpxkupHZzeK1P9w1ub0Z5V+u87/8Itv9bxFIbIwmJtQQk6VIkUVCC9ZGHAARb42RUgk31kQYEZcmy7m+5VF9feLWwEgDIYFSavH3BWwPaJaKF4iA1wYofJm7EBVEKapUmxRBPPAbtRWlXOWFCP9PE3gOEM9F8R7CEkopSJHARPHdgE+lbObxGJolcUQp5XV7/QGm9mytfwEAAA=="
            },
            "eventSource": "aws:kinesis",
            "eventVersion": "1.0",
            "eventID": "shardId-000000000000:49551516247794032858200542565772891528151132064570146818",
            "eventName": "aws:kinesis:record",
            "invokeIdentityArn": "arn:aws:iam::{your-account-id}:role/lambda_kinesis_role",
            "awsRegion": "us-west-2",
            "eventSourceARN": "arn:aws:kinesis:us-west-2:{your-account-id}:stream/AccessLogStream"
        }
    ]
}

このデータを加工し、Zabbix Senderと同じ要領でZabbixにログを転送します。 参考までにLambdaのコードをあげておきます。 Zabbixに送るデータのフォーマットはドキュメントを参考にしました。 「ZabbixAddress」の部分は実際のZabbixサーバのアドレスを入れてください。

var async = require('async');
var net = require('net');
var zlib = require('zlib');

var zabbixAddress = <<IP or Host>>;
var zabbixPort = '10051';
var maxLog = 10;

var extractMessage = function (record, callback) {
  var buffer = new Buffer(record.kinesis.data, 'base64');
  zlib.unzip(buffer, function(err, buffer) {
    if (err) {
      console.log(err);
    } else {
      callback(null, JSON.parse(buffer.toString('utf-8')));
    }
  });
};

exports.handler = function(event, context) {
  console.log("Event: %j", event);
  async.waterfall(
    [
      // extract log messages
      function (callback) {
        async.map (event.Records,
                    extractMessage,
                    function(err, messages) {
                      callback(null, messages);
                    }
                  );
      },
      // send logs to Zabbix
      function (messages, callback) {
        async.every(messages,
          function (message, cb1) {
            var logSets = [];
            for (var logs = message.logEvents; logs.length > 0;) {
              logSets.push(logs.splice(0, maxLog));
            }
            async.every(logSets,
              function (logSet, cb2) {
                // message format is:
                // https://www.zabbix.org/wiki/Docs/protocols/zabbix_sender/2.0
                var zabbixMessage = {
                  request: "sender data",
                  data: logSet.map(function(log, index, logs) {
                     return {
                               host: message.logStream,
                               key: message.logGroup,
                               value: log.message,
                               clock: log.timestamp / 1000
                            };
                  }),
                  clock: Math.floor(new Date().getTime() / 1000)
                };
                var client = net.connect({host: zabbixAddress, port: zabbixPort},
                  function() {
                    client.write(JSON.stringify(zabbixMessage));
                  }
                );
                client.on('data', function(data) {
                  console.log(data.toString('utf-8'));
                  client.end();
                });
                client.on('end', function() {
                  cb2(true);
                });
              },
              function(result) {
                cb1(true);
              }
            );
          },
          function(result) {
            callback(null, result);
          }
        );
      }
    ],
    function (err, result) {
      if (err) {;context.done('error', err);}
      else {context.done(null, result);}
    }
  );
}

Lambdaファンクションを登録します。

CloudWatch Logs Subscriptionsの時と同様、LambdaにもKinesisアクセス用のIAM Roleが必要なので、まず、Roleを作成し、その後にLambdaファンクションを作成します。

# Lambda用Role作成
[ec2-user@web01 ~]$ cat TrustPolicyForLambda.json
{
  "Statement": {
    "Effect": "Allow",
    "Principal": {
      "Service": "lambda.amazonaws.com"
    },
    "Action": "sts:AssumeRole"
  }
}
[ec2-user@web01 ~]$ aws iam create-role \
--role-name lambda_kinesis_role \
--assume-role-policy-document file://~/TrustPolicyForLambda.json


# 作成したRoleにLambdaの起動権限、Kinesisの参照権限、CloudWatch Logsへの書き込み権限を付与
[ec2-user@web01 ~]$ cat PermissionsForLambda.json
{
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "lambda:InvokeFunction"
      ],
      "Resource": [
        "*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "kinesis:GetRecords",
        "kinesis:GetShardIterator",
        "kinesis:DescribeStream",
        "kinesis:ListStreams",
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "*"
    }
  ]
}
[ec2-user@web01 ~]$ aws iam put-role-policy \
--role-name lambda_kinesis_role \
--policy-name lambda_kinesis_role_policy \
--policy-document file://~/PermissionsForLambda.json


[ec2-user@web01 ~]$ git clone https://github.com/yokota-shinsuke/aws-lambda-cloudwatchlogs-to-zabbix
(省略)

[ec2-user@web01 ~]$ cd aws-lambda-cloudwatchlogs-to-zabbix/
# Zabbixサーバのアドレスを記入
[ec2-user@web01 aws-lambda-cloudwatchlogs-to-zabbix]$ vi cloudWatchLogsToZabbix.js

# Zipファイル作成
[ec2-user@web01 aws-lambda-cloudwatchlogs-to-zabbix]$ npm install
(省略)
[ec2-user@web01 aws-lambda-cloudwatchlogs-to-zabbix]$ zip -r cloudWatchLogsToZabbix.zip cloudWatchLogsToZabbix.js node_modules/

# Lambdaファンクション作成
[ec2-user@web01 aws-lambda-cwlogs-to-zabbix]$  aws lambda create-function \
    --function-name "cloudWatchLogsToZabbix" \
    --runtime nodejs\
    --role arn:aws:iam::{your-account-id}:role/lambda_kinesis_role\
    --handler "cloudWatchLogsToZabbix.handler"\
    --timeout 60\
    --zip-file "fileb://cloudWatchLogsToZabbix.zip"\
    --region us-west-2
{
    "FunctionName": "cloudWatchLogsToZabbix",
    "CodeSize": 130509,
    "MemorySize": 128,
    "FunctionArn": "arn:aws:lambda:us-west-2:{your-account-id}:function:cloudWatchLogsToZabbix",
    "Handler": "cloudWatchLogsToZabbix.handler",
    "Role": "arn:aws:iam::{your-account-id}:role/lambda_kinesis_role",
    "Timeout": 60,
    "LastModified": "2015-06-14T22:01:51.458+0000",
    "Runtime": "nodejs",
    "Description": ""
}

# LambdaのイベントソースにKinesisストリームを指定
[ec2-user@web01 ~]$ aws lambda create-event-source-mapping \
    --event-source-arn arn:aws:kinesis:us-west-2:{your-account-id}:stream/AccessLogStream \
    --function-name cloudWatchLogsToZabbix \
    --starting-position TRIM_HORIZON \
    --region us-west-2
{
    "UUID": "088f5166-09eb-462a-8528-8ef12c7336d5",
    "StateTransitionReason": "User action",
    "LastModified": 1434320037.056,
    "BatchSize": 100,
    "EventSourceArn": "arn:aws:kinesis:us-west-2:{your-account-id}:stream/AccessLogStream",
    "FunctionArn": "arn:aws:lambda:us-west-2:{your-account-id}:function:cloudWatchLogsToZabbix",
    "State": "Creating",
    "LastProcessingResult": "No records processed"
}

これで、CloudWatch LogsのログイベントがZabbixまで転送されるようになったはずです。

動作確認のため、500リクエストほど発行してみます。

[ec2-user@web01 ~]$ ab -c 20 -n 500 localhost/zabbix-test
(省略)

Zabbixのアイテムヒストリーで確認すると500件のログが登録されていました。

スクリーンショット 2015-06-14 15.20.47

まとめ

CloudWatch Logs Subscriptionsを使って、CloudWatch LogsのログイベントをZabbixに転送されるようにしてみました。 Zabbixの場合は、Zabbix Agentを利用して手軽にログ監視できるので、あまり意味はないです。

ただ、同様のやり方でログイベントをリアルタイムに他のシステムに転送できるので、例えばNorikraやKibanaなどと組み合わせることでログを様々な用途に活用できるようになります。 Kinesisストリームは複数のLambdaファンクションのイベントソースに指定できます。なので、同一のログを同時にリアルタイムに監視、分析、可視化することも可能です。

cwlogs-to-zabbix_until-multi