컨테이너는 VM과 달리 Host의 OS를 공유한다.
1. Cgroups(control groups) 격리
cpu, memory, network 등 프로세스 그룹의 시스템 리소스 사용량을 관리한다. 어떤 리소스 사용량이 많다면 그 애플리케이션을 cgroup에 넣어서 cpu와 memory 등의 사용 제한을 가능하게 한다.
보통 /sys/fs/crouop 에서 여러 종류의 제어 그룹 위계구조들을 볼 수 있다.
$ snchoi@snchoi:/$ ls sys/fs/cgroup/
cgroup.controllers cgroup.threads dev-mqueue.mount io.stat sys-fs-fuse-connections.mount
cgroup.max.depth cpu.pressure init.scope memory.numa_stat sys-kernel-config.mount
cgroup.max.descendants cpuset.cpus.effective io.cost.model memory.pressure sys-kernel-debug.mount
cgroup.procs cpuset.mems.effective io.cost.qos memory.stat sys-kernel-tracing.mount
cgroup.stat cpu.stat io.pressure misc.capacity system.slice
cgroup.subtree_control dev-hugepages.mount io.prio.class proc-sys-fs-binfmt_misc.mount user.slice
- 제어 그룹들을 잘 조율하면 한 프로세스가 자원을 혼자 너무 많이 사용해서 다른 프로세스의 행동에 영향을 미치는 일을 방지할 수 있다.
- 하나의 제어 그룹에 속할 수 있는 프로세스의 수를 제한하는 pid 라는 제어 그룹도 있다.
- 위계구조마다 그것을 제어하는 제어 그룹 제어기 (cgroup controller)가 있다.
- 모든 리눅스 프로세스는 각 자원 종류의 위계구조에 있는 한 제어 그룹에 속한다.
- 한 프로세스가 처음 생성될 때 프로세스는 부모 프로세스의 제어 그룹들을 물려받는다.
- 제어 그룹들을 관리한다는 것은 결국 이 위계구조들에 있는 파일들과 디렉터리들을 읽고 쓰는 것에 해당한다.
- 컨테이너를 실행하면 런타임이 컨테이너를 위한 새 제어그룹들을 생성한다.
- 컨테이너 안에서는 컨테이너가 속한 제어 그룹들을 /proc 디렉터리의 특정 파일에서 볼 수 있다.
- 제어 그룹의 특정 매개변수를 변경하려면 해당 파일에 값을 기록하면 된다.
- 제어 그룹의 특정 파일에 프로세스 ID를 추가하면 해당 프로세스가 제어 그룹에 배정된다.
2. 리눅스 이름공간
- cgroups가 프로세스가 사용할 수 있는 자원을 제한한다면, 이름공간(namespace)은 프로세스가 볼 수 있는 것들을 제한한다.
- 프로세스를 어떤 이름공간에 넣으면 프로세스는 그 이름공간이 허용하는 것들만 볼 수 있게 된다.
- 현재 리눅스는 다음과 같은 여러 종류의 이름공간을 지원한다.
- 유닉스 시분할 시스템 (UTS) - 프로세스가 인식하는 시스템의 호스트 이름과 도메인 이름들에 관한 이름공간
- 프로세스 ID
- 마운트 지점
- 네트워크
- 사용자 ID, 그룹 ID
- IPC (inter-process communication, 프로세스 간 통신)
- 제어 그룹
- 현재 존재하는 이름공간들은 lsns 라는 명령으로 확인할 수 있다.
$ snchoi@snchoi:~$ lsns
NS TYPE NPROCS PID USER COMMAND
4026531834 time 54 1264 snchoi /lib/systemd/systemd --user
4026531835 cgroup 54 1264 snchoi /lib/systemd/systemd --user
4026531836 pid 54 1264 snchoi /lib/systemd/systemd --user
4026531837 user 54 1264 snchoi /lib/systemd/systemd --user
4026531838 uts 54 1264 snchoi /lib/systemd/systemd --user
4026531839 ipc 54 1264 snchoi /lib/systemd/systemd --user
4026531840 net 54 1264 snchoi /lib/systemd/systemd --user
4026531841 mnt 54 1264 snchoi /lib/systemd/systemd --user
- 비루트 사용자의 경우 불완전한 정보를 보여준다.
$ snchoi@snchoi:~$ sudo su
$ root@snchoi:/home/snchoi$ lsns
NS TYPE NPROCS PID USER COMMAND
4026531834 time 188 1 root /sbin/init
4026531835 cgroup 188 1 root /sbin/init
4026531836 pid 188 1 root /sbin/init
4026531837 user 187 1 root /sbin/init
4026531838 uts 185 1 root /sbin/init
4026531839 ipc 188 1 root /sbin/init
4026531840 net 187 1 root /sbin/init
4026531841 mnt 174 1 root /sbin/init
4026531862 mnt 1 37 root kdevtmpfs
4026532351 mnt 1 472 root /lib/systemd/systemd-udevd
4026532352 uts 1 472 root /lib/systemd/systemd-udevd
4026532409 mnt 1 657 systemd-timesync /lib/systemd/systemd-timesyncd
4026532410 uts 1 657 systemd-timesync /lib/systemd/systemd-timesyncd
4026532411 mnt 1 695 systemd-network /lib/systemd/systemd-networkd
4026532412 mnt 1 697 systemd-resolve /lib/systemd/systemd-resolved
4026532413 net 1 707 root /usr/libexec/accounts-daemon
4026532468 mnt 1 707 root /usr/libexec/accounts-daemon
4026532469 mnt 1 1026 root /usr/libexec/upowerd
4026532470 user 1 1026 root /usr/libexec/upowerd
4026532474 mnt 2 4487 root /usr/libexec/fwupd/fwupd
4026532525 mnt 1 716 root /usr/sbin/irqbalance --foreground
4026532526 mnt 1 721 root /lib/systemd/systemd-logind
4026532528 uts 1 721 root /lib/systemd/systemd-logind
4026532529 mnt 1 757 root /usr/sbin/ModemManager
4026532592 mnt 1 1199 colord /usr/libexec/colord
4026532593 mnt 1 1230 root /usr/sbin/spice-vdagentd
1. 호스트 이름 격리
- 호스트 이름(hostname)과 도메인 이름들에 관한 것이다.
- 프로세스를 독자적인 UTS 이름공간에 넣으면 그 프로세스가 인식하는 호스트 이름을 그 프로세스가 실행 중인 컴퓨터 또는 VM의 호스트 이름과는 무관하게 임의로 변경할 수 있다.
$ snchoi@snchoi:~$ hostname
snchoi
$ snchoi@snchoi:~$ sudo docker run --rm -it --name hello ubuntu bash
Unable to find image 'ubuntu:latest' locally
latest: Pulling from library/ubuntu
5af00eab9784: Pull complete
Digest: sha256:0bced47fffa3361afa981854fcabcd4577cd43cebbb808cea2b1f33a3dd7f508
Status: Downloaded newer image for ubuntu:latest
$ root@986688c2be44:/$ hostname
986688c2be44
- 컨테이너 시스템은 각 컨테이너 ID가 호스트 이름으로 쓰인다.
- 도커로 컨테이너 실행 시 컨테이너 이름(hello)을 지정했다고 해도 그 이름이 컨테이너의 이름으로 쓰이지는 않는다는 점도 알 수 있다.
- 이렇게 컨테이너가 개별적인 호스트 이름을 가지는 이유는 도커가 개별적인 UTS 이름공간을 컨테이너에 적용했기 때문이다.
- 이처럼 개별적인 UTS 이름공간을 가진 프로세스를 만드는 수단으로 unshare 라는 명령이 있다.
$ snchoi@snchoi:~$ sudo unshare --uts sh
$ hostname
snchoi
$ hostname experiment
$ hostname
experiment
$ exit
$ snchoi@snchoi:~$ hostname
snchoi
- 개별적인 UTL 이름공간을 가진 새 프로세스에서 sh 셸을 실행한다.
- 호스트 이름을 변경해도 호스트 컴퓨터의 호스트 이름은 변하지 않는다.
2. 프로세스 ID 격리
도커 컨테이너 안에서 ps 명령을 실행하면 그 컨테이너 안에서 실행되는 프로세스들만 나올 뿐 호스트에서 실행되는 프로세스들은 나오지 않는다. 컨테이너에 개별적인 프로세스 ID 이름공간을 적용했기 때문이다.
$ snchoi@snchoi:~$ sudo docker run --rm -it --name hello ubuntu bash
$ root@5820b5a88ea6:/# ps -eaf
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 00:56 pts/0 00:00:00 bash
root 9 1 0 00:56 pts/0 00:00:00 ps -eaf
$ root@5820b5a88ea6:/# exit
프로세스 ID 이름공간 격리도 unshare 명령을 통해 진행해보자
$ snchoi@snchoi:~$ sudo unshare --pid sh
$ whoami
root
$ whoami
sh: 2: Cannot fork
$ whoami
sh: 3: Cannot fork
$ whoami
sh: 4: Cannot fork
$ exit
- 첫 프로세스는 잘 작동한 것으로 보이지만, 그 이후의 모든 명령은 포크가 불가능하다는 이유로 실패한다.
- 프로세스가 1씩 증가했다.
- 프로세스 ID 들을 보면 첫 whoami의 프로세스 ID 가 1 이었음을 짐작할 수 있다.
- 프로세스 ID 가 1로 시작했다는 점은 호스트와는 개별적인 새 PID 이름공간이 작용했음을 말해준다.
$ snchoi@snchoi:~$ ps fa
PID TTY STAT TIME COMMAND
3527676 pts/0 Ss 0:00 bash
3666327 pts/0 S 0:00 \_ sudo unshare --pid sh
3666328 pts/0 S+ 0:00 \_ sh
- unshare 매뉴얼 페이지의 —fork 플래그 항목에 대한 설명에 따르면, 이 플래그는 “지정된 프로그램을 직접 실행하는 대신 unshare의 자식 프로세스로 포크한다. 새 pid 이름공간을 만들 때 유용하다”
- sh 프로세스가 unshare의 자식이 아니라 sudo 의 자식인 것이 문제였다.
—fork 플래그를 지정해서 다시 시도해보자
$ snchoi@snchoi:~$ sudo unshare --pid --fork sh
$ whoami
root
$ whoami
root
$ snchoi@snchoi:~$ ps fa
PID TTY STAT TIME COMMAND
3527676 pts/0 Ss 0:00 bash
3673711 pts/0 S 0:00 \_ sudo unshare --pid --fork sh
3673712 pts/0 S 0:00 \_ unshare --pid --fork sh
3673713 pts/0 S+ 0:00 \_ sh
- —fork 플래그를 지정해서 unshare를 실행하면 sh 셸이 unshare 프로세스의 자식으로 실행되므로, 이 셸 안에서 얼마든지 많은 자식 명령을 실행할 수 있다.
- 셸이 자신만의 프로세스 ID 이름공간 안에 있으므로 ps 명령의 출력이 아주 간단할 것 같지만, 실제로는 그렇지 않다.
- ps는 /proc 의 가상 파일들을 읽어서 작동한다.
- ps가 새 이름공간 안에 있는 프로세스들에 관한 정보만 출력하게 하려면 커널이 호스트의 것과는 개별적인 /proc 디렉터리에 프로세스들에 관한 정보를 기록하게 만들어야 한다.
- /proc 는 루트 디렉터리 바로 아래에 있는 디렉터리이므로, 그러려면 루트 디렉터리 자체를 변경해야 한다.
3. 루트 디렉터리 변경
- 컨테이너 안에서는 호스트의 파일 시스템 전체를 볼 수 없다. 컨테이너 안에서는 파일 시스템 전체의 한 부분집합을 볼 수 있을 뿐인데, 이는 컨테이너 실행 시 루트 디렉터리가 바뀌기 때문이다.
- 리눅스에서 루트 디렉터리는 chroot 라는 명령으로 변경할 수 있다.
- 이 명령은 현재 프로세스에 대한 루트 디렉터리가 파일 시스템 안의 다른 어떤 위치를 가리키게 만든다.
- chroot 명령을 성공적으로 실행하고 나면, 전체 파일 시스템 위계구조에서 새 루트 디렉터리보다 위쪽에 있는 것에는 전혀 접근할 수 없다.
- 새 디렉터리를 생성하고 chroot 로 루트 디렉터리로 만들어보았지만, 제대로 동작하지 않는다.
$ snchoi@snchoi:~$ mkdir new_root
$ snchoi@snchoi:~$ sudo chroot new_root/
[sudo] password for snchoi:
chroot: failed to run command /bin/bash: No such file or directory
- 문제의 원인은, 새 루트 디렉터리에 bin 디렉터리가 없다는 것이다. 따라서 /bin/bash 셸도 실행할 수 없다.
- 새 루트에서 어떤 명령을 실행하려면 적절한 디렉터리에 실행 파일이 있어야 한다.
- 컨테이너에 이런 문제가 없는 것은, 필요한 파일들이 담긴 파일 시스템을 미리 컨테이너 이미지에 담아 두고 그로부터 컨테이너 인스턴스를 만들어서 실행하기 때문이다.
- 알파인 리눅스를 마치 컨테이너처럼 실행해보자!
$ snchoi@snchoi:~$ mkdir alpine
$ snchoi@snchoi:~$ cd apline
$ snchoi@snchoi:~/alpine$ curl -o alpine.tar.gz https://dl-cdn.alpinelinux.org/alpine/v3.10/releases/x86_64/alpine-minirootfs-3.10.0-x86_64.tar.gz
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 2562k 100 2562k 0 0 7255k 0 --:--:-- --:--:-- --:--:-- 7259k
$ snchoi@snchoi:~/alpine$ ls
alpine.tar.gz
$ snchoi@snchoi:~/alpine$ tar xvf alpine.tar.gz
$ snchoi@snchoi:~/alpine$ rm alpine.tar.gz
$ snchoi@snchoi:~/alpine$ ls
bin dev etc home lib media mnt opt proc root run sbin srv sys tmp usr var
- 알파인 배포판을 지역 디렉터리에 풀어 놓았으니, 이제 chroot로 alpine 디렉터리를 루트 디렉터리로 만들고 그 디렉터리 위계구조에 있는 명령을 실행해 볼 수 있다.
$ snchoi@snchoi:~$ sudo chroot alpine/ ls
bin dev etc home lib media mnt opt proc root run sbin srv sys tmp usr var
$ snchoi@snchoi:~$ sudo chroot alpine/ sh
/ # ls
bin dev etc home lib media mnt opt proc root run sbin srv sys tmp usr var
/ # whoami
root
/ # exit
- chroot는 이름 그대로 프로세스의 “루트(root)를 변경한다.”
- 루트가 바뀌면 프로세스는 파일 시스템 위계구조에서 새 루트 디렉터리보다 아래에 있는 파일들과 디렉터리들에만 접근할 수 있다.
4. 이름공간과 루트 변경의 조합
- 이름공간과 루트 변경을 조합할 수 있다. chroot를 새 이름공간에서 실행하면 된다.
- 루트 디렉터리 변경 기능을 이용하면 호스트의 /proc과는 독립적인 /proc 디렉터리를 컨테이너가 사용하게 만들 수 있다.
- 커널이 디렉터리에 프로세스 정보를 채우게 하려면 이 디렉터리를 proc 형식의 유사(pseudo) 파일 시스템으로 마운트해야 한다.
- 드디어 자신의 프로세스들만 볼 수 있게 하는 프로세스 ID 격리에 성공하였다!
$ snchoi@snchoi:~$ sudo unshare --pid --fork chroot alpine sh
/ $ mount -t proc proc proc
/ $ ps
PID USER TIME COMMAND
1 root 0:00 sh
4 root 0:00 ps
5. 마운트 이름공간
- 컨테이너가 호스트의 모든 파일 시스템 마운트에 접근하는 것은 바람직하지 않다.
- 컨테이너에 개별적인 마운트 이름공간을 부여하면 특정 마운트들만 보이게 만들 수 있다.
$ snchoi@snchoi:~$ sudo unshare --mount sh
$ mkdir source
$ touch source/HELLO
$ ls source
HELLO
$ mkdir target
$ mount --bind source target
$ ls target
HELLO
$ findmnt target
TARGET SOURCE FSTYPE OPTIONS
/home/snchoi/target /dev/mapper/ubuntu--vg-ubuntu--lv[/home/snchoi/source] ext4 rw,relatime
$ findmnt
... (엄청나게 많은 마운트 데이터 출력)
- 독자적인 마운트 이름공간을 가진 프로세스를 띄우고 간단한 바인드 마운트를 만들어보았다.
- 호스트에서 findmnt target 을 해보면 아무 데이터도 나오지 않는다.
- 그러나 마운트 이름공간 안에서 findmnt를 하면 꽤 긴 출력이 나온다. 이는 앞의 프로세스 ID 격리와 비슷하게 커널이 /proc/<프로세스ID>/mounts 파일에서 마운트 정보를 읽어서 프로세스에 제공하기 때문에 이런 결과가 나온 것 뿐이다.
- 컨테이너화된 프로세스의 마운트 집합을 완전히 격리하려면 새 루트 디렉터리와 새 마운트 이름공간, 그리고 새 proc 마운트 디렉터리를 조합해야 한다.
$ snchoi@snchoi:~$ sudo unshare --mount chroot alpine sh
/ $ mount -t proc proc proc
/ $ mount
proc on /proc type proc (rw,relatime)
/ $ mkdir source
/ $ touch source/HELLP
/ $ mkdir target
/ $ mount --bind source target
/ $ mount
proc on /proc type proc (rw,relatime)
/dev/mapper/ubuntu--vg-ubuntu--lv on /target type ext4 (rw,relatime)
6. 네트워크 이름공간
- 컨테이너가 볼 수 있는 네트워크 인터페이스들과 라우팅 테이블을 제한할 수 있다.
- 네트워크 이름공간의 이름들은 lsns 명령으로 확인할 수 있다.
# (호스트)
$ snchoi@snchoi:~$ sudo lsns -t net
NS TYPE NPROCS PID USER NETNSID NSFS COMMAND
4026531840 net 181 1 root unassigned /sbin/init
4026532413 net 1 718 root unassigned /usr/libexec/accounts-daemon
$ snchoi@snchoi:~$ sudo unshare --net bash
# (격리)
$ root@snchoi:/home/snchoi$ echo $$
3552
$ root@snchoi:/home/snchoi$ lsns -t net | grep 3552
4026532476 net 2 3552 root unassigned bash
- 새 네트워크 이름공간을 부여받은 프로세스는 루프백 인터페이스만 가지고 시작한다.
# (호스트)
$ snchoi@snchoi:~$ sudo unshare --net bash
# (격리)
$ root@snchoi:/home/snchoi$ ip a
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
- 루프백 인터페이스만으로는 다른 누군가와 통신할 수 없다.
- 외부 세계로의 통로를 뚫으려면 가상 이더넷 인터페이스를 만들어야 한다. (한 쌍의 가상 이더넷 인터페이스가 필요하다)
# (격리) 가상 이더넷 쌍을 만든다
$ root@snchoi:/home/snchoi$ ip link add ve1 netns 3552 type veth peer name ve2 netns 1
# (격리)
$ root@snchoi:/home/snchoi$ ip a
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
2: ve1@if4: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
link/ether f6:55:54:c7:b6:70 brd ff:ff:ff:ff:ff:ff link-netnsid 0
- 링크의 상태가 “DOWN” 이다. 따라서 지금은 이 링크로 통신을 할 수 없다. 이쪽 끝뿐만 아니라 다른 쪽 끝도 “UP”이 되게 해야한다.
# (호스트) ve2 활성화
$ snchoi@snchoi:~$ sudo ip link set up ve2
# (격리) ve1 활성화
$ root@snchoi:/home/snchoi$ ip link set ve1 up
# (격리)
$ root@snchoi:/home/snchoi$ ip a
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
2: ve1@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether f6:55:54:c7:b6:70 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet6 fe80::f455:54ff:fec7:b670/64 scope link
valid_lft forever preferred_lft forever
- IP 데이터를 전송하려면 인터페이스에 IP 주소를 부여해야 한다.
# (격리) ve1에 IP 주소를 부여한다.
$ root@snchoi:/home/snchoi$ ip addr add 192.168.1.100/24 dev ve1
# (호스트) ve2에 IP 주소를 부여한다.
$ snchoi@snchoi:~$ sudo ip addr add 192.168.1.200/24 dev ve2
- 이렇게 하면 하나의 IP 경로가 컨테이너 라우팅 테이블에 추가되는 효과도 생긴다.
# (격리)
$ root@snchoi:/home/snchoi$ ip route
192.168.1.0/24 dev ve1 proto kernel scope link src 192.168.1.100
- 이 라우팅 정보는 호스트의 라우팅 테이블과는 독립적이다.
- 컨테이너는 IP 주소 192.168.1.100/24 에서(만) 데이터를 주고받을 수 있다.
- 이를 확인하기 위해, 호스트에서 컨테이너의 주소로 핑을 날려본다.
# (호스트)
$ snchoi@snchoi:~$ ping 192.168.1.100
PING 192.168.1.100 (192.168.1.100) 56(84) bytes of data.
64 bytes from 192.168.1.100: icmp_seq=1 ttl=64 time=0.157 ms
64 bytes from 192.168.1.100: icmp_seq=2 ttl=64 time=0.101 ms
64 bytes from 192.168.1.100: icmp_seq=3 ttl=64 time=0.132 ms
7. 사용자 이름공간
- 프로세스가 특정한 사용자ID들과 그룹ID들을 볼 수 있게 만드는 수단이다.
- 사용자, 그룹ID 격리의 주된 장점은 컨테이너 안에서 루트ID 0을 호스트의 비루트 계정에 부여할 수 있다는 것이다.
- 컨테이너 안에서는 소프트웨어 자체는 루트 계정으로 실행되지만, 공격자가 컨테이너에서 탈출해서 호스트로 간다고 해도 거기에서는 권한 없는 비루트계정일 뿐이다.
- 일반적으로 새 이름공간을 만들려면 루트 권한이 필요하다. (도커 데몬이 루트로 실행되는 것이 이 때문이다.) 그러나 사용자 이름공간은 예외이다.
$ snchoi@snchoi:~$ unshare --user bash
$ nobody@snchoi:~$ id
uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup)
$ nobody@snchoi:~$ echo $$
2162
- 새 사용자 이름공간에서 사용자의 계정은 nobody이다.
- 기존 사용자들에 접근하려면 이름공간 바깥쪽과 안쪽의 사용자ID들을 연결해줘야 한다.
- 대응(연결) 관계에 관한 정보는 /proc/<프로세스ID>/uid_map 파일에 있다. (수정을 위해서 루트 계정이 필요하다)
# 호스트에서 uid_map 파일을 수정한다.
$ snchoi@snchoi:~$ sudo echo '0 1000 1' > /proc/2162/uid_map
- 이렇게 하면 자식 프로세스의 사용자가 루트가 된다.
$ nobody@snchoi:~$ id
uid=0(root) gid=65534(nogroup) groups=65534(nogroup)
도커의 사용자 이름공간 제약
- 도커는 사용자 이름공간을 지원하지만, 기본적으로는 사용자 이름공간 기능이 비활성화되어 있다. → 기본적으로 도커 컨테이너는 root 사용자로 실행된다
- 사용자 이름공간이 도커 사용자들이 원할 만한 몇 가지 것과 호환되지 않기 때문이다.
- 사용자 이름공간은 호스트와의 프로세스 ID 공간 또는 네트워크 이름공간 공유와 호환되지 않는다.
- 컨테이너 안에서 프로세스가 루트로 실행된다고 해도 전체 루트 권한을 가지지는 않는다.
- 컨테이너화된 프로세스가 파일과 상호작용하려면 적절한 접근 권한들이 필요하다.
8. IPC 이름공간
- 리눅스는 서로 다른 두 프로세스가 메모리의 공유 영역에 접근해서, 또는 공유 메시지 대기열을 이용해서 통신하는 기능을 제공한다.
- 그런 통신이 가능해지려면 두 프로세스가 같은 IPC 이름공간에 있어야 한다.
- 일반적으로 컨테이너가 다른 컨테이너의 공유 메모리에 접근하게 하는 것은 바람직하지 않다.
- 이 때문에 컨테이너는 개별적인 IPC 이름공간에 있어야 한다.
$ snchoi@snchoi:~$ ipcmk -M 1000
Shared memory id: 5
$ snchoi@snchoi:~$ ipcs
------ Message Queues --------
key msqid owner perms used-bytes messages
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x8cfc5454 4 snchoi 644 1000 0
0x7ed23259 5 snchoi 644 1000 0
------ Semaphore Arrays --------
key semid owner perms nsems
$ snchoi@snchoi:~$ sudo unshare --ipc sh
# ipcs
------ Message Queues --------
key msqid owner perms used-bytes messages
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
------ Semaphore Arrays --------
key semid owner perms nsems
9. 제어 그룹 이름공간
- croups 파일 시스템에 chroot를 적용하는 것과 다소 비슷하다.
- 프로세스가 전체 cgroups 파일 시스템 위계구조에서 자신의 제어 그룹들에 관한 디렉터리 위쪽은 볼 수 없게 만든다.
$ snchoi@snchoi:~$ cat /proc/self/cgroup
0::/user.slice/user-1000.slice/user@1000.service/app.slice/app-org.gnome.Terminal.slice/vte-spawn-fb34540f-3917-4fe8-afe9-389f4e277d98.scope
$ snchoi@snchoi:~$ sudo unshare --cgroup bash
root@snchoi:/home/snchoi# cat /proc/self/cgroup
0::/
호스트의 관점에서 본 컨테이너 프로세스
- 컨테이너는 컨테이너화된 프로세스이다.
- 컨테이너는 여전히 호스트 컴퓨터에서 실행되는 하나의 리눅스 프로세스이지만, 호스트 컴퓨터의 일부만 볼 수 있고 전체 파일 시스템의 한 부분 트리에만 접근할 수 있다.
- 컨테이너는 하나의 프로세스일 뿐이므로 호스트 운영체제의 문맥 안에 존재하며, 호스트의 커널을 다른 프로세스들과 공유한다.
- 호스트에서 컨테이너 프로세스들을 볼 수 있다는 것은 컨테이너와 VM의 근본적인 차이점 중 하나이다.
- 호스트에 접근한 공격자는 호스트에서 실행되는 모든 컨테이너를 인식하고 영향을 미칠 수 있다.
컨테이너 전용 호스트
- 컨테이너는 호스트와 다른 컨테이너들과 하나의 커널을 공유한다.
- 호스트가 침해되면 호스트의 모든 컨테이너가 잠재적 피해자가 된다.
- 컨테이너 이외의 응용 프로그램을 전혀 실행하지 않는다면 호스트 컴퓨터에는 최소한의 사용자 계정들만 있으면 된다.
- 호스트를 불변 객체로 취급하면 침입을 탐지하기 쉽다.
'리눅스(Linux) > 컨테이너(Container)' 카테고리의 다른 글
[컨테이너] Container Layer 구조 (OverlayFS, 컨테이너 이미지 구조) (0) | 2023.08.15 |
---|---|
VM과 컨테이너 (0) | 2023.07.29 |
[Docker] docker inspect (0) | 2021.09.11 |
[도커] 스웜모드 & 스웜모드의서비스 장애 복구 & 서비스 컨테이너에 설정 정보 전달(secret,config) & 도커 스웜 네트워크 & 서비스 디스커버리 (0) | 2020.09.19 |
[도커] 쉘스크립트에 도커 명령어 작성 & Docker compose & swarm (0) | 2020.09.16 |