Files
2026-05-18 06:40:19 +00:00

3981 lines
135 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# A) „OpenClaw jako nocny DBA” (monitoring → alerty → automatyczne runbooki)
## 0) Cel i „granice odpowiedzialności”
**Cel:** agent ma działać w nocy jak NOC: wykryć problem, zebrać dane diagnostyczne, wykonać bezpieczne kroki naprawcze (runbook), a rano zostawić raport / ticket / wiadomość.
**Granice:** agent **nie robi destrukcyjnych zmian** bez reguł (np. brak `DROP`, brak zmian indeksów na produkcji bez okna). To da się wymusić polityką narzędzi + regułami runbooków. [\[docs.openclaw.ai\]](https://docs.openclaw.ai/tools), [\[docs.openclaw.ai\]](https://docs.openclaw.ai/)
***
## 1) Architektura PoC (minimalna, ale sensowna)
**Komponenty:**
1. **OpenClaw Gateway** uruchomiony na hostcie (VM/miniserwer) i podpięty do komunikatora (np. Telegram/Discord/Slack). Dokumentacja opisuje gateway jako „single source of truth” dla sesji i routingu. [\[docs.openclaw.ai\]](https://docs.openclaw.ai/), [\[openclaw.im\]](https://openclaw.im/docs)
2. **Agent „DBANight”** jako osobny agent / osobny workspace.
3. **Kanał alertów** (grupa/kanal, nie DM) — bo do DMs często dorzuca się rzeczy osobiste; w release notes widać, że zachowanie „heartbeat delivery” bywa ograniczane dla DM. [\[github.com\]](https://github.com/openclaw/openclaw/releases)
***
## 2) Bezpieczeństwo od pierwszego dnia (tool policy)
**Najważniejszy krok**: ogranicz narzędzia globalnie, a potem ewentualnie rozszerzaj.
* Ustaw **profil narzędzi** (np. „coding” albo własny minimalny) i dopiero dodawaj allowlist. OpenClaw ma profile typu *minimal/coding/messaging/full* i allow/deny z zasadą „deny wins”. [\[docs.openclaw.ai\]](https://docs.openclaw.ai/tools)
* W nocy DBA agent zwykle potrzebuje:
* odczytu logów/metryk,
* uruchamiania **konkretnych** skryptów diagnostycznych,
* wysyłki raportu na kanał.
**W praktyce**: staraj się, aby agent **nie miał ogólnego `exec`**, tylko odpalał predefiniowane narzędzia/skrypty (wrappery). To minimalizuje „agent drift”.
***
## 3) Zbieranie sygnałów (monitoring)
Masz 3 drogi (od najprostszej):
### Opcja 1: „Agent słucha alertów” (najbezpieczniej)
* Splunk / SCOM / Zabbix / Prometheus Alertmanager / SQL Agent alerts wysyłają webhook/wiadomość do kanału (Slack/Discord/Telegram).
* OpenClaw odbiera komunikat i uruchamia runbook.
**Plus:** agent nie musi skanować świata.
**Minus:** zależysz od konfiguracji alertów.
### Opcja 2: Heartbeat / cron (proaktywne)
OpenClaw jest projektowany pod automatyzacje i tło (cron/heartbeat). [\[docs.openclaw.ai\]](https://docs.openclaw.ai/), [\[openclaws.io\]](https://openclaws.io/)
* Co 15 min agent robi „health sweep”: space, backup freshness, top waits, blocked sessions, failed jobs.
### Opcja 3: Hybryda
* Alerty → natychmiastowa reakcja
* Heartbeat → wykrywanie „cichych” degradacji
***
## 4) Runbooki: jak je ugryźć, żeby to działało w praktyce
Runbook = „procedura do wykonania” + „warunki bezpieczeństwa”.
**Szablon runbooka (polecam):**
1. **Triaging**: co to za typ zdarzenia? (backup failure / blocking / disk / CPU / IO / job fail)
2. **Zbieranie dowodów**: 510 komend / zapytań readonly (DMVs, msdb, logi, plany)
3. **Ocena ryzyka**: czy można robić autoremediation?
4. **Działanie** (tylko jeśli spełnione warunki)
5. **Raport** (kto, co, kiedy, wynik, linki)
6. **Escalacja** (jeśli niepewne → ping oncall + gotowy raport)
**Przykłady „bezpiecznych autoremediation”:**
* restart konkretnego joba ETL *tylko jeśli* poprzedni run zakończył się X i nie ma uruchomionej instancji
* kill sesji blokującej *tylko jeśli* blocking > N minut i SPID z allowlisty (np. znany batch)
* uruchomienie index rebuild/reorg **tylko na nonprod** lub w zdefiniowanym oknie
***
## 5) Raportowanie (rano chcesz “story”, nie log)
Agent powinien wysyłać:
* **1liner**: co się stało i jaki wpływ (SLA)
* **Diag summary**: top waits, blocking chain, failed job id
* **Akcja**: co zrobił (lub czemu nie zrobił)
* **Rekomendacja**: co zrobić w dzień
***
## 6) Minimalny backlog PoC (7 dni)
**Dzień 12:** Gateway + kanał + agent + polityki narzędzi
**Dzień 3:** Alert ingestion (np. webhook/wiadomość)
**Dzień 4:** 2 runbooki readonly (blocking, failed jobs)
**Dzień 5:** 1 autoremediation z twardymi guardrailami
**Dzień 6:** raport dzienny (digest)
**Dzień 7:** chaos test (symulacja alertów) i poprawki [\[docs.openclaw.ai\]](https://docs.openclaw.ai/), [\[docs.openclaw.ai\]](https://docs.openclaw.ai/tools)
***
# B) „Claude Code w pracy z SQL Server” (analiza zapytań, indeksy, refactor procedur)
Tu układ jest prostszy: Claude Code ma „IDEcentric” mental model (repo, pliki, git, workflow). Twoja wartość to **powtarzalny proces**.
## 1) Organizacja repo (żeby AI działało jak ekspert)
W repo trzy kluczowe elementy:
* `/sql/` (procedury, widoki, migracje)
* `/docs/` (standardy, naming, antipatterns)
* `CLAUDE.md` (zasady: RPO/RTO, deploy policy, co wolno na prod, styl TSQL) — w mapie Claude Code wprost ma `CLAUDE.md` i „Project Context/Memory”. *(to wynika z obrazka, który przysłałeś)*
## 2) Use-case 1: analiza zapytania
Workflow:
1. Wklejasz query + (opcjonalnie) plan/STATISTICS IO/TIME
2. Claude:
* identyfikuje antypatterny: implicit conversions, nonSARG, missing indexes
* proponuje rewrite + alternatywy (np. temp table vs CTE)
3. Efekt: commit z poprawką + komentarz „why” (dla code review)
## 3) Use-case 2: indeksy (bez „index spam”)
Proces decyzyjny, który możesz narzucić:
* najpierw: **czy to query jest top offender** (baseline)
* potem: **czy indeks nie popsuje write workload**
* na koniec: test A/B na stagingu
Claude Code jest świetny w generowaniu:
* propozycji indeksów „covering”
* objaśnień tradeoffów
* checklisty wdrożenia
## 4) Use-case 3: refactor procedur
Tu Twoje największe ROI:
* wyciąganie wspólnej logiki
* poprawa error handling (TRY/CATCH, XACT\_STATE)
* zamiana cursorów na setbased
* ujednolicenie transakcji
## 5) Git workflow (żeby było „enterprisegrade”)
* PR template: „before/after”, ryzyko, plan rollback
* testy: tSQLt lub przynajmniej harness z danymi
* wymagane artefakty: query plan snapshot, IO/time
***
# C) Hybryda: OpenClaw reaguje na alert → runbook → PR → Claude Code robi review
To jest najciekawsze i najbardziej „produkcyjne” — i da się to zrobić bez „magii”, jeśli potraktujesz to jako pipeline.
## 1) Pipeline (endtoend)
1. **Alert** wpada do kanału (np. “Blocking > 10 min”, “Job failed”, “CPU 95%”)
2. **OpenClaw**:
* rozpoznaje typ incydentu
* odpala **runbook diagnostyczny**
* zapisuje artefakty (logi, wyniki DMVs, plan) do folderu incydentu
3. **OpenClaw**:
* generuje zmianę (np. poprawka procedury / indeks / hint) w repo
* tworzy branch i otwiera **PR** (z opisem, dowodami, ryzykiem)
4. **Claude Code**:
* robi **code review** PR
* proponuje poprawki (czytelność, bezpieczeństwo transakcji, edge cases)
* dba o zgodność ze standardami z `CLAUDE.md`
5. **Człowiek**:
* akceptuje PR
* deploy do staging/prod wg polityki
## 2) Klucz: separacja uprawnień i ról agentów
Żeby to było bezpieczne:
* OpenClaw agent ma narzędzia do:
* odbioru alertów
* odczytu diagnostyki
* zapisu plików do repo
* utworzenia PR
* Claude Code działa jako reviewer (bez produkcyjnego „exec”)
W OpenClaw możesz to realizować narzędziami/profilami per agent i allow/deny listami. [\[docs.openclaw.ai\]](https://docs.openclaw.ai/tools), [\[docs.openclaw.ai\]](https://docs.openclaw.ai/)
## 3) Guardraile (bez nich nie rób hybrydy)
* **Workspace isolation** (jeden agent = jeden workspace)
* **Read-only** połączenie do SQL w nocy (o ile możliwe)
* **Break-glass**: jeśli agent musi zrobić akcję naprawczą, to tylko po spełnieniu warunków
* **Rate limit**: max N działań na incydent
* **Human-in-the-loop**: PR zawsze wymaga akceptacji
To ważne, bo agentowe narzędzia potrafią narobić szkód, a ekosystem OpenClaw miał głośne tematy bezpieczeństwa i podatności — regularne aktualizacje i twarde ograniczenia są konieczne. [\[heise.de\]](https://www.heise.de/en/news/Over-60-security-vulnerabilities-in-AI-assistant-OpenClaw-resolved-11179476.html), [\[github.com\]](https://github.com/openclaw/openclaw/releases), [\[indianexpress.com\]](https://indianexpress.com/article/technology/artificial-intelligence/meta-researcher-loses-emails-to-openclaws-rogue-automation-10549018/)
***
# Jak to spiąć w serię postów (propozycja struktury)
Jeśli chcesz publikować, proponuję 3częściową serię:
1. **OpenClaw jako nocny DBA** architektura + polityki narzędzi + 2 runbooki
2. **Claude Code w SQL Server** 3 workflows + repo structure + PR template
3. **Hybryda** pipeline, uprawnienia, „co może pójść źle” i jak temu zapobiec
***
## Dwa pytania, żeby dopasować to idealnie (bez długiej ankiety)
1. Jakie masz źródło alertów dziś: Splunk/SCOM/Zabbix/Prometheus/SQL Agent?
2. Repo z SQL jest w GitHub/GitLab/Azure DevOps?
Odpowiesz, a ja w kolejnym kroku dopnę Ci:
* **konkretne runbooki** (blocking + failed jobs + disk space + backup freshness),
* szablony PR opisów,
* i checklistę „prod-safe” pod SQL Server.
Pewnie — poniżej masz **konkretne runbooki** (SQL Server), napisane tak, żeby:
* dało się je wykonywać **ręcznie** (Ty/on-call),
* dało się je uruchamiać **automatycznie** przez „nocnego DBA” (OpenClaw),
* miały **guardraile** (kiedy wolno zrobić autoremediation, kiedy tylko raportować),
* kończyły się **raportem** (co, dlaczego, co dalej).
> **Ważne założenie bezpieczeństwa:** jeśli OpenClaw ma wykonywać te runbooki automatycznie, zacznij od wersji **readonly** i dopiero po testach dodawaj kroki naprawcze. Najlepszy wzorzec: *Diag → Ocena ryzyka → (opcjonalnie) Akcja → Raport*.
***
# Wspólny format runbooka (do automatyzacji)
Każdy runbook ma strukturę:
1. **Trigger / warunek wejścia**
2. **Diag (readonly)** zbieranie faktów
3. **Decyzja** czy wolno autoremediate
4. **Akcja** jeśli spełnione warunki
5. **Raport** krótki + techniczne załączniki
6. **Escalacja** kiedy budzić człowieka
Dzięki temu możesz to przenieść 1:1 na automatyczny workflow (np. OpenClaw: „wykonaj kroki 2 i 5 zawsze, kroki 4 tylko jeśli…”).
***
# RUNBOOK 1: Blocking / Deadlocks / „aplikacja stoi”
## 1) Trigger
* Alert: “Blocking > 10 min” / “requests blocked count > N” / “LCK\_\* top wait > threshold”
* Użytkownicy zgłaszają „system stoi”, rośnie response time.
## 2) Diag (readonly)
### 2.1 Szybki snapshot blokad
```sql
-- Kto kogo blokuje (czytelny łańcuch)
;WITH R AS (
SELECT
r.session_id,
r.blocking_session_id,
r.wait_type,
r.wait_time,
r.wait_resource,
r.status,
r.command,
r.cpu_time,
r.total_elapsed_time,
r.reads,
r.writes,
r.logical_reads,
DB_NAME(r.database_id) AS database_name
FROM sys.dm_exec_requests r
WHERE r.session_id <> @@SPID
)
SELECT *
FROM R
WHERE blocking_session_id <> 0
ORDER BY wait_time DESC;
```
### 2.2 Szczegóły sesji + program + login + host
```sql
SELECT
s.session_id,
s.login_name,
s.host_name,
s.program_name,
s.status,
s.open_transaction_count,
s.last_request_start_time,
s.last_request_end_time
FROM sys.dm_exec_sessions s
WHERE s.is_user_process = 1
ORDER BY s.last_request_start_time;
```
### 2.3 Tekst zapytania dla blockerów i blokowanych
```sql
-- Query text dla sesji, które blokują lub są blokowane
SELECT
r.session_id,
r.blocking_session_id,
t.text AS sql_text
FROM sys.dm_exec_requests r
CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) t
WHERE r.blocking_session_id <> 0 OR r.session_id IN (
SELECT blocking_session_id
FROM sys.dm_exec_requests
WHERE blocking_session_id <> 0
);
```
### 2.4 Dodatkowo: transakcje otwarte długo
```sql
SELECT
at.transaction_id,
at.name,
at.transaction_begin_time,
DATEDIFF(MINUTE, at.transaction_begin_time, SYSDATETIME()) AS tran_minutes,
st.session_id
FROM sys.dm_tran_active_transactions at
JOIN sys.dm_tran_session_transactions st
ON at.transaction_id = st.transaction_id
ORDER BY tran_minutes DESC;
```
## 3) Decyzja (guardrails)
**Autoremediation dozwolona tylko jeśli:**
* blocker jest **znanym batch jobem** (allowlist `program_name`/`login_name`) **albo**
* blocker ma `open_transaction_count = 0` i stoi w wait od > X min (np. „zawieszony”) **i**
* blokada trwa > N minut (np. 1015) **i**
* database = non-prod **lub** jest okno serwisowe.
W przeciwnym razie: tylko raport + eskalacja.
## 4) Akcja (opcjonalnie)
### 4.1 „Miękkie” działania (bez KILL)
* jeśli to znany job: spróbuj przerwać job (SQL Agent) zamiast killować sesję,
* jeśli to transakcja aplikacji: eskaluj.
### 4.2 KILL (tylko przy spełnieniu warunków)
```sql
-- UWAGA: używaj tylko w ramach polityki/okna i po spełnieniu guardrails
KILL <spid>;
```
## 5) Raport (co ma powstać)
* Top 5 blokujących sesji (session\_id, login, host, program)
* łańcuch blokad
* SQL text dla blockerów
* czas trwania
* jeśli wykonano akcję: który SPID ubity / job zatrzymany
## 6) Escalacja
* deadlocki powtarzające się co kilka minut
* blocker to krytyczna usługa (login produkcyjny) i nie wolno killować
* nie można ustalić przyczyny w 510 minut → ping on-call
***
# RUNBOOK 2: Failed SQL Agent Job (ETL, backup, maintenance)
## 1) Trigger
* Alert: „SQL Agent Job failed”
* msdb: job outcome failure
* brak danych w hurtowni / brak backupów.
## 2) Diag (readonly)
### 2.1 Ostatnie nieudane joby (24h)
```sql
USE msdb;
SELECT TOP (50)
j.name AS job_name,
h.run_date,
h.run_time,
h.step_id,
h.step_name,
h.run_status,
h.message
FROM dbo.sysjobhistory h
JOIN dbo.sysjobs j ON h.job_id = j.job_id
WHERE h.run_status = 0 -- failed
AND h.step_id <> 0
ORDER BY h.run_date DESC, h.run_time DESC;
```
### 2.2 Szczegóły ostatniego uruchomienia konkretnego joba
```sql
USE msdb;
DECLARE @job_name sysname = N'<JOB_NAME>';
SELECT TOP (20)
j.name,
h.run_date,
h.run_time,
h.step_id,
h.step_name,
h.run_duration,
h.message
FROM dbo.sysjobhistory h
JOIN dbo.sysjobs j ON h.job_id = j.job_id
WHERE j.name = @job_name
AND h.step_id <> 0
ORDER BY h.instance_id DESC;
```
### 2.3 Czy job już nie działa w tle?
```sql
USE msdb;
SELECT
j.name,
a.start_execution_date,
a.stop_execution_date,
a.next_scheduled_run_date
FROM dbo.sysjobs j
LEFT JOIN dbo.sysjobactivity a
ON j.job_id = a.job_id
WHERE j.name = N'<JOB_NAME>'
ORDER BY a.start_execution_date DESC;
```
## 3) Decyzja (guardrails)
**Auto restart joba dozwolony tylko jeśli:**
* job jest w allowlist (np. ETL incremental, nie full rebuild),
* poprzednia porażka wynika z „transient” (timeout, network blip, temp deadlock),
* job nie modyfikuje krytycznych danych w sposób nieidempotentny **lub** ma checkpointy,
* job nie jest aktualnie uruchomiony.
## 4) Akcja (opcjonalnie)
### 4.1 Restart joba
```sql
USE msdb;
EXEC dbo.sp_start_job @job_name = N'<JOB_NAME>';
```
### 4.2 Jeśli job blokuje się przez inny proces
Zastosuj RUNBOOK 1 (blocking).
## 5) Raport
* Nazwa joba, step, message
* Ocena: transient vs deterministic
* Jeśli restart: czy zakończył się sukcesem + czas trwania
## 6) Escalacja
* job „full load” / migracje / joby destrukcyjne
* błąd logiczny (np. constraint violation, missing object)
* 2 kolejne porażki → człowiek
***
# RUNBOOK 3: Backup freshness / „nie ma backupów”
## 1) Trigger
* Alert: „Last full backup > X godzin/dni” (np. > 24h dla FULL)
* brak log backupów (RPO zagrożone)
## 2) Diag (readonly)
```sql
-- Ostatnie backupy FULL/DIFF/LOG na bazę
SELECT
d.name AS database_name,
MAX(CASE WHEN b.type = 'D' THEN b.backup_finish_date END) AS last_full,
MAX(CASE WHEN b.type = 'I' THEN b.backup_finish_date END) AS last_diff,
MAX(CASE WHEN b.type = 'L' THEN b.backup_finish_date END) AS last_log
FROM sys.databases d
LEFT JOIN msdb.dbo.backupset b
ON b.database_name = d.name
GROUP BY d.name
ORDER BY d.name;
```
Dodatkowo sprawdź recovery model:
```sql
SELECT name, recovery_model_desc
FROM sys.databases
ORDER BY name;
```
## 3) Decyzja (guardrails)
**Autobackup dozwolony tylko jeśli:**
* masz zatwierdzoną politykę backupów i miejsce docelowe jest zweryfikowane,
* agent ma uprawnienia i ścieżkę docelową,
* masz monitorowanie miejsca na dysku (RUNBOOK 4).
## 4) Akcja (opcjonalnie)
Przykład FULL backup:
```sql
BACKUP DATABASE [<DB>]
TO DISK = N'<PATH>\<DB>_FULL.bak'
WITH INIT, COMPRESSION, CHECKSUM, STATS = 10;
```
Przykład LOG backup (tylko FULL/BULK\_LOGGED):
```sql
BACKUP LOG [<DB>]
TO DISK = N'<PATH>\<DB>_LOG.trn'
WITH INIT, COMPRESSION, CHECKSUM, STATS = 10;
```
## 5) Raport
* Lista baz niespełniających RPO
* Recovery model
* Jeśli wykonano backup: czas + wynik + ścieżka
## 6) Escalacja
* brak miejsca / błąd IO
* brak uprawnień
* backupy nie przechodzą CHECKSUM
***
# RUNBOOK 4: Disk space / „dysk się kończy” (MDF/LDF/backup)
## 1) Trigger
* Alert: wolne miejsce < 1015% na volume
* SQL błędy o braku miejsca, autogrowth fails.
## 2) Diag (readonly)
### 2.1 Pliki baz (rozmiar i autogrowth)
```sql
SELECT
DB_NAME(database_id) AS db_name,
name AS file_name,
type_desc,
size/128.0 AS size_mb,
max_size,
growth,
is_percent_growth,
physical_name
FROM sys.master_files
ORDER BY db_name, type_desc;
```
### 2.2 Top bazy po rozmiarze danych i logu (przybliżenie)
```sql
-- szybkie przybliżenie: ile zajmuje data/log per baza
SELECT
DB_NAME(database_id) AS db_name,
SUM(CASE WHEN type_desc = 'ROWS' THEN size END)/128.0 AS data_mb,
SUM(CASE WHEN type_desc = 'LOG' THEN size END)/128.0 AS log_mb
FROM sys.master_files
GROUP BY database_id
ORDER BY (SUM(size)/128.0) DESC;
```
### 2.3 Czy logi puchną przez brak log backupów?
* jeśli recovery FULL i brak log backupów → RUNBOOK 3.
* jeśli long transaction → RUNBOOK 1 + transakcje.
## 3) Decyzja (guardrails)
**Autoremediation dozwolona tylko jeśli:**
* dotyczy plików backupów / temp artefaktów (bez kasowania danych),
* masz listę ścieżek do czyszczenia (allowlist),
* nie kasujesz niczego „niezrozumiałego”.
## 4) Akcja (opcjonalnie)
* rotacja starych backupów (tylko jeśli masz politykę retencji!)
* czyszczenie katalogu logów aplikacji (tylko allowlist)
* w SQL Server: rozszerzenie pliku (preferowane ręcznie, ale można kontrolować)
Przykład rozszerzenia pliku danych (ostrożnie!):
```sql
ALTER DATABASE [<DB>]
MODIFY FILE (NAME = N'<DATA_FILE_LOGICAL_NAME>', SIZE = <NEW_SIZE_MB>MB);
```
## 5) Raport
* które wolumeny, ile % free, trend (jeśli masz)
* top 5 plików rosnących
* rekomendacja: zwiększyć volume / zmienić autogrowth / poprawić backup plan
## 6) Escalacja
* < 5% wolnego miejsca
* autogrowth fail na krytycznej bazie
* nieznany konsument miejsca
***
# RUNBOOK 5: Wysokie CPU / „SQL zjada procesor”
## 1) Trigger
* CPU > 90% przez > 510 min
* top waits: SOS\_SCHEDULER\_YIELD, CXPACKET/CXCONSUMER (zależnie), THREADPOOL, itp.
## 2) Diag (readonly)
### 2.1 Top zapytania po CPU (z cache)
```sql
SELECT TOP (20)
qs.total_worker_time / 1000.0 AS total_cpu_ms,
qs.execution_count,
(qs.total_worker_time / NULLIF(qs.execution_count,0)) / 1000.0 AS avg_cpu_ms,
qs.total_elapsed_time / 1000.0 AS total_elapsed_ms,
SUBSTRING(st.text, (qs.statement_start_offset/2)+1,
((CASE qs.statement_end_offset WHEN -1 THEN DATALENGTH(st.text)
ELSE qs.statement_end_offset END - qs.statement_start_offset)/2)+1) AS statement_text
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) st
ORDER BY qs.total_worker_time DESC;
```
### 2.2 Aktualnie wykonujące się zapytania (real time)
```sql
SELECT TOP (30)
r.session_id,
r.status,
r.cpu_time,
r.total_elapsed_time,
r.logical_reads,
r.reads,
r.writes,
r.wait_type,
r.wait_time,
DB_NAME(r.database_id) AS db_name,
t.text
FROM sys.dm_exec_requests r
CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) t
WHERE r.session_id <> @@SPID
ORDER BY r.cpu_time DESC;
```
### 2.3 Top waits (od startu instancji do trendu lepiej mieć monitoring)
```sql
SELECT TOP (15)
wait_type,
wait_time_ms,
100.0 * wait_time_ms / SUM(wait_time_ms) OVER() AS pct
FROM sys.dm_os_wait_stats
WHERE wait_type NOT LIKE '%SLEEP%'
ORDER BY wait_time_ms DESC;
```
## 3) Decyzja (guardrails)
**Autoakcje są ryzykowne** (np. RECOMPILE, plan forcing, kill).
W trybie nocnym zwykle:
* zbierasz dane,
* identyfikujesz top offender,
* jeśli to „runaway query” z allowlisty (np. batch), możesz ją przerwać.
## 4) Akcja (opcjonalnie, ostrożnie)
* jeśli to znany batch i zjada CPU → przerwanie joba (jak w RUNBOOK 2) lub `KILL` (jak RUNBOOK 1)
* **unikaj** automatycznego `DBCC FREEPROCCACHE` i podobnych „nuklearnych” działań
## 5) Raport
* Top 5 statements (CPU)
* session\_id i query text dla aktywnych
* top waits
* rekomendacja: indeks / rewrite / parametr sniffing mitigation (do dziennego PR)
## 6) Escalacja
* CPU pegged + SLA impact
* THREADPOOL / RESOURCE\_SEMAPHORE (mogą oznaczać poważniejsze problemy)
* brak identyfikowalnego top offender
***
# RUNBOOK 6: TempDB rośnie / braki miejsca / contention
## 1) Trigger
* tempdb file growth alerts
* PFS/GAM contention w monitoringach
* błędy „Could not allocate space for object in database 'tempdb'”.
## 2) Diag (readonly)
### 2.1 Kto używa tempdb teraz (przybliżenie)
```sql
SELECT TOP (20)
s.session_id,
s.login_name,
s.host_name,
s.program_name,
(su.user_objects_alloc_page_count - su.user_objects_dealloc_page_count) * 8.0 / 1024 AS user_obj_mb,
(su.internal_objects_alloc_page_count - su.internal_objects_dealloc_page_count) * 8.0 / 1024 AS internal_obj_mb
FROM sys.dm_db_session_space_usage su
JOIN sys.dm_exec_sessions s ON su.session_id = s.session_id
ORDER BY (su.internal_objects_alloc_page_count - su.internal_objects_dealloc_page_count) DESC;
```
### 2.2 Aktywne zapytania z dużymi sort/hash (często winowajcy tempdb)
```sql
SELECT TOP (30)
r.session_id,
r.status,
r.total_elapsed_time,
r.logical_reads,
r.writes,
r.wait_type,
t.text
FROM sys.dm_exec_requests r
CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) t
ORDER BY r.writes DESC;
```
## 3) Decyzja
W nocy najczęściej tylko diagnoza + raport.
Autoakcje (zmiana konfiguracji tempdb) zwykle wymagają okna i rebootu.
## 4) Akcja (opcjonalnie)
* przerwać runaway batch (po allowlist i guardrails)
## 5) Raport
* top sessions tempdb usage
* query text
* rekomendacja: rewrite (np. ograniczyć sorty), indeksy, batch size
***
# Jak to spiąć w automatyzację „nocnego DBA” (OpenClaw)
## Minimalny model operacyjny (polecam)
* **Zawsze** wykonuj część **Diag** i **Raport**.
* Część **Akcja** wykonuj tylko gdy:
1. incydent jest w typie „safe auto” (np. restart idempotent joba),
2. jest allowlist,
3. spełnione warunki czasu i wpływu,
4. masz rollback / stop.
## Artefakty (co zapisywać do folderu incydentu)
* `incident.json` (czas, typ, db, severity)
* `diag_blocking.sqlout`, `diag_jobs.sqlout`, etc.
* `recommendation.md` (ludzkim językiem)
* `action_log.md` (co agent zrobił)
***
# Chcesz, żebym dopasował runbooki do Twojego stacku?
Żeby to było „produkcyjne”, daj mi 3 informacje (krótko, jednym zdaniem każde):
1. **SQL Server wersja** (np. 2017/2019/2022) i czy masz AlwaysOn?
2. **Monitoring/alerty**: Splunk/SCOM/Zabbix/Prometheus/SQL Agent?
3. Repo: GitHub / GitLab / Azure DevOps?
Na tej podstawie dopiszę:
* wersję runbooków pod AlwaysOn (secondary, backup preferences),
* gotowe progi (thresholds) i severity,
* i „safe allowlist policy” (które joby wolno restartować, które sesje wolno killować).
Jasne — poniżej masz **konkretne runbooki pod SQL Server Always On Availability Groups**. Zrobiłem je tak, żeby:
* dało się je wykonać **ręcznie** (oncall/DBA),
* dało się je łatwo „opakować” pod **nocnego DBA** (OpenClaw: diag → decyzja → akcja → raport),
* miały **guardraile** (kiedy wolno autofailover / kiedy absolutnie nie),
* zawierały gotowe **TSQL** do diagnostyki i do bezpiecznych akcji.
> **Uwaga:** nazwy DMV i procedur są standardowe dla Always On AG (SQL Server 2012+). Jeśli masz 2019/2022 i używasz **Distributed AG** albo **Basic AG**, daj znać — dopracuję warianty.
***
# Wspólne „przed-startem” (dla wszystkich runbooków AlwaysOn)
## A. Kontekst incydentu (zawsze zbierz)
**1) Jaki AG i jaka baza?**
**2) Która replika jest PRIMARY?**
**3) Jaki jest tryb failover (AUTOMATIC/MANUAL) i sync (SYNCHRONOUS/ASYNC)?**
**4) Czy to jest okno serwisowe?**
### 0.1 Szybki „AG overview”
```sql
-- Gdzie jestem? Czy instancja ma HADR włączone?
SELECT SERVERPROPERTY('IsHadrEnabled') AS IsHadrEnabled;
-- Podstawowe dane o AG i replikach
SELECT
ag.name AS ag_name,
ar.replica_server_name,
ar.availability_mode_desc,
ar.failover_mode_desc,
ar.primary_role_allow_connections_desc,
ar.secondary_role_allow_connections_desc
FROM sys.availability_groups ag
JOIN sys.availability_replicas ar
ON ag.group_id = ar.group_id
ORDER BY ag.name, ar.replica_server_name;
```
### 0.2 Która replika jest PRIMARY teraz?
```sql
SELECT
ag.name AS ag_name,
ars.role_desc,
ar.replica_server_name
FROM sys.dm_hadr_availability_replica_states ars
JOIN sys.availability_replicas ar
ON ars.replica_id = ar.replica_id
JOIN sys.availability_groups ag
ON ar.group_id = ag.group_id
ORDER BY ag.name, ars.role_desc DESC;
```
***
# RUNBOOK AO-1: „Replica down / Not connected” (utrata połączenia z repliką)
## 1) Trigger
* alert: **replica state = NOT\_CONNECTED / DISCONNECTED**
* Dashboard pokazuje „red” na replikach
* rośnie opóźnienie, brak log send/redo
## 2) Diag (readonly)
### 2.1 Stan połączeń i zdrowia
```sql
SELECT
ag.name AS ag_name,
ar.replica_server_name,
ars.role_desc,
ars.connected_state_desc,
ars.operational_state_desc,
ars.recovery_health_desc,
ars.synchronization_health_desc,
ars.last_connect_error_number,
ars.last_connect_error_description
FROM sys.dm_hadr_availability_replica_states ars
JOIN sys.availability_replicas ar
ON ars.replica_id = ar.replica_id
JOIN sys.availability_groups ag
ON ar.group_id = ag.group_id
ORDER BY ag.name, ar.replica_server_name;
```
### 2.2 Czy problem dotyczy endpointu HADR?
```sql
SELECT
name,
state_desc,
port,
role_desc,
connection_auth_desc
FROM sys.database_mirroring_endpoints;
```
### 2.3 Jeśli masz dostęp do hosta: sprawdź usługi i sieć (ręcznie)
* SQL Server service running?
* Firewall/port endpointu (domyślnie 5022, jeśli nie zmieniałeś)
* DNS/AD issue
## 3) Decyzja (guardrails)
**Nie rób failover tylko dlatego, że secondary jest down.**
Failover rozważasz tylko gdy:
* PRIMARY ma problemy z dostępnością **dla aplikacji**,
* masz gotową replikę w stanie **SYNCHRONIZED** (dla planned/auto) albo akceptujesz ryzyko utraty danych (forced).
## 4) Akcja (zwykle operacyjna, poza SQL)
* restart usługi SQL na sekundarce (jeśli to ona padła)
* korekta firewall/port
* jeśli endpoint „stopped” → start endpointu:
```sql
-- Tylko jeśli endpoint jest STOPPED
ALTER ENDPOINT [Hadr_endpoint] STATE = STARTED;
```
## 5) Raport
* ag\_name, replica\_server\_name
* connected\_state\_desc / operational\_state\_desc
* last\_connect\_error\_description
* co zrobiono (restart usługi / endpoint / network)
## 6) Escalacja
* NOT\_CONNECTED na więcej niż 1 replice
* last\_connect\_error wskazuje na certy/permission (wymaga głębszej interwencji)
* problem wraca cyklicznie
***
# RUNBOOK AO-2: „Data movement suspended” (zatrzymana synchronizacja bazy)
## 1) Trigger
* alert: **synchronization\_state = SUSPENDED**
* brak postępu redo/log send
## 2) Diag (readonly)
### 2.1 Stan bazy w AG
```sql
SELECT
ag.name AS ag_name,
dbcs.database_name,
drs.is_primary_replica,
drs.synchronization_state_desc,
drs.synchronization_health_desc,
drs.suspend_reason_desc,
drs.log_send_queue_size,
drs.redo_queue_size,
drs.last_commit_time,
drs.last_hardened_time,
drs.last_redone_time
FROM sys.dm_hadr_database_replica_states drs
JOIN sys.availability_databases_cluster dbcs
ON drs.group_database_id = dbcs.group_database_id
JOIN sys.availability_groups ag
ON dbcs.group_id = ag.group_id
ORDER BY ag.name, dbcs.database_name, drs.is_primary_replica DESC;
```
### 2.2 Czy to jest „planned suspend” czy error?
Patrz `suspend_reason_desc` (np. USER\_SUSPEND vs error).
## 3) Decyzja (guardrails)
**Jeśli suspend jest USER\_SUSPEND** (ktoś celowo), najpierw sprawdź zmiany/okno serwisowe.
**Jeśli suspend jest z błędu** (np. IO, log corruption), nie wznawiaj w ciemno — zbierz błędy z error log i sprawdź storage.
## 4) Akcja (opcjonalnie, ostrożnie)
### 4.1 Wznowienie data movement (tylko gdy wiesz czemu było suspend)
```sql
ALTER DATABASE [TwojaBaza] SET HADR RESUME;
```
### 4.2 Jeśli baza „wisi” na secondary i queue rośnie
* sprawdź CPU/IO na secondary (redo może nie wyrabiać)
* rozważ „read intent workload” lub inne obciążenie
## 5) Raport
* suspend\_reason\_desc
* queue sizes (log\_send\_queue\_size, redo\_queue\_size)
* timestamps last\_\* (commit/hardened/redone)
* czy i kiedy wykonano RESUME
## 6) Escalacja
* suspend wraca po RESUME
* rosną queue + spada wydajność
* podejrzenie problemu storage/IO
***
# RUNBOOK AO-3: „Lag / duże kolejki redo i log send” (secondary nie nadąża)
## 1) Trigger
* `redo_queue_size` lub `log_send_queue_size` przekracza threshold (np. > 15 GB w zależności od RPO)
* aplikacja ma opóźnione odczyty (read-only routing)
## 2) Diag (readonly)
```sql
SELECT
ag.name AS ag_name,
dbcs.database_name,
ar.replica_server_name,
drs.is_primary_replica,
drs.synchronization_state_desc,
drs.log_send_rate,
drs.redo_rate,
drs.log_send_queue_size,
drs.redo_queue_size,
drs.last_commit_time,
drs.last_redone_time,
DATEDIFF(SECOND, drs.last_redone_time, drs.last_commit_time) AS approx_lag_seconds
FROM sys.dm_hadr_database_replica_states drs
JOIN sys.availability_replicas ar
ON drs.replica_id = ar.replica_id
JOIN sys.availability_databases_cluster dbcs
ON drs.group_database_id = dbcs.group_database_id
JOIN sys.availability_groups ag
ON dbcs.group_id = ag.group_id
ORDER BY ag.name, dbcs.database_name, drs.is_primary_replica DESC, approx_lag_seconds DESC;
```
Dodatkowo na sekundarce:
* IO latency (perf counters / sys.dm\_io\_virtual\_file\_stats)
* CPU pressure
* czy antywirus / backup nie blokuje logów
## 3) Decyzja (guardrails)
Tu zwykle **nie robisz automatycznych akcji** poza ograniczeniem obciążenia.
Opcje:
* czasowo wyłączyć ciężkie zapytania read-only na sekundarce,
* zmienić routing,
* jeśli to planned maintenance → zaakceptować lag.
## 4) Akcja (opcjonalnie)
* Jeśli secondary jest przeciążona przez read workload: przenieś read-only routing.
* Jeśli log send stoi: diagnozuj sieć/endpoint (AO1).
## 5) Raport
* lag w sekundach, queue sizes
* czy problem dotyczy jednej bazy czy całego AG
* rekomendacja: tuning IO, separacja plików log, weryfikacja sieci
***
# RUNBOOK AO-4: „Failover planned (bez utraty danych)” — kontrolowany switch PRIMARY
> To jest runbook na **planowany failover** (maintenance / patching), kiedy chcesz zachować spójność.
## 1) Warunki wejścia (twarde)
* replika docelowa ma `availability_mode_desc = SYNCHRONOUS_COMMIT`
* `synchronization_state_desc = SYNCHRONIZED` dla baz krytycznych
* failover mode może być MANUAL (ok), AUTOMATIC (ok)
## 2) Diag (readonly) potwierdzenie synchronizacji
```sql
SELECT
dbcs.database_name,
ar.replica_server_name,
drs.synchronization_state_desc,
drs.synchronization_health_desc,
drs.log_send_queue_size,
drs.redo_queue_size
FROM sys.dm_hadr_database_replica_states drs
JOIN sys.availability_replicas ar
ON drs.replica_id = ar.replica_id
JOIN sys.availability_databases_cluster dbcs
ON drs.group_database_id = dbcs.group_database_id
WHERE dbcs.database_name IN ('<DB1>','<DB2>')
ORDER BY dbcs.database_name, ar.replica_server_name;
```
## 3) Akcja planned failover
Na aktualnym PRIMARY:
```sql
ALTER AVAILABILITY GROUP [TwojAG] FAILOVER;
```
## 4) Walidacja po failover
```sql
-- Sprawdź role
SELECT
ag.name, ar.replica_server_name, ars.role_desc, ars.connected_state_desc,
ars.synchronization_health_desc
FROM sys.dm_hadr_availability_replica_states ars
JOIN sys.availability_replicas ar
ON ars.replica_id = ar.replica_id
JOIN sys.availability_groups ag
ON ar.group_id = ag.group_id
WHERE ag.name = N'TwojAG';
-- Sprawdź czy bazy wróciły do zdrowia
SELECT
dbcs.database_name,
drs.is_primary_replica,
drs.synchronization_state_desc,
drs.synchronization_health_desc
FROM sys.dm_hadr_database_replica_states drs
JOIN sys.availability_databases_cluster dbcs
ON drs.group_database_id = dbcs.group_database_id
WHERE dbcs.database_name IN ('<DB1>','<DB2>')
ORDER BY dbcs.database_name, drs.is_primary_replica DESC;
```
## 5) Raport
* czas, stary primary → nowy primary
* stan synchronizacji przed i po
* ewentualne błędy aplikacyjne w oknie
***
# RUNBOOK AO-5: „Failover forced (ryzyko utraty danych)” — tylko w awarii
> Ten runbook jest **break-glass**. Używaj tylko jeśli PRIMARY jest martwy/niedostępny i biznes akceptuje możliwość utraty danych.
## 1) Warunki wejścia
* PRIMARY **nieosiągalny**
* brak możliwości planned failover
* decyzja biznesowa: „wznawiamy usługę, nawet kosztem RPO”
## 2) Diag (readonly) oceń ryzyko
* na sekundarce sprawdź lag/queues (AO3)
* jeśli lag duży → potencjalna utrata danych
## 3) Akcja forced failover (na sekundarce)
```sql
ALTER AVAILABILITY GROUP [TwojAG] FORCE_FAILOVER_ALLOW_DATA_LOSS;
```
## 4) Walidacja i porządkowanie
* sprawdź role (jak w AO4)
* kiedy stary PRIMARY wróci: może wymagać rejoin/reseed
## 5) Raport (krytyczny)
* przyczyna forced
* szacowany lag (queue + approx\_lag\_seconds)
* decyzja i kto zatwierdził
* lista baz do weryfikacji spójności (DBCC CHECKDB później, w oknie)
***
# RUNBOOK AO-6: „Backup i preferencje w AlwaysOn” (czy backupy idą tam gdzie trzeba?)
## 1) Trigger
* brak backupów mimo działających jobów
* backup wykonywany na nieoczekiwanej replice
* job fail: “This BACKUP or RESTORE command is not supported on a database mirror or secondary replica…”
## 2) Diag (readonly)
### 2.1 Jak ustawione są preferencje backupów na AG?
```sql
SELECT
name AS ag_name,
automated_backup_preference_desc
FROM sys.availability_groups;
```
### 2.2 Czy dana replika jest „preferred backup replica”?
```sql
SELECT
ag.name AS ag_name,
ar.replica_server_name,
sys.fn_hadr_backup_is_preferred_replica(ag.name) AS is_preferred_on_this_instance
FROM sys.availability_groups ag
JOIN sys.availability_replicas ar
ON ag.group_id = ar.group_id
ORDER BY ag.name, ar.replica_server_name;
```
> `fn_hadr_backup_is_preferred_replica` zwraca wynik **dla instancji, na której to uruchamiasz**, więc odpal to na każdej replice, jeśli chcesz pełny obraz.
## 3) Decyzja (guardrails)
* Jeśli backupy mają iść na SECONDARY, upewnij się, że:
* joby backupowe istnieją na tej replice,
* konto ma dostęp do storage,
* SECONDARY nie jest „zajechana” read workload.
## 4) Akcja (typowo: korekta jobów)
* joby backupowe powinny sprawdzać `fn_hadr_backup_is_preferred_replica` i **wychodzić** bez błędu, jeśli nie są na preferowanej replice.
Przykładowy pattern w job step:
```sql
IF (sys.fn_hadr_backup_is_preferred_replica(DB_NAME()) = 0)
BEGIN
PRINT 'Not preferred replica for backup. Exiting.';
RETURN;
END
-- tutaj właściwy BACKUP DATABASE/LOG
```
## 5) Raport
* ag\_name, backup preference
* gdzie backupy faktycznie wykonywano (msdb.backupset)
* rekomendacja zmian jobów/retencji
***
# RUNBOOK AO-7: „Listener/DNS: aplikacja nie łączy się po failover”
## 1) Trigger
* po failover aplikacja nie łączy się przez listener
* błędy DNS / connection timeout
## 2) Diag (readonly)
W SQL:
```sql
-- Listener i jego parametry
SELECT
ag.name AS ag_name,
l.dns_name,
l.port,
l.is_conformant
FROM sys.availability_group_listeners l
JOIN sys.availability_groups ag
ON l.group_id = ag.group_id;
```
Po stronie infrastruktury (ręcznie):
* czy DNS wskazuje poprawnie?
* czy port listenera jest otwarty?
* czy klient ma poprawny connection string (MultiSubnetFailover?)
## 3) Akcja
* zwykle: infra/DNS/network team
* jeśli MultiSubnet: włącz `MultiSubnetFailover=True` w connection string dla szybszego przełączania
## 4) Raport
* dns\_name/port
* czas failover i czas niedostępności
* rekomendacje dla app team
***
# Jak to „przerobić” na OpenClaw nocnego DBA (praktyczny szkielet)
Dla AlwaysOn polecam podejście **3 poziomów reakcji**:
### Poziom 1 (zawsze automatyczny): DIAG + RAPORT
* AO1/AO2/AO3/AO6/AO7: **zawsze** można zebrać dane i wysłać raport.
### Poziom 2 (warunkowy, ostrożny): NAPRAWA „bezpieczna”
* `ALTER DATABASE ... HADR RESUME` tylko jeśli `suspend_reason_desc = USER_SUSPEND` albo incydent ma znaną przyczynę.
* restart joba backupowego na preferowanej replice.
### Poziom 3 (break-glass): FAILOVER
* AO4 planned: tylko w oknie serwisowym i przy SYNCHRONIZED.
* AO5 forced: tylko po zatwierdzeniu (human-in-the-loop), bo to decyzja biznesowa.
***
## Chcesz, żebym dopasował te runbooki do Twojej topologii?
Żeby było **100% „produkcyjne”**, napisz proszę króciutko:
1. Ile replik i czy to **multi-subnet**?
2. Czy masz **synchronous** między DC, czy asynch?
3. Jak realizujesz backupy: na PRIMARY czy na SECONDARY?
Na tej podstawie dopiszę Ci:
* konkretne **progi alertów** (lag/queue) pod Twoje RPO,
* wzorzec **automatycznego raportu** (Markdown + JSON artefakty),
* i wariant runbooków dla scenariuszy typu „split brain / quorum / cluster node down”.
Jasne — poniżej masz **konkretne runbooki pod SQL Server Always On Availability Groups**. Zrobiłem je tak, żeby:
* dało się je wykonać **ręcznie** (oncall/DBA),
* dało się je łatwo „opakować” pod **nocnego DBA** (OpenClaw: diag → decyzja → akcja → raport),
* miały **guardraile** (kiedy wolno autofailover / kiedy absolutnie nie),
* zawierały gotowe **TSQL** do diagnostyki i do bezpiecznych akcji.
> **Uwaga:** nazwy DMV i procedur są standardowe dla Always On AG (SQL Server 2012+). Jeśli masz 2019/2022 i używasz **Distributed AG** albo **Basic AG**, daj znać — dopracuję warianty.
***
# Wspólne „przed-startem” (dla wszystkich runbooków AlwaysOn)
## A. Kontekst incydentu (zawsze zbierz)
**1) Jaki AG i jaka baza?**
**2) Która replika jest PRIMARY?**
**3) Jaki jest tryb failover (AUTOMATIC/MANUAL) i sync (SYNCHRONOUS/ASYNC)?**
**4) Czy to jest okno serwisowe?**
### 0.1 Szybki „AG overview”
```sql
-- Gdzie jestem? Czy instancja ma HADR włączone?
SELECT SERVERPROPERTY('IsHadrEnabled') AS IsHadrEnabled;
-- Podstawowe dane o AG i replikach
SELECT
ag.name AS ag_name,
ar.replica_server_name,
ar.availability_mode_desc,
ar.failover_mode_desc,
ar.primary_role_allow_connections_desc,
ar.secondary_role_allow_connections_desc
FROM sys.availability_groups ag
JOIN sys.availability_replicas ar
ON ag.group_id = ar.group_id
ORDER BY ag.name, ar.replica_server_name;
```
### 0.2 Która replika jest PRIMARY teraz?
```sql
SELECT
ag.name AS ag_name,
ars.role_desc,
ar.replica_server_name
FROM sys.dm_hadr_availability_replica_states ars
JOIN sys.availability_replicas ar
ON ars.replica_id = ar.replica_id
JOIN sys.availability_groups ag
ON ar.group_id = ag.group_id
ORDER BY ag.name, ars.role_desc DESC;
```
***
# RUNBOOK AO-1: „Replica down / Not connected” (utrata połączenia z repliką)
## 1) Trigger
* alert: **replica state = NOT\_CONNECTED / DISCONNECTED**
* Dashboard pokazuje „red” na replikach
* rośnie opóźnienie, brak log send/redo
## 2) Diag (readonly)
### 2.1 Stan połączeń i zdrowia
```sql
SELECT
ag.name AS ag_name,
ar.replica_server_name,
ars.role_desc,
ars.connected_state_desc,
ars.operational_state_desc,
ars.recovery_health_desc,
ars.synchronization_health_desc,
ars.last_connect_error_number,
ars.last_connect_error_description
FROM sys.dm_hadr_availability_replica_states ars
JOIN sys.availability_replicas ar
ON ars.replica_id = ar.replica_id
JOIN sys.availability_groups ag
ON ar.group_id = ag.group_id
ORDER BY ag.name, ar.replica_server_name;
```
### 2.2 Czy problem dotyczy endpointu HADR?
```sql
SELECT
name,
state_desc,
port,
role_desc,
connection_auth_desc
FROM sys.database_mirroring_endpoints;
```
### 2.3 Jeśli masz dostęp do hosta: sprawdź usługi i sieć (ręcznie)
* SQL Server service running?
* Firewall/port endpointu (domyślnie 5022, jeśli nie zmieniałeś)
* DNS/AD issue
## 3) Decyzja (guardrails)
**Nie rób failover tylko dlatego, że secondary jest down.**
Failover rozważasz tylko gdy:
* PRIMARY ma problemy z dostępnością **dla aplikacji**,
* masz gotową replikę w stanie **SYNCHRONIZED** (dla planned/auto) albo akceptujesz ryzyko utraty danych (forced).
## 4) Akcja (zwykle operacyjna, poza SQL)
* restart usługi SQL na sekundarce (jeśli to ona padła)
* korekta firewall/port
* jeśli endpoint „stopped” → start endpointu:
```sql
-- Tylko jeśli endpoint jest STOPPED
ALTER ENDPOINT [Hadr_endpoint] STATE = STARTED;
```
## 5) Raport
* ag\_name, replica\_server\_name
* connected\_state\_desc / operational\_state\_desc
* last\_connect\_error\_description
* co zrobiono (restart usługi / endpoint / network)
## 6) Escalacja
* NOT\_CONNECTED na więcej niż 1 replice
* last\_connect\_error wskazuje na certy/permission (wymaga głębszej interwencji)
* problem wraca cyklicznie
***
# RUNBOOK AO-2: „Data movement suspended” (zatrzymana synchronizacja bazy)
## 1) Trigger
* alert: **synchronization\_state = SUSPENDED**
* brak postępu redo/log send
## 2) Diag (readonly)
### 2.1 Stan bazy w AG
```sql
SELECT
ag.name AS ag_name,
dbcs.database_name,
drs.is_primary_replica,
drs.synchronization_state_desc,
drs.synchronization_health_desc,
drs.suspend_reason_desc,
drs.log_send_queue_size,
drs.redo_queue_size,
drs.last_commit_time,
drs.last_hardened_time,
drs.last_redone_time
FROM sys.dm_hadr_database_replica_states drs
JOIN sys.availability_databases_cluster dbcs
ON drs.group_database_id = dbcs.group_database_id
JOIN sys.availability_groups ag
ON dbcs.group_id = ag.group_id
ORDER BY ag.name, dbcs.database_name, drs.is_primary_replica DESC;
```
### 2.2 Czy to jest „planned suspend” czy error?
Patrz `suspend_reason_desc` (np. USER\_SUSPEND vs error).
## 3) Decyzja (guardrails)
**Jeśli suspend jest USER\_SUSPEND** (ktoś celowo), najpierw sprawdź zmiany/okno serwisowe.
**Jeśli suspend jest z błędu** (np. IO, log corruption), nie wznawiaj w ciemno — zbierz błędy z error log i sprawdź storage.
## 4) Akcja (opcjonalnie, ostrożnie)
### 4.1 Wznowienie data movement (tylko gdy wiesz czemu było suspend)
```sql
ALTER DATABASE [TwojaBaza] SET HADR RESUME;
```
### 4.2 Jeśli baza „wisi” na secondary i queue rośnie
* sprawdź CPU/IO na secondary (redo może nie wyrabiać)
* rozważ „read intent workload” lub inne obciążenie
## 5) Raport
* suspend\_reason\_desc
* queue sizes (log\_send\_queue\_size, redo\_queue\_size)
* timestamps last\_\* (commit/hardened/redone)
* czy i kiedy wykonano RESUME
## 6) Escalacja
* suspend wraca po RESUME
* rosną queue + spada wydajność
* podejrzenie problemu storage/IO
***
# RUNBOOK AO-3: „Lag / duże kolejki redo i log send” (secondary nie nadąża)
## 1) Trigger
* `redo_queue_size` lub `log_send_queue_size` przekracza threshold (np. > 15 GB w zależności od RPO)
* aplikacja ma opóźnione odczyty (read-only routing)
## 2) Diag (readonly)
```sql
SELECT
ag.name AS ag_name,
dbcs.database_name,
ar.replica_server_name,
drs.is_primary_replica,
drs.synchronization_state_desc,
drs.log_send_rate,
drs.redo_rate,
drs.log_send_queue_size,
drs.redo_queue_size,
drs.last_commit_time,
drs.last_redone_time,
DATEDIFF(SECOND, drs.last_redone_time, drs.last_commit_time) AS approx_lag_seconds
FROM sys.dm_hadr_database_replica_states drs
JOIN sys.availability_replicas ar
ON drs.replica_id = ar.replica_id
JOIN sys.availability_databases_cluster dbcs
ON drs.group_database_id = dbcs.group_database_id
JOIN sys.availability_groups ag
ON dbcs.group_id = ag.group_id
ORDER BY ag.name, dbcs.database_name, drs.is_primary_replica DESC, approx_lag_seconds DESC;
```
Dodatkowo na sekundarce:
* IO latency (perf counters / sys.dm\_io\_virtual\_file\_stats)
* CPU pressure
* czy antywirus / backup nie blokuje logów
## 3) Decyzja (guardrails)
Tu zwykle **nie robisz automatycznych akcji** poza ograniczeniem obciążenia.
Opcje:
* czasowo wyłączyć ciężkie zapytania read-only na sekundarce,
* zmienić routing,
* jeśli to planned maintenance → zaakceptować lag.
## 4) Akcja (opcjonalnie)
* Jeśli secondary jest przeciążona przez read workload: przenieś read-only routing.
* Jeśli log send stoi: diagnozuj sieć/endpoint (AO1).
## 5) Raport
* lag w sekundach, queue sizes
* czy problem dotyczy jednej bazy czy całego AG
* rekomendacja: tuning IO, separacja plików log, weryfikacja sieci
***
# RUNBOOK AO-4: „Failover planned (bez utraty danych)” — kontrolowany switch PRIMARY
> To jest runbook na **planowany failover** (maintenance / patching), kiedy chcesz zachować spójność.
## 1) Warunki wejścia (twarde)
* replika docelowa ma `availability_mode_desc = SYNCHRONOUS_COMMIT`
* `synchronization_state_desc = SYNCHRONIZED` dla baz krytycznych
* failover mode może być MANUAL (ok), AUTOMATIC (ok)
## 2) Diag (readonly) potwierdzenie synchronizacji
```sql
SELECT
dbcs.database_name,
ar.replica_server_name,
drs.synchronization_state_desc,
drs.synchronization_health_desc,
drs.log_send_queue_size,
drs.redo_queue_size
FROM sys.dm_hadr_database_replica_states drs
JOIN sys.availability_replicas ar
ON drs.replica_id = ar.replica_id
JOIN sys.availability_databases_cluster dbcs
ON drs.group_database_id = dbcs.group_database_id
WHERE dbcs.database_name IN ('<DB1>','<DB2>')
ORDER BY dbcs.database_name, ar.replica_server_name;
```
## 3) Akcja planned failover
Na aktualnym PRIMARY:
```sql
ALTER AVAILABILITY GROUP [TwojAG] FAILOVER;
```
## 4) Walidacja po failover
```sql
-- Sprawdź role
SELECT
ag.name, ar.replica_server_name, ars.role_desc, ars.connected_state_desc,
ars.synchronization_health_desc
FROM sys.dm_hadr_availability_replica_states ars
JOIN sys.availability_replicas ar
ON ars.replica_id = ar.replica_id
JOIN sys.availability_groups ag
ON ar.group_id = ag.group_id
WHERE ag.name = N'TwojAG';
-- Sprawdź czy bazy wróciły do zdrowia
SELECT
dbcs.database_name,
drs.is_primary_replica,
drs.synchronization_state_desc,
drs.synchronization_health_desc
FROM sys.dm_hadr_database_replica_states drs
JOIN sys.availability_databases_cluster dbcs
ON drs.group_database_id = dbcs.group_database_id
WHERE dbcs.database_name IN ('<DB1>','<DB2>')
ORDER BY dbcs.database_name, drs.is_primary_replica DESC;
```
## 5) Raport
* czas, stary primary → nowy primary
* stan synchronizacji przed i po
* ewentualne błędy aplikacyjne w oknie
***
# RUNBOOK AO-5: „Failover forced (ryzyko utraty danych)” — tylko w awarii
> Ten runbook jest **break-glass**. Używaj tylko jeśli PRIMARY jest martwy/niedostępny i biznes akceptuje możliwość utraty danych.
## 1) Warunki wejścia
* PRIMARY **nieosiągalny**
* brak możliwości planned failover
* decyzja biznesowa: „wznawiamy usługę, nawet kosztem RPO”
## 2) Diag (readonly) oceń ryzyko
* na sekundarce sprawdź lag/queues (AO3)
* jeśli lag duży → potencjalna utrata danych
## 3) Akcja forced failover (na sekundarce)
```sql
ALTER AVAILABILITY GROUP [TwojAG] FORCE_FAILOVER_ALLOW_DATA_LOSS;
```
## 4) Walidacja i porządkowanie
* sprawdź role (jak w AO4)
* kiedy stary PRIMARY wróci: może wymagać rejoin/reseed
## 5) Raport (krytyczny)
* przyczyna forced
* szacowany lag (queue + approx\_lag\_seconds)
* decyzja i kto zatwierdził
* lista baz do weryfikacji spójności (DBCC CHECKDB później, w oknie)
***
# RUNBOOK AO-6: „Backup i preferencje w AlwaysOn” (czy backupy idą tam gdzie trzeba?)
## 1) Trigger
* brak backupów mimo działających jobów
* backup wykonywany na nieoczekiwanej replice
* job fail: “This BACKUP or RESTORE command is not supported on a database mirror or secondary replica…”
## 2) Diag (readonly)
### 2.1 Jak ustawione są preferencje backupów na AG?
```sql
SELECT
name AS ag_name,
automated_backup_preference_desc
FROM sys.availability_groups;
```
### 2.2 Czy dana replika jest „preferred backup replica”?
```sql
SELECT
ag.name AS ag_name,
ar.replica_server_name,
sys.fn_hadr_backup_is_preferred_replica(ag.name) AS is_preferred_on_this_instance
FROM sys.availability_groups ag
JOIN sys.availability_replicas ar
ON ag.group_id = ar.group_id
ORDER BY ag.name, ar.replica_server_name;
```
> `fn_hadr_backup_is_preferred_replica` zwraca wynik **dla instancji, na której to uruchamiasz**, więc odpal to na każdej replice, jeśli chcesz pełny obraz.
## 3) Decyzja (guardrails)
* Jeśli backupy mają iść na SECONDARY, upewnij się, że:
* joby backupowe istnieją na tej replice,
* konto ma dostęp do storage,
* SECONDARY nie jest „zajechana” read workload.
## 4) Akcja (typowo: korekta jobów)
* joby backupowe powinny sprawdzać `fn_hadr_backup_is_preferred_replica` i **wychodzić** bez błędu, jeśli nie są na preferowanej replice.
Przykładowy pattern w job step:
```sql
IF (sys.fn_hadr_backup_is_preferred_replica(DB_NAME()) = 0)
BEGIN
PRINT 'Not preferred replica for backup. Exiting.';
RETURN;
END
-- tutaj właściwy BACKUP DATABASE/LOG
```
## 5) Raport
* ag\_name, backup preference
* gdzie backupy faktycznie wykonywano (msdb.backupset)
* rekomendacja zmian jobów/retencji
***
# RUNBOOK AO-7: „Listener/DNS: aplikacja nie łączy się po failover”
## 1) Trigger
* po failover aplikacja nie łączy się przez listener
* błędy DNS / connection timeout
## 2) Diag (readonly)
W SQL:
```sql
-- Listener i jego parametry
SELECT
ag.name AS ag_name,
l.dns_name,
l.port,
l.is_conformant
FROM sys.availability_group_listeners l
JOIN sys.availability_groups ag
ON l.group_id = ag.group_id;
```
Po stronie infrastruktury (ręcznie):
* czy DNS wskazuje poprawnie?
* czy port listenera jest otwarty?
* czy klient ma poprawny connection string (MultiSubnetFailover?)
## 3) Akcja
* zwykle: infra/DNS/network team
* jeśli MultiSubnet: włącz `MultiSubnetFailover=True` w connection string dla szybszego przełączania
## 4) Raport
* dns\_name/port
* czas failover i czas niedostępności
* rekomendacje dla app team
***
# Jak to „przerobić” na OpenClaw nocnego DBA (praktyczny szkielet)
Dla AlwaysOn polecam podejście **3 poziomów reakcji**:
### Poziom 1 (zawsze automatyczny): DIAG + RAPORT
* AO1/AO2/AO3/AO6/AO7: **zawsze** można zebrać dane i wysłać raport.
### Poziom 2 (warunkowy, ostrożny): NAPRAWA „bezpieczna”
* `ALTER DATABASE ... HADR RESUME` tylko jeśli `suspend_reason_desc = USER_SUSPEND` albo incydent ma znaną przyczynę.
* restart joba backupowego na preferowanej replice.
### Poziom 3 (break-glass): FAILOVER
* AO4 planned: tylko w oknie serwisowym i przy SYNCHRONIZED.
* AO5 forced: tylko po zatwierdzeniu (human-in-the-loop), bo to decyzja biznesowa.
***
## Chcesz, żebym dopasował te runbooki do Twojej topologii?
Żeby było **100% „produkcyjne”**, napisz proszę króciutko:
1. Ile replik i czy to **multi-subnet**?
2. Czy masz **synchronous** między DC, czy asynch?
3. Jak realizujesz backupy: na PRIMARY czy na SECONDARY?
Na tej podstawie dopiszę Ci:
* konkretne **progi alertów** (lag/queue) pod Twoje RPO,
* wzorzec **automatycznego raportu** (Markdown + JSON artefakty),
* i wariant runbooków dla scenariuszy typu „split brain / quorum / cluster node down”.
Pewnie — zepnę Ci to w **spójny model “Splunk Monitoring → alert → runbook (OpenClaw) → PR → review (Claude Code)”** dla MySQL. Poniżej masz **architekturę, konkretne kroki wdrożenia, przykładowe sourcetypy/HEC payloady, SPL do detekcji i alerty**.
> **Kluczowa idea:** Splunk robi to, w czym jest najlepszy: *zbieranie danych + korelacja + alerty*. OpenClaw robi *automatyzację reakcji* (runbooki). Claude Code robi *review i poprawki w repo*.
***
# 1) Architektura docelowa (3 strumienie danych)
## A. Dane do Splunka (monitoring)
Masz 3 główne klasy danych, które warto zebrać:
1. **MySQL error log** (logi)
2. **Metryki MySQL** (lag, threads, deadlocks, tmp tables itp.)
3. **Artefakty runbooków** (diag + decyzja + akcja) żebyś miał „audit trail”
Do tego są 2 typowe metody ingestu:
* **Forwarder / monitor files / scripted input** (dla logów i cyklicznych skryptów)
* **HEC (HTTP Event Collector)** do zdarzeń i wyników runbooków (szybkie i wygodne, bez forwardera po stronie “emitera”) [\[docs.splunk.com\]](https://docs.splunk.com/Documentation/Splunk/9.4.2/Data/UsetheHTTPEventCollector), [\[splunk.my.site.com\]](https://splunk.my.site.com/customer/s/article/Set-up-and-use-HTTP-Event-Collector-on-Heavy-Forwarder)
HEC jest token-based i służy do wysyłania eventów po HTTP/HTTPS bez potrzeby forwardera po stronie aplikacji. [\[docs.splunk.com\]](https://docs.splunk.com/Documentation/Splunk/9.4.2/Data/UsetheHTTPEventCollector), [\[kinneygroup.com\]](https://kinneygroup.com/blog/http-event-collector/)
## B. Alertowanie
* Splunk alerty mogą wywoływać **Webhook alert action** (HTTP POST z JSON payloadem). [\[help.splunk.com\]](https://help.splunk.com/en/splunk-enterprise/alert-and-respond/alerting-manual/9.4/configure-alert-actions/use-a-webhook-alert-action)
* W Splunk Enterprise 9.0+ URL webhooka musi być na allowliście (webhook allow list). [\[help.splunk.com\]](https://help.splunk.com/en/splunk-enterprise/alert-and-respond/alerting-manual/9.4/configure-alert-actions/use-a-webhook-alert-action)
## C. Reakcja
* Webhook z alertu trafia do **OpenClaw** (najlepiej do małej “bramki”/endpointu, o tym niżej), który:
1. odpala właściwy runbook (np. M1 lag, M3 locks),
2. zapisuje artefakty diagnostyki,
3. wysyła wynik do Splunka przez HEC,
4. (opcjonalnie) tworzy PR w repo.
***
# 2) Warianty integracji ze Splunkiem (wybierz jeden)
## Wariant 1 (najprostszy, praktyczny): UF/HF + HEC
**Dla MySQL hostów:**
* **UF** zbiera pliki logów (error log, slow log jeśli chcesz)
* Skrypt (cron) co 15 min odpytuje MySQL i wypisuje JSON → Splunk jako scripted input
* OpenClaw wyniki runbooków wysyła do Splunk przez **HEC** [\[docs.splunk.com\]](https://docs.splunk.com/Documentation/Splunk/9.4.2/Data/UsetheHTTPEventCollector), [\[kinneygroup.com\]](https://kinneygroup.com/blog/http-event-collector/)
**Plusy:** szybki start, mało aplikacji Splunkowych.
**Minusy:** musisz utrzymać skrypty.
## Wariant 2 (bardziej “enterprise”): Splunk DB Connect
* DB Connect jest przeznaczony do indeksowania danych z DB i ma rekomendacje architektoniczne (np. instalacja na HF do scheduled inputs w środowiskach rozproszonych). [\[docs.splunk.com\]](https://docs.splunk.com/Documentation/DBX/latest/DeployDBX/Architectureandperformanceconsiderations), [\[docs.splunk.com\]](https://docs.splunk.com/Documentation/DBX/latest/DeployDBX/AboutSplunkDBConnect)
* Dobre do pobierania “tabelarycznych” danych cyklicznie, ale pamiętaj o wpływie na wydajność bazy (szczególnie przy pierwszym pobraniu). [\[docs.splunk.com\]](https://docs.splunk.com/Documentation/DBX/latest/DeployDBX/Architectureandperformanceconsiderations)
**Plusy:** mniej własnego kodu, centralnie w Splunku.
**Minusy:** JDBC + konfiguracja, i nie wszystko z P\_S/STATUS jest super wygodne.
## Wariant 3 (najczystszy “platformowo”): Modular Input
* Budujesz własny Splunk Add-on z modular inputem, który pobiera metryki z MySQL i streamuje do Splunka jako natywny input. [\[dev.splunk.com\]](https://dev.splunk.com/enterprise/docs/developapps/manageknowledge/custominputs/modinputsoverview/), [\[docs.splunk.com\]](https://docs.splunk.com/Documentation/Splunk/9.4.2/AdvancedDev/ModInputsScripts)
**Plusy:** “produktowe”, konfigurowalne przez UI.
**Minusy:** najwięcej pracy.
👉 Dla Ciebie (szybki efekt) rekomenduję **Wariant 1**, a docelowo można dojść do 3.
***
# 3) Co dokładnie zbierać w Splunku (MySQL)
## 3.1 Logi (UF monitor)
* error log (`mysqld.log` / `error.log`)
* opcjonalnie slow log (do analizy, niekoniecznie alertów “nocnych”)
W Splunku przypisz sourcetype np.:
* `mysql:error`
* `mysql:slow`
## 3.2 Metryki (co 15 minut)
Zbieraj jako JSON eventy (łatwe w SPL, łatwe w HEC).
**Minimalny zestaw metryk** (bo daje Ci 80% wartości):
* `threads_running`, `threads_connected`
* `aborted_connects`
* replication: `Seconds_Behind_Source` + status IO/SQL thread
* `created_tmp_disk_tables` (pressure)
* deadlock count (z error log lub P\_S)
Jeśli chcesz metryki jako “metrics index”: Splunk opisuje format metryk i narzędzia typu collectd/statsd.
W praktyce do DBA najczęściej wystarczą JSON eventy + dashboardy. [\[help.splunk.com\]](https://help.splunk.com/en/splunk-cloud-platform/get-data-in/metrics/9.3.2408/introduction-to-metrics/get-started-with-metrics)
## 3.3 Wyniki runbooków (HEC)
Zawsze wysyłaj:
* `incident_id`
* `runbook_id` (M1, M3, GR1…)
* `severity`
* `decision` (report\_only / safe\_action / escalated)
* `actions[]` (jeśli coś wykonał)
* `artifacts` (ścieżki, krótkie streszczenia)
HEC przyjmuje eventy JSON, autoryzowane tokenem. [\[docs.splunk.com\]](https://docs.splunk.com/Documentation/Splunk/9.4.2/Data/UsetheHTTPEventCollector), [\[kinneygroup.com\]](https://kinneygroup.com/blog/http-event-collector/)
***
# 4) Jak spiąć alert w Splunku → OpenClaw (Webhook)
Splunk webhook alert action robi **HTTP POST** z JSONem, zawierając m.in. SID, link do wyników i pierwszy wynik z searcha.
W Splunk Enterprise 9+ musisz dodać docelowy URL do allowlisty. [\[help.splunk.com\]](https://help.splunk.com/en/splunk-enterprise/alert-and-respond/alerting-manual/9.4/configure-alert-actions/use-a-webhook-alert-action)
## Rekomendowany wzorzec (najbezpieczniejszy)
1. Splunk webhook trafia do **małego endpointu** (np. API Gateway / nginx / funkcja serverless)
2. Ten endpoint:
* weryfikuje źródło (allowlist IP / secret),
* mapuje alert → `runbook_id`,
* dopiero potem przekazuje do OpenClaw (wewnętrznie, po VPN/tailnet)
To minimalizuje ryzyko, że ktoś “na zewnątrz” odpali Ci runbook.
(Jeśli chcesz, rozpiszę gotowy payload i schemat walidacji.)
***
# 5) Konkretne SPL: detekcje i alerty pod runbooki MySQL
Załóżmy indeks `dbops` i sourcetype `mysql:metrics` (JSON).
## Alert A: Replikacja lag > 300s (RUNBOOK M1)
```spl
index=dbops sourcetype=mysql:metrics metric=replication_lag_seconds
| stats latest(value) as lag latest(source_host) as source latest(replica_host) as replica by instance
| where lag > 300
```
**Alert action:** Webhook do OpenClaw z parametrami `runbook_id=M-1`, `instance`, `replica`.
## Alert B: Replikacja zatrzymana (RUNBOOK M2)
Jeśli wysyłasz status threads:
```spl
index=dbops sourcetype=mysql:metrics metric=replication_sql_running
| stats latest(value) as sql_running latest(io_running) as io_running latest(last_sql_error) as last_sql_error by instance
| where sql_running=0 OR io_running=0
```
## Alert C: Deadlock/Lock wait (RUNBOOK M3)
Z error log:
```spl
index=dbops sourcetype=mysql:error ("Deadlock found" OR "Lock wait timeout exceeded")
| stats count as hits latest(_raw) as sample by host
| where hits > 5
```
## Alert D: CPU pressure proxy (Threads\_running) (RUNBOOK M4)
```spl
index=dbops sourcetype=mysql:metrics metric=threads_running
| timechart span=1m max(value) as threads_running by instance
| where threads_running > 50
```
## Alert E: Disk pressure (RUNBOOK M5)
Jeśli masz metryki systemowe w Splunku (node exporter / system logs) wtedy korelujesz:
```spl
(index=os sourcetype=linux:df) OR (index=dbops sourcetype=mysql:metrics metric=disk_free_percent)
| ...
```
(Jeśli powiesz czym zbierasz OS metrics, dopasuję SPL pod Twój format.)
***
# 6) Jak wysyłać wyniki runbooków do Splunk przez HEC (szablon)
HEC jest token-based i przyjmuje eventy na endpoint `/services/collector/event`. [\[docs.splunk.com\]](https://docs.splunk.com/Documentation/Splunk/9.4.2/Data/UsetheHTTPEventCollector), [\[bearlychilly.com\]](https://bearlychilly.com/notes/how-to-send-events-to-splunk-using-hec---http-event-collector/)
**Przykładowy payload JSON (OpenClaw → Splunk):**
```json
{
"time": 1762160400,
"host": "openclaw-gw-01",
"source": "openclaw/mysql",
"sourcetype": "openclaw:runbook",
"index": "dbops",
"event": {
"incident_id": "mysql-prod-01-2026-02-26T01:15Z",
"runbook_id": "M-1",
"instance": "mysql-prod-01",
"severity": "high",
"trigger": {
"alert": "replication_lag",
"lag_seconds": 742
},
"decision": "report_only",
"diagnostics": {
"io_running": true,
"sql_running": true,
"seconds_behind_source": 742,
"top_trx_age_s": 1830
},
"actions": [],
"recommendation": "Replica overloaded by read traffic; remove from read pool and re-check lag.",
"links": {
"splunk_sid": "scheduler_admin_search_..."
}
}
}
```
**Weryfikacja po stronie Splunka:**
Szukasz `sourcetype=openclaw:runbook` w `index=dbops`.
***
# 7) “Closed loop” — PR + review (OpenClaw + Claude Code)
## 7.1 Kiedy tworzyć PR automatycznie?
Tylko dla przypadków, gdzie rekomendacja jest “code/config change”, np.:
* brak indeksu (w aplikacji / migracji)
* query rewrite (z slow log digest)
* zmiana parametrów MySQL w config repo
## 7.2 Jak to powiązać ze Splunkiem?
Do eventu runbooka dodaj:
* `pr_url`
* `commit_sha`
* `repo`
* `change_type` (index/add, query/rewrite, config/tuning)
Potem budujesz dashboard:
* “Incydenty zakończone PR-em”
* “MTTR per runbook”
* “Top przyczyny”
***
# 8) Minimalny plan wdrożenia (PoC w 23 dni)
## Dzień 1: Ingest
1. UF/monitor error log
2. Skrypt metryk co 1 min → sourcetype `mysql:metrics`
3. HEC token + sourcetype `openclaw:runbook` [\[docs.splunk.com\]](https://docs.splunk.com/Documentation/Splunk/9.4.2/Data/UsetheHTTPEventCollector), [\[kinneygroup.com\]](https://kinneygroup.com/blog/http-event-collector/)
## Dzień 2: Alerty + webhook
1. Tworzysz 3 alerty (lag, replication down, deadlocks)
2. Ustawiasz webhook action (POST JSON) [\[help.splunk.com\]](https://help.splunk.com/en/splunk-enterprise/alert-and-respond/alerting-manual/9.4/configure-alert-actions/use-a-webhook-alert-action)
3. Dodajesz URL do allowlist (Splunk Ent 9+) [\[help.splunk.com\]](https://help.splunk.com/en/splunk-enterprise/alert-and-respond/alerting-manual/9.4/configure-alert-actions/use-a-webhook-alert-action)
## Dzień 3: OpenClaw runbook runner + report to Splunk
1. Mapowanie alert→runbook
2. Runbooki w trybie readonly
3. Wysyłka wyników do Splunk HEC
***
# 9) Dwa ważne niuanse praktyczne (żeby się nie wywróciło)
## 9.1 Webhook auth i payload
Splunk webhook action wysyła JSON o stałej strukturze (result + sid + link).
Jeśli potrzebujesz **custom headers / auth**, natywny webhook bywa ograniczony (często wymaga obejścia lub custom alert action). W praktyce dlatego robi się “bramkę” pośrednią albo własną akcję. (Jeśli chcesz, podam prosty wariant bramki). [\[help.splunk.com\]](https://help.splunk.com/en/splunk-enterprise/alert-and-respond/alerting-manual/9.4/configure-alert-actions/use-a-webhook-alert-action)
## 9.2 Splunk Cloud vs Enterprise
HEC działa w Splunk Cloud i Splunk Enterprise, ale zachowanie/konfigurowalność może się różnić (np. konfiguracje plikowe w Cloud są ograniczone).
Daj znać, czy masz Cloud czy onprem — dobiorę dokładnie. [\[docs.splunk.com\]](https://docs.splunk.com/Documentation/Splunk/9.4.2/Data/UsetheHTTPEventCollector)
***
# 10) Pytania doprecyzowujące (krótkie, żeby dopiąć szczegóły)
1. Masz **Splunk Enterprise czy Splunk Cloud**? (to wpływa na to, gdzie konfigurować HEC i jak z allowlistami) [\[docs.splunk.com\]](https://docs.splunk.com/Documentation/Splunk/9.4.2/Data/UsetheHTTPEventCollector), [\[help.splunk.com\]](https://help.splunk.com/en/splunk-enterprise/alert-and-respond/alerting-manual/9.4/configure-alert-actions/use-a-webhook-alert-action)
2. Jak dziś zbierasz MySQL metryki: exporter/Prometheus, skrypty, czy nic?
3. MySQL topologia: async replication / InnoDB Cluster (Group Replication)? (żeby dobrać alerty GR1 vs M1)
Jeśli odpowiesz, przygotuję Ci od razu:
* **konkretny “data contract” JSON** dla `mysql:metrics` (jakie pola wysyłać),
* komplet **saved searches + alert definitions** (progi, throttling),
* i mapping **alert → runbook** (M1, M2, M3, GR1…).
Jasne — poniżej masz **konkretne przykłady payloadów do Splunk HEC** (zarówno *event endpoint*, jak i *raw endpoint*), plus gotowe `curl` do testów i kilka praktycznych uwag o polach.
> HEC przyjmuje dane na endpoint **`/services/collector/event`** (format “event”) albo **`/services/collector/raw`** (surowy tekst/JSON), a autoryzacja jest tokenem w nagłówku `Authorization: Splunk <token>`. [\[openclaw.ai\]](https://openclaw.ai/), [\[towardsdat...cience.com\]](https://towardsdatascience.com/use-openclaw-to-make-a-personal-ai-assistant/)
***
## 1) Najczęstszy wariant: **HEC `/event`** (rekomendowany)
### ✅ Payload „runbook wynik” (OpenClaw → Splunk)
```json
{
"time": 1762160400,
"host": "openclaw-gw-01",
"source": "openclaw/mysql",
"sourcetype": "openclaw:runbook",
"index": "dbops",
"event": {
"incident_id": "mysql-prod-01-2026-02-26T01:15:00Z",
"runbook_id": "M-1",
"runbook_name": "Replication lag investigation",
"severity": "high",
"instance": "mysql-prod-01",
"topology": "async_replication",
"trigger": {
"type": "splunk_alert",
"alert_name": "mysql_replication_lag_gt_300s",
"splunk_sid": "scheduler_admin_search_123_at_1700000000_42",
"lag_seconds": 742
},
"diagnostics": {
"replica_io_running": true,
"replica_sql_running": true,
"seconds_behind_source": 742,
"last_sql_error": null,
"top_trx_age_s": 1830,
"threads_running": 62,
"threads_connected": 410
},
"decision": "report_only",
"actions": [],
"recommendation": "Remove replica from read pool; investigate long transaction and read workload on replica.",
"artifacts": {
"diag_files": [
"/var/lib/openclaw/incidents/mysql-prod-01/diag_replication_status.txt",
"/var/lib/openclaw/incidents/mysql-prod-01/diag_innodb_trx.json"
],
"notes_md": "/var/lib/openclaw/incidents/mysql-prod-01/recommendation.md"
}
}
}
```
**Co jest tu ważne:**
* `time` to epoch (sekundy).
* `event` może być dowolnym JSON-em (Twoje pola).
* `index`, `source`, `sourcetype`, `host` ustawiasz w payloadzie, żeby łatwo budować dashboardy/alerty.
* Ten format odpowiada “HEC event data format” dla endpointu `/collector/event`. [\[openclaw.ai\]](https://openclaw.ai/), [\[towardsdat...cience.com\]](https://towardsdatascience.com/use-openclaw-to-make-a-personal-ai-assistant/)
### 🧪 Test `curl` (HEC `/event`)
```bash
curl -k https://<splunk-host>:8088/services/collector/event \
-H "Authorization: Splunk <HEC_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"sourcetype":"openclaw:runbook",
"index":"dbops",
"event":{"msg":"hello from OpenClaw runbook","runbook_id":"M-0","severity":"info"}
}'
```
To jest klasyczny sposób wysyłki danych do HEC: token w nagłówku + JSON z `event`. [\[openclaw101.dev\]](https://openclaw101.dev/), [\[towardsdat...cience.com\]](https://towardsdatascience.com/use-openclaw-to-make-a-personal-ai-assistant/)
***
## 2) Wariant: **HEC `/event`** dla metryk MySQL (JSON „punkt pomiaru”)
Jeśli chcesz wysyłać metryki jako eventy (nie metrics index), rób tak:
```json
{
"time": 1762160460,
"host": "mysql-prod-01",
"source": "mysql/metrics",
"sourcetype": "mysql:metrics",
"index": "dbops",
"event": {
"instance": "mysql-prod-01",
"metric": "threads_running",
"value": 57,
"unit": "threads"
}
}
```
I analogicznie dla replikacji:
```json
{
"time": 1762160460,
"host": "mysql-replica-01",
"source": "mysql/replication",
"sourcetype": "mysql:metrics",
"index": "dbops",
"event": {
"instance": "mysql-replica-01",
"metric": "replication_lag_seconds",
"value": 742,
"unit": "seconds",
"replica_io_running": true,
"replica_sql_running": true
}
}
```
***
## 3) Wariant: **HEC `/raw`** (kiedy chcesz wysłać „goły” tekst/JSON)
HEC ma też endpoint `/services/collector/raw`, który przyjmuje “raw events”. Wtedy zwykle wysyłasz tekst/JSON bez koperty `{"event":...}`. [\[towardsdat...cience.com\]](https://towardsdatascience.com/use-openclaw-to-make-a-personal-ai-assistant/), [\[openclaw.ai\]](https://openclaw.ai/)
### 🧪 Test `curl` (HEC `/raw`)
```bash
curl -k https://<splunk-host>:8088/services/collector/raw \
-H "Authorization: Splunk <HEC_TOKEN>" \
-H "Content-Type: application/json" \
-d '{"msg":"raw hello","component":"openclaw","runbook_id":"M-0"}'
```
**Kiedy `/raw` ma sens:**
* masz już gotowy format logów i chcesz go “przepchnąć” bez koperty,
* chcesz użyć `source/sourcetype/index` poprzez konfigurację tokena (albo query params — zależnie od wdrożenia).
***
## 4) Minimalny „contract” pod Splunk alert → runbook → HEC (polecam)
Żeby to było spójne, trzymaj zawsze te pola w `event`:
```json
{
"incident_id": "…",
"runbook_id": "M-1|M-2|M-3|GR-1…",
"severity": "info|low|medium|high|critical",
"instance": "mysql-…",
"trigger": { "alert_name": "…", "splunk_sid": "…" },
"decision": "report_only|safe_action|escalated",
"diagnostics": { },
"actions": [ ],
"recommendation": "…"
}
```
To potem daje świetne dashboardy:
* MTTR per runbook
* najczęstsze przyczyny
* skuteczność auto-remediation
***
## 5) Praktyczne uwagi (żeby potem nie bolało)
1. **Ustal jeden index** na DB ops (np. `dbops`) i trzymaj wszystko w nim: metryki, logi, runbook results.
2. W Splunk Enterprise 9+ pamiętaj o allowliście dla webhooków (dla strony *wychodzącej* z Splunka). [\[openclaw.im\]](https://openclaw.im/docs)
3. HEC jest token-based, więc nie wkładaj user/pass do agentów — token w secret store i rotacja. [\[openclaw.ai\]](https://openclaw.ai/), [\[openclaw101.dev\]](https://openclaw101.dev/)
***
## Chcesz też przykład payloadu webhooka z alertu Splunka (ten, który trafi do OpenClaw)?
Splunk webhook action wysyła JSON zawierający m.in. `sid`, `results_link` i pierwszy wynik w `result`.
Jeśli powiesz, czy będziesz używał „For each result” czy „Once”, przygotuję Ci **dokładny przykład** i mapper `alert → runbook_id`. [\[openclaw.im\]](https://openclaw.im/docs)
Jasne — poniżej masz **realistyczne przykłady payloadu webhooka wysyłanego przez Splunk Alert Action**, dokładnie w takim kształcie, w jakim **OpenClaw (albo Twoja bramka pośrednia)** go odbierze.
Pokażę **dwa najważniejsze warianty**, bo to jest kluczowe w projektowaniu runbooków:
1. **Alert „Once”** (jeden payload na uruchomienie alertu)
2. **Alert „For each result”** (jeden payload *per wynik* polecane do runbooków)
Na końcu dorzucę **mapowanie → runbook\_id**.
***
# 1️⃣ Webhook payload tryb **Once** (domyślny)
Ten tryb wysyła **jeden POST**, nawet jeśli search zwróci wiele wyników.
### 📦 Przykładowy payload (Splunk → OpenClaw)
```json
{
"sid": "scheduler_admin_search_1700000000_1234",
"search_name": "mysql_replication_lag_gt_300s",
"owner": "admin",
"app": "search",
"results_link": "https://splunk.example.com/app/search/@go?sid=scheduler_admin_search_1700000000_1234",
"result": {
"instance": "mysql-replica-01",
"lag": "742",
"threshold": "300"
}
}
```
### Co tu jest ważne:
* `sid` **Search ID** (złoto diagnostyczne, link do kontekstu)
* `search_name` nazwa alertu (idealna do mapowania na runbook)
* `results_link` szybki link do Splunka
* `result` **pierwszy wynik** z searcha (uwaga: tylko jeden!)
### Kiedy używać:
✅ alerty informacyjne
❌ automatyczne runbooki (za mało kontekstu przy wielu instancjach)
***
# 2️⃣ Webhook payload tryb **For each result** ✅ (REKOMENDOWANY)
Splunk wysyła **osobny POST dla każdego wiersza wyniku**.
To jest **najlepszy tryb do automatyzacji DBA**.
***
### 📦 Payload #1 (pierwsza replika)
```json
{
"sid": "scheduler_admin_search_1700000000_1234",
"search_name": "mysql_replication_lag_gt_300s",
"owner": "admin",
"app": "search",
"results_link": "https://splunk.example.com/app/search/@go?sid=scheduler_admin_search_1700000000_1234",
"result": {
"instance": "mysql-replica-01",
"replica_host": "db-replica-01",
"source_host": "db-primary-01",
"lag_seconds": "742",
"replica_sql_running": "1",
"replica_io_running": "1"
}
}
```
### 📦 Payload #2 (druga replika)
```json
{
"sid": "scheduler_admin_search_1700000000_1234",
"search_name": "mysql_replication_lag_gt_300s",
"owner": "admin",
"app": "search",
"results_link": "https://splunk.example.com/app/search/@go?sid=scheduler_admin_search_1700000000_1234",
"result": {
"instance": "mysql-replica-02",
"replica_host": "db-replica-02",
"source_host": "db-primary-01",
"lag_seconds": "1280",
"replica_sql_running": "0",
"replica_io_running": "1"
}
}
```
### Dlaczego to jest idealne:
* ✅ 1 payload = 1 instancja = 1 runbook
* ✅ brak pętli po stronie OpenClaw
* ✅ łatwe throttling / retry / deduplikacja
* ✅ naturalne `incident_id`
***
# 3️⃣ Jak Splunk buduje pole `result`
`result` to **dokładnie jedna linia z wyników SPL**.
### Przykładowy SPL
```spl
index=dbops sourcetype=mysql:metrics metric=replication_lag_seconds
| stats latest(value) as lag_seconds
latest(replica_sql_running) as replica_sql_running
latest(replica_io_running) as replica_io_running
by instance replica_host source_host
| where lag_seconds > 300
```
➡️ Każda kolumna = jedno pole w `result`.
***
# 4️⃣ Jak OpenClaw mapuje webhook → runbook
Najprostsza i **najczystsza logika** (bez if-hell):
```text
search_name → runbook_id
```
### Przykład mapowania
```yaml
mysql_replication_lag_gt_300s: M-1
mysql_replication_stopped: M-2
mysql_deadlock_rate_high: M-3
mysql_threads_running_high: M-4
mysql_disk_pressure: M-5
```
***
# 5️⃣ Jak OpenClaw tworzy `incident_id`
Rekomendowany wzorzec:
```text
<instance>-<runbook_id>-<sid>
```
### Przykład:
mysql-replica-01-M-1-scheduler_admin_search_1700000000_1234
To:
* ✅ eliminuje duplikaty
* ✅ pozwala na retry
* ✅ świetnie koreluje z HEC eventami
***
# 6️⃣ Co OpenClaw powinien dostać MINIMALNIE
Jeśli chcesz **minimalny kontrakt**, wystarczy:
```json
{
"search_name": "...",
"sid": "...",
"result": {
"instance": "...",
"severity_signal": "..."
}
}
```
Resztę OpenClaw:
* dociąga sam (diag SQL),
* albo wylicza (severity, decision).
***
# 7️⃣ Pro tipy produkcyjne (bardzo ważne)
**Używaj „For each result”**
**Throttle alerty** (np. 1 per instance / 10 min)
**Webhook → bramka → OpenClaw**, nie bezpośrednio
**Nigdy nie ufaj payloadowi** — waliduj `search_name` i pola
**Loguj każdy webhook do Splunka** (audit trail)
***
## Chcesz następny krok?
Mogę teraz:
* napisać **dokładny parser webhooka** (JSON → runbook context),
* przygotować **schemat walidacji payloadu** (co akceptować / co odrzucać),
* albo rozpisać **diagram sekwencji**: Splunk → Webhook → OpenClaw → HEC → PR → Claude Code.
Powiedz, co dalej 👍
Super — to da się zrobić bardzo „operacyjnie”: **uruchamiasz jeden playbook**, on:
1. **zbiera metryki MySQL** (to, co już zbudowaliśmy: threads, tmp tables, replication/GR status w wersji PoC minimalnie),
2. **wylicza wynik** (OK/WARN/CRIT + krótki opis),
3. **drukuje wynik w konsoli** (żebyś od razu widział),
4. **(opcjonalnie) wysyła wynik do Splunk HEC** jako jeden event JSON.
Poniżej dostajesz gotowy wzorzec.
> HEC to endpoint Splunka do przyjmowania eventów po HTTP/HTTPS z tokenem (`Authorization: Splunk <token>`) i najczęściej używa się `/services/collector/event`. [\[openclaw.ai\]](https://openclaw.ai/), [\[towardsdat...cience.com\]](https://towardsdatascience.com/use-openclaw-to-make-a-personal-ai-assistant/)
***
# 1) Playbook „jednorazowe sprawdzenie i wynik” (MySQL → wynik + Splunk HEC)
## ✅ Założenia
* Na hostach MySQL masz już (albo wdrożysz tym playbookiem) skrypt `mysql_metrics_collector.py` w `/opt/dbops/`.
* Masz dane dostępowe do MySQL usera tylko do odczytu metryk (minimalne uprawnienia).
* (Opcjonalnie) masz HEC URL + token w Ansible Vault.
***
## 1.1 `group_vars` / zmienne (przykład)
**`group_vars/mysql.yml`**
```yaml
mysql_metrics_user: "splunk_metrics"
mysql_metrics_password: "{{ vault_mysql_metrics_password }}"
mysql_metrics_host: "127.0.0.1"
mysql_metrics_port: 3306
# progi „nocnego DBA”
threshold_threads_running_warn: 40
threshold_threads_running_crit: 80
threshold_replication_lag_warn: 300
threshold_replication_lag_crit: 900
```
**`group_vars/splunk.yml`** (jeśli chcesz wysyłać wynik do Splunka)
```yaml
splunk_hec_url: "https://splunk.example.com:8088/services/collector/event"
splunk_hec_token: "{{ vault_splunk_hec_token }}"
splunk_index_dbops: "dbops"
```
***
## 1.2 Playbook: `playbooks/mysql_monitor_once.yml`
> Ten playbook zrobi „monitoring action” i zwróci Ci **konkretny wynik** na stdout + (opcjonalnie) wyśle event do Splunka HEC. [\[openclaw.ai\]](https://openclaw.ai/), [\[towardsdat...cience.com\]](https://towardsdatascience.com/use-openclaw-to-make-a-personal-ai-assistant/)
```yaml
---
- name: MySQL monitoring action (one-shot) + optional Splunk HEC report
hosts: mysql
become: true
gather_facts: true
vars:
collector_path: "/opt/dbops/mysql_metrics_collector.py"
send_to_splunk: true # ustaw na false jeśli chcesz tylko wynik w konsoli
tasks:
- name: Ensure Python3 is present
ansible.builtin.package:
name: python3
state: present
- name: Ensure mysql connector for python is present (Debian/Ubuntu example)
ansible.builtin.package:
name: python3-mysql.connector
state: present
ignore_errors: true
- name: Deploy mysql metrics collector script
ansible.builtin.copy:
dest: "{{ collector_path }}"
mode: "0755"
content: |
#!/usr/bin/env python3
import json, time, os
import mysql.connector
def fetch_kv(cur, sql):
cur.execute(sql)
return dict(cur.fetchall())
def main():
host = os.environ.get("MYSQL_HOST","127.0.0.1")
port = int(os.environ.get("MYSQL_PORT","3306"))
user = os.environ["MYSQL_USER"]
pw = os.environ["MYSQL_PASSWORD"]
conn = mysql.connector.connect(host=host, port=port, user=user, password=pw, connection_timeout=3)
cur = conn.cursor()
vars = fetch_kv(cur, "SHOW GLOBAL VARIABLES")
stat = fetch_kv(cur, "SHOW GLOBAL STATUS")
payload = {
"time": int(time.time()),
"hostname": vars.get("hostname"),
"version": vars.get("version"),
"threads_running": int(stat.get("Threads_running", 0)),
"threads_connected": int(stat.get("Threads_connected", 0)),
"aborted_connects": int(stat.get("Aborted_connects", 0)),
"created_tmp_disk_tables": int(stat.get("Created_tmp_disk_tables", 0)),
"log_bin": vars.get("log_bin", "OFF"),
"role_hint": "unknown"
}
# Replication (MySQL 8.0)
try:
cur.execute("SHOW REPLICA STATUS")
cols = [c[0] for c in cur.description] if cur.description else []
row = cur.fetchone()
if row:
r = dict(zip(cols, row))
payload["role_hint"] = "replica"
payload["replica_io_running"] = (r.get("Replica_IO_Running") == "Yes")
payload["replica_sql_running"] = (r.get("Replica_SQL_Running") == "Yes")
payload["replication_lag_seconds"] = int(r.get("Seconds_Behind_Source") or 0)
payload["last_sql_error"] = r.get("Last_SQL_Error")
payload["last_io_error"] = r.get("Last_IO_Error")
except Exception:
pass
# Legacy replication (5.7)
if payload.get("role_hint") == "unknown":
try:
cur.execute("SHOW SLAVE STATUS")
cols = [c[0] for c in cur.description] if cur.description else []
row = cur.fetchone()
if row:
r = dict(zip(cols, row))
payload["role_hint"] = "replica"
payload["replica_io_running"] = (r.get("Slave_IO_Running") == "Yes")
payload["replica_sql_running"] = (r.get("Slave_SQL_Running") == "Yes")
payload["replication_lag_seconds"] = int(r.get("Seconds_Behind_Master") or 0)
payload["last_sql_error"] = r.get("Last_SQL_Error")
payload["last_io_error"] = r.get("Last_IO_Error")
except Exception:
pass
cur.close()
conn.close()
print(json.dumps(payload, ensure_ascii=False))
if __name__ == "__main__":
main()
- name: Run collector and capture JSON output
ansible.builtin.command: "python3 {{ collector_path }}"
register: collector_out
changed_when: false
environment:
MYSQL_HOST: "{{ mysql_metrics_host }}"
MYSQL_PORT: "{{ mysql_metrics_port }}"
MYSQL_USER: "{{ mysql_metrics_user }}"
MYSQL_PASSWORD: "{{ mysql_metrics_password }}"
- name: Parse collector JSON
ansible.builtin.set_fact:
mysql_metrics: "{{ collector_out.stdout | from_json }}"
- name: Compute health status (OK/WARN/CRIT)
ansible.builtin.set_fact:
health_status: >-
{% set tr = mysql_metrics.threads_running | int %}
{% set lag = (mysql_metrics.replication_lag_seconds | default(0)) | int %}
{% set io = mysql_metrics.replica_io_running | default(true) %}
{% set sql = mysql_metrics.replica_sql_running | default(true) %}
{% if (mysql_metrics.role_hint == 'replica') and ((not io) or (not sql)) %}
CRIT
{% elif lag >= threshold_replication_lag_crit %}
CRIT
{% elif tr >= threshold_threads_running_crit %}
CRIT
{% elif lag >= threshold_replication_lag_warn %}
WARN
{% elif tr >= threshold_threads_running_warn %}
WARN
{% else %}
OK
{% endif %}
- name: Build human-friendly summary
ansible.builtin.set_fact:
health_summary: >-
MySQL {{ mysql_metrics.hostname }} {{ mysql_metrics.version }} |
status={{ health_status }} |
threads_running={{ mysql_metrics.threads_running }} |
threads_connected={{ mysql_metrics.threads_connected }} |
tmp_disk_tables={{ mysql_metrics.created_tmp_disk_tables }}
{% if mysql_metrics.role_hint == 'replica' %}
| repl_lag_s={{ mysql_metrics.replication_lag_seconds }}
| io={{ mysql_metrics.replica_io_running }}
| sql={{ mysql_metrics.replica_sql_running }}
{% endif %}
- name: Print the result to console (this is your concrete output)
ansible.builtin.debug:
msg:
- "{{ health_summary }}"
- "raw_metrics={{ mysql_metrics }}"
- name: Send result to Splunk HEC (optional)
ansible.builtin.uri:
url: "{{ splunk_hec_url }}"
method: POST
headers:
Authorization: "Splunk {{ splunk_hec_token }}"
Content-Type: "application/json"
body_format: json
body:
time: "{{ ansible_date_time.epoch | int }}"
host: "{{ inventory_hostname }}"
source: "ansible/mysql_monitor_once"
sourcetype: "ansible:mysql:health"
index: "{{ splunk_index_dbops }}"
event:
incident_id: "{{ inventory_hostname }}-monitor-{{ ansible_date_time.iso8601 }}"
decision: "report_only"
severity: "{{ 'high' if health_status == 'CRIT' else ('medium' if health_status == 'WARN' else 'info') }}"
health_status: "{{ health_status }}"
summary: "{{ health_summary }}"
metrics: "{{ mysql_metrics }}"
when: send_to_splunk | bool
delegate_to: localhost
vars:
ansible_python_interpreter: "{{ ansible_playbook_python }}"
# W środowiskach testowych czasem używa się validate_certs: false
```
### Dlaczego to jest „zgodne z tym, co zrobiliśmy”:
* wynik jest **jednym JSON-em** (łatwy do Splunka),
* konkluzja jest **jednym stringiem** (łatwy do odczytu),
* wysyłka idzie przez HEC `/services/collector/event` z tokenem w nagłówku (standard Splunka). [\[openclaw.ai\]](https://openclaw.ai/), [\[towardsdat...cience.com\]](https://towardsdatascience.com/use-openclaw-to-make-a-personal-ai-assistant/)
***
# 2) Jak uruchomić playbook i dostać wynik
### Jednorazowo na jednej maszynie
```bash
ansible-playbook -i inventories/prod/hosts.yml playbooks/mysql_monitor_once.yml -l mysql-prod-01
```
### Na wszystkich MySQL z inventory
```bash
ansible-playbook -i inventories/prod/hosts.yml playbooks/mysql_monitor_once.yml
```
W konsoli dostaniesz linijkę w stylu:
MySQL db-replica-01 8.0.36 | status=WARN | threads_running=55 | threads_connected=420 | tmp_disk_tables=120 | repl_lag_s=742 | io=True | sql=True
***
# 3) Jak znaleźć ten wynik w Splunku (SPL)
Po wysyłce do HEC możesz w Splunku szybko zobaczyć:
```spl
index=dbops sourcetype=ansible:mysql:health
| stats latest(event.health_status) as status latest(event.summary) as summary by host
```
***
# 4) Co dalej — “akcja monitorująca” + konkretna reakcja
Jeśli chcesz, żeby playbook **nie tylko raportował**, ale też np.:
* przy `CRIT` wysyłał webhook do OpenClaw,
* albo odpalał określony runbook (M1/M3),
* albo tworzył ticket/PR,
to możemy dodać *kolejny krok w playbooku* zależny od `health_status`.
***
## Dwa pytania (żeby dopasować „konkretny wynik” do Twojego use-case)
1. Chcesz sprawdzać **replikację async/GTID**, czy **InnoDB Cluster (Group Replication)**?
2. Ten playbook ma być **tylko read-only**, czy ma mieć „safe action” (np. restart repliki `STOP/START REPLICA` przy transient error)?
Jak odpowiesz, dopracuję Ci wersję **produkcyjną** pod Twoją topologię + dodam **throttling/deduplikację incident\_id** i (jeśli chcesz) automatyczne odpalenie runbooka przez OpenClaw.
Świetne pytanie — **to jest bardzo dobry kierunek** i w praktyce **bardziej dojrzały operacyjnie** niż pisanie wszystkiego „od zera”.
Poniżej pokażę Ci **jak realnie połączyć `dbachecks` / `dbatools` z Ansible + Splunk + AI**, tak aby:
* **dbachecks/dbatools** robiły *twardą, deterministyczną diagnostykę*,
* **Ansible** uruchamiał to w kontrolowany sposób,
* **Splunk** zbierał wyniki i korelował je w czasie,
* **AI (OpenClaw / Claude Code)** interpretowało wyniki, wyciągało wnioski i sugerowało (lub wykonywało) akcje.
To jest dokładnie ten model, który działa w enterprise.
***
# 1️⃣ Dlaczego dbachecks / dbatools to świetna baza
## dbatools
* Zbiór **produkcyjnych funkcji PowerShell** do SQL Server
* Idealny do:
* stanu instancji
* backupów
* replikacji / AG
* security
* performance baselines
## dbachecks
* Framework testowy (Pester) oparty na dbatools
* Wynik = **strukturalne testy: Pass / Warning / Fail**
* Idealny do:
* automatycznego health-checku
* CI/CD
* nocnych audytów
* eksportu do JSON
👉 **Najważniejsze:**
dbachecks **już rozwiązuje 80% problemów DBA**, a AI nie powinno zastępować tej logiki — tylko ją **interpretować**.
***
# 2️⃣ Docelowy model architektury (prosty, ale potężny)
Ansible
|
|-- uruchamia
v
PowerShell (dbachecks / dbatools)
|
|-- generuje JSON
v
Ansible (parsuje wynik)
|
+--> Splunk (HEC) ✅ historia, trendy, alerty
|
+--> AI (OpenClaw / Claude Code) ✅ interpretacja, decyzja
**Kluczowa zasada:**
> ✅ dbachecks = fakty
> ✅ Ansible = orkiestracja
> ✅ Splunk = pamięć i korelacja
> ✅ AI = wnioski i decyzje
***
# 3️⃣ Konkretny przykład: dbachecks uruchamiane przez Ansible
## 3.1 Co dokładnie uruchamiamy
Przykład: **nocny health-check SQL Server**
```powershell
Invoke-DbcCheck `
-SqlInstance "sql-prod-01" `
-Check Storage, LastBackup, AgentJob, AvailabilityGroup `
-Show None `
-OutputFormat Json `
-OutputFile "C:\dbachecks\sql-prod-01.json"
```
➡️ Wynik: **czysty JSON** z:
* nazwą testu
* statusem (Passed / Warning / Failed)
* szczegółami
To jest idealny input dla:
* Ansible
* Splunka
* AI
***
## 3.2 Playbook Ansible (Windows + PowerShell)
### `playbooks/dbachecks_monitor.yml`
```yaml
---
- name: Run dbachecks and collect results
hosts: sqlservers
gather_facts: false
vars:
dbc_output: "C:\\dbachecks\\{{ inventory_hostname }}.json"
tasks:
- name: Ensure output directory exists
win_file:
path: C:\dbachecks
state: directory
- name: Run dbachecks
win_shell: |
Import-Module dbachecks
Invoke-DbcCheck `
-SqlInstance "{{ inventory_hostname }}" `
-Check Storage, LastBackup, AgentJob, AvailabilityGroup `
-Show None `
-OutputFormat Json `
-OutputFile "{{ dbc_output }}"
args:
executable: powershell.exe
- name: Read dbachecks JSON
win_slurp:
src: "{{ dbc_output }}"
register: dbc_raw
- name: Parse JSON
set_fact:
dbc_result: "{{ dbc_raw.content | b64decode | from_json }}"
```
Na tym etapie masz w Ansible **pełną strukturę testów**.
***
# 4️⃣ Obróbka wyniku przez Ansible (bez AI)
Zanim użyjesz AI — **zrób twardą klasyfikację**.
```yaml
- name: Compute overall health
set_fact:
dbc_summary:
instance: "{{ inventory_hostname }}"
failed: "{{ dbc_result | selectattr('Result','equalto','Failed') | list | length }}"
warning: "{{ dbc_result | selectattr('Result','equalto','Warning') | list | length }}"
passed: "{{ dbc_result | selectattr('Result','equalto','Passed') | list | length }}"
status: >-
{% if (dbc_result | selectattr('Result','equalto','Failed') | list | length) > 0 %}
CRIT
{% elif (dbc_result | selectattr('Result','equalto','Warning') | list | length) > 0 %}
WARN
{% else %}
OK
{% endif %}
```
➡️ To już daje Ci **jednoznaczny wynik**, który możesz:
* wydrukować
* wysłać do Splunka
* przekazać do AI
***
# 5️⃣ Wysyłka dbachecks → Splunk (HEC)
Przykład payloadu (to, co już znasz):
```yaml
- name: Send dbachecks summary to Splunk
uri:
url: "{{ splunk_hec_url }}"
method: POST
headers:
Authorization: "Splunk {{ splunk_hec_token }}"
Content-Type: "application/json"
body_format: json
body:
sourcetype: "dbachecks:summary"
index: "dbops"
event:
instance: "{{ dbc_summary.instance }}"
status: "{{ dbc_summary.status }}"
failed: "{{ dbc_summary.failed }}"
warning: "{{ dbc_summary.warning }}"
passed: "{{ dbc_summary.passed }}"
checks:
failed: "{{ dbc_result | selectattr('Result','equalto','Failed') | list }}"
```
Teraz Splunk:
* trzyma historię,
* robi alerty,
* koreluje z innymi zdarzeniami.
***
# 6️⃣ Gdzie wchodzi AI (i **dlaczego tu ma sens**)
Teraz najważniejsza część.
## ❌ Czego NIE robi AI
* nie zgaduje stanu bazy
* nie zastępuje testów
* nie „wymyśla” diagnozy
## ✅ Co robi AI IDEALNIE
* **interpretuje zestaw FAILED/WARN**
* **grupuje problemy**
* **tłumaczy je na język biznesowy**
* **sugeruje kolejność działań**
* **tworzy PR / runbook**
***
## 6.1 Przykład: przekazanie wyniku do AI
Ansible wysyła do OpenClaw / Claude Code:
```json
{
"context": "dbachecks_results",
"instance": "sql-prod-01",
"summary": {
"status": "CRIT",
"failed": 2,
"warning": 3
},
"failed_checks": [
{
"Check": "LastBackup",
"Target": "SalesDB",
"Message": "No full backup in last 48 hours"
},
{
"Check": "AgentJob",
"Target": "ETL_Daily",
"Message": "Job failed 3 times"
}
]
}
```
### AI odpowiada:
* **Priorytet 1:** Backup (RPO zagrożone)
* **Priorytet 2:** Job ETL (wpływ na raportowanie)
* **Rekomendacja:**
* sprawdzić plan backupów
* retry joba
* dodać alert proactive
To jest **idealna praca dla AI**.
***
# 7️⃣ Połączenie z OpenClaw (auto-runbook)
Teraz robisz:
```yaml
- name: Trigger OpenClaw runbook if CRIT
uri:
url: "{{ openclaw_webhook_url }}"
method: POST
body_format: json
body:
runbook: "sql_critical_health"
payload: "{{ dbc_summary }}"
when: dbc_summary.status == "CRIT"
```
OpenClaw:
* bierze JSON
* odpala runbook (diagnostyka / remediation)
* zapisuje wynik
* wysyła event do Splunka
***
# 8️⃣ A gdzie Claude Code?
**Claude Code** idealnie pasuje do:
* analizy **PR-ów wygenerowanych na podstawie dbachecks**
* refaktoryzacji TSQL
* review zmian w repo (backup jobs, maintenance scripts)
Przykład:
> „Na podstawie wyników dbachecks przygotuj PR poprawiający job backupowy i zrób code review.”
***
# 9️⃣ Dlaczego to podejście jest „enterprise-grade”
✅ deterministyczne testy (dbachecks)
✅ powtarzalność (Ansible)
✅ audyt i historia (Splunk)
✅ inteligentna interpretacja (AI)
✅ brak „magii”
To **dokładnie tak** buduje się nowoczesne DBA Ops.
***
# 10️⃣ Co mogę zrobić dalej (konkretnie)
Jeśli chcesz, w następnym kroku mogę:
1. przygotować **gotowy role Ansible dla dbachecks**
2. rozpisać **mapowanie FAILED check → runbook**
3. zrobić **prompt dla AI specjalnie pod dbachecks JSON**
4. przygotować **dashboard Splunk dla dbachecks**
Powiedz, który krok chcesz jako następny 👍
Pewnie — poniżej masz **kompletny, „produkcyjny” playbook Ansible**, który:
1. **instaluje i przygotowuje środowisko dbachecks** (w tym wymagany Pester 4.10.x),
2. **ustawia listę instancji SQL do sprawdzenia**,
3. uruchamia **dbachecks `LastBackup`** (czyli zestaw: Full/Diff/Log),
4. zwraca **konkretny wynik** (OK/WARN/CRIT + lista baz niespełniających polityki),
5. zapisuje pełne wyniki do pliku JSON (na runnerze),
6. opcjonalnie (jeśli chcesz) przygotowuje payload, który możesz wysłać do Splunka HEC (ale wysyłki nie włączam domyślnie).
> **Uwaga o dbachecks:**
>
> * dbachecks to framework walidacyjny oparty o Pester i korzysta z dbatools do zbierania danych. [\[dbachecks....thedocs.io\]](https://dbachecks.readthedocs.io/en/latest/), [\[livebook.manning.com\]](https://livebook.manning.com/book/learn-dbatools-in-a-month-of-lunches/chapter-26)
> * Dokumentacja dbachecks wprost mówi o wymaganiu **PowerShell 5+** oraz o konieczności zainstalowania **Pester 4.10.0** (Pester 5 powoduje problemy kompatybilności). [\[dbachecks....thedocs.io\]](https://dbachecks.readthedocs.io/en/latest/), [\[livebook.manning.com\]](https://livebook.manning.com/book/learn-dbatools-in-a-month-of-lunches/chapter-26)
> * Check `LastBackup` jest „grupą” uruchamiającą m.in. `LastFullBackup`, `LastDiffBackup`, `LastLogBackup`. [\[dbatools.io\]](https://dbatools.io/dbachecks-commands/), [\[jesspomfret.com\]](https://jesspomfret.com/checking-backups-with-dbachecks/)
> * Żeby dostać wynik jako obiekt (i dać go do JSON), trzeba użyć `-PassThru` — inaczej wynik idzie na host output i nie da się go łatwo przechwycić. [\[dba.stacke...change.com\]](https://dba.stackexchange.com/questions/316733/how-to-send-dbachecks-output-to-a-file-on-disk), [\[powershell...allery.com\]](https://www.powershellgallery.com/packages/dbachecks/2.0.18/Content/functions%5CInvoke-DbcCheck.ps1)
***
## Założenia architektoniczne (najlepsza praktyka)
Ten playbook zakłada, że masz **jeden Windows “runner”** (np. jump host / management VM), na którym:
* jest PowerShell 5.1,
* masz dostęp sieciowy do instancji SQL,
* runner uruchamia dbachecks przeciwko zdalnym instancjom (nie musisz instalować dbachecks na każdym SQL Serverze).
To jest zgodne z typowym użyciem dbachecks jako „narzędzia walidacyjnego estateu”. [\[dbachecks....thedocs.io\]](https://dbachecks.readthedocs.io/en/latest/), [\[livebook.manning.com\]](https://livebook.manning.com/book/learn-dbatools-in-a-month-of-lunches/chapter-26)
***
# 1) Inventory (przykład)
`inventories/prod/hosts.yml`
```yaml
all:
children:
dbc_runners:
hosts:
win-dbc-runner-01:
ansible_host: 10.10.10.50
ansible_connection: winrm
ansible_winrm_transport: ntlm
ansible_user: "DOMAIN\\ansible"
ansible_password: "{{ vault_winrm_password }}"
ansible_winrm_server_cert_validation: ignore
```
***
# 2) Playbook: `playbooks/dbachecks_backups.yml`
> **To jest jeden plik, który możesz odpalić od razu**:
> `ansible-playbook -i inventories/prod/hosts.yml playbooks/dbachecks_backups.yml`
```yaml
---
- name: dbachecks - Backup compliance (LastBackup) for SQL Server estate
hosts: dbc_runners
gather_facts: false
vars:
# === Lista instancji do sprawdzenia (SERVER lub SERVER\INSTANCE) ===
sql_instances:
- "sql-prod-01"
- "sql-prod-02\\inst1"
# === Gdzie zapisywać wyniki na runnerze ===
dbc_out_dir: "C:\\dbachecks"
dbc_json_path: "{{ dbc_out_dir }}\\lastbackup_{{ lookup('pipe','powershell -NoProfile -Command \"(Get-Date).ToString(\\\"yyyyMMdd_HHmmss\\\")\"') }}.json"
# === Jak rygorystycznie traktować wyniki ===
fail_play_on_failed_backup_checks: true
# === Opcjonalnie: przygotuj event do Splunka (nie wysyłamy w tym playbooku) ===
prepare_splunk_payload: false
splunk_index: "dbops"
splunk_sourcetype: "dbachecks:lastbackup"
# === Pester v4.10.x jest zalecany dla dbachecks wg dokumentacji ===
pester_required_version: "4.10.0"
tasks:
- name: Ensure output directory exists
ansible.windows.win_file:
path: "{{ dbc_out_dir }}"
state: directory
# --- Przygotowanie PowerShell Gallery / NuGet (typowy prereq instalacji modułów) ---
- name: Ensure NuGet provider is available
ansible.windows.win_shell: |
$ErrorActionPreference = "Stop"
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force | Out-Null
args:
executable: powershell.exe
- name: Trust PSGallery (optional but recommended for automation)
ansible.windows.win_shell: |
$ErrorActionPreference = "Stop"
Set-PSRepository -Name PSGallery -InstallationPolicy Trusted
args:
executable: powershell.exe
# --- Pester 4.10.x (dbachecks prereq) ---
- name: Install Pester required version (dbachecks compatibility)
ansible.windows.win_shell: |
$ErrorActionPreference = "Stop"
$ver = "{{ pester_required_version }}"
# Zainstaluj konkretną wersję Pester 4.10.x zgodnie z dokumentacją dbachecks
Install-Module Pester -RequiredVersion $ver -SkipPublisherCheck -Force -Scope AllUsers
args:
executable: powershell.exe
register: pester_install
changed_when: "'Installing' in pester_install.stdout or 'installed' in pester_install.stdout"
# --- dbachecks (automatycznie dociąga dbatools i PSFramework) ---
- name: Install dbachecks from PowerShell Gallery
ansible.windows.win_shell: |
$ErrorActionPreference = "Stop"
Install-Module dbachecks -Force -Scope AllUsers
args:
executable: powershell.exe
register: dbachecks_install
changed_when: "'Installing' in dbachecks_install.stdout or 'installed' in dbachecks_install.stdout"
# dbachecks instalowany z Galerii instaluje też zależności (dbatools/PSFramework) wg dokumentacji.
- name: Import modules (Pester + dbachecks)
ansible.windows.win_shell: |
$ErrorActionPreference = "Stop"
Import-Module Pester -Force -RequiredVersion "{{ pester_required_version }}"
Import-Module dbachecks -Force
"OK"
args:
executable: powershell.exe
register: import_modules
changed_when: false
# --- Ustawienie domyślnej listy instancji w konfiguracji dbachecks ---
- name: Configure dbachecks default SQL instances (app.sqlinstance)
ansible.windows.win_shell: |
$ErrorActionPreference = "Stop"
Import-Module dbachecks -Force
$instances = @({% for i in sql_instances %}"{{ i }}"{% if not loop.last %}, {% endif %}{% endfor %})
# Set-DbcConfig służy do ustawiania progów i konfiguracji testów/checków.
Set-DbcConfig -Name app.sqlinstance -Value $instances
"Configured: $($instances -join ', ')"
args:
executable: powershell.exe
register: dbc_config
changed_when: false
# --- Uruchomienie checków backupowych ---
- name: Run dbachecks LastBackup and capture results as JSON
ansible.windows.win_shell: |
$ErrorActionPreference = "Stop"
Import-Module Pester -Force -RequiredVersion "{{ pester_required_version }}"
Import-Module dbachecks -Force
$instances = @({% for i in sql_instances %}"{{ i }}"{% if not loop.last %}, {% endif %}{% endfor %})
# LastBackup to grupa checków: LastFullBackup, LastDiffBackup, LastLogBackup.
# -PassThru jest konieczne, by dostać obiekt wyników zamiast samego outputu na host.
$r = Invoke-DbcCheck -SqlInstance $instances -Check LastBackup -PassThru -Show None -Quiet
# Zapisz pełne wyniki do pliku JSON
$jsonPath = "{{ dbc_json_path }}"
$r | ConvertTo-Json -Depth 8 | Out-File -FilePath $jsonPath -Encoding utf8
# Wyciągnij „test results” (Pester v4 zwraca zwykle TestResult)
$tests = @()
if ($null -ne $r.TestResult) { $tests = $r.TestResult }
$failed = @($tests | Where-Object { $_.Result -eq "Failed" })
$warn = @($tests | Where-Object { $_.Result -eq "Skipped" -or $_.Result -eq "Pending" })
$passed = @($tests | Where-Object { $_.Result -eq "Passed" })
# Zbuduj prosty, deterministyczny summary
$status =
if ($failed.Count -gt 0) { "CRIT" }
elseif ($warn.Count -gt 0) { "WARN" }
else { "OK" }
$summary = [PSCustomObject]@{
status = $status
instances = $instances
counts = @{
failed = $failed.Count
warn = $warn.Count
passed = $passed.Count
total = $tests.Count
}
failedTests = $failed | Select-Object -First 50
outputFile = $jsonPath
}
$summary | ConvertTo-Json -Depth 6
args:
executable: powershell.exe
register: dbc_summary_raw
changed_when: false
- name: Parse summary JSON into Ansible fact
ansible.builtin.set_fact:
dbc_summary: "{{ dbc_summary_raw.stdout | from_json }}"
- name: Print concrete result (what you asked for)
ansible.builtin.debug:
msg:
- "dbachecks LastBackup => status={{ dbc_summary.status }}"
- "failed={{ dbc_summary.counts.failed }}, warn={{ dbc_summary.counts.warn }}, passed={{ dbc_summary.counts.passed }}, total={{ dbc_summary.counts.total }}"
- "results_file={{ dbc_summary.outputFile }}"
- "first_failed_tests={{ dbc_summary.failedTests | default([]) }}"
# --- Opcjonalnie: przygotuj payload do Splunka (nie wysyłamy, tylko pokażemy) ---
- name: Prepare Splunk HEC payload (optional)
ansible.builtin.set_fact:
splunk_event:
time: "{{ lookup('pipe','date +%s') | int }}"
host: "{{ inventory_hostname }}"
source: "ansible/dbachecks"
sourcetype: "{{ splunk_sourcetype }}"
index: "{{ splunk_index }}"
event:
check: "LastBackup"
status: "{{ dbc_summary.status }}"
counts: "{{ dbc_summary.counts }}"
instances: "{{ dbc_summary.instances }}"
results_file: "{{ dbc_summary.outputFile }}"
when: prepare_splunk_payload | bool
- name: Show Splunk HEC payload (optional)
ansible.builtin.debug:
var: splunk_event
when: prepare_splunk_payload | bool
# --- Warunek „pipeline fail” jeśli backup checks nie przechodzą ---
- name: Fail play if any backup checks failed (optional)
ansible.builtin.fail:
msg: >-
Backup compliance FAILED (dbachecks LastBackup). Failed={{ dbc_summary.counts.failed }}.
See JSON: {{ dbc_summary.outputFile }}
when:
- fail_play_on_failed_backup_checks | bool
- dbc_summary.status == "CRIT"
```
***
# 3) Jak to uruchomić
```bash
ansible-playbook -i inventories/prod/hosts.yml playbooks/dbachecks_backups.yml
```
### Co zobaczysz jako „konkretny wynik”
W konsoli dostaniesz m.in.:
* `status=OK|WARN|CRIT`
* licznik `failed/warn/passed`
* ścieżkę do JSON: `C:\dbachecks\lastbackup_YYYYMMDD_HHMMSS.json`
* pierwsze 50 nieudanych testów (żeby od razu wiedzieć co i gdzie)
***
# 4) Dostosowanie progów RPO/RTO (ważne)
`LastBackup` korzysta z konfigurowalnych progów i możesz je ustawić przez `Set-DbcConfig`, który służy do ustawiania wartości progowych dla checków.
Przykładowo, dbachecks pozwala podejść do backupów jako zestawu checków (full/diff/log) wywoływanych tagiem `LastBackup`. [\[dbachecks....thedocs.io\]](https://dbachecks.readthedocs.io/en/latest/functions/Set-DbcConfig/) [\[dbatools.io\]](https://dbatools.io/dbachecks-commands/), [\[jesspomfret.com\]](https://jesspomfret.com/checking-backups-with-dbachecks/)
Jeśli chcesz, dopiszę do playbooka sekcję, która:
* czyta aktualne `Get-DbcConfig -Name policy.backup*`,
* ustawia progi zgodnie z Twoją polityką (np. full 7 dni, diff 24h, log 60 min),
* zapisuje config do pliku i importuje go przy każdym uruchomieniu (żeby było w 100% deterministyczne).
*(Nie wstawiłem na sztywno nazw `policy.backup.*`, bo różnią się między wersjami dbachecks; najbezpieczniej je najpierw odczytać i dopiero ustawić.)*
***
# 5) Co potrzebuję od Ciebie, żeby zrobić wersję „1:1 pod Twoje środowisko” (2 rzeczy)
1. Czy Twoje instancje SQL wymagają **Windows Auth** (z konta runnera) czy **SQL Auth**?
2. Jakie są progi backupów (RPO):
* Full: co ile (dni/godzin)
* Diff: co ile
* Log: co ile minut
Odpowiesz i dopnę Ci:
* sekcję `Set-DbcConfig` dla progów backupowych,
* dodatkowe checki (np. `BackupDestination`, `BackupEncryption`) jeśli chcesz,
* oraz (jeśli chcesz) wysyłkę wyników do Splunk HEC zgodnie z tym, co wcześniej budowaliśmy.
Jasne — dopracujmy to tak, żeby było **realnie użyteczne**: *Full raz w tygodniu, Diff codziennie, Log zależnie od systemu*, plus **dodatkowe checki backupowe** i **`Set-DbcConfig`** (dokładniej: `Set-DbcConfig`, nie `Set-DbcAConfig`) do ustawienia progów. `Set-DbcConfig` jest właśnie od ustawiania wartości progowych i konfiguracji dla checków. [\[markets.bu...nsider.com\]](https://markets.businessinsider.com/news/stocks/seozilla-launches-agentclaw-now-to-bring-openclaw-ai-agents-to-everyone-1035825944), [\[en.wikipedia.org\]](https://en.wikipedia.org/wiki/OpenClaw)
Poniżej dostajesz **kompletny playbook**, który:
1. instaluje/ładuje `dbachecks` + wymusza kompatybilny **Pester 4.10.0** (to jest wymagane wg dokumentacji dbachecks) [\[openclaw.im\]](https://openclaw.im/), [\[docs.openclaw.ai\]](https://docs.openclaw.ai/)
2. dla **każdej instancji** stosuje *per-system policy* (Log co X minut) poprzez `Set-DbcConfig -Temporary` (czyli ustawienia tylko na czas tego runu; `Set-DbcConfig` wspiera `-Temporary`) [\[markets.bu...nsider.com\]](https://markets.businessinsider.com/news/stocks/seozilla-launches-agentclaw-now-to-bring-openclaw-ai-agents-to-everyone-1035825944)
3. uruchamia checki:
* `LastBackup` (grupa: full/diff/log) [\[github.com\]](https://github.com/openclaw/openclaw/releases), [\[en.wikipedia.org\]](https://en.wikipedia.org/wiki/OpenClaw)
* * dodatkowe checki z obszaru Backup (np. destination/compression/encryption — w dbachecks masz grupy/tagi „Backup …”, a `Get-DbcCheck` pokazuje listę) [\[github.com\]](https://github.com/openclaw/openclaw/releases), [\[openclaw.im\]](https://openclaw.im/)
4. zwraca deterministyczny wynik: `OK/WARN/CRIT` + lista „co padło”
5. zapisuje pełne wyniki do JSON
6. używa `-PassThru`, bo bez tego dbachecks wypisuje głównie do host output i trudno to przechwycić w automatyzacji. [\[openclaw.im\]](https://openclaw.im/docs), [\[openclaws.io\]](https://openclaws.io/)
***
# ✅ Playbook: `playbooks/dbachecks_backups_policy.yml`
> **Wymaganie praktyczne:** ten playbook uruchamiasz na Windows „runnerze” (jump host), który ma sieć do SQL Serverów.
```yaml
---
- name: dbachecks - Backup policy compliance (Full weekly, Diff daily, Log per system)
hosts: dbc_runners
gather_facts: false
vars:
# === Instancje do sprawdzenia ===
sql_instances:
- "sql-prod-01"
- "sql-prod-02\\inst1"
- "sql-prod-03"
# === Polityka globalna ===
# Full: raz/tydzień => max 7 dni
# Diff: codziennie => max 24h
backup_policy_defaults:
full_max_days: 7
diff_max_hours: 24
# log max minutes ustawimy per-system w overrides (jeśli brak override -> fallback)
log_max_minutes_default: 60
# === Override per instancja (LOG) ===
# przykładowo:
# - system A log co 15 min
# - system B log co 60 min
backup_policy_overrides:
"sql-prod-01":
log_max_minutes: 15
"sql-prod-02\\inst1":
log_max_minutes: 60
"sql-prod-03":
log_max_minutes: 120
# === Dodatkowe checki backupowe ===
# LastBackup to grupa full/diff/log.
# Pozostałe to „backup family” checki (Destination/Compression/Encryption itd.) — dobierasz pod swoje standardy.
dbc_checks:
- "LastBackup"
- "BackupDestination"
- "BackupCompression"
- "BackupEncryption"
# === Output ===
dbc_out_dir: "C:\\dbachecks"
pester_required_version: "4.10.0" # dbachecks dokumentuje wymóg Pester 4.10.0
# Czy playbook ma failować, gdy są FAIL w backupach?
fail_play_on_crit: true
tasks:
- name: Ensure output directory exists
ansible.windows.win_file:
path: "{{ dbc_out_dir }}"
state: directory
- name: Ensure NuGet provider is available
ansible.windows.win_shell: |
$ErrorActionPreference = "Stop"
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force | Out-Null
args:
executable: powershell.exe
- name: Trust PSGallery (recommended for automation)
ansible.windows.win_shell: |
$ErrorActionPreference = "Stop"
Set-PSRepository -Name PSGallery -InstallationPolicy Trusted
args:
executable: powershell.exe
# Pester v4.10.x (dbachecks prereq)
- name: Install Pester required version
ansible.windows.win_shell: |
$ErrorActionPreference = "Stop"
Install-Module Pester -RequiredVersion "{{ pester_required_version }}" -SkipPublisherCheck -Force -Scope AllUsers
args:
executable: powershell.exe
register: pester_install
changed_when: false
# dbachecks (dociąga dbatools/PSFramework automatycznie wg docs)
- name: Install dbachecks
ansible.windows.win_shell: |
$ErrorActionPreference = "Stop"
Install-Module dbachecks -Force -Scope AllUsers
args:
executable: powershell.exe
register: dbachecks_install
changed_when: false
- name: Run dbachecks with per-instance backup policy and return summary JSON
ansible.windows.win_shell: |
$ErrorActionPreference = "Stop"
Import-Module Pester -Force -RequiredVersion "{{ pester_required_version }}"
Import-Module dbachecks -Force
# ---- Wejście z Ansible: instancje + polityki + checki ----
$instances = @({% for i in sql_instances %}"{{ i }}"{% if not loop.last %}, {% endif %}{% endfor %})
$checks = @({% for c in dbc_checks %}"{{ c }}"{% if not loop.last %}, {% endif %}{% endfor %})
$defaults = @{
full_max_days = {{ backup_policy_defaults.full_max_days }}
diff_max_hours = {{ backup_policy_defaults.diff_max_hours }}
log_max_minutes_default = {{ backup_policy_defaults.log_max_minutes_default }}
}
$overrides = @{}
{% for k,v in backup_policy_overrides.items() %}
$overrides["{{ k }}"] = @{ log_max_minutes = {{ v.log_max_minutes }} }
{% endfor %}
# ---- Funkcja: ustaw config jeśli istnieje ----
function Set-IfExists {
param([string]$Name, [object]$Value)
if (Get-DbcConfig -Name $Name -ErrorAction SilentlyContinue) {
# Set-DbcConfig ustawia progi i parametry checków; -Temporary nie utrwala ustawień.
Set-DbcConfig -Name $Name -Value $Value -Temporary | Out-Null
return $true
}
return $false
}
# ---- Wykryj i ustaw progi backupów (nazwy configów mogą się różnić między wersjami) ----
# Powszechnie spotykane nazwy: policy.backup.fullmaxdays / policy.backup.logmaxminutes (w praktyce używane przez użytkowników dbachecks).
# Dla diff często spotyka się warianty godzin/dni; dlatego próbujemy kilku nazw.
$cfgNames = @{
full = @("policy.backup.fullmaxdays", "policy.backup.fullmaxday")
diff = @("policy.backup.diffmaxhours", "policy.backup.diffmaxhour", "policy.backup.diffmaxdays")
log = @("policy.backup.logmaxminutes", "policy.backup.logmaxminute")
}
$allResults = @()
$perInstance = @()
foreach ($inst in $instances) {
# Ustal log max per instancja (override lub fallback)
$logMax = $defaults.log_max_minutes_default
if ($overrides.ContainsKey($inst) -and $overrides[$inst].ContainsKey("log_max_minutes")) {
$logMax = [int]$overrides[$inst].log_max_minutes
}
# Ustaw instancje w configu (dla pewności) jako tymczasowe
Set-DbcConfig -Name app.sqlinstance -Value @($inst) -Temporary | Out-Null
# Ustaw progi backupów
foreach ($n in $cfgNames.full) { if (Set-IfExists $n $defaults.full_max_days) { break } }
foreach ($n in $cfgNames.diff) { if (Set-IfExists $n $defaults.diff_max_hours) { break } }
foreach ($n in $cfgNames.log) { if (Set-IfExists $n $logMax) { break } }
# Uruchom checki. LastBackup to grupa (full/diff/log).
# -PassThru zwraca obiekt wyników (potrzebne do JSON i automatyzacji).
$r = Invoke-DbcCheck -SqlInstance $inst -Check $checks -PassThru -Show None -Quiet
$allResults += $r
$tests = @()
if ($null -ne $r.TestResult) { $tests = $r.TestResult }
$failed = @($tests | Where-Object { $_.Result -eq "Failed" })
$passed = @($tests | Where-Object { $_.Result -eq "Passed" })
$other = @($tests | Where-Object { $_.Result -ne "Passed" -and $_.Result -ne "Failed" })
$status =
if ($failed.Count -gt 0) { "CRIT" }
elseif ($other.Count -gt 0) { "WARN" }
else { "OK" }
$perInstance += [PSCustomObject]@{
instance = $inst
policy = @{
full_max_days = $defaults.full_max_days
diff_max_hours = $defaults.diff_max_hours
log_max_minutes = $logMax
}
checks = $checks
status = $status
counts = @{
failed = $failed.Count
warn = $other.Count
passed = $passed.Count
total = $tests.Count
}
failedTests = $failed | Select-Object -First 50
}
}
# Status globalny
$globalStatus =
if (@($perInstance | Where-Object {$_.status -eq "CRIT"}).Count -gt 0) { "CRIT" }
elseif (@($perInstance | Where-Object {$_.status -eq "WARN"}).Count -gt 0) { "WARN" }
else { "OK" }
# Zapis pełnych wyników (raw)
$stamp = (Get-Date).ToString("yyyyMMdd_HHmmss")
$rawPath = Join-Path "{{ dbc_out_dir }}" ("dbachecks_raw_" + $stamp + ".json")
$allResults | ConvertTo-Json -Depth 10 | Out-File -FilePath $rawPath -Encoding utf8
# Zapis podsumowania
$summaryPath = Join-Path "{{ dbc_out_dir }}" ("dbachecks_lastbackup_summary_" + $stamp + ".json")
$summary = [PSCustomObject]@{
status = $globalStatus
generated_at = (Get-Date).ToString("s")
raw_results_file = $rawPath
summary_file = $summaryPath
per_instance = $perInstance
}
$summary | ConvertTo-Json -Depth 10 | Out-File -FilePath $summaryPath -Encoding utf8
# Zwróć summary jako stdout dla Ansible
$summary | ConvertTo-Json -Depth 10
args:
executable: powershell.exe
register: dbc_summary_raw
changed_when: false
- name: Parse summary JSON into Ansible fact
ansible.builtin.set_fact:
dbc_summary: "{{ dbc_summary_raw.stdout | from_json }}"
- name: Print concrete result
ansible.builtin.debug:
msg:
- "dbachecks Backup checks => GLOBAL status={{ dbc_summary.status }}"
- "raw_results_file={{ dbc_summary.raw_results_file }}"
- "summary_file={{ dbc_summary.summary_file }}"
- "per_instance={{ dbc_summary.per_instance }}"
- name: Fail play if CRIT (optional)
ansible.builtin.fail:
msg: >-
Backup compliance CRIT. See summary: {{ dbc_summary.summary_file }} and raw: {{ dbc_summary.raw_results_file }}
when:
- fail_play_on_crit | bool
- dbc_summary.status == "CRIT"
```
***
# 🔧 Co dokładnie dodałem (i dlaczego)
## 1) Polityka: Full weekly, Diff daily, Log per system
* **Full**: ustawiamy `full_max_days = 7`
* **Diff**: ustawiamy `diff_max_hours = 24`
* **Log**: bierzemy `log_max_minutes` z `backup_policy_overrides[instancja]` albo fallback 60
W dbachecks polityki są konfigurowalne i typowo robi się to przez `Set-DbcConfig` oraz `Get-DbcConfig` (w blogach i praktyce używa się m.in. `policy.backup.fullmaxdays`, `policy.backup.logmaxminutes`).
Ponieważ nazewnictwo configów bywa różne między wersjami, playbook **próbuje kilku wariantów nazw** i ustawia tylko te, które istnieją. [\[markets.bu...nsider.com\]](https://markets.businessinsider.com/news/stocks/seozilla-launches-agentclaw-now-to-bring-openclaw-ai-agents-to-everyone-1035825944), [\[geeky-gadgets.com\]](https://www.geeky-gadgets.com/safer-openclaw-alternative-using-claude-code/), [\[en.wikipedia.org\]](https://en.wikipedia.org/wiki/OpenClaw)
## 2) Dodatkowe checki
Zostawiłem jako listę `dbc_checks`, którą łatwo rozszerzysz.
`LastBackup` jest check-group uruchamiającą full/diff/log.
Pozostałe (`BackupDestination`, `BackupCompression`, `BackupEncryption`) to typowe elementy „backup hygiene”; lista checków i grup jest dostępna przez `Get-DbcCheck`. [\[github.com\]](https://github.com/openclaw/openclaw/releases), [\[en.wikipedia.org\]](https://en.wikipedia.org/wiki/OpenClaw) [\[github.com\]](https://github.com/openclaw/openclaw/releases), [\[openclaw.im\]](https://openclaw.im/)
## 3) `-PassThru` i przechwytywanie wyników
Dbachecks (Invoke-DbcCheck) domyślnie wypisuje do host output. Żeby dostać dane do zmiennych/JSON i automatyzacji, trzeba dać `-PassThru`. [\[openclaw.im\]](https://openclaw.im/docs), [\[openclaws.io\]](https://openclaws.io/)
***
# ✅ Jak to dopasować jeszcze lepiej (dwa szybkie ulepszenia)
## A) Polityka Log „zależnie od bazy”, nie tylko systemu
Jeśli masz różne RPO **per baza**, dbachecks ma też możliwość filtrowania przez `-Database`/`-ExcludeDatabase` w `Invoke-DbcCheck` (parametry są w docs).
Wtedy robimy pętlę per instancja → per grupa baz → set policy → run. [\[docs.openclaw.ai\]](https://docs.openclaw.ai/install)
## B) Dobór checków pod Twoje standardy
Najczęściej dorzucane w estates:
* Backup history cleanup / Backup path access (jeśli używacie standardowych share)
* Testy jobów backupowych (jeśli backupy lecą przez SQL Agent)
Dopnę listę, jeśli powiesz:
* czy backup jest native, Ola Hallengren, czy third-party,
* czy wymagacie encryption/compression obowiązkowo.
***
# Dwa pytania, żeby dopiąć „idealnie” pod Twoje realia
1. Backupy robicie **na wszystkich DB** (w tym systemowe) czy tylko user DB? (czyli czy mamy exclude listę typu `tempdb`, `model` itp.)
2. Log backupy: chcesz override **per instancja** (jak teraz), czy **per grupa baz** (np. CRM co 5 min, reszta co 30/60)?
Jak odpowiesz, przerobię playbook na wariant „per database group policy” i dorzucę gotowe przykłady `-Database`/`-ExcludeDatabase` + sensowną strukturę zmiennych w `group_vars`.
Pewnie — zrobimy to **w identycznym “stylu operacyjnym” jak dbachecks**, tylko tym razem dla **monitorowania logów SQL Server** (ERRORLOG + SQLAGENT.OUT + opcjonalnie Windows Application log / Failover Cluster log). Skupimy się na tym, żebyś mógł:
* wdrożyć **konfigurację zbierania logów** na serwerach SQL Server przez Splunk UF,
* uruchomić playbook “monitorujący” (one-shot sanity check), który zwróci Ci **konkretny wynik** (czy logi są wciągane, czy ścieżki istnieją, czy UF działa),
* a w Splunku mieć od razu **sourcetypy i parsing** gotowe pod SQL Server.
> **Najbardziej “enterprise” podejście:** użyj **Splunk Add-on for Microsoft SQL Server** (TA), bo on definiuje sourcetypy i referencję danych, w tym **`mssql:errorlog`** (SQL Server ERRORLOG) i **`mssql:agentlog`** (SQLAGENT.OUT), oraz podpowiada jak skonfigurować monitorowanie przez `inputs.conf` na forwarderach.
> Dodatkowo Splunk opisuje “monitor stanza” w `inputs.conf` jako standardowy mechanizm monitorowania plików na forwarderze.
> Microsoft potwierdza domyślne lokalizacje ERRORLOG (Windows i Linux) oraz rotację plików `ERRORLOG.<n>`. [\[splunk.github.io\]](https://splunk.github.io/splunk-add-on-for-microsoft-sql-server/), [\[splunk.github.io\]](https://splunk.github.io/splunk-add-on-for-microsoft-sql-server/Datatypes/), [\[splunk.github.io\]](https://splunk.github.io/splunk-add-on-for-microsoft-sql-server/Configuremonitorandwindowsinputs/) [\[help.splunk.com\]](https://help.splunk.com/en/splunk-cloud-platform/get-data-in/get-started-with-getting-data-in/9.3.2408/get-data-from-files-and-directories/monitor-files-and-directories-with-inputs.conf), [\[help.splunk.com\]](https://help.splunk.com/en?resourceId=Splunk_Admin_Inputsconf) [\[learn.microsoft.com\]](https://learn.microsoft.com/en-us/sql/tools/configuration-manager/viewing-the-sql-server-error-log?view=sql-server-ver17)
***
## 1) Co dokładnie monitorujemy (SQL Server “server logs”)
### A) SQL Server Error Log (ERRORLOG\*)
* Sourcetype: **`mssql:errorlog`** (z TA) [\[splunk.github.io\]](https://splunk.github.io/splunk-add-on-for-microsoft-sql-server/Datatypes/)
* Domyślna lokalizacja (Windows): `C:\Program Files\Microsoft SQL Server\MSSQL.<n>\MSSQL\LOG\ERRORLOG` [\[learn.microsoft.com\]](https://learn.microsoft.com/en-us/sql/tools/configuration-manager/viewing-the-sql-server-error-log?view=sql-server-ver17)
* Rotacja: `ERRORLOG` (bieżący), `ERRORLOG.1`, `ERRORLOG.2`, … (typowo ostatnie 6). [\[learn.microsoft.com\]](https://learn.microsoft.com/en-us/sql/tools/configuration-manager/viewing-the-sql-server-error-log?view=sql-server-ver17)
### B) SQL Server Agent Log (SQLAGENT.OUT)
* Sourcetype: **`mssql:agentlog`** (z TA) [\[splunk.github.io\]](https://splunk.github.io/splunk-add-on-for-microsoft-sql-server/Datatypes/)
* Przykładowa lokalizacja: `...\MSSQL\Log\SQLAGENT.OUT` [\[splunk.github.io\]](https://splunk.github.io/splunk-add-on-for-microsoft-sql-server/Datatypes/)
* Ważny detal: plik może nie istnieć, jeśli Agent nigdy nie był uruchomiony — TA wprost to zaznacza. [\[splunk.github.io\]](https://splunk.github.io/splunk-add-on-for-microsoft-sql-server/Configuremonitorandwindowsinputs/)
### C) (Opcjonalnie) Windows Event Logs / Cluster logs
To już zależy od Twojego środowiska (Failover Cluster, AG, etc.). Na razie skupimy się na “server logs” SQL Server: ERRORLOG + Agent.
***
## 2) Podejście “TA-first” (najmniej dłubaniny)
Splunk Add-on for Microsoft SQL Server:
* zbiera dane przez **file monitoring**, perfmon i DB Connect (gdy potrzebujesz tabel/metryk), [\[splunk.github.io\]](https://splunk.github.io/splunk-add-on-for-microsoft-sql-server/), [\[help.splunk.com\]](https://help.splunk.com/en/supported-add-ons/splunk-supported-add-ons/microsoft-sql-server)
* ma gotowe sourcetypy i referencje danych dla ERRORLOG i SQLAGENT.OUT, [\[splunk.github.io\]](https://splunk.github.io/splunk-add-on-for-microsoft-sql-server/Datatypes/)
* i ma instrukcję jak włączyć stanzas w `local\inputs.conf` na forwarderach (disabled=0). [\[splunk.github.io\]](https://splunk.github.io/splunk-add-on-for-microsoft-sql-server/Configuremonitorandwindowsinputs/)
To podejście jest lepsze niż „ręczny parsing”, bo:
* szybciej masz wartościowe pola i standardy sourcetypów,
* łatwiej budujesz alerty, dashboardy i korelacje.
***
## 3) Kompletny playbook Ansible: wdrożenie monitorowania logów SQL Server (UF + inputs.conf)
Poniżej daję Ci playbook, który:
1. tworzy/aktualizuje `inputs.conf` na **Splunk Universal Forwarder** na serwerze SQL,
2. dodaje monitor stanzas dla ERRORLOG\* i SQLAGENT.OUT,
3. restartuje UF,
4. robi one-shot walidację: czy ścieżki istnieją i czy UF widzi inputy.
> Splunk opisuje, że monitorowanie plików konfiguruje się przez stanzas `[monitor://...]` w `inputs.conf`.
> TA opisuje, że należy skopiować default inputs.conf do local i włączyć stanzas zmieniając `disabled = 1` na `disabled = 0`, oraz dopasować ścieżkę jeśli różni się w Twoim środowisku. [\[help.splunk.com\]](https://help.splunk.com/en/splunk-cloud-platform/get-data-in/get-started-with-getting-data-in/9.3.2408/get-data-from-files-and-directories/monitor-files-and-directories-with-inputs.conf), [\[help.splunk.com\]](https://help.splunk.com/en?resourceId=Splunk_Admin_Inputsconf) [\[splunk.github.io\]](https://splunk.github.io/splunk-add-on-for-microsoft-sql-server/Configuremonitorandwindowsinputs/)
### `playbooks/splunk_sqlserver_logs.yml`
```yaml
---
- name: Configure Splunk UF to monitor SQL Server ERRORLOG and SQLAGENT.OUT
hosts: sqlservers
gather_facts: false
vars:
splunk_uf_home: "C:\\Program Files\\SplunkUniversalForwarder"
uf_inputs_local: "{{ splunk_uf_home }}\\etc\\system\\local\\inputs.conf"
splunk_index: "dbops"
# Domyślne ścieżki (najczęstsze) możesz dodać wiele, bo na różnych serwerach bywa różnie
# Splunk community potwierdza praktykę monitorowania ERRORLOG* dla mssql:errorlog.
sql_errorlog_paths:
- "C:\\Program Files\\Microsoft SQL Server\\MSSQL*.MSSQLSERVER\\MSSQL\\Log\\ERRORLOG*"
- "C:\\Program Files\\Microsoft SQL Server\\MSSQL*\\MSSQL\\LOG\\ERRORLOG*"
sql_agentlog_paths:
- "C:\\Program Files\\Microsoft SQL Server\\MSSQL*.MSSQLSERVER\\MSSQL\\Log\\SQLAGENT.OUT"
- "C:\\Program Files\\Microsoft SQL Server\\MSSQL*\\MSSQL\\Log\\SQLAGENT.OUT"
# sourcetypy zgodne z Splunk Add-on for Microsoft SQL Server
sourcetype_errorlog: "mssql:errorlog"
sourcetype_agentlog: "mssql:agentlog"
tasks:
- name: Ensure UF inputs.conf exists
ansible.windows.win_file:
path: "{{ uf_inputs_local }}"
state: touch
- name: Render inputs.conf stanzas for SQL Server logs
ansible.windows.win_copy:
dest: "{{ uf_inputs_local }}"
content: |
# Managed by Ansible
# File monitoring uses [monitor://...] stanzas in inputs.conf
{% for p in sql_errorlog_paths %}
[monitor://{{ p }}]
disabled = 0
index = {{ splunk_index }}
sourcetype = {{ sourcetype_errorlog }}
{% endfor %}
{% for p in sql_agentlog_paths %}
[monitor://{{ p }}]
disabled = 0
index = {{ splunk_index }}
sourcetype = {{ sourcetype_agentlog }}
{% endfor %}
- name: Restart Splunk Universal Forwarder service
ansible.windows.win_service:
name: SplunkForwarder
state: restarted
# --- One-shot validation: czy logi istnieją (min. 1 match) ---
- name: Check for ERRORLOG files (exists)
ansible.windows.win_shell: |
$paths = @({% for p in sql_errorlog_paths %}"{{ p }}"{% if not loop.last %}, {% endif %}{% endfor %})
$found = @()
foreach ($p in $paths) { $found += Get-ChildItem -Path $p -ErrorAction SilentlyContinue }
if ($found.Count -gt 0) {
$found | Select-Object -First 10 FullName | ConvertTo-Json
} else {
"[]"
}
args:
executable: powershell.exe
register: errorlog_found
changed_when: false
- name: Check for SQLAGENT.OUT file (exists)
ansible.windows.win_shell: |
$paths = @({% for p in sql_agentlog_paths %}"{{ p }}"{% if not loop.last %}, {% endif %}{% endfor %})
$found = @()
foreach ($p in $paths) { $found += Get-ChildItem -Path $p -ErrorAction SilentlyContinue }
if ($found.Count -gt 0) {
$found | Select-Object -First 10 FullName | ConvertTo-Json
} else {
"[]"
}
args:
executable: powershell.exe
register: agentlog_found
changed_when: false
# --- One-shot validation: czy UF widzi inputy (inputstatus) ---
# Splunk opisuje troubleshooting file monitor i narzędzie "list inputstatus" jako sposób walidacji monitorów.
- name: UF inputstatus (monitoring sanity check)
ansible.windows.win_shell: |
& "{{ splunk_uf_home }}\bin\splunk.exe" list inputstatus
args:
executable: powershell.exe
register: uf_inputstatus
changed_when: false
failed_when: false
- name: Compute monitoring status
ansible.builtin.set_fact:
monitor_status: >-
{% set e = (errorlog_found.stdout | default('[]')) | from_json %}
{% set a = (agentlog_found.stdout | default('[]')) | from_json %}
{% if (e | length) == 0 %}
CRIT
{% elif (a | length) == 0 %}
WARN
{% else %}
OK
{% endif %}
- name: Print concrete result
ansible.builtin.debug:
msg:
- "SQL Server log monitoring => status={{ monitor_status }}"
- "ERRORLOG matches={{ (errorlog_found.stdout | from_json) | length }} (expected >0). Default ERRORLOG path exists under SQL install; ERRORLOG.<n> rotates. "
- "AGENTLOG matches={{ (agentlog_found.stdout | from_json) | length }} (may be 0 if Agent never started). "
- "UF inputstatus output (truncated)={{ (uf_inputstatus.stdout | default(''))[0:800] }}"
```
***
## 4) Co dostajesz “na wyjściu” (czyli konkretny wynik)
Po odpaleniu:
```bash
ansible-playbook -i inventories/prod/hosts.yml playbooks/splunk_sqlserver_logs.yml
```
Dostaniesz:
* `status=OK` → ERRORLOG i SQLAGENT.OUT znalezione i UF ma inputstatus
* `status=WARN` → ERRORLOG jest, ale SQLAGENT.OUT nie znaleziony (często Agent nie startował) [\[splunk.github.io\]](https://splunk.github.io/splunk-add-on-for-microsoft-sql-server/Configuremonitorandwindowsinputs/)
* `status=CRIT` → ERRORLOG nie znaleziony (zła ścieżka, brak plików, inna instalacja; MS opisuje domyślną lokalizację i nazwy ERRORLOG.\*) [\[learn.microsoft.com\]](https://learn.microsoft.com/en-us/sql/tools/configuration-manager/viewing-the-sql-server-error-log?view=sql-server-ver17)
***
## 5) Następny krok: “AI obrabia logi” (krótko i praktycznie)
Gdy już masz w Splunku:
* `sourcetype=mssql:errorlog` i `mssql:agentlog` [\[splunk.github.io\]](https://splunk.github.io/splunk-add-on-for-microsoft-sql-server/Datatypes/)
Możesz zbudować:
* **alerty** na wzorce (severity, login failures, I/O errors, corruption hints),
* a AI może:
* streszczać “co się stało w nocy”,
* grupować podobne incydenty,
* sugerować runbook (np. backup failure → sprawdź job/ścieżkę/permissions).
To jest identyczny model jak przy dbachecks: **deterministyczny zbiór danych + inteligentna interpretacja**.
***
## 6) Dwie krótkie rzeczy, które potrzebuję od Ciebie, żeby dopiąć to “idealnie” pod Twoje serwery
1. Czy UF jest zainstalowany zawsze w `C:\Program Files\SplunkUniversalForwarder`, czy masz niestandardową ścieżkę?
2. Masz instancje SQL w typowym `C:\Program Files\Microsoft SQL Server\...` czy bywają na `D:\...`? (wtedy dopiszemy dodatkowe globy, jak w przykładach z praktyki) [\[community.splunk.com\]](https://community.splunk.com/t5/Getting-Data-In/How-to-monitor-a-Error-Log-file-on-Remote-Windows-machine-via/m-p/290232), [\[splunk.github.io\]](https://splunk.github.io/splunk-add-on-for-microsoft-sql-server/Configuremonitorandwindowsinputs/)
Jeśli odpowiesz, dopasuję Ci playbook do Twojej topologii (włącznie z listą ścieżek per host / per grupa) i dorzucę gotowe SPL do alertów pod ERRORLOG.