AWS Cloud School 13기 67~68일차

Forever 김·2026년 4월 7일

AWS Cloud School

목록 보기
63/97

토이 프로젝트 회고

4월 1일 ~ 4월 7일까지 진행한 도커 토이 프로젝트가 있었다. 3명씩 한 팀을 이뤄 도커에 대한 토이 프로젝트를 진행하였다. 이로 인해 최근 velog를 작성하지 못하여서 오늘은 도커 토이 프로젝트를 회고하는 시간을 가져보도록 하겠다.

주제

컨테이너 이미지 최적화가 인프라 가용성에 미치는 영향 — 실측 데이터 기반 검증


1. 프로젝트 개요 및 실증 목적

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

일반적인 최적화 프로젝트는 "이미지 용량을 줄였다"는 선에서 마무리된다. 하지만 실제 프로덕션 환경에서 이미지 크기 감소가 무중단 배포나 장애 복구 시의 빠른 기동(Startup)을 보장하지는 않는다. 따라서 본 실험은 가장 극단적인 두 가지 장애 상황을 설정하고, 자체 개발한 CLI를 통해 각 시나리오별 3단계(Phase) 병목 구간을 정량화했다. 이를 통해 RTO(목표 복구 시간) 단축과 TCO 관점에서 어떠한 이득이 있는지를 파악하는 것이 목표다.


2. 시나리오별 검증

시나리오 1 : CPU 100% 경합 환경에서의 컨테이너 기동 성능 분석

목적

단일 서버의 CPU 자원을 stress-ng를 통해 100% 점유하여 경합 상황에서, 컨테이너 기반 이미지의 경량화가 실제 미치는 영향을 정량적인 실측 데이터를 통해 검증한다.

측정 지표

저희가 측정한 지표는 크게 세 가지다.

  • startup_ms : docker run 명령부터 HTTP 200 응답까지의 총 소요 시간
  • ctx_switches : 컨테이너 PID를 추적해 커널에서 읽어온 문맥 교환 횟수 (/proc/$PID/schednr_switches)
  • kernel_wait_ms : 프로세스가 CPU를 할당받지 못해 대기한 시간 (cgroup PSI cpu.pressure 기반)

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

측정 환경

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

측정 결과 (5회 반복 교차 측정)

2026-04-03 06:04:08,1,myapp:baseline,2526,192,300
2026-04-03 06:04:16,1,myapp:optimized,2784,183,427
2026-04-03 06:04:25,2,myapp:baseline,2439,226,306
2026-04-03 06:04:33,2,myapp:optimized,3042,423,445
2026-04-03 06:04:41,3,myapp:baseline,2398,219,325
2026-04-03 06:04:50,3,myapp:optimized,3044,353,417
2026-04-03 06:04:58,4,myapp:baseline,2475,193,390
2026-04-03 06:05:07,4,myapp:optimized,3004,313,395
2026-04-03 06:05:15,5,myapp:baseline,2308,145,335
2026-04-03 06:05:24,5,myapp:optimized,3029,318,391
지표BaselineOptimized변화
Startup Time 평균2,429 ms2,980 ms❌ +551 ms (+22.7%) 악화
Kernel Wait Time 평균195 ms318 ms❌ +123 ms (+63.1%) 악화
Context Switches 평균331 회415 회❌ +83 회 (+25.3%) 증가

결과 분석 — 최적화의 역설 (Optimization Paradox)

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

이러한 지연의 근본 원인은 현재 CLI 도구가 가진 '정적 분석' 아키텍처의 한계에 있다고 판단했다.

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

즉, 우리가 만든 CLI는 Dockerfile의 텍스트 문법만을 정적으로 분석하여 경량화 룰을 일괄 주입하기 때문에, 컴파일 오버헤드나 내부 구동 로직까지는 파악하지 못한다. 용량은 줄었으나 기동 시간은 오히려 22.7% 지연되는 역설적인 상황이 발생한 이유다.

스크립트 구축 과정에서의 트러블슈팅

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

백그라운드 부하 프로세스(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)만 기록하도록 로직 개선.

시나리오 2 : Cold Start 기반 재해 복구(DR) 환경에서의 RTO 검증

목적

기존 운영 서버에 치명적인 장애가 발생한 상황을 가정하여, 경량 이미지가 네트워크와 디스크 I/O 병목을 어떻게 극복하고 복구 시간을 단축하는지 검증한다.

측정 방식

작성한 스크립트(verify_full_lifecycle.sh)는 도커 이미지 캐시를 전면 삭제해 Cold Start 환경을 강제한다.

  • Phase 1 & 2 : docker pull 소요 시간 측정 → 네트워크 다운로드 + 디스크 I/O 구간
  • Phase 3 : docker run 직후부터 타겟 엔드포인트에 Readiness Check를 지속적으로 폴링(Polling) → 애플리케이션 초기화 과정의 CPU/Kernel 스케줄링 지연이 반영된 서비스 구동 시간

이를 통해 시스템 총 복구 시간(Total RTO)을 체계적으로 도출한다.

측정 결과

2026-04-06 01:28:14,0206pdh/imgadvisor-test:pre1-original,132764,1232,133996,SUCCESS
2026-04-06 01:29:06,0206pdh/imgadvisor-test:pre1-optimized,29924,1674,31598,SUCCESS
2026-04-06 01:31:05,0206pdh/imgadvisor-test:pre2-original,103930,1806,105736,SUCCESS
2026-04-06 01:31:29,0206pdh/imgadvisor-test:pre2-optimized,13338,1920,15258,SUCCESS
2026-04-06 01:31:52,0206pdh/imgadvisor-test:pre3-original,12417,687,13104,SUCCESS
2026-04-06 01:32:06,0206pdh/imgadvisor-test:pre3-optimized,6518,1535,8053,SUCCESS
Target ImagePull & Extract (ms)Container Ready (ms)Total RTO (ms)
pre1-original132,7641,232133,996
pre1-optimized29,9241,67431,598
pre2-original103,9301,806105,736
pre2-optimized13,3381,92015,258
pre3-original12,41768713,104
pre3-optimized6,5181,5358,053

