docker는 어떻게 내 컴퓨터 안에서 또 다른 OS가 독립적으로 돌아가도록 할 수 있을까?
image를 통해 여러 container들을 만들어보면서, 신기하다... 밖에 생각을 안했다.
컨테이너는 VM처럼 하드웨어를 가상화하는 게 아니다.
리눅스 커널의 기능을 이용해 프로세스를 격리하고, 제한하는 기술을 기반으로 한다.
이 기능은 namespace, cgroup + union fs를 메인으로 한 기술들을 통해 이루어진다.
오늘은 namespace에 대해 알아본다. (사실 다 하려했다가 분량이 넘 길어져서 컷함 ^^7)
https://bentist.tistory.com/73
가상화, 도커 의미(이미지와 컨테이너)
도커가 필요한 이유는 무엇인가? 내가 만든 파워포인트 문서를 다른 컴퓨터에서 열었을 때, 글꼴이 다르게 나오거나 한글파일의 버전이 달라서 글꼴이 깨져서 나온 경험을 해봤을 것이다. 이처
bentist.tistory.com
Namespace
나만의 방을 만들어 주는 것과 같다. 리눅스 커널의 리소스를 논리적으로 분할해, 특정 프로세스들이 나머지 시스템과 격리된 상태에서 리소스를 볼 수 있게 해준다.
커널?
https://namu.wiki/w/%EC%BB%A4%EB%84%90(%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C)
커널(운영체제)
커널(Kernel)은 운영체제 의 핵심이라 할 수 있는 매우 중요한 소프트웨어로 하드웨어의 자원을 자원이 필요한
namu.wiki
커널은 간략히 말해 하드웨어 <-> 프로그램 사이에서 중재자 역할을 하는 OS의 엔진 역할을 하는 녀석이라고 보면 된다.
격리된 상태에서 리소스를 본다?
- 어떤 프로그램을 실행한다고 해보자.
- 실제 시스템(커널 입장에서)에선 이 프로그램이 12345번(가정)이지만 격리된 방안에서 이 프로그램은 자기 자신을 1번이라고 생각한다.
- 즉, 자기가 이 세상의 주인인줄 아는.. 트루먼쇼 주인공이 된다.
- 12345번 PID를 가지는 프로그램은 아래와 같은 특징을 가진다.
- 실제 컴퓨터의 IP와는 전혀 다른, 자기만의 독립된 IP 주소를 가진다.
- 이 방 안에서는 다른 방의 파일이 전혀 보이지 않는다. 나만의 파일 시스템이 보인다.
- 즉, 리소스를 볼 수 있게 해준다.. 라는 말은 시스템 전체를 보여주는 게 아니라, 자신(프로그램)만을 위해 준비된(조작된) 리소스만을 보여준다라는 뜻이다.
- 이는 격리된 환경에서 할당된 것만 보고 있는 상태라는 것을 의미한다.
다시 네임스페이스로 돌아와보자.
위에서도 말을 했듯 네임스페이스는 프로세스에게 독립된 공간을 제공한다.
- 커널 리소스를 논리적으로 쪼개서, 특정 프로세스 그룹이 다른 그룹의 리소스를 보거나 간섭하지 못하게 한다.
핵심 기능: 격리(Isolation)
- 보안: 하나의 서비스가 뚫려도 다른 서비스에 영향을 주지 않도록 피해 반경을 줄인다.
- 독립성: 마치 별도의 VM에 잇는 것(!)처럼 느껴지게 한다.
네임스페이스의 종류
- 리눅스 커널은 다양한 리소스를 격리할 수 있도록 여러 네임스페이스를 제공한다.
| 종류 | 설명 |
| PID (Process ID) | 프로세스 ID를 격리한다. 컨테이너 내부의 프로세스는 자신이 PID 1번(관리자)이라고 생각하지만, 호스트 입장에서는 그냥 수많은 프로세스 중 하나일 뿐이다. |
| Network | 네트워크 스택(IP, 포트, 라우팅 테이블 등)을 격리한다. 각 컨테이너가 고유한 IP를 가질 수 있는 이유다. |
| Mount (MNT) | 파일 시스템 마운트 지점을 격리한다. 호스트 파일 시스템에 영향을 주지 않고 자유롭게 마운트/언마운트가 가능하다. |
| User | UID/GID를 격리한다. 컨테이너 안에서는 root 권한을 가지더라도, 호스트에서는 일반 사용자로 매핑되어 보안을 강화할 수 있다. |
| UTS | 호스트네임과 도메인을 격리합니다. 컨테이너마다 다른 호스트 이름을 가질 수 있게 한다. |
| IPC | 프로세스 간 통신(메시지 큐 등) 자원을 격리한다. |
각 네임스페이스는 언제 사용될까?
| 종류 | 이게 없다면? | 있을 때 어떻게 사용됨? | 실제 사용 예시 |
| PID | 다른 컨테이너 프로세스를 kill 가능 | 자기 프로세스만 보이고 관리 | 컨테이너 내부에서 nginx가 PID 1로 동작 |
| Network | 80포트는 서버당 하나만 사용 가능 | 컨테이너마다 각자 80포트 사용 | Docker가 컨테이너별로 172.17.0.x IP 부여 |
| Mount | 라이브러리 버전 충돌, 설정 파일 공유 | 컨테이너마다 독립된 파일 시스템 | Python 2.7, 3.11 컨테이너가 함께 공존 |
| User | 컨테이너 탈출 시 호스트 root 권한 획득 | 탈출해도 호스트에선 일반 유저 | Rootless Docker로 보안 강화 |
| UTS | 모든 컨테이너가 같은 호스트명 | 컨테이너마다 다른 이름 설정 | 호스트네임 기반 라이선스 체크 우회 |
| IPC | 다른 컨테이너가 메모리 엿보기 가능 | IPC 자원 완전 격리 | DB 컨테이너 간 공유메모리 충돌 방지 |
내부에선 어떻게 사용되길래?
우선 알아야 하는 게 몇가지 있다.
리눅스 커널에서 모든 프로세스는 task_struct 라는 구조체로 관리된다.
프로세스 하나가 생성되면, 커널은 task_struct 하나를 만들어서 이 프로세스의 모든 정보를 기록한다.
https://elixir.bootlin.com/linux/v6.18/source/include/linux/sched.h#L819
sched.h - include/linux/sched.h - Linux source code v6.18 - Bootlin Elixir Cross Referencer
elixir.bootlin.com
이 구조체 안에는 nsproxy라는 명의 포인터가 있다.
- 프로세스가 바라보는 세상(?)을 정의하는 포인터다.
- task_struct를 사람이라고 치면, nsproxy를 VR 안경이라고 생각하면 된다.
- 이걸 바꿔끼우면 다른 세상을 보게 된다.
struct task_struct {
// ...
struct nsproxy *nsproxy; // 이 프로세스 속한 네임스페이스 묶음은 여기를 보면 된다~ 라는 뜻
// ...
};
nsproxy는 아래와 같은 형태로 정의되어 있다.
- cgroup_ns도 있는데, 이건 다음에 다룰 cgroup과 관련된 녀석이다.
struct nsproxy {
refcount_t count;
struct uts_namespace *uts_ns;
struct ipc_namespace *ipc_ns;
struct mnt_namespace *mnt_ns;
struct pid_namespace *pid_ns_for_children;
struct net *net_ns;
struct time_namespace *time_ns;
struct time_namespace *time_ns_for_children;
struct cgroup_namespace *cgroup_ns;
};
프로세스를 만들 때 처음에 fork()를 들어본 적이 있을 거다.
이는 부모와 자식 간 같은 nsproxy를 공유하도록 한다.

