3 minute read

독립된 실행 환경

이번 실습은 다음을 보여준다.

  1. 리눅스에서 독립적인 실행 환경을 만드는 방식 : 즉, 실제 운영체제처럼 작동하는 작은 루트 파일 시스템(rootfs)을 구성해보고, 그 환경에서 직접 명령어 실행이 가능하도록 세팅하는 것이 목적이다.

  2. 컨테이너 기술의 기초 원리를 실습으로 이해하기 : Docker, LXC(Linux Container), systemd-nspawn 같은 컨테이너 기술은 결국 chroot, namespaces, cgroups를 활용한다. 그중 chroot는 파일 시스템 관점에서 “루트(/)를 제한”하는 가장 기본적인 격리 수단이다. 이 실습은 Docker의 내부 동작의 파일 시스템 격리(File System Isolation)을 이해하기 위한 기초 실습이다.

실습을 통해 얻을 수 있는 이해

  • 컨테이너는 어떻게 독립적인가?
    • Docker가 격리된 환경을 제공하는 이유는 단순히 가상머신이 아니라, 이렇게 루트 파일 시스템을 제한(chroot)하고, 그 안에 필요한 바이너리와 라이브러리를 넣기 때문이다.
    • 이 실습은 그 원리를 맨바닥부터 직접 구성한 것이다.
  • 파일 시스템이 격리의 핵심이라는 것
    • chroot 환경에서는 호스트 파일 시스템에 접근이 불가능하다. 완전히 분리된 새로운 루트로 바뀐다. 이걸 기반으로 Docker는 파일, 네트워크, 프로세스, 사용자 등 다양한 격리를 추가한다.

사용할 명령어 설명

  1. chroot : 실행 환경의 루트를 바꿔주는 명령어
sudo chroot ./myroot /usr/bin/bash
  • ./myroot 디렉토리를 새로운 루트 디렉토리 /로 지정하고,
  • 그 환경에서 /usr/bin/bash 를 실행한다.
  • 즉, myroot 아래가 가짜 루트(/)가 되어, 그 안에서 다른 시스템은 보이지 않게 된다.
  • 이건 컨테이너의 파일 시스템 격리 개념의 기반이다.
  1. 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해버리면 오류가 난다.

Alt text

3. bash가 실행되려면 필요한 라이브러리 확인 ldd

ldd /usr/bin/bash

Alt text

  • 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가 실행되기 위한 기본 의존성이다.
  • 해당 위치 그대로 디렉토리 구조도 맞춰줘야 한다.

Alt text

5. chroot 명령어로 진입해 보기

sudo chroot ~/myroot /usr/bin/bash
  • chroot는 root 디렉토리를 ~/myroot로 바꿔서 bash를 실행하는 명령이다.
  • 여기서 실행된 bash는 외부 시스템 파일을 못 보고, myroot/ 안에 있는 것만 볼 수 있다. 즉, 일종의 미니 리눅스 환경이 되는 셈이다.

6. 내부 명령어 테스트

ls
# -> bash: command not found
  • 명령어를 찾을 수 없다고 뜨는 것이 당연하다. ls 명령어가 없기 때문이다.
  • bash처럼 ls도 동일한 작업이 필요하다.

  • 먼저 ldd 명령어로 필요한 라이브러리를 확인해준다.

Alt text

  • 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/

Alt text

  • 이후 다시 chroot를 이용해 bash를 실행하고 ls 명령어를 입력해보면 아래와 같은 결과가 나온다.

Alt text

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 명령으로 확인이 가능하다.

Alt text

Alt text

+ 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로 포장해서 주어야 한다.