Docker, K8s

Pipe - 컨테이너 런타임이 어떻게 컨테이너의 로그를 캡쳐하는가?

shuotudou 2025. 3. 18. 22:06

1. 컨테이너는 본질적으로 리눅스 프로세스이며, 모든 프로세스는 기본적으로 stdout과 stderr를 가지고 있음.
2. 컨테이너 프로세스의 표준출력과 표준입력을 Pipe를 통해 컨테이너 런타임으로 전달
3. Pipe로 전달될때 파일 디스크립터 리디렉션(File Descriptor Redirection)의 방식으로 전달됨.

 

 

 

단계별 설명:

  1. 프로세스 생성 및 초기 파일 디스크립터: 컨테이너 런타임은 새로운 컨테이너를 실행하기 위해 fork() (또는 유사한 시스템 호출)를 통해 새로운 프로세스를 생성합니다. 이 자식 프로세스(컨테이너의 메인 프로세스)는 부모 프로세스(컨테이너 런타임)의 열려 있는 파일 디스크립터들을 복사하여 상속받습니다. 일반적으로 부모 프로세스의 표준 입력(stdin, 파일 디스크립터 0), 표준 출력(stdout, 파일 디스크립터 1), 표준 에러(stderr, 파일 디스크립터 2)가 자식 프로세스에도 그대로 연결되어 있습니다. 초기 상태에서는 이들은 보통 컨테이너 런타임이 실행되는 환경(예: 터미널)과 연결되어 있습니다.
  2. 파이프 생성: 컨테이너 런타임은 컨테이너 프로세스를 실제로 실행하기 전에, 컨테이너의 표준 출력과 표준 에러를 캡처하기 위해 각각 하나의 파이프를 생성합니다. 각 파이프는 익명의 양방향 통신 채널이며, 두 개의 파일 디스크립터를 가집니다. 하나는 데이터를 쓸 수 있는 **쓰기 끝(write end)**이고, 다른 하나는 데이터를 읽을 수 있는 **읽기 끝(read end)**입니다.
  3. 파일 디스크립터 리디렉션: 핵심적인 부분은 바로 이 단계입니다. 컨테이너 런타임은 생성한 파이프의 파일 디스크립터를 사용하여 컨테이너 프로세스의 표준 출력(파일 디스크립터 1)과 표준 에러(파일 디스크립터 2)를 리디렉션합니다. 이는 일반적으로 dup2() 시스템 호출을 통해 이루어집니다.
    • dup2(oldfd, newfd) 시스템 호출은 oldfd가 가리키는 파일 디스크립터를 newfd로 복사합니다. 만약 newfd가 이미 열려 있다면 먼저 닫고 복사합니다. 중요한 점은 newfd가 이제 oldfd와 동일한 파일을 가리키게 된다는 것입니다.
    • 컨테이너 런타임은 다음과 같은 리디렉션을 수행합니다.
      • 컨테이너 표준 출력을 위한 파이프의 쓰기 끝 파일 디스크립터를 파일 디스크립터 1(stdout)로 복사합니다. 즉, dup2(pipe_stdout[1], 1) (여기서 pipe_stdout[1]은 표준 출력 파이프의 쓰기 끝 파일 디스크립터입니다).
      • 컨테이너 표준 에러를 위한 파이프의 쓰기 끝 파일 디스크립터를 파일 디스크립터 2(stderr)로 복사합니다. 즉, dup2(pipe_stderr[1], 2) (여기서 pipe_stderr[1]은 표준 에러 파이프의 쓰기 끝 파일 디스크립터입니다).
  4. 컨테이너 프로세스 실행: 리디렉션이 완료된 후, 컨테이너 런타임은 컨테이너의 실제 명령을 실행합니다. 이제 컨테이너 내부의 애플리케이션이 표준 출력(예: printf(), console.log())이나 표준 에러(예: 오류 메시지 출력)를 통해 데이터를 쓰려고 하면, 이 데이터는 원래 연결되어 있던 터미널이 아니라, 리디렉션된 파이프의 쓰기 끝으로 전달됩니다.
  5. 데이터 흐름: 컨테이너가 표준 출력이나 표준 에러로 데이터를 쓰면, 이 데이터는 해당 파이프를 통해 컨테이너 런타임이 가지고 있는 파이프의 읽기 끝으로 흘러갑니다.
  6. 로그 처리: 컨테이너 런타임은 자신이 열어 놓은 파이프의 읽기 끝에서 데이터를 지속적으로 읽어들여, 설정된 로깅 드라이버(예: json-file, journald)를 사용하여 로그 파일에 기록하거나, 중앙 로깅 시스템으로 전송하는 등의 후속 처리를 수행합니다.