Elasticsearchは、広範囲のデータ内の情報をすばやく検索するように設計された強力なソフトウェアソリューションです。 LogstashおよびKibanaと組み合わせると、これは非公式に名前が付けられたものになります 「任意のスタック」 、およびログデータの収集、一時的な保存、分析、および視覚化によく使用されます。通常、他のいくつかのソフトウェアが必要です。 Filebeat サーバーからLogstashにログを送信し、 Elastalert Elasticsearchに保存されているデータに対して実行された分析の結果に基づいてアラートを生成します。
ログの管理にELKを使用した私の経験はかなり複雑です。一方で、それは非常に強力であり、その機能の範囲は非常に印象的です。一方、設定は難しく、維持するのが頭痛の種になる可能性があります。
事実、Elasticsearchは一般的に非常に優れており、さまざまなシナリオで使用できます。検索エンジンとしても使えます!ログデータの管理に特化していないため、このようなデータを管理する特定のニーズに合わせて動作をカスタマイズするには、より多くの構成作業が必要になります。
ELKクラスターのセットアップは非常にトリッキーで、最終的に起動して実行するために、いくつかのパラメーターを試してみる必要がありました。次に、それを構成する作業が行われました。私の場合、構成するソフトウェアは5つありました(Filebeat、Logstash、Elasticsearch、Kibana、Elastalert)。ドキュメントを読み、次の要素と通信しないチェーンの1つの要素をデバッグする必要があったため、これは非常に面倒な作業になる可能性があります。最終的にクラスターを起動して実行した後でも、パッチの適用、OSパッケージのアップグレード、CPU、RAM、ディスクの使用状況の確認、必要に応じた微調整など、クラスターの定期的なメンテナンス操作を実行する必要があります。
Logstashの更新後、ELKスタック全体が機能しなくなりました。よく調べてみると、何らかの理由で、ELK開発者は設定ファイルのキーワードを変更して複数形にすることにしました。それが最後の藁であり、より良い解決策を探すことにしました(少なくとも私の特定のニーズのためのより良い解決策)。
ApacheとさまざまなPHPおよびノードアプリによって生成されたログを保存し、それらを解析してソフトウェアのバグを示すパターンを見つけたいと思いました。私が見つけた解決策は次のとおりです。
そして、大まかに言えば、それだけです!メンテナンスを必要とせずに正常に動作し、追加の作業なしで適切に拡張できる100%サーバーレスソリューション。サーバーのクラスターに対するこのようなサーバーレスソリューションの利点は数多くあります。
あなた自身のプライベートエクイティファンドを始める方法
それでは、詳細を見ていきましょう。エンジニアに警告するためのSlackWebhookを備えた、このようなセットアップでCloudFormationテンプレートがどのように表示されるかを見てみましょう。最初にすべてのSlackセットアップを構成する必要があるので、詳しく見ていきましょう。
AWSTemplateFormatVersion: 2010-09-09 Description: Setup log processing Parameters: SlackWebhookHost: Type: String Description: Host name for Slack web hooks Default: hooks.slack.com SlackWebhookPath: Type: String Description: Path part of the Slack webhook URL Default: /services/YOUR/SLACK/WEBHOOK
このためにSlackワークスペースを設定する必要があります。チェックアウトしてください このWebHooksforSlackガイド 追加情報については。
Slackアプリを作成し、着信フックを構成すると、フックURLがCloudFormationスタックのパラメーターになります。
Resources: ApacheAccessLogGroup: Type: AWS::Logs::LogGroup Properties: RetentionInDays: 100 # Or whatever is good for you ApacheErrorLogGroup: Type: AWS::Logs::LogGroup Properties: RetentionInDays: 100 # Or whatever is good for you
ここでは、2つのロググループを作成しました。 Apacheアクセスログ 、他の Apacheエラーログ 。
この記事の範囲外であるため、ログデータのライフサイクルメカニズムを構成しませんでした。実際には、保持期間を短くし、S3ライフサイクルポリシーを設計して、一定期間後にそれらをGlacierに移動することをお勧めします。
それでは、Apacheアクセスログを処理するLambda関数を実装しましょう。
BasicLambdaExecutionRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: lambda.amazonaws.com Action: sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
ここでは、Lambda関数にアタッチされて職務を実行できるようにするIAMロールを作成しました。事実上、AWSLambdaBasicExecutionRole
(その名前にもかかわらず)AWSによって提供されるIAMポリシーです。 Lambda関数がロググループとそのグループ内のログストリームを作成し、独自のログをCloudWatchLogsに送信できるようにするだけです。
ProcessApacheAccessLogFunction: Type: AWS::Lambda::Function Properties: Handler: index.handler Role: !GetAtt BasicLambdaExecutionRole.Arn Runtime: python3.7 Timeout: 10 Environment: Variables: SLACK_WEBHOOK_HOST: !Ref SlackWebHookHost SLACK_WEBHOOK_PATH: !Ref SlackWebHookPath Code: ZipFile: | import base64 import gzip import json import os from http.client import HTTPSConnection def handler(event, context): tmp = event['awslogs']['data'] # `awslogs.data` is base64-encoded gzip'ed JSON tmp = base64.b64decode(tmp) tmp = gzip.decompress(tmp) tmp = json.loads(tmp) events = tmp['logEvents'] for event in events: raw_log = event['message'] log = json.loads(raw_log) if log['status'][0] == '5': # This is a 5XX status code print(f'Received an Apache access log with a 5XX status code: {raw_log}') slack_host = os.getenv('SLACK_WEBHOOK_HOST') slack_path = os.getenv('SLACK_WEBHOOK_PATH') print(f'Sending Slack post to: host={slack_host}, path={slack_path}, url={url}, content={raw_log}') cnx = HTTPSConnection(slack_host, timeout=5) cnx.request('POST', slack_path, json.dumps({'text': raw_log})) # It's important to read the response; if the cnx is closed too quickly, Slack might not post the msg resp = cnx.getresponse() resp_content = resp.read() resp_code = resp.status assert resp_code == 200
したがって、ここでは、Apacheアクセスログを処理するためのLambda関数を定義しています。 Apacheのデフォルトである一般的なログ形式を使用していないことに注意してください。アクセスログの形式をそのように構成しました(基本的にJSONとして形式化されたログが生成されるため、処理がはるかに簡単になります)。
LogFormat '{'vhost': '%v:%p', 'client': '%a', 'user': '%u', 'timestamp': '%{%Y-%m-%dT%H:%M:%S}t', 'request': '%r', 'status': '%>s', 'size': '%O', 'referer': '%{Referer}i', 'useragent': '%{User-Agent}i'}' json
このLambda関数はPython3で記述されています。CloudWatchから送信されたログ行を受け取り、パターンを検索できます。上記の例では、5XXステータスコードを生成したHTTPリクエストを検出し、Slackチャネルにメッセージを投稿します。
パターン検出に関しては何でもできます。LogstashまたはElastalert構成ファイルの正規表現パターンではなく、真のプログラミング言語(Python)であるという事実により、複雑なパターン認識を実装する多くの機会が得られます。 。
リビジョン管理について簡単に説明します。このような小さなユーティリティLambda関数のCloudFormationテンプレートにコードをインライン化することは、非常に受け入れられやすく便利であることがわかりました。もちろん、多くのLambda関数とレイヤーを含む大規模なプロジェクトの場合、これはおそらく不便であり、SAMを使用する必要があります。
ApacheAccessLogFunctionPermission: Type: AWS::Lambda::Permission Properties: FunctionName: !Ref ProcessApacheAccessLogFunction Action: lambda:InvokeFunction Principal: logs.amazonaws.com SourceArn: !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:*
上記は、CloudWatchLogsにLambda関数を呼び出す権限を与えています。注意の一言:SourceAccount
を使用していることがわかりましたプロパティは、SourceArn
との競合につながる可能性があります。
一般的に言って、Lambda関数を呼び出しているサービスが同じAWSアカウントにある場合は、これを含めないことをお勧めします。 SourceArn
とにかく、他のアカウントがLambda関数を呼び出すことを禁止します。
ApacheAccessLogSubscriptionFilter: Type: AWS::Logs::SubscriptionFilter DependsOn: ApacheAccessLogFunctionPermission Properties: LogGroupName: !Ref ApacheAccessLogGroup DestinationArn: !GetAtt ProcessApacheAccessLogFunction.Arn FilterPattern: '{$.status = 5*}'
サブスクリプションフィルターリソースは、CloudWatchLogsとLambdaの間のリンクです。ここでは、ApacheAccessLogGroup
に送信されたログ上で定義したLambda関数に転送されますが、フィルターパターンを通過するログのみが転送されます。ここで、フィルターパターンは入力としてJSONを想定しており(フィルターパターンは「{」で始まり「}」で終わります)、フィールドstatus
がある場合にのみログエントリと一致します。 「5」で始まります。
これは、Apacheによって返されるHTTPステータスコードが500コードである場合にのみ、Lambda関数を呼び出すことを意味します。これは通常、非常に悪いことが起こっていることを意味します。これにより、Lambda関数を呼び出しすぎないようにし、不要なコストを回避できます。
フィルタパターンの詳細については、をご覧ください。 AmazonCloudWatchのドキュメント 。 CloudWatchのフィルターパターンは非常に優れていますが、Grokほど強力ではありません。
DependsOn
に注意してくださいフィールド。サブスクリプションが作成される前に、CloudWatchLogsが実際にLambda関数を呼び出すことができるようにします。これは単なるお菓子です。実際のシナリオのように、Apacheはおそらく少なくとも数秒前にリクエストを受信しないでしょう(例:EC2インスタンスをロードバランサーにリンクして負荷を取得するため)。 EC2インスタンスのステータスを正常であると認識するバランサー)。
それでは、Apacheエラーログを処理するLambda関数を見てみましょう。
ProcessApacheErrorLogFunction: Type: AWS::Lambda::Function Properties: Handler: index.handler Role: !GetAtt BasicLambdaExecutionRole.Arn Runtime: python3.7 Timeout: 10 Environment: Variables: SLACK_WEBHOOK_HOST: !Ref SlackWebHookHost SLACK_WEBHOOK_PATH: !Ref SlackWebHookPath Code: ZipFile: | import base64 import gzip import json import os from http.client import HTTPSConnection def handler(event, context): tmp = event['awslogs']['data'] # `awslogs.data` is base64-encoded gzip'ed JSON tmp = base64.b64decode(tmp) tmp = gzip.decompress(tmp) tmp = json.loads(tmp) events = tmp['logEvents'] for event in events: raw_log = event['message'] log = json.loads(raw_log) if log['level'] in ['error', 'crit', 'alert', 'emerg']: # This is a serious error message msg = log['msg'] if msg.startswith('PHP Notice') or msg.startswith('PHP Warning'): print(f'Ignoring PHP notices and warnings: {raw_log}') else: print(f'Received a serious Apache error log: {raw_log}') slack_host = os.getenv('SLACK_WEBHOOK_HOST') slack_path = os.getenv('SLACK_WEBHOOK_PATH') print(f'Sending Slack post to: host={slack_host}, path={slack_path}, url={url}, content={raw_log}') cnx = HTTPSConnection(slack_host, timeout=5) cnx.request('POST', slack_path, json.dumps({'text': raw_log})) # It's important to read the response; if the cnx is closed too quickly, Slack might not post the msg resp = cnx.getresponse() resp_content = resp.read() resp_code = resp.status assert resp_code == 200
この2番目のLambda関数は、Apacheエラーログを処理し、重大なエラーが発生した場合にのみSlackにメッセージを投稿します。この場合、PHPの通知および警告メッセージは、アラートをトリガーするほど深刻であるとは見なされません。
この場合も、この関数はApacheエラーログがJSON形式であることを想定しています。だからここに私が使っているエラーログフォーマット文字列があります:
ErrorLogFormat '{'vhost': '%v', 'timestamp': '%{cu}t', 'module': '%-m', 'level': '%l', 'pid': '%-P', 'tid': '%-T', 'oserror': '%-E', 'client': '%-a', 'msg': '%M'}'
ApacheErrorLogFunctionPermission: Type: AWS::Lambda::Permission Properties: FunctionName: !Ref ProcessApacheErrorLogFunction Action: lambda:InvokeFunction Principal: logs.amazonaws.com SourceArn: !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:* SourceAccount: !Ref AWS::AccountId
このリソースは、CloudWatchLogsにLambda関数を呼び出すためのアクセス許可を付与します。
ApacheErrorLogSubscriptionFilter: Type: AWS::Logs::SubscriptionFilter DependsOn: ApacheErrorLogFunctionPermission Properties: LogGroupName: !Ref ApacheErrorLogGroup DestinationArn: !GetAtt ProcessApacheErrorLogFunction.Arn FilterPattern: '{$.msg != 'PHP Warning*' && $.msg != 'PHP Notice*'}'
最後に、Apacheエラーロググループのサブスクリプションフィルターを使用して、CloudWatchLogsをLambda関数にリンクします。 「PHPWarning」または「PHPNotice」で始まるメッセージを含むログがLambda関数の呼び出しをトリガーしないようにするフィルターパターンに注意してください。
コストについて最後に一言:このソリューションは、ELKクラスターを運用するよりもはるかに安価です。 CloudWatchに保存されているログの価格はS3と同じレベルであり、Lambdaは無料枠の一部として月額100万回の通話を許可しています。これは、トラフィックが中程度から大量のWebサイト(CloudWatch Logsフィルターを使用している場合)にはおそらく十分です。特に、適切にコーディングし、エラーが多すぎない場合はそうです。
また、Lambda関数は最大1,000の同時呼び出しをサポートすることに注意してください。これを書いている時点では、これはAWSの厳しい制限であり、変更することはできません。ただし、上記の関数の呼び出しは約30〜40ミリ秒続くと予想できます。これは、かなり重いトラフィックを処理するのに十分な速度である必要があります。ワークロードが非常に激しく、この制限に達する場合は、Kinesisに基づくより複雑なソリューションが必要になる可能性があります。これについては、今後の記事で取り上げます。
ELKはElasticsearch-Logstash-Kibanaの頭字語です。 Beats(ログとメトリックをLogstashに送信するツールのコレクション)やElastalert(Elasticsearch時系列データに基づいてアラートを生成する)など、追加のソフトウェアアイテムが必要になることがよくあります。
簡単な答えは:はい。 ELKスタックを構成するさまざまなソフトウェアアイテムにはさまざまなソフトウェアライセンスがありますが、通常はサポートなしで無料で使用できるライセンスがあります。ただし、ELKクラスターをセットアップして保守するのはあなた次第です。
ELKスタックは高度に構成可能であるため、それを機能させる単一の方法はありません。たとえば、Apacheログエントリのパスは次のとおりです。Filebeatはエントリを読み取り、Logstashに送信して解析し、Elasticsearchに送信して保存してインデックスを作成します。その後、Kibanaはデータを取得して表示できます。