Published on

OpenTelmetry로 auth.log와 syslog 수집하기

Authors
  • avatar
    Name
    Jay
    Twitter

OpenTelemetry Collector를 사용해서 Ubuntu 서버의 /var/log/auth.log/var/log/syslog를 수집하고 싶었다.

환경

  • Ubuntu 20.04
  • opentelemetry-collector Chart version 0.90.0

Filelog Receiver

Filelog Receiver로 log file을 tail하고 parse를 할 수 있다. 따라서 audit.log, syslog 파일들을 filelog로 수집하면 되겠다는 생각을 하였다. 따라서 아래와 같이 value값을 설정하여 Helm으로 배포하였다.

values.yaml

config:
	receivers:
		jaeger: null
		prometheus: null
		zipkin: null
		filelog:
			exclude: []
			include:
			- /var/log/auth.log
			- /var/log/syslog
			include_file_name: false
			include_file_path: true
			operators:
			- id: parser-host-log
				regex: ^(?P<time>^[^ Z]+) (?P<hostname>[^ ]*) (((?P<service>[^ ]*)\[(?P<pid>.*)\]|(?P<command>[^ ]*)):) (?P<message>.*)$
				timestamp:
					layout: 2006-01-02T15:04:05.999999999Z07:00
					layout_type: gotime
					parse_from: attributes.time
				type: regex_parser
			- type: add
				field: attributes.container
				value: host
			poll_interval: 5s
			start_at: end
	service:
		telemetry:
			logs:
				level: info
		pipelines:
			traces:
				receivers:
					- otlp
			metrics:
				receivers:
					- otlp
			logs:
				receivers:
					- filelog
				exporters:
					- debug
	exporters:
		debug:
			verbosity: detailed
ports:
	jaeger-compact:
		enabled: false
	jaeger-thrift:
		enabled: false
	jaeger-grpc:
		enabled: false
	zipkin:
		enabled: false
mode: daemonset
image:
	repository: otel/opentelemetry-collector-k8s
extraVolumeMounts:
- mountPath: /var/log/
	name: varlog
	readOnly: true
extraVolumes:
- hostPath:
		path: /var/log/
	name: varlog
presets:
	logsCollection:
		enabled: true
		includeCollectorLogs: true

opentelmetry-collector Chart template를 보면, logsCollection.enabled이 true로 설정했을 때 아래와 같이 hostPath가 추가되는 것을 알 수 있다.

# VolumeMounts
{{- if .Values.presets.logsCollection.enabled }}
- name: varlogpods
	mountPath: /var/log/pods
	readOnly: true
- name: varlibdockercontainers
	mountPath: /var/lib/docker/containers
	readOnly: true
{{- if .Values.presets.logsCollection.storeCheckpoints}}
- name: varlibotelcol
	mountPath: /var/lib/otelcol
{{- end }}
{{- end }}

# Volumes
{{- if .Values.presets.logsCollection.enabled }}
- name: varlogpods
	hostPath:
		path: /var/log/pods
{{- if .Values.presets.logsCollection.storeCheckpoints}}
- name: varlibotelcol
	hostPath:
		path: /var/lib/otelcol
		type: DirectoryOrCreate
{{- end }}
- name: varlibdockercontainers
	hostPath:
		path: /var/lib/docker/containers
{{- end }}

Host의 /var/log 아래에 있는 auth.logsyslog를 접근하기 위해서는 hostPath를 추가해줘야 한다. 따라서 Helm values에 아래와 같이 추가한다.

extraVolumeMounts:
- mountPath: /var/log/
	name: varlog
	readOnly: true
extraVolumes:
- hostPath:
		path: /var/log/
	name: varlog

그런데 Ubuntu server에서 실행되는 rsyslog가 남기는 파일 권한이 640으로 되어있다. 따라서 hostPath를 사용해도 파일들을 읽을 권한이 없어서 에러가 발생한다. 따라서 opentelemetry collector agent에 에러 로그가 남는 것을 확인할 수 있다. rsyslog가 파일을 생성할 때, rsyslog.conf$FileCreateMode을 수정하여 644로 조정하였다.

/etc/rsyslog.conf

#
# Set the default permissions for all log files.
#
$FileOwner syslog
$FileGroup adm
$FileCreateMode 0644 # 640에서 변경
$DirCreateMode 0755
$Umask 0022
$PrivDropToUser syslog
$PrivDropToGroup syslog

