[NKS] K8S + uvicorn 에서 표준출력을 활용해 custom log를 남겨보기

2025. 10. 27. 15:15·K8S & Docker

예상 독자

  • uvicorn + k8s로 로그를 찍으려는데 로그가 안 나오는 슬픈 사람들

 

개발환경

  • NKS, FastAPI, Docker, K8S, Source Pipeline

 

문제 상황

  • NKS로 구성된 API 서버 로그를 좀 더 구체적으로 보기 위해서 logger.info로 로그를 찍고 kubectl log <pod이름> 으로 log를 보고 싶었다.
  • Uvicorn이 기본적으로 남겨주는 API서버 로그들은 제대로 출력이 되는 반면 내가 logger.info로 찍는 로그들은 제대로 나오지 않았다.
  • CI과정에 NCP Container Registry에 도커 이미지를 올려두는데 똑같은 이미지를 일반 Server + Docker 환경에서 pull 받은 경우 커스텀 로그가 제대로 찍혔다.
  • 작은 애플리케이션을 운영하고 있고, 인수인계가 3개월단위로 이루어지고 있기 때문에 elk, loki는 염두해 두지 않고 콘솔+파일에 로그를 찍는 것을 목적으로 두었다.

 

k8s 로그 메커니즘 이해하기

k8s는 파일 디스크립터 1번 2번만 로그로 취급한다. 즉, 애플리케이션 로그가 컨테이너 표준 출력 스트림으로 출력되어야 한다!

그래야 쿠버네티스가 로그를 탐지할 수 있다.

 


https://kubernetes.io/docs/concepts/cluster-administration/logging/
파이썬 log들을 콘솔에 찍으려면 표준출력으로 보내야한다.
아래와 같이 Gemini나 GPT가 logger를 설정하라고 알려줄거다!

import logging
import sys

# 1. 로거 객체 생성
logger = logging.getLogger("my_app_logger")
logger.setLevel(logging.INFO)  # 로거의 최소 로그 레벨 설정

# 2. StreamHandler(표준 출력) 객체 생성
# sys.stdout을 인자로 전달하여 표준 출력(콘솔)에 로그를 보냄
stream_handler = logging.StreamHandler(sys.stdout)

# 3. Formatter 객체 생성 및 형식 지정
# 로그 메시지의 형식을 지정. 예: 시간 - 로거이름 - 레벨 - 메시지
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

# 4. 핸들러와 포매터 연결
stream_handler.setFormatter(formatter)

# 5. 로거에 핸들러 추가
logger.addHandler(stream_handler)

# 로그 메시지 출력
logger.info("Application started.")
logger.warning("Something might be wrong.")
logger.error("An error occurred!")

 

 

 

하지만 이렇게하면 도커에서는 돌아가지만 k8s에서는 돌아가지 않는다!
정말 신기했던 것은 uvicorn이 자동으로 직접 찍는 로그들은 kubectl log로 조회가 되는데

logger.info(f"[docs_main] 현재 데이터 읽기 시작")

이런식으로 내가 직접 커스텀한 로그들은 찍히지 않는 것이다!!
이쯤되면 생각해볼 수 있다

  1. uvicorn이 내가 만든 로그를 덮어쓰고 있다. 한마디로 내 로그들이 씹히는거다!! ㅠㅠ
  2. 그러면 uvicorn이 뭘까?

 

 

uvicorn 이해하기

Uvicorn은 python으로 만들어진 ASGI 웹서버이다.
WSGI의 비동기 버전으로 WSGI가 싱글 스레드 요청 처리에 초점을 맞춘 반면 ASGI는 비동기 I/O를 통해 여러 요청을 동시에 처리할 수 있다.
나는 FastAPI를 쓰고 있었는데 FastAPI에 사용되는 웹서버가 uvicorn이다.
https://devocean.sk.com/blog/techBoardDetail.do?ID=165922&boardType=techBlog
상단의 링크의 블로그에 uvicorn을 잘 설명해주셔서 이것을 보면 더 좋을 것 같다.

 

 

uvicorn log config

이제 왜 내 로그가 uvicorn에게 덮어 씌여지는지 알아볼 차례이다.
여기서부턴 추측성이 많아서 정확하지 않을 수 있다. 이 사람은 이런 생각의 흐름으로 → 이런 방식을 사용해서 → k8s+uvicorn에 로그가 찍히지 않는 문제를 해결했구나 정도로만 보면 좋을듯하다.


https://www.uvicorn.org/settings/#logging

 

Settings - Uvicorn

Settings Use the following options to configure Uvicorn, when running from the command line. Configuration Methods There are three ways to configure Uvicorn: Command Line: Use command line options when running Uvicorn directly. uvicorn main:app --host 0.0.

www.uvicorn.org

 

--log-config <path> - Logging configuration file. 
Options: dictConfig() formats: .json, .yaml. 
Any other format will be processed with fileConfig(). 
Set the formatters.default.use_colors and formatters.access.use_colors values 
to override the auto-detected behavior.
If you wish to use a YAML file for your logging config, 
you will need to include PyYAML as a dependency for your project or install uvicorn with the [standard] optional extras.

 

