AWS Cloud School 13기 65~66일차

Forever 김·2026년 4월 3일

AWS Cloud School

목록 보기
62/97

토이 프로젝트 기간

[성능 프로파일링 보고서] Docker 이미지 경량화의 역설: 자동화 CLI의 운영 환경 검증 및 커널 레벨 디버깅


1. 프로젝트 목적 및 실무적 의의

본 프로젝트의 핵심 목적은 직접 개발한 Dockerfile 정적 분석 및 자동 경량화 CLI 도구(imgadvisor)의 산출물이 실제 운영 환경의 가혹 조건에서도 실효성 있는 성능 개선을 만들어내는지 검증하는 것이다.

[CLI 도구 소개: imgadvisor]
imgadvisor는 Dockerfile을 정적으로 분석하여 최적화 포인트를 도출하는 자체 개발 CLI 도구다.

  • $ imgadvisor recommend -f Dockerfile : 최적화 가능한 레이어 및 안티패턴 진단
  • $ imgadvisor validate -f Dockerfile --optimized [파일명] : 분석 결과를 바탕으로 멀티 스테이지 빌드 등이 적용된 경량화 Dockerfile 자동 생성

일반적인 최적화 프로젝트는 "이미지 용량을 줄였다"는 선에서 마무리된다. 하지만 실제 프로덕션 환경에서 이미지 크기 감소가 무중단 배포나 장애 복구 시의 빠른 기동(Startup)을 보장하지는 않는다. 따라서 본 실험은 CLI가 자동 생성한 최적화 결과물을 극단적인 CPU 자원 경합(Contention) 상황에 노출시켜, 단순한 용량 축소가 런타임 성능에 미치는 영향을 커널 레벨에서 디버깅하는 실무형 트러블슈팅 과정으로 기획되었다.


2. 측정 환경 및 지표 수집 원리

단순히 time docker run으로 측정하는 E2E 지표만으로는 "왜 느려졌는지" 근본 원인을 규명할 수 없다. 따라서 컨테이너 기동 과정에서의 병목을 추적하기 위해 커널 레벨의 지표를 직접 추출하는 자체 프로파일링 쉘 스크립트(measure_host_pressure.sh)를 구현했다.

항목내용
호스트 환경VMware VM (Docker Swarm Manager Node)
vCPU / RAM4코어 (nproc=4) / 3.8GiB (여유 3.0GiB 확인)
OS / 커널Ubuntu, Kernel 6.8.0-106-generic
부하 도구stress-ng --cpu 4 --cpu-method matrixprod --timeout 60s
애플리케이션Flask (app.py) + Gunicorn WSGI 서버

[핵심 측정 지표 추출 원리]

  • CPU 대기 시간 (cgroup PSI): cat /sys/fs/cgroup/.../cpu.pressure
    • 기존 cpu.statwait_sum 필드가 커널 버전에 따라 미지원되는 이슈가 있어, 대안으로 PSI(Pressure Stall Information)의 some total 값을 추출. 이는 프로세스가 CPU 할당을 기다린 누적 시간(μs)을 의미함.
  • 문맥 전환 횟수 (Context Switches): grep nr_switches /proc/$PID/sched
    • 컨테이너 초기화 구간 동안 OS 스케줄러가 해당 프로세스에 몇 번이나 CPU를 할당하고 회수했는지(경합의 척도)를 직접 추적함.

3. 운영 환경 검증 시나리오 및 트러블슈팅 과정

본 실험은 시스템 복원력(Resilience) 검증을 위해 두 가지 핵심 운영 시나리오를 모사했다.

  • 시나리오 A (Scale-out): 트래픽 쇄도로 노드 CPU가 100% 포화된 상태에서, 오케스트레이터가 부하 분산을 위해 신규 컨테이너를 스케줄링할 때의 골든타임 확보 여부.
  • 시나리오 B (Disaster Recovery): 물리 노드 1대가 다운되어 다른 노드로 태스크가 즉각 재배치될 때, 런타임 초기화 과정의 오버헤드가 RTO(목표 복구 시간)에 미치는 영향.

이를 위해 호스트 가용 CPU를 강제 고갈시킨 상태에서 Baseline(679MB) 이미지와 Optimized(179MB) 이미지의 기동 속도를 비교했다. (모두 Gunicorn Worker 2개로 통일)

3.1. 측정 환경 구축 간 트러블슈팅 (삽질 이력)

프로파일링 스크립트를 구축하는 과정에서 겪은 주요 문제와 해결 과정은 다음과 같다.

① 백그라운드 부하 프로세스(stress-ng) 강제 종료 현상

  • 현상: 테스트 시작 직후 CPU 부하를 주어야 할 프로세스가 조용히 종료됨.
  • 원인 추적: 윈도우 환경에서 편집한 스크립트의 한글 주석 인코딩 문제로 인해, 백그라운드 실행을 지시하는 & 기호가 유실되거나 파싱 에러 발생.
  • 해결: 스크립트 포맷(LF) 및 인코딩을 맞추고, 백그라운드 실행을 명확히 한 뒤 PID를 파일에 기록하여 제어하도록 수정.

