도커는 내부적으로 리눅스의 LXC 라이브러리를 사용하는데, LXC는 내부적으로 namespace, cgroup, chroot 리눅스 API를 사용합니다. 이 중에서 chroot
를 사용하면 특정 프로세스 내부적으로 /
로 지정된 디렉토리 밖으로 접근하는 것을 방지할 수 있는데, 이번 글에선 chroot
가 실제로 어떻게 동작하는지 알아보려고 합니다.
이번 실습은 Ubuntu 20.04 LTS 환경에서 진행됐습니다.
chroot
로 프로세스를 격리하고 해당 프로세스에 /bin/bash
기능을 추가해봅시다.
$ apt update
$ apt install sudo tree -y
apt
를 통해 필요한 패키지를 설치합니다. 각 패키지 설치 목적은 아래와 같습니다.
sudo
: 관리자 권한으로 명령어를 실행할 수 있는 패키지입니다.tree
: 폴더 하위 구조를 시각화해서 출력해주는 패키지입니다.$ ldd /bin/bash
linux-vdso.so.1 (0x00007ffca10be000)
libtinfo.so.6 => /lib/x86_64-linux-gnu/libtinfo.so.6 (0x00007fb65218c000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fb652186000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fb651f94000)
/lib64/ld-linux-x86-64.so.2 (0x00007fb6522ed000)
ldd
를 통해 /bin/bash
를 실행하는데 필요한 패키지 목록을 확인할 수 있습니다.
$ cd /home
$ mkdir -p chroot-test/bin
$ mkdir -p chroot-test/lib/x86_64-linux-gnu
$ mkdir -p chroot-test/lib64
/bin/bash
를 실행하기 위해 필요한 파일을 chroot-test
폴더에 넣어주기 위해 폴더를 생성해줍니다.
$ cp /bin/bash chroot-test/bin
$ cp /lib/x86_64-linux-gnu/libtinfo.so.6 chroot-test/lib/x86_64-linux-gnu
$ cp /lib/x86_64-linux-gnu/libdl.so.2 chroot-test/lib/x86_64-linux-gnu
$ cp /lib/x86_64-linux-gnu/libc.so.6 chroot-test/lib/x86_64-linux-gnu
$ cp /lib64/ld-linux-x86-64.so.2 chroot-test/lib64
그리고 이전에 ldd
로 확인했던, /bin/bash
를 실행하기 위해 필요한 파일을 cp
명령어를 이용해 호스트OS로부터 chroot-test
폴더 안에 복사합니다.
tree
명령어를 통해 필요한 파일이 chroot-test
제대로 복사됐는지 확인합니다. 폴더 구조는 위와 같이 출력되면 됩니다.
chroot
실행$ sudo chroot chroot-test /bin/bash
bash-5.0$ pwd
/
chroot
명령어를 입력해 chroot-test
폴더를 최상위 폴더로 설정한 후 해당 폴더 내부의 /bin/bash
를 실행시키는 프로세스를 생성합니다. exit
을 입력하면 다시 호스트OS 터미널로 돌아올 수 있습니다.
그리고 pwd
명령어를 입력했을 때 chroot-test
디렉토리가 /
으로 출력되는 것을 확인할 수 있습니다.
/bin/ls
는?$ sudo chroot chroot-test /bin/bash
bash-5.0$ ls
bash: ls: command not found
하지만 chroot
로 생성된 프로세스의 bash
에서 ls
명령어를 입력하면 찾을 수 없다고 나옵니다. 왜나하면 /bin/bash
와 마찬가지로 ls
명령어를 실행하는데 필요한 파일도 chroot-test
폴더에 복사해줘야 하기 때문입니다.
$ ldd /bin/ls
linux-vdso.so.1 (0x00007ffd3fbfb000)
libselinux.so.1 => /lib/x86_64-linux-gnu/libselinux.so.1 (0x00007f8830d25000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f8830b33000)
libpcre2-8.so.0 => /lib/x86_64-linux-gnu/libpcre2-8.so.0 (0x00007f8830aa3000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f8830a9d000)
/lib64/ld-linux-x86-64.so.2 (0x00007f8830d79000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f8830a7a000)
그래서 ldd
를 통해 /bin/ls
기능을 실행하기 위해 필요한 파일을 확인하고,
$ cp /bin/ls chroot-test/bin
$ cp /lib/x86_64-linux-gnu/libselinux.so.1 chroot-test/lib/x86_64-linux-gnu
$ cp /lib/x86_64-linux-gnu/libc.so.6 chroot-test/lib/x86_64-linux-gnu
$ cp /lib/x86_64-linux-gnu/libpcre2-8.so.0 chroot-test/lib/x86_64-linux-gnu
$ cp /lib/x86_64-linux-gnu/libdl.so.2 chroot-test/lib/x86_64-linux-gnu
$ cp /lib64/ld-linux-x86-64.so.2 chroot-test/lib64
$ cp /lib/x86_64-linux-gnu/libpthread.so.0 chroot-test/lib/x86_64-linux-gnu
cp
를 통해 필요한 외부 패키지 파일을 chroot-test
폴더 안에 복사해줍니다.
tree
명령어를 입력했을 때 위와 같이 출력되면 chroot
를 사용했을 때 /bin/bash
도 동작하고 ls
도 동작하게 됩니다.
$ sudo chroot chroot-test /bin/bash
bash-5.0$ pwd
/
bash-5.0$ ls
bin lib lib64
그리고 chroot
명령어를 입력하면, chroot-test
폴더를 최상위 폴더로 설정한 후 해당 폴더 내부의 /bin/bash
를 실행시키는 프로세스가 생성됩니다.
위와 같이 pwd
를 입력하면 현재 디렉토리가 /
으로 출력되지만, ls
를 입력하면 호스트 OS의 /
에 있는 파일이 출력되는 것이 아니라 chroot-test
폴더 안에 있는 파일이 출력되는 것을 볼 수 있습니다. 이처럼 chroot
를 사용하면 특정 디렉토리를 프로세스 내부적으로 /
로 취급되게끔 설정할 수 있어 호스트 OS로부터 프로세스를 격리할 수 있습니다.
$ docker run -it ubuntu:20.04 bash
docker run
명령어가 위 과정을 자동으로 수행해줍니다. 저 위의 여러 명령어가 도커에선 한줄로 바뀌니까 매우 편리해졌습니다.
실제로 도커 컨테이너는 호스트 OS에서 동작하는 프로세스이고, 컨테이너가 가진 내부 공간은 호스트 OS의 /var/lib/docker/overlay2/{컨테이너ID}/merged
경로의 공간과 동일합니다. 단지 컨테이너 내부 공간은 chroot
를 통해 호스트 OS로부터 격리됐기 때문에, 호스트 OS에선 컨테이너 내부 공간에 접근할 수 있지만 컨테이너 내부에선 호스트 OS 공간에 접근할 수 없다는 점이 특별합니다.
좋은 글 잘 읽고 갑니다. 저는 busybox 를 이용해서 실습했습니다.