컨테이너와 Docker
네임스페이스와 cgroups가 프로세스를 격리하는 원리를 익히고 Docker 이미지 레이어와 컨테이너 실행 흐름을 이해합니다.
가상 머신은 강한 격리를 제공하지만, 각 VM이 완전한 OS를 포함하므로 무겁습니다.
부팅에 수십 초가 걸리고, 수 GB의 디스크를 차지합니다.
컨테이너는 같은 커널을 공유하면서 프로세스를 격리하는 경량 가상화 기술입니다.
2013년 Docker의 등장 이후 컨테이너는 소프트웨어 배포의 표준이 되었습니다.
컨테이너 vs 가상 머신
각 VM은 자체 커널과 시스템 서비스를 가지므로 격리는 강하지만 부팅, 메모리, 이미지 비용이 크다.
- Hardware
CPU/mem/disk 물리 자원
- Hypervisor
상 하드웨어 VM마다 분배
- Guest OS
자체 커널 부팅과 패치 필요
- App
라이브러리 포함 VM 내부 실행
| 비교 | 가상 머신 | 컨테이너 |
|---|---|---|
| 격리 수준 | OS 전체 격리 (커널 독립) | 프로세스 수준 격리 (커널 공유) |
| 시작 시간 | 수십 초 ~ 수 분 | 수 밀리초 ~ 수 초 |
| 이미지 크기 | 수 GB | 수십 MB ~ 수백 MB |
| 메모리 오버헤드 | OS당 수백 MB | 프로세스 크기 |
| 밀도 | 서버당 수십 개 | 서버당 수백~수천 개 |
| 보안 격리 | 강함 (하이퍼바이저 경계) | 상대적으로 약함 |
컨테이너는 같은 커널을 공유하므로, 커널 취약점이 발견되면 같은 호스트의 컨테이너들이 함께 영향을 받을 수 있습니다.
멀티테넌트 환경에서는 VM을 사용하거나, VM 위에서 컨테이너를 실행하는 방식을 택합니다.
리눅스 컨테이너의 핵심 기술
컨테이너는 VM처럼 OS를 새로 띄우지 않고 커널 기능을 조합해 별도 실행 세계를 제공한다.
- Namespace
PID/mount/net 보이는 세계 분리
- cgroup
CPU/mem/I/O 사용량 제한
- Runtime
image/rootfs 프로세스 시작
- Host kernel
공유 커널 격리 한계
컨테이너는 완전히 새로운 가상화 기술이라기보다, 리눅스 커널에 이미 있는 격리와 자원 제어 기능을 런타임이 조합해서 제공하는 실행 환경입니다.
네임스페이스 (Namespaces)
프로세스에게 시스템 자원의 독립된 뷰를 제공합니다.
컨테이너 안의 프로세스는 외부를 볼 수 없습니다.
| 네임스페이스 | 격리 대상 | 효과 |
|---|---|---|
| PID | 프로세스 ID | 컨테이너 안에서 PID 1번부터 시작 |
| NET | 네트워크 스택 | 독립된 IP, 포트, 라우팅 테이블 |
| MNT | 파일 시스템 마운트 | 독립된 루트 파일 시스템 |
| UTS | 호스트 이름 | 컨테이너마다 다른 hostname |
| IPC | 프로세스 간 통신 | 독립된 메시지 큐, 세마포어 |
| USER | 사용자/그룹 ID | 컨테이너 안 root ≠ 호스트 root |
| CGROUP | cgroup 뷰 | 자원 제한의 독립적 뷰 |
#define _GNU_SOURCE
#include <stdio.h>
#include <sched.h>
#include <unistd.h>
#include <sys/wait.h>
/* 새 네임스페이스에서 실행될 함수 */
int child_func(void *arg) {
printf("자식 PID (네임스페이스 내부): %d\n", getpid()); /* 1 */
char hostname[] = "container";
sethostname(hostname, sizeof(hostname) - 1);
/* 새 네임스페이스에서 셸 실행 */
execlp("/bin/sh", "sh", NULL);
return 0;
}
int main() {
char stack[65536];
/* 새 PID + UTS + NET 네임스페이스로 프로세스 생성 */
pid_t pid = clone(
child_func,
stack + sizeof(stack),
CLONE_NEWPID | CLONE_NEWUTS | CLONE_NEWNET | SIGCHLD,
NULL
);
printf("호스트에서 본 자식 PID: %d\n", pid); /* 실제 PID */
waitpid(pid, NULL, 0);
return 0;
}cgroups (Control Groups)
프로세스 그룹을 계층적으로 묶고 CPU, 메모리, I/O, PID 같은 자원 사용량을 제한하거나 분배합니다.
# cgroup v2에서 메모리 제한 설정 (개념)
# /sys/fs/cgroup/my-container/
# 메모리 제한: 256MB
echo $((256 * 1024 * 1024)) > /sys/fs/cgroup/my-container/memory.max
# CPU 제한: 50% (100ms 중 50ms만 사용)
echo "50000 100000" > /sys/fs/cgroup/my-container/cpu.max
# PID 개수 제한: 최대 100개 프로세스
echo 100 > /sys/fs/cgroup/my-container/pids.max
# 프로세스를 cgroup에 추가
echo $$ > /sys/fs/cgroup/my-container/cgroup.procs| 컨트롤러 | 제한 대상 | 설정 파일 | 예시 |
|---|---|---|---|
| memory | 메모리 사용량 | memory.max | 512MB |
| cpu | CPU 시간 | cpu.max | 1.5 코어 |
| io | 디스크 I/O | io.max | 100MB/s |
| pids | 프로세스 수 | pids.max | 100개 |
컨테이너 런타임은 cgroup 파일에 한도를 쓰고, 커널은 메모리, CPU, I/O, 프로세스 수를 실행 중에 강제한다.
- 1. 제한값 기록
제한값 기록 런타임이 컨테이너 cgroup에 controller 값을 설정한다.
- 2. 커널이 강제
커널이 강제 프로세스가 한도를 넘으면 지연, 제한, OOM 처리가 일어난다.
- 3. 파일로 확인
파일로 확인 운영자는 cgroup 파일을 읽어 실제 적용 상태를 검증한다.
- 메모리 사용량 제한
memory 메모리 사용량 제한 컨테이너가 지정한 최대 메모리를 넘지 못하게 한다. memory.max
- CPU 시간 배분
cpu CPU 시간 배분 CPU quota나 share로 과도한 점유를 줄인다. cpu.max
- 디스크 I/O 조절
io 디스크 I/O 조절 읽기와 쓰기 대역폭이 다른 서비스에 피해를 주지 않게 한다. io.max
- 프로세스 개수 제한
pids 프로세스 개수 제한 fork 폭주가 호스트 전체 장애로 번지는 것을 막는다. pids.max
- 설정값
런타임 옵션과 cgroup 파일 값이 일치하는가.
- 장애 격리
한 컨테이너의 폭주가 다른 서비스로 번지지 않는가.
- 관측
OOM, throttling, pids 제한 이벤트를 모니터링하는가.
Docker의 동작 원리
Docker는 컨테이너를 쉽게 빌드, 배포, 실행하기 위한 도구입니다.
이미지와 레이어
Docker 이미지(Image)는 컨테이너의 파일 시스템 스냅샷입니다.
이미지는 레이어(Layer)로 구성됩니다.
FROM, RUN, COPY 단계는 변경분을 레이어로 남기고, 실행 중 변경은 컨테이너 쓰기 레이어에 기록된다.
- Base
FROM image 공유 rootfs
- Build
RUN/COPY 읽기 전용 레이어
- Pull cache
digest 재사용 중복 다운로드 감소
- Runtime
writable layer 컨테이너별 변경
이미지 레이어는 읽기 전용이며, 컨테이너가 실행되면 그 위에 쓰기 가능한 레이어가 추가됩니다(Union FS: OverlayFS).
같은 베이스 이미지를 공유하는 컨테이너들은 중복 저장 없이 공통 레이어를 공유하므로 디스크를 절약합니다.
Dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
# non-root 사용자로 실행 (보안)
RUN useradd -r appuser
USER appuser
EXPOSE 8000
CMD ["python", "app.py"]각 명령어(FROM, RUN, COPY)가 하나의 레이어를 생성합니다.
RUN 명령을 합치면 레이어 수를 줄여 이미지 크기를 최적화할 수 있습니다.
Docker 실행
# 이미지 빌드
docker build -t myapp:1.0 .
# 컨테이너 실행 (리소스 제한 + 보안 옵션)
docker run -d \
--name web \
-p 8080:80 \
--memory=512m \
--cpus=1.0 \
--read-only \
--security-opt=no-new-privileges \
nginx:alpine
# 실행 중인 컨테이너 목록
docker ps
# 컨테이너 내부 접속
docker exec -it web /bin/sh
# 리소스 사용량 모니터링
docker stats web
# 컨테이너가 사용하는 네임스페이스 확인
docker inspect --format '{{.State.Pid}}' web
# ls -la /proc/<PID>/ns/--memory=512m은 cgroups로 메모리를 512MB로 제한합니다.
이 한도를 초과하면 OOM Killer가 컨테이너 프로세스를 종료합니다.
컨테이너 격리의 수준과 한계
컨테이너가 같은 커널을 공유한다는 것은 커널 수준의 취약점에 모든 컨테이너가 노출된다는 뜻입니다.
격리 강화 기술
| 기술 | 방식 | 보안 수준 |
|---|---|---|
| rootless 컨테이너 | 엔진을 non-root로 실행 | 중 |
| seccomp | 허용 시스템 콜 제한 | 중상 |
| AppArmor/SELinux | MAC 정책 적용 | 상 |
| user namespace | 컨테이너 root ≠ 호스트 root | 중상 |
| gVisor | 사용자 공간 커널 (시스콜 프록시) | 상 (성능 감소) |
| Kata Containers | 경량 VM 안에서 컨테이너 실행 | 최상 |
| Firecracker | 마이크로 VM (AWS Lambda 사용) | 최상 |
# Docker 기본 seccomp 프로파일은 위험한 시스템 콜 일부를 차단
# 예: mount, reboot, syslog 등 운영체제 전체에 영향을 주는 호출
# 커스텀 seccomp 프로파일 적용
docker run --security-opt seccomp=custom-profile.json myapp실무에서는 보안 요구사항에 따라 적절한 수준을 선택합니다.
일반 웹 서비스는 rootless + seccomp으로 충분하고, 멀티테넌트 환경은 Kata/Firecracker를 고려합니다.
다음 절에서는 컨테이너를 대규모로 관리하는 오케스트레이션과 실무 인프라 선택 기준을 다루겠습니다.
같은 격리처럼 보여도 보호 경계가 다르다. 컨테이너는 빠르고 가볍고, VM은 더 무겁지만 커널 경계가 강하다.
- 호스트 커널 공유
Container Container A Container B namespace · cgroup · seccomp 공유 Host kernel Hardware 격리 수단 namespace로 보이는 자원을 나누고 cgroup으로 사용량을 제한한다. 장점 시작이 빠르고 이미지 배포와 스케일 아웃이 쉽다. 주의 커널 취약점과 privileged 권한은 호스트 경계를 약하게 만든다.
- guest OS와 커널 분리
VM VM A app VM B app Guest kernel A Guest kernel B Hypervisor 격리 경계 Hardware 격리 수단 hypervisor가 가상 하드웨어를 제공하고 각 VM이 자기 OS를 실행한다. 장점 커널 경계가 강해 서로 다른 OS와 높은 보안 요구에 맞다. 비용 부팅, 메모리, 이미지 크기 부담이 컨테이너보다 크다.
- namespace
PID, mount, net처럼 컨테이너 안에서 보이는 세계를 분리한다.
- cgroup
메모리, CPU, I/O, 프로세스 수를 제한해 운영 장애 전파를 막는다.
- security
rootless, seccomp, MAC 정책으로 커널 공유 위험을 낮춘다.
- 일반 웹 서비스
컨테이너에 자원 제한과 기본 보안 정책을 적용한다.
- 멀티테넌트
Kata Containers나 Firecracker처럼 VM 경계를 추가로 고려한다.
- 판단 질문
커널 공유 위험을 받아들일 수 있는지 먼저 확인한다.