위 관측 가능성 4가지 소프트웨어를 사용한다
ref: https://tindevops.medium.com/how-to-debug-an-issue-by-grafana-lab-mimir-loki-and-tempo-dd4a7edc532c
eks 를 이용하여 쿠버네티스 환경을 띄우고 helm 을 설치하여 관측 가능성 툴을 설치할 준비를 한다. (eks 가 없다면 minikube 사용해도 된다)
ref: https://github.com/philllipjung/o11ybook/tree/main/4.1
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
kompose.cmd: kompose convert -f docker-compose.yaml
kompose.version: 1.26.1 (a9d05d509)
creationTimestamp: null
labels:
io.kompose.service: minio
name: minio
spec:
replicas: 1
selector:
matchLabels:
io.kompose.service: minio
strategy:
type: Recreate
template:
metadata:
annotations:
kompose.cmd: kompose convert -f docker-compose.yaml
kompose.version: 1.26.1 (a9d05d509)
creationTimestamp: null
labels:
io.kompose.service: minio
spec:
containers:
- args:
- server
- --console-address
- :9001
- /export
env:
- name: MINIO_ACCESS_KEY
value: 697d0993dd097f38d5b8
- name: MINIO_SECRET_KEY
value: 9f88738761b57c63f6a81bdfd471
- name: MINIO_ROOT_USER # 로그인할 아이디와 패스워드
value: admin
- name: MINIO_ROOT_PASSWORD
value: admin123
image: minio/minio:latest
livenessProbe:
exec:
command:
- curl
- -f
- http://localhost:9000/minio/health/live
failureThreshold: 3
periodSeconds: 30
timeoutSeconds: 20
name: minio
ports:
- containerPort: 9000
- containerPort: 9001
resources: {}
volumeMounts:
- mountPath: /export
name: minio-claim0
restartPolicy: Always
volumes:
- name: minio-claim0
persistentVolumeClaim:
claimName: minio-claim0
status: {}
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
kompose.cmd: ./kompose convert -f docker-compose.yaml
kompose.version: 1.26.1 (a9d05d509)
creationTimestamp: null
labels:
io.kompose.service: redis
name: redis
spec:
replicas: 1
selector:
matchLabels:
io.kompose.service: redis
strategy:
type: Recreate
template:
metadata:
annotations:
kompose.cmd: ./kompose convert -f docker-compose.yaml
kompose.version: 1.26.1 (a9d05d509)
creationTimestamp: null
labels:
io.kompose.service: redis
spec:
containers:
- env:
- name: ALLOW_EMPTY_PASSWORD
value: "yes"
image: bitnami/redis:latest
name: redis
ports:
- containerPort: 6379
resources: {}
volumeMounts:
- mountPath: /data
name: redis-claim0
restartPolicy: Always
volumes:
- name: redis-claim0
persistentVolumeClaim:
claimName: redis-claim0
status: {}
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
kompose.cmd: ./kompose convert -f docker-compose.yaml
kompose.version: 1.26.1 (a9d05d509)
creationTimestamp: null
labels:
io.kompose.service: redis-commander
name: redis-commander
spec:
replicas: 1
selector:
matchLabels:
io.kompose.service: redis-commander
strategy: {}
template:
metadata:
annotations:
kompose.cmd: ./kompose convert -f docker-compose.yaml
kompose.version: 1.26.1 (a9d05d509)
creationTimestamp: null
labels:
io.kompose.service: redis-commander
spec:
containers:
- env:
- name: REDIS_HOSTS
value: local:redis:6379
image: rediscommander/redis-commander:latest
name: redis-commander
ports:
- containerPort: 8081
resources: {}
restartPolicy: Always
status: {}
다음은 redis commander GUI 화면이다.
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
kompose.cmd: kompose convert -f docker-compose.yaml
kompose.version: 1.26.1 (a9d05d509)
creationTimestamp: null
labels:
io.kompose.service: consul
name: consul
spec:
replicas: 1
selector:
matchLabels:
io.kompose.service: consul
strategy:
type: Recreate
template:
metadata:
annotations:
kompose.cmd: kompose convert -f docker-compose.yaml
kompose.version: 1.26.1 (a9d05d509)
creationTimestamp: null
labels:
io.kompose.service: consul
spec:
containers:
- args:
- agent
- -log-level=info
- -dev
- -ui
- -client
- 0.0.0.0
image: consul:1.9
name: consul
ports:
- containerPort: 8500
resources: {}
volumeMounts:
- mountPath: /consul/config
name: consul-claim0
- mountPath: /consul/data
name: consul-claim1
restartPolicy: Always
volumes:
- name: consul-claim0
persistentVolumeClaim:
claimName: consul-claim0
- name: consul-claim1
persistentVolumeClaim:
claimName: consul-claim1
status: {}
초기에는 consul 서비스만 띄워져있다.
문서를 참고하여 consul API 를 사용하여 redis 를 별도로 등록한다.
curl --request PUT --data '{
"name": "redis",
"tags": ["database", "cache"],
"address": "redis.ada-grafana",
"port": 6379,
"checks": [
{
"name": "Redis TCP Check",
"tcp": "redis.ada-grafana:6379",
"interval": "10s",
"timeout": "1s"
}
]
}' http://10.77.110.138:30885/v1/agent/service/register
항목 | 멤캐시트 | 레디스 |
---|---|---|
데이터 분할 | 지원 | 지원 |
다양한 데이터 구조 지원 | 미지원 | 지원 |
스레드 모델 | 멀티스레드 | 싱글스레드 |
데이터 저장 | 미지원 | 지원 |
데이터 복제 | 미지원 | 지원 |
트랜잭션 지원 | 미지원 | 지원 |
발행과 구독 | 미지원 | 지원 |
멤캐시트는 멀티 스레드를 지원하고, 레디스는 싱글 스레드이지만 스케일 아웃을 지원하고 있다.
각 컴포넌트를 개별 마이크로서비스로 실행하여 마이크로서비스의 개수를 늘려 확장시키는 모드이다. 설정과 유지 관리가 복잡하지만 대규모 로키 클러스터 또는 확장과 클러스터 작업에 대한 많은 제어가 필요한 경우 사용된다.
쓰기 방식 (안정성 확보)
읽기 방식
=> 전체적으로 로키 컴포넌트는 다수의 해시 링에 샤드를 구성한 다음 WAL, 쿼럼, 가십, 캐시, 레플리케이션 팩터를 향상시키고, 병렬 처리와 중복제거를 기본으로 한다
들어오는 스트림을 처리하는 역할로서 로그 데이터에 대한 쓰기 경로의 첫번 째 단계다. 스트림을 수신하면 각 스트림의 정확성 검증되고 및 구성된 테넌트의 제한 내에 있는지 확인한다. 그런 다음 유효한 청크를 배치로 분할하고 인제스터에 병렬로 보낸다. 디스트리뷰터는 상태 비저장 컴포넌트이며 스트림 검증 작업을 인제스터로부터 독립적으로 확장할 수 있기 때문에 로키가 수집기에 과부라흫 줄 수 있는 공격으로부터 보호할 수 있다.
-querier.frontend-address
플래그에 쿼리 프런트엔드 주소를 넣어 연결한다 고가용성 구현과, 클러스터 수평 확장 또는 축소를 지원하기 위해 일관된 해시링을 로키 클러스터 아키텍처와 통합하여 사용한다. 모든 로키 컴포넌트가 해시 링으로 자동 연결되는 것은 아니며 다음 컴포넌트는 해시 링에 연결해야 한다.
해시 링에 등록된 각 노드마다 키-값 저장소를 다음과 같이 보유한다
4.2.2 로키 바이너리 구성은 스킵하고 바로 쿠버네티스에 설치한다
helm repo add grafana https://grafana.github.io/helm-charts
helm fetch grafana/loki-distributed
tar xvf loki-distributed-0.78.3.tgz
cd loki-distributed
로키 헬름차트를 가져왔으면 values.yaml 파일을 다음과 같이 수정한다
distributor:
autoscaling:
enabled: true # distributor.autoscaling 사용
querier:
autoscaling:
enabled: true # querier.autoscaling 사용
persistence:
enabled: true # querier.persistence 사용
queryFrontend:
autoscaling:
enabled: true # queryFrontend.autoscaling 사용
gateway:
enabled: true # gateway.enabled 사용
autoscaling:
enabled: true # gateway.autoscaling 사용
compactor:
enabled: true # compactor.enabled 사용
memcachedExporter:
enabled: true # memcachedExporter 사용
memcachedChunks:
enabled: true # memcachedChunk 사용
memcachedFrontend:
enabled: true # memcachedFrontend 사용
memcachedIndexQueries:
enabled: true # memcachedIndexQueries 사용
memcachedIndexWrites:
enabled: true # memcachedIndexWrites 사용
그리고 로키를 설치하면 다음과 같이 뜬다
> loki-distributed % helm install loki .
NAME: loki
LAST DEPLOYED: Fri Mar 1 15:23:12 2024
NAMESPACE: ada-grafana
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
***********************************************************************
Welcome to Grafana Loki
Chart version: 0.78.3
Loki version: 2.9.4
***********************************************************************
Installed components:
* gateway
* ingester
* distributor
* querier
* query-frontend
* compactor
* memcached-chunks
* memcached-frontend
* memcached-index-queries
* memcached-index-writes
> loki-distributed % k get pod | grep loki
loki-loki-distributed-compactor-0 0/1 CrashLoopBackOff 7 (55s ago) 12m
loki-loki-distributed-distributor-6fb8d74fbb-p7v8s 1/1 Running 0 12m
loki-loki-distributed-gateway-87cd896d5-jzdtz 1/1 Running 0 12m
loki-loki-distributed-ingester-0 1/1 Running 0 12m
loki-loki-distributed-memcached-chunks-0 2/2 Running 0 12m
loki-loki-distributed-memcached-frontend-0 2/2 Running 0 12m
loki-loki-distributed-memcached-index-queries-0 2/2 Running 0 12m
loki-loki-distributed-memcached-index-writes-0 2/2 Running 0 12m
loki-loki-distributed-querier-0 1/1 Running 0 12m
loki-loki-distributed-query-frontend-67c5c65f5b-z7bcv 1/1 Running 0 12m
이후 그라파나 대시보드를 설치하여 로키를 확인한다
> helm fetch grafana/grafana
> tar xvf grafana-7.3.3.tgz
> cd grafana
> helm install grafana .
NAME: grafana
LAST DEPLOYED: Fri Mar 1 15:38:39 2024
NAMESPACE: ada-grafana
STATUS: deployed
REVISION: 1
NOTES:
1. Get your 'admin' user password by running:
kubectl get secret --namespace ada-grafana grafana -o jsonpath="{.data.admin-password}" | base64 --decode ; echo
2. The Grafana server can be accessed via port 80 on the following DNS name from within your cluster:
grafana.ada-grafana.svc.cluster.local
Get the Grafana URL to visit by running these commands in the same shell:
export POD_NAME=$(kubectl get pods --namespace ada-grafana -l "app.kubernetes.io/name=grafana,app.kubernetes.io/instance=grafana" -o jsonpath="{.items[0].metadata.name}")
kubectl --namespace ada-grafana port-forward $POD_NAME 3000
3. Login with the password from step 1 and the username: admin
#################################################################################
###### WARNING: Persistence is disabled!!! You will lose your data when #####
###### the Grafana pod is terminated. #####
#################################################################################
> kubectl get secret --namespace ada-grafana grafana -o jsonpath="{.data.admin-password}" | base64 --decode ; echo
# 어드민 비밀번호 출력
이후 그라파나에 접속하여 아이디 & 비밀번호에 admin
, 위에서 출력된 비밀번호 입력 후 데이터 소스에 로키를 추가한다.
curl -v -H "Content-Type: application/json" -XPOST -s "http://10.77.110.164:32498/loki/api/v1/push" --data-raw \
'{"streams": [{ "stream": { "foo": "bar2" }, "values": [ [ "1709276745000000000", "ada" ] ] }]}'
* Trying 10.77.110.164:32498...
* Connected to 10.77.110.164 (10.77.110.164) port 32498 (#0)
> POST /loki/api/v1/push HTTP/1.1
> Host: 10.77.110.164:32498
> User-Agent: curl/7.79.1
> Accept: */*
> Content-Type: application/json
> Content-Length: 94
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 204 No Content
< Server: nginx/1.20.2
< Date: Fri, 01 Mar 2024 07:08:01 GMT
< Connection: keep-alive
<
* Connection #0 to host 10.77.110.164 left intact
장기 저장소 솔루션으로 타노스가 있지만, 타노스는 튜닝이 어렵고 다른 관측 가능 가능성과 연계 설정하는 것이 어렵다. 타노스를 대체할 수 있는 그라파나 미미르를 살펴본다.
미미르도 로키와 마찬가지로 병렬 실행과 수평 확장 가능한 마이크로서비스가 다수 존재한다. 프로메테우스 및 오픈 메트릭 등을 지원하며 이들로부터 대용량의 시계열 데이터를 저장한다.
프로메테우스
타깃으로부터 샘플을 스크랩하고 프로메테우스의 원격쓰기 API 를 사용하여 미미르에 push 한다. API 의 HTTP 요청에는 테넌트 ID 를 지정하는 헤더와 스내피 압축 프로토콜 버퍼 메시지를 본문에 담겨 있다.
디스트리뷰터
프로메테우스로부터 받은 샘플을 처리한다.
인제스터
디스트리뷰터를 통해 인제스터로 들어온 샘플은 메모리에 보관되고, WAL 에 기록된다. WAL 은 해당 샘플이 새 TSDB 블록으로 생성될 때 없어지고 기본적으로 이는 두 시간마다 실행된다. 새로 생성된 블록은 장기 저장소에 업로드되고 -blocks-storage.tsdb.retention-repiod
가 만료될때까지 인제스터에 보관된다. 만약 인제스터가 갑자기 종료된 경우 WAL 을 영구 디스크에 저장한다(AWS EBS 등). 그리고 k8s에서 미미르 클러스터를 실행하는 경우 PVC 와 함께 스테이트풀셋을 사용할 수도 있다.
콤팩터
여러 개로 복제된 인제스터로부터 쓰여진 블록을 단일 블록으로 병합하고 중복 샘플을 제거 및 압축하여 스토리지 절약하고 효율적인 읽기/쓰기를 가능하게 한다.
쿼리 프런트엔드
미미르로 들어오는 쿼리는 쿼리 프런트엔드에 입수되고 여러 개의 작은 쿼리로 분할한다. 그런 다음 결과 캐시를 확인하여 캐시된 경우는 캐시된 결과를 반환하고, 캐시에서 응답할 수 없는 쿼리는 프런트엔드 내 메모리 큐에 삽입한다.
쿼리어
쿼리 프런트엔드 내 메모리 큐에서 쿼리를 가져와 작업한다. 스토어 게이트웨이와 인제스터에 연결하여 쿼리를 실행하는 데 필요한 모든 데이터를 가져온다. 이후 쿼리 결과를 프런트엔드에 반환한다
미미르는 각 테넌트의 시계열 데이터를 자체 TSDB 에 저장함으로써 블록의 시리즈를 유지한다. 기본적으로 블록의 범위는 두 시간이며, 각 블록 디렉토리는 인덱스 파일, 메타데이터, 청크가 존재한다.
미미르에서 사용하는 디스크로는 AWS, GCP 등의 스토리지, 로컬 파일시스템 단일 노드 등이 있다.
인제스터가 각각 2, 5, 8 토큰에 순서대로 등록되면 시계방향을 다른 토큰들을 담당하게 된다. 예를 들어, 토큰 1에 스트림이 들어오면 인제스터1(토큰2)가 담당하고 스트림 쓰기 작업을 진행한다. 그리고 안정적으로 데이터를 전달하기 위해 복제본을 만드는데 이때도 마찬가지로 시계방향으로 가까운 인제스터를 찾아 복제를 한다.
토큰 3에 작업요청이 들어오면 인제스터2가 처리하고 복제본으로 인제스터 3에게도 전달하여 쓰기 요청을 한다
해시 링을 사용하는 미미르 컴포넌트는 다음과 같다
해시링 데이터 구조는 그라파나 미미르 인스턴스 간에 키-값 저장소를 사용하여 공유되며 이를 통해 구축한 기능은 다음과 같다
노드 익스포터 -> 프로메테우스 -> 미미르 순으로 메트릭이 전달된다.
1. 노드 익스포터에서 메트릭을 수집하여 프로메테우스에 보내고
2. 다수의 프로메테우스로부터 메트릭을 수집하여 미미르에 전달된다.
로키와 같이 grafana 레포로부터 헬름차트를 인스톨한다.
ref: https://grafana.com/docs/mimir/latest/set-up/helm-chart
> helm fetch grafana/mimir-distributed
> tar xvf mimir-distributed-5.2.1.tgz
> helm install mimir .
W0301 17:04:40.291057 12419 warnings.go:70] metadata.name: this is used in Pod names and hostnames, which can result in surprising behavior; a DNS label is recommended: [must not contain dots]
NAME: mimir
LAST DEPLOYED: Fri Mar 1 17:04:32 2024
NAMESPACE: ada-grafana
STATUS: deployed
REVISION: 1
NOTES:
Welcome to Grafana Mimir!
Remote write endpoints for Prometheus or Grafana Agent:
Ingress is not enabled, see the nginx.ingress values.
From inside the cluster:
http://mimir-nginx.ada-grafana.svc:80/api/v1/push
Read address, Grafana data source (Prometheus) URL:
Ingress is not enabled, see the nginx.ingress values.
From inside the cluster:
http://mimir-nginx.ada-grafana.svc:80/prometheus
**IMPORTANT**: Always consult CHANGELOG.md file at https://github.com/grafana/mimir/blob/main/operations/helm/charts/mimir-distributed/CHANGELOG.md and the deprecation list there to learn about breaking changes that require action during upgrade.
k get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
mimir-alertmanager ClusterIP 172.20.187.32 <none> 8080/TCP,9095/TCP 2m1s
mimir-alertmanager-headless ClusterIP None <none> 8080/TCP,9095/TCP,9094/TCP 2m1s
mimir-compactor ClusterIP 172.20.138.0 <none> 8080/TCP,9095/TCP 2m1s
mimir-distributor ClusterIP 172.20.180.42 <none> 8080/TCP,9095/TCP 2m1s
mimir-distributor-headless ClusterIP None <none> 8080/TCP,9095/TCP 2m1s
mimir-gossip-ring ClusterIP None <none> 7946/TCP 2m1s
mimir-ingester-headless ClusterIP None <none> 8080/TCP,9095/TCP 2m1s
mimir-ingester-zone-a ClusterIP 172.20.231.78 <none> 8080/TCP,9095/TCP 2m1s
mimir-ingester-zone-b ClusterIP 172.20.55.232 <none> 8080/TCP,9095/TCP 2m1s
mimir-ingester-zone-c ClusterIP 172.20.227.182 <none> 8080/TCP,9095/TCP 2m1s
mimir-minio ClusterIP 172.20.81.199 <none> 9000/TCP 2m1s
mimir-minio-console ClusterIP 172.20.215.234 <none> 9001/TCP 2m1s
mimir-nginx ClusterIP 172.20.201.56 <none> 80/TCP 2m1s
mimir-overrides-exporter ClusterIP 172.20.33.125 <none> 8080/TCP,9095/TCP 2m1s
mimir-querier ClusterIP 172.20.144.166 <none> 8080/TCP,9095/TCP 2m1s
mimir-query-frontend ClusterIP 172.20.171.98 <none> 8080/TCP,9095/TCP 2m1s
mimir-query-scheduler ClusterIP 172.20.115.148 <none> 8080/TCP,9095/TCP 2m1s
mimir-query-scheduler-headless ClusterIP None <none> 8080/TCP,9095/TCP 2m1s
mimir-ruler ClusterIP 172.20.186.105 <none> 8080/TCP 2m1s
mimir-store-gateway-headless ClusterIP None <none> 8080/TCP,9095/TCP 2m1s
mimir-store-gateway-zone-a ClusterIP 172.20.104.77 <none> 8080/TCP,9095/TCP 2m1s
mimir-store-gateway-zone-b ClusterIP 172.20.43.107 <none> 8080/TCP,9095/TCP 2m1s
mimir-store-gateway-zone-c ClusterIP 172.20.98.205 <none> 8080/TCP,9095/TCP 2m1s
미미르에서 미니오 객체스토리지가 초기화 된 것을 확인할 수 있다
ref: https://grafana.com/docs/mimir/latest/visualize/
이후 설치한 그라파나 대시보드에 데이터 소스 연결한다.
ref: https://grafana.com/docs/grafana-cloud/alerting-and-irm/slo/set-up/additionaldatasources/
1. 데이터 소스 추가에 프로메테우스 클릭
2. performance 에 mimir 선택하고 connection 에 mimir-nginx 엔드포인트 url을 넣는다(path 는 /prometheus 이다)
3. explore 에서 미미르를 통해 프로메테우스 지표를 갖고오는 걸 확인한다
정리하면 프로메테우스는 미미르에 지표를 보내고 미미르에서는 Minio 를 통해 객체스토리지로 데이터를 보내서 장기간동안 보관한다. 그리고 그라파나 대시보드에서 Mimir 에 접근하여 프로메테우스의 지표를 읽어와 시각화한다. 이렇게 함으로써 프로메테우스에서 장기간동안 대용량 데이터를 보관못하는 단점을 보완한다.
ref: https://grafana.com/docs/tempo/latest/operations/architecture/
템포 또한 앞서 봤던 로키, 미미르와 마찬가지로 아키텍쳐 구조가 읽기와 쓰기가 나눠지고 내부 컴포넌트와 동작방식(해시 링 등)이 동일하다. 앞서 구체적으로 설명하였으니 아키텍쳐에 대한 내용은 스킵하고 바로 실습으로 넘어간다.
쿠버네티스 환경에서 실습하기에 해당 스텝은 넘어간다
> helm fetch grafana/tempo-distributed
> tar xvf tempo-distributed-1.8.5.tgz
> cd tempo-distributed
> helm install tempo .
NAME: tempo
LAST DEPLOYED: Sat Mar 9 16:46:37 2024
NAMESPACE: ada-grafana
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
***********************************************************************
Welcome to Grafana Tempo
Chart version: 1.8.5
Tempo version: 2.3.1
***********************************************************************
Installed components:
* ingester
* distributor
* querier
* query-frontend
* compactor
* memcached
> k get pod
tempo-compactor-557d799589-r6wnl 1/1 Running 0 41s
tempo-distributor-566657f5b9-c75hk 1/1 Running 0 41s
tempo-ingester-0 1/1 Running 0 41s
tempo-ingester-1 1/1 Running 0 41s
tempo-ingester-2 1/1 Running 0 41s
tempo-memcached-0 1/1 Running 0 41s
tempo-querier-c854c9557-67qvf 1/1 Running 0 41s
tempo-query-frontend-68c67ff65c-hjs4p 1/1 Running 0 41s
앞서 로키, 미미르와 마찬가지로 그라파나 대시보드에 데이터소스를 추가하여 템포를 시각화한다.
더미 데이터: https://grafana.com/docs/grafana/latest/datasources/tempo/json-trace-file/
위 링크에 있는 json 을 tempo 에 import trace
하면 다음과 같이 나온다.
추적은 시스템 전체적으로 실행의 흐름을 집계하고 세부 트랜잭션을 분석하는 데 사용된다. 스팬 사이의 인과관계를 통해 추적으로부터 요청의 세부적인 내용을 파악해야한다.