Kubernetes CronJob 을 이용한 NGINX Pod 로그 관리

Kubernetes에서 NGINX Pod의 로그를 효과적으로 관리하는 것은 시스템 유지보수와 운영의 핵심 요소입니다. 일반적으로 로그는 Pod 내부에서 생성되며, 이를 적절히 보존하거나 주기적으로 정리하지 않으면 저장 공간 부족과 같은 문제를 초래할 수 있습니다. 이를 해결하기 위해 Kubernetes의 CronJob 을 활용하면, 자동화된 방식으로 로그를 정리하거나 백업할 수 있습니다. 본 문서에서는 CronJob 을 사용하여 NGINX Pod의 로그를 효율적으로 관리하는 방법을 다룹니다.

목차

1. 환경구성
2. Cronjob Resource란?
3. NGINX Deployment, Service 구성
4. Cronjob Resource 구성

5. 결론

1. 환경구성

kubernetes v1.30.4

2. CronJob Resource란?

Kubernetes의 CronJob은 지정된 일정에 따라 작업을 자동으로 실행하도록 설계된 Kubernetes 리소스입니다. 이를 통해 주기적으로 실행되는 작업(예: 데이터 백업, 로그 정리, 이메일 전송)을 자동화할 수 있습니다.

ex)

apiVersion: batch/v1
kind: CronJob
metadata:
  name: example-cronjob
spec:
  schedule: "55 * * * *"  # "55분 매시간 매일 매달 매요일" 실행합니다.
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: example
            image: busybox # busybox 이미지를 사용합니다
            args: # 아래와 같은 명령문을 사용합니다.
            - /bin/sh
            - -c
            - date; echo "Hello from the CronJob"
          restartPolicy: OnFailure # 컨테이너가 비정상적인 종료일 경우 재시작합니다. 해당 Cronjob의 지정된 작업이 완료시 재시작하지 않습니다.

위와 같이 구성하게 된다면 get을 통하여 CronJob 리소스에서 확인할 수 있습니다.

CronJob이 지정된 시간이 될경우 아래와 같이 Pod가 생성되고 작업이 완료 되었을 때 Completed를 확인할 수 있습니다.

위의 구성된 예제에서 CronJob은 날짜와 echo 명령문을 사용하게 되는데 결과 값은 모두 kubectl logs를 통하여 확인할 수 있습니다. (Pod의 Timezone은 UTC로 지정되어 있습니다.)

3. Cronjob을 이용한 NGINX Log 관리

CronJob을 구성하기 전 NGINX를 구성하여 logging 및 정적 파일을 구성합니다.

3-1. NGINX Service 및 Deployment, Configmap 구성

Service를 구성합니다.

apiVersion: v1
kind: Service
metadata:
  name: nginx-svc
  namespace: nginx-cronjob
spec:
  selector:
    app: nginx-static
  ports:
    - protocol: TCP 
      port: 80
      targetPort: 80
  type: LoadBalancer 

Deployment를 구성합니다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
  namespace: nginx-cronjob
spec:
  selector:
    matchLabels:
      app: nginx-static
  template:
    metadata:
      labels:
        app: nginx-static
    spec:
      nodeSelector:
        kubernetes.io/hostname: workernode1 # 지정된 Node에 배포될 수 있도록 node를 선택합니다. (재배포시 고정된 node에 배포하여 로그가 유지 될 수 있도록 구성)
      containers:
        - name: nginx-oss
          image: nginx 
          ports:
            - containerPort: 80
          volumeMounts:
            - name: nginx-log-pv # 볼륨를 사용하여 로그 경로를 Node에 마운트합니다. 
              mountPath: "/var/log/nginx/"
            - name: nginx-config-volume # nginx 구성을 변경할 수 있도록 지정합니다.
              mountPath: "/etc/nginx/conf.d/default.conf"
              subPath: "default.conf"
      volumes:
      - name: nginx-log-pv # pvc를 지정하여 스토리지에 대한 사용을 요청합니다.
        persistentVolumeClaim: 
          claimName: nginx-log-pvc # pvc의 이름을 구성합니다.
      - name: nginx-config-volume # nginx 구성을 configmap으로 변경할 수 있도록 지정합니다.
        configMap:
          name: nginx-config # configmap의 이름을 지정합니다.