결과적으로 bash에서 ls를 실행하면, 둘 다 같은 세상을 본다.
따라서 ls가 본 파일 목록이 곧 bash가 볼 수 있는 파일 목록이 된다.
근데 네임스페이스를 새로 만들면 어떻게 되길래?
- 우선 일반적인 상황을 보자.
컴퓨터 용량 때문에 UTM에 깔았던 OS를 삭제했어서...
도커를 이용해 linux를 실행시켰다
docker run -it --privileged --name my-linux ubuntu:22.04 /bin/bash
root@c563fd5d2604:/# echo $$
1
root@c563fd5d2604:/# ps aux | head -5
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 4140 3040 pts/0 Ss 05:52 0:00 /bin/bash
root 11 0.0 0.0 6440 2368 pts/0 R+ 05:54 0:00 ps aux
root 12 0.0 0.0 2236 832 pts/0 S+ 05:54 0:00 head -5
root@c563fd5d2604:/# hostname
c563fd5d2604
지금 내 bash 프로세스(PID 316)는 호스트의 모든 것을 볼 수 있다.
- 시스템의 모든 프로세스
- 호스트의 hostname
- + @
Linux에는 unshare라는 명령어가 있다.
- 부모랑 같은 namespace 안 쓰고, 내 거를 따로 만들어달라고 한다.
echo $$
// 현재 bash의 PID가 뜰거다. 우리의 경우 c...
unshare --pid --uts --mount --fork --mount-proc bash
echo $$root@c563fd5d2604:/# unshare --pid --uts --mount --fork --mount-proc bash
root@c563fd5d2604:/# echo $$
1
root@c563fd5d2604:/# ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 4140 3168 pts/0 S 05:55 0:00 bash
root 4 0.0 0.0 6440 2368 pts/0 R+ 05:55 0:00 ps aux
root@c563fd5d2604:/# hostname container-test
root@c563fd5d2604:/# hostname
container-test
마찬가지로 PID가 1이 뜨고, 프로세스 목록에 나밖에 없는 것을 확인할 수 있다.
추가로 hostname도 바꿀 수 있다. 확인 시 container-test가 나온다!
근데 다른 터미널(호스트)에서 확인해도 그럴까?
다른 터미널을 하나 띄우자.
# 실행 중인 'my-linux' 컨테이너에 접속한다
docker exec -it my-linux /bin/bash
우측 터미널(호스트)에서 hostname을 확인하면 호스트는 그대로인 것을 확인할 수 있다.
root@c563fd5d2604:/# ps aux | grep bash
root 1 0.0 0.0 4140 3040 pts/0 Ss 05:52 0:00 /bin/bash
root 16 0.0 0.0 2228 904 pts/0 S 05:55 0:00 unshare --pid --uts --mount --fork --mount-proc bash
root 17 0.0 0.0 4140 3168 pts/0 S+ 05:55 0:00 bash
root 24 0.0 0.0 4140 3232 pts/1 Ss 05:57 0:00 /bin/bash
root 34 0.0 0.0 2904 1408 pts/1 S+ 06:01 0:00 grep --color=auto bash
또한 호스트에서 프로세스 목록을 보면 위와 같이 확인할 수 있다. PID 17이 곧 unshare가 만들어낸 격리된 자식이다.
namespace 정보를 호스트(1) / 격리된 녀석(17)을 비교해보자.


