우리가 관측 가능성(Observability)에 대해서 말할때, 보통 시계열의 수치 데이터를 뜻하는 메트릭(Metric)이나 인간이 읽을 수 있는(human-readable) 형태로 상태를 출력하는 로그(Log)를 떠올릴 것입니다.
하지만 관측 가능성의 3개 기둥(Three pilars of observability)라고 하는 3개 주요 관측 가능성 요소 중에는 메트릭,로그와 함께 트레이스(Trace)가 존재합니다.
이 트레이스가 무엇인지, 또 어떻게 트레이스에 대한 관측 가능성을 확보할 수 있는지 대표적인 트레이싱 OSS 도구인 Grafana tempo와 함께 알아보겠습니다.
1. 트레이스가 뭐지?
트레이스는 메트릭, 로그와 함께 관측가능성의 3개 주요 요소 중 하나입니다. 하지만 트레이스에 대해서는 나머지 두 요소에 비해 덜 알려져 있는 것도 사실입니다.
트레이스란 분산되어 있는 이벤트 간의 연관성을 하나의 플로우로 표현하는 관측 가능성 요소입니다.
현대 어플리케이션에서는 우리가 서비스에 요청을 보낼 때 내부에서는 분산 서비스 사이에서 수십 번의 이벤트가 일어나게 됩니다.
그렇기 때문에 어느 날 서비스 요청의 레이턴시가 증가했을때, 서비스 운영자 입장에서는 구성 요소의 어떤 부분이 원인인지 파악하기 힘듭니다.
특히 작고 유연한 아키텍쳐를 지향하는 MSA(MicroServiceArchitecture) 구조에서는 작은 마이크로 서비스들 사이에서 주고받는 이벤트가 자주 일어나기 때문에 원인이 되는 서비스를 파악하는 것은 더욱 요원해집니다.
트레이스는 이 문제를 해결하기 위해 등장한 개념인데요.
트레이스는 각 요청마다 고유한 ID를 부여합니다. 그 ID를 가진 요청이 이벤트를 발생시킬 때마다 이벤트의 시작시간과 끝시간을 측정해 레이턴시를 파악합니다. 결과적으로 고유한 ID를 가진 이벤트끼리 묶어주면 위와 같은 하나의 다이어그램이 완성됩니다.
이렇게 트레이스는 이벤트 간의 연관성을 만들어주기 때문에 흩어져 있는 분산 서비스를 하나의 연관 서비스로 묶을 수 있으며, 각 서비스의 레이턴시를 측정할 수 있기 때문에 어떤 서비스가 몇ms의 지연을 가지는지 쉽게 파악할 수 있습니다.
하나의 트레이스는 여러 개의 스팬(Span)으로 이루어져 있습니다. 하나의 스팬이 곧 하나의 이벤트라고 볼 수 있습니다.
각 스팬은 일어난 이벤트를 의미하며 이 스팬들은 공통된 고유 ID를 기준으로 연관되어 있습니다.
예를 들어 서비스 A 스팬이라고 한다면 요청이 들어오고 서비스 A에 도달해 기능을 실행했을 때의 시간과 끝냈을 때의 시간을 측정한 것이라고 볼 수 있겠죠.
이런 트레이스를 이용해서 어플리케이션이 어떤 경로를 이용해 이벤트끼리 상호작용하고, 얼마 만큼의 레이턴시를 가지며, 어떤 부분에서 문제가 발생할 수 있는지 파악할 수 있습니다.
이 트레이스를 관측하기 위한 방법은 사용하는 도구마다 천차만별이였는데요. 기존에는 트레이싱을 구현하기 위해 하드 코딩된 트레이스 관련 부분을 일일이 애플리케이션에 심어야 하고 도구를 변경할 때마다 기존 코드를 일괄 변경해야 한다는 단점이 있었습니다.
그래서 이를 통일하고 개발자 중심의 트레이싱 방법론을 제공하기 위해 OpenTelemetry라는 도구를 출범했습니다.
OpenTelemetry는 기존에 존재하던 OpenCencus와 OpenTracing을 통합한 도구인데요, CNCF 재단의 프로젝트이기도 합니다.
현재 사용할 수 있는 대부분의 트레이싱 도구들이 OpenTelemetry와 호환이 가능합니다.
2. Grafana의 트레이싱 도구 Tempo
시중에 나와있는 트레이싱 도구는 다양합니다. Datadog, Dynatrace같은 엔터프라이즈 솔루션부터, ELK로 유명한 Elastic도 APM이라는 트레이싱 도구를, Jaeger, Zipkin같은 OSS 도구까지 선택할 수 있는 도구가 많죠,
이번 포스팅에서는 그 중에서도 Grafana 사의 OSS 트레이싱 도구인 Tempo를 사용해보겠습니다.
Grafana tempo는 다음과 같은 이점이 있습니다.
- Grafana 도구와의 통합성
Grafana Tempo는 Grafana 사의 도구이기 떄문에 Grafana 사의 대십도ㅡ 도구인 Grafana, 로깅 도구인 Loki와 강한 통합성을 제공합니다. 이로 인해 로그 -> 트레이스, 메트릭 -> 트레이스 등 관측 가능성 요소들 간의 원활한 연계가 가능해 더 강력한 관측 가능성 확보에 큰 도움이 됩니다. - 다른 도구들과의 높은 호환성
Grafana Tempo는 Opentelemetry는 물론 Zipkin, Jaeger 등 다른 트레이싱 도구들과도 호환성이 좋습니다. 떄문에 다른 도구를 이용했더라도 코드 단의 변경 없이 그대로 Tempo를 이용할 수 있습니다. - 폭 넓은 저장소 지원
트레이스는 영구적으로 저장되어야 하는 데이터이기 때문에 특정 저장소에 쌓아야 합니다. Jaeger같은 다른 도구들은 트레이스 저장소로 무거운 Elasticsearch나 Cassandra를 사용하지만 Grafana Tempo는 편리하고 가벼운 Object storage(S3, GCS 등..)를 지원하기 떄문에 저장소 관리가 더욱 편리합니다.
Grafana Tempo는 여러 구성 요소로 이루어져 있습니다.
구성 요소들은 크게 트레이스를 수집하는 Distributor, 트레이스를 저장하는 Ingester, ID로 트레이스를 찾아주는 Querier, 검색을 위해 샤딩을 담당하는 Query-frontend, Backend storage에 데이터를 넣거나 뺴는 Compactor로 나눌 수 있습니다.
이 각각의 요소들은 MSA 구성으로 각자 다른 서비스로 배포할 수도, 혹은 하나의 애플리케이션으로 통합해서 배포할 수도 있습니다.
MSA구성으로 배포한다면 각 구성 서비스 별로 스케일링되거나, 디커플링이 가능하다는 장점이 있겠지만 아무래도 관리의 복잡도가 올라가는 단점이 있습니다.
Grafana Tempo의 트레이스를 관측할 수 있는 UI는 같은 Grafana 사의 대시보드인 Grafana를 이용하는 것이 보통입니다.
아래와 같은 UI로 Tempo만의 트레이스를 보는데도 편리하지만, Loki와 같은 로깅 도구와의 연계도 편하도록 설계되어 있습니다.
이번 포스팅에서는 하나의 단일 애플리케이션으로 Grafana Tempo를 배포해서 Grafana 대시보드로 트레이스를 관측해보도록 하겠습니다.
3. Grafana Tempo 사용해보기
이번 포스팅에서는 Grafana Tempo를 직접 배포해서 트레이스를 관측해보겠습니다.
배포 환경은 Local Kubernetes 플랫폼인 Minikube에서 실행했습니다.
3-1. Demo 애플리케이션 배포
먼저 트레이스를 관측하기 위한 Demo 애플리케이션을 배포해보겠습니다.
이번에 배포할 Demo 애플리케이션은 Grafana에서 제공한 tns(The New Stack)입니다.
아래 레포지토리를 가져와서 사용합니다.
https://github.com/grafana/tns/tree/main/production
배포에 필요한 매니페스트는 "tns/production/k8s-yamls"에 존재합니다.
매니페스트 컨텐츠 중 JAEGER_AGENT_HOST 환경 변수를 아래와 같이 tempo.tempo.svc.cluster.local로 변경합니다.
이 환경 변수는 나중에 배포할 Grafana Tempo의 호스트 주소를 적는 것으로, 아래와 같이 변경함으로써 tempo 네임스페이스에 존재하는 tempo 서비스를 엔드포인트로 잡는 것입니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
Handling connection for 3000
replicas: 1
selector:
matchLabels:
name: app
template:
metadata:
labels:
name: app
spec:
containers:
- name: app
image: grafana/tns-app:latest
imagePullPolicy: IfNotPresent
args:
- -log.level=debug
- http://db
ports:
- name: http-metrics
containerPort: 80
env:
- name: JAEGER_AGENT_HOST
value: tempo.tempo.svc.cluster.local
...
|
cs |
"app" 디플로이먼트에서 했던 수정을 "db"와 "loadgen" 디플로이먼트도 동일하게 적용해줍니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
apiVersion: apps/v1
kind: Deployment
metadata:
name: db
spec:
replicas: 1
selector:
matchLabels:
name: db
template:
metadata:
labels:
name: db
spec:
containers:
- name: db
image: grafana/tns-db:latest
imagePullPolicy: IfNotPresent
args:
- -log.level=debug
ports:
- name: http-metrics
containerPort: 80
env:
- name: JAEGER_AGENT_HOST
value: tempo.tempo.svc.cluster.local
... |
cs |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
apiVersion: apps/v1
kind: Deployment
metadata:
name: loadgen
spec:
replicas: 1
selector:
matchLabels:
name: loadgen
template:
metadata:
labels:
name: loadgen
spec:
containers:
- name: loadgen
image: grafana/tns-loadgen:latest
imagePullPolicy: IfNotPresent
args:
- -log.level=debug
- http://app
ports:
- name: http-metrics
containerPort: 80
env:
- name: JAEGER_AGENT_HOST
value: tempo.tempo.svc.cluster.local
- name: JAEGER_TAGS
value: cluster=docker,namespace=default
...
|
cs |
세 디플로이먼트 모두 변경을 완료했다면 이제 아래 명령어로 매니페스트들을 적용합니다.
1
|
kubectl apply -f ./
|
cs |
아래와 같이 3개의 pod가 제대로 활성화되면 정상적으로 진행된 것입니다.
3-2. Grafana Tempo 배포
다음으로는 이번 포스팅의 주인공인 Grafana Tempo를 배포해보겠습니다.
이전 장에서 말씀드렸던 것처럼, Kubernetes에 Tempo를 배포하는 방법은 2가지가 있습니다.
하나는 MSA형태의 각 요소별로 서비스를 구성하는 배포 방법이고,
두번째는 하나의 단일 애플리케이션으로 배포하는 방법입니다.
이번 포스팅에서는 단일 애플리케이션으로 배포하는 방법을 채택해 진행해보겠습니다.
설치 방법은 Kubernetes Package manager인 Helm으로 진행합니다. Helm에 대해서는 공식 문서를 참고해주세요.
먼저 아래 명령어로 Helm 차트를 레포지토리에 추가합니다.
1
|
helm repo add grafana https://grafana.github.io/helm-charts
|
cs |
그리고 아래 명령어로 Tempo를 배포할 네임스페이스를 생성 후, Helm 차트로 Tempo와 Grafana를 배포합니다.
1
2
|
kubectl create ns tempo
helm install tempo grafana/tempo -n tempo
helm install grafana grafana/grafana -n tempo |
cs |
정상적으로 실행되었다면 아래와 같이 리소스가 생성된 것을 확인할 수 있습니다.
3-3. Grafana 설정
이제 Grafana 대시보드를 이용해 트레이스를 관측해보도록 하겠습니다.
우선 Grafana 대시보드 웹 페이지로 진입하기 위해 아래 명령어로 포트 포워딩을 실행합니다.
1
|
kubectl port-forward GRAFANA_POD 3000 -n tempo &
|
cs |
그리고 localhost:3000 페이지로 진입해 Grafana 대시보드 웹페이지로 진입합니다.
초기 ID와 Password는 admin, admin입니다. 간혹 이 Password로 로그인이 되지 않는 경우가 있는데, 이 경우에는 grafana 컨테이너로 진입해서 grafana-cli로 패스워드 초기화 작업을 해주어야 합니다.
admin,admin으로 로그인이 안될시에 아래 명령어로 패스워드를 초기화합니다.
1
2
|
kubectl exec GRAFANA_POD --it -n tempo grafana-cli admin reset-admin-password admin
|
cs |
Grafana 대시보드로 로그인했다면 Tempo 데이터 소스를 추가해야 합니다. 좌측 Configuration -> Data sources로 진입해 데이터 소스로 Tempo를 선택합니다.
Tempo 데이터소스 URL은 http://tempo:3100 입니다. 현재 Tempo 서비스가 이 주소의 3100번 포트로 서버를 유지하고 있기 때문입니다.
Save&test 버튼을 누르면 Tempo 데이터 소스를 사용할 수 있습니다. 이렇게 Grafana 대시보드에서의 설정은 모두 마쳤습니다.
3-4. Tempo 트레이스 관측하기
이제 Grafana 대시보드를 이용해서 트레이스를 관측할 수 있습니다.
좌측의 Explorer 탭으로 진입하면 트레이스를 쿼리할 수 있는 페이지로 진입합니다.
TraceID 란에 트레이스의 고유 ID를 적으면 해당 트레이스를 관측할 수 있습니다. 트레이스 ID는 현재 DEMO 애플리케이션이 stdout 로그로 출력하고 있기 때문에 아래 명령어로 트레이스 ID를 찾겠습니다.
1
|
kubectl logs APP_POD
|
cs |
명령어를 실행하면 APP 어플리케이션이 출력하는 로그를 볼 수 있습니다. 로그 중 traceID 값이 트레이스의 고유 ID입니다. 현재 애플리케이션은 이렇게 요청마다 트레이스를 하나씩 생성하는 중입니다. traceID값을 복사해서 Grafana 대시보드의 traceID란에 붙여넣습니다.
traceID란에 ID를 넣은 후 Run query 버튼을 누르면 아래와 같이 스팬, 깊이, 지속시간 등 트레이스의 자세한 내용을 볼 수 있습니다.
트레이스를 관측함으로써 다양한 정보를 얻을 수 있습니다. 현재 트레이스의 요청은 lb, app, db 3개의 서비스를 통과한 것을 알 수 있고 총 6개의 스팬으로 이루어져 있습니다. 그리고 각 스팬마다 가지고 있는 duration 값을 통해 어떤 이벤트에서 얼마만큼의 지연시간이 있었는지 파악할 수 있습니다.
예를 들어 lb에서 app으로 HTTP GET 요청을 보내는 이벤트는 5.4ms의 레이턴시가 있는 것을 쉽게 알 수 있습니다. 이런 이벤트들의 집합으로 하나의 요청이 어떤 경로를 통해, 얼마만큼의 레이턴시로 이루어져 있는지 알 수 있습니다.
각 스팬을 클릭함으로써 이벤트의 세부 사항을 알 수도 있습니다. 예를 들어 클러스터의 이름이나, 호스트네임, ip주소같은 이벤트를 특정하는데 도움을 줄 수 있는 정보들을 알 수 있습니다.
이런 정보를 관측함으로써 어떤 서비스의 지연 시간이 큰 영향을 미치는지, 어떤 이벤트가 경로 상에 존재하는지 알 수 있을 것입니다.
이같은 정보를 통해 잠재된 문제의 원인을 파악할 수 있는 점이 트레이싱 관측 가능성의 큰 장점입니다.
+) Tempo search로 트레이스 검색하기
지금까지 했던 방법처럼 애플리케이션 로그에서 TraceID를 가져오는 방법도 가능하지만, 로그에 TraceID를 출력하지 않는 경우도 있을 것입니다.
이런 경우에 Tempo의 Tempo search 기능을 이용하면 쉽게 TraceID를 찾을 수 있습니다. 현재(2022.01.27) 기준으로는 아직 베타 버전인 기능이지만 편리한 기능이므로 사용해보도록 하겠습니다.
우선 Grafana 대시보드에서 Tempo search 기능을 활성화하겠습니다. 아래 명령어로 Grafana의 Configuration 파일을 담당하는 Configmap을 수정합니다.
1
|
kubectl edit cm grafana -n tempo
|
cs |
그리고 아래 매니페스트와 같이 [feature_toggles] enable= tempoSearch tempoBackendSearch 부분을 추가합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
apiVersion: v1
data:
grafana.ini: |
[analytics]
check_for_updates = true
[grafana_net]
url = https://grafana.net
[log]
mode = console
[paths]
data = /var/lib/grafana/
logs = /var/log/grafana
plugins = /var/lib/grafana/plugins
provisioning = /etc/grafana/provisioning
[feature_toggles]
enable = tempoSearch tempoBackendSearch
...
|
cs |
다음에는 Tempo에서도 Temposearch 기능을 활성화합니다. 아래 명령어로 Tempo의 Configuration 파일을 담당하는 Configmap을 수정합니다.
1
|
kubectl edit cm tempo -n tempo
|
cs |
아래 매니페스트와 같이 search_enabled 어트리뷰트를 true로 변경합니다.
1
2
3
4
5
6
7
8
9
|
apiVersion: v1
data:
overrides.yaml: |
overrides:
{}
tempo.yaml: |
multitenancy_enabled: false
search_enabled: true
...
|
cs |
이후 Grafana 대시보드로 진입하면 Search- Beta 탭이 추가되어 있는 것을 볼 수 있습니다. 여기서 서비스 이름과 스팬 이름을 지정하면 조건에 맞는 TraceId가 목록에 나오는 것을 확인할 수 있습니다.
목록의 TraceID를 클릭하면 이전과 같이 트레이스를 분석할 수 있는 탭이 생성됩니다. 이렇게 로그에서 TraceID를 찾지 않고도 Grafana 대시보드에서 원하는 트레이스나 스팬을 조건으로 TraceID를 발견할 수 있습니다.
4. GCS를 백엔드 스토리지로 사용하기
Grafana Tempo의 장점 중 하나가 오브젝트 스토리지를 백엔드 스토리지로 사용할 수 있다는 것이라고 처음에 말씀드렸습니다.
오브젝트 스토리지, 특히 s3, GCS같은 클라우드 기반의 오브젝트 스토리지를 저장소로 사용하면 트레이스 저장소 관리에 대한 부담을 덜 수 있기 떄문입니다.
지금까지는 local 스토리지를 저장소로 사용했지만, Grafana Tempo의 장점을 활용하기 위해 Google Cloud의 오브젝트 스토리지인 GCS를 Tempo의 백엔드 스토리지로 사용해보겠습니다.
우선 GCS 버켓 하나와 버켓에 Read,Write할 수 있는 권한을 가진 Service account key를 생성합니다.
그리고 Tempo가 GCS에 접근할 수 있도록 컨테이너의 GOOGLE_APPLICATION_CREDENTIALS 환경 변수에 Service account key를 넣는 작업을 하겠습니다.
아래 명령어로 생성한 Key 파일에 기반해 Kubernetes secret 오브젝트를 생성합니다.
1
2
|
kubectl create secret generic service-account-credentials --from-file=key.json=./key_file.json -n tempo
|
cs |
그리고 아래와 같이 Tempo의 statefulset 매니페스트를 변경합니다. 생성한 secret 오브젝트를 컨테이너 내에 위치시키고, 이를 환경 변수로 잡는 변경입니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
name: tempo
...
env:
- name: GOOGLE_APPLICATION_CREDENTIALS
value: /etc/gcp/key.json
...
volumeMounts:
- mountPath: /conf
name: tempo-conf
- mountPath: /etc/gcp
name: service-account-credentials-volume
readOnly: true
...
volumes:
- name: service-account-credentials-volume
secret:
defaultMode: 420
items:
- key: key.json
path: key.json
secretName: service-account-credentials
|
cs |
다시 파드를 가동하면 아래와 같이 GCS 버켓에 index.json 파일이 생성되면서 오브젝트 스토리지를 백엔드 스토리지로 사용하는 것을 볼 수 있습니다.
5. Instrument
이렇게 트레이스를 Tempo에서 관측할 수 있는 것은 애플리케이션에서 트레이스 관측에 필요한 정보들을 Tempo로 보내기 때문입니다.
그렇다면 어떻게 애플리케이션이 이런 정보들을 보내게 할 수 있을까요?
Intsrument라고 하는, 애플리케이션에 트레이스를 위한 코드를 심는 과정을 통해서 이런 정보들을 보내도록 할 수 있습니다.
이 Instrument의 과정은 언어마다, 도구마다 다르기 때문에 정확한 방법은 해당 도구의 공식 문서를 참고해야 합니다.
예를 들어 이번 포스팅에 사용한 Demo 애플리케이션은 Opentracing을 구현하기 위해 Jaeger exporter를 사용했습니다.
아래 코드는 Demo 애플리케이션의 "app"의 코드 중 일부분입니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
func main() {
serverConfig := server.Config{
MetricsNamespace: "tns",
}
serverConfig.RegisterFlags(flag.CommandLine)
flag.Parse()
// Use a gokit logger, and tell the server to use it.
logger := level.NewFilter(log.NewLogfmtLogger(log.NewSyncWriter(os.Stdout)), serverConfig.LogLevel.Gokit)
serverConfig.Log = logging.GoKit(logger)
// Setting the environment variable JAEGER_AGENT_HOST enables tracing
trace, err := tracing.NewFromEnv("app")
if err != nil {
level.Error(logger).Log("msg", "error initializing tracing", "err", err)
os.Exit(1)
}
defer trace.Close()
|
cs |
위 코드에서 tracing.NewFromEnv() 부분이 Jaeger exporter를 시작하는 코드입니다. 이 예제처럼 트레이스 정보를 내보내는 agent를 실행시킬 수 있어야 트레이스를 수집할 수 있습니다.
6. 마무리
지금까지 애플리케이션의 요청 과정을 추적해 이벤트 별 정보를 확인할 수 있는 트레이스 관측가능성과 그 트레이스를 관측할 수 있게 해주는 도구 Grafana Tempo에 대해서 알아봤습니다.
트레이스 관측가능성은 보통 단독으로 사용하기보다 메트릭, 로깅과 함께 사용하면 더욱 효과적입니다. 예를 들어 특정 시간대에 특정 애플리케이션의 레이턴시가 늘어났음을 메트릭으로 감지하면, 애플리케이션의 로그를 확인하고, 그 로그에서 확인할 수 있는 트레이스를 이용해 문제의 원인을 밝혀내는 식으로 사용할 수 있습니다.
많은 트레이싱 도구가 존재하지만 Grafana Tempo는 특히 오브젝트 스토리지를 백엔드 스토리지로 사용할 수 있기 때문에 저장소 관리가 훨씬 편하다는 점, Grafana 대시보드와 연동성이 좋다는 장점이 있습니다.
'Observability' 카테고리의 다른 글
Opentelemetry로 Kubernetes Observability 확보하기 : Logging (0) | 2024.03.07 |
---|---|
Prometheus Operator를 사용해 Kubernetes 환경에서 Prometheus 구성하기 (8) | 2022.03.31 |
Elasticsearch에 fluentd를 얹은 EFK stack 구축하기(with kubernetes) (9) | 2022.01.08 |
클라우드 리소스 Observability 확보 도구 Steampipe 사용기 + GCP IAM report 제작기 (0) | 2021.12.27 |
Elasticsearch의 ELK Stack을 GKE Cluster에 구성해 GCP 관측 가능성 확보하기 (6) | 2021.12.12 |