[Linux] namespace (unshare, chroot)
시스템 리소스를 논리적으로 분리하는 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