rsyslog를 재시작하고 나면 이제 새로 만들어지는 파일에 대해서 변경된 권한이 적용된다. 이렇게 변경된 권한의 파일을 opentelemetry collector agent가 정상적으로 읽을 수 있게 된다.

systemctl daemon-reload
systemctl restart rsyslog

그리고 log message의 format을 보면 아래와 같이 기본 설정이 되어 있었다.

May  1 20:33:04 hostname sshd[2692917]: pam_tally2(sshd:setcred): unknown option: reset

May 1 20:33:04 값이 연도 숫자가 없고, Filelog의 operator를 통해서 parse하는데도 어려움이 있었다. syslog.conf를 확인하면 아래와 같이 rsyslog에서 기본적으로 제공하는 template중에 하나가 선택되어 있다.

/etc/rsyslog.conf

$ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat

날짜형식을 rfc3339으로 하는 template을 아래와 같이 설정하고, rsyslog를 다시 재시작한다.

$ActionFileDefaultTemplate RSYSLOG_FileFormat

변경이 적용되면 아래와 같이 log message가 남게 된다.

2024-05-01T23:04:35.590363+09:00 hostname systemd: pam_tally2(systemd-user:setcred): unknown option: reset

그래서 이제 regex_parser를 통해서 log message를 정규표현식에 맞춰서 parse한다. time group값을 통해서 timestamp를 생성한다. gotime과 strptime type이 있고 layout은 이 문서를 확인해서 작성한다.

- id: parser-host-log
	regex: ^(?P<time>^[^ Z]+) (?P<hostname>[^ ]*) (((?P<service>[^ ]*)\[(?P<pid>.*)\]|(?P<command>[^ ]*)):) (?P<message>.*)$
	timestamp:
		layout: 2006-01-02T15:04:05.999999999Z07:00
		layout_type: gotime
		parse_from: attributes.time
	type: regex_parser

debug exporter를 설정하여 standout으로 출력되는 값을 확인해본다.

exporters:
	debug:
		verbosity: detailed

이제 정상적으로 아래와 같이 opentelmetry collector agent에 로그가 남는다.

kubectl logs -f -l component=agent-collector
LogRecord #6
ObservedTimestamp: 2024-05-01 15:12:16.671281334 +0000 UTC
Timestamp: 2024-05-01 15:12:14.30976 +0000 UTC
SeverityText:
SeverityNumber: Unspecified(0)
Body: Str(2024-05-02T00:12:14.309760+09:00 hostname sshd[2727770]: pam_tally2(sshd:setcred): unknown option: reset)
Attributes:
     -> command: Str()
     -> message: Str(pam_tally2(sshd:setcred): unknown option: reset)
     -> container: Str(host)
     -> log.file.path: Str(/var/log/auth.log)
     -> time: Str(2024-05-02T00:12:14.309760+09:00)
     -> hostname: Str(hostname)
     -> service: Str(sshd)
     -> pid: Str(2727770)
Trace ID:
Span ID:
Flags: 0
	{"kind": "exporter", "data_type": "logs", "name": "debug"}

Syslog Receiver

그런데 Receiver 종류를 보다가 Syslog Receiver도 있는 것을 알게 되었다. 그리고 rsyslog 모듈인 omfwd를 통해서 rsyslog가 opentelemetry syslog receiver에 로그를 보낼 수 있는 것을 알게 되었다. SigNoz의 문서에서 rsyslog 설정하는 방법을 잘 설명하고 있다.

이번에는 아래와 같이 values.yaml을 작성하고 Helm Chart를 배포한다.

values.yaml

config:
	receivers:
		jaeger: null
		prometheus: null
		zipkin: null
		syslog:
			tcp:
				listen_address: "localhost:4319"
			protocol: rfc3164
			location: UTC
			operators:
				- type: move
					from: attributes.message
					to: body
				- type: add
                  field: attributes.source
                  value: syslog
	service:
		telemetry:
			logs:
				level: info
		pipelines:
			traces:
				receivers:
					- otlp
			metrics:
				receivers:
					- otlp
			logs:
				receivers:
					- syslog
				exporters:
					- debug
	exporters:
		debug:
			verbosity: detailed