ConfigmMap을 지정하여 NGINX 컨테이너의 구성을 변경합니다.

apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-config
  namespace: nginx-cronjob 
data:
  default.conf: |
        server {
            access_log /var/log/nginx/80_port.log main; 
            listen 80; 
            location / { 
                return 200 "Hello from Nginx \n";
            }
        }

3-2. NGINX log 저장용 PVC,PV 구성

PersistentVolume을 구성하여 Node의 스토리지를 접근 할 수 있도록 구성합니다.

apiVersion: v1
kind: PersistentVolume
metadata:
  name: nginx-log-pv
  labels:
    type: local # local Storage 사용 
spec:
  capacity:
    storage: 5Gi #PV의 용량을 지정합니다.
  accessModes:
  - ReadWriteMany # 여러 파드가 동시에 읽고 쓸 수 있도록 지정합니다.
  hostPath:
    path: "/tmp/nginx_log" # node의 "/tmp/nginx_log/"에 마운트합니다.

PVC를 사용하여 Node에 대해 스토리지 접근을 요청합니다.

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nginx-log-pvc
  namespace: nginx-cronjob
spec:
  accessModes:
  - ReadWriteMany # 여러 파드가 동시에 읽고 쓸 수 있도록 지정합니다.
  resources:
    requests:
      storage: 5Gi # PVC에서 요청하는 저장소의 크기를 지정합니다. 최대 용량이 PV에 지정된 값을 넘을 수 없습니다.
  storageClassName: manual # PVC에 StorageClass를 추가하여 PVC가 사용할 수 있는 PV를 찾을 수 있도록 합니다. 

Service, Deployment, Configmap NGINX Pod가 Node의 /tmp/nginx_log에 로그에 저장되는지 확인합니다.

Service의 EXTERNAL-IP를 통해 NGINX Service에 요청을 보냅니다.

요청을 보낸 이후 지정했었던 Node인 workernode1로 접속하여 로그 저장 디렉토리를 확인합니다.

(workernode1)

NGINX Pod의 Log가 마운트 된 WorkerNode1에 적히는 것을 확인할 수 있습니다.

4. CronJob Resource 구성

위와 같이 NGINX 로그의 양이 많아질 경우 리소스 소비, 저장공간 소비, 로그 분석이 어렵습니다.

Kubernetes CronJob을 이용하여 NGINX Pod의 로그를 관리합니다.

NGINX의 로그는 아래와 같이 NGINX 시스템에서 기록을 하고 있기 때문에 파일 압축시 특정 신호를 보내 로그 파일을 재작성 할 수 있도록 CronJob에서 지정하여야합니다.

이미지를 생성하기 전 Logrotate의 구파일과 로테이트시 실행 될 스크립트 파일을 생성합니다.

kubectl.sh (로테이트 시 실행 될 스크립트 파일)

  1 # !/bin/bash
  2 set -x
  3 POD_NAME=$(kubectl get pods -n $NAMESPACE -l $LABEL_NAME=$LABEL_VALUE -o jsonpath='{.items[0].metadata.name}')
  4 NGINX_PID=$(kubectl exec -n $NAMESPACE $POD_NAME -- cat /run/nginx.pid)
  5 NGINX_PID=$(echo "$NGINX_PID" | tr -cd '0-9')
  6 $(kubectl exec -i -n $NAMESPACE $POD_NAME -- bash -c "kill -USR1 $NGINX_PID")

