본문 바로가기
일반IT

방치된 컨테이너 이미지, 조용한 시한폭탄이 되는 이유

by gasbugs 2025. 8. 4.

https://docs.docker.com/engine/storage/drivers/

컨테이너 기술, 특히 도커와 쿠버네티스는 현대적인 애플리케이션 개발과 배포의 표준이 되었습니다. 우리는 docker pull, docker build 명령어를 통해 손쉽게 필요한 환경을 구성하고 애플리케이션을 실행합니다. 하지만 이 편리함 뒤에 숨겨진 관리의 중요성을 간과하고 있지는 않으신가요?

마치 창고에 물건을 쌓아두기만 하고 정리하지 않으면 발 디딜 틈이 없어지는 것처럼, 컨테이너 이미지를 제대로 관리하지 않으면 심각한 성능 저하, 보안 위협, 비용 증가라는 부작용을 초래할 수 있습니다. 오늘은 방치된 컨테이너 이미지가 어떻게 조용한 시한폭탄이 되는지, 그 위험성을 상세히 알아보겠습니다.


1. 디스크 사용량의 무한 증식: 조용한 디스크 암살자 💾

가장 직관적으로 체감할 수 있는 문제입니다. 개발과 테스트를 반복하다 보면 컨테이너 호스트의 디스크 용량이 어느새 꽉 차 있는 경험을 해보셨을 겁니다. 이는 대부분 관리되지 않는 이미지들 때문입니다.

  • 댕글링 이미지 (Dangling Images): 소스 코드를 수정하고 이미지를 다시 빌드하면, 기존 이미지는 이름과 태그(repository:tag)를 잃고 <none>:<none> 상태가 됩니다. 이를 '댕글링 이미지'라고 부릅니다. 이들은 더 이상 사용되지 않지만, 명시적으로 삭제해주지 않으면 디스크 공간을 계속 차지합니다.
  • 사용하지 않는 구버전 이미지: ubuntu:22.04를 사용하다 ubuntu:24.04로 업데이트했을 때, 이전 버전인 22.04 이미지는 그대로 남아있습니다. 테스트를 위해 내려받았던 수많은 버전의 이미지들 역시 마찬가지입니다.
  • 중첩된 이미지 레이어: 컨테이너 이미지는 여러 개의 읽기 전용 '레이어(Layer)'로 구성됩니다. 이미지를 빌드하고 업데이트하는 과정에서 공통 레이어는 공유되지만, 새로운 레이어들이 계속해서 쌓이면서 디스크 사용량을 늘립니다.

💥 부작용: 서버의 디스크가 가득 차면 더 이상 새로운 이미지를 빌드하거나 내려받을 수 없게 됩니다. 심각한 경우, 실행 중인 애플리케이션의 로깅이 중단되거나 시스템 전체가 불안정해지는 상황으로 이어질 수 있습니다.


2. 알려진 보안 취약점의 온상: 잠금장치 없는 대문 💣

"일단 만들었으니 괜찮아"라는 생각은 매우 위험합니다. 당신이 사용한 베이스 이미지(Base Image)는 수많은 시스템 라이브러리와 패키지를 포함하고 있으며, 여기에는 시간이 지나면서 새로운 보안 취약점(CVE)이 발견될 수 있습니다.

  • 오래된 베이스 이미지: node:18 이미지를 1년 전에 받아 그대로 사용하고 있다면, 그동안 OpenSSL, curl 등 핵심 라이브러리에서 발견된 수많은 보안 취약점에 그대로 노출된 상태입니다.
  • 취약점 스캔 부재: 정기적으로 이미지의 취약점을 스캔하고 패치하지 않으면, 공격자는 이미 알려진 공격 코드를 이용해 손쉽게 컨테이너의 제어권을 탈취할 수 있습니다.

💥 부작용: 취약점이 있는 컨테이너는 공격자에게 시스템 내부로 들어오는 '정문'을 열어주는 것과 같습니다. 컨테이너 내부 데이터 유출은 물론, 권한 상승 공격을 통해 호스트 시스템 전체나 내부 네트워크까지 장악당하는 최악의 시나리오로 이어질 수 있습니다.


