[Docker] Docker9 - 독립된 실행 환경 만들기(Mini Root Filesystem + chroot)
독립된 실행 환경
이번 실습은 다음을 보여준다.
-
리눅스에서 독립적인 실행 환경을 만드는 방식 : 즉, 실제 운영체제처럼 작동하는 작은 루트 파일 시스템(rootfs)을 구성해보고, 그 환경에서 직접 명령어 실행이 가능하도록 세팅하는 것이 목적이다.
-
컨테이너 기술의 기초 원리를 실습으로 이해하기 : Docker, LXC(Linux Container), systemd-nspawn 같은 컨테이너 기술은 결국 chroot, namespaces, cgroups를 활용한다. 그중 chroot는 파일 시스템 관점에서 “루트(/)를 제한”하는 가장 기본적인 격리 수단이다. 이 실습은 Docker의 내부 동작의 파일 시스템 격리(File System Isolation)을 이해하기 위한 기초 실습이다.
실습을 통해 얻을 수 있는 이해
- 컨테이너는 어떻게 독립적인가?
- Docker가 격리된 환경을 제공하는 이유는 단순히 가상머신이 아니라, 이렇게 루트 파일 시스템을 제한(chroot)하고, 그 안에 필요한 바이너리와 라이브러리를 넣기 때문이다.
- 이 실습은 그 원리를 맨바닥부터 직접 구성한 것이다.
- 파일 시스템이 격리의 핵심이라는 것
- chroot 환경에서는 호스트 파일 시스템에 접근이 불가능하다. 완전히 분리된 새로운 루트로 바뀐다. 이걸 기반으로 Docker는 파일, 네트워크, 프로세스, 사용자 등 다양한 격리를 추가한다.
사용할 명령어 설명
- chroot : 실행 환경의 루트를 바꿔주는 명령어
sudo chroot ./myroot /usr/bin/bash
- ./myroot 디렉토리를 새로운 루트 디렉토리 /로 지정하고,
- 그 환경에서 /usr/bin/bash 를 실행한다.
- 즉, myroot 아래가 가짜 루트(/)가 되어, 그 안에서 다른 시스템은 보이지 않게 된다.
- 이건 컨테이너의 파일 시스템 격리 개념의 기반이다.
- ldd : 실행 파일이 어떤 공유 라이브러리를 필요로 하는지 보여주는 명령어
ldd /usr/bin/bash
- 이 명령어는 해당 실행 파일(bash)이 동작하는 데 어떤 라이브러리 파일이 필요한지를 보여준다.
예시출력:
libtinfo.so.6 => /lib/aarch64-linux-gnu/libtinfo.so.6
libc.so.6 => /lib/aarch64-linux-gnu/libc.so.6
- 위 파일들이 없다면 bash는 실행될 수 없기 때문에, chroot 환경 안에도 직접 복사해줘야 한다.
리눅스 독립 실행 환경 만들기 실습
1. 가짜 루트(root) 파일 시스템 만들기
mkdir -p ~/myroot
- myroot는 우리가 만드는 작은 리눅스 환경의 시작점이다.
- 이 디렉토리를 나중에 리눅스의 루트 /라고 생각게 된다.
2. bash 실행 파일 확인
which bash
# -> /usr/bin/bash
- 실제 시스템의 bash 경로를 확인한다.
- 이 파일을 myroot/usr/bin/ 아래로 복사해야 하므로 폴더도 같이 생성해준다.
mkdir -p ~/myroot/usr/bin
cp /usr/bin/bash ~/myroot/usr/bin/
- -p는 -parents 옵션으로, 상위 디렉토리가 없으면 자동으로 함께 생성해주는 옵션이다.
- ~/myroot
- ~/myroot/usr
- ~/myroot/usr/bin
- 위 디렉토리 중에 없는 게 있으면 전부 만들어준다. -p 없이 mkdir해버리면 오류가 난다.
3. bash가 실행되려면 필요한 라이브러리 확인 ldd
ldd /usr/bin/bash
- bash는 혼자 실행되지 않는다. 위처럼 라이브러리(so파일)들이 필요하다.
- 이처럼 bash가 실행되기 위해 필요한 라이브러리를 ldd로 확인한다.
4. 필요한 라이브러리 복사
mkdir -p ~/myroot/lib/aarch64-linux-gnu
sudo cp /lib/aarch64-linux-gnu/libtinfo.so.6 ~/myroot/lib/aarch64-linux-gnu/
sudo cp /lib/aarch64-linux-gnu/libc.so.6 ~/myroot/lib/aarch64-linux-gnu/
sudo cp /lib/ld-linux-aarch64.so.1 ~/myroot/lib/
- lib, libc, ld-linux 등은 bash가 실행되기 위한 기본 의존성이다.
- 해당 위치 그대로 디렉토리 구조도 맞춰줘야 한다.
5. chroot 명령어로 진입해 보기
sudo chroot ~/myroot /usr/bin/bash
- chroot는 root 디렉토리를 ~/myroot로 바꿔서 bash를 실행하는 명령이다.
- 여기서 실행된 bash는 외부 시스템 파일을 못 보고, myroot/ 안에 있는 것만 볼 수 있다. 즉, 일종의 미니 리눅스 환경이 되는 셈이다.
6. 내부 명령어 테스트
ls
# -> bash: command not found
- 명령어를 찾을 수 없다고 뜨는 것이 당연하다. ls 명령어가 없기 때문이다.
-
bash처럼 ls도 동일한 작업이 필요하다.
- 먼저 ldd 명령어로 필요한 라이브러리를 확인해준다.
- ldd에서 확인한 경로 그대로 복사해준다.
cp /usr/bin/ls ~/myroot/usr/bin/
sudo cp /lib/aarch64-linux-gnu/libselinux.so.1 ~/myroot/lib/aarch64-linux-gnu/
sudo cp /lib/aarch64-linux-gnu/libpcre2-8.so.0 ~/myroot/lib/aarch64-linux-gnu/
- 이후 다시 chroot를 이용해 bash를 실행하고 ls 명령어를 입력해보면 아래와 같은 결과가 나온다.
7. 만든 환경을 Docker 이미지로 만들기
cd ~
sudo tar -C myroot -cf myroot.tar .
- tar : 파일을 묶어서 .tar 형식으로 압축하는 명령어이다.
- -C myroot : myroot/ 디렉토리로 먼저 이동해서 tar 작업 수행
- -c : 새 tar 파일을 생성(create)
- -f myroot.tar : 결과를 myroot.tar라는 파알로 저장
-
. : 현재 디렉토리(즉 myroot/ 내부)의 모든 파일과 폴더를 압축 대상으로 지정
- 결과적으로 myroot/ 내부 내용만 담긴 myroot.tar가 생성된다.
cat myroot.tar | docker import - my-custom-image
- cat myroot.tar : tar 파일 출력
-
** ** : 그 출력을 다음 명령어로 전달(파이프) -
docker import - my-custom-image : 표준 입력(즉 -)으로 받은 tar 파일을 Docker 이미지로 가져오기
- 결과적으로 myroot.tar에 있는 루트 파일 시스템이 Docker 이미지로 변환된다.
- docker images 명령으로 확인이 가능하다.
+ tar?
tar은 여러 개의 파일과 디렉토리를 하나의 파일로 묶어주는 유틸리티이다. 원래 이름은 tape archive이다. .tar 확장자는 묶기만 하고 압축은 하지 않는다.
tar -cf archive.tar folder/ # folder/를 하나의 tar 파일로 묶는다.
tar -xvf archive.tar # 묶은 tar 파일을 다시 푼다.
- 왜 Docker 이미지를 생성할 때 tar를 사용하는가?
- Docker에서 이미지의 핵심은 ‘루트 파일 시스템(rootfs)이다. 즉, 컨테이너가 부팅되었을 때 처음으로 마운트할 시스템을 Docker는 필요로 한다.
- 우리가 만든 myroot/ 폴더는 루트 파일 시스템(rootfs) 역할을 한다.
- 이걸 tar로 묶어서, 컨테이너 내부에 설치된 리눅스 파일 시스템 전체를 하나로 전달한다.
- Docker는 이 tar 파일을 읽어서 이미지의 기반으로 사용한다.
- Docker는 rootfilesystem 전체를 하나의 묶음으로 받고 싶어 하며, Docker가 tar을 입력 포맷으로 공식 지원한다. docker import는 tar 또는 tar.gz 포맷만 받도록 설계되어 있다.
docker import [TAR FILE | URL | -] [REPOSITORY[:TAG]]
Docker 데몬과 API는 “tar 포맷”을 기준으로 파일 시스템을 해석하도록 설계되어 있다. 따라서 파일을 직접 하나하나 넘겨줄 수 없고, 내부적으로든 외부적으로든 결국 tar로 포장해서 주어야 한다.