Kubernetes - Kubernetes 로깅 운영(logging), Fluentd
오늘 다루어볼 내용은 쿠버네티스 환경에서의 로깅운영 방법이다. 지금까지는 쿠버네티스에 어떻게 팟을 띄우는지에 대해 집중했다면 오늘 포스팅 내용은 운영단계의 내용이 될 것 같다. 사실 어떻게 보면 가장 중요한 내용중에 하나라고 볼 수 있는 것이 로깅이다. 물리머신에 웹을 띄울 때는 파일로 로그를 날짜별로 남기고, 누적 일수이상된 파일은 제거 혹은 다른 곳으로 파일을 옮기는 등의 작업을 했을 것이다. 하지만 쿠버네티스에서는 파일로 로그를 남기지 않으며 조금 다른 방법으로 로깅운영을 진행한다.
컨테이너 환경에서 로그를 운영하는 구체적인 방법을 설명하기 전에 컨테이너 환경에서 로그가 어떻게 생성되는지 알아본다. 비컨테이너 환경의 애플리케이션에서는 보통 로그를 파일로 많이 남기고 한다. 이에 비해 도커에서는 로그를 파일이 아닌 표준 출력으로 출력하고 이를 다시 Fluentd 같은 로그 컬렉터로 수집하는 경우가 많다. 이런 방법은 애플리케이션 쪽에서 로그 로테이션이 필요없으며 로그 전송을 돕는 로깅 드라이버 기능도 갖추고 있으므로 로그 수집이 편리하다.
간단하게 필자가 만든 이미지로 실습을 진행한다.
> docker pull 1223yys/springboot-web:0.2.5
> docker container run -it --rm -p 8080:8080 1223yys/springboot-web:0.2.5
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.2.1.RELEASE)
2020-02-23 11:29:15.368 INFO 1 --- [ main] com.kebe.sample.SampleApplication : Starting SampleApplication on d1e5a6d24e34 with PID 1 (/app/app.jar started by root in /)
2020-02-23 11:29:15.375 INFO 1 --- [ main] com.kebe.sample.SampleApplication : No active profile set, falling back to default profiles: default
2020-02-23 11:29:17.099 INFO 1 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2020-02-23 11:29:17.122 INFO 1 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2020-02-23 11:29:17.124 INFO 1 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.27]
2020-02-23 11:29:17.244 INFO 1 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2020-02-23 11:29:17.245 INFO 1 --- [ main] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 1784 ms
2020-02-23 11:29:17.619 INFO 1 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2020-02-23 11:29:17.885 INFO 1 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2020-02-23 11:29:17.890 INFO 1 --- [ main] com.kebe.sample.SampleApplication : Started SampleApplication in 3.221 seconds (JVM running for 3.936)
우선 이미지를 내려받고 애플리케이션을 포어그라운드로 실행시킨다. 그 다음 로그가 호스트에서 어떻게 출력되는지 확인해보자.
그전에 아래의 명령으로 실행 중인 컨테이너들의 로그가 어떻게 찍히는 지 확인할 수 있다.
> docker run -it --rm -v /var/lib/docker/containers:/json-log alpine ash
명령 실행 후에 /json-log 디렉토리에 위에서 실행한 컨테이너의 아이디 디렉토리로 들어가면 현재 우리가 표준출력으로 찍고 있는 로그가 json 타입으로 찍히고 있으며 이 표준출력이 파일로 남고 있는 것을 볼 수 있다.
다시 말해, 애플리케이션에서 로그를 파일로 출력하지 않았더라도 도커에서 컨테이너의 표준 출력을 로그로 출력해주는 것이다. 따라서 로그 출력 자체를 완전히 도커에 맡길 수 있다.
도커 로깅 드라이버
도커 컨테이너의 로그가 JSON 포맷으로 출력되는 이유는 도커에 json-file이라는 기본 로깅 드라이버가 있기 때문이다. 로깅 드라이버는 도커 컨테이너가 출력하는 로그를 어떻게 다룰지를 제어하는 역할을 한다. json-file 외에도 다음과 같은 로깅 드라이버가 존재한다.
logging driver | description |
Syslog | syslog로 로그를 관리 |
Journald | systemd로 로그를 관리 |
Awslogs | AWS CloudWatch Logs로 로그를 전송 |
Gcplogs | Google Cloud Logging으로 로그를 전송 |
Fluentd | fluentd로 로그를 관리 |
도커 로그는 fluentd를 사용해 수집하는 것이 정석이다.
컨테이너 로그의 로테이션
애플리케이션에서 표준 출력으로 출력하기만 해도 로그를 파일에 출력할 수 있지만, 웹 애플리케이션처럼 컨테이너 업타임이 길거나 액세스 수에 비례해 로그 출력량이 늘어나는 경우에는 JSON 로그 파일 크기가 점점 커진다. 컨테이너를 오랜 시간 운영하려면 이 로그를 적절히 로테이션할 필요가 있다.
도커 컨테이너에는 로깅 동작을 제어하는 옵션인 --log-opt가 있어서 이 옵션으로 도커 컨테이너의 로그 로테이션을 설정할 수 있다. max-size는 로테이션이 발생하는 로그 파일 최대 크기이며 l/m/g 단위로 파일 크기 지정이 가능하다. max-file은 최대 파일 개수를 의미하며 파일 개수가 이 값을 초과할 경우 오래된 파일부터 삭제된다.
docker container run -it --rm -p 8080:8080 --log-opt max-size=1m --log-opt max-file=5 1223yys/springboot-web:0.2.5
이 설정을 매번 컨테이너 실행마다 할 필요는 없고, 도커 데몬에서 log-opt를 기본값으로 설정할 수 있다. Preference 화면에서 Deamon > Advanced 항목에서 다음과 같이 JSON 포맷으로 설정할 수 있다.
{
"experimental" : false,
"debug" : true,
"log-driver": "json-file",
"log-opts": {
"max-size": "1m",
"max-file": "5"
}
}
쿠버네티스에서 로그 관리하기
다른 예제는 뛰어넘고, 바로 쿠버네티스 환경에서 로그관리하는 방법을 알아본다. 우선 가장 대중적인 쿠버네티스 로그 운영은 아래와 같은 플로우로 많이 진행하는 것 같다.
app ---> fluentd ---> elasticsearch ---> kibana
app에서는 표준 출력으로 로그를 출력하고 fluentd는 로그를 긁어서 엘라스틱서치에 색인한다. 그리고 키바나를 통해 색인된 로그들을 모니터링한다. 쿠버네티스의 로그 관리에서도 역시 컨테이너는 표준 출력으로만 로그를 내보내면 되며, 그에 대한 처리는 컨테이너 외부에서 이루어진다.
쿠버네티스는 다수의 도커 호스트를 노드로 운영하는데, 어떤 노드에 어떤 파드가 배치될지는 쿠버네티스 스케줄러가 결정한다. 그러므로 각 컨테이너가 독자적으로 로그를 관리하면 비용이 많이 든다. 먼저 로컬 쿠버네티스 환경에 Elasticsearch와 Kibana를 구축한 다음, 로그를 전송할 fluentd와 DaemonSet을 구축한다.
쿠버네티스에 Elasticsearch와 Kibana 구축하기
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
엘라스틱서치 manifast 파일이다. 볼륨마운트, 컨피그 맵 등의 설정이 추가적으로 들어갔다.
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"
키바나 manifast 파일이다.
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
마지막으로 fluentd manifast이다. DaemonSet 으로 구성되며 클러스터 노드마다 할당되는 리소스이다. DaemonSet은 클러스터 전반적인 로깅등의 작업을 수행하는 리소스에 할당하는 타입이다. 어떠한 팟마다 같이 뜨는 리소스가 아니고 클러스터 노드마다 뜨는 리소스라고 보면 된다. 로그 컬렉터같이 호스트마다 특정할 역할을 하는 에이전트를 두고자 할 때 적합하다.
여기까지 간단하게 쿠버네티스 환경에서 로깅 운영하는 방법을 간단하게 다루어봤다. 다음 예제에서는 기본적인 플로우 이외로 output을 kafka로 보내는 등의 다른 플러그인을 사용하여 로그를 수집하는 예제를 살펴볼 것이다.
출처: https://coding-start.tistory.com/322?category=761720 [코딩스타트]
'컨테이너 > Kubernetes' 카테고리의 다른 글
Kubernetes - 쿠버네티스 클러스터 로깅(logging, fluentd + kafka + elk) (0) | 2021.04.26 |
---|---|
Kubernetes - 볼륨(Volume),퍼시스턴트 볼륨&볼륨 클레임(persistent volume&claim) (0) | 2021.04.26 |
Kubernetes - ingress nginx 설치 및 사용법 (0) | 2021.04.26 |
Kubernetes - kubernetes(쿠버네티스) resources(cpu/momory) 할당 및 관리 (0) | 2021.04.26 |
Kubernetes - Kubernetes 아키텍쳐 (0) | 2021.04.25 |
Kubernetes - Kubernetes 용어설명 (0) | 2021.04.25 |
Kubernetes - Helm(헬름)이란? 주요개념,설치 , 사용법 (0) | 2021.04.25 |
Kubernetes - 쿠버네티스 서비스,인그레스(Service,Ingress) (0) | 2021.04.25 |