Post

Docker없이 컨테이너 만들기 Part2

Docker없이 컨테이너 만들기 Part 2: 네임스페이스

아래의 내용은 Kakao 발표자료와 영상을 기반으로 작성했습니다.

들어가며

컨테이너는 논리적으로 잘 격리된 프로세스이다. 여기서는 컨테이너 상태를 만들기 위해 필요한 필요한 프로세스 격리 기술을 하나씩 알아보고 실습을 진행해보고자 한다. 앞선 포스팅에서 디렉터리를 격리하는 방법에 대해 살펴봤다. 이번편에서는 네임스페이스를 통해 가능한 격리 옵션을 살펴보려고 한다.

네임스페이스

리눅스에서는 네임스페이스를 통해 여러 상태를 격리할 수 있다.

1
2
3
4
# /proc 경로를 통해 확인
ls -al /proc/{pid}/ns
# 명령어를 통해 확인 (출력이 깔끔)
lsns -p {pid}

image.png

PID

PID는 Processs ID의 약자로, 리눅스에서 프로세스를 식별하는 ID이다. 아래의 그림처럼 네임스페이스 별로 본연의 ID와 해당 네임스페이스 기준으로 ID가 나뉘며, 이런 네임스페이스 구분을 통해 관리가 편해진다.

  • 자식 PID 네임스페이스는 부모 프로세스 ID와 자신의 네임스페이스 ID 값을 가진다.

image.png

출처: https://dev4devs.com/2019/10/19/understanding-containers-in-15-minutes/

추가로 컨테이너 실행할 때 명령어 끝나고 죽는데 그것은 프로세스가 격리되어서 Init 프로세스 자체가 컨테이너 실행시 진행되는 명령어만 수행하고 죽어, 컨테이너의 모든 프로세스가 죽기 때문이다.

자세히 알아보면 “unshare할 때 fork 하여, 자식 PID 네임스페이스의 pid 1로 실행”할 때 pid 1 (init) 이 종료되면 pid namespace 도 종료된다. Container 실행 시 바로 종료되는 명령어만 실행하면 컨테이너가 끝나는 이유도 동일하다. (init process가 echo와 같은 명령어라면, 실행 후 종료되므로 Init 프로세스가 죽어 컨테이너 자체도 끝난다.)

실습

[터미널 1에서는 프로세스 네임스페이스를 실행한다.]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
sudo unshare -fp --mount-proc /bin/sh
# echo $$
1
# ps -ef
UID          PID    PPID  C STIME TTY          TIME CMD
root           1       0  0 12:09 pts/2    00:00:00 /bin/sh
root           2       1  0 12:10 pts/2    00:00:00 ps -ef
# ps aux
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root           1  0.0  0.0   2892  1664 pts/2    S    12:09   0:00 /bin/sh
root           3  0.0  0.1  10464  3328 pts/2    R+   12:10   0:00 ps aux
# lsns -t pid -p 1
        NS TYPE NPROCS PID USER COMMAND
4026532207 pid       2   1 root /bin/sh
# sleep 10000

[터미널 2는 호스트로, 실제 터미널1에서 격리된 프로세스를 죽여본다.]

1
2
echo $$
12566
  • 프로세스 확인
1
2
3
4
5
6
ps aux | grep '/bin/sh'
root       14092  0.0  0.2  11904  5632 pts/0    S+   12:09   0:00 sudo unshare -fp --mount-proc /bin/sh
root       14093  0.0  0.1  11904  2300 pts/2    Ss   12:09   0:00 sudo unshare -fp --mount-proc /bin/sh
root       14094  0.0  0.0   6192  1792 pts/2    S    12:09   0:00 unshare -fp --mount-proc /bin/sh
root       14095  0.0  0.0   2892  1664 pts/2    S+   12:09   0:00 /bin/sh
ubuntu     14314  0.0  0.1   7008  2432 pts/1    S+   12:10   0:00 grep --color=auto /bin/sh
  • 격리된 프로세스 확인(아래의 출력 중 NS를 네임스페이스가 다른 것을 확인할 수 있다.)
1
2
3
sudo lsns -t pid -p 14095
        NS TYPE NPROCS   PID USER COMMAND
4026532207 pid       1 14095 root /bin/sh
  • sleep 프로세스를 죽여본다. (격리된 프로세스 환경 속에 있는)
1
sudo kill -SIGKILL $(pgrep sleep)

위의 명령어를 실행하면, 터미널1에서 “killed”로 표시되며 격리 환경이 exit되는 것을 확인할 수 있다.

mount

pivot_root에서 설명한 마운트 네임스페이스를 실습한다.

실습

  • 터미널1
1
2
3
sudo unshare -m
# 격리된 환경에서 아래의 명령어를 실행한다.
lsns -p $$
  • 터미널2
1
sudo lsns -p 1 | grep mnt

아래의 사진을 확인해보면 mnt의 네임스페이스의 값이 다른 것을 확인할 수 있다.