공식 문서를 보면 —log-config <yml 파일> 을 사용해서 logging을 설정해주라고 한다.

 

 

# AS-IS
uvicorn main:app --host 0.0.0.0 --port 8000
# TO-BE
uvicorn main:app --host 0.0.0.0 --port 8000 --log-config logging_config.yaml

두 명령어의 차이점은 uvicorn 웹서버를 실행할때 부터 내가 원하는 로그 설정으로 고정을 해주겠다는 거다.
AS-IS와 같이 명령어를 입력하면 내가 원하는 로그 설정이 아닌 uvicorn 기본 설정으로 돌아가는 것 같았다!

 

 

 

version: 1
disable_existing_loggers: false

formatters:
  default:
    (): uvicorn.logging.DefaultFormatter
    fmt: "%(levelprefix)s %(asctime)s - %(name)s - %(message)s"
    use_colors: null
  access:
    (): uvicorn.logging.AccessFormatter
    fmt: '%(levelprefix)s %(client_addr)s - "%(request_line)s" %(status_code)s'

handlers:
  console:
    class: logging.StreamHandler
    formatter: default
    stream: ext://sys.stdout
    level: INFO
  file:
    class: logging.handlers.RotatingFileHandler
    formatter: default
    filename: logs/app.log
    maxBytes: 10485760
    backupCount: 5
    encoding: utf-8
    level: INFO

loggers:
  "": -> 루트 로거
    handlers: [console, file]
    level: INFO
  uvicorn: -> uvicron
    handlers: [console, file]
    level: INFO
    propagate: false
  uvicorn.error:
    handlers: [console, file]
    level: INFO
    propagate: false
  uvicorn.access:
    handlers: [console, file]
    level: INFO
    propagate: false

yaml 파일은 위와 같이 적어준다. 나는 파일에도 로그를 찍어주고 싶어서 fileHandler도 사용했다.
https://docs.python.org/ko/3/library/logging.handlers.html
파이썬 공식문서를 보면 logging handler에 대한 설명이 나와있다. 보통 StreamHandler로 콘솔을 다루는 듯하고 FileHandler로 로그 파일을 다루는 듯 하다!
우리는 k8s에서 로그를 찍는 것이 목적이니 반드시!! stream을 sys.out으로 설정해야한다.

 

 

 

k8s에서 logging할 수 있게 yaml에 logger 설정해주기!

이제 k8s에 배포를 해야한다.
deployment는 아래와 같이 정의 되어있다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: backend-deployment
  labels:
    app: backend
spec:
  replicas: 2
  selector:
    matchLabels:
      app: backend
  template:
    metadata:
      labels:
        app: backend
    spec:
      imagePullSecrets:
      - name: regcred
      containers:
      - name: backend-container
        image: {경로}
        workingDir: /app
        ports:
        - containerPort: {포트}
        command: ["uvicorn"]
        args: ["main:app", "--host", "0.0.0.0", "--port", "8000", "--log-config", "logging_config.yaml"]
        envFrom:
        - configMapRef:
            name: backend-env
        volumeMounts:
        - name: logging-config-volume
          mountPath: /app/logging_config.yaml
          subPath: logging_config.yaml
        - name: logs-volume
          mountPath: /app/logs
      volumes:
      - name: logging-config-volume
        configMap:
          name: logging-config 
      - name: logs-volume
        emptyDir: {} 

실행명령어에 logging-config.yaml이 있기 때문에 k8s 볼륨에 logging-config.yaml을 올려줘야 한다.

 

 


logging-config.yaml을 ConfigMap에 yaml 형태로 저장해 둔 뒤 이 yaml을 k8s volume에 mount하고 uvicorn으로 실행하면 k8s에서도 내가 커스텀한 logging 설정으로 실행될 수 있다.

apiVersion: v1
kind: ConfigMap
metadata:
  name: logging-config
data:
  logging_config.yaml: |
    version: 1
    disable_existing_loggers: false
    formatters:
      default:
        (): uvicorn.logging.DefaultFormatter
        fmt: "%(levelprefix)s %(asctime)s - %(name)s - %(message)s"
        use_colors: null
      access:
        (): uvicorn.logging.AccessFormatter
        fmt: '%(levelprefix)s %(client_addr)s - "%(request_line)s" %(status_code)s'
    handlers:
      console:
        class: logging.StreamHandler
        formatter: default
        stream: ext://sys.stdout
        level: INFO
      file:
        class: logging.handlers.RotatingFileHandler
        formatter: default
        filename: logs/app.log
        maxBytes: 10485760
        backupCount: 5
        encoding: utf-8
        level: INFO
    loggers:
      "":
        handlers: [console, file]
        level: INFO
      uvicorn:
        handlers: [console, file]
        level: INFO
        propagate: false
      uvicorn.error:
        handlers: [console, file]
        level: INFO
        propagate: false
      uvicorn.access:
        handlers: [console, file]
        level: INFO
        propagate: false

