한줄요약
리눅스 네임스페이스는 커널 기반의 자원 격리 메커니즘으로, 프로세스에게 독립적인 시스템 뷰를 제공하여 컨테이너 기술의 핵심 기반을 이룹니다.
Linux 네임스페이스란?
네임스페이스는 2008년 리눅스 커널 버전 2.6.24에서 본격적으로 도입되었습니다. 이 기능은 프로세스가 사용할 수 있는 리소스의 집합을 정의하며, 각 프로세스에게 독립적인 시스템 뷰(System View)를 제공하여 서로 간섭 없이 독립적으로 동작할 수 있도록 해줍니다. 특히, 마운트 포인트, 네트워크 스택, 프로세스 간 통신(IPC, Inter-Process Communication) 유틸리티 등 전역 운영체제 리소스를 세밀하게 분할할 수 있게 해줍니다. 또한, 네임스페이스의 강력한 특징은 실행 중인 프로세스가 제한 사항을 인지하지 못한 채 시스템 리소스에 대한 접근을 제한합니다.
네임스페이스들에 대한 정보는 /proc/<pid>/ns 디렉토리 아래에서 확인할 수 있습니다.
# 현재 프로세스 ID 확인
[root@rocky ~]# echo $$
2150
# 네임스페이스 정보 확인
[root@rocky ~]# ls /proc/$$/ns -al
total 0
dr-x--x--x. 2 root root 0 Oct 13 12:14 .
dr-xr-xr-x. 9 root root 0 Oct 13 11:56 ..
lrwxrwxrwx. 1 root root 0 Oct 13 12:14 cgroup -> 'cgroup:[4026531835]'
lrwxrwxrwx. 1 root root 0 Oct 13 12:14 ipc -> 'ipc:[4026531839]'
lrwxrwxrwx. 1 root root 0 Oct 13 12:14 mnt -> 'mnt:[4026531840]'
lrwxrwxrwx. 1 root root 0 Oct 13 12:14 net -> 'net:[4026531992]'
lrwxrwxrwx. 1 root root 0 Oct 13 12:14 pid -> 'pid:[4026531836]'
lrwxrwxrwx. 1 root root 0 Oct 13 12:14 time -> 'time:[4026531834]'
lrwxrwxrwx. 1 root root 0 Oct 13 12:14 user -> 'user:[4026531837]'
lrwxrwxrwx. 1 root root 0 Oct 13 12:14 uts -> 'uts:[4026531838]'
네임스페이스를 사용하는 이유
네임스페이스는 자원 격리가 필수적인 컨테이너 기술 (Docker, Kubernetes, Podman, LXC 등)의 핵심 기반을 제공합니다.
- 격리 (Isolation): 한 네임스페이스 내에서 리소스를 변경하거나 제거해도 다른 네임스페이스에는 영향을 미치지 않아 안정적인 서비스 운영이 가능합니다.
- 세밀한 제어 및 자원 공유: 특정 네임스페이스를 공유하도록 설정할 수 있어, 리소스의 독립성을 유지하면서도 협력이 필요한 경우 부분적으로 공유 환경을 설정할 수 있습니다.
- 보안 강화: 네임스페이스는 공격 표면(Attack Surface)을 줄여줍니다. 한 네임스페이스 내에서 발생하는 보안 위협이 다른 네임스페이스로 확산되는 것을 방지합니다.
- 실행 중인 프로세스는 자신이 격리된 환경에 있다는 사실을 인지하지 못하고 안전하게 실행됩니다.
네임스페이스 생성방법
네임스페이스는 주로 다음 두 가지 시스템 콜을 통해 생성할 수 있습니다.
네임스페이스는 clone() 시스템 콜에 하나 이상의 CLONE_NEW* 플래그를 지정하여 생성됩니다. 또는 unshare() 시스템 콜을 통해서도 생성될 수 있습니다. clone()과 unshare()의 차이는 clone()은 새로운 네임스페이스 세트 안에 프로세스를 생성하고, unshare()는 현재의 프로세스를 새로운 네임스페이스 세트 안으로 이동시킨다는 차이가 있습니다. 추가적으로, 이미 존재하는 네임스페이스에 진입하는 setns() 시스템 콜도 있습니다. 새로운 네임스페이스는 해당 프로세스의 부모의 네임스페이스를 상속받습니다.
네임스페이스 종류
리눅스 커널에는 현재 총 8가지의 네임스페이스가 있습니다.
- PID namespace: 시스템 프로세스 트리 격리
- NET namespace: 호스트 네트워크 스택 격리
- MNT namespace: 호스트 파일 시스템 마운트 포인트 격리
- UTS namespace: 호스트 네임 격리
- IPC namespace: IPC 유틸리티 격리
- USER namespace: 시스템 유저 ID 격리
- CGROUP namespace: 호스트의 가상 cgroup 파일시스템 격리
- Time namespace: 시스템 시간 격리
PID namespace

