ユーザーデータを使って Amazon EC2 (Windows Server) インスタンスのログ管理を実装してみた

こんにちは、なべです。
最近レトルトカレーにはまっていて、名店のレトルトカレーの食べ比べをしています。

現在AWS環境のIaC構築を推進しており、EC2の非機能周りも自動化で実装されると嬉しいなと思いました。
関連の検索をしても非機能周りの自動実装が無かったので、私の方でログ管理のスクリプトを作成してみました。

概要

EC2起動時にユーザーデータを実行し、以下を設定します。
なお、前提としてWindowsServerであること、CloudWatchAgentをインストールするためインターネットやモジュール取得先のS3につながる環境があること、環境によっては必要なVPCエンドポイント、IAMロールが割り当てられている事が前提の条件となりますが、こちらは今回割愛します。

<CloudWatchAgent設定>
①CloudWatchAgentのインストール
②CloudWatchLogsへの出力設定(JSON)
③CloudWatchAgent起動設定

<S3イベントログアップロード設定>
①イベントログアップロードスクリプト配置
②タスクスケジューラ設定
③AWS Tools for PowerShellのインストール

概要図はこちらです
ログはいずれもSystem,Application,Securityログを送信します。

やってみましょう

今回設定するユーザーデータスクリプトはこちらです。
スクリプト内の以下6所は要件に合わせて任意で設定いただく箇所になります。
・ロググループ名
・スクリプト配置パス
・任意のプレフィックス
・任意のプレフィックス
・アップロード先S3バケット名
・タスク実行時間

# タイムゾーンを東京に設定
Set-TimeZone -Id "Tokyo Standard Time"

# CloudWatch Agentのダウンロード
Invoke-WebRequest -Uri "https://amazoncloudwatch-agent.s3.amazonaws.com/windows/amd64/latest/amazon-cloudwatch-agent.msi" -OutFile "amazon-cloudwatch-agent.msi"

# MSIパッケージのインストール
Start-Process msiexec.exe -Wait -ArgumentList "/i amazon-cloudwatch-agent.msi /qn"

# # 5回までフォルダの存在を確認する
# for ($i = 0; $i -lt 5; $i++) {

# # フォルダの存在を確認
# if (Test-Path -Path "C:\ProgramData\Amazon\AmazonCloudWatchAgent\Configs") {
# break
# } else {
# Start-Sleep -Seconds 5
# }
# }

# CloudWatch Agent設定をJSONとして保存
$configJson = @'
{
"agent": {
"metrics_collection_interval": 60,
"run_as_user": "System"
},
"logs": {
"logs_collected": {
"windows_events": {
"collect_list": [
{
"event_name": "System",
"event_levels": ["ERROR", "WARNING", "INFORMATION"],
"log_group_name": "/nabe/tokyo/ec2/system",
"log_stream_name": "{local_hostname}_{instance_id}"
},
{
"event_name": "Application",
"event_levels": ["ERROR", "WARNING", "INFORMATION"],
"log_group_name": "/nabe/tokyo/ec2/application",
"log_stream_name": "{local_hostname}_{instance_id}"
},
{
"event_name": "Security",
"event_levels": ["ERROR", "WARNING", "INFORMATION", "CRITICAL"],
"log_group_name": "/nabe/tokyo/ec2/security",
"log_stream_name": "{local_hostname}_{instance_id}"
}
]
}
}
},
"metrics": {
"metrics_collected": {
"Memory": {
"measurement": [
{"name": "% Committed Bytes In Use", "unit": "Percent"}
],
"metrics_collection_interval": 60
},
"LogicalDisk": {
"measurement": [
{"name": "% Free Space", "unit": "Percent"}
],
"metrics_collection_interval": 60,
"resources": ["*"]
}
},
"append_dimensions": {
"InstanceId": "${aws:InstanceId}",
"InstanceType": "${aws:InstanceType}"
}
}
}
'@

# 設定ファイルの保存
[System.IO.File]::WriteAllText("C:\ProgramData\Amazon\AmazonCloudWatchAgent\Configs\Config.json", $configJson)

# CloudWatch Agentの起動と設定
& "C:\Program Files\Amazon\AmazonCloudWatchAgent\amazon-cloudwatch-agent-ctl.ps1" -a fetch-config -m ec2 -s -c file:"C:\ProgramData\Amazon\AmazonCloudWatchAgent\Configs\Config.json"
Start-Sleep -Seconds 10
& "C:\Program Files\Amazon\AmazonCloudWatchAgent\amazon-cloudwatch-agent-ctl.ps1" -a start

# エージェントのステータス確認とログ出力
& "C:\Program Files\Amazon\AmazonCloudWatchAgent\amazon-cloudwatch-agent-ctl.ps1" -a status | Out-File -FilePath "C:\CloudWatchAgent\agent-status.txt"

