인사이트

인사이트

컨테이너 이미지 다이어트: distroless·멀티스테이지로 크기와 공격면 줄이기

컨테이너 이미지 다이어트 distroless·멀티스테이지로 크기와 공격면 줄이기

🤖 AI Summary

컨테이너 이미지가 무거우면 내려받고 배포하는 데 시간이 더 들고, 그 안에 든 패키지가 많을수록 취약점과 CVE 노이즈도 함께 늘어납니다. 이미지를 줄이는 두 축은 명확합니다. 첫째, 멀티스테이지 빌드로 컴파일러 같은 빌드 도구를 최종 이미지에서 떼어내고 실행에 필요한 산출물만 남깁니다. 둘째, distroless처럼 셸도 패키지 매니저도 없는 최소 베이스를 골라 처음부터 들어가는 것을 줄입니다. 구글 distroless 문서 기준 가장 작은 static 이미지는 약 2MiB로 데비안(124MiB)의 2% 미만이고, 불필요한 것이 없으니 공격면과 스캐너 노이즈도 함께 줄어듭니다. 다만 셸이 없으면 컨테이너 안을 직접 들여다보기 어려워, 디버깅은 :debug 이미지로 나눠 쓰는 절충이 필요합니다. 이 글은 그 방법과 트레이드오프를 1차 출처로 정리합니다.

블로그 목차

이미지가 무거우면 크기만 무거운 게 아닙니다

컨테이너 이미지가 커지면 가장 먼저 눈에 띄는 건 배포 속도입니다. 노드마다 이미지를 내려받고 올리는 시간이 길어지죠. 그런데 문제는 속도에서 끝나지 않습니다. 이미지 안에 든 패키지가 많을수록 취약점이 유입될 통로도 함께 늘어납니다. 도커 공식 문서는 작은 베이스 이미지가 "이미지 크기를 줄일 뿐 아니라 의존성을 통해 유입되는 취약점의 수도 최소화한다"고 설명합니다.

정리하면 이미지를 가볍게 만드는 일은 두 가지를 동시에 챙기는 작업입니다. 배포 속도공격면(attack surface)이죠. 그리고 그 둘을 줄이는 방법은 크게 두 갈래입니다. 만드는 과정에서 군더더기를 떼어내는 멀티스테이지 빌드, 그리고 처음부터 최소만 담긴 베이스를 고르는 최소 베이스 이미지입니다. 하나씩 보겠습니다.




멀티스테이지 빌드: 빌드 도구를 결과물에서 떼어내기

애플리케이션을 빌드하려면 컴파일러나 빌드 도구가 필요합니다. 하지만 막상 실행할 때는 그 도구들이 필요 없습니다. 문제는 하나의 스테이지로만 이미지를 만들면 빌드에 쓴 도구가 최종 이미지에 그대로 남는다는 점이죠. 멀티스테이지 빌드는 이 낭비를 없애기 위한 방식입니다.

도커 공식 문서의 설명은 이렇습니다. 멀티스테이지 빌드에서는 하나의 Dockerfile에 여러 개의 FROM 문을 두고, 각 FROM은 서로 다른 베이스를 쓸 수 있으며 저마다 새로운 스테이지를 시작합니다. 그런 다음 한 스테이지에서 다른 스테이지로 COPY --from을 써서 "원하는 산출물만 선택적으로 복사하고, 원하지 않는 것은 모두 남겨 둡니다".

흐름은 단순합니다. 앞 스테이지에서 컴파일과 설치를 끝내고, 뒤 스테이지에는 실행에 필요한 결과물만 옮깁니다. 도커 문서의 표현을 그대로 빌리면 결과는 "바이너리 외에는 아무것도 없는 아주 작은 프로덕션 이미지"이고, 애플리케이션을 빌드하는 데 필요한 도구는 하나도 포함되지 않습니다. 스테이지에 AS 이름을 붙여 두면 나중에 순서가 바뀌어도 COPY가 깨지지 않아 유지보수도 편해집니다.




distroless: 셸도 패키지 매니저도 없는 런타임

