System & Network/Linux

[Linux] namespace (unshare, chroot)

EndiYou 2025. 3. 14. 14:26

시스템 리소스를 논리적으로 분리하는 namespace에 대해서 정리한다.


namespace

cgroup은 프로세스가 사용할 수 있는 물리자원을 제한하고, namespace는 사용하면 프로세스가 볼 수 있는 파일을 제한한다. 프로세스를 특정 namespace에 할당하면 해당 namespace가 허용하는 것들만 볼 수 있다. namespace를 이용해 프로세스가 접근할 수 있는 파일을 제한하는 원리와 방법을 정리한다.

 

namespace 종류

프로세스는 namespace type 별로 하나의 공간에 소속된다. 별도의 설정이 없을 경우 namespace는 종류 별로 1개씩 생성되어 있지만, namespace를 추가해서 프로세스를 배정할 수 있다.

  • pid : Process ID를 격리한다. 각 네임스페이스는 자체적인 프로세스 트리를 갖는다.
  • network : 네트워크 스택을 격리한다. 각 네임스페이스는 자체 IP 주소, Routing Table, Socket을 갖는다.
  • mount : 파일 시스템 마운트 지점을 격리한다. 네임스페이스 별 독립적인 마운트 트리를 갖는다.
  • uts : 호스트명과 도메인명을 격리한다.
  • ipc : 프로세스 간 통신 리소스를 격리한다.
  • user : 사용자와 그룹 ID를 격리한다.
  • cgroup : Control Group을 격리한다.
  • time : 시스템 시계를 격리한다.

 

namespace 목록

리눅스에서 lsns (List System namespace) 명령어를 사용하면 현재 존재하는 namespace 를 볼 수 있다. lsns 명령은 /proc 파일 시스템을 읽어서 결과를 반환하는데 일반 사용자가 실행한 결과와 루트 사용자가 실행한 결과가 다르다.

  • 일반 사용자 
ubuntu@ip-10-0-0-220:/Workshop$ lsns
        NS TYPE   NPROCS   PID USER   COMMAND
4026531834 time       28   643 ubuntu /usr/lib/code-server/lib/node /usr/lib/code-server
4026531835 cgroup     28   643 ubuntu /usr/lib/code-server/lib/node /usr/lib/code-server
4026531836 pid        28   643 ubuntu /usr/lib/code-server/lib/node /usr/lib/code-server
4026531837 user       28   643 ubuntu /usr/lib/code-server/lib/node /usr/lib/code-server
4026531838 uts        28   643 ubuntu /usr/lib/code-server/lib/node /usr/lib/code-server
4026531839 ipc        28   643 ubuntu /usr/lib/code-server/lib/node /usr/lib/code-server
4026531840 net        28   643 ubuntu /usr/lib/code-server/lib/node /usr/lib/code-server
4026531841 mnt        28   643 ubuntu /usr/lib/code-server/lib/node /usr/lib/code-server
  • 루트 사용자
ubuntu@ip-10-0-0-220:/Workshop$ sudo lsns
        NS TYPE   NPROCS   PID USER            COMMAND
4026531834 time      212     1 root            /sbin/init
4026531835 cgroup    212     1 root            /sbin/init
4026531836 pid       212     1 root            /sbin/init
4026531837 user      212     1 root            /sbin/init
4026531838 uts       206     1 root            /sbin/init
4026531839 ipc       212     1 root            /sbin/init
4026531840 net       212     1 root            /sbin/init
4026531841 mnt       199     1 root            /sbin/init
4026532573 mnt         1   259 root            ├─/usr/lib/systemd/systemd-udevd
4026532574 uts         1   259 root            ├─/usr/lib/systemd/systemd-udevd
4026532594 mnt         1   560 systemd-resolve ├─/usr/lib/systemd/systemd-resolved
4026532603 mnt         1   598 systemd-network ├─/usr/lib/systemd/systemd-networkd
4026532635 mnt         1   677 polkitd         ├─/usr/lib/polkit-1/polkitd --no-debug
4026532636 mnt         2   901 _chrony         ├─/usr/sbin/chronyd -F 1
4026532637 uts         1   694 root            ├─/usr/lib/systemd/systemd-logind
4026532638 uts         2   901 _chrony         ├─/usr/sbin/chronyd -F 1
4026532692 uts         1   919 syslog          ├─/usr/sbin/rsyslogd -n -iNONE
4026532693 mnt         1   668 root            ├─/usr/sbin/irqbalance
4026532694 mnt         1   671 memcache        ├─/usr/bin/memcached -m 64 -p 11211 -u memcache -l 127.0.0.1 -l ::1 -P /var/run/memcached/memcached.pid
4026532695 mnt         1   694 root            ├─/usr/lib/systemd/systemd-logind
4026532701 uts         1   677 polkitd         ├─/usr/lib/polkit-1/polkitd --no-debug
4026532702 mnt         2   970 root            ├─/usr/sbin/haproxy -Ws -f /etc/haproxy/haproxy.cfg -p /run/haproxy.pid -S /run/haproxy-master.sock
4026532703 mnt         1   979 root            └─/usr/sbin/ModemManager
4026531862 mnt         1    38 root            kdevtmpfs

 