기존의 리눅스 시스템은 부팅 시 시작되는 systemd (혹은 init) 프로세스가 PID 1가지며, 모든 프로세스는 이 트리의 자식으로 존재합니다. 그러나 PID 네임스페이스를 사용하면 시스템의 주 프로세스 트리와 완전히 격리된 중첩된 프로세스 트리를 만들 수 있습니다. PID 네임스페이스에서 시작한 프로세스는 그 네임스페이스에서 PID 1을 부여받게 됩니다. 이 프로세스는 마치 독립된 운영체제의 init 프로세스처럼 동작하며, 해당 네임스페이스 내의 모든 프로세스를 관리하는 권한을 갖게 됩니다.
PID namespace 생성방법
# 새로운 PID 네임스페이스 생성
# --mount-proc 플래그는 해당 네임스페이스 전용의 새로운 "/proc" 파일시스템을 마운트하도록 지시
[user2@rocky2 ~]$ sudo unshare --fork --pid --mount-proc /bin/bash
# 새로운 네임스페이스에서 프로세스 목록 확인
[root@rocky2 user2]# ps
PID TTY TIME CMD
1 pts/0 00:00:00 bash <-- 이 bash 프로세스가 새 네임스페이스에서 PID=1이 됨
30 pts/0 00:00:00 ps
NET namespace
네트워크 네임스페이스는 호스트의 네트워크 자원을 격리하고 분리합니다. 격리된 환경 내의 프로세스는 호스트와 독립된 자신만의 네트워크 (인터페이스, 라우팅 규칙, 방화벽 규칙, 소켓 정보 등)을 가질 수 있게 됩니다.
# 네트워크 네임스페이스 예시
# 루트 네트워크 네임스페이스
[root@rocky2 ~]# ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: ens18: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
link/ether bc:24:11:db:3f:b4 brd ff:ff:ff:ff:ff:ff
altname enp0s18
...
[root@rocky2 ~]# ip route
default via 192.168.0.1 dev ens18 proto dhcp src 192.168.0.88 metric 100
192.168.0.0/24 dev ens18 proto kernel scope link src 192.168.0.88 metric 100
...
[root@rocky2 ~]# iptables --list-rules
-P INPUT ACCEPT
-P FORWARD ACCEPT
-P OUTPUT ACCEPT
-N LIBVIRT_INP
# 새로 생성된 네트워크 네임스페이스
[root@rocky2 ~]# unshare --net /bin/bash
[root@rocky2 ~]# ip route
[root@rocky2 ~]# iptables --list-rules
-P INPUT ACCEPT
-P FORWARD ACCEPT
-P OUTPUT ACCEPT
네임스페이스끼리 연결하기

