15. 로그 관리방법(Fluentd, Elasticsearch 활용)

도커에서는 로그 라이브러리를 사용한다 해도 로그를 파일이 아닌 표준 출력으로 출력하고 이를 Fluentd 같은 로그 컬렉터로 수집하는 경우가 많다. 이를 활용할 경우 어플리케이션 쪽에서 로그 로테이션을 할 필요가 없으며 로그 전송을 돕는 로깅 드라이버 기능도 갖추고 있으므로 로그 수집이 편리하다.
로그 로테이션 : 로그로테이션이란 일정시간 주기로 원본 로그 파일을 다른 이름으로 복사하고, 로그 파일을 truncate 해서 새로 로그를 쌓게 하는 일련의 과정을 말한다. 일정 시간이 지난 로그파일은 알아서 삭제를 해주기 때문에 로그파일이 계속 커지는 것을 막을 수 있다. 참고:https://www.joinc.co.kr/w/man/12/logrotate

도커의 로깅 드라이버

도커 컨테이너의로그는 JSON 포멧으로 출력되는데 이는 도커에 json-file이라는 기본 로깅 드라이버가 있기 때문이다. json-file 외에도 다양한 것들을 사용할 수 있다. 도커 로그는 fluentd를 사용하는것이 정석이며 퍼블릭 클라우드에서 도커를 사용하는 경우에는 awslogs나 gcplogs를 많이 사용한다.

Syslog : syslog로 로그를 관리
Journald : systemd로 로그 관리
Awslogs : AWS CloudWatch Logs로 로그를 전송
Gcplogs : Google Cloud Logging으로 로그 전송
Fluentd : fluentd로 로그를 관리.

컨테이너에서 로그 관리

컨테이너가 아닐 때 어플리케이션을 당연히 로그를 파일에 출력한다. 이 방법은 컨테이너에 적용하기에는 어려움이 있다. 장애로 인해 컨테이너가 날라갈경우 로그까지 날라가는 경우가 생길 수 있다. 도커처럼 로그를 표준 출력으로 남기면 로그가 호스트에 위치한 파일에 남는다. 물론 컨테이너에 공유한 볼륨에 파일로 로그를 남기는것도 가능하지만 로그는 표준 출력으로 남기고 이 내용을 호스트에서 파일에 수집하는것이 더 간단하며 이것이 도커에서는 정석으로 여겨진다.

쿠버네티스에서 로그 관리

컨테이너에서는 표준 출력으로만 로그를 내보내면 되고 이에 대한 처리는 컨테이너 외부에서 이뤄진다.

로컬 쿠버네티스 환경에 Elashticsearch와 kibana를 구축한 다음 로그를 전송할 fluentd와 DaemonSet을 구축해보자.

Kibana: 로그 열람기능 활용.
Fluentd: 로그 수집기. Elasticsearch로 전송하는 기능을 할 용도
Elasticsearch : JVM에서 동작하는 풀텍스트 검색 엔진. fluentd로 수집한 로그를 검색하는 용도로도 사용할 수 있다. fluentd+Elasticsearch는 로그 관리에서 단골로 사용된다.

로컬에서 Elashticsearch와 Kibana 구축하기

kube-system 네임스페이스에 Elashticsearch를 구축해보자.
해당 네임스페이스는 쿠버네티스 클러스터의 기본 네임스페이스로 코어 컴포넌트가 배치된다. 로그는 네임스페이스를 뛰어넘어 Elashticsearch에 모여야 로그 검색에 편리하므로 Elashticsearch를 kube-system 네임스페이스에 배치한다.

1.엘라스틱서치 설치
PVC, 서비스, 디플로이먼트, 컨피그맵 등의 매니페스트를 포함한 다음 파일을 생성한다.

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
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: elasticsearch-pvc
namespace: kube-system
labels:
kubernetes.io/cluster-service: "true"
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 2G

---
apiVersion: v1
kind: Service
metadata:
name: elasticsearch
namespace: kube-system
spec:
selector:
app: elasticsearch
ports:
- protocol: TCP
port: 9200
targetPort: http

---
apiVersion: apps/v1
kind: Deployment
metadata:
name: elasticsearch
namespace: kube-system
labels:
app: elasticsearch
spec:
replicas: 1
selector:
matchLabels:
app: elasticsearch
template:
metadata:
labels:
app: elasticsearch
spec:
containers:
- name: elasticsearch
image: elasticsearch:5.6-alpine
ports:
- containerPort: 9200
name: http
volumeMounts:
- mountPath: /data
name: elasticsearch-pvc
- mountPath: /usr/share/elasticsearch/config
name: elasticsearch-config
volumes:
- name: elasticsearch-pvc
persistentVolumeClaim:
claimName: elasticsearch-pvc
- name: elasticsearch-config
configMap:
name: elasticsearch-config

---
kind: ConfigMap
apiVersion: v1
metadata:
name: elasticsearch-config
namespace: kube-system
data:
elasticsearch.yml: |-
http.host: 0.0.0.0
path.scripts: /tmp/scripts

log4j2.properties: |-
status = error

appender.console.type = Console
appender.console.name = console
appender.console.layout.type = PatternLayout
appender.console.layout.pattern = [%d{ISO8601}][%-5p][%-25c{1.}] %marker%m%n

rootLogger.level = info
rootLogger.appenderRef.console.ref = console