멀티스테이지로 군더더기를 떼어냈다면, 다음은 뒤 스테이지의 베이스를 무엇으로 두느냐입니다. 여기서 최소 베이스라는 선택지가 등장합니다. 도커는 최소 베이스로 알파인(도커 문서 기준 6MB 미만)을 권장하는데, 알파인은 작긴 해도 여전히 완전한 리눅스 배포판이라 셸과 패키지 매니저를 갖고 있습니다. 더 줄이고 싶다면 구글의 distroless가 대안입니다.

distroless 문서의 정의는 명확합니다. distroless 이미지는 "애플리케이션과 그 런타임 의존성만" 담고, "패키지 매니저나 셸, 그 밖에 표준 리눅스 배포판에서 흔히 보는 프로그램을 포함하지 않습니다". 넣지 않았으니 크기가 작습니다. 문서 기준 가장 작은 static-debian13 이미지는 약 2MiB로, 알파인(약 5MiB)의 절반이고 데비안(124MiB)의 2% 미만입니다.

크기보다 중요한 건 보안 측면의 효과입니다. 불필요한 프로그램이 없으니 취약점 스캐너가 훑을 대상 자체가 줄어듭니다. distroless 문서는 이를 두고 "스캐너(예: CVE)의 신호 대 잡음비를 개선하고, 출처(provenance)를 확인해야 하는 부담을 꼭 필요한 것만으로 줄인다"고 설명합니다. 셸이 없으면 침입자가 컨테이너 안에서 쓸 수 있는 도구도 그만큼 사라지고요. 참고로 쿠버네티스도 v1.15부터 자체 이미지에 distroless를 쓰고 있습니다.

한 가지 선을 그어 두겠습니다. 이미지에 무엇이 들었는지 스캔하고 서명·목록으로 검증하는 공급망 보안은 이 글의 범위가 아닙니다. 그 주제는 앞서 컨테이너 공급망 보안(Trivy·SBOM) 글에서 따로 다뤘습니다. 여기서는 이미지를 애초에 작고 얇게 만드는 빌드 단계에 집중합니다.

베이스 이미지 크기 비교 (근사치)




작을수록 좋기만 할까: 트레이드오프

이미지를 얇게 만들수록 크기와 공격면은 줄지만, 대신 컨테이너 안에서 직접 상태를 들여다보기가 어려워집니다. distroless는 기본적으로 셸이 없기 때문이죠. 이 때문에 두 가지를 챙겨야 합니다. 하나는 ENTRYPOINT를 셸을 거치지 않는 벡터 형식으로 지정하는 것, 다른 하나는 문제를 볼 때 쓸 디버깅 경로를 마련해 두는 것입니다.

distroless는 이를 위해 이미지마다 busybox 셸이 들어간 :debug 태그를 제공합니다. 문서의 안내는 간단합니다. 살펴봐야 할 때는 최종 이미지를 :debug로 바꿔 빌드한 뒤 셸로 진입해 확인하고, 운영에는 다시 셸이 없는 기본 이미지를 쓰면 됩니다. 즉 운영은 최소로, 디버깅은 따로라는 원칙입니다.

여기에 도커 문서가 권하는 습관을 더하면 효과가 커집니다. 있으면 좋을 것 같다는 이유로 불필요한 패키지를 설치하지 않는 것입니다. 문서는 그렇게 하면 "복잡도, 의존성, 파일 크기, 빌드 시간이 모두 줄어든다"고 정리합니다. 빌드에 넣을 필요 없는 파일은 .dockerignore로 처음부터 제외하고요. 결국 이미지 다이어트는 특별한 기교가 아니라, 넣기 전에 정말 필요한지 묻는 습관에 가깝습니다.

이미지를 얇게 만드는 순서

정리하면 순서는 이렇습니다. 멀티스테이지로 빌드 도구를 떼어내고, 뒤 스테이지의 베이스를 최소로 고르고, 넣을 필요 없는 것은 처음부터 넣지 않습니다. 크기가 줄면 배포가 빨라지고, 안에 든 것이 줄면 공격면과 스캐너가 훑을 대상도 줄어듭니다. 얇게 만든 대가로 디버깅 편의가 줄지만, 그건 :debug 이미지처럼 운영과 분리된 경로로 풀 수 있습니다.




이것만 기억하세요

