やりたいこと
- Windowsサーバにソフトウェアをインストールしないでfluentdにログを渡したい
- ファイルサーバの監査ログをKibanaで気軽に見たい
ハードウェア構成
- 稼働中のWindowsサーバ7台 (2008R2が3台 + 2012R2が4台) ← あまりいじりたくない
- 余っているLinuxサーバ1台 (Ubuntu Server 16.04 LTS) ← 自由にいじれる
方針
- dockerにfluentdとElasticsearch、Kibanaコンテナを立てる
- fluentdの
in_http
プラグインでJSONを受ける - PowerShellスクリプトでJSONを生成してfluentdに送信
docker + fluentd + Elasticsearch + Kibana
fluentdのin_http
でJSONを待ち受けるように設定する。
以下のようにdocker-compose.yaml
とDockerfile
、fluent.conf
を配置。
今回はfluentdのin_http
では9880ポート、Kibanaでは5602ポートを使う。
.
├── docker-compose.yaml
└── fluentd
├── Dockerfile
└── fluent.conf
FROM fluent/fluentd
RUN gem install fluent-plugin-elasticsearch
COPY fluent.conf /fluentd/etc/fluent.conf
<source>
@type http
port 9880
bind 0.0.0.0
body_size_limit 512m
keepalive_timeout 10s
</source>
<match **>
type elasticsearch
host elasticsearch
time_key TimeCreated
port 9200
logstash_format true
flatten_hashes true
flatten_hashes_separator _
buffer_type file
buffer_path /tmp/fluentd*.buffer
buffer_chunk_limit 1024m
# buffer_queue_limit 256
# flush_interval 60s
# retry_wait 5s
</match>
version: '2'
services:
fluentd:
build: ./fluentd
volumes:
- windowslog:/var/log/windows
- fluentdata:/var/log/fluent
environment:
FLUENTD_CONF: fluent.conf
ports:
- "9880:9880"
restart: always
depends_on:
- elasticsearch
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:5.5.2
volumes:
- esdata:/usr/share/elasticsearch/data
- esconfig:/usr/share/elasticsearch/config
expose:
- "9200"
restart: always
environment:
- bootstrap.memory_lock=true
- xpack.security.enabled=false
- xpack.monitoring.enabled=false
- xpack.watcher.enabled=false
- xpack.graph.enabled=false
- xpack.ml.enabled=false
- http.max_content_length=1g
- thread_pool.index.queue_size=-1
- thread_pool.bulk.queue_size=-1
- "ES_JAVA_OPTS=-Xms2048m -Xmx2048m"
ulimits:
memlock:
soft: -1
hard: -1
mem_limit: 4g
kibana:
image: docker.elastic.co/kibana/kibana:5.5.2
ports:
- "5602:5601"
restart: always
environment:
- "ELASTICSEARCH_URL=http://elasticsearch:9200"
- xpack.graph.enabled=false
- xpack.security.enabled=false
- xpack.ml.enabled=false
depends_on:
- elasticsearch
volumes:
windowslog:
driver: local
fluentdata:
driver: local
esdata:
driver: local
esconfig:
driver: local
コンテナ起動。
docker-compose up -d --build
Windowsサーバ側
「ソフトウェアをインストールしない」という要件を満たすため、PowerShellのGet-WinEvent
を使用する。
Get-WinEvent
はリモートコンピュータのイベントログも取得できるので、Windowsサーバの内1台にだけスクリプトを設置。ただし、ConvertTo-Json
を使用するので、PowerShell 3.0以降が入ったWindows 2012R2上で実行する。
下記は1日1回タスクスケジューラで実行することを前提としたスクリプトで、前日の1日分のログを収集してfluentdに送信する。
$hosts = @("SERVER1", "SERVER2", "SERVER3", "SERVER4", "SERVER5", "SERVER6", "SERVER7")
$lognames = @("Application", "Security", "System")
$date = Get-Date
$dateend = $date.AddHours(-$date.Hour).AddMinutes(-$date.Minute).AddSeconds(-$date.Second)
$datestart = $dateend.AddDays(-1)
$datestartutc = [System.TimeZoneInfo]::ConvertTimeToUtc($datestart).ToString("yyyy-MM-ddTHH:mm:ssZ")
$dateendutc = [System.TimeZoneInfo]::ConvertTimeToUtc($dateend).ToString("yyyy-MM-ddTHH:mm:ssZ")
$query = "*[System[TimeCreated[@SystemTime>='$datestartutc' and @SystemTime<='$dateendutc']]]"
foreach($h in $hosts){
foreach($l in $lognames){
Get-WinEvent -LogName $l -ComputerName $h -FilterXPath $query | % {
$evt = $_ | ConvertTo-Json -Depth 5 | ConvertFrom-Json
$evt.TimeCreated = $evt.TimeCreated.ToString("yyyy-MM-ddTHH:mm:ss.fffzzzz")
$evt.KeywordsDisplayNames = $evt.KeywordsDisplayNames -join " "
$evt.Properties = ($evt.Properties | % { $_.Value }) -join ' '
$evt.MatchedQueryIds = $evt.MatchedQueryIds -join " "
$evt.Bookmark = $evt.Bookmark -join " "
$evt.Message = $evt.Message -replace "\p{C}+", " "
Write-Host "$h : $l : $($evt.RecordId)"
$json = ConvertTo-Json $evt -Compress -Depth 5
$json = $json -replace "\+", "\u002b"
$body = [System.Text.Encoding]::UTF8.GetBytes("json=" + $json)
try{
$null = Invoke-WebRequest "http://(dockerホストのIP):9880/windows.eventlog" -Method Post -Body $body
}catch{
Write-Host $_.Exception.Message -ForegroundColor Red
}
}
}
}
ConvertTo-Json
では日付型の値が"\/Date(1326441600000)\/"
という形式になってしまいfluentdで解釈できないため、二度手間ではあるがConvertTo-Json
→ConvertFrom-Json
として複製した後、TimeCreate
の値をフォーマット。
その他ネストした配列やオブジェクトはfluentdが上手く解釈できないので適当にくっつけておく。
たまにMessage
中に不正な文字列が残るらしく、fluentdがエラーを返すので-replace "\p{C}+", " "
でUnicode制御文字を消しておく。
URLエンコードしていないのが原因でした。$body
は下記のようにUrlEncode
しませう。
Add-Type -AssemblyName System.Web
$body = [System.Text.Encoding]::UTF8.GetBytes("json=" + [System.Web.HttpUtility]::UrlEncode($json))
in_http
プラグインの仕様により、"+"
が無視されてしまうので、"+"
も"\u002b"
エンコードする。
PowerShellの文字列をそのままPOSTすると、非ASCII文字が全部"?"
になるので、UTF-8に変換する。
Invoke-WebRequest
でfluentdコンテナにJSONをPOSTする。IPはホストのIP、ポートはdocker-compose.yaml
で指定した9880
ポート。
ある程度まとめて送ってHTTPリクエスト数を減らしたほうが若干高速化するが、下記の理由によりこのスクリプトではイベント1件ずつfluentdに送信している。
-
Get-WinEvent
に律速されるためあまり高速化しない - JSON文字列に不正な文字列が入っていた場合、全件弾かれてしまう
Kibana
ブラウザからhttp://(dockerホストのIP):5602/
にアクセス。
時刻に"TimeCreated"
を指定してインデックスパターンを作成。
感想
WindowsサーバにRubyなどをインストールしなくても、fluent-plugin-windows-eventlog相当の機能を実現できた。
懸念点はやはり速度で、だいたい数万件/時間でしかログを吐けない。ファイルサーバのアクセス監査ログだと生成速度に追いつけない。
速度のボトルネックはGet-WinEvent
なので、他にwevtutl.exe
を使う方法も検討したが、出力されるXMLに情報が不足している(具体的にはMessage
がない)ので断念した。なんとかできないものかなぁ。