앞서 unshare 뒤 flag로 mnt, pid, uts만을 추가했다.
따라서 mnt, pid, uts의 숫자가 다르게 나올텐데, 이는 곧 해당 네임스페이스들이 다른 namespace에 있다는 것을 확인할 수 있다.
명령어를 통해 호스트와 격리된 프로세스의 네임스페이스 ID가 다르다는 것을 확인할 수 있었다.
namespace를 새로 만들면 서로 다른 커널 메모리 구조체(Struct)를 가리킨다.
unshare를 실행하는 순간 커널 내부에서
- 부모 프로세스는 nsproxy 포인터가 0x100번지를 가리키고 있다고 가정해보자.
- unshare가 실행되면 0x100번지의 내용을 복사해 새로운 0x200 번지에 방을 판다.
- 그리고 자식 프로세스의 nsproxy 포인터를 0x200 번지로 바꾼다.
조그음 더 low하게 보면 unshare를 실행하면 clone(...) 명령이 내부적으로 실행된다.
int pid = clone(
child_func,
stack_top,
CLONE_NEWPID | // 새 PID namespace
CLONE_NEWUTS | // 새 UTS namespace (hostname)
CLONE_NEWNS | // 새 Mount namespace
CLONE_NEWNET | // 새 Network namespace
SIGCHLD,
NULL
);
clone은 또!! 내부적으로 아래의 연쇄 동작을 일으킨다.