② 문맥 전환(ctx_switches) 지표 누락 문제

  • 현상: /proc/$PID/sched에서 조회한 nr_switches 값이 지속적으로 0에 가깝게 도출됨.
  • 원인 추적: Baseline의 Dockerfile이 Shell form(CMD flask run ...)으로 작성되어, 컨테이너의 PID 1이 Gunicorn이 아닌 /bin/sh에 할당됨. 이로 인해 실제 워커 프로세스의 문맥 전환이 측정되지 않음.
  • 해결: 비교군 모두 Exec form ["gunicorn", "-w", "2", ...]으로 통일하여 PID 추적 대상을 명확히 함. 동시에 누적값 오염을 막기 위해 컨테이너 시작 시점(스냅샷)과 /ready 응답 후의 차이값(Delta)만 기록하도록 로직 개선.

4. 실험 결과: 최적화의 역설 (Optimization Paradox)

5회 반복 교차 측정(캐시 삭제 포함)을 통해 도출된 지표는 다음과 같다.

측정 지표Baseline (679MB)Optimized (179MB)증감
Startup Time 평균2429.2 ms2980.6 ms+551.4 ms (+22.7%) 악화
Kernel Wait Time 평균195.0 ms318.0 ms+123.0 ms (+63.1%) 악화
Context Switches 평균331.2 회415.0 회+83.8 회 (+25.3%) 증가

결과 요약: imgadvisor를 통해 이미지 크기를 679MB에서 179MB로 무려 73% 감소시켰으나, Baseline 대비 기동 속도(+22.7%), 커널 대기 시간(+63.1%), 문맥 전환(+25.3%) 모두 심각하게 악화되었다. 가벼워진 용량(I/O 이득)이 런타임 오버헤드(CPU 비용)에 잡아먹힌 '최적화의 역설'이 발생했다.


5. 원인 분석 (Root Cause Analysis)

본 성능 저하는 프로젝트의 실패가 아니라, "정적 분석 기반의 이미지 경량화가 곧바로 Startup 성능 최적화로 이어지지 않는다" 는 실무적 맹점을 입증한 결과다.

[핵심 가설: Python .pyc 컴파일 오버헤드]

  • Baseline의 이점 (Pre-compiled): python:3.11 풀 이미지 내부에는 numpy, pandas 등 무거운 C 확장 기반 패키지들의 표준 라이브러리가 바이트코드(.pyc) 형태로 사전 컴파일되어 저장되어 있다.
  • Optimized의 함정 (Slim Image): 최적화 과정에서 적용된 python:3.11-slim 이미지는 용량 감소를 위해 내부 .pyc 파일을 모두 제거한 상태다. 격리된 가상환경(venv) 패키지들은 처음 import 시 메모리상에서 실시간으로 JIT 컴파일을 수행해야 한다.
  • 병목 발생 메커니즘: CPU 100% 고갈 상태에서 Gunicorn worker들이 동시에 .py 소스를 읽어 컴파일을 시도하면서, 이미 포화된 Run-Queue에 막대한 추가 부하를 발생시켰고 이것이 kernel_wait_ms 폭증으로 직결되었다.

(한계점 인식) 현재 도구는 docker inspect State.Pid를 통해 Gunicorn Master 프로세스를 추적하므로 지표의 정밀도에 한계가 있다. 향후 grep 'gunicorn.*worker'를 통해 실제 연산을 수행하는 Worker PID를 직접 추적하도록 보완이 필요하다.


6. 실무적 가이드라인 (Python 컨테이너 최적화)

본 프로파일링 결과를 바탕으로, 실무 환경에서 Python 애플리케이션 컨테이너를 최적화할 때 반드시 고려해야 할 가이드라인을 도출했다.

  • Slim 이미지 사용 시 반드시 빌드 시점에 .pyc 사전 컴파일 적용
    단순히 베이스 이미지를 교체하는 것에 그치지 않고, Dockerfile(또는 Builder stage)의 마지막에 RUN python -m compileall /opt/venv/lib /app 명령어를 주입하여 런타임 오버헤드를 빌드 타임으로 전가해야 한다.
  • 무거운 패키지 사용 시 Slim 이미지의 역효과 인지
    numpy, pandas, scikit-learn 등을 다수 포함하는 환경에서는 Slim 이미지를 통한 I/O 대역폭 확보보다 런타임 초기화 오버헤드(CPU 자원 소비)가 더 클 수 있음을 인지하고 트레이드오프를 계산해야 한다.
  • CPU 경합 환경에서는 '크기'보다 '초기화 속도'가 우선
    재해 복구(DR)나 오토 스케일링이 빈번한 프로덕션 환경에서는 이미지 Pull 속도보다 애플리케이션의 Ready 상태 도달 속도가 클러스터 전체 가용성에 결정적인 영향을 미친다.

7. Next Steps: CLI 알고리즘 고도화 방안

단순한 'Dockerfile 파서' 수준인 현재의 CLI 알고리즘을 '지능형 배포 최적화 도구'로 고도화할 계획이다.

  1. Application-Aware 로직 도입: 단순 문법 검사를 넘어, 디렉토리 내의 app.py, requirements.txt 등을 참조하여 실행될 애플리케이션의 런타임 특성(Python 등)을 동적으로 감지한다.
  2. Build-time Offloading 자동 주입: Python 앱으로 판단될 경우, CLI가 스스로 판단하여 .pyc 강제 생성 명령어를 최적화된 Dockerfile에 자동 주입하도록 로직을 개선한다.

이를 통해 트래픽 폭주 상황에서도 즉각적인 Scale-out 골든타임을 보장하는 완성도 높은 도구로 발전시킬 예정이다.

profile
나를 한줄로

0개의 댓글