image.png

UTS 네임스페이스

Unix Time Sharing (여러 사용자 작업 환경 제공하고자 서버 시분할 나눠쓰기) 특히 호스트명, 도메인명을 격리할 수 있다.

실습

  • 터미널1: unshare -u 옵션을 통해 격리한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
sudo unshare -u
root@MyServer:/tmp# lsns -p $$
        NS TYPE   NPROCS   PID USER COMMAND
4026531834 time      106     1 root /sbin/init
4026531835 cgroup    106     1 root /sbin/init
4026531836 pid       106     1 root /sbin/init
4026531837 user      106     1 root /sbin/init
4026531839 ipc       106     1 root /sbin/init
4026531840 net       106     1 root /sbin/init
4026531841 mnt        98     1 root /sbin/init
4026532206 uts         2 10048 root -bash
root@MyServer:/tmp# hostname
MyServer
root@MyServer:/tmp# hostname KANS
root@MyServer:/tmp# hostname
KANS
root@MyServer:/tmp#
  • 터미널2: 호스트에서 변경된 내용이 있는지 확인한다.(호스트명의 변화가 없음을 확인할 수 있다.)
1
2
3
4
5
hostname
MyServer

sudo lsns -p 1 | grep uts
4026531838 uts       104   1 root /sbin/init

USER

USER 네임스페이스 : 2012년, UID/GID 넘버스페이스 격리한다. User 네임스페이스는 비교적 최근에 나온 기술이다. 이를 통해 컨테이너의 root 권한 문제를 해결할 수 있다 우선 컨테이너의 root 권한을 아래의 실습을 통해 확인해보자.

실습

  • 컨테이너를 실행한다.
1
docker run -it ubuntu /bin/sh

[docker 내부 환경]

1
2
3
lsns -p $$ -t user
        NS TYPE  NPROCS PID USER COMMAND
4026531837 user       2   1 root /bin/sh

[Host 환경]

1
2
3
4
ps -ef |grep "/bin/sh"
ubuntu     16140   15353  0 12:17 pts/0    00:00:00 docker run -it ubuntu /bin/sh
root       16286   16242  0 12:18 pts/0    00:00:00 /bin/sh
ubuntu     16584   12566  0 12:21 pts/1    00:00:00 grep --color=auto /bin/sh

네임스페이가 같은지 호스트에서 확인해보자

1
2
3
lsns -p $$ -t user
        NS TYPE  NPROCS   PID USER   COMMAND
**4026531837** user       5 12285 ubuntu /lib/systemd/systemd --user

“4026531837” = “4026531837”으로 유저 네임스페이스가 같다.

“컨테이너는 호스트와 네임스페이스도 같고, 권한도 Root를 가져간다.” 이는 보안상 취약하다.

우리는 아래에서 User 네임스페이스를 통해 격리하는 방식을 진행해본다.

(docker도 유저 네임스페이스는 지원하나, 기본값은 아니다.)

  • 격리된 상태
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
unshare -U --map-root-user /bin/sh
# whoami
root
#
# id
uid=0(root) gid=0(root) groups=0(root),65534(nogroup)
# lsns -p $$
        NS TYPE   NPROCS   PID USER COMMAND
4026531834 time        2 16856 root /bin/sh
4026531835 cgroup      2 16856 root /bin/sh
4026531836 pid         2 16856 root /bin/sh
4026531838 uts         2 16856 root /bin/sh
4026531839 ipc         2 16856 root /bin/sh
4026531840 net         2 16856 root /bin/sh
4026531841 mnt         2 16856 root /bin/sh
4026532206 user        2 16856 root /bin/sh
  • 호스트
1
2
3
4
ps -ef |grep "/bin/sh"
ubuntu      6874    5348  0 15:42 pts/0    00:00:00 /bin/sh
ubuntu     16909   15353  0 12:24 pts/0    00:00:00 /bin/sh
ubuntu     16927   12566  0 12:24 pts/1    00:00:00 grep --color=auto /bin/sh

호스트에서 확인했을 때, root권한이 아닌 ubuntu 유저로 확인된다.

💡 k8s, docker 환경에서
k8s, docker에서는 usernamespace가 기본값이 아님.

  • docker가 User Namespace를 지원. root 권한에 대한 취약점을 해결하기 위해

  • Pod에서도 User Namespace를 지원함. (v1.30 beta)

Time

프로세스가 볼 수 있는 시스템 시간을 격리하여, 특정 프로세스에 대해 다른 시간대를 설정할 수 있다. 별도로 실습은 진행하지 않는다.

마치며

리눅스에서 기본적으로 제공하는 네임스페이스 형태에 대해 알아봤다. PID, 마운트, UTS, Time 등 다양한 상태에 대해 격리를 제공한다. 다음 편에서는 자원 할당과 관련된 내용을 살펴볼 예정이다.

This post is licensed under CC BY 4.0 by the author.