티스토리 뷰
구성 목표 요약
- K8s 클러스터에 Splunk(Logging 시스템) 설치
- Fluent Bit로 애플리케이션/노드 로그 수집
- Splunk HEC(HTTP Event Collector)로 전송
- Splunk에서 검색 및 대시보드 구성
1. 사전 준비 사항
- Kubernetes 클러스터 준비 (kind, minikube, 실제 클러스터 등)
- Splunk Docker Image
- Splunk용 PVC 또는 임시볼륨
- Fluent Bit 설치
2. Splunk 설치 (K8s에 배포)
2-1. Splunk Deployment YAML (예: splunk-deploy.yaml)
apiVersion: apps/v1
kind: Deployment
metadata:
name: splunk
labels:
app: splunk
spec:
replicas: 1
selector:
matchLabels:
app: splunk
template:
metadata:
labels:
app: splunk
spec:
containers:
- name: splunk
image: splunk/splunk:latest
env:
- name: SPLUNK_START_ARGS
value: "--accept-license"
- name: SPLUNK_PASSWORD
value: changeme123!
ports:
- containerPort: 8000 # UI
- containerPort: 8088 # HEC
---
apiVersion: v1
kind: Service
metadata:
name: splunk
spec:
selector:
app: splunk
type: NodePort
ports:
- port: 8000
targetPort: 8000
nodePort: 30080
- port: 8088
targetPort: 8088
nodePort: 30088
kubectl apply -f splunk-deploy.yaml
3. Splunk HEC Token 발급
- 웹 UI 접속: http://:30080
- 설정(Settings) → Data Inputs → HTTP Event Collector
- 새 토큰 생성 (이름: k8s-logs, Token: ABC123456TOKEN...)
4. Fluent Bit 설치 및 Splunk 연동
4-1. Helm 설치 (Fluent Bit 공식 차트)
helm repo add fluent https://fluent.github.io/helm-charts
helm repo update
4-2. values.yaml 구성 (예시)
backend:
type: splunk
splunk:
host: http://splunk.default.svc.cluster.local:8088
token: ABC123456TOKEN...
insecureSSL: true
source: k8s
sourcetype: _json
index: main
config:
service: |
[SERVICE]
Flush 5
Daemon Off
Log_Level info
inputs: |
[INPUT]
Name tail
Path /var/log/containers/*.log
Parser docker
Tag kube.*
Refresh_Interval 5
outputs: |
[OUTPUT]
Name splunk
Match *
Host splunk.default.svc.cluster.local
Port 8088
TLS off
Splunk_Token ABC123456TOKEN...
Splunk_Send_Raw Off
Retry_Limit False
4-3. Fluent Bit 설치
helm install fluent-bit fluent/fluent-bit -f values.yaml
5. Splunk 검색 예제
Splunk UI → Search
index=main sourcetype=_json source=k8s
- pod_name, container_name, log, namespace 등의 필드로 필터 가능
- 알림(Alert), 대시보드 구성도 가능
실무 팁
항목 내용
| Token 갱신 주기 | 보안 정책에 따라 주기적으로 갱신하여 Secret로 K8s에 저장 |
| 고가용성 구성 | StatefulSet + PVC + Liveness/Readiness 설정 권장 |
| 로그 필터링 | Fluent Bit에서 로그 수준 필터링, 특정 namespace/pod만 수집 가능 |
| Helm 자동화 배포 | GitOps (ArgoCD, Flux 등)와 연계하여 Helm chart 버전 관리 |
| 대시보드 확장 | Splunk Dashboard Studio 사용 → 실시간 K8s 로그/이벤트/성능 모니터링 구성 가능 |
예제 디렉토리 구조
splunk-on-k8s/
├── splunk-deploy.yaml
├── fluent-bit/
│ └── values.yaml
└── README.md
마무리 요약
- Splunk를 K8s에 직접 배포하거나 Helm 차트 사용 가능
- Fluent Bit는 로그 수집의 표준이며 Splunk와 안정적으로 연동 가능
- HEC 토큰 보안관리 중요, ConfigMap/Secret으로 관리
- 실시간 로그 검색 + 대시보드 구성으로 운영 효율성 ↑
Splunk 사용법 모범 사례 (Best Practices)
Splunk 쿼리는 어떻게 작성하느냐에 따라 검색 속도, 리소스 사용량, 그리고 가독성에서 큰 차이를 보인다. 아래의 원칙들을 따르면 더 빠르고, 효율적이며, 유지보수가 쉬운 SPL 작성.
1. 데이터 범위를 최대한 좁힌다 (Filter Early and Often)
가장 중요한 원칙. 검색을 시작할 때 최대한 많은 데이터를 필터링하여 처리할 데이터의 양을 줄여야 한다.
- 메타데이터 필드 활용: index, sourcetype, source, host 와 같은 메타데이터 필드를 검색어 가장 앞에 명시. 이 필드들은 Splunk에 의해 인덱싱(색인)되어 있어 매우 빠른 필터링 가능.
나쁜 예시 (Bad Practice):
index=*
| search sourcetype="access_combined" host="webserver01" status=404
이유: index=*는 모든 인덱스를 스캔한 후 search 명령어로 필터링하므로 매우 비효율적이다.
좋은 예시 (Good Practice):
index="web" sourcetype="access_combined" host="webserver01" status=404
이유: 검색 시작과 동시에 web 인덱스의 access_combined 소스 타입 데이터만 대상으로 삼아 검색 범위를 획기적으로 줄인다.
2. 효율적인 명령어 사용 (Use Efficient Commands)
- search vs where: where는 search로 필터링할 수 없는 경우(예: stats로 계산된 필드)에만 사용. search는 인덱싱된 데이터를 활용하므로 훨씬 빠르다.
나쁜 예시:
index="web" sourcetype="access_combined"
| where status >= 400
좋은 예시:
index="web" sourcetype="access_combined" status>=400
- tstats 활용: tstats 명령어는 일반 search와 달리 인덱싱된 메타데이터 요약 정보(.tsidx 파일) 사용. 데이터 모델이나 Accelerated sourcetype을 대상으로 집계 연산을 할 때 엄청나게 빠르다.
나쁜 예시 (수십억 건의 로그를 모두 스캔):
index="firewall"
| stats count by src_ip
좋은 예시 (데이터 모델 사용 시):
| tstats count from datamodel=Network_Traffic where nodename=All_Traffic by All_Traffic.src_ip
| rename All_Traffic.src_ip as src_ip
이유: tstats는 원본 로그(raw data)가 아닌 요약 정보를 참조하므로 집계 속도가 월등히 빠르다.
3. 필드 추출 최적화 (Optimize Field Extraction)
- 내장 필드 활용: Splunk가 자동으로 추출해주는 필드(status, user 등)를 최대한 활용.
- rex는 필요할 때만: 정규표현식(rex)은 강력하지만 비용이 큰 연산. 꼭 필요할 때, 그리고 최대한 좁혀진 데이터 범위에만 사용.
- 구체적인 정규표현식: (.*)처럼 광범위한 패턴 대신 (\w+), ([^\s]+) 와 같이 더 구체적인 패턴을 사용하면 성능 향상.
나쁜 예시 (불필요하게 넓은 범위):
index="app" "login failed"
| rex field=_raw "user=(?<username>.*) from"
좋은 예시 (더 구체적인 패턴):
index="app" "login failed"
| rex field=_raw "user=(?<username>\w+) from"
4. 검색 결과 가공은 마지막에 (Process Results Late)
- fields vs table: 검색 파이프라인 중간에 필드를 선택해야 한다면 table 대신 fields 사용. table은 결과를 표 형태로 완전히 변환하지만, fields는 필요한 필드만 남기고 나머지 데이터는 유지하여 후속 연산 가능.
- 정렬은 마지막에: sort는 많은 메모리를 사용하는 명령어. 꼭 필요한 경우에, 그리고 가급적 검색 파이프라인의 마지막 단계에서 사용.
나쁜 예시 (중간에 table을 사용하여 데이터 유실):
index="sales" action=purchase
| table user, product_id, price
| stats count by user
이유: table 명령어 다음에는 user, product_id, price 필드만 남으므로 stats count by user가 제대로 동작하지 않거나 원하는 결과를 얻을 수 없다.
좋은 예시:
index="sales" action=purchase
| stats count by user
| table user, count
5. Subsearch 사용 시 주의사항
Subsearch(하위검색)는 유용하지만, 기본적으로 10,000건의 결과와 60초의 시간 제한을 가진다. 대용량 데이터를 처리할 때는 성능 저하의 주된 원인이 될 수 있다. join, lookup, append 또는 stats를 활용한 재구성으로 대체 고려.
나쁜 예시 (Subsearch):
index=main sourcetype=access_log
[search index=main sourcetype=auth_log "login failure" | dedup user | fields user]
좋은 예시 (Join 사용):
index=main sourcetype=access_log
| join user [search index=main sourcetype=auth_log "login failure" | dedup user | fields user]
더 좋은 예시 (stats 사용 - 종종 가장 빠름):
index=main (sourcetype=access_log OR sourcetype=auth_log)
| stats values(sourcetype) as sourcetypes by user
| where mvcount(sourcetypes)=2 AND "auth_log" IN sourcetypes
이유: 단일 검색으로 두 소스 타입의 데이터를 모두 가져온 후 stats로 사용자를 그룹화하는 것이 여러 검색을 실행하는 것보다 효율적.
6. 가독성 높은 SPL 작성 (Write Readable SPL)
- 파이프(|) 기준으로 줄 바꿈: 쿼리가 길어지면 각 파이프 앞에서 줄을 바꿔 가독성 높인다.
- 주석 활용: 복잡한 로직에는 주석을 추가하여 다른 사람(그리고 미래의 나)이 쉽게 이해할 수 있도록 만든다.
- 필드명 변경(rename / as): as 키워드를 사용해 stats 결과의 필드명을 직관적으로 바꾸면 쿼리의 목적을 이해하기 쉬워진다.
나쁜 예시:
index=web status=200 action=purchase | stats count as c, dc(user_id) as u by product_id | sort -c | head 10
좋은 예시:
index=web status=200 action=purchase
`comment("웹서버에서 성공(200)한 구매(purchase) 로그만 필터링")`
| stats count as "구매 횟수", dc(user_id) as "순수 구매자 수" by product_id
`comment("제품 ID별로 구매 횟수와 순수 구매자 수를 집계")`
| sort -"구매 횟수"
| head 10
종합 예시: 나쁜 쿼리를 좋은 쿼리로 개선하기
요구사항: 지난 24시간 동안 웹 서버에서 404 에러를 발생시킨 사용자가 구매한 상품 목록 찾기
나쁜 쿼리 (Bad Query):
index=* sourcetype=access_combined
| search [search index=* sourcetype=access_combined status=404 | dedup clientip | fields clientip]
| search status=200
| rex "purchase\.php\?product=(?<product_id>\d+)"
| stats count by product_id
- index=* 사용
- 느린 Subsearch 사용
- 불필요한 search 명령어 반복
- 비효율적인 필터링 순서
좋은 쿼리 (Good Query):
`comment("1단계: 웹 로그에서 404 에러와 구매 기록이 있는 IP를 효율적으로 찾는다")`
index="web" sourcetype="access_combined" (status=404 OR status=200)
| stats values(status) as statuses by clientip
| where mvcount(statuses)=2 AND "404" IN statuses
`comment("2단계: 위에서 찾은 IP 목록을 사용하여 해당 IP의 구매 기록만 필터링한다")`
| join clientip [
search index="web" sourcetype="access_combined" status=200 action=purchase
]
`comment("3단계: 구매한 상품 ID별로 판매량을 집계한다")`
| stats count by product_id
| sort -count
| rename count as "판매량", product_id as "상품ID"
이 쿼리는 join 대신 stats를 활용하여 더 최적화할 수도 있지만, 단계별로 로직을 명확히 보여주기 위해 join을 사용했다. 이처럼 먼저 필터링하고, 효율적인 명령어를 사용하며, 가독성을 고려하는 것만으로도 Splunk 쿼리의 품질을 크게 향상시킬 수 있다.
# Splunk 사용법 모범 사례 및 코드 예제
1. 검색 효율화
모범 사례: 검색 시간 범위 제한
나쁜 예: 시간 범위 없이 전체 데이터 검색
* | stats count by sourcetype
좋은 예: 적절한 시간 범위 지정
index=main earliest=-24h@h latest=now | stats count by sourcetype
모범 사례: 불필요한 필드 제거
나쁜 예: 모든 필드 유지
index=main | table *
# 좋은 예: 필요한 필드만 선택
index=main | table _time, host, sourcetype, message
2. 인덱싱 최적화
모범 사례: props.conf 설정 예제
[mysourcetype]
SHOULD_LINEMERGE = false
LINE_BREAKER = ([\r\n]+)
TIME_FORMAT = %Y-%m-%d %H:%M:%S
MAX_TIMESTAMP_LOOKAHEAD = 25
3. 필드 추출
모범 사례: 정규식 필드 추출
# 인라인 필드 추출
index=main | rex field=_raw "user=(?<user>\w+), action=(?<action>\w+)"
# 정규식으로 필드 추출 후 통계
index=web_logs | rex "GET (?<url>\S+) HTTP" | stats count by url
4. 서브검색 최적화
모범 사례: 서브검색 대신 join 사용
나쁜 예: 큰 데이터셋에 서브검색 사용
[search index=main error | table host] | search index=performance host=$host$
좋은 예: join 사용
index=main error | table host | join type=inner host [search index=performance | stats avg(cpu) by host]
5. 스케줄링 및 경고
모범 사례: 경고 설정 예제
index=security failed login | stats count by user | where count > 5
이 검색을 저장하고 경고로 설정:
- 트리거 조건: 결과 수 > 0
- 실행 시간: 매 15분
- 작업: 이메일 알림 전송
6. 대시보드 최적화
모범 사례: XML 대시보드 예제
<dashboard>
<label>시스템 모니터링</label>
<row>
<panel>
<title>CPU 사용률 Top 5</title>
<chart>
<search>
<query>index=perfmon metric=cpu | stats avg(value) as avg_cpu by host | sort - avg_cpu | head 5</query>
<earliest>-1h</earliest>
<latest>now</latest>
</search>
<option name="charting.chart">bar</option>
</chart>
</panel>
</row>
</dashboard>
7. 로그 포맷팅
모범 사례: 구조화된 로깅
# 애플리케이션에서 구조화된 로그 생성 예제
import logging
import json
log_data = {
"timestamp": "2023-01-01T12:00:00Z",
"level": "ERROR",
"service": "payment",
"user_id": "12345",
"transaction_id": "txn-67890",
"message": "Payment processing failed",
"error_code": "PAY-402"
}
# JSON 형식으로 로깅
logging.info(json.dumps(log_data))
8. 데이터 샘플링
모범 사례: 대량 데이터 처리 시 샘플링
# 전체 데이터 대신 10% 샘플 사용
index=bigdata | head 10000 | stats count by type
# 또는
index=bigdata | sample ratio=0.1 | stats count by type
9. lookup 테이블 활용
모범 사례: lookup 테이블 생성 및 사용
# lookup 테이블 생성
| inputlookup mylookup.csv | table user_id, department
# lookup 테이블 조인
index=main | lookup mylookup.csv user_id as user OUTPUT department
10. 매크로 활용
모범 사례: 재사용 가능한 매크로 정의
```splunk
# 매크로 정의 (restmacro)
[error_count(1)]
search index=$index$ error | stats count as error_count
# 매크로 사용
`error_count(main)`
---