volume과 volume mount 알아보기

위의 yaml에서는 두 개의 볼륨을 마운트한다.

  • logging-config-volume
    • logging-config ConfigMap을 /app/logging_config.yaml 경로에 파일로 마운트
  • logs-volume
    • 로그 파일을 저장할 logs 디렉토리를 위한 임시 저장소(emptyDir)를 생성하여 마운트

 

 

이쯤에서 Volume과 Volume mount의 개념을 알고 K8S에서 어떻게 작동하는지 짚고 넘어갈 필요가 있다.
https://kubernetes.io/docs/concepts/storage/volumes/

[Volumes

Kubernetes volumes provide a way for containers in a pod to access and share data via the filesystem. There are different kinds of volume that you can use for different purposes, such as: populating a configuration file based on a ConfigMap or a Secret pro

kubernetes.io](https://kubernetes.io/docs/concepts/storage/volumes/)

 

 

 

 

Volume

  • Pod안에 있는 컨테이너들에게 FileSystem을 통해 data를 공유하고 접근할 수 있게 해준다.
  • 이를 테면 Configmap이나 Secret 같은 설정 파일을 저장할 수도 있고, 같은 pod 안에 서로 다른 두개의 컨테이너들의 fs를 공유할 수 있게도 해준다.

 

 

Volume mount

호스트의 fs경로를 컨테이너 내부에 연결하는 것을 의미한다. 마운트를 해야 디렉토리나 파일을 도커 컨테이너 내부에서 사용하거나 읽을 수 있다!

 

 

 

Volume의 종류

찾다보니 Volume의 종류는 두가지로 나뉘는 듯하다.
Persistent Volumes와 Ephermal Volumes로 나뉘는데, 전자의 경우 영구적인 친구들, 후자의 경우 일시적인 친구들이다.
앞에서 다룬 ConfigMap은 Ephermal Volumes에 해당하며 Pod안에 Kubernetes 데이터들을 주입하는 역할을 한다.
https://kubernetes.io/docs/concepts/storage/persistent-volumes/
https://kubernetes.io/docs/concepts/storage/ephemeral-volumes/

[Ephemeral Volumes

This document describes ephemeral volumes in Kubernetes. Familiarity with volumes is suggested, in particular PersistentVolumeClaim and PersistentVolume. Some applications need additional storage but don't care whether that data is stored persistently acro

kubernetes.io](https://kubernetes.io/docs/concepts/storage/ephemeral-volumes/)

 

 

 

 

 

앞으로 더 나아가기 위해서는?

  • 로그들은 팀원 모두 볼 수 있도록 스토리지에 적재한다.
  • ELK, Loki등 로깅 솔루션들을 붙여본다!!

참고 자료들

https://stackoverflow.com/questions/77001129/how-to-configure-fastapi-logging-so-that-it-works-both-with-uvicorn-locally-and

'K8S & Docker' 카테고리의 다른 글

[NKS] NKS + Prometheus + Loki + Promtail로 로그 수집하고 Ncloud Storage(또는 Object Storage) 에 로그 보관하기  (0) 2025.11.06
[NKS] NKS NAS 볼륨 CSI를 사용해 두개 이상의 pod끼리 파일 공유하기  (1) 2025.10.27
[NKS] K8S Cron Job으로 PVC 청소하기 🫧🧽  (0) 2025.10.17
'K8S & Docker' 카테고리의 다른 글
  • [NKS] NKS + Prometheus + Loki + Promtail로 로그 수집하고 Ncloud Storage(또는 Object Storage) 에 로그 보관하기
  • [NKS] NKS NAS 볼륨 CSI를 사용해 두개 이상의 pod끼리 파일 공유하기
  • [NKS] K8S Cron Job으로 PVC 청소하기 🫧🧽
jjungking
jjungking
쩡킹의 고상한 코딩 이야기
  • jjungking
    jjungking
    jjungking
  • 전체
    오늘
    어제
    • 분류 전체보기 (19)
      • Cloud (2)
      • K8S & Docker (4)
      • Linux (3)
      • Next.js & React (3)
      • SpringBoot (2)
      • OS (0)
      • Network (1)
      • AWS (1)
      • Git (1)
      • OpenSource (1)
      • 회고록 (1)
      • 기술세션 공부하기 (0)
      • Certi (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • 깃허브
  • 공지사항

  • 인기 글

  • 태그

    AWS
    nks
    리눅스시스템프로그래밍
    uncontrolledinput
    NCP
    네이버클라우드
    ec2
    Grafana
    aws이관
    ncloud
    RDS
    cronjob
    Loki
    Helm
    Husky
    springboot
    controlledInput
    promtail
    오픈소스기여
    네이버클라우드플랫폼
    k8s
    githook
    uvicorn
    http응답느릴때
    rds이관
    objectstorage
    Nclouder
    ReactHookForm
    ec2느릴때
    HikariCP
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.2
jjungking
[NKS] K8S + uvicorn 에서 표준출력을 활용해 custom log를 남겨보기
상단으로

티스토리툴바