
컨테이너 내부에서 발생하는 상황을 이해하고 분석하기 위해 로깅을 해보자.
로그는 애플리케이션 내부에서 발생하는 상황을 이해하는 데 도움이 된다. 특히 문제를 디버깅하고 클러스터 활동을 모니터링하는 데 유용하다.
stdout, stderr를 통해 Node에 기록된다. (kubectl logs로 확인 가능)기본적인 로그 수집 방식은 3가지가 있으니 각각 살펴보자.
모든 노드에 Logging Agent를 놓는 방식으로 Agent가
/var/log/containers등에서 로그를 수집해서 logging backend로 보낸다.
helm repo add fluent https://fluent.github.io/helm-charts
helm repo update
kubectl create namespace logging
# 각 노드에 Fluent Bit를 DaemonSet으로 설치
# 배포된 DaemonSet이 `/var/log/containers/*.log` 경로의 컨테이너 로그를 수집
# 수집된 로그를 Fluent Bit Pod의 stdout으로 출력한다.
helm install fluent-bit fluent/fluent-bit -n logging
# 로그 확인
kubectl logs -n logging -l k8s-app=fluent-bit
logging을 위한 사이드카 컨테이너를 Pod에 포함시키는 방식
apiVersion: v1
kind: Pod
metadata:
name: app-with-sidecar
spec:
containers:
- name: myapp
image: myapp:latest
volumeMounts:
- name: logs
mountPath: /app/logs # myapp이 /app/logs/app.log에 로그를 남김
- name: log-forwarder
image: fluent/fluent-bit # fluent-bit 사이드카가 로그를 읽어 외부로 전송
args: ["-i", "tail", "-p", "path=/app/logs/app.log", "-o", "stdout"]
volumeMounts:
- name: logs
mountPath: /app/logs
volumes:
- name: logs
emptyDir: {}
애플리케이션이 자체적으로 로그 수집기 없이 백엔드로 로그를 전송하는 방식
import logging
from logstash import TCPLogstashHandler
logger = logging.getLogger('myapp')
logger.setLevel(logging.INFO)
logger.addHandler(TCPLogstashHandler('logstash-service.default.svc.cluster.local', 5959, version=1))
logger.info("hello from myapp")
이 외에도 Runtime interface logging, Logging Operator, Cloud-native 로깅 플랫폼 방식 등도 존재한다.
실제 운영 환경에서는 이 중에서도 가장 보편적이고 널리 채택된 방식이 EFK 스택을 활용한 로그 중앙화로, 어떻게 구성되고 동작하는지 살펴보자.
해당 아키텍처는 각 구성 요소가 명확한 역할을 가지며, 이들이 유기적으로 연결되어 로그 수집 → 저장 → 시각화라는 전체 흐름을 완성한다.
apiVersion: apps/v1
kind: StatefulSet # Elasticsearch Pod가 상태 유지하며 실행시키는 StatefulSet
metadata:
name: elasticsearch
namespace: logging
spec:
serviceName: elasticsearch # # StatefulSet이 사용할 headless 서비스 이름
replicas: 1 # # 하나의 Elasticsearch 인스턴스를 실행
selector:
matchLabels:
app: elasticsearch
template:
metadata:
labels:
app: elasticsearch
spec:
containers:
- name: es
image: docker.elastic.co/elasticsearch/elasticsearch:7.17.9
ports:
- containerPort: 9200 # Elasticsearch 기본 포트
env:
- name: discovery.type
value: "single-node" # 단일 노드 모드로 실행 (개발용)
volumeMounts:
- name: data
mountPath: /usr/share/elasticsearch/data # 데이터 저장 경로
volumeClaimTemplates: # PVC 설정해서 고유한 디스크 보유
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi # 각 인스턴스당 10Gi 볼륨 할당
apiVersion: v1
kind: Service # Elasticsearch에 접근하기 위한 클러스터 내부용 서비스
metadata:
name: elasticsearch
namespace: logging
spec:
ports:
- port: 9200
name: http
selector:
app: elasticsearch
apiVersion: apps/v1
kind: Deployment # Kibana 웹 UI를 배포하는 Deployment
metadata:
name: kibana
namespace: logging
spec:
replicas: 1 # 1개의 Kibana 인스턴스
selector:
matchLabels:
app: kibana
template:
metadata:
labels:
app: kibana
spec:
containers: # Kibana 공식 이미지를 사용
- name: kibana
image: docker.elastic.co/kibana/kibana:7.17.9
ports:
- containerPort: 5601 # Kibana UI 접속 포트
env: # Kibana가 Elasticsearch에 연결할 주소
- name: ELASTICSEARCH_HOSTS
value: "http://elasticsearch.logging.svc.cluster.local:9200"
apiVersion: v1
kind: Service # 클러스터 내 또는 포트포워딩용 Kibana Service
metadata:
name: kibana
namespace: logging
spec:
ports:
- port: 5601
targetPort: 5601
selector:
app: kibana
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole # Fluentd가 Pod나 namespace 정보를 조회할 수 있는 권한 정의
metadata:
name: fluentd-role
rules:
- apiGroups: [""]
resources: ["pods", "namespaces"]
verbs: ["get", "list", "watch"] # 로그 수집 대상 정보를 조회
apiVersion: v1
kind: ServiceAccount # Fluentd가 사용할 서비스 계정
metadata:
name: fluentd
namespace: logging
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding # ClusterRole과 ServiceAccount를 연결
metadata:
name: fluentd-rolebinding
subjects:
- kind: ServiceAccount
name: fluentd
namespace: logging
roleRef:
kind: ClusterRole
name: fluentd-role
apiGroup: rbac.authorization.k8s.io
apiVersion: apps/v1
kind: DaemonSet # 각 Node에서 로그를 수집하는 Fluentd DaemonSet
metadata:
name: fluentd
namespace: logging
spec:
selector:
matchLabels:
app: fluentd
template:
metadata:
labels:
app: fluentd
spec:
serviceAccountName: fluentd # 위에서 생성한 계정 사용
containers:
- name: fluentd
image: fluent/fluentd:v1.14 # Fluentd 기본 이미지
env: # Elasticsearch에 로그를 보낼 주소
- name: FLUENT_ELASTICSEARCH_HOST
value: "elasticsearch.logging.svc.cluster.local"
volumeMounts: # 컨테이너 수준에서 볼륨을 어디에 mount할지 지정
- name: varlog
mountPath: /var/log
readOnly: true
volumes: # Pod 수준에서 실제 mount할 볼륨을 정의
- name: varlog
hostPath:
path: /var/log # Node의 로그 디렉토리를 마운트
/var/log/containers에 기록되므로 Fluentd DaemonSet이 자동으로 수집한다.kubectl run test-logger \
--image=busybox \ # busybox 이미지로 가벼운 컨테이너 실행
--restart=Never \
--namespace=logging \
--command -- sh -c \ # stdout으로 계속 로그를 찍어서 Fluentd가 수집 가능
"while true; do echo \$(date) hello from test-logger; sleep 5; done"
# Kibana UI 접속을 위한 포트 포워딩 후 http://localhost:5601 접속
kubectl port-forward -n logging svc/kibana 5601:5601
# 접속 후 Create index pattern 페이지에 들어가 패턴 입력 및 필드를 선택한다.
EFK 스택은 로그를 수집하고 시각화하는 데 효과적이지만,
실시간으로 로그를 빠르게 조회하거나 디버깅할 땐 stern 같은 CLI 도구도 유용
쿠버네티스 클러스터 내 여러 파드의 로그를 실시간으로 동시에 병렬적으로 조회할 수 있는 도구 Stern 깃허브
go install github.com/stern/stern@latest
kubectl krew install stern
kubectl stern <pod-name-pattern>
kubectl stern --help # 설치 확인
stern nginx # nginx라는 이름이 포함된 모든 Pod의 로그를 동시에 확인
stern -n logging fluentd # logging 네임스페이스 내 fluentd 관련 파드의 로그 확인
stern --selector app=web # app=web 라벨이 붙은 모든 파드의 로그 확인
stern --exclude-container sidecar # sidecar 컨테이너를 제외하고 로그 확인
stern --container main --color always # main 컨테이너만 컬러 출력으로 보기
```