3981 lines
135 KiB
Markdown
3981 lines
135 KiB
Markdown
# 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/mini‑serwer) 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 „DBA‑Night”** 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**: 5–10 komend / zapytań read‑only (DMVs, msdb, logi, plany)
|
||
3. **Ocena ryzyka**: czy można robić auto‑remediation?
|
||
4. **Działanie** (tylko jeśli spełnione warunki)
|
||
5. **Raport** (kto, co, kiedy, wynik, linki)
|
||
6. **Escalacja** (jeśli niepewne → ping on‑call + gotowy raport)
|
||
|
||
**Przykłady „bezpiecznych auto‑remediation”:**
|
||
|
||
* 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 non‑prod** lub w zdefiniowanym oknie
|
||
|
||
***
|
||
|
||
## 5) Raportowanie (rano chcesz “story”, nie log)
|
||
|
||
Agent powinien wysyłać:
|
||
|
||
* **1‑liner**: 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ń 1–2:** Gateway + kanał + agent + polityki narzędzi
|
||
**Dzień 3:** Alert ingestion (np. webhook/wiadomość)
|
||
**Dzień 4:** 2 runbooki read‑only (blocking, failed jobs)
|
||
**Dzień 5:** 1 auto‑remediation 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 „IDE‑centric” 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, anti‑patterns)
|
||
* `CLAUDE.md` (zasady: RPO/RTO, deploy policy, co wolno na prod, styl T‑SQL) — 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 anty‑patterny: implicit conversions, non‑SARG, 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ń trade‑offó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 set‑based
|
||
* ujednolicenie transakcji
|
||
|
||
## 5) Git workflow (żeby było „enterprise‑grade”)
|
||
|
||
* 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 (end‑to‑end)
|
||
|
||
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ę 3‑częś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ć auto‑remediation, 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 **read‑only** 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 (read‑only)** – zbieranie faktów
|
||
3. **Decyzja** – czy wolno auto‑remediate
|
||
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 (read‑only)
|
||
|
||
### 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)
|
||
|
||
**Auto‑remediation 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. 10–15) **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 5–10 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 (read‑only)
|
||
|
||
### 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 (read‑only)
|
||
|
||
```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)
|
||
|
||
**Auto‑backup 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 < 10–15% na volume
|
||
* SQL błędy o braku miejsca, autogrowth fails.
|
||
|
||
## 2) Diag (read‑only)
|
||
|
||
### 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)
|
||
|
||
**Auto‑remediation 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 > 5–10 min
|
||
* top waits: SOS\_SCHEDULER\_YIELD, CXPACKET/CXCONSUMER (zależnie), THREADPOOL, itp.
|
||
|
||
## 2) Diag (read‑only)
|
||
|
||
### 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)
|
||
|
||
**Auto‑akcje 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 (read‑only)
|
||
|
||
### 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.
|
||
Auto‑akcje (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** (on‑call/DBA),
|
||
* dało się je łatwo „opakować” pod **nocnego DBA** (OpenClaw: diag → decyzja → akcja → raport),
|
||
* miały **guardraile** (kiedy wolno auto‑failover / kiedy absolutnie nie),
|
||
* zawierały gotowe **T‑SQL** 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 (read‑only)
|
||
|
||
### 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 (read‑only)
|
||
|
||
### 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. > 1–5 GB w zależności od RPO)
|
||
* aplikacja ma opóźnione odczyty (read-only routing)
|
||
|
||
## 2) Diag (read‑only)
|
||
|
||
```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 (AO‑1).
|
||
|
||
## 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 (read‑only) – 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 (read‑only) – oceń ryzyko
|
||
|
||
* na sekundarce sprawdź lag/queues (AO‑3)
|
||
* 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 AO‑4)
|
||
* 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 (read‑only)
|
||
|
||
### 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 (read‑only)
|
||
|
||
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
|
||
|
||
* AO‑1/AO‑2/AO‑3/AO‑6/AO‑7: **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
|
||
|
||
* AO‑4 planned: tylko w oknie serwisowym i przy SYNCHRONIZED.
|
||
* AO‑5 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** (on‑call/DBA),
|
||
* dało się je łatwo „opakować” pod **nocnego DBA** (OpenClaw: diag → decyzja → akcja → raport),
|
||
* miały **guardraile** (kiedy wolno auto‑failover / kiedy absolutnie nie),
|
||
* zawierały gotowe **T‑SQL** 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 (read‑only)
|
||
|
||
### 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 (read‑only)
|
||
|
||
### 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. > 1–5 GB w zależności od RPO)
|
||
* aplikacja ma opóźnione odczyty (read-only routing)
|
||
|
||
## 2) Diag (read‑only)
|
||
|
||
```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 (AO‑1).
|
||
|
||
## 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 (read‑only) – 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 (read‑only) – oceń ryzyko
|
||
|
||
* na sekundarce sprawdź lag/queues (AO‑3)
|
||
* 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 AO‑4)
|
||
* 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 (read‑only)
|
||
|
||
### 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 (read‑only)
|
||
|
||
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
|
||
|
||
* AO‑1/AO‑2/AO‑3/AO‑6/AO‑7: **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
|
||
|
||
* AO‑4 planned: tylko w oknie serwisowym i przy SYNCHRONIZED.
|
||
* AO‑5 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. M‑1 lag, M‑3 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 1–5 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 1–5 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` (M‑1, M‑3, GR‑1…)
|
||
* `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 M‑1)
|
||
|
||
```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 M‑2)
|
||
|
||
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 M‑3)
|
||
|
||
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 M‑4)
|
||
|
||
```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 M‑5)
|
||
|
||
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 2–3 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 read‑only
|
||
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 on‑prem — 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 GR‑1 vs M‑1)
|
||
|
||
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** (M‑1, M‑2, M‑3, GR‑1…).
|
||
|
||
|
||
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 (M‑1/M‑3),
|
||
* 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 T‑SQL
|
||
* 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 estate’u”. [\[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 check’i
|
||
|
||
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.
|