티스토리 뷰
Helm으로 Loki + Grafana + Alloy(=Grafana Agent)
1) 개요(한줄)
Helm으로 loki-stack(storage), grafana, grafana-agent(DaemonSet) 설치 → Grafana에 Loki/Prometheus datasource 프로비저닝 → 대시보드(네임스페이스 기반, 로그+메트릭 연계) 적용 → 알람 룰 등록.
2) Helm 리포지터리 추가 & 공통 명령
helm repo add grafana https://grafana.github.io/helm-charts
helm repo update
3) Loki (loki-stack) 설치 권장 values (loki-values.yaml)
주요 포인트: persistence, compactor, 라벨 인덱싱(네임스페이스/ pod / container), retention 설정.
# loki-values.yaml
loki:
persistence:
enabled: true
size: 50Gi
config:
table_manager:
retention_deletes_enabled: true
retention_period: 168h # 7 days, 필요시 늘려라
ingester:
wal:
enabled: true
schema_config:
configs:
- from: 2020-10-24
store: boltdb-shipper
object_store: filesystem
schema: v11
index:
prefix: index_
period: 24h
# 필요시 클러스터가 크면 boltdb-shipper + s3 등 외부 오브젝트스토어 권장
설치:
helm install loki grafana/loki-stack -f loki-values.yaml --namespace monitoring --create-namespace
4) Grafana 설치 권장 values (grafana-values.yaml)
포인트: admin 비밀번호, persistence, datasource & dashboard provisioning(사이드카) 설정.
# grafana-values.yaml
persistence:
enabled: true
size: 10Gi
adminPassword: "StrongAdminPwd123!"
datasources:
datasources.yaml:
apiVersion: 1
datasources:
- name: Loki
type: loki
url: http://loki.monitoring.svc.cluster.local:3100
access: proxy
isDefault: false
- name: Prometheus
type: prometheus
url: http://prometheus.monitoring.svc.cluster.local:9090
access: proxy
plugins: []
sidecar:
dashboards:
enabled: true
label: grafana_dashboard
datasources:
enabled: true
label: grafana_datasource
설치:
helm install grafana grafana/grafana -f grafana-values.yaml --namespace monitoring
참고: Dashboard 파일은 ConfigMap으로 올리고 grafana_dashboard 라벨을 달면 Grafana가 자동으로 로드함. (내가 이미 만든 대시보드 JSON은 Canvas에 있으니, 그걸 가져와 ConfigMap으로 올려도 됨)
5) Grafana Agent (Alloy 역할) DaemonSet 설치값 (agent-values.yaml)
목표: 모든 노드에서 로그(=journal, /var/log/*) + 메트릭(노드 메트릭) 수집해 Loki/Prometheus로 전송.
# agent-values.yaml (간단 형태)
agent:
enabled: true
config:
server:
log_level: info
metrics:
global:
scrape_interval: 15s
receivers:
- prometheus
processors: []
exporters:
- prometheus_remote_write:
url: http://prometheus.remote-write.endpoint:9201 # optional
logs:
loki:
- url: http://loki.monitoring.svc.cluster.local:3100/loki/api/v1/push
sources:
journal:
forward_to: [loki]
files:
- paths: ["/var/log/syslog", "/var/log/messages", "/var/log/*.log"]
labels:
job: syslog
daemonset:
enabled: true
hostNetwork: false
volumes:
- name: varlog
hostPath:
path: /var/log
- name: journal
hostPath:
path: /var/log/journal
설치(예: grafana/agent chart 사용):
helm install grafana-agent grafana/agent -f agent-values.yaml --namespace monitoring
운영환경에서는 Agent에 RBAC, serviceAccount, 최소권한(읽기 전용)만 부여하고, 로그 읽기 성능을 고려해 batching/bootsize 설정을 조정하자.
6) Grafana에 대시보드/데이터소스 자동 프로비저닝 (권장)
- Datasource는 Helm values로 이미 등록했음.
- Dashboard는 ConfigMap으로 JSON 올리고 grafana_dashboard: "1" 라벨 달아두면 Grafana sidecar가 자동으로 임포트.
- 예: cm-grafana-dashboard-namespace.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: grafana-dashboard-namespace
namespace: monitoring
labels:
grafana_dashboard: "1"
data:
k8s-namespace-monitoring.json: |-
<여기에 dashboard JSON 내용>
(주의: 내 만든 JSON은 이미 Canvas에 있으니 그것을 복사해서 넣어라.)
7) 모범사례(벤치마크) — 최적 스택 & 설정 요약
- Retention: 로그는 기본 7일, 중요 시스템은 30~90일 (비용 고려)
- Index 라벨 관리: Loki에 인덱스할 라벨 최소화(예: namespace, pod, container, app) — 너무 많은 라벨은 비용 폭주
- Grafana provisioning: datasources + dashboards 자동 프로비저닝 (ConfigMap + sidecar)
- 알림(Alerts): Prometheus Alertmanager로 CPU/Memory/PVC(>85%)/PodReady결핍/CrashLoopBackOff 등 알림 구현
- 보안: Grafana + Loki 통신은 내부 네트워크에서만, 외부 접근은 OAuth/LDAP/Reverse proxy로 제어
- 모니터링 범위: 메트릭(Prometheus) + 로그(Loki) + 트레이스(옵션) 연계 — 문제파악 속도 3배 향상
- Resource Limits: Loki ingester/querier와 Grafana에 적절한 CPU/RAM 요청·제한 지정
- PVC Size & IOPS: Loki 인덱스/객체스토어 수준에 따라 IOPS 높은 스토리지 사용 권장
8) 최선의 대시보드(권장 셋) — 벤치마킹 요소
내가 네임스페이스 모니터링 기반으로 최적화할 때 넣는 것들(짧게):
- Namespace Overview: CPU%, Memory%, PVC% (Gauge) + Top-5 파드 (Bar)
- Namespace Trends: CPU/Memory 시계열(1h/24h/7d)
- Pod Health: Ready/Running/Pending/CrashLoop(테이블 + 색상)
- PVC Details: 사용량, inodes, growth-rate, 알림 임계치
- Logs Quick-Find: Loki query 템플릿(예: {namespace="$namespace"} |= "error" | json)
- Replica Health: Deployment spec vs ready (Alert 적용)
- Node / System Logs: Node별 journal 에러 카운트(주간)
- OS Inventory: (Exporter 필요) Pod별 OS 라벨 테이블 — 드릴다운 가능
- SLA Dashboard: 중요 서비스 실패율, Job 실패율, 응답시간(가능하면 APM 연동)
각 패널에 Threshold + 색상(녹/노/적) 적용. Alert rule은 Prometheus Alertmanager로 분리.
9) Alerts(예시) — PrometheusRule snippets
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: k8s-alerts
namespace: monitoring
spec:
groups:
- name: k8s.rules
rules:
- alert: HighNamespaceCPU
expr: (sum(rate(container_cpu_usage_seconds_total{namespace=~".+"}[5m])) by (namespace)) / (sum(kube_pod_container_resource_limits_cpu_cores{namespace=~".+"}) by (namespace)) * 100 > 90
for: 5m
labels:
severity: critical
annotations:
summary: "CPU > 90% in namespace {{ $labels.namespace }}"
10) 배포 체크리스트 (롤아웃 전)
- PVC (Loki/Grafana) 프로비저닝 완료
- RBAC/ServiceAccount 권한 최소화 설정
- Grafana admin 비밀 안전하게 저장(secrets)
- 로그 라벨 설계(어떤 라벨을 인덱싱할지 결정)
- 대시보드 프로비저닝(파일 준비)
- 알림 채널(Slack/Email) 구성
- helm install (loki-values.yaml, grafana-values.yaml, agent-values.yaml, dashboard ConfigMaps, PrometheusRule)
- Grafana (네임스페이스 전체 대시보드, Pod OS 리스트, Replica Health, PVC Monitoring)
- 배포 후 테스트 쿼리 & 알람 시뮬레이션 스크립트
# Loki + Grafana + Alloy (Grafana Agent)
아래 파일들을 그대로 저장 후 Helm/`kubectl apply -f`
간단 설명 + 설치/테스트 커맨드까지 포함.
---
## 1) loki-values.yaml
```yaml
loki:
persistence:
enabled: true
size: 50Gi
config:
table_manager:
retention_deletes_enabled: true
retention_period: 168h
ingester:
wal:
enabled: true
schema_config:
configs:
- from: 2020-10-24
store: boltdb-shipper
object_store: filesystem
schema: v11
index:
prefix: index_
period: 24h
ingester:
lifecycler:
ring:
kvstore:
store: inmemory
# 필요시 외부 오브젝트스토어(S3/GCS)로 변경 권장
```
---
## 2) grafana-values.yaml
```yaml
persistence:
enabled: true
size: 10Gi
adminPassword: "StrongAdminPwd123!"
datasources:
datasources.yaml:
apiVersion: 1
datasources:
- name: Loki
type: loki
url: http://loki.monitoring.svc.cluster.local:3100
access: proxy
isDefault: false
- name: Prometheus
type: prometheus
url: http://prometheus.monitoring.svc.cluster.local:9090
access: proxy
isDefault: true
sidecar:
dashboards:
enabled: true
label: grafana_dashboard
datasources:
enabled: true
label: grafana_datasource
```
---
## 3) grafana-agent-values (agent-values.yaml)
```yaml
daemonset:
enabled: true
hostNetwork: false
tolerations: []
config:
server:
log_level: info
logs:
scrape_configs:
- job_name: system_journal
journal:
path: /var/log/journal
relabel_configs: []
labels:
job: systemd-journal
- job_name: syslog
static_configs:
- targets: [localhost]
labels:
job: syslog
__path__: /var/log/syslog
clients:
- url: http://loki.monitoring.svc.cluster.local:3100/loki/api/v1/push
metrics:
scrape_configs:
- job_name: node_exporter
static_configs:
- targets: ['localhost:9100']
# hostPath volumes handled in DaemonSet manifest by chart
```
---
## 4) Helm 설치 명령 (순서)
```bash
helm repo add grafana https://grafana.github.io/helm-charts
helm repo update
# 1) Loki
helm install loki grafana/loki-stack -f loki-values.yaml --namespace monitoring --create-namespace
# 2) Grafana
helm install grafana grafana/grafana -f grafana-values.yaml --namespace monitoring
# 3) Grafana Agent (Alloy 역할)
helm install grafana-agent grafana/agent -f agent-values.yaml --namespace monitoring
```
---
## 5) Dashboard ConfigMap 예제 (자동 프로비저닝용)
- 아래 ConfigMap 3개를 만들어 Grafana sidecar가 로드하게 함.
- 파일명 예: cm-dashboard-namespace.yaml, cm-dashboard-pod-os.yaml, cm-dashboard-replica-pvc.yaml
### a) cm-dashboard-namespace.yaml (Namespace 종합 대시보드)
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: grafana-dashboard-namespace
namespace: monitoring
labels:
grafana_dashboard: "1"
data:
k8s-namespace-monitoring.json: |-
{
"id": null,
"title": "K8s Namespace Monitoring - Best Practice",
"schemaVersion": 39,
"version": 1,
"timezone": "browser",
"templating": { "list": [ { "name": "namespace", "type": "query", "query": "label_values(kube_pod_info, namespace)", "datasource": "Prometheus", "multi": true, "includeAll": true } ] },
"panels": [
{ "type": "row", "title": "Overview", "collapsed": false },
{ "title": "Namespace CPU %", "type": "gauge", "targets": [{ "expr": "(sum(rate(container_cpu_usage_seconds_total{namespace=\"$namespace\",container!~\"POD|\"}[5m])) by (namespace)) / (sum(kube_pod_container_resource_limits_cpu_cores{namespace=\"$namespace\"}) by (namespace)) * 100", "refId": "A" }], "fieldConfig": { "defaults": { "thresholds": { "mode": "absolute", "steps": [{"color":"green","value":0},{"color":"orange","value":70},{"color":"red","value":80}] } } } },
{ "title": "Namespace Memory %", "type": "gauge", "targets": [{ "expr": "(sum(container_memory_usage_bytes{namespace=\"$namespace\",container!~\"POD|\"}) by (namespace)) / (sum(kube_pod_container_resource_limits_memory_bytes{namespace=\"$namespace\"}) by (namespace)) * 100", "refId": "B" }], "fieldConfig": { "defaults": { "thresholds": { "mode": "absolute", "steps": [{"color":"green","value":0},{"color":"orange","value":70},{"color":"red","value":80}] } } } },
{ "title": "Namespace PVC %", "type": "gauge", "targets": [{ "expr": "(sum(kubelet_volume_stats_used_bytes{namespace=\"$namespace\"}) by (namespace)) / (sum(kubelet_volume_stats_capacity_bytes{namespace=\"$namespace\"}) by (namespace)) * 100", "refId": "C" }], "fieldConfig": { "defaults": { "thresholds": { "mode": "absolute", "steps": [{"color":"green","value":0},{"color":"orange","value":70},{"color":"red","value":85}] } } } },
{ "title": "Top 5 Pods CPU in Namespace", "type": "bargauge", "targets": [{ "expr": "topk(5, sum(rate(container_cpu_usage_seconds_total{namespace=\"$namespace\",container!~\"POD|\"}[5m])) by (pod))", "refId": "D" }] }
]
}
```
### b) cm-dashboard-pod-os.yaml (Pod별 OS 리스트)
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: grafana-dashboard-pod-os
namespace: monitoring
labels:
grafana_dashboard: "1"
data:
k8s-pod-os.json: |-
{
"id": null,
"title": "Pod OS Inventory",
"schemaVersion": 38,
"version": 1,
"panels": [
{
"type": "table",
"title": "Pod → OS",
"gridPos": {"x": 0, "y": 0, "w": 24, "h": 12},
"targets": [{ "expr": "k8s_container_os_issue_info", "format": "table", "instant": true }],
"transformations": [{ "id": "labelsToFields", "options": {} }],
"options": { "showHeader": true }
}
]
}
```
### c) cm-dashboard-replica-pvc.yaml (Replica / PVC 건강 대시보드)
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: grafana-dashboard-replica-pvc
namespace: monitoring
labels:
grafana_dashboard: "1"
data:
k8s-replica-pvc.json: |-
{
"id": null,
"title": "Replica & PVC Health",
"schemaVersion": 39,
"version": 1,
"panels": [
{ "title": "Deployment Spec vs Ready", "type": "timeseries", "targets": [ { "expr": "sum(kube_deployment_spec_replicas{namespace=~\"$namespace\"}) by (deployment,namespace)", "refId": "A"}, { "expr": "sum(kube_deployment_status_replicas_ready{namespace=~\"$namespace\"}) by (deployment,namespace)", "refId": "B" } ] },
{ "title": "PVC Usage List", "type": "table", "targets": [ { "expr": "kubelet_volume_stats_used_bytes", "format": "table", "instant": true } ], "transformations": [{ "id": "labelsToFields", "options": {} }] }
]
}
```
---
## 6) Prometheus Alert Rule 예시 (PrometheusRule CRD)
```yaml
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: k8s-health-rules
namespace: monitoring
spec:
groups:
- name: k8s.rules
rules:
- alert: NamespaceHighCPU
expr: |
(sum(rate(container_cpu_usage_seconds_total{container!~"POD|"}[5m])) by (namespace))
/
(sum(kube_pod_container_resource_limits_cpu_cores) by (namespace)) * 100 > 90
for: 5m
labels:
severity: critical
annotations:
summary: "CPU usage >90% in namespace {{ $labels.namespace }}"
- alert: PVCUsageHigh
expr: (sum(kubelet_volume_stats_used_bytes) by (namespace)) / (sum(kubelet_volume_stats_capacity_bytes) by (namespace)) * 100 > 85
for: 10m
labels:
severity: warning
annotations:
summary: "PVC usage >85% in namespace {{ $labels.namespace }}"
```
---
## 7) 배포/적용 명령 모음
```bash
# 1) values 파일로 Helm 설치
helm repo add grafana https://grafana.github.io/helm-charts
helm repo update
helm install loki grafana/loki-stack -f loki-values.yaml --namespace monitoring --create-namespace
helm install grafana grafana/grafana -f grafana-values.yaml --namespace monitoring
helm install grafana-agent grafana/agent -f agent-values.yaml --namespace monitoring
# 2) Dashboard ConfigMap 적용 (sidecar가 자동 임포트)
kubectl apply -f cm-dashboard-namespace.yaml
kubectl apply -f cm-dashboard-pod-os.yaml
kubectl apply -f cm-dashboard-replica-pvc.yaml
# 3) Alert Rule 적용 (PrometheusOperator 필요)
kubectl apply -f prometheusrule.yaml
```
---
## 8) 테스트 & 검증 스크립트 (C)
### a) Loki 수집 확인
```bash
# Grafana Agent가 로그를 보냈는지 확인
kubectl logs -n monitoring -l app.kubernetes.io/name=grafana-agent --tail 200
# Loki가 수신했는지 확인 (샘플 쿼리)
kubectl exec -n monitoring svc/loki -c loki -- curl -s "http://localhost:3100/loki/api/v1/query?query={job=\"systemd-journal\"}" | jq .
```
### b) Prometheus 메트릭 수집 확인
```bash
# prometheus가 있다면
kubectl port-forward -n monitoring svc/prometheus 9090:9090 &
# 브라우저에서 http://localhost:9090 에 접속 후 PromQL 입력
# 예: sum(rate(container_cpu_usage_seconds_total[5m])) by (namespace)
```
### c) Grafana 대시보드 확인
- Grafana 서비스 접속 (port-forward 또는 LoadBalancer)
```bash
kubectl port-forward -n monitoring svc/grafana 3000:80 &
# 브라우저에서 http://localhost:3000 (admin / StrongAdminPwd123!)
```
- 좌측 메뉴 → Dashboards → Manage 에서 자동로드된 대시보드 확인
### d) 알람 시뮬레이션
- CPU alert 테스트: 임의로 부하 생성
```bash
kubectl run -n default loadgen --image=busybox --restart=Never -- sh -c "while true; do dd if=/dev/zero of=/dev/null bs=1M count=1024; sleep 0.1; done"
```
- Prometheus에 메트릭 반영 후 Alertmanager에서 경보 확인
---
## 9) 모범사례 팁 (짧게)
- Loki 라벨은 최소화: namespace,pod,container,app
- Logs retention은 비용과 규정 고려해서 세팅
- Grafana dashboard는 provisioning으로 배포(버전관리)
- Alert은 for 조건(예: 5m 이상)으로 일시적 스파이크 방지
- Agent(=Alloy)의 리소스 limit 설정 필수
---
