후니의 IT인프라 사전

컨테이너 만들기 1) 컨테이너의 기원 - chroot (컨테이너와 격리) 본문

카테고리 없음

컨테이너 만들기 1) 컨테이너의 기원 - chroot (컨테이너와 격리)

james_janghun 2024. 9. 1. 02:29

가시다 님의 KANS 스터디 내용 중 컨테이너의 격리 부분을 잘 설명한 유명한 동영상 '이게 돼요? 도커 없이 컨테이너 만들기' 영상을 정리한 내용을 공유합니다.

 

해당 내용은 동영상 https://www.youtube.com/watch?v=mSD88FuST80 에 매우 잘 나와있으며,

https://github.com/sam0kim/container-internal 깃허브에서 자료를 다운받으실 수 있습니다.

 

 

 

컨테이너의 기원 (일종의 최초 격리)

컨테이너의 기원은 1979년 chroot로 보는 견해가 많다.

서버에는 다양한 유저들이 접속하므로 악의적인 사용자(해커)도 접근할 수가 있다. 따라서 이를 막기 위해 사용자 프로세스를 가두는 방법을 고안했다. chroot는 루트 디렉토리 밖으로는 프로세스가 나갈 수 없다는 것에서 착안했다. 유저 디렉토리를 유저 프로세스에게 루트라고 속이게 된다. 이를 fake root라고한다.

 

chroot 사용법

chroot [옵션] NEWROOT [커멘드]

예시) chroot myroot /bin/sh

 

커맨드 지정이 없을 경우 $SHELL이 기본값이다.

 

 

실습

자 그럼 실제로 확인해보자.

chroot myroot /bin/sh

 

다음과 같이 chroot를 통해서 myroot의 /bin/sh을 실행시켰으나 다음과 같이 에러가 발생한다.

 

chroot: cannot change root directory to 'myroot': No such file or directory

이 이유는 myroot 밑에 /bin 디렉토리와 sh 명령어가 존재하지 않아 아무것도 실행할 수 없기 때문이다. 따라서 실제로 해당 디렉토리와 명령어를 만들어주어야 작동시킬 수 있다.

 

먼저 sh 명령어를 복사하기 위해 which로 확인해준다. 또한 ldd 명령어를 통해서 실제 동작하는 바이너리가 어떤 것인지 확인해 준다.

which sh
ldd /bin/sh

 

그럼 이제 해당 디렉토리와 명령어를 복사해주도록 하자.

mkdir -p myroot/bin
cp /usr/bin/sh myroot/bin;
mkdir -p myroot/{lib,lib/aarch64-linux-gnu};
cp /lib/aarch64-linux-gnu/libc.so.6 myroot/lib/aarch64-linux-gnu/;
cp /lib/ld-linux-aarch64.so.1 myroot/lib;

* 참고로 vdso는 커널레이어에 있는 것으로 복사할 필요가 없다.

 

이제는 이렇게 /bin/sh 쉘로 들어갈 수 있는 것을 확인할 수 있다. 그런데 ls 명령어로 파일 목록을 보려고 했더니 다음과 같이 명령어를 찾을 수 없다고 한다. 앞서 쉘을 넣은 것처럼 동일한 방식으로 ls 명령어도 넣어준다.

 

which ls
ldd /usr/bin/ls

 

 

cp /usr/bin/ls myroot/bin/;
cp /lib/aarch64-linux-gnu/{libselinux.so.1,libc.so.6,libpcre2-8.so.0,libdl.so.2,libpthread.so.0} myroot/lib/aarch64-linux-gnu/;
cp /lib/ld-linux-aarch64.so.1 myroot/lib/;

 

따라서 이렇게 옮겨주고 tree구조를 살펴보면 다음과 같이 확인할 수 있다.

 

자 이제 이를 확인하기 위해서 다시 chroot를 실행해보자.

 

이제는 ls까지 잘 수행하는 것을 확인할 수 있다. 여기서 cd .. 를 통해서 해당 디렉토리를 벗어날 수 있는지도 확인해보자.

 

cd로 디렉터리가 잘 이동되는 건 확인할 수 있는데, 막상 myroot 밖으로는 나갈 수 없는것도 확인할 수 있었다.

 

마지막으로 ps 명령어 까지 넣어보도록 하자.

 

cp /bin/ps myroot/bin/;
cp /lib/aarch64-linux-gnu/{libprocps.so.8,libdl.so.2,libc.so.6,libsystemd.so.0,librt.so.1,liblzma.so.5,liblz4.so.1,libgcrypt.so.20,libpthread.so.0,libgpg-error.so.0} myroot/lib/aarch64-linux-gnu/;
cp /lib/ld-linux-aarch64.so.1 myroot/lib/;

 

이렇게 ps 명령어를 주입하고, 실행해보면 다음과 같이 mount오류가 발생한다.

 

ps 명령어는 /proc 라는 가상 파일 시스템을 통해 실제 디스크에 저장된 파일이 아니라 커널에 의해 동적으로 생성되는 정보를 해당 디렉터리에 저장한다. 이 곳에서 현재 실행 중인 프로세스와 시스템 상태에 대한 정보를 제공한다.

참고로 리눅스 커널은 각 프로세스에 대해 /proc/PID/ 디렉터리를 생성해, 해당 PID의 메모리 사용량, cpu 사용량 등을 동적으로 업데이트 한다.

 

따라서 현재 chroot 속에서는 /proc 디렉터리도 없고, 마운트 명령어도 없기 때문에 모두 넣어줘야한다.

 

동일하게 마운트 명령어를 먼저 넣어준 다음 proc 디렉터리를 만들고 실제로 chroot 속에서 mount를 시켜보자.

 

cp /usr/bin/mount myroot/bin/;
cp /lib/aarch64-linux-gnu/{libmount.so.1,libc.so.6,libblkid.so.1,libselinux.so.1,libpcre2-8.so.0,libdl.so.2,libpthread.so.0} myroot/lib/aarch64-linux-gnu/;
cp /lib/ld-linux-aarch64.so.1 myroot/lib/;

mkdir -p myroot/proc

# myroot 접속
chroot myroot /bin/sh
mount -t proc proc /proc

 

mount -t 옵션을 통해서 proc라는 파일 시스템을 /proc에 마운트를 하도록 한다. 그럼 다음과 같이 ps 명령어를 확인할 수 있게된다.

 

 

또한 실제로 proc 내부를 보면 동적으로 커널 정보를 불러오는 것을 확인할 수 있다.

 

호스트에서도 mount 정보를 확인하면 host 뿐 아니라 myroot에서 마운트 된 것을 확인할 수 있다.

 

다만 실제로 host과 myroot 각각 ps를 조회하여도 세션의 차이가 있을 뿐 host의 ps를 공유하고 있었다.

왼쪽은 myroot의 ps이고, 오른쪽은 호스트의 ps인데 둘의 차이가 없었다.

 

 

다음 명령어를 통해서 mount를 해제하고 실습을 종료한다.

umount /tmp/myroot/proc

 

chroot에서 확인하고 가야할 것

chroot를 통해서 실제 컨테이너에서 사용하는 기본적인 기술인 격리에 대해서 어느정도 확인할 수 있었다.

 

- 격리 : 새로운 경로를 기준으로 실행해서 해당 환경을 벗어나지 못하도록 독립되어 작동하도록 한다. 유저프로세스를 가두는 방식으로 속이는 것이다.

 

다음 장에서는 패키징에 대해서 알아보고자 한다.

- 패키징 : 필요한 바이너리를 모아서 이미지화 시킴