Nativní Azure Monitor a Microsoft Sentinel nově umí levnější logy a zabudovanou levnější archivaci - praxe (část 2)
V minulém díle jsem zkoumal nové vlastnosti Log Analytics pro Azure Monitor a Microsoft Sentinel, které mohou výrazně prolevnit celé řešení - basic logs, archive tier, search jobs a restore. Dnes si to vyzkoušíme prakticky.
V následujících textech budu pro některé operace používat přímo API. GUI pro nastavování archivací a search joby existuje v preview v rámci Sentinelu a nevím jak to bude ve finální podobě - API ale předpokládám zůstane, takže se na něj zaměřím.
Jedna z tabulek, která mě hodně zajímá, jsou logy z kontejnerů. Těch totiž může být hodně a nativní monitoring AKS tak mohou prodražovat, přitom nad nimi typicky nepotřebuji dělat nějaké složité analytické operace (na rozdíl třeba od bezpečnostních logů). Vytvořím tedy Azure Kubernetes Service s Azure Monitor a spustím kontejner, který mi bude generovat logy.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# Create resource group
az group create -n logs -l westeurope
# Create Log Analytics workspace
export workspaceName=tomaslogs$RANDOM
az monitor log-analytics workspace create -g logs -n $workspaceName
# Create AKS
export workspaceId=$(az monitor log-analytics workspace show -g logs -n $workspaceName --query id -o tsv)
az aks create -g logs -n logsaks -l westeurope -c1 -x -s Standard_B2s \
-a monitoring --workspace-resource-id $workspaceId
az aks get-credentials -g logs -n logsaks --admin --overwrite-existing
# Start log generating app
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: loggen
spec:
selector:
matchLabels:
app: loggen
template:
metadata:
labels:
app: loggen
spec:
containers:
- name: loggen
image: ubuntu
command:
- "/bin/bash"
args:
- "-c"
- "while true; do echo type\$(expr \$RANDOM % 5): ip=1.1.1.\$(expr \$RANDOM % 254) port=\$(expr \$RANDOM % 65554); sleep 2; done"
EOF
Dalším krokem teď bude tabulku ContainerLog přepnout z výchozího analytického tieru do režimu basic. Ten má v ceně retenci 8 dní a to je pro mě málo, nicméně prodloužit ji, na rozdíl od analytického tieru, nemůžu. Nicméně můžu použít archivní tier. To udělám tak, že nastavím parametr totalRetentionInDays a vše nad 8 dní bude v archivu.
1
2
3
4
5
6
7
8
9
# Configure ContainerLogs table as Basic and enable archive for 180 days
export url="https://management.azure.com$workspaceId/tables/ContainerLog?api-version=2021-12-01-preview"
az rest --method patch --url $url --headers "Content-Type=application/json" \
-b '{
"properties": {
"plan": "Basic",
"totalRetentionInDays": 180
}
}'
V basic jsem schopen efektivně vyhledávat. Zkusím například query, které nejprve přidá nový nové sloupečky, které vzniknou regex parsováním logu, následně podle těchto ad-hoc vytvořených sloupečků budu filtrovat na množinu typů a porty menší než 1024. Na závěr odejmu z výstupu sloupce, které mě nezajímají.
1
2
3
4
5
6
7
ContainerLog
| extend type = extract(@"^(.*?):", 1, LogEntry),
ip = extract(@"ip=(.*?)\s", 1, LogEntry),
port = extract(@"port=(.*?)$", 1, LogEntry)
| where type in ( "type1", "type3")
| where toint(port) < 1024
| project-away _ResourceId, LogEntry, LogEntrySource, _SubscriptionId
Nicméně pokud se pokusím o něco analytického, nebude to možné. Například zkusím jednoduše sumarizovat počet událostí za jednotlivé ip.
1
2
3
4
5
6
7
8
ContainerLog
| extend type = extract(@"^(.*?):", 1, LogEntry),
ip = extract(@"ip=(.*?)\s", 1, LogEntry),
port = extract(@"port=(.*?)$", 1, LogEntry)
| where type in ( "type1", "type3")
| where toint(port) < 1024
| project-away _ResourceId, LogEntry, LogEntrySource, _SubscriptionId
| summarize count() by ip
Protože v basic (a také v archivu) platím za to, jak velký objem dat musí engine projít, bude zajímavé o dotazu získat nějakou statistiku. V rámci API si stačí statistické informace vyžádat při zadávání dotazu.
1
2
3
4
5
6
7
8
9
# When quering we can requests stats to understand volume of scanned data
export workspaceCustomer=$(az monitor log-analytics workspace show -g logs -n $workspaceName --query customerId -o tsv)
az rest -u "https://api.loganalytics.io/v1/workspaces/$workspaceCustomer/search?timespan=P1D" \
--headers "Content-Type=application/json" \
--headers "Prefer=include-statistics=true" \
--method post \
--body '{"query":"ContainerLog"}' \
| jq .statistics
Tohle je výstup.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
{
"query": {
"datasetStatistics": [
{
"tableRowCount": 43087,
"tableSize": 14598614
}
],
"executionTime": 0.5937856,
"inputDatasetStatistics": {
"extents": {
"scanned": 1,
"scannedMaxDatetime": "2022-02-26T12:41:19.2909747Z",
"scannedMinDatetime": "2022-02-25T11:03:02.2175447Z",
"total": 1
},
"rows": {
"scanned": 36656,
"total": 36656
},
"rowstores": {
"scannedRows": 12323,
"scannedValuesSize": 4308574
},
"shards": {
"queriesGeneric": 1,
"queriesSpecialized": 0
}
},
"resourceUsage": {
"cache": {
"disk": {
"hits": 0,
"misses": 0,
"total": 0
},
"memory": {
"hits": 0,
"misses": 0,
"total": 0
},
"shards": {
"bypassbytes": 0,
"cold": {
"hitbytes": 0,
"missbytes": 0,
"retrievebytes": 0
},
"hot": {
"hitbytes": 682106,
"missbytes": 0,
"retrievebytes": 0
}
}
},
"cpu": {
"kernel": "00:00:00",
"totalCpu": "00:00:00.0468750",
"user": "00:00:00.0468750"
},
"memory": {
"peakPerNode": 104100080
},
"network": {
"crossClusterTotalBytes": 0,
"interClusterTotalBytes": 34897588
}
}
}
}
Přímé volání query, kdy dostáváme velmi rychle výsledek, je možné v basic a analytickém tier, ale archiv funguje jinak. Ten bude procházet studená data, která jsou uložena levněji (= pomaleji) a bude obcházet větší množství nodů a odpověď skládat. To co trvalo vteřiny, může teď trvat třeba minuty.
Dejme tedy tomu, že si připravím query na rozparsování zprávy na nové sloupce a v jednom z nich hledám “type2”.
1
2
3
4
5
6
ContainerLog
| extend type = extract(@"^(.*?):", 1, LogEntry),
ip = extract(@"ip=(.*?)\s", 1, LogEntry),
port = extract(@"port=(.*?)$", 1, LogEntry)
| where type == "type2"
| project-away _ResourceId, LogEntry, LogEntrySource, _SubscriptionId
Jak to funguje? Search job poběží na pozadí a výsledky bude ukládat do nové analytické tabulky, která musí mít příponu _SRCH. Použije se API na založení tabulky, které nově podporuje vlastnost searchResults, ve kterém jsem specifikoval výše uvedené query, potom limit (může být vhodná ochrana před nějakou chybou v zadání, protože nechci, aby výsledná tabulka, u které platím za ingesting, měla třeba 1 TB, to by stálo moc peněz) a časové okno hledání.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Search job - parse and query type2 logs
export resultTable=type2logs
export url="https://management.azure.com$workspaceId/tables/${resultTable}_SRCH?api-version=2021-12-01-preview"
az rest --method put --url $url --headers "Content-Type=application/json" \
-b '{
"properties": {
"searchResults": {
"query": "ContainerLog \r\n| extend type = extract(@\"^(.*?):\", 1, LogEntry),\r\n ip = extract(@\"ip=(.*?)\\s\", 1, LogEntry),\r\n port = extract(@\"port=(.*?)$\", 1, LogEntry)\r\n| where type == \"type2\"\r\n| project-away _ResourceId, LogEntry, LogEntrySource, _SubscriptionId",
"limit": 10000,
"startSearchTime": "2022-02-21T00:00:00Z",
"endSearchTime": "2022-02-26T00:00:00Z"
}
}
}'
Job běží na pozadí, což si můžu zkontrolovat GETem na stejnou URL
1
2
# Get job status
az rest --method get --url $url --headers "Content-Type=application/json"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"id": "/subscriptions/a0f4a733-4fce-4d49-b8a8-d30541fc1b45/resourcegroups/logs/providers/Microsoft.OperationalInsights/workspaces/tomaslogs7613/tables/type2logs_SRCH",
"name": "type2logs_SRCH",
"properties": {
"archiveRetentionInDays": 0,
"createdBy": "7424fb4c-5e9f-45cd-9f7d-453d45655e75",
"lastPlanModifiedDate": "2022-02-26T14:24:18.747Z",
"plan": "Analytics",
"provisioningState": "Updating",
"retentionInDays": 30,
"totalRetentionInDays": 30
},
"systemData": {
"createdBy": "7424fb4c-5e9f-45cd-9f7d-453d45655e75"
}
}
Vidím, že analytická tabulka s výsledky se právě vytváří. Po nějaké době GET zopakuji a už vidím strukturu tabulky, z kterého query vzešla a tak podobně.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
{
"id": "/subscriptions/a0f4a733-4fce-4d49-b8a8-d30541fc1b45/resourcegroups/logs/providers/Microsoft.OperationalInsights/workspaces/tomaslogs7613/tables/type2logs_SRCH",
"name": "type2logs_SRCH",
"properties": {
"archiveRetentionInDays": 0,
"createdBy": "7424fb4c-5e9f-45cd-9f7d-453d45655e75",
"lastPlanModifiedDate": "2022-02-26T14:24:18.747Z",
"plan": "Analytics",
"provisioningState": "InProgress",
"resultStatistics": {
"ingestedRecords": 0,
"progress": 0.0,
"scannedGb": 0.0
},
"retentionInDays": 30,
"schema": {
"columns": [
{
"isDefaultDisplay": false,
"isHidden": false,
"name": "SourceSystem",
"type": "string"
},
{
"isDefaultDisplay": false,
"isHidden": false,
"name": "TimeGenerated",
"type": "datetime"
},
{
"isDefaultDisplay": false,
"isHidden": false,
"name": "Computer",
"type": "string"
},
{
"isDefaultDisplay": false,
"isHidden": false,
"name": "TimeOfCommand",
"type": "datetime"
},
{
"isDefaultDisplay": false,
"isHidden": false,
"name": "ContainerID",
"type": "string"
},
{
"isDefaultDisplay": false,
"isHidden": false,
"name": "Image",
"type": "string"
},
{
"isDefaultDisplay": false,
"isHidden": false,
"name": "ImageTag",
"type": "string"
},
{
"isDefaultDisplay": false,
"isHidden": false,
"name": "Repository",
"type": "string"
},
{
"isDefaultDisplay": false,
"isHidden": false,
"name": "Name",
"type": "string"
},
{
"isDefaultDisplay": false,
"isHidden": false,
"name": "Id",
"type": "string"
},
{
"isDefaultDisplay": false,
"isHidden": false,
"name": "ip",
"type": "string"
},
{
"isDefaultDisplay": false,
"isHidden": false,
"name": "port",
"type": "string"
},
{
"isDefaultDisplay": false,
"isHidden": false,
"name": "_OriginalType",
"type": "string"
},
{
"isDefaultDisplay": false,
"isHidden": false,
"name": "_OriginalItemId",
"type": "string"
},
{
"isDefaultDisplay": false,
"isHidden": false,
"name": "_OriginalTimeGenerated",
"type": "datetime"
}
],
"name": "type2logs_SRCH",
"searchResults": {
"azureAsyncOperationId": "557f8f12-23e2-486c-9861-adf82274d850",
"description": "This table was created using a Search Job with the following query: 'ContainerLog \r\n| extend type = extract(@\"^(.*?):\", 1, LogEntry),\r\n ip = extract(@\"ip=(.*?)\\s\", 1, LogEntry),\r\n port = extract(@\"port=(.*?)$\", 1, LogEntry)\r\n| where type == \"type2\"\r\n| project-away _ResourceId, LogEntry, LogEntrySource, _SubscriptionId'.",
"endSearchTime": "2022-02-26T00:00:00Z",
"limit": 10000,
"query": "ContainerLog \r\n| extend type = extract(@\"^(.*?):\", 1, LogEntry),\r\n ip = extract(@\"ip=(.*?)\\s\", 1, LogEntry),\r\n port = extract(@\"port=(.*?)$\", 1, LogEntry)\r\n| where type == \"type2\"\r\n| project-away _ResourceId, LogEntry, LogEntrySource, _SubscriptionId",
"sourceTable": "ContainerLog",
"startSearchTime": "2022-02-21T00:00:00Z"
},
"solutions": [
"LogManagement"
],
"standardColumns": [
{
"isDefaultDisplay": false,
"isHidden": false,
"name": "TenantId",
"type": "guid"
}
],
"tableSubType": "DataCollectionRuleBased",
"tableType": "SearchResults"
},
"totalRetentionInDays": 30
},
"systemData": {
"createdBy": "7424fb4c-5e9f-45cd-9f7d-453d45655e75"
}
}
Pokud bych něco takového chtěl dělat graficky z portálu, takhle vypadá UI v Microsoft Sentinel.
Pěkně je, že výsledky našeho hledání v archivu jsou teď v analytické tabulce, takže nad nimi můžeme pokračovat dál s analytikou - například sumarizaci podle IP.
1
2
3
4
type2logs_SRCH
| extend TimeGenerated = _OriginalTimeGenerated
| project-away _OriginalTimeGenerated
| summarize count() by ip
Místo search jobu můžeme také přistoupit k zavlažení archivních logů. Jak jsem minule popisoval, funguje to tak, že se data na nějakou dobu přesunou do analytického tieru a můžete se pak v nich prohrabávat naplno. Platíte podle objemu zavlažených dat a to za každý načatý den, kdy je chcete držet v analytice. Všimněte si, že v následujícím API volání nemůžeme specifikovat query, jen časové okno (minimum jsou dva dny). Pokud tedy hledám něco dost specifického, co mi z tabulky vrátí méně jak 4% dat, je výhodnější dát search job a zaplatit ingesting. Pokud ale nevím přesně co hledám a budu dat vracet významnější procento, bude výhodnější je zavlažit. V API stačí opět vytvořit novou tabulku tentokrát s příponou _RST a v properties specifikovat resourceLogs s časovým oknem a zdrojovou tabulkou.
1
2
3
4
5
6
7
8
9
10
11
12
# Restore archive logs to analytics tier
export url="https://management.azure.com$workspaceId/tables/ContainerLog_RST?api-version=2021-12-01-preview"
az rest --method put --url $url --headers "Content-Type=application/json" \
-b '{
"properties": {
"restoredLogs": {
"startRestoreTime": "2022-01-21T00:00:00Z",
"endRestoreTime": "2022-01-25T00:00:00Z",
"sourceTable": "ContainerLog"
}
}
}'
Takhle to vypadá v UI Sentinelu.
Na závěr - basic logs neumožňují joiny a jiné složité operace a to ovlivnilo UI Container Insights v Azure Monitor, které joinuje ContainerLogs s několika dalšími tabulkami s inventářem (jak se jmenoval Pod a tak).
Možná právě proto je v private preview obohacování dat přímo na agentovi, takže v tabulce najdete i informaci o Podu nebo namespace. Znamená to samozřejmě, že takový denormalizovaný přístup generuje větší objem dat, ale na druhou stranu pak nepotřebujete složité join operace a search zvládnete v basic nebo archive tieru v pohodě. Aktuálně už je dokumentace datové struktury tabulky ContainerLogV2 - nicméně vyzkoušet zatím nelze, snad to bude brzy hotové.
Tolik tedy zatím k využití nových basic a archiv logů v praxi a metodám prohledávání nebo zavlažování archivních logů. Jsem zvědavý hlavně jak se bude rozvíjet podpora v jednotlivých řešeních typu různých Insights v Azure Monitor, protože UI v Microsoft Sentinel už teď vypadá myslím velmi dobře. Zkuste si to!