새로운 네트워크 네임스페이스를 다른 네트워크 네임스페이스와 연결하기 위해서는, 한 쌍의 가상 인터페이스가 필요합니다. 다음의 과정은 새로운 네트워크 네임스페이스를 생성하고 한쌍의 인터페이스를 생성한뒤, 하나씩 네임스페이스에 연결하는 과정입니다. 먼저 네임스페이스를 생성하고 루트 네임스페이스에서 인터페이스를 활성화 합니다.
# 새로운 네트워크 네임스페이스(test) 생성 후 확인
[root@rocky2 ~]# ip netns add test
[root@rocky2 ~]# ls /var/run/netns
test
# 한 쌍의 가상 인터페이스 생성 (veth0와 ceth0)
[root@rocky2 ~]# ip link add veth0 type veth peer name ceth0
[root@rocky2 ~]# ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
...
4: ceth0@veth0: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether 52:4b:0a:7f:b0:59 brd ff:ff:ff:ff:ff:ff
5: veth0@ceth0: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether 1a:30:9b:b3:26:c5 brd ff:ff:ff:ff:ff:ff
# 새로 생성한 네트워크 네임스페이스에 인터페이스 한쪽(ceth0)을 연결
[root@rocky2 ~]# ip link set ceth0 netns test
# ceth0가 사라졌는지 확인
[root@rocky2 ~]# ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
...
5: veth0@if4: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether 1a:30:9b:b3:26:c5 brd ff:ff:ff:ff:ff:ff link-netns test
# 인터페이스를 활성화하고 IP 할당
[root@rocky2 ~]# ip link set veth0 up
[root@rocky2 ~]# ip addr add 172.12.0.11/24 dev veth0
루트 네임스페이스에서 인터페이스가 정상적으로 활성화가 됬다면 새로 생성한 네임스페이스(test)에서도 연결합니다.
# test 네임스페이스로 진입
[root@rocky2 ~]# nsenter --net=/var/run/netns/test /bin/bash
# 위에서 할당한 ceth0 인터페이스가 있는지 확인
[root@rocky2 ~]# ip link
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
4: ceth0@if5: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether 52:4b:0a:7f:b0:59 brd ff:ff:ff:ff:ff:ff link-netnsid 0
# 인터페이스 활성화 후 IP 할당
[root@rocky2 ~]# ip link set lo up
[root@rocky2 ~]# ip link set ceth0 up
[root@rocky2 ~]# ip addr add 172.12.0.12/24 dev ceth0
[root@rocky2 ~]# ip addr | grep ceth
4: ceth0@if5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
inet 172.12.0.12/24 scope global ceth0
네임스페이스 두 곳에서 정상적으로 연결했다면 통신이 되는지 테스트해봅니다.
# 루트 네임스페이스에서
[root@rocky2 ~]# ping 172.12.0.12
PING 172.12.0.12 (172.12.0.12) 56(84) bytes of data.
64 bytes from 172.12.0.12: icmp_seq=1 ttl=64 time=0.034 ms
64 bytes from 172.12.0.12: icmp_seq=2 ttl=64 time=0.035 ms
...
# test 네임스페이스에서
[root@rocky2 ~]# tcpdump
dropped privs to tcpdump
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on ceth0, link-type EN10MB (Ethernet), capture size 262144 bytes
17:24:22.164692 IP 172.12.0.11 > rocky2: ICMP echo request, id 2, seq 5, length 64
17:24:22.164707 IP rocky2 > 172.12.0.11: ICMP echo reply, id 2, seq 5, length 64
17:24:23.185151 IP 172.12.0.11 > rocky2: ICMP echo request, id 2, seq 6, length 64
17:24:23.185164 IP rocky2 > 172.12.0.11: ICMP echo reply, id 2, seq 6, length 64
...
위와 같이 통신이 된다면 네임스페이스 간의 연결이 된 것입니다. 이 외에도 여러개의 네임스페이스를 연결하기 위해 브릿지를 생성하여 루트 네임스페이스에서 브릿지와 가상 인터페이스(veth0)를 묶어서 LAN 처럼 사용할 수 도 있고 인터넷과 네임스페이스를 연결하기 위해 게이트웨이 구성과 NAT를 통해 통신할 수도 있습니다. 마지막으로 네트워크 네임스페이스에 대해서 정리하자면:
- 특정 네트워크 네임스페이스 내의 프로세스들은 자신만의 독립적인 네트워크 스택을 가지며, 여기에는 네트워크 인터페이스, 라우팅 테이블, iptables 규칙, 소켓(ss, netstat) 등이 포함됩니다.
- 네트워크 네임스페이스 간의 연결은 두 개의 가상 인터페이스(veth pair)를 통해 이루어집니다.
- 동일한 네임스페이스 내에서 격리된 네트워크 스택 간의 통신은 브릿지를 통해 이루어집니다.
- 네트워크 네임스페이스는 일부 프로세스만 외부 세계와 통신할 수 있도록 제한하는 "박스"를 시뮬레이션하는 데 사용할 수 있습니다. (일부 네트워크 네임스페이스의 라우팅 규칙에서 호스트의 기본 게이트웨이를 제거하여)
User namespace
사용자 네임스페이스는 프로세스에 대한 사용자 ID(UID), 그룹 ID(GID), 루트 디렉토리 등 보안 관련 식별자 및 속성을 격리하는 커널 기능입니다. 이를 통해 프로세스는 자신이 속한 네임스페이스 내에서는 권한(privilege)을 가질 수 있지만, 외부 OS에서는 권한이 없는 상태를 유지할 수 있습니다.
초기상태 및 생성방법
유저 네임스페이스는 일반 사용자도 사용자 네임스페이스를 생성할 수 있습니다. 생성 초기에 사용자 ID 매핑이 없는 경우, 새 네임스페이스 내부의 프로세스는 외부 자원(예: 파일 시스템)에 접근 시 매핑 테이블에 없는 ID로 판단하여, 루트 네임스페이스에서 오버플로우 UID 값(일반적으로 65534, nobody/nogroup)에 매핑합니다. 따라서 새 네임스페이스 내부에서 파일을 생성하면, 네임스페이스 내부에서는 nobody 소유로 보이지만, 루트 네임스페이스에서 보면 해당 파일을 생성한 프로세스의 원래 UID 소유로 보입니다.
[user2@rocky1 ~]$ unshare --user /bin/bash
[nobody@rocky1 ~]$ id
uid=65534(nobody) gid=65534(nobody) groups=65534(nobody)
[nobody@rocky1 ~]$ touch uid_test
[nobody@rocky1 ~]$ ls -l uid_test
-rw-rw-r--. 1 nobody nobody 0 Oct 14 11:27 uid_test
[user2@rocky1 ~]$ ls -l uid_test
-rw-rw-r--. 1 user2 user2 0 Oct 14 11:27 uid_test
UID 및 GID 매핑
프로세스가 네임스페이스내에서 UID 0으로 동작할 필요가 있거나 파일 시스템 접근이 필요한 경우 매핑을 사용합니다. 매핑은 /proc/<PID>/uid_map 및 /proc/<PID>/gid_map 파일을 통해 설정됩니다. 포맷은 다음과 같습니다.
ID-inside-ns ID-outside-ns length
ID-inside-ns는 새 네임스페이스 내부에서 매핑이 시잘될 ID, ID-outside-ns : 새 네임스페이서 외부에서 매핑이 시작될 ID를 의미합니다. 매핑 방법은 다음과 같습니다.
[user2@rocky1 bash1]$ ps -ef | grep /bin/bash
root 19878 1 0 Oct13 ? 00:00:00 /bin/bash /usr/sbin/ksmtuned
user2 99672 98029 0 11:24 pts/0 00:00:00 /bin/bash <--
[user2@rocky1 bash1]$ echo "0 1001 65535" | sudo tee /proc/99672/uid_map
[nobody@rocky1 ~]$ id
uid=0(root) gid=65534(nobody)
이 매핑 설정을 통해, 네임스페이스 내부의 UID 0 프로세스는 바깥세상(호스트 OS)에서 UID 1001의 권한으로 활동할 수 있게 됩니다.
참고로, 매핑에는 규칙이 있습니다. 먼저, 매핑 설정은 프로세스가 속한 네임스페이스당 한 번만 가능합니다. 또한, 매핑을 설정하는 프로세스는 CAP_SETUID 또는 CAP_SETGID 권한을 가지고 있어야 합니다. 그리고 매핑을 작성하는 프로세스는 대상 프로세스의 네임스페이스 또는 그 직계 부모 네임스페이스 내에 있어야 합니다.
결론적으로 새 사용자 네임스페이스가 생성될 때, 해당 네임스페이스를 생성한 프로세스는 새 네임스페이스 내에서만 UID 0 권한을 부여받으며, 이 권한을 사용하여 해당 네임스페이스의 UID 및 GID 매핑을 설정할 수 있습니다.
MNT namespace
마운트 네임스페이스는 파일 시스템의 마운트 포인트(Mount Point)를 격리합니다. 이는 컨테이너가 호스트 시스템의 파일 시스템을 변경하거나 액세스하지 않고도 자신만의 루트 파일 시스템 구조와 마운트된 볼륨을 가질 수 있도록 합니다. 새로운 마운트 네임스페이스를 생성하면, 해당 네임스페이스는 부모의 마운트 구조를 복사하여 시작하지만, 이후의 모든 마운트 및 언마운트 작업은 격리되어 이루어집니다.
# 새로운 마운트 네임스페이스 생성
[root@rocky1 ~]# unshare --mount /bin/bash
# 격리된 환경 내에서 임시 파일 시스템 마운트
[root@rocky1 ~]# mount -t tmpfs none /mnt/test
[root@rocky1 ~]# df -h | grep test
none 386M 0 386M 0% /mnt/test
# 격리된 환경 종료 후 호스트(부모) 네임스페이스에서 확인 -> 아무것도 출력되지 않음
[nobody@rocky1 ~]$ df -h | grep test
[nobody@rocky1 ~]$ mount | grep test
UTS namespace
UTS 네임스페이스는 호스트 시스템의 이름과 도메인 이름을 격리합니다. 이를 통해 각 컨테이너는 호스트 시스템 이름에 영향을 주지 않고 자신만의 고유한 호스트 이름 및 도메인 이름을 설정할 수 있습니다.