컨테이너는 앱과 실행에 필요한 파일을 한 묶음으로 만들어, 호스트의 커널 위에서 격리된 프로세스로 실행하는 방식입니다. 이 글에서는 가상 머신과 무엇이 다른지, 리눅스의 namespace와 cgroup이 격리를 어떻게 만드는지, Docker가 어떤 구성 요소를 거쳐 컨테이너를 띄우는지 정리합니다.
컨테이너부터 Kubernetes까지 시리즈 1편입니다. 전체 순서는 시리즈 목차에 있고, Docker 설치는 [Docker] Ubuntu에 설치 스크립트로 Docker 설치를 참고합니다.
1. 컨테이너가 필요한 이유
"제 PC에서는 되는데 서버에서는 안 돼요"는 대부분 실행 환경 차이에서 생깁니다. 파이썬 버전, 설치된 라이브러리, OS 설정이 조금만 달라도 같은 코드가 다르게 동작합니다. 컨테이너는 앱과 앱이 쓰는 라이브러리, 설정을 이미지(Image) 하나로 묶어서 어느 서버에서든 같은 모습으로 실행하게 해 줍니다.
이름 그대로 화물 컨테이너와 비슷합니다. 안에 무엇이 들었든 크기와 고정 장치가 표준이라 배, 기차, 트럭 어디에나 그대로 실을 수 있습니다. 소프트웨어 컨테이너도 안에 파이썬 앱이 있든 자바 앱이 있든 docker run 한 가지 방법으로 실행합니다.
2. 가상 머신과 비교
가상 머신(VM)은 Hypervisor 위에 OS 전체를 하나 더 올리고, 컨테이너는 호스트의 커널을 함께 쓰면서 프로세스만 격리합니다. 집에 비유하면 VM은 기초와 배관을 따로 갖춘 단독주택이고, 컨테이너는 건물의 기초와 배관은 같이 쓰고 현관문만 따로 있는 아파트 호실입니다.

VM은 VM마다 Guest OS 커널이 있고, 컨테이너는 Host OS 커널 하나를 함께 씁니다
| 항목 | 가상 머신 | 컨테이너 |
| 격리 단위 | OS 전체 | 프로세스 |
| 커널 | VM마다 따로 | 호스트 커널을 함께 씀 |
| 시작 시간 | OS 부팅이 필요해 수십 초 이상 | 프로세스 시작이라 보통 1초 안팎 |
| 크기 | OS를 포함해 GB 단위 | 앱과 라이브러리만 담아 MB 단위부터 |
| 격리 강도 | Hypervisor 경계로 강함 | 커널을 공유해 상대적으로 약함 |
| 다른 OS 실행 | 가능(리눅스 위 Windows 등) | 호스트와 같은 커널만 가능 |
리눅스 컨테이너는 리눅스 커널이 있어야 돌아갑니다. 그래서 Mac이나 Windows의 Docker Desktop은 안에 작은 리눅스 VM을 두고 그 위에서 컨테이너를 실행합니다. 둘은 경쟁 관계라기보다 함께 쓰는 경우가 많고, 클라우드에서도 VM 위에 컨테이너를 올려 운영합니다.
3. 격리를 만드는 namespace와 cgroup
컨테이너는 특별한 가상 장치가 아니라 리눅스 커널 기능 두 가지를 조합한 일반 프로세스입니다. namespace는 프로세스가 볼 수 있는 범위를 나누고, cgroup은 쓸 수 있는 자원의 양을 제한합니다. namespace가 칸막이라면 cgroup은 칸마다 달린 전기 계량기와 차단기입니다.

호스트에서는 PID 4331인 nginx가, 컨테이너 안에서는 자기 자신만 보이는 PID 1로 보입니다
| 타입 | 격리 대상 | 컨테이너 안에서 보이는 모습 |
| PID | 프로세스 번호 | 앱이 PID 1, 다른 프로세스는 안 보임 |
| NET | 네트워크 인터페이스, IP, 포트 | 자기만의 eth0과 IP |
| MNT | 파일시스템 마운트 | 이미지의 파일만 보임 |
| UTS | hostname | 컨테이너 ID가 hostname |
| IPC | 공유 메모리 등 프로세스 간 통신 | 다른 컨테이너와 분리 |
| USER | 사용자 ID 매핑 | Docker 기본값에서는 사용하지 않음 |
cgroup으로는 CPU, 메모리, 프로세스 수 같은 자원의 상한을 정합니다. docker run의 --memory, --cpus 옵션이 cgroup 설정으로 바뀌고, 메모리 상한을 넘은 컨테이너는 커널이 강제로 종료(OOM Kill)해서 종료 코드 137로 끝납니다.
4. Docker 구성 요소
docker run 명령 한 줄은 여러 구성 요소를 거쳐 컨테이너가 됩니다. 각자 맡은 일이 나뉘어 있어서, Kubernetes는 이 중 containerd부터 아래만 가져다 씁니다.

