Docker의 chroot 이해하기

곽태욱·2021년 7월 30일
1

도커는 내부적으로 리눅스의 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에선?

$ docker run -it ubuntu:20.04 bash

docker run 명령어가 위 과정을 자동으로 수행해줍니다. 저 위의 여러 명령어가 도커에선 한줄로 바뀌니까 매우 편리해졌습니다.

실제로 도커 컨테이너는 호스트 OS에서 동작하는 프로세스이고, 컨테이너가 가진 내부 공간은 호스트 OS의 /var/lib/docker/overlay2/{컨테이너ID}/merged 경로의 공간과 동일합니다. 단지 컨테이너 내부 공간은 chroot를 통해 호스트 OS로부터 격리됐기 때문에, 호스트 OS에선 컨테이너 내부 공간에 접근할 수 있지만 컨테이너 내부에선 호스트 OS 공간에 접근할 수 없다는 점이 특별합니다.

profile
이유와 방법을 알려주는 메모장 겸 블로그. 블로그 내용에 대한 토의나 질문은 언제나 환영합니다.

1개의 댓글

comment-user-thumbnail
2022년 6월 26일

좋은 글 잘 읽고 갑니다. 저는 busybox 를 이용해서 실습했습니다.

답글 달기