unshare Command

unshare 명령어를 사용하면 별도의 namespace를 생성할 수 있다. unshare 명령은 부모와 공유하지 않는 namespace 공간에 프로그램을 실행할 때 사용하는 명령어다. 자식 프로세스가 fork()에 의해 생성되면서 부모 메모리 주소와는 별개의 가상 메모리 주소를 할당 받는 Copy-on-Write 방식으로 설정을 상속 받는다.

더보기

Copy on Write 

  • 부모와 자식 프로세스가 서로 다른 가상 메모리 공간을 할당 받지만 같은 물리 메모리 공간을 참조한다.
  • 자식 프로세스에서 변경 사항이 없는 경우 같은 물리 메모리 공간만을 사용한다.
  • 자식 프로세스에서 값을 변경하게 되면, 수정이 발생한 내용만 별도의 물리 메모리 공간에 저장한다.
  • 변경이 발생하지 않은 데이터는 부모와 같은 메모리 공간을 참고하고, 변경된 부분은 새로 할당 받은 물리 메모리 공간은 참조한다.

 

uts namespace 격리

Unix Timesharing System (UTS) Namespace 는 유닉스 시분할 시스템으로 시스템의 호스트 이름과 도메인을 분리해주는 공간이다. 프로세스를 추가로 생성한 uts namespace에 할당한 다음 호스트 이름을 변경하면, 새로 격리된 namespace 안에 적용되기 때문에 호스트 단말기의 호스트 이름은 변하지 않는다.

  • 호스트 단말기의 hostname 확인
ubuntu@ip-10-0-0-220:/Workshop$ hostname
ip-10-0-0-220
  • uts namespace 생성 후 bash shell 실행 
ubuntu@ip-10-0-0-220:/Workshop$ sudo unshare --uts bash
root@ip-10-0-0-220:/Workshop# hostname
ip-10-0-0-220
  • hostname 변경
root@ip-10-0-0-220:/Workshop# hostname new
root@ip-10-0-0-220:/Workshop# hostname
new
  • 리눅스 터미널 추가 후 namespace 확인
ubuntu@ip-10-0-0-220:/Workshop$ sudo lsns -t uts
        NS TYPE NPROCS    PID USER    COMMAND
4026531838 uts     207      1 root    /sbin/init
4026532574 uts       1    259 root    ├─/usr/lib/systemd/systemd-udevd
4026532637 uts       1    694 root    ├─/usr/lib/systemd/systemd-logind
4026532638 uts       2    901 _chrony ├─/usr/sbin/chronyd -F 1
4026532692 uts       1    919 syslog  ├─/usr/sbin/rsyslogd -n -iNONE
4026532701 uts       1    677 polkitd └─/usr/lib/polkit-1/polkitd --no-debug
4026532634 uts       1 457854 root    bash
ubuntu@ip-10-0-0-220:/Workshop$ hostname
ip-10-0-0-220
  • bash shell 종료 후 hostname 확인
root@ip-10-0-0-220:/Workshop# exit
exit
ubuntu@ip-10-0-0-220:/Workshop$ hostname
ip-10-0-0-220

 

pid namespace 격리

프로세스 id 도 별도의 namespace를 생성하면 호스트 단말기에서 사용 중인 다른 프로세스들과 격리되어 구성된다.

  • pid namespace 생성
ubuntu@ip-10-0-0-220:/Workshop$ sudo unshare --pid --fork bash
root@ip-10-0-0-220:/Workshop#
  • 리눅스 터미널 추가 후 namespace 확인 ※ NPROCS 항목은 namespace에서 실행 중인 프로세스의 수를 나타낸다.
