프로세스끼리 시스템 자원 충돌을 막는 것은 중요하다.
웹서버에서, 컨테이너 하나만 실행시키는 건 토이프로젝트에서나 가능하다.
운영환경에서는 여러 웹서버 컨테이너들이 실행된다.
웹서버의 기본 포트 번호는 80.
컨테이너를 이용하게 되면 여러 컨테이너가 동시에 80포트를 사용할 수있다.
반면, 일반적인 프로세스에서는 포트 번호는 중복될 수 없다.
네트워크적, UID, Mount, UTS, IPC, PID 등 여러 네임스페이스들이 있다.
(그 중 PID, 네트워크, 마운트가 특히 중요하다.)
프로세스에 할당되는 고유한 ID
네트워크 디바이스, 포트번호, IP 주소 등
UID와 GID를 독립적으로 가질 수 있게 한다.
mount(장치와 폴더의 연결)에 대하여 독립된 파일시스템 트리를 관리
호스트명, 도메인명을 독자적으로 가져간다.
프로세스간 통신(IPC)에 대해서 namespace별 독립화
프로세스와 컨테이너가 어떤 차이가 있을까?
namespace 실습을 통해 하나하나 확인해보자.
reallinux@ubuntu:~$ docker run -it -d ubuntu:16.04
b7456d853038a716ef3ef95c82af2358f380c8aceef88dda74b3a7bcdcf64d56
reallinux@ubuntu:~$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b7456d853038 ubuntu:16.04 "/bin/bash" 16 seconds ago Up 3 seconds nostalgic_murdock
호스트 서버에서 아래 명령어를 입력하여 bash 프로그램의 PID 확인
reallinux@ubuntu:~$ echo $$
1937
reallinux@ubuntu:~$ ps -ef | grep 1937
reallin+ 1937 1936 0 11:43 pts/0 00:00:00 -bash
reallin+ 2089 1937 0 11:52 pts/0 00:00:00 ps -ef
reallin+ 2090 1937 0 11:52 pts/0 00:00:00 grep --color=auto 1937
reallinux@ubuntu:~$ docker inspect b7456d853038 | grep Pid
"Pid": 2040,
"PidMode": "",
"PidsLimit": null,
컨테이너의 PID는 2040
이다.
우리는 컨테이너도 프로세스라고 배웠다.
호스트 서버에서 해당 프로세스를 찾아보자.
reallinux@ubuntu:~$ ps -ef | grep 2040
root 2040 2019 0 11:51 pts/0 00:00:00 /bin/bash
reallin+ 2107 1937 0 11:54 pts/0 00:00:00 grep --color=auto 2040
하지만 ps -ef 명령어만으론
PID 2040의 프로세스가 컨테이너인지 아닌지 알 수없다.
lsns 명령어로 프로세스의 namespace를 들여다 본다면 구분이 가능하다.
: lists information about the accessible namespaces or a specific namespace
# 도커 컨테이너 프로세스의 namespace
reallinux@ubuntu:~$ sudo lsns -p 2040
[sudo] password for reallinux:
NS TYPE NPROCS PID USER COMMAND
4026531835 cgroup 126 1 root /sbin/init maybe-ubiquity
4026531837 user 126 1 root /sbin/init maybe-ubiquity
4026532191 mnt 1 2040 root /bin/bash
4026532192 uts 1 2040 root /bin/bash
4026532193 ipc 1 2040 root /bin/bash
4026532194 pid 1 2040 root /bin/bash
4026532196 net 1 2040 root /bin/bash
(+ Q. 근데 sudo 없이 하면 왜 다른 결과가 나오지?!)
주목할 점은 프로세스1번의 좌측 네임스페이스 ID와 bash 프로세스의 그것이 일치한다는 것이다.
# bash 프로세스의 namespace
reallinux@ubuntu:~$ sudo lsns -p 1937
NS TYPE NPROCS PID USER COMMAND
4026531835 cgroup 128 1 root /sbin/init maybe-ubiquity
4026531836 pid 127 1 root /sbin/init maybe-ubiquity
4026531837 user 128 1 root /sbin/init maybe-ubiquity
4026531838 uts 127 1 root /sbin/init maybe-ubiquity
4026531839 ipc 127 1 root /sbin/init maybe-ubiquity
4026531840 mnt 122 1 root /sbin/init maybe-ubiquity
4026531992 net 127 1 root /sbin/init maybe-ubiquity
# PID 1번의 namespace
reallinux@ubuntu:~$ sudo lsns -p 1
NS TYPE NPROCS PID USER COMMAND
4026531835 cgroup 128 1 root /sbin/init maybe-ubiquity
4026531836 pid 127 1 root /sbin/init maybe-ubiquity
4026531837 user 128 1 root /sbin/init maybe-ubiquity
4026531838 uts 127 1 root /sbin/init maybe-ubiquity
4026531839 ipc 127 1 root /sbin/init maybe-ubiquity
4026531840 mnt 122 1 root /sbin/init maybe-ubiquity
4026531992 net 127 1 root /sbin/init maybe-ubiquity
기본적으로 모든 프로세스들은 PID 1인 /sbin/init
프로세스의 namespace를 상속받는다.
그래서 1번 프로세스와 bash 프로그램의 namespace는 일치하는 것이다.
하지만 (격리된, 독립된 환경의) 컨테이너는 namespace가 다름을 확인할 수있다.
이상 lsns 명령어로 프로세스와 컨테이너를 구별하는 방법을 살펴봤다.
reallinux@ubuntu:~$ sudo su
root@ubuntu:/home/reallinux# ll /proc/1/ns
# PID 1
total 0
dr-x--x--x 2 root root 0 Feb 16 11:57 ./
dr-xr-xr-x 9 root root 0 Feb 16 11:41 ../
lrwxrwxrwx 1 root root 0 Feb 16 11:57 cgroup -> 'cgroup:[4026531835]'
lrwxrwxrwx 1 root root 0 Feb 16 11:57 ipc -> 'ipc:[4026531839]'
lrwxrwxrwx 1 root root 0 Feb 16 11:57 mnt -> 'mnt:[4026531840]'
lrwxrwxrwx 1 root root 0 Feb 16 11:57 net -> 'net:[4026531992]'
lrwxrwxrwx 1 root root 0 Feb 16 11:57 pid -> 'pid:[4026531836]'
lrwxrwxrwx 1 root root 0 Feb 16 12:21 pid_for_children -> 'pid:[4026531836]'
lrwxrwxrwx 1 root root 0 Feb 16 11:57 user -> 'user:[4026531837]'
lrwxrwxrwx 1 root root 0 Feb 16 11:57 uts -> 'uts:[4026531838]'
# 컨테이너
root@ubuntu:/home/reallinux# ll /proc/2040/ns
total 0
dr-x--x--x 2 root root 0 Feb 16 11:51 ./
dr-xr-xr-x 9 root root 0 Feb 16 11:51 ../
lrwxrwxrwx 1 root root 0 Feb 16 11:57 cgroup -> 'cgroup:[4026531835]'
lrwxrwxrwx 1 root root 0 Feb 16 11:57 ipc -> 'ipc:[4026532193]'
lrwxrwxrwx 1 root root 0 Feb 16 11:57 mnt -> 'mnt:[4026532191]'
lrwxrwxrwx 1 root root 0 Feb 16 11:51 net -> 'net:[4026532196]'
lrwxrwxrwx 1 root root 0 Feb 16 11:57 pid -> 'pid:[4026532194]'
lrwxrwxrwx 1 root root 0 Feb 16 12:21 pid_for_children -> 'pid:[4026532194]'
lrwxrwxrwx 1 root root 0 Feb 16 11:57 user -> 'user:[4026531837]'
lrwxrwxrwx 1 root root 0 Feb 16 11:57 uts -> 'uts:[4026532192]'
# 현재 실행중인 bash
root@ubuntu:/home/reallinux# echo $$
2276
root@ubuntu:/home/reallinux# ll /proc/2276/ns
total 0
dr-x--x--x 2 root root 0 Feb 16 12:22 ./
dr-xr-xr-x 9 root root 0 Feb 16 12:22 ../
lrwxrwxrwx 1 root root 0 Feb 16 12:22 cgroup -> 'cgroup:[4026531835]'
lrwxrwxrwx 1 root root 0 Feb 16 12:22 ipc -> 'ipc:[4026531839]'
lrwxrwxrwx 1 root root 0 Feb 16 12:22 mnt -> 'mnt:[4026531840]'
lrwxrwxrwx 1 root root 0 Feb 16 12:22 net -> 'net:[4026531992]'
lrwxrwxrwx 1 root root 0 Feb 16 12:22 pid -> 'pid:[4026531836]'
lrwxrwxrwx 1 root root 0 Feb 16 12:22 pid_for_children -> 'pid:[4026531836]'
lrwxrwxrwx 1 root root 0 Feb 16 12:22 user -> 'user:[4026531837]'
lrwxrwxrwx 1 root root 0 Feb 16 12:22 uts -> 'uts:[4026531838]'
root@ubuntu:/home/reallinux#
ll /proc/{PID}/ns
명령어를 통해서도 프로세스와 컨테이너는 서로 다른 namespace를 갖고 있음을 확인할 수 있다.
이번엔 도커를 사용하지 말고 다른 방식으로 접근해보자.
직접 격리된 환경을 구축한 프로세스에서도 위의 결과와 동일할까?
# 지난 시간 다운로드했던 rootfs 활용
reallinux@ubuntu:~$ unshare --mount --uts --ipc --net --pid --fork --user --map-root-user chroot rootfs /bin/bash
root@ubuntu:/#
root@ubuntu:/# mount -t proc proc /proc
root@ubuntu:/# ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 12:38 ? 00:00:00 /bin/bash
root 5 1 0 12:40 ? 00:00:00 ps -ef
수동으로 만든 컨테이너 내부 프로세스 ID는 역시나 1이다.
root@ubuntu:/# echo $$
1
(Q. 이 부분 다시 수정 필요)
root 5 1 0 12:40 ? 00:00:00 ps -ef
root@ubuntu:/# 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
이제 새로운 터미널 창에서 아래 명령어를 실행한다.
reallinux@ubuntu:~$ pidof unshare
2498
reallinux@ubuntu:~$ sudo lsns -p 2498
[sudo] password for reallinux:
NS TYPE NPROCS PID USER COMMAND
4026531835 cgroup 134 1 root /sbin/init maybe-ubiquity
4026531836 pid 132 1 root /sbin/init maybe-ubiquity
4026532250 user 2 2498 reallinux unshare --mount --uts --ipc --net --pid --fork --user --map-root-user chroot rootfs /bin/bash
4026532251 mnt 2 2498 reallinux unshare --mount --uts --ipc --net --pid --fork --user --map-root-user chroot rootfs /bin/bash
4026532252 uts 2 2498 reallinux unshare --mount --uts --ipc --net --pid --fork --user --map-root-user chroot rootfs /bin/bash
4026532253 ipc 2 2498 reallinux unshare --mount --uts --ipc --net --pid --fork --user --map-root-user chroot rootfs /bin/bash
4026532256 net 2 2498 reallinux unshare --mount --uts --ipc --net --pid --fork --user --map-root-user chroot rootfs /bin/bash
reallinux@ubuntu:~$ sudo lsns -p 1
NS TYPE NPROCS PID USER COMMAND
4026531835 cgroup 134 1 root /sbin/init maybe-ubiquity
4026531836 pid 132 1 root /sbin/init maybe-ubiquity
4026531837 user 132 1 root /sbin/init maybe-ubiquity
4026531838 uts 131 1 root /sbin/init maybe-ubiquity
4026531839 ipc 131 1 root /sbin/init maybe-ubiquity
4026531840 mnt 126 1 root /sbin/init maybe-ubiquity
4026531992 net 131 1 root /sbin/init maybe-ubiquity
pidof unshare
명령어로 수동으로 생성한 프로세스의 PID를 얻었다.
도커에서의 비교 결과와 동일하다.
직접 격리시킨 프로세스의 namespace와 프로세스 1번의 namespace는 분명 다르다.
ll /proc/{PID}/ns
명령어도 적용해보자.reallinux@ubuntu:~$ sudo su
root@ubuntu:/home/reallinux# ll /proc/2498/ns
total 0
dr-x--x--x 2 reallinux reallinux 0 Feb 16 12:46 ./
dr-xr-xr-x 9 reallinux reallinux 0 Feb 16 12:45 ../
lrwxrwxrwx 1 reallinux reallinux 0 Feb 16 12:46 cgroup -> 'cgroup:[4026531835]'
lrwxrwxrwx 1 reallinux reallinux 0 Feb 16 12:46 ipc -> 'ipc:[4026532253]'
lrwxrwxrwx 1 reallinux reallinux 0 Feb 16 12:46 mnt -> 'mnt:[4026532251]'
lrwxrwxrwx 1 reallinux reallinux 0 Feb 16 12:46 net -> 'net:[4026532256]'
lrwxrwxrwx 1 reallinux reallinux 0 Feb 16 12:46 pid -> 'pid:[4026531836]'
lrwxrwxrwx 1 reallinux reallinux 0 Feb 16 12:48 pid_for_children -> 'pid:[4026532254]'
lrwxrwxrwx 1 reallinux reallinux 0 Feb 16 12:46 user -> 'user:[4026532250]'
lrwxrwxrwx 1 reallinux reallinux 0 Feb 16 12:46 uts -> 'uts:[4026532252]'
root@ubuntu:/home/reallinux# ll /proc/1/ns
total 0
dr-x--x--x 2 root root 0 Feb 16 12:46 ./
dr-xr-xr-x 9 root root 0 Feb 16 11:41 ../
lrwxrwxrwx 1 root root 0 Feb 16 12:46 cgroup -> 'cgroup:[4026531835]'
lrwxrwxrwx 1 root root 0 Feb 16 12:46 ipc -> 'ipc:[4026531839]'
lrwxrwxrwx 1 root root 0 Feb 16 12:46 mnt -> 'mnt:[4026531840]'
lrwxrwxrwx 1 root root 0 Feb 16 12:46 net -> 'net:[4026531992]'
lrwxrwxrwx 1 root root 0 Feb 16 12:46 pid -> 'pid:[4026531836]'
lrwxrwxrwx 1 root root 0 Feb 16 12:48 pid_for_children -> 'pid:[4026531836]'
lrwxrwxrwx 1 root root 0 Feb 16 12:46 user -> 'user:[4026531837]'
lrwxrwxrwx 1 root root 0 Feb 16 12:46 uts -> 'uts:[4026531838]'
마찬가지로 unshare명령어로 만든 프로세스 역시 일반적인 프로세스와는 다른 namespace를 갖는다.
reallinux@ubuntu:~$ whatis nsenter
nsenter (1) - run program with namespaces of other processes
# unshare로 만든 프로세스의 ip 주소
reallinux@ubuntu:~$ pidof unshare
2498
# 일반 프로세스의 ip 주소
reallinux@ubuntu:~$ sudo nsenter -t 2498 -n ip addr
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
reallinux@ubuntu:~$ ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 08:00:27:6c:2f:72 brd ff:ff:ff:ff:ff:ff
inet 10.0.2.15/24 brd 10.0.2.255 scope global dynamic enp0s3
valid_lft 82272sec preferred_lft 82272sec
inet6 fe80::a00:27ff:fe6c:2f72/64 scope link
valid_lft forever preferred_lft forever
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:53:2e:49:5a brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
valid_lft forever preferred_lft forever
inet6 fe80::42:53ff:fe2e:495a/64 scope link
valid_lft forever preferred_lft forever
5: vethd7c91c8@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
link/ether 36:a7:02:4f:0f:cb brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet6 fe80::34a7:2ff:fe4f:fcb/64 scope link
valid_lft forever preferred_lft forever
reallinux@ubuntu:~$
# 도커 컨테이너의 ip 주소
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
4: eth0@if5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
nsenter 명령어로 임의의 프로세스의 namespace가 그대로 적용된 프로세스를 만들고 명령어를 실행시켜 보려고 한다.
도커로도 exec 명령어로 충분히 할 수 있다.
하지만 여기에 비밀이 숨겨져 있다.
내부적으로는 이러한 방식과 명령어를 통해 프로세스를 운용하는 것이다.
즉, 도커는 컨테이너 생성/관리/운영을 편리하게 해주는 단순한 도구에 불과하다.
도커 프로세스 ID인 2040의 namespace를 그대로 상속받는 프로세스를 생성하고 임의의 명령어를 실행시켜보자.
reallinux@ubuntu:~$ sudo nsenter -t 2040 -p -r ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 11:51 pts/0 00:00:00 /bin/bash
root 11 0 28 12:55 ? 00:00:00 ps -ef
# 2040 namespace 활용하여 top 명령어 실행
reallinux@ubuntu:~$ sudo nsenter -t 2040 -p -r top
top - 12:56:23 up 1:14, 0 users, load average: 1.18, 1.08, 1.19
Tasks: 2 total, 1 running, 1 sleeping, 0 stopped, 0 zombie
%Cpu(s): 0.1 us, 1.4 sy, 15.3 ni, 83.1 id, 0.1 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 2034848 total, 86264 free, 166092 used, 1782492 buff/cache
KiB Swap: 2094076 total, 2093552 free, 524 used. 1626348 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1 root 20 0 18228 3144 2728 S 0.0 0.2 0:00.90 bash
12 root 20 0 36652 3124 2680 R 0.0 0.2 0:00.05 top
# 일반 프로세스 top 명령어 실행
reallinux@ubuntu:~$ top
top - 12:57:05 up 1:15, 2 users, load average: 1.13, 1.08, 1.18
Tasks: 129 total, 4 running, 71 sleeping, 0 stopped, 0 zombie
%Cpu(s): 0.5 us, 1.3 sy, 4.8 ni, 92.8 id, 0.2 wa, 0.0 hi, 0.3 si, 0.0 st
KiB Mem : 2034848 total, 85696 free, 221496 used, 1727656 buff/cache
KiB Swap: 2094076 total, 2093552 free, 524 used. 1625956 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1789 root 39 19 204428 100684 60760 R 99.1 4.9 74:11.51 unattended-upgr
2658 reallin+ 20 0 42792 4084 3468 R 2.9 0.2 0:00.38 top
2627 root 20 0 0 0 0 I 1.7 0.0 0:00.94 kworker/u8:2-ev
2467 root 20 0 0 0 0 I 0.9 0.0 0:06.71 kworker/u8:5-ev
99 root 20 0 0 0 0 I 0.7 0.0 0:14.61 kworker/u8:1-ev
918 root 20 0 110408 2088 1868 S 0.7 0.1 0:01.72 irqbalance
926 root 20 0 1437148 44464 32436 S 0.6 2.2 0:12.51 containerd
2492 root 20 0 0 0 0 I 0.2 0.0 0:02.31 kworker/2:0-mm_
1 root 20 0 159496 8836 6652 S 0.0 0.4 0:11.00 systemd
2 root 20 0 0 0 0 S 0.0 0.0 0:00.02 kthreadd
3 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 rcu_gp
4 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 rcu_par_gp
6 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 kworker/0:0H
7 root 20 0 0 0 0 I 0.0 0.0 0:01.15 kworker/0:1-cgr
9 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 mm_percpu_wq
10 root 20 0 0 0 0 S 0.0 0.0 0:00.37 ksoftirqd/0
11 root 20 0 0 0 0 R 0.0 0.0 0:02.84 rcu_sched
12 root rt 0 0 0 0 S 0.0 0.0 0:00.25 migration/0
13 root -51 0 0 0 0 S 0.0 0.0 0:00.00 idle_inject/0
14 root 20 0 0 0 0 S 0.0 0.0 0:00.00 cpuhp/0
15 root 20 0 0 0 0 S 0.0 0.0 0:00.00 cpuhp/1
16 root -51 0 0 0 0 S 0.0 0.0 0:00.00 idle_inject/1
17 root rt 0 0 0 0 S 0.0 0.0 0:00.49 migration/1
18 root 20 0 0 0 0 S 0.0 0.0 0:00.59 ksoftirqd/1
20 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 kworker/1:0H
21 root 20 0 0 0 0 S 0.0 0.0 0:00.00 cpuhp/2
프로세스와 컨테이너는 서로 다른 프로세스다.
따라서, 상태를 보여주는 top 명령어 실행 결과도 다를 수밖에 없다.
# 2040 namespace 활용하여 top 명령어 실행
reallinux@ubuntu:~$ sudo nsenter -t 2040 -p -u hostname
b7456d853038
# 호스트에서 실행
reallinux@ubuntu:~$ hostname
ubuntu
reallinux@ubuntu:~$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b7456d853038 ubuntu:16.04 "/bin/bash" About an hour ago Up About an hour nostalgic_murdock
2040은 도커 컨테이너의 프로세스 ID였다.
2040 프로세스의 namespace를 그대로 적용하여 hostname 명령어를 새롭게 실행시켰고, 도커 컨테이너 ID와 일치하고 있다.
도커 컨테이너의 hostname은 도커 컨테이너의 ID임을 알 수 있다.
reallinux@ubuntu:~$ sudo nsenter -t 2040 -p -a
[sudo] password for reallinux:
root@b7456d853038:/#
호스트 이름 root@b7456d853038
이 보인다.
도커 컨테이너 ID와 동일한 값이다.
즉, 같은 namespace를 갖는 프로세스를 하나 생성하여 /bin/bash를 실행시킨 것이다.
이 사실은 ps -ef 명령어로 확인이 가능하다.
root@b7456d853038:/# ps -ef
UID PID PPID C STIME TTY
root 1 0 0 11:51 pts/0 00:00:00 /bin/bash
root 14 0 0 13:00 ? 00:00:00 -bash
root 17 14 0 13:02 ? 00:00:00 ps -ef
1번이 도커 컨테이너이고
14번이 동일한 namespace를 적용한 프로세스다.