이미지 크기 vs RTO 단축 요약

케이스이미지 크기 변화Pull 시간 변화RTO 단축총 단축률
pre1507MB → 99.6MB (▼80.4%)132,764ms → 29,924ms (▼77.5%)102,398ms 단축▼76.4%
pre2459MB → 73.3MB (▼84.0%)103,930ms → 13,338ms (▼87.2%)90,478ms 단축▼85.6%
pre3443MB → 64.4MB (▼85.5%)12,417ms → 6,518ms (▼47.5%)5,051ms 단축▼38.5%

Container Ready Time 오버헤드 분석

케이스Original ReadyOptimized Ready증가분
pre11,232 ms1,674 ms+442 ms (+35.9%)
pre21,806 ms1,920 ms+114 ms (+6.3%)
pre3687 ms1,535 ms+848 ms (+123.4%)

모든 케이스에서 optimized 이미지의 Container Ready Time이 소폭 증가하는 일관된 패턴이 관찰되었다. 이는 시나리오 1에서 분석한 .pyc 사전 컴파일 부재로 인한 런타임 초기화 오버헤드와 동일한 원인이다.

그러나 이 런타임 오버헤드는 Cold Start 환경에서 단축된 네트워크(Pull) 시간에 의해 완전히 상쇄되었다. Ready Time 손실이 가장 컸던 pre3의 경우에도, 런타임 초기화에서 848ms가 증가했지만 Pull & Extract에서 5,899ms를 단축해 순 이득(Net Gain) 5,051ms를 달성했다.

지표 분석 결과, 캐시가 없는 Cold Start 장애 상황에서 기존 시스템은 전체 복구 시간(RTO) 중 약 97%를 Pull & Extract 과정에 소모하는 심각한 병목 현상을 보였다. 이미지의 물리적 크기를 평균 80% 이상 경량화한 결과, 해당 구간의 소요 시간을 최대 87.2%까지 단축시킬 수 있었다. 최종적으로 최대 약 102초(102,398ms)의 실질적인 RTO 단축을 이끌어내며, 도커 이미지 최적화가 장애 복구를 결정짓는 중요한 요소임을 데이터로 실증했다.


3. 핵심 결론

RTO 관점
캐시가 없는 Cold Start 장애 복구 환경에서 이미지 경량화는 압도적인 효과를 발휘한다. 전체 RTO의 97%를 차지하는 Pull & Extract 병목을 최대 87.2% 단축하여, 최악의 장애 복구 시간을 최대 102초 앞당겼다.

TCO 관점
이미지 크기를 80% 이상 경량화하여 불필요한 스토리지 낭비를 막고, 데이터 전송량 감소를 통한 구조적인 네트워크 대역폭 비용 절감을 확인했다.

정적 분석의 한계
CPU 경합 환경에서는 이미지 크기 감소가 곧 기동 속도 향상으로 이어지지 않는다. Slim 이미지의 .pyc 부재로 인한 런타임 컴파일 오버헤드가 CPU 포화 상태에서 증폭되어 기동 시간을 오히려 악화시킬 수 있다. 이를 해결하려면 Dockerfile의 Builder stage 마지막에 RUN python -m compileall /opt/venv/lib /app 명령어를 주입하여 런타임 오버헤드를 빌드 타임으로 전가해야 한다.


회고

이렇게 오랜만에 다시 다른 사람들과 팀을 이뤄 주제 선정부터 실험, PPT 작성까지 총 4.5일간 진행을 해보았다.

어려웠던 점은 도커에 대해 깊게 탐구를 하다 보니 우리가 예상치 못한 상황이 발생했다는 것이다. 시나리오 1에서 우리가 예상했을 때는 최적화된 이미지가 CPU 구동 시간이 더 빨라야 한다고 생각했는데, 계속 우리의 예측과 반대로 결과가 나와 어려움을 겪었다. 이를 통해 우리가 너무 우리 가설에 실험을 맞추려 했다는 생각이 들어, 결과를 있는 그대로 받아들이고 왜 빗나갔는지에 대해 분석하는 방향으로 전환했다.

처음 분석할 때는 단순히 파이썬 ENV 설정 문제로 접근했는데, 깊게 파고들수록 .pyc 바이트코드 사전 컴파일 여부라는 근본 원인에 도달할 수 있었다. 주어진 기간 동안 모든 코드에 대해 동적 분석하는 CLI를 만들기에는 버거워서 문제를 분석하고 Next Step을 도출하는 것으로 마무리했지만, 이 과정 자체가 실무에서 트러블슈팅하는 방식과 동일하다는 것을 느꼈다.

이를 통해 모든 것이 내 생각대로 맞다는 확신을 갖지 않고, 있는 그대로를 받아들이면서 내 생각과 왜 다른지, 내가 왜 틀렸는지를 분석하는 것이 중요하다는 것을 다시 한번 깨달았다.

또한 발표에서 강사님의 피드백 중 중요한 수치는 강조하면 좋겠다는 점과 PPT 여백이 너무 많다는 점을 들었기 때문에, 향후 프로젝트에서는 이런 피드백이 나오지 않도록 보완해야겠다.

오랜만에 프로젝트를 해서 재미있었고, 도커를 또 언제 이렇게 깊게 공부할까 라는 생각과 함께 이번 velog를 여기서 끝!

profile
나를 한줄로

0개의 댓글