ubuntu@ip-10-0-0-220:/Workshop$ sudo lsns -t pid
        NS TYPE NPROCS    PID USER COMMAND
4026531836 pid     218      1 root /sbin/init
4026532634 pid       1 477540 root bash
  • namespace 내부의 프로세스 목록 조회
root@ip-10-0-0-220:/Workshop# ps -aux
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root           1  0.0  0.0  22972 13944 ?        Ss   Mar13   0:03 /sbin/init
root           2  0.0  0.0      0     0 ?        S    Mar13   0:00 [kthreadd]
...
root      477537  0.0  0.0  17144  6912 pts/7    S+   06:44   0:00 sudo unshare --pid --fork bash
root      477538  0.0  0.0  17144  2488 pts/4    Ss   06:44   0:00 sudo unshare --pid --fork bash
root      477539  0.0  0.0   5692  2048 pts/4    S    06:44   0:00 unshare --pid --fork bash
root      477540  0.0  0.0   7604  4480 pts/4    S    06:44   0:00 bash
root      477867  0.0  0.0   6112  1920 ?        S    06:45   0:00 sleep 60
root      478042  0.0  0.0  12316  5376 pts/4    R+   06:45   0:00 ps -aux

pid namespace를 생성 후 sh 프로세스를 할당 했지만, 추가 설정이 필요한 부분이 있다. ps 명령을 이용하면 자신이 속한 namespace의 프로세스 id만 보여야 하는데, ps의 특성상 /proc 경로에 있는 파일을 읽어서 프로세스 목록을 보여 주기 때문에 chroot를 통해 별도의 루트 디렉터리를 구성해서 ps가 동작하게 해야 한다.

  • chroot 테스트용 신규 루트 디렉터리 생성
ubuntu@ip-10-0-0-220:/Workshop$ mkdir new_root_directory
ubuntu@ip-10-0-0-220:/Workshop$ cd new_root_directory/
  • 리눅스 파일 시스템 구성 
ubuntu@ip-10-0-0-220:/Workshop/new_root_directory$ curl -o alpine.tar.gz https://dl-cdn.alpinelinux.org/v3.20/releases/x86_64/alpine-minirootfs-3.20.0-x86_64.tar.gz
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 3405k  100 3405k    0     0  1573k      0  0:00:02  0:00:02 --:--:-- 1573k
ubuntu@ip-10-0-0-220:/Workshop/new_root_directory$ tar xzvf alpine.tar.gz 
./
./sys/
./srv/
...
ubuntu@ip-10-0-0-220:/Workshop/new_root_directory$ ls
alpine.tar.gz  bin  dev  etc  home  lib  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
ubuntu@ip-10-0-0-220:/Workshop/new_root_directory$ cd ..
ubuntu@ip-10-0-0-220:/Workshop$
  • pid namespace 생성 및 chroot 적용
ubuntu@ip-10-0-0-220:/Workshop$ sudo unshare --pid --fork chroot ./new_root_directory sh
/ # ls
alpine.tar.gz  dev            home           media          opt            root           sbin           sys            usr
bin            etc            lib            mnt            proc           run            srv            tmp            var
/ # ps
PID   USER     TIME  COMMAND
/ # ls proc
/ #

최초 chroot 적용 후 프로세스 목록을 조회하면 /proc 폴더가 비어 있기 때문에 아무것도 조회되지 않는다. 커널이 해당 디렉터리에 프로세스 정보를 채우게 하려면 proc 타입의 파일 시스템을 Mount 해 주어야 한다. 

  • proc 타입 파일 시스템 마운트 후 프로세스 목록 조회
/ # mount -t proc proc proc
/ # ls proc
1                  consoles           execdomains        kallsyms           latency_stats      mtrr               slabinfo           timer_list
13                 cpuinfo            fb                 kcore              loadavg            net                softirqs           tty
acpi               crypto             filesystems        key-users          locks              pagetypeinfo       stat               uptime
bootconfig         devices            fs                 keys               mdstat             partitions         swaps              version
buddyinfo          diskstats          interrupts         kmsg               meminfo            pressure           sys                version_signature
bus                dma                iomem              kpagecgroup        misc               schedstat          sysrq-trigger      vmallocinfo
cgroups            driver             ioports            kpagecount         modules            scsi               sysvipc            vmstat
cmdline            dynamic_debug      irq                kpageflags         mounts             self               thread-self        zoneinfo
/ # ps
PID   USER     TIME  COMMAND
    1 root      0:00 sh
   14 root      0:00 ps