Observability를 확보하는 것은 시스템 및 소프트웨어를 유지하는데 필수적인 행위입니다.
Three pilars of Observability로 불리는 Log, Metric, Trace 3가지 Telemetry 데이터를 수집하고 관리하는 것이 Observability를 잘 확보한 것이라 할 수 있는데요.
이렇게 3개의 요소들을 바탕으로 Observability를 확보하고자 할때 허들이 되는 요소 중 하나는, Telemetry 데이터의 프로토콜이 Observability 서비스 및 플랫폼마다 다르다는 것입니다.
이 때문에 Corrleation이라고 하는, Telemetry 데이터 간 연동이 힘들고 수집한 데이터들을 한 곳에서 가공 및 관리하는 것이 불가능했었습니다.
이러한 문제를 해결하기 위해 벤더 중립적이고, 기존 프로토콜들을 통합할 수 있는 Observability 프레임워크인 Opentelemetry가 출범했습니다.
이번 시리즈에서는 이 Opentelemetry 프레임워크를 사용해서 Kubernetes 환경의 Log, Metric, Trace의 3가지 Telemetry 데이터를 수집,가공 및 관리하고 Correlation을 통해 데이터 간 연동까지 구현해보도록 하겠습니다.
그 중 이번 포스팅은 Kubernetes 환경의 Logging을 주제로 다뤄보겠습니다.
1. Opentelemetry Collector
1-1. Opentelemetry Collector란?
Opentelemtry는 Observability를 확보하기 위한 Telemetry 데이터를 수집,가공,관리하기 위할 수 있는 프레임워크이며, 이를 위한 API, SDK, Library, Collector 등의 도구를 제공하고 있습니다.
이 중 Opentelemetry Collector는 Opentelemetry에서 제공하는 기능들을 구현하는 구현체로서 Telemetry 데이터를 수집하고, 가공하고, 이를 통해 얻은 데이터를 Backend로 보낼 수 있는 기능을 제공하고 있습니다.
그래서 Opentelemetry Collector(이하 OTel Collector)는 Log, Metric, Trace 데이터를 방출하는 Source와 수집한 데이터를 보관 및 분석하는 Backend의 중간 다리 역할과 동시에 각 데이터들을 연동할 수 있습니다.
이를 통해 환경, 벤더, 플랫폼에 관계없이 동일한 프레임워크로 Observability를 확보할 수 있습니다.
1-2. Opentelemetry Collector의 구성 요소
OTel Collector를 사용하기 위한 설정은 Receiver, Processor, Exporter, Connector 4개의 구성 요소로 이루어져 있습니다.
Receivers
Receiver는 Telemetry 데이터를 가져오는 Source를 정의합니다.
Source로부터 데이터를 가져오는 Pull 방식과 Source가 Otel Collector로 데이터를 보내는 Push 방식을 모두 지원합니다.
Processors
Processors는 Receiver로부터 가져온 데이터를 가공하는 역할을 합니다.
규칙에 따라 데이터를 필터링하거나, 드랍하거나, 계산하는 등 다양한 작업을 수행할 수 있습니다.
Exporters
Exporters는 Telemetry 데이터를 내보낼 Destination을 정의합니다.
Destination으로 데이터를 보내는 Push 방식과 Destination이 데이터를 가져가도록 하는 Pull 방식을 모두 지원합니다.
마지막으로 OTel Collector의 “Pipeline” 설정을 통해서 위 3개 요소들을 하나의 파이프라인으로 구성할 수 있습니다.
2. Kubernetes 환경 Logging의 특징
Kubernetes는 역동적이며 다양한 구성요소로 이루어져 있다는 특징을 가진 분산 환경입니다.
때문에 Kubernetes 환경의 Log 수집은 기존의 Monolithic 환경과 다른 점이 존재합니다.
아래 목록은 Logging 관점에서 Kubernetes 환경이 기존 환경과 차별되는 지점입니다.
Control Plane과 Data Plane의 분리
Kubernetes는 kube-api, audit Log를 수집할 수 있는 Control plane과 Container, kubelet, kube-proxy Log를 수집할 수 있는 Data Plane으로 분리되어 있기 때문에, 각 구성 요소에 맞는 로그를 수집해야 함
대량의 Container 환경
Kubernetes는 영속적이지 않고 대량으로 존재하는 Container에서 Log를 수집해야 한다는 특징이 존재함
다양한 Layer가 존재
Kubernetes는 Container, Host, Cloud라는 3가지 Layer로 이루어져 있어 각 Layer별로 적절한 Log 수집 방법을 사용해야 함
위의 차이점들이 존재하기 때문에, Kubernetes 환경의 가시성을 확보하기 위해서는 아래와 같은 계층들에서 Log를 수집해야 합니다.
Layer | Log target | Source | Example |
Container level | Containers | /var/log/pods/... | Applications, Add-ons |
Host level | Kubernetes Dataplane | /var/log/journal/... | kubelet, container runtime, kube-proxy |
Host level | Linux Host | /var/log/secure, /var/log/dmesg... | Linux System logs |
Cluster level | Kubernetes Controlplane, API | Kubernetes API | Kubernetes Events, kube-api, audit |
3. Logging Architecture
위에서 다룬 OTel Collector와 Kubernetes 환경의 특성을 고려해 Logging Architecture를 구성할 수 있습니다.
아키텍쳐는 OTel Collector와 기존의 로그 수집 도구를 혼합해 구성한 Plan A와 OTel Collector만으로 구성한 Plan B로 나눌 수 있습니다.
3-1. Plan A. Promtail + EventExporter + OTel Collector
Plan A는 OTel Collector와 기존의 로그 수집 도구를 혼합한 방식입니다.
Promtail은 Daemonset으로 배포되어 Container Log와 Container가 존재하는 Linux Host의 System Log를 노드에서 수집하는 역할을 합니다.
EventExporter는 단 한개 배포되어 Kubernetes API를 통해 Kubernetes Event Log를 수집합니다.
OTel Collector는 Promtail와 EventExporter에서 수집한 Log들을 받아서 가공하고 Loki로 전송하는 중간다리 역할을 합니다.
Loki는 수집한 로그 데이터들을 수집해 인덱싱한뒤, 보관하는 구성요소입니다.
마지막으로 Grafana에서 Loki의 Log 데이터들을 시각화 및 쿼리해 시스템의 Observability를 확보할 수 있습니다.
EKS, GKE, AKS등의 CSP에서 제공하는 Managed Kubernetes 서비스를 이용할시에는, CSP에서 제공하는 로깅 서비스를 통해 Kubernetes API, Audit Log를 가져와 Grafana로 전송해 확인할 수 있습니다.
위와 같은 내용을 아래 테이블과 같이 정리할 수 있습니다.
Layer | Component | Role | Deploy |
Container level | Promtail | Container Log | Daemonset |
Host level | Promtail | Dataplane, System Log | Daemonset |
Cluster level | Event Exporter | Kubernetes Events Log | Deployment Or Statefulset |
Cluster level | CSP Log Service (Cloudwatch, Cloud Logging ...) | Kubernetes API, Audit Log | None |
None | OTel Collector | Log Processing | Deployment |
Plan A의 장점 및 단점은 다음과 같습니다.
Pros
Logging Architecture의 SPOF(Single Point Of Failure) 방지 가능, 뛰어난 Logging Performance
Cons
Pod IP, 리소스와 같은 자원 사용량이 높음, 설정의 파편화, 네트워크 홉 추가로 인한 트래픽 증가
3-2. Plan B. OTel Collector Only
Plan B는 Logging Architecture를 OTel Collector로만 구성한 방식입니다.
OTel Collector는 Receiver를 통해 다양한 Source에서 Log 데이터를 수집할 수 있는데요.
이 중 Filelog Receiver를 통해 Container와 Sytem Log를, K8SEvent Receiver를 통해 Kubernetes Event를 수집할 수 있습니다.
동시에 Log 데이터를 가공하며 Loki로 가는 중간다리 역할까지 맡을 수 있습니다.
OTel Collector에게 Log 수집과 가공, Backend로 전송하는 역할을 모두 맡기는 방식입니다.
위와 같은 내용을 아래와 같은 테이블로 정리할 수 있습니다.
Layer | Component | Role | Deploy |
Container level | OTel Collector | Container Log | Daemonset |
Host level | OTel Collector | Dataplane, System Log | Daemonset |
Cluster level | OTel Collector | Kubernetes Events Log | Deployment Or Statefulset |
Cluster level | CSP Log Service (Cloudwatch, Cloud Logging ...) | Kubernetes API, Audit Log | None |
None | OTel Collector | Log Processing | Daemonset |
Plan B의 장점 및 단점은 아래와 같습니다.
Pros
설정의 단일화, 효율적인 자원 사용
Cons
로그 수집의 SPOF 발생, 낮은 Logging Performance
4. Opentelemetry Collector Configuration
이제 위에서 언급한 Plan들을 기반으로 Kubernetes 환경에서 OTel Collector를 설정하는 방법에 대해 알아보겠습니다.
4-1. Opentelemetry Operator
OTel Collector를 Kubernetes 환경에서 사용하는 방법은 단순히 OTel Collector Pod를 배포하는 것도 가능하지만, OTel Operator를 이용해서 Operator Pattern으로 Opentelemetry를 사용하는 것도 가능합니다.
Operator Pattern을 사용하면 CR(Custom Resource)를 사용해서 오브젝트를 쉽게 관리할 수 있으므로 OTel Operator를 사용하는 것을 권장합니다.
OTel Operator는 다음과 같이 설치 및 설정이 가능합니다.
1. Helm을 이용해 OTel Operator 설치
1
2
3
4
5
|
$ helm repo add open-telemetry https://open-telemetry.github.io/opentelemetry-helm-charts
$ helm repo update
$ helm install --set admissionWebhooks.certManager.enabled=false --set admissionWebhooks.autoGenerateCert.enabled=true \
opentelemetry-operator open-telemetry/opentelemetry-operator --create-namespace -n otel
|
cs |
OTel Operator의 동작을 위해서는 AdmissionWebHook을 위한 Certification이 필요하기 때문에, 인증서를 생성 및 관리할 수 있는 CertManager가 필요하지만, 본 포스팅에서는 Helm에서 제공하는 autoGenerateCert 기능으로 이를 대체하겠습니다.
2. OpenTelemetryCollector CR 배포
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
$ kubectl apply -f - <<EOF
apiVersion: opentelemetry.io/v1alpha1
kind: OpenTelemetryCollector
metadata:
name: log-collector
spec:
volumeMounts:
- mountPath: /var/log
name: varlog
volumes:
- hostPath:
path: /var/log
name: varlog
mode: daemonset
config: |
...
EOF
|
cs |
OTel Operator를 설치했다면 위와 같이 "OpentelemetryCollector" CR을 통해서 OTel Collector를 배포할 수 있습니다.
3. ClusterRole 수정
1
2
3
4
5
6
7
8
9
10
11
12
13
|
$ kubectl edit clusterrole opentelemetry-operator-manager
...
- apiGroups:
- ""
resources:
- events
verbs:
- list
- get
...
|
cs |
추가적으로 OTel Collector를 통해 Kubernetes Event를 수집하고자 한다면, "events" 리소스를 가져올 수 있도록 Clusterrole을 수정합니다.
4-2. Plan A Configuration
OTel Collector를 배포할 준비가 끝났다면, 이제 위의 Logging Architecture를 구현하기 위한 OTel Collector Configuration을 설정해보겠습니다.
이 중 Promtail + EventExporter + Otel Collector로 이루어진 Plan A를 구현할 수 있는 설정을 구성요소 별로 알아보겠습니다.
Overall Configuration
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
|
exporters:
loki:
endpoint: http://lgtm-loki-distributed-gateway.lgtm.svc.cluster.local/loki/api/v1/push
processors:
k8sattributes:
auth_type: "serviceAccount"
passthrough: false
extract:
metadata:
- k8s.pod.name
- k8s.pod.uid
- k8s.deployment.name
- k8s.statefulset.name
- k8s.daemonset.name
- k8s.namespace.name
- k8s.node.name
- k8s.pod.start_time
- k8s.cluster.uid
pod_association:
- sources:
- from: resource_attribute
name: k8s.pod.name
- from: resource_attribute
name: k8s.namespace.name
resource:
attributes:
- action: insert
key: loki.format
value: raw
- action: insert
key: service.name
from_attribute: k8s.deployment.name
- action: insert
key: service.name
from_attribute: k8s.daemonset.name
- action: insert
key: service.name
from_attribute: k8s.statefulset.name
- action: insert
key: loki.resource.labels
value:
- k8s.container.name
- k8s.namespace.name
- k8s.pod.name
- service.name
batch: {}
memory_limiter:
check_interval: 5s
limit_percentage: 80
spike_limit_percentage: 25
receivers:
loki:
protocols:
http: {}
service:
pipelines:
logs:
exporters:
- loki
processors:
- k8sattributes
- resource
- memory_limiter
- batch
receivers:
- loki
|
cs |
Receiver Configuration
1
2
3
4
|
receivers:
loki:
protocols:
http: {}
|
cs |
Receiver는 Promtail 및 EventExporter로부터 Log 데이터를 받는 진입점을 위한 loki receiver를 사용합니다.
loki receiver를 사용하면 Otel Collector에 기존의 Loki가 노출하는 endpoint를 동일하게 노출시켜 기존의 Log 수집 컴포넌트들이 동일한 방법으로 OTel Collector에 Log를 보낼 수 있도록 구성할 수 있습니다.
Processor Configuration
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
processors:
k8sattributes:
auth_type: "serviceAccount"
passthrough: false
extract:
metadata:
- k8s.pod.name
- k8s.pod.uid
- k8s.deployment.name
- k8s.statefulset.name
- k8s.daemonset.name
- k8s.namespace.name
- k8s.node.name
- k8s.pod.start_time
- k8s.cluster.uid
pod_association:
- sources:
- from: resource_attribute
name: k8s.pod.name
- from: resource_attribute
name: k8s.namespace.name
resource:
attributes:
- action: insert
key: loki.format
value: raw
- action: insert
key: service.name
from_attribute: k8s.deployment.name
- action: insert
key: service.name
from_attribute: k8s.daemonset.name
- action: insert
key: service.name
from_attribute: k8s.statefulset.name
- action: insert
key: loki.resource.labels
value:
- k8s.container.name
- k8s.namespace.name
- k8s.pod.name
- service.name
batch: {}
memory_limiter:
check_interval: 5s
limit_percentage: 80
spike_limit_percentage: 25
|
Processor는 Log에 Kubernetes Attribute를 부착하기 위한 k8sattributes, Loki Label을 구성하기 위한 resource, OOM 방지를 위한 memory_limiter, Log를 batch성으로 전송하기 위한 batch 4개를 사용합니다.
k8sattributes Processor는 filelog로부터 수집한 Container log를 기반으로 이와 일치하는 Pod, Deployment, Cluster 등의 정보를 데이터에 부착합니다.
resource Processor는 위에서 부착한 정보를 Loki의 indexing에 필요한 Label로 변환하는 작업을 수행합니다.
batch와 memory_limiter Processor는 가공한 Log 데이터를 Export하는 방법을 제공합니다.
Exporter Configuration
1
2
3
|
exporters:
loki:
endpoint: http://lgtm-loki-distributed-gateway.lgtm.svc.cluster.local/loki/api/v1/push
|
cs |
Exporter는 Log를 Loki로 전송하기 위한 loki exporter를 사용합니다.
loki의 endpoint 어트리뷰트에 loki 주소의 "/loki/api/v1/push" Path를 붙여 로그 진입점을 값으로 넣어 수집한 Log를 Loki로 전송합니다.
Pipeline Configuration
1
2
3
4
5
6
7
8
9
10
11
12
|
service:
pipelines:
logs:
exporters:
- loki
processors:
- k8sattributes
- resource
- memory_limiter
- batch
receivers:
- loki
|
cs |
마지막으로 위에서 정의한 Receiver, Processor, Exporter를 순서에 맞게 조합하는 Pipeline을 정의합니다.
특히 Processor 요소들의 배치 순서에 따라 Log를 가공하는 순서가 달라지기 때문에, 위의 순서를 준수하는 것이 중요합니다.
Loki Receiver에서 Log 데이터를 수집해 k8sattributes, resource, memory_limiter, batch 순으로 가공한 뒤, Loki Exporter를 사용해 Loki backend로 전송합니다.
결과적으로 아래와 같은 파이프라인이 구성됩니다.
4-3. Plan B Configuration
이번엔 OTel Collector만으로 이루어진 Plan B를 구현할 수 있는 설정을 구성요소 별로 알아보겠습니다.
Processor와 Exporter는 Plan A와 동일하기 때문에 생략합니다.
Overall Configuration
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
|
exporters:
loki:
endpoint: http://lgtm-loki-distributed-gateway.lgtm.svc.cluster.local/loki/api/v1/push
processors:
k8sattributes:
auth_type: "serviceAccount"
passthrough: false
extract:
metadata:
- k8s.pod.name
- k8s.pod.uid
- k8s.deployment.name
- k8s.statefulset.name
- k8s.daemonset.name
- k8s.namespace.name
- k8s.node.name
- k8s.pod.start_time
- k8s.cluster.uid
pod_association:
- sources:
- from: resource_attribute
name: k8s.pod.name
- from: resource_attribute
name: k8s.namespace.name
resource:
attributes:
- action: insert
key: loki.format
value: raw
- action: insert
key: service.name
from_attribute: k8s.deployment.name
- action: insert
key: service.name
from_attribute: k8s.daemonset.name
- action: insert
key: service.name
from_attribute: k8s.statefulset.name
- action: insert
key: loki.resource.labels
value:
- k8s.container.name
- k8s.namespace.name
- k8s.pod.name
- service.name
batch: {}
memory_limiter:
check_interval: 5s
limit_percentage: 80
spike_limit_percentage: 25
receivers:
k8s_events: {}
filelog/syslog:
include:
- /var/log/{dmesg,messages,secure}
start_at: beginning
include_file_name: true
include_file_path: true
retry_on_failure:
enabled: true
operators:
- type: syslog_parser
protocol: rfc3164
parse_to: body
filelog/containerlog:
exclude: []
include:
- /var/log/pods/*/*/*.log
include_file_name: true
include_file_path: true
operators:
- id: get-format
routes:
- expr: body matches "^\\{"
output: parser-docker
- expr: body matches "^[^ Z]+ "
output: parser-crio
- expr: body matches "^[^ Z]+Z"
output: parser-containerd
type: router
- id: parser-crio
regex: ^(?P<time>[^ Z]+) (?P<stream>stdout|stderr) (?P<logtag>[^ ]*) ?(?P<log>.*)$
timestamp:
layout: 2006-01-02T15:04:05.999999999Z07:00
layout_type: gotime
parse_from: attributes.time
type: regex_parser
- combine_field: attributes.log
combine_with: ""
id: crio-recombine
is_last_entry: attributes.logtag == 'F'
max_log_size: 102400
output: extract_metadata_from_filepath
source_identifier: attributes["log.file.path"]
type: recombine
- id: parser-containerd
regex: ^(?P<time>[^ ^Z]+Z) (?P<stream>stdout|stderr) (?P<logtag>[^ ]*) ?(?P<log>.*)$
timestamp:
layout: '%Y-%m-%dT%H:%M:%S.%LZ'
parse_from: attributes.time
type: regex_parser
- combine_field: attributes.log
combine_with: ""
id: containerd-recombine
is_last_entry: attributes.logtag == 'F'
max_log_size: 102400
output: extract_metadata_from_filepath
source_identifier: attributes["log.file.path"]
type: recombine
- id: parser-docker
output: extract_metadata_from_filepath
parse_from: attributes["log.file.path"]
regex: ^.*\/(?P<namespace>[^_]+)_(?P<pod_name>[^_]+)_(?P<uid>[a-f0-9\-]+)\/(?P<container_name>[^\._]+)\/(?P<restart_count>\d+)\.log$
type: regex_parser
- from: attributes.stream
to: attributes["log.iostream"]
type: move
- from: attributes.container_name
to: resource["k8s.container.name"]
type: move
- from: attributes.namespace
to: resource["k8s.namespace.name"]
type: move
- from: attributes.pod_name
to: resource["k8s.pod.name"]
type: move
- from: attributes.restart_count
to: resource["k8s.container.restart_count"]
type: move
- from: attributes.uid
to: resource["k8s.pod.uid"]
type: move
- from: attributes.log
to: body
type: move
retry_on_failure:
enabled: true
start_at: end
service:
pipelines:
logs:
exporters:
- loki
processors:
- k8sattributes
- resource
- memory_limiter
- batch
receivers:
- filelog/containerlog
- filelog/syslog
- k8s_events
|
cs |
Receiver Configuration
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
|
receivers:
k8s_events: {}
filelog/syslog:
include:
- /var/log/{dmesg,messages,secure}
start_at: beginning
include_file_name: true
include_file_path: true
retry_on_failure:
enabled: true
operators:
- type: syslog_parser
protocol: rfc3164
parse_to: body
filelog/containerlog:
exclude: []
include:
- /var/log/pods/*/*/*.log
include_file_name: true
include_file_path: true
operators:
- id: get-format
routes:
- expr: body matches "^\\{"
output: parser-docker
- expr: body matches "^[^ Z]+ "
output: parser-crio
- expr: body matches "^[^ Z]+Z"
output: parser-containerd
type: router
- id: parser-crio
regex: ^(?P<time>[^ Z]+) (?P<stream>stdout|stderr) (?P<logtag>[^ ]*) ?(?P<log>.*)$
timestamp:
layout: 2006-01-02T15:04:05.999999999Z07:00
layout_type: gotime
parse_from: attributes.time
type: regex_parser
- combine_field: attributes.log
combine_with: ""
id: crio-recombine
is_last_entry: attributes.logtag == 'F'
max_log_size: 102400
output: extract_metadata_from_filepath
source_identifier: attributes["log.file.path"]
type: recombine
- id: parser-containerd
regex: ^(?P<time>[^ ^Z]+Z) (?P<stream>stdout|stderr) (?P<logtag>[^ ]*) ?(?P<log>.*)$
timestamp:
layout: '%Y-%m-%dT%H:%M:%S.%LZ'
parse_from: attributes.time
type: regex_parser
- combine_field: attributes.log
combine_with: ""
id: containerd-recombine
is_last_entry: attributes.logtag == 'F'
max_log_size: 102400
output: extract_metadata_from_filepath
source_identifier: attributes["log.file.path"]
type: recombine
- id: parser-docker
output: extract_metadata_from_filepath
parse_from: attributes["log.file.path"]
regex: ^.*\/(?P<namespace>[^_]+)_(?P<pod_name>[^_]+)_(?P<uid>[a-f0-9\-]+)\/(?P<container_name>[^\._]+)\/(?P<restart_count>\d+)\.log$
type: regex_parser
- from: attributes.stream
to: attributes["log.iostream"]
type: move
- from: attributes.container_name
to: resource["k8s.container.name"]
type: move
- from: attributes.namespace
to: resource["k8s.namespace.name"]
type: move
- from: attributes.pod_name
to: resource["k8s.pod.name"]
type: move
- from: attributes.restart_count
to: resource["k8s.container.restart_count"]
type: move
- from: attributes.uid
to: resource["k8s.pod.uid"]
type: move
- from: attributes.log
to: body
type: move
retry_on_failure:
enabled: true
start_at: end
|
cs |
Receiver는 Container Log 수집을 위한 filelog, System Log 수집을 위한 filelog, Kubernetes Event Log 수집을 위한 k8s_event 3개를 사용합니다.
Container Log는 filelog receiver로 /var/log/pods/*/*/*.log 경로에서 수집하고, 수집한 파일들을 기반으로 Path 및 Body를 분석해 Container 명, Pod 명, Namespace 명 등의 정보를 추출합니다.
System Log는 별도의 filelog receiver로 /var/log 경로에서 수집한 dmesg, messages, secure 파일들에서 syslog_parser로 정보를 추출해 수집합니다.
Kubernetes Event Log는 k8s_event receiver를 이용해 Kubernetes API로부터 수집합니다.
Pipeline Configuration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
service:
pipelines:
logs:
exporters:
- loki
processors:
- k8sattributes
- resource
- memory_limiter
- batch
receivers:
- filelog/containerlog
- filelog/syslog
- k8s_events
|
cs |
filelog, k8s_events Receiver에서 Log 데이터를 수집해k8sattributes, resource, memory_limiter, batch순으로 가공한 뒤, Loki Exporter를 사용해 Loki backend로 전송합니다.
결과적으로 아래와 같은 파이프라인을 구성하게 됩니다.
4-4. Configuration Results
위의 설정을 사용한 Log 데이터의 가공 결과는 아래와 같습니다.
Loki는 Label 필드를 사용해서 Indexing을 수행하기 때문에, 어떤 Attribute를 Label로 사용할지가 성능에 큰 영향을 미칩니다.
따라서 위의 Attribute를 그대로 사용할 필요 없이 Loki를 구성하는 상황에 맞게 Label을 지정하면 되겠습니다.
이 중 service.name 필드는 추후 구성할 Trace와의 Correlation에 중요한 역할을 하기 때문에 꼭 필요한 Label입니다.
5. 마무리
Opentelemetry Collector는 Kubernetes 환경에서 Log, Metric, Trace 데이터를 수집, 가공 및 관리하는 벤더 중립적인 Observability 프레임워크입니다.
Kubernetes에서 로그를 수집할 때는 컨테이너, 호스트, 클러스터 레벨에서 다양한 로그를 수집해야 하며, OTel Collector는 이를 위한 Receiver, Processor, Exporter 설정을 통해 로그를 수집, 가공, 전송할 수 있습니다.
이번 포스팅에서 OTel Collector를 이용해 다양한 컴포넌트를 조합한 아키텍쳐와 OTel Collector만을 이용한 아키텍쳐 2개를 살펴봤으며, 이를 구현하기 위한 설정을 구성해봤습니다.
이 포스팅을 보는 분들이 Opentelemetry를 활용해 Logging 아키텍쳐를 수립하는데 도움이 되기를 바랍니다.
'Observability' 카테고리의 다른 글
Opentelemetry로 Kubernetes Observability 확보하기 : Tracing (0) | 2024.04.30 |
---|---|
Opentelemetry로 Kubernetes Observability 확보하기 : Monitoring (1) | 2024.03.24 |
Prometheus Operator를 사용해 Kubernetes 환경에서 Prometheus 구성하기 (8) | 2022.03.31 |
트레이싱 관측 도구 Grafana Tempo로 트레이스를 관측해보자 (0) | 2022.01.27 |
Elasticsearch에 fluentd를 얹은 EFK stack 구축하기(with kubernetes) (9) | 2022.01.08 |