기타 Lobsters · 2026-10-22

"컨테이너는 실체가 없다?" 직접 만들어보고 알게 된 도커의 기괴한 진실

핵심 요약
  • 한 개발자가 장난삼아 직접 컨테이너 런타임을 구현해보며 배운 도커의 독특하고 기괴한 내부 원리들을 공개했습니다.
  • 컨테이너는 실제 존재하는 기술이 아니라 여러 리눅스 커널 기능(네임스페이스, cgroups 등)을 조합한 껍데기 프로세스이자 단순 폴더에 불과했습니다.
  • 도커 런타임인 runc가 멀티스레드 기반의 Go 언어로 작성되어 발생하는 시스템 콜 및 fork 부재 문제를 온갖 기상천외한 편법으로 해결하고 있음을 짚었습니다.
요약 개발자가 장난삼아 직접 컨테이너 런타임을 만들어보면서 알게 된 도커(Docker)의 기묘한 내부 동작 원리를 정리한 개발 썰입니다. 글쓴이가 직접 파고들며 발견한 가장 놀라운 사실은 '컨테이너'라는 실체 자체가 사실상 존재하지 않는다는 점입니다. 가상 머신(VM)과 달리 컨테이너는 그저 특수한 제약이 걸려 있는 평범한 리눅스 프로세스일 뿐이며, 호스트의 커널에서 직접 실행되기 때문에 경로(/proc)만 제대로 찾아보면 프로세스를 그대로 확인할 수 있습니다. 리눅스 커널 안에는 '컨테이너'라는 단일 기능 자체가 없고, 네임스페이스(격리), cgroups(자원 제한), 그리고 복잡한 네트워크 격리 기능 등 5가지 커널 기능이 바바리코트를 걸쳐 입고 컨테이너인 척하고 있는 구조에 가깝습니다. 심지어 도커 이미지 역시 거창한 가상화 디스크가 아니라 그냥 압축이 풀린 일반 폴더에 불과합니다. 도커는 이미지를 특정 폴더에 내려받은 뒤 chroot나 pivot_root 같은 시스템 콜을 호출해 해당 폴더를 프로세스의 루트 디렉터리로 속여서 실행할 뿐입니다. 또한 도커의 표준 컨테이너 런타임인 runc가 Go 언어로 작성된 점도 기이한 대목으로 꼽힙니다. Go는 기본적으로 멀티스레드 기반이라 단일 스레드 환경에서만 호출해야 하는 리눅스 시스템 콜을 다루기 어렵습니다. 이 때문에 runc는 Go 런타임이 초기화되기도 전에 괴상한 C 언어 모듈을 먼저 띄워 시스템 콜을 처리하는 편법을 씁니다. 게다가 Go에는 fork가 없어, 자식 프로세스를 흉내 내기 위해 /proc/self/exe를 특수 인자와 함께 다시 실행하는 방식을 쓰고 있습니다. 여기에 더해 도커 데몬이 기본적으로 root 권한을 요구하는 구조와, 비(root) 권한 환경에서는 네트워크 스택 전체를 유저 공간에 다시 구현해야 하는 고충 등을 언급하며, 기존에 쓰던 도구를 밑바닥부터 직접 만들어보는 것이 얼마나 큰 배움과 충격을 주는지 전하고 있습니다.
Sponsored · 광고