jvm.options: |-
-Xms128m
-Xmx256m
-XX:+UseConcMarkSweepGC
-XX:CMSInitiatingOccupancyFraction=75
-XX:+UseCMSInitiatingOccupancyOnly
-XX:+AlwaysPreTouch
-server
-Xss1m
-Djava.awt.headless=true
-Dfile.encoding=UTF-8
-Djna.nosys=true
-Djdk.io.permissionsUseCanonicalPath=true
-Dio.netty.noUnsafe=true
-Dio.netty.noKeySetOptimization=true
-Dio.netty.recycler.maxCapacityPerThread=0
-Dlog4j.shutdownHookEnabled=false
-Dlog4j2.disable.jmx=true
-Dlog4j.skipJansi=true
-XX:+HeapDumpOnOutOfMemoryError

2.키바나 설치

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
apiVersion: v1
kind: Service
metadata:
name: kibana
namespace: kube-system
spec:
selector:
app: kibana
ports:
- protocol: TCP
port: 5601
targetPort: http
nodePort: 30050
type: NodePort

---
apiVersion: apps/v1
kind: Deployment
metadata:
name: kibana
namespace: kube-system
labels:
app: kibana
spec:
replicas: 1
selector:
matchLabels:
app: kibana
template:
metadata:
labels:
app: kibana
spec:
containers:
- name: kibana
image: kibana:5.6
ports:
- containerPort: 5601
name: http
env:
- name: ELASTICSEARCH_URL
value: "http://elasticsearch:9200"

kubectl get pod -n kube-system 으로 파드들이 잘 돌고있나 확인해보자

localhost:30050으로 접속하여 키바나가 제대로 동작하고 있는지 보자.
나의경우 localhost:30050 으로 접속이 되질 않아 minikube service kibana --url -n kube-system 명령어를 통해 주소를 알아냈다.

3.DaemonSet로 fluentd 구축하기
DeamonSet은 파드를 관리하는 리소스로 모든 노드에 하나씩 배치된다. 로그 컬렉터같이 호스트마다 특정할 역할을 하는 에이전트를 두고자 할 때 적합하다.

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
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluentd
namespace: kube-system
labels:
app: fluentd-logging
version: v1
kubernetes.io/cluster-service: "true"
spec:
selector:
matchLabels:
app: fluentd-logging
template:
metadata:
labels:
app: fluentd-logging
version: v1
kubernetes.io/cluster-service: "true"
spec:
tolerations:
- key: node-role.kubernetes.io/master
effect: NoSchedule
containers:
- name: fluentd
image: fluent/fluentd-kubernetes-daemonset:elasticsearch
env:
- name: FLUENT_ELASTICSEARCH_HOST
value: "elasticsearch"
- name: FLUENT_ELASTICSEARCH_PORT
value: "9200"
- name: FLUENT_ELASTICSEARCH_SCHEME
value: "http"
- name: FLUENT_UID
value: "0"
resources:
limits:
memory: 200Mi
requests:
cpu: 100m
memory: 200Mi
volumeMounts:
- name: varlog
mountPath: /var/log
- name: varlibdockercontainers
mountPath: /var/lib/docker/containers
readOnly: true
terminationGracePeriodSeconds: 30
volumes:
- name: varlog
hostPath:
path: /var/log
- name: varlibdockercontainers
hostPath:
path: /var/lib/docker/containers

위를 apply 하자. 이는 fluent/fluentd-kubernetes-daemonset:elasticsearch 이미지를 사용한다.
로그를 전달받을 Elasticsearch의 주소를 환경변수에 설정하고 데이터나 로그가 저장될 위치인 /var/lib/containers에 볼륨을 마운트한 다음 fluentd컨테이너에서 로그를 받아간다.

책에서 나온 예제를 그대로 따라하면 fluentd가 Crash났다고 뜬다.
로그를 찾아 구글링 해보니 환경변수에 FLUENT_UID / 0 을 추가하라고 해서 해결함.

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
apiVersion: v1
kind: Service
metadata:
name: echo
spec:
selector:
app: echo
ports:
- protocol: TCP
port: 80
targetPort: http
nodePort: 30080
type: NodePort

---
apiVersion: apps/v1
kind: Deployment
metadata:
name: echo
labels:
app: echo
spec:
replicas: 1
selector:
matchLabels:
app: echo
template:
metadata:
labels:
app: echo
spec:
containers:
- name: nginx
image: gihyodocker/nginx:latest
env:
- name: BACKEND_HOST
value: localhost:8080
- name: LOG_STDOUT
value: "true"
ports:
- name: http
containerPort: 80
- name: echo
image: gihyodocker/echo:latest
ports:
- containerPort: 8080

위의 파일을 만들고 apply하자.
minikube service echo –url
나온 주소로 curl 을 보내보자

로그 확인

kibana로 접속하여 인덱스 패턴을 logstash-*로 생성한다. 이는 fluent/fluentd-kubernetes-daemonset 이미지의 기본 설정에 따라 생성된것이다.

discover에서 curl보낸것에 대한 로그들을 볼 수 있다. echo 컨테이너에서 어플리케이션이 출력한 log 필드에 등록되며 그 외 레이블, 파드명, 컨테이너 명등을 볼 수 있다.

도커 쿠버네티스 로그 관리 원칙

어플리케이션 로그는 모두 표준 출력으로 출력한다.
nginx 등의 미들웨어는 로그가 표준 출력으로 출력되도록 이미지를 빌드한다.
표준 출력으로 출력으로 출력되는 로그는 모두 JSON포멧으로 출력해 각 속성을 검색할 수 있게 한다.
쿠버네티스 환경에서는 fluentd/fluentd-kubernetes-daemonset를 포함하는 파드를 DaemonSet를 사용해 각 호스트에 배포한다.
쿠버네티스 리소스에는 적절히 레이블을 부여해 로그를 검색할 수 있게한다.

Share