[End-Gamers] ์ค์ AI ํ์ต์ฉ ๋ก๊ทธ๋ฅผ ๋ง๋ค์ด๋ณด์ - Loki·Prometheus·Grafana ์คํ ๊ตฌ์ถ๊ธฐ
"ํ ์คํธ ๋ก๊ทธ๋ ์ด๋์ ๊ตฌํ์ง..?"
AI ์ฅ์ ๋ถ์ ๋๊ตฌ๋ฅผ ๋ง๋ค๋ค๊ฐ ์ ์ผ ๋จผ์ ๋งํ ๊ฒ ์ด๊ฑฐ์๋ค.
์ค์ ์ด์ ๋ก๊ทธ๋ ๋น์ฐํ ๋ชป ์ฐ๊ณ , ์์ผ๋ก ๋ง๋ค์๋ ํจํด์ด ๋๋ฌด ๋จ์กฐ๋ก์์ AI๊ฐ ์ ๋๋ก ํ์ต์ด ์ ๋๋ค.
loghub๊ฐ์ ์ ์ตํ ์ฌ์ดํธ๋ ์์ง๋ง, ๋ด๊ฐ ์ํ๋ ์ ๋ํผ ๋ก๊ทธ๋ KB ์คํ์ผ์ ๋ก๊ทธ๊ฐ ์๋์๊ธฐ ๋๋ฌธ์
ํ์ต์ฉ ๋ก๊ทธ๋ฅผ ๊ณ์ํด์ ์์ฑํ ๋ก๊ทธ ์์ฑ๊ธฐ๊ฐ ํ์ํ๋ค.
https://github.com/logpai/loghub
GitHub - logpai/loghub: A large collection of system log datasets for AI-driven log analytics [ISSRE'23]
A large collection of system log datasets for AI-driven log analytics [ISSRE'23] - logpai/loghub
github.com

๊ฒฐ๊ณผ์ ์ผ๋ก 1์ด์ N๊ฑด์ฉ 4์ข ๋ฅ์ ๊ธ์ต ๋ก๊ทธ๋ฅผ ์ค์๊ฐ์ผ๋ก ์๊ณ ,
Loki·Prometheus·Grafana๋ก ๋ชจ๋ํฐ๋งํ๋ ์คํ๊น์ง ๊ตฌ์ถํ๋ค.
1. ๋ ์ ์ฅ์๊ฐ ์ด๋ฃจ๋ ํ์ดํ๋ผ์ธ
ํ๋ก์ ํธ๋ ๋ ๋ ํฌ์งํ ๋ฆฌ๊ฐ ์ฐ๊ฒฐ๋ ๊ตฌ์กฐ๋ค.
log-autogenerator incident-autopsy
โโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
์๋๋ฆฌ์ค๋ณ ๋ก๊ทธ ์์ฑ (4์ข
) →→→ output/ ํด๋ ์ง์ ์ฝ๊ธฐ
→→→ incident API (REST)
→→→ Loki (GUID ๊ธฐ๋ฐ ์กฐํ)
↓
AI ๋ถ์ → ๋ณด๊ณ ์ ์์ฑ
log-autogenerator๊ฐ ์ค์ ๊ธ์ต ์์คํ ์ฒ๋ผ ๋์ํ๋ ๋ก๊ทธ๋ฅผ ๋ง๋ค๊ณ , incident-autopsy๊ฐ ๊ฐ์ ธ๋ค๊ฐ ๋ถ์ํ๋ค.
Loki·Prometheus·Grafana๋ ๊ทธ ํ๋ฆ์ ์ค์๊ฐ์ผ๋ก ๋ณด์ฌ์ฃผ๋ ๋ชจ๋ํฐ๋ง ์คํ์ด๋ค.
2. ํต์ฌ ์ค๊ณ - GUID ๊ณต์
์ค์ ๊ธ์ต ์์คํ ์์ ํธ๋์ญ์ ํ๋๋ ์ฌ๋ฌ ์์คํ ์ ๋์์ ํ์ ์ ๋จ๊ธด๋ค.
์ด๊ฑธ ์ฌํํ์ง ์์ผ๋ฉด AI๊ฐ "๋ฉํฐ์์ค ๋ก๊ทธ์์ ๊ฐ์ ์ฅ์ ๋ฅผ ์ฐพ๋" ์ฐ์ต์ ํ ์ ์๋ค.
์ค์ ์ํ ๋ฐ์ดํฐ๋ฅผ ๋ฏ์ด๋ณด๋ ํต์ฌ ํํธ๊ฐ ์์๋ค. Jennifer APM ๋ก๊ทธ์ KB Framework ์ฑ๋ก๊ทธ๊ฐ ๋์ผํ 33์ GUID๋ฅผ ๊ณต์ ํ๊ณ ์์๋ค.
Jennifer APM: GUID : AA0VW3YT2GJAGZ1Y8KXPQMNE7RFTU0C
Framework ์ฑ: [AA0VW3YT2GJAGZ1Y8KXPQMNE7RFTU0C][E][AbsSqlHandler] SQLException
์์ฑํ๋ ์์ค๋ 4์ข ์ด๋ค.
| ์์ค | ์๊ด ID |
|---|---|
| Jennifer APM Transaction | GUID 33์ (AA0...) |
| KB Framework ์ฑ๋ก๊ทธ | GUID 33์ (Jennifer์ ๋์ผ) |
| Spring Boot REST + Hibernate | UUID v4 (๋ณ๋) |
| Spring Boot Log4j | TXN-NNNNNNN (๋ณ๋) |
์์ ๋ ์์ค๊ฐ ๊ฐ์ GUID๋ฅผ ์ฐ๊ธฐ ๋๋ฌธ์, incident-autopsy๊ฐ GUID ํ๋๋ง ์๋ฉด ์ฐ๊ด๋ ๋ก๊ทธ ์ ๋ถ๋ฅผ ์ฐพ์๋ผ ์ ์๋ค.
3. ์๋๋ฆฌ์ค ์ค๊ณ
์ฅ์ ํจํด์ ๊ทธ๋ฅ ๋๋ค์ผ๋ก ๋ง๋ค๋ฉด ์๋ฏธ๊ฐ ์๋ค.
๊ทธ๋์ ์ค์ ๊ธ์ต ์์คํ ์์ ์์ฃผ ๋์ค๋ ํจํด 4์ข ์ ์ ์ํ๋ค.
| ์๋๋ฆฌ์ค | ์ํฉ | ์๋ต ์๊ฐ |
|---|---|---|
sql_exception |
SQL ์ปฌ๋ผ ์ฌ์ด์ฆ ์ด๊ณผ / ์ ์ฝ ์๋ฐ | 900~1500ms |
payment_timeout |
๊ฒฐ์ ๊ฒ์ดํธ์จ์ด ์๋ต ์์ | 3000~10050ms |
null_pointer |
์ฌ์ฉ์ ํ๋กํ LEFT JOIN ๊ฒฐ๊ณผ ์์ → NPE | 200~500ms |
normal |
์ ์ ์๋ฃ 3์ข | 50~400ms |
์๋ฌ ํ์ ๋ง๋ค ์คํํธ๋ ์ด์ค ๋ด์ฉ์ด ๋ค๋ฅด๋ค.
sql_exception์ด๋ฉด ORA-01401, DataIntegrityViolationException์ด ๋์ค๊ณ ,
null_pointer๋ฉด NullPointerException์ด ๋์จ๋ค. ๊ฐ์ ํจํด๋ง ๋ฐ๋ณตํ๋ฉด AI๊ฐ ์ธ์๋ฒ๋ฆฌ๊ธฐ ๋๋ฌธ์ ๋ค์์ฑ์ด ์ค์ํ๋ค.
์ฐ์ ๋ชจ๋๋ก ์คํํ๋ฉด ํ๊ตญ ๊ธ์ต๊ถ ๊ธฐ์ค ์๊ฐ๋๋ณ ํผํฌ ํจํด๋ ์ ์ฉ๋๋ค.
python generator.py --continuous --rate 2 --error-rate 0.05 --peak-pattern
์ค์ 10์, ์คํ 15์์ ํธ๋ํฝ์ด ์ต๋ 2๋ฐฐ๊น์ง ์ฌ๋ผ๊ฐ๋ ์ฌ์ธํ ๋ชจ๋ธ์ด๋ค. ์ค์ ์์คํ ๊ณผ ๋น์ทํ ํธ๋ํฝ ํจํด์ ์ฌํํ๋ ค๊ณ ๋ฃ์๋ค.
4. Loki๋ฅผ ์ ํํ ์ด์
ํ์ผ๋ก๋ง ์์ผ๋ฉด GUID ๊ฒ์์ด ๋ถํธํ๋ค. ๊ทธ๋ฆฌ๊ณ ์ค์๊ฐ์ผ๋ก ์๋ฌ์จ์ ๋ณด๋ฉด์ "๋ด๊ฐ ์ํ๋ ํจํด์ผ๋ก ๋ก๊ทธ๊ฐ ๋์ค๊ณ ์๋" ํ์ธํ๊ณ ์ถ์๋ค.
๋ชจ๋ํฐ๋ง ์คํ์ ๋ถ์ด๊ธฐ๋ก ํ๋ฉด์ Loki์ Elasticsearch๋ฅผ ๋น๊ตํ๋ค.
| ํญ๋ชฉ | Loki | Elasticsearch |
|---|---|---|
| ๋ฉ๋ชจ๋ฆฌ | ~256MB | ~2GB+ |
| ๋ณธ๋ฌธ ์ธ๋ฑ์ฑ | ๋ ์ด๋ธ๋ง | ์ ์ฒด ๋จ์ด |
| ๋ผ์ด์ ์ค | AGPL | SSPL (2021~) |
๊ฐ๋ฐ ํ๊ฒฝ์์ Elasticsearch๋ฅผ ๋์ฐ๋ฉด ๋ค๋ฅธ ๊ฑธ ์คํํ ์ฌ์ ๊ฐ ์๋ค.
๊ทธ๋ฆฌ๊ณ ์ฐ๋ฆฌ ์ผ์ด์ค์์ ์ ๋ฌธ ์ธ๋ฑ์ฑ์ด ํ์ํ ๊ฒ ์๋์๋ค.
๋ ์ด๋ธ๋ก ๋ฒ์๋ฅผ ์ขํ ๋ค์ GUID๋ฅผ ๋ณธ๋ฌธ์์ grepํ๋ฉด ์ถฉ๋ถํ๋ค.
์ด๋ฌํ ์ด์ ๋ก Loki๋ฅผ ์ฌ์ฉํ๊ธฐ๋ก ๊ฒฐ์ ํ๋ค.
5. ์นด๋๋๋ฆฌํฐ - ๋ ์ด๋ธ ์ค๊ณ์์ ๊ฐ์ฅ ๊ณ ๋ฏผํ ๋ถ๋ถ
๋ ์ด๋ธ์ ์๋ชป ์ค๊ณํ๋ฉด ์คํธ๋ฆผ์ด ํญ๋ฐํ๊ณ ์คํ๋ ค ๋ ๋๋ ค์ง๋ค.
Loki๋ ๋ ์ด๋ธ ์กฐํฉ๋ง๋ค ๋ณ๋ ์คํธ๋ฆผ์ ๋ง๋ ๋ค. ๋ ์ด๋ธ ๊ฐ์ ์ข ๋ฅ๊ฐ ๋ง์์ง์๋ก ์คํธ๋ฆผ ์๊ฐ ๊ณฑ์ ์ผ๋ก ๋์ด๋๋ค. ์คํธ๋ฆผ์ด 10,000๊ฐ๋ฅผ ๋์ผ๋ฉด ์ธ๋ฑ์ค๊ฐ ๋น๋ํด์ ธ์ ๊ฒ์์ด ์คํ๋ ค ๋๋ ค์ง๋ค.
๊ฐ์ฅ ๊ณ ๋ฏผํ ๊ฒ GUID์๋ค. GUID๊ฐ ๋ ์ด๋ธ์ด๋ฉด ๊ฒ์์ด ์ ์ผ ๋น ๋ฅด์ง๋ง, ๊ฐ์ด ์๋ง ๊ฐ๋ค.
GUID๊ฐ ๋ ์ด๋ธ์ผ ๋:
source(4) × has_error(2) × error_type(4) × guid(์๋ง) = ์์ญ๋ง ์คํธ๋ฆผ โ
๊ฒฐ๊ตญ GUID๋ ๋ณธ๋ฌธ์ ๋จ๊ธฐ๊ณ ๋๋จธ์ง๋ง ๋ ์ด๋ธ๋ก ์ฐ๊ธฐ๋ก ํ๋ค.
source(4) × has_error(2) × error_type(4) × tx_code(20) = 640 ์คํธ๋ฆผ โ
GUID๋ ๋ ์ด๋ธ๋ก ์กฐํํ๋ ๋์ , ๋จผ์ source์ error_type์ผ๋ก ๋ฒ์๋ฅผ ์ขํ ๋ค์ ๋ณธ๋ฌธ์์ grep ํ๋ค.
์๋ ์ฐจ์ด๋ ํฌ์ง ์์๋ค.
6. ํธ๋ฌ๋ธ์ํ
1. Grafana ํ๊ธ ๊นจ์ง
Grafana ๋์๋ณด๋๋ฅผ ์ฝ๋๋ก ๋ง๋ค๊ธฐ ์ํด PowerShell ์คํฌ๋ฆฝํธ๋ก JSON ํ์ผ์ ์์ฑํ๋ค.
๊ทผ๋ฐ ํจ๋ ์ด๋ฆ "์๋ฌ ๊ฑด์", "์๋ต ์๊ฐ" ๊ฐ์ ํ๊ธ์ด ์ ๋ถ ???? ๊ฐ์ ๋ฌธ์๋ก ๊นจ์ ธ์ ๋์๋ค.
์์ธ์ BOM์ด์๋ค. BOM(Byte Order Mark)์ ํ์ผ ๋งจ ์์ ๋ชฐ๋ ๋ถ๋ 3๋ฐ์ดํธ์ง๋ฆฌ ๋ง์ปค์ธ๋ฐ, "์ด ํ์ผ์ UTF-8์ด์์"๋ผ๊ณ ์๋ ค์ฃผ๋ ์ญํ ์ ํ๋ค.
๊ทผ๋ฐ Grafana API๋ BOM์ ์ธ์ ๋ชป ํ๊ณ ํ์ผ ๋ด์ฉ์ด ๊นจ์ง ๊ฒ์ฒ๋ผ ์ฝ๋๋ค.
PowerShell์ ํ์ผ์ ์ ์ฅํ ๋ ์๋์ผ๋ก BOM์ ๋ถ์ด๋ ๊ฒ ๊ธฐ๋ณธ ๋์์ด๋ผ ์ด๊ฒ ๋ฌธ์ ๊ฐ ๋๋ค.
๋ฐ๋ผ์ Python์ผ๋ก ํ์ผ ์์ฑ ๋ฐฉ์์ ๋ฐ๊ฟจ๋ค.
with open("dashboard.json", "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False)
Python์ open(encoding="utf-8")์ BOM ์์ด ์ ์ฅํ๋ค.
ensure_ascii=False๋ ํ๊ธ์ ์๋ฌ์ฒ๋ผ ์ด์ค์ผ์ดํํ์ง ๋ง๊ณ ๊ทธ๋ฅ "์๋ฌ"๋ก ์ ์ฅํ๋ผ๋ ๋ป์ด๋ค.
์ด ๋ ๊ฐ์ง๊ฐ ํฉ์ณ์ง๋๊น Grafana๊ฐ ํ๊ธ์ ์ ๋๋ก ์ฝ๊ฒ ๋์๋ค.
2. Stat ํจ๋์ด ์ซ์ ํ๋ ๋์ ์ 15๊ฐ๋ก ์ชผ๊ฐ์ง๋ ๋ฒ๊ทธ
์๋ฌ ์ด ๊ฑด์๋ฅผ ํผ์งํ ์ซ์ ํ๋๋ก ๋ณด์ฌ์ฃผ๋ stat ํจ๋์ ๋ง๋ค์๋ค.
๊ทผ๋ฐ ์ซ์ ํ๋ ๋์ 15๊ฐ ์ ์ด ๊ฒน์ณ์ ๊ฝค๋ ๋ณด๊ธฐ ๋ถํธํ ๊ทธ๋ํ๊ฐ ๋์๋ค.
์์ธ์ Loki๊ฐ ๋ก๊ทธ ํ์ผ๋ง๋ค ๋ณ๋ ์๋ฆฌ์ฆ๋ฅผ ๋ง๋ค๊ธฐ ๋๋ฌธ์ด๋ค.
๋ก๊ทธ ํ์ผ์ด 15๊ฐ๋ฉด ์๋ฌ ๊ฑด์๋ ํ์ผ๋ณ๋ก 15๊ฐ ์ซ์๊ฐ ๋ฐ๋ก ๊ณ์ฐ๋๋ค.
Grafana๋ ์ด๊ฑธ ํฉ์ฐํ์ง ์๊ณ ๊ทธ๋ฅ 15๊ฐ ์ ์ผ๋ก ๋ค ๊ทธ๋ ค๋ฒ๋ฆฐ๋ค.
sum()์ผ๋ก ๊ฐ์ธ๋ฉด 15๊ฐ๋ฅผ ํ๋๋ก ๋ํด์ค๋ค.
# ์ : ํ์ผ๋ง๋ค ๋ฐ๋ก → 15๊ฐ ์
count_over_time({job="incident-autopsy"} |= "ERROR" [1m])
# ํ: ์ ๋ถ ํฉ์ฐ → ์ซ์ ํ๋
sum(count_over_time({job="incident-autopsy"} |= "ERROR" [1m]))
3. Dashboard UID ์ถฉ๋
Grafana๋ ๋์๋ณด๋๋ฅผ UID๋ก ์๋ณํ๋ค.
UID๊ฐ ๊ฐ์ผ๋ฉด "๊ฐ์ ๋์๋ณด๋๋ฅผ ์ ๋ฐ์ดํธํ๋ ๊ฒ"์ผ๋ก ๋ณด๊ณ , UID๊ฐ ๋ค๋ฅด๋ฉด "์ ๋์๋ณด๋๋ฅผ ๋ง๋๋ ๊ฒ"์ผ๋ก ๋ณธ๋ค.
๋ฒ์ ์
๊ทธ๋ ์ด๋๋ฅผ ํ๋ฉด์ UID๋ฅผ dashboard-v1์์ dashboard-v2๋ก ๋ฐ๊ฟจ๋ค. ๊ทธ๋ฌ๋๋ Grafana ๋ด๋ถ DB์๋ v1์ง๋ฆฌ ๋์๋ณด๋๊ฐ ๋จ์์๋๋ฐ, ํ์ผ์์๋ v2๊ฐ ๋ค์ด์ค๋๊น ์ถฉ๋์ด ๋ฌ๋ค. ํ๋ก๋น์ ๋ ์์ฒด๊ฐ ์คํจํ๋ฉด์ ๋์๋ณด๋๊ฐ ์์ ์ ๋จ๋ ์ํฉ์ด ๋๋ค.
UID๋ฅผ ๋ค์ v1์ผ๋ก ๋๋๋ฆฌ๋๊น Grafana๊ฐ ๊ธฐ์กด ๋ ์ฝ๋๋ฅผ ์
๋ฐ์ดํธํ๋ ๋ฐฉ์์ผ๋ก ์ฒ๋ฆฌํ๋ฉด์ ์ ์์ ์ผ๋ก ๋ฏ๋ค.
* ๊ตํ: UID๋ ํ ๋ฒ ์ ํ๋ฉด ํจ๋ถ๋ก ๋ฐ๊พธ๋ฉด ์ ๋๋ค.
4. Docker ์ปจํ ์ด๋์์ host ์๋น์ค ์ ๊ทผ ๋ถ๊ฐ
Prometheus๋ Docker ์ปจํ
์ด๋ ์์์ ๋๊ณ , ๋ฉํธ๋ฆญ์ ์์งํ๋ ค๋ log-autogenerator๋ host(๋ด ์ปดํจํฐ)์์ ์ง์ ๋๊ณ ์์๋ค.localhost:9000์ผ๋ก ์ ๊ทผ์ ์๋ํ๋๋ฐ ์ฐ๊ฒฐ์ด ์ ๋๋ค.
์์ธ์ Docker ๋คํธ์ํฌ ๊ฒฉ๋ฆฌ๋ค. ์ปจํ
์ด๋ ์์์ localhost๋ ๋ด ์ปดํจํฐ๊ฐ ์๋๋ผ ์ปจํ
์ด๋ ์๊ธฐ ์์ ์ ๊ฐ๋ฆฌํจ๋ค. ์ปจํ
์ด๋ ์
์ฅ์์ host(๋ด ์ปดํจํฐ)๋ ์ธ๋ถ ์๋ฒ์ ๋ง์ฐฌ๊ฐ์ง๋ผ ์ฃผ์๋ฅผ ๋ฐ๋ก ์์์ผ ํ๋ค.
extra_hosts:
- "host.docker.internal:host-gateway"
host-gateway๋ Docker๊ฐ ์๋์ผ๋ก host IP๋ฅผ ํ ๋นํด์ฃผ๋ ํน์ ํค์๋๋ค. ์ด ์ค์ ์ ์ถ๊ฐํ๋ฉด ์ปจํ
์ด๋ ์์์ host.docker.internal์ด๋ผ๋ ์ด๋ฆ์ผ๋ก host์ ์ ๊ทผํ ์ ์๋ค. Windows Docker Desktop๊ณผ EC2 Ubuntu ํ๊ฒฝ ๋ ๋ค ์ด ์ค์ ํ๋๋ก ํด๊ฒฐ๋๋ค.
7. ์ต์ข ๊ฒฐ๊ณผ
python generator.py --continuous --rate 2 --error-rate 0.05 --peak-pattern
์ด ์ธ์๋ํธ: 1,479๊ฑด
์๋ฌ ๊ฑด์: 17๊ฑด
์๋ฌ์จ: 1.22%
์์ค 4์ข
์์ง: jennifer-apm / framework-applog / log4j / modern-rest โ
ํผํฌ ๋ฐฐ์: 1.75 (์คํ ํผํฌํ์)
Grafana์์ ํผํฌ ๋ฐฐ์๊ฐ ์ฌ๋ผ๊ฐ์๋ก ์๋ฌ ๊ฑด์๋ ๊ฐ์ด ์ฌ๋ผ๊ฐ๋ ๊ฑธ ์ค์๊ฐ์ผ๋ก ํ์ธํ ์ ์์๋ค.
์ค์ ๊ธ์ต ์์คํ ์ ํผํฌํ์ ํจํด์ ํ์ตํ ๊ฒฐ๊ณผ, ๋น์ทํ๊ฒ ๋์ํ๋ค.

8. ์ค๋ฌด์ ๋ถ์ด๋ ค๋ฉด
ํ์ฌ ๊ตฌ์กฐ๋ ์๋ฎฌ๋ ์ดํฐ ๊ธฐ์ค์ผ๋ก tx_code๊ฐ 20๊ฐ๋ค.
์ค์ ๊ธ์ต ์์คํ ์ ๊ฑฐ๋์ฝ๋๋ ์์ฒ ๊ฐ ์์ค์ด๋ผ ๊ทธ๋๋ก ๋ ์ด๋ธ๋ก ์ฐ๋ฉด ์คํธ๋ฆผ์ด ํญ๋ฐํ๋ค.
์ฑ๋(10) × ์
๋ฌด(20) × ํธ๋์ญ์
(~500) = ์์ฒ ์ข
→ source(4) × has_error(2) × error_type(4) × tx_code(3,000) = 96,000 ์คํธ๋ฆผ โ
์ค๋ฌด ํ๊ฒฝ์ด๋ผ๋ฉด tx_code๋ฅผ ์ ๋ฌด ๋๋ถ๋ฅ(๊ณ์ข, ๊ฒฐ์ , ๋์ถ ๋ฑ ~10์ข )๋ก ๋ฌถ๊ฑฐ๋,
PostgreSQL์ ๋ณ๋ ์ธ๋ฑ์ค DB๋ฅผ ๋ณํํด์ GUID๋ฅผ ๋น ๋ฅด๊ฒ ์ฐพ๋ ๊ตฌ์กฐ๋ก ๊ฐ์ผ ํ๋ค.
ํ ์คํธ ๋ฐ์ดํฐ๊ฐ ํ์ค์ ์ด์ด์ผ AI ๋ถ์๋ ํ์ค์ ์ด๋ค.
๋จ์ํ ๋๋ฏธ ๋ฐ์ดํฐ๋ก ํ์ตํ AI๋ ์ค์ ๋ก๊ทธ ์์์ ๋ฌด๋์ง๋ค.
End-Gamers | incident-autopsy | log-autogenerator