runc는 namespace와 cgroup을 설정해 프로세스를 띄운 뒤 빠지고, 이후는 containerd-shim이 관리합니다
| 항목 | 용도 |
| docker CLI | 사용자가 입력하는 명령을 dockerd API로 보냄 |
| dockerd | Docker API 서버, 빌드·네트워크·볼륨 관리 /var/run/docker.sock으로 요청을 받음 |
| containerd | 이미지를 받고 컨테이너의 생성·삭제를 관리 |
| containerd-shim | 컨테이너마다 하나씩 떠서 입출력과 종료 코드를 관리 dockerd를 재시작해도 컨테이너가 유지되는 이유 |
| runc | namespace와 cgroup을 설정해 프로세스를 시작하는 OCI 런타임 |
| Registry | 이미지를 보관하는 서버(Docker Hub, ACR 등) |
Kubernetes는 1.24부터 dockerd를 거치는 연결(dockershim)을 없애고, kubelet이 CRI로 containerd 같은 런타임을 직접 호출합니다. Docker로 만든 이미지는 표준(OCI) 형식이라 Kubernetes에서도 그대로 씁니다.
5. 직접 해 보기
로컬에 설치한 Docker에서 nginx 컨테이너를 띄우고, 위에서 설명한 격리를 눈으로 확인합니다.
docker run -d --name web -p 8080:80 nginx:alpine
docker ps
브라우저에서 http://localhost:8080 에 접속하면 nginx 기본 페이지가 보이고, docker ps 목록에 web 컨테이너가 Up 상태로 나옵니다. 이어서 컨테이너 안과 호스트에서 같은 프로세스를 비교합니다.
docker exec web ps
docker exec web hostname
docker top web
컨테이너 안의 ps에는 nginx 프로세스만 보이고 master가 PID 1입니다. hostname은 컨테이너 ID의 앞 12자리이고, docker top은 같은 프로세스를 호스트 기준 PID로 보여 줘서 번호가 다릅니다. Docker Desktop에서는 이 호스트가 내부 리눅스 VM입니다.
다음은 cgroup으로 자원을 제한한 컨테이너입니다.
docker run -d --name limited --memory 64m --cpus 0.5 nginx:alpine
docker stats --no-stream limited
MEM USAGE / LIMIT 열의 LIMIT가 64MiB로 나오면 제한이 걸린 것입니다. --cpus 0.5는 CPU 1개 시간의 절반까지만 쓰게 합니다. 확인이 끝나면 docker rm -f web limited로 두 컨테이너를 지웁니다.
6. 운영 시 주의점
| 항목 | 조치 |
| 커널 공유 | 커널 취약점은 모든 컨테이너에 영향을 줌 호스트 커널을 업데이트하고, 신뢰할 수 없는 코드는 VM이나 gVisor, Kata Containers로 격리 |
| root 실행 | USER namespace를 쓰지 않으면 컨테이너 안 root는 호스트의 root와 같은 UID 이미지에서 일반 사용자로 실행(3편에서 다룸) |
| 자원 제한 | 제한이 없으면 컨테이너 하나가 호스트 메모리를 다 쓸 수 있음 --memory, --cpus 지정 |
| 데이터 | 컨테이너를 지우면 안에서 쓴 파일도 사라짐 남길 데이터는 볼륨에 저장(4편에서 다룸) |
| --privileged | 격리를 대부분 해제하므로 쓰지 않음 필요한 권한만 --cap-add로 추가 |
| docker.sock 공유 | 컨테이너에 /var/run/docker.sock을 마운트하면 호스트 root 권한과 같음 |
7. 정리
- 컨테이너는 앱과 라이브러리를 이미지로 묶어 호스트 커널 위에서 격리된 프로세스로 실행하는 방식입니다.
- namespace는 보이는 범위를, cgroup은 쓸 수 있는 자원의 양을 나눠 격리를 만듭니다.
- docker CLI, dockerd, containerd, runc가 단계별로 일을 나누고, Kubernetes는 containerd부터 직접 사용합니다.
다음 글 2편에서는 컨테이너를 만드는 틀인 이미지와 레이어를 다룹니다.
참고
'DevOps > Docker' 카테고리의 다른 글
| [Docker] Dockerfile 작성과 멀티 스테이지 빌드 정리 (0) | 2026.09.28 |
|---|---|
| [Docker] 이미지와 레이어 정리 (0) | 2026.09.28 |
| [Docker] Dockerfile 명령어 정리 (0) | 2021.09.03 |
| [Docker] 자주 쓰는 도커 명령어 정리 (0) | 2021.07.30 |
| [Docker] docker commit으로 이미지 만들고 삭제 (0) | 2021.02.13 |