개발일기/Docker

4. Mount 네임스페이스로 파일시스템 분리하기

ignuy 2025. 5. 30.

4. Mount 네임스페이스로 파일시스템 분리하기

Go로 "컨테이너스러운" 프로세스를 만들어보자.

 

지금까지 PID, UTS 네임스페이스를 활용하여 프로세스 ID와 호스트 명을 격리하였다. 이번엔 파일시스템을 분리하여 컨테이너 안에서 진짜 루트 디렉터리가 다르게 보이도록 만들어보자. 이를 위해 Mount 네임스페이스를 활용할 것이다.

 

🔍 Mount 네임스페이스란?

Mount?

리눅스에서는 디스크나 외부 장치를 어떤 디렉터리 경로에 연결해야만 접근할 수 있다. 이 과정을 "mount"라고 부른다.

$ sudo mount /dev/sdb1 /mnt/usb
$ ls /mnt/usb
  사진/ 문서/ 영화/

리눅스는 하나의 거대한 트리 구조(/) 안에 모든 저장 장치, 파티션, 네트워크 파일시스템 등을 "붙여서" 사용하게 된다. C:\또는 D:\와 같이 드라이브가 분리되어 있는 윈도우와는 다르게 리눅스에서 /home, /var, /mnt/usb 등은 실제로는 다른 디스크일 수 있다. mount 덕분에 하나의 트리로 통합되는 것이다.

🎯 Mount 네임스페이스

리눅스의 이러한 특징 때문에 OS 관리 차원에서 서로 분리된 다른 파일시스템을 다루어야 할 때가 있다. 이 때, Mount 네임스페이스를 활용하게 된다. Mount 네임스페이스는 각 프로세스가 서로 다른 파일 시스템 마운트 구조를 가지게 해주는 기능이다. 즉, 같은 리눅스 커널 위에 있는 프로세스라도, 전혀 다른 디렉터리 구조를 보게 할 수 있는 격리 기술이며 아래와 같은 특징을 가진다.

 

  • 컨테이너 내부에서 mount나 umount를 해도 외부에 영향 없음
  • chroot나 pivot_root를 써서 루트 디렉터리를 바꿀 수 있음
  • 특정 디렉토리만 바인드해서 공유할 수 있음
  • 리눅스에서 진짜 컨테이너처럼 격리된 파일시스템 환경을 구성하는 핵심

🧠 Mount vs Mount Namespace

항목 Mount Mount Namespace
역할 장치를 디렉토리에 연결 마운트 트리를 격리해서 다르게 보여줌
범위 시스템 전체에 적용 프로세스 단위로 적용
사용 이유 디스크 접근 파일시스템 격리와 보안

📂 코드 수정하기

✅ run() 수정

// isolation namespace
cmd.SysProcAttr = &syscall.SysProcAttr{
    Cloneflags: syscall.CLONE_NEWPID |
        syscall.CLONE_NEWUTS |
        syscall.CLONE_NEWNS, // 추가
}

 

우선, run() 함수에서 마운트 네임스페이스를 추가한다.

✅ child() 수정

func child() {
	fmt.Printf("Running child process PID %d\n", syscall.Getpid())

	must(syscall.Sethostname([]byte("containerM")), "set hostname")

	// 추가 Configure new root
	newRoot := "/tmp/containerRoot"
	must(syscall.Chroot(newRoot), "chroot")
	must(os.Chdir("/"), "chdir")

	cmd := exec.Command(os.Args[2], os.Args[3:]...)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	must(cmd.Run())
}

chroot를 적용하여 루트 디렉토리를 새롭게 설정한다.

chroot vs pivot_root

chroot()는 단순히 루트 디렉터리 보여주기를 바꾸지만, 보안상 약점이 있다. 따라서, 진짜 컨테이너처럼 격리하려면 pivot_root()를 써야 한다. 우리는 이 튜토리얼에선 단계적으로 구현하므로 우선 chroot()를 써서 실험해보자.

📂 루트 파일시스템 준비

실험 환경에 루트 파일시스템 역할을 할 디렉터리를 미리 준비한다. 본 실험에서는 간단히 busybox 바이너리를 포함한 디렉터리를 사용했다.

$ mkdir -p /tmp/containerRoot
$ cd /tmp/containerRoot
# busybox는 여러 유틸리티를 포함하는 미니멀 바이너리
$ wget https://busybox.net/downloads/binaries/1.35.0-x86_64-linux-musl/busybox
$ chmod +x busybox
$ mkdir bin
$ mv busybox bin/

# busybox 내부 명령어 목록을 기반으로 심볼릭 링크 생성
$ cd bin
$ ./busybox --list | while read cmd; do ln -s busybox "$cmd"; done

이제 ls, sh, cat, echo, mkdir 등 거의 모든 유틸리티 명령들이 busybox 바이너리를 가리키는 심볼릭 링크 형태로 생성되었다.

🧪 실험

빌드 방법은 전 포스팅과 동일하다. 빌드 후에 아래 명령어로 containerM을 실행해보자.

$ sudo ./containerM run /bin/sh

containerM 실행 후 열린 쉘 안에서 아래와 같은 실험을 진행해보자.

# pwd
/

# ls
bin

# hostname
containerM

4. Mount 네임스페이스로 파일시스템 분리하기 - 🔍 Mount 네임스페이스란? - 🧪 실험

📌 정리

기능 적용 방법
마운트 네임스페이스 CLONE_NEWNS
루트 변경 chroot() 또는 pivot_root()
파일시스템 격리 효과 컨테이너 안에서만 루트 디렉터리 바뀜

🔜 다음 이야기

다음 포스팅에서는 pivot_root()를 사용하여 chroot보다 더 안전하고 진짜 컨테이너에 가까운 루트 디렉터리 격리를 구현해보자.

 

댓글