컨테이너 이미지 다이어트의 축은 둘입니다. 멀티스테이지 빌드로 빌드 도구를 결과물에서 떼어내고, distroless 같은 최소 베이스로 처음부터 적게 담는 것입니다. 넣은 게 적으면 크기·공격면·CVE 스캐너 노이즈가 함께 줄어듭니다. 단, distroless는 셸이 없어 디버깅은 :debug 이미지로 따로 처리하는 절충이 필요합니다.




자주 묻는 질문 (FAQ)

Q. 컨테이너 이미지가 크면 왜 문제인가요?

큰 이미지는 내려받고 배포하는 데 시간이 더 걸리고, 안에 든 패키지가 많을수록 취약점도 함께 늘어나요. 도커 공식 문서도 작은 베이스 이미지가 크기를 줄일 뿐 아니라 의존성을 통해 유입되는 취약점의 수를 최소화한다고 설명합니다. 그래서 이미지를 줄이는 일은 배포 속도만이 아니라 공격면 관리와도 이어집니다.

Q. 멀티스테이지 빌드가 이미지를 어떻게 줄이나요?

하나의 Dockerfile에 여러 개의 FROM 문을 두어 각 FROM이 새 스테이지를 시작해요. 앞 스테이지에서 컴파일과 설치를 끝낸 뒤 COPY --from으로 실행에 필요한 산출물만 뒤 스테이지로 옮기고, 나머지는 남겨 둡니다. 그러면 컴파일러 같은 빌드 도구가 빠진, 필요한 것만 담긴 작은 이미지가 만들어집니다.

Q. distroless 이미지는 무엇이고 알파인과 뭐가 다른가요?

distroless는 애플리케이션과 런타임 의존성만 담고 패키지 매니저나 셸을 넣지 않은 이미지예요. 알파인은 작긴 해도 완전한 리눅스 배포판이라 셸과 패키지 매니저를 갖고 있죠. 구글 distroless 문서 기준 가장 작은 static 이미지는 약 2MiB로, 알파인(약 5MiB)의 절반이고 데비안(124MiB)의 2% 미만입니다.

Q. distroless는 셸이 없는데 디버깅은 어떻게 하나요?

각 이미지마다 busybox 셸이 든 :debug 태그를 따로 제공해요. 살펴봐야 할 때는 최종 이미지를 :debug로 바꿔 빌드한 뒤 셸로 들어가 확인하고, 운영에는 다시 셸 없는 기본 이미지를 쓰면 됩니다. 셸이 없으니 ENTRYPOINT는 셸을 거치지 않는 벡터 형식으로 지정해야 합니다.

Q. 이미지를 무조건 작게만 만들면 되나요?

작게 만들수록 크기와 공격면에는 좋지만, 셸과 도구가 사라지면 컨테이너 안을 직접 들여다보기 어려워져요. 그래서 운영 이미지는 최소로 유지하되 디버깅은 :debug 이미지나 별도 도구로 처리하는 식으로 나누는 편이 현실적입니다. 무엇을 남기고 뺄지는 팀의 운영·디버깅 방식에 맞춰 정하면 됩니다.

비용 절감부터 차별화된 속도와 안정적 운영까지
기업에 최적화된 IT 환경을 지원합니다

비용 절감부터 차별화된 속도와
안정적 운영까지 기업에 최적화된 IT 환경을 지원합니다

비용 절감부터
차별화된 속도와 안정적 운영까지
기업에 최적화된 IT 환경을 지원합니다

(주)스피디

경기도 성남시 수정구 위례서일로 18, 1101호 (위례 더존메디컬타워)

TEL 031-697-8413

FAX 02-6455-4743

E.mail sales@speedykorea.com

© SPEEDY. All rights reserved

(주)스피디

경기도 성남시 수정구 위례서일로 18, 1101호
(위례 더존메디컬타워)


TEL 031-697-8413

FAX 02-6455-4743

E.mail sales@speedykorea.com

© SPEEDY. All rights reserved

(주)스피디

경기도 성남시 수정구 위례서일로 18, 1101호
(위례 더존메디컬타워)


TEL 031-697-8413

FAX 02-6455-4743

E.mail sales@speedykorea.com

© SPEEDY. All rights reserved