ports:
	jaeger-compact:
		enabled: false
	jaeger-thrift:
		enabled: false
	jaeger-grpc:
		enabled: false
	zipkin:
		enabled: false
	syslog:
		enabled: true
		containerPort: 4319
		hostPort: 4319
		protocol: TCP
mode: daemonset
image:
	repository: otel/opentelemetry-collector-contrib
hostNetwork: true
presets:
	logsCollection:
		enabled: false
		includeCollectorLogs: true

syslog receiver를 사용하기 위해서 image.repositoryotel/opentelemetry-collector-contrib로 설정하였다. 그리고 listen_address0.0.0.0:4319로 설정했는데, rsyslog가 host에서 해당 4319 포트로 접근할 수 있도록 hostPort를 사용하도록 설정하였다.

hostPort를 설정하였기 때문에, opentelemetry collector의 설정을 보면 4319/TCP가 설정된 것을 확인할 수 있다.

$ kubectl describe pod opentelemetry-collector-agent-b7g2j
...생략
Ports:         4317/TCP, 4318/TCP, 4319/TCP
Host Ports:    4317/TCP, 4318/TCP, 4319/TCP

그리고 Cilium을 CNI로 사용할 때, 아래와 같이 service의 목록을 보면 HostPort가 설정된 것을 확인할 수 있다.

$ kubectl exec -it cilium-gxdr7 -n kube-system -- cilium service list
...생략
196   10.50.3.14:4317         HostPort       1 => 198.18.4.92:4317 (active)
197   0.0.0.0:4317           HostPort       1 => 198.18.4.92:4317 (active)
198   10.50.3.14:4318         HostPort       1 => 198.18.4.92:4318 (active)
199   0.0.0.0:4318           HostPort       1 => 198.18.4.92:4318 (active)
200   10.50.3.14:4319        HostPort       1 => 198.18.4.92:4319 (active)
201   0.0.0.0:4319          HostPort       1 => 198.18.4.92:4319 (active)

이제 SigNoz의 문서에서 rsyslog 설정하는 방법에 나온 것처럼 rsyslog 설정을 아래와 같이 추가한다.

/etc/rsyslog.conf

template(
  name="UTCTraditionalForwardFormat"
  type="string"
  string="<%PRI%>%TIMESTAMP:::date-utc% %HOSTNAME% %syslogtag:1:32%%msg:::sp-if-no-1st-sp%%msg%"
)

*.* action(type="omfwd" target="127.0.0.1" port="4319" protocol="tcp" template="UTCTraditionalForwardFormat")

이제 rsyslog를 재시작하고 나면 Syslog Receiver로 로그를 받은 것을 확인할 수 있다.

2024-05-01T15:01:34.346Z	info	ResourceLog #0
Resource SchemaURL:
ScopeLogs #0
ScopeLogs SchemaURL:
InstrumentationScope
LogRecord #0
ObservedTimestamp: 2024-05-01 15:01:34.219612783 +0000 UTC
Timestamp: 2024-05-01 15:01:34 +0000 UTC
SeverityText: err
SeverityNumber: Error(17)
Body: Str(pam_tally2(sshd:setcred): unknown option: reset)
Attributes:
     -> priority: Int(83)
     -> facility: Int(10)
     -> hostname: Str(hostname)
     -> appname: Str(sshd)
     -> proc_id: Str(2725522)
Trace ID:
Span ID:
Flags: 0
	{"kind": "exporter", "data_type": "logs", "name": "debug"}

결론

OpenTelmetry에서 Syslog Receiver가 Alpha 상태로 제공되고 있다. rsyslog에서 omfwd 모듈을 통해서 syslog message를 Syslog Receiver로 보낼수가 있다. 그렇게 하기 위해서 rsyslog.conf 설정값을 변경하여 rsyslog를 재시작해야 한다. 처음에는 Syslog Receiver로 쉽게 받을 수 있는 것을 생각하지 못하고, Filelog를 통해서 받도록 설정하였다. 이 과정에서 rsyslog가 file을 생성할 때 644로 권한설정하도록 설정하고, opentelemetry collector agent가 hostPath를 통해서 /var/log에 접근하도록 하였다. 하지만 이 과정에서 Regex rule을 올바르게 작성하는데 어려움이 있었고, 잘못된 설정들로 인해서 최종적으로 원하는 동작을 확인하기까지 시간이 꽤 걸렸다.🥺