1. bash 셸을 사용합니다.
2. Debug를 사용하여 Kubernetes 로그에서 확인할 수 있도록 지정합니다.
3. 각 명령어가 실행될 때마다 해당 명령어를 출력합니다.
4. label을 통해 Pod 이름을 찾습니다. (LABEL_NAME, LABEL_VALUE, NAMESPACE 환경 변수를 사용합니다.)
5. NGINX Pod의 NGINX pid를 찾습니다. (POD_NAME, NAMESPACE 환경 변수를 사용합니다.
6. NGINX Pod에 Kill -USR1 [NGINX PID] 명령어를 전송합니다. (NAMESPACE, POD_NAME 환경 변수를 사용합니다.)

위의 스크립트 파일은 NGINX Pod에 압축 이후 로그를 재작성하는 신호를 NGINX에 보냅니다.

nginx (logrotate 구성 파일)

  1 /var/log/nginx/*.log {
  2     create 644 root root 
  3     size 10M
  4     rotate 100000
  5     missingok
  6     compress
  7     maxage 365
  8     dateext
  9     dateformat -%Y%m%d_%H:%M:%S
 10     notifempty
 11     sharedscripts
 12     postrotate
 13               sleep 5
 14               /bin/bash /tmp/kubectl.sh
 15     endscript
 16 }

1. /var/log/nginx/의 모든 로그를 아래와 같은 Logrotate 구성을 사용합니다.
2. RW-R–R– root root로 새로운 로그 파일이 생성될 수 있도록 지정합니다.
3. 사이즈가 10MB가 넘을 때 로테이트 될 수 있도록 지정합니다.
4. 압축된 로그파일이 최대 100000개 저장할 수 있도록 지정합니다.
5. 로그파일이 없어도 에러처리 하지 않습니다.
6. 로그를 압축합니다.
7. 압축된 로그가 최대 365일을 저장할 수 있도록 지정합니다.
8. 로그파일-날짜 와 같이 로그 파일 뒤에 압축 날짜가 지정됩니다.
9. 날짜의 포멧을 20250218_15:00:00와 같은 방식으로 저장합니다.
10. 로그파일이 없을경우 압축하지 않습니다.
11. 스크립트를 한번에 실행합니다.
12. 로테이트가 진행된 후 스크립트를 실행합니다.
13. /tmp/kubectl.sh 스크립트를 실행합니다.
14. 스크립트의 마지막을 나타냅니다.

Dockerfile을 작성하여 이미지를 생성합니다.

FROM bitnami/kubectl:latest # base 이미지를 kubectl을 사용할 수 있는 이미지로 지정합니다.

USER root # root 유저를 사용합니다. (기본 유저가 권한이 없어 이미지가 빌드되지 않습니다.)

RUN apt update && \
    apt install logrotate gzip -y && \ # logrotate와 gzip을 사용할 수 있도록 패키지를 다운받습니다.
    apt clean && apt autoclean # apt 패키지의 캐시를 삭제와 오래된 패키지를 삭제하여 이미지의 크기를 최적화합니다.
 
COPY ./kubectl.sh /tmp/kubectl.sh # 스크립트 파일을 복사하여 Container 내에서 사용할 수 있도록 스크립트 파일을 이미지 내로 복사합니다.

RUN chmod 100 /tmp/kubectl.sh # 스크립트를 사용할 수 있도록 실행 권한을 지정합니다. (root 권한만)

COPY nginx /etc/logrotate.d/nginx # 구성했던 logrotate 구성을 이미지로 복사합니다.

CMD ["/bin/bash"] #컨테이너가 시작될 때 기본적으로 실행되는 명령어로 bash 셸을 실행합니다.

이미지를 Build하여 개인 Repository에 올려 사용합니다.

docker build . --no-cache

4-2. CronJob 구성

CronJob을 구성하기 전 Pod 내에서 kubectl을 사용하기 위해서는 아래와 같은 역할기반 접근 제어를 구성합니다.

# Cluster에 대한 권한을 담을 Service Account를 생성합니다.
apiVersion: v1
kind: ServiceAccount 
metadata:
  name: nginx-log-sa # Service Account를 생성합니다.
  namespace: nginx-cronjob
---
# Cluster에 대한 권한을 구성합니다.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: pod-reader-cluster-role
rules:
  - apiGroups:
      - "" # 기본적인 리소스(pods)에 접근할 수 있음을 나타냅니다.
    resources:
      - pods # Pod 정보를 조회할 수 있습니다.
      - pods/exec # Pod 내에서 exec 명령을 실행할 수 있습니다.
    verbs:
      - get # 특정 Pod의 정보를 조회할 수 있습니다.
      - list # 모든 Pod 목록을 조회할 수 있습니다.
      - watch # Pod의 변경 사항을 감시할 수 있습니다.
---
# 해당 Service Account에 지정했었던 ClusterRole의 권한을 할당합니다.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: cronjobBinding
  namespace: nginx-cronjob
subjects:
  - kind: ServiceAccount
    name: nginx-log-sa
    namespace: nginx-cronjob
roleRef:
  kind: ClusterRole
  name: pod-reader-cluster-role 
  apiGroup: rbac.authorization.k8s.io

CronJob을 구성합니다.

apiVersion: batch/v2
kind: CronJob
metadata:
  name: nginx-compress-pod-logs
  namespace: nginx-cronjob
spec:
  schedule: "0 * * * *" # 매시간마다 CronJob이 실행됩니다.
  jobTemplate:
    spec:
      template:
        spec:
          serviceAccountName: nginx-log-sa # Service Account를 지정하여 Container내에서 Kubectl을 사용할 수 있도록 지정합니다.
          nodeSelector:
            kubernetes.io/hostname: workernode1 # 지정된 Node에 배포될 수 있도록 구성합니다.
          containers:
          - name: compress-logs 
            image: [이미지]:[태그] # Repository에 Push 했던 이미지와 태그를 지정합니다.
            env: 
            - name: TZ # Time Zone을 아시아/서울로 지정합니다.
              value: "Asia/Seoul"
            - name: LABEL_NAME # LABEL_NAME 환경변수를 지정합니다.
              value: app # NGINX Deployment에 지정했었던 Label의 이름을 지정합니다,
            - name: LABEL_VALUE # LABEL_VALUE 환경변수를 지정합니다.
              value: nginx-static # NGINX Deployment에 지정했었던 Label의 이름을 지정합니다,
            - name: NAMESPACE # NAMESPACE 환경변수를 지정합니다.
              value: nginx-cronjob # NGINX Deployment의 Namespace를 지정합니다.
            command:
            - /bin/bash # bash Shell을 사용합니다.
            args:
            - -c
            - "logrotate -v /etc/logrotate.d/nginx && logrotate /etc/logrotate.d/nginx" # k8s log에서 확인하기 위해 debug 모드와 Logrotate를 실행합니다.
            volumeMounts:
            - mountPath: "/var/log/nginx/" # logrotate 구성과 같은 디렉토리에 볼륨을 마운트합니다.
              name: nginx-log-pv
          volumes:
          - name: nginx-log-pv
            persistentVolumeClaim:  # pvc를 지정하여 스토리지에 대한 사용을 요청합니다.
              claimName: nginx-log-pvc
          restartPolicy: OnFailure # 컨테이너가 비정상적으로 종료되었을 때 재시작합니다.

위와 같이 구성 후 배포합니다.

CronJob이 배포된 것을 확인할 수 있습니다.

위와 같이 로그파일이 10MB가 넘은 경우

1시간마다 CronJob이 작동하여 자동으로 NGINX의 로그가 압축되는 것을 확인할 수 있습니다.

로테이트 이후 스크립트가 작동하여 NGINX에서 새로운 로그파일에 기록하는 것을 확인할 수 있습니다.

CronJob은 1시간마다 작동하게 되어있고 logrotate는 로그파일이 10mb가 넘을 경우 파일을 압축하기 때문에 1시간마다 로그파일이 10mb 조건을 충족할 경우 아래와 같이 1시간마다 기록되는 것을 확인할 수 있습니다.

Pods 로그를 통해 Logorotate가 진행되었는지 확인할 수 있습니다.

kubectl logs -n nginx-cronjob [CronJob POD NAME]

위와 같이 명령어를 입력하여 CronJob의 실행 여부를 파악할 수 있습니다.

압축 이후 로그의 내용은 아래와 같습니다.

reading config file /etc/logrotate.d/nginx  
Creating stub state file: /var/lib/logrotate/status
acquired lock on state file /var/lib/logrotate/statusReading state from file: /var/lib/logrotate/status
Allocating hash table for state file, size 64 entries

Handling 1 logs

rotating pattern: /var/log/nginx/*.log  10485760 bytes (100000 rotations)
empty log files are not rotated, old logs are removed
considering log /var/log/nginx/80_port.log  # 지정된 크기가 초과되어 Rotate 대상입니다.
Creating new state
  Now: 2025-02-25 09:00
  Last rotated at 2025-02-25 09:00 # 2025-02-25 09:00에 Lorate 되었습니다.
  log needs rotating
considering log /var/log/nginx/access.log
Creating new state
  Now: 2025-02-25 09:00
  Last rotated at 2025-02-25 09:00
  log does not need rotating (log size is below the 'size' threshold)
considering log /var/log/nginx/error.log
Creating new state
  Now: 2025-02-25 09:00
  Last rotated at 2025-02-25 09:00
  log does not need rotating (log size is below the 'size' threshold)
rotating log /var/log/nginx/80_port.log, log->rotateCount is 100000
Converted '-%Y%m%d_%H:%M:%S' -> '-%Y%m%d_%H:%M:%S' # 다음과 같은 Date Format이 적용됩니다.
dateext suffix '-20250225_09:00:05' # 다음과 같은 시간이 압축로그파일에 적용됩니다.
glob pattern '-[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]_[0-9][0-9]:[0-9][0-9]:[0-9][0-9]' 
renaming /var/log/nginx/80_port.log to /var/log/nginx/80_port.log-20250225_09:00:05 # Date Format을 적용시켜 압축 파일 이름을 수정합니다. 
creating new /var/log/nginx/80_port.log mode = 0644 uid = 0 gid = 0 # 새로운 로그파일을 생성합니다.
running postrotate script # Lotate 이후 실행되는 스크립트입니다.
# 아래는 구성했었던 Shell Script의 Debug 로그입니다.
++ kubectl get pods -n nginx-cronjob -l app=nginx-static -o 'jsonpath={.items[0].metadata.name}' # Label을 통해 NGINX Pod를 찾습니다.
+ POD_NAME=nginx-6c56966b56-j5k4f
++ kubectl exec -n nginx-cronjob nginx-6c56966b56-j5k4f -- cat /run/nginx.pid
+ NGINX_PID=1 # NGINX Pod의 PID를 찾습니다.
++ echo 1
++ tr -cd 0-9
+ NGINX_PID=1
++ kubectl exec -i -n nginx-cronjob nginx-6c56966b56-j5k4f -- bash -c 'kill -USR1 1'
# NGINX PID를 통해 로그를 재작성하라는 신호를 보냅니다.
compressing log with: /bin/gzip

5. 결론

Kubernetes 환경에서 CronJob을 활용하면 NGINX Pod의 로그를 주기적으로 관리할 수 있습니다. 이를 통해 로그 파일이 무분별하게 증가하는 것을 방지하고, 시스템의 안정성을 유지할 수 있습니다. 또한, 적절한 로그 보존 정책을 설정하면 운영 및 문제 해결에 필요한 데이터를 효율적으로 관리할 수 있습니다. 본 문서에서 제시한 방법을 참고하여, 환경에 맞는 로그 관리 전략을 적용해 보시기 바랍니다.

Kubernetes에 관련한 더 많은 정보를 알고싶으시다면 NGINX STORE Kubernetes 카테고리를 방문해 주세요

NGINX STORE를 통한 솔루션 도입 및 기술지원 무료 상담 신청

* indicates required