# スクリプトディレクトリの作成
New-Item -ItemType Directory -Force -Path "C:\infra\script"

# S3転送スクリプトの作成
$s3TransferScript = @'
# 日付とホスト情報定義
$hostName = [System.Net.Dns]::GetHostName()
$date = (Get-Date).AddDays(-1).ToString("yyyyMMddHHmmss")
$zipFileName = "任意の名前" -f $hostName, $date

# S3のバケット名とプレフィックス
$bucketName = $args[0]
$prefix = "任意のプレフィックス" -f $hostName

# 抽出対象イベントログの名前定義
$eventLogNames = "System", "Application", "Security"

# スクリプト名定義
$scriptName = $MyInvocation.MyCommand.Name

# イベントソースが存在するかどうかを確認する
if (-not [System.Diagnostics.EventLog]::SourceExists($scriptName)) {
# イベントソースが存在しない場合、新しく作成する
New-EventLog -LogName Application -Source $scriptName
}

# スクリプトの開始ログを出力する
Write-EventLog -LogName Application -Source $scriptName -EntryType Information -EventId 100 -Message "[Info 001]: Script started."

# イベントログをエクスポートしてzip化処理
ForEach ($logName in $eventLogNames) {
Try {
$fileName = "{0}-{1}.txt" -f $logName, $date
Get-WinEvent -FilterHashtable @{ LogName=$logName; StartTime=(Get-Date).AddDays(-1).Date; EndTime=(Get-Date).Date } | Format-Table -AutoSize -Wrap > $fileName
Compress-Archive -Path $fileName -Update -DestinationPath $zipFileName
Remove-Item $fileName
}
Catch {
$errorMsg = "[Error 001]: Script failed while exporting and zipping event logs. Error details: $_"
Write-Error $errorMsg
Write-EventLog -LogName Application -Source $scriptName -EntryType Error -EventId 1 -Message $errorMsg
Break
}
Finally {
Write-EventLog -LogName Application -Source $scriptName -EntryType Information -EventId 101 -Message "[Info 002]: Exporting and zipping event logs completed."
}
}

# AWS S3にZIPファイルをアップロード
Try {
Write-S3Object -BucketName $bucketName -File $zipFileName -Key $prefix/$zipFileName
}
Catch {
$errorMsg = "[Error 002]: Script failed while uploading the ZIP file to S3. Error details: $_"
Write-Error $errorMsg
Write-EventLog -LogName Application -Source $scriptName -EntryType Error -EventId 2 -Message $errorMsg
}
Finally {
Write-EventLog -LogName Application -Source $scriptName -EntryType Information -EventId 102 -Message "[Info 003]: Uploading the ZIP file to S3 completed."
Remove-Item $zipFileName
}

Write-Host "ZIP file has been uploaded to S3."
Write-EventLog -LogName Application -Source $scriptName -EntryType Information -EventId 103 -Message "[Info 004]: Script completed."

'@

# スクリプトの保存
[System.IO.File]::WriteAllText("C:\infra\script\s3_log_transfer.ps1", $s3TransferScript)

# タスクスケジューラーの設定
$action = New-ScheduledTaskAction `
-Execute "PowerShell.exe" `
-Argument "-ExecutionPolicy Bypass C:\infra\script\s3_log_transfer.ps1 アップロード先S3バケット名"

$trigger = New-ScheduledTaskTrigger -Daily -At 2am
$settings = New-ScheduledTaskSettingsSet -StartWhenAvailable -DontStopIfGoingOnBatteries -AllowStartIfOnBatteries

Register-ScheduledTask `
-TaskName "S3EventLogTransfer" `
-Action $action `
-Trigger $trigger `
-Settings $settings `
-User "System" `
-RunLevel Highest `
-Force


# AWS Tools for PowerShellのインストール
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force
Install-Module -Name AWS.Tools.Common -Force
Install-Module -Name AWS.Tools.S3 -Force

周辺環境のデプロイが手間なので、今回私はAWS CDKでデプロイしますが、EC2作成時にユーザーデータ欄に以下のように<powershell></powershell>と挟んでスクリプトをコピペしてください。

動作確認

まずは、CloudWatchLogsからです、SYSTEMログとSECURITYログは割愛しますが、以下のようにデプロイ直後から出力されています。

 

 

 

 

 

続いて、イベントログS3アップロードです。
以下のようにEC2のタスクスケジューラにタスクがしっかり登録されていました。
では、強制的に実行してみましょう。

以下のようにイベントログ(Application)に途中経過が出力されます。

S3にログがアップロードされていることを確認できました。

今回は以上となります。
少しでも皆様のお力になれれば幸いです。

次回はAWS CDK周りの案件に合わせた実装の記事を執筆したいと思います。

タイトルとURLをコピーしました