copy_namespaces의 경우
- CLONE_NEW* 플래그를 확인해서
- CLONE_NEWPID, UTS, NET.. 등 여러 플래그 값을 확인해 들어오면 새로운 (종류에 맞는) namespace를 만들어준다!
/*
* called from clone. This now handles copy for nsproxy and all
* namespaces therein.
*/
int copy_namespaces(u64 flags, struct task_struct *tsk)
{
struct nsproxy *old_ns = tsk->nsproxy;
struct user_namespace *user_ns = task_cred_xxx(tsk, user_ns);
struct nsproxy *new_ns;
if (likely(!(flags & (CLONE_NEWNS | CLONE_NEWUTS | CLONE_NEWIPC |
CLONE_NEWPID | CLONE_NEWNET |
CLONE_NEWCGROUP | CLONE_NEWTIME)))) {
if ((flags & CLONE_VM) ||
likely(old_ns->time_ns_for_children == old_ns->time_ns)) {
get_nsproxy(old_ns);
return 0;
}
} else if (!ns_capable(user_ns, CAP_SYS_ADMIN))
return -EPERM;
/*
* CLONE_NEWIPC must detach from the undolist: after switching
* to a new ipc namespace, the semaphore arrays from the old
* namespace are unreachable. In clone parlance, CLONE_SYSVSEM
* means share undolist with parent, so we must forbid using
* it along with CLONE_NEWIPC.
*/
if ((flags & (CLONE_NEWIPC | CLONE_SYSVSEM)) ==
(CLONE_NEWIPC | CLONE_SYSVSEM))
return -EINVAL;
new_ns = create_new_namespaces(flags, tsk, user_ns, tsk->fs);
if (IS_ERR(new_ns))
return PTR_ERR(new_ns);
if ((flags & CLONE_VM) == 0)
timens_on_fork(new_ns, tsk);
tsk->nsproxy = new_ns;
return 0;
}
여기까지 왔으면.. Docker가 하는 일에 대해 조금 더 쉽게 이해할 수 있다.
만약에 Docker가 없이 직접 컨테이너를 만든다면 우리는 아래의 수많은 과정을 해야될거다..
- C언어로 clone(CLONE_NEWPID | ...) 코드 작성 및 컴파일
- 새로운 root filesystem (이미지) 직접 준비
- pivot_root() 시스템 콜로 루트 디렉토리 변경
- /sys/fs/cgroup/... 경로 찾아가서 직접 숫자(제한 값) 입력...
만약 Docker를 쓴다면?
docker run -it ubuntu bash
그럼 도커가 알아서 커널이랑 왔다리갔다리 하면서 복잡한 과정들을 처리해준다.
즉, 도커는 리눅스 커널의 기능을 편하게 포장해 둔 도구다.
여기까지 Docker의 근본 기술 중 하나인 namespace에 대해 알아봤다.
정리하면,
- namespace는 리눅스 커널이 제공하는 격리 기능이다.
- 프로세스에게 자기만의 세상을 보여주는 VR 안경 같은 역할을 한다.
- Docker는 이걸 직접 clone() 치고 cgroup 설정하고... 하는 귀찮은 일을 대신 해주는 도구다.
그런데 격리는 됐는데...
- 이 녀석이 CPU를 100% 다 먹으면?
- 메모리를 무한정 잡아먹으면?
namespace만으로는 리소스 제한이 안 된다.
다음 글에서는 이 문제를 해결하는 cgroup에 대해 알아보자.
'Infra > Docker' 카테고리의 다른 글
| Container의 근반이 되는 기술에 대해 알아보자(완) UnionFS (0) | 2025.12.11 |
|---|---|
| Container의 근반이 되는 기술에 대해 알아보자(2) cgroup (0) | 2025.12.11 |
| 도커(Docker)로 PHP / Laravel 개발 환경 구축하기 (0) | 2025.12.08 |
| Utility Docker에 대해 알아보자 (0) | 2025.12.07 |
| Docker Compose (0) | 2025.12.06 |