3. 민감 정보의 유출 통로: 이미지 속에 숨겨진 비밀번호 🤫

컨테이너 이미지의 레이어는 한 번 생성되면 해당 레이어의 내용은 변경되지 않습니다. 많은 개발자들이 이 특징을 간과하고 이미지 내부에 민감 정보를 남기는 실수를 저지릅니다.

  • Dockerfile에 하드코딩: Dockerfile에 ENV API_KEY="..." 와 같이 비밀번호, API 키 등을 직접 작성하는 경우.
  • 설정 파일 복사: DB 접속 정보 등이 포함된 설정 파일을 COPY 명령어로 이미지에 포함시키는 경우.
  • 빌드 중 사용된 키 유출: private git repository 접근을 위해 사용한 SSH 키나 인증 토큰을 이미지 빌드 중간 단계에 포함시킨 후, 다음 레이어에서 RM 명령어로 삭제하는 경우가 대표적입니다. 파일을 삭제하더라도 이전 레이어에는 해당 파일이 그대로 남아있습니다.

💥 부작용: 이렇게 빌드된 이미지가 외부(예: Docker Hub)에 유출되거나 접근 권한을 탈취당하면, 공격자는 docker history나 이미지 레이어 분석 도구를 통해 이미지 속에 박제된 모든 민감 정보를 손쉽게 빼낼 수 있습니다. 이는 곧바로 심각한 데이터 유출 사고로 이어집니다.


4. CI/CD 파이프라인의 병목: 느려지는 빌드와 배포 🐢

이미지의 크기는 개발 생산성과 직결됩니다. 불필요하게 무거운 이미지는 전체 개발 및 배포 사이클을 느리게 만드는 주범입니다.

  • 거대한 베이스 이미지 사용: 간단한 Go 바이너리 하나를 실행하기 위해 수백 MB 크기의 ubuntu 전체 이미지를 사용하는 것은 매우 비효율적입니다.
  • .dockerignore 미사용: .git 폴더, 로컬 개발 로그, node_modules 와 같이 빌드에 전혀 필요 없는 파일들까지 이미지 빌드 컨텍스트에 포함시켜 빌드 속도를 저하하고 이미지 크기를 키웁니다.
  • 비효율적인 레이어 캐싱: Dockerfile 작성 순서가 비효율적이면(예: 의존성 설치보다 소스 코드 복사를 먼저 수행), 코드가 한 줄만 바뀌어도 매번 무거운 의존성 라이브러리를 새로 설치하게 되어 빌드 시간이 기하급수적으로 늘어납니다.

💥 부작용: 빌드 시간이 길어지면 개발자의 대기 시간이 늘어나 생산성이 저하됩니다. 또한, 커진 이미지는 레지스트리에 푸시(Push)하고 서버에서 풀(Pull)하는 데 더 많은 시간과 네트워크 대역폭을 소모시켜 배포, 롤백, 오토스케일링 속도를 현저히 떨어뜨립니다.

결론: 이미지 관리는 선택이 아닌 필수

컨테이너 이미지는 더 이상 '한 번 만들고 끝'인 존재가 아닙니다. 디스크 관리, 보안, 성능, 비용 등 시스템 전반에 영향을 미치는 핵심 관리 대상입니다.

주기적으로 사용하지 않는 이미지를 삭제하고(docker image prune), 이미지 취약점을 스캔하며, 최소한의 기능만 담은 베이스 이미지(distroless, alpine)를 사용하고, 멀티 스테이지 빌드와 .dockerignore를 적극 활용하는 것은 건강한 컨테이너 생태계를 유지하기 위한 기본 수칙입니다. 지금 바로 여러분의 서버에 접속해 docker images 명령어를 실행해보세요. 잠자고 있는 시한폭탄이 당신을 기다리고 있을지도 모릅니다.