Mount Namespaces and Propagation Modes
한국어 원문으로 표시합니다.
한 줄 요약
/proc/self/mountinfo 는 "이 프로세스가 보는 마운트 트리 전체"를 담고 있고, 거기에는 mount 명령이 보여 주지 않는 전파 방식(propagation) 까지 적혀 있다.
왜 이게 필요했나
컨테이너를 쓰기 시작하면 마운트가 갑자기 어려워진다. 호스트에서 붙인 볼륨이 컨테이너에 안 보이거나, 컨테이너 안에서 붙인 것이 호스트로 새거나, 컨테이너를 지웠는데 마운트가 남아 있다. 이 현상들은 전부 마운트 네임스페이스와 전파 방식으로 설명된다.
어떻게 동작하나
mountinfo 읽기
36 25 0:31 / /sys/fs/cgroup rw,nosuid,nodev,noexec shared:9 - cgroup2 cgroup2 rw
[1][2][3] [4] [5] [6] [7] [8] [9] [10] [11]
| 필드 | 의미 |
|---|---|
| 1 | 마운트 ID |
| 2 | 부모 마운트 ID |
| 3 | major:minor 장치 번호 |
| 4 | 원본 안에서의 루트 — 바인드 마운트면 여기가 / 가 아니다 |
| 5 | 마운트 지점 |
| 6 | 마운트 옵션 |
| 7 | 선택 필드 — 전파 방식 (shared:, master:, propagate_from:, 없으면 private) |
| 8 | 구분자 - |
| 9~11 | 파일시스템 타입, 소스, 상위 옵션 |
4번 필드가 바인드 마운트를 식별하는 열쇠다. 같은 장치의 하위 디렉터리를 붙였다면 거기에 / 대신 그 하위 경로가 적힌다.
전파 방식 네 가지
| 방식 | mountinfo 표시 | 동작 |
|---|---|---|
| private | (표시 없음) | 이 마운트의 변화가 아무 데도 전파되지 않는다 |
| shared | shared:N |
양방향 전파. 여기서 붙이면 짝에게도 보이고 반대도 마찬가지 |
| slave | master:N |
단방향. 마스터의 변화는 받지만 내 변화는 안 보낸다 |
| unbindable | unbindable |
바인드 마운트의 원본이 될 수 없다 |
컨테이너 런타임이 볼륨을 붙일 때 rslave 나 rprivate 를 쓰는 이유가 여기 있다. 컨테이너 안에서 실수로 붙인 마운트가 호스트로 새면 곤란하고, 반대로 호스트가 붙인 것은 보여야 하는 경우가 많다.
tmpfs
메모리 위의 파일시스템이다. 재부팅하면 사라진다.
tmpfs /var/cache/app tmpfs size=256M,mode=1777,noexec,nosuid,nodev 0 0
size=를 반드시 지정한다. 없으면 기본값이 물리 메모리의 절반이고, 누가 큰 파일을 쓰면 시스템 전체가 메모리 압박을 받는다.- tmpfs 는 스왑으로 나갈 수 있다. 진짜 램디스크를 원하면
ramfs지만, ramfs 는 크기 제한이 없어 더 위험하다. mode=1777은 sticky bit./tmp처럼 모두가 쓰되 남의 파일은 못 지우게 한다.
systemd 의 .mount 유닛
fstab 대신 유닛 파일로도 마운트를 정의할 수 있다. 유닛 이름은 마운트 지점 경로를 이스케이프한 것이어야 한다.
# /etc/systemd/system/var-cache-app.mount
[Unit]
Description=Application cache (tmpfs)
[Mount]
What=tmpfs
Where=/var/cache/app
Type=tmpfs
Options=size=256M,mode=1777,noexec,nosuid,nodev
[Install]
WantedBy=local-fs.target
/var/cache/app → var-cache-app.mount. 앞의 슬래시는 빼고, 나머지 슬래시는 하이픈으로 바꾼다. 이름이 경로와 안 맞으면 systemd 가 유닛을 거부한다. systemd-escape --path /var/cache/app 로 정확한 이름을 얻을 수 있다.
마운트가 사라지거나 안 보일 때
바인드 마운트와 tmpfs 는 단순해 보이는데, 전파와 이름공간 때문에 예상과 다르게 동작하는 경우가 있다.
컨테이너 안에서 나중에 붙인 마운트가 안 보인다. 호스트에서 /mnt/data 에
새 디스크를 마운트해도, 이미 그 경로를 바인드해 둔 컨테이너에는 안 나타난다.
전파 방식이 private 이면 이후의 마운트가 전달되지 않기 때문이다. 쿠버네티스에서
mountPropagation: HostToContainer 를 쓰는 이유가 이것이다.
findmnt -o TARGET,SOURCE,PROPAGATION /mnt/data
tmpfs 는 메모리를 쓴다. df 에는 파일 시스템으로 보이지만 실제로는 RAM 이고,
컨테이너에서는 그 메모리가 cgroup 한도에 포함된다. /tmp 를 tmpfs 로 두고
큰 파일을 쓰면 OOM 으로 죽는다. 크기를 반드시 정한다.
tmpfs /tmp tmpfs size=512M,mode=1777,nosuid,nodev 0 0
빈 디렉터리에 마운트하지 않으면 원래 내용이 가려진다. 그 파일들은 사라진 것이 아니라 밑에 깔린 채 공간을 차지한다. 되찾으려면 마운트를 걷거나 다른 곳에 바인드해서 본다.
mkdir /mnt/under && mount --bind / /mnt/under
ls /mnt/under/mnt/data # 가려진 원래 내용
umount 가 안 될 때는 누가 쓰고 있는지 본다.
lsof +f -- /mnt/data
fuser -vm /mnt/data
umount -l /mnt/data # 마지막 수단: 이름만 떼고 나중에 정리한다
-l(lazy)은 열려 있는 파일이 닫힐 때까지 실제로는 붙어 있으므로, 디스크를
물리적으로 빼는 상황에서는 안전하지 않다.
바인드 마운트에 걸린 옵션은 두 번 걸어야 한다. mount --bind -o ro 는
리눅스에서 읽기 전용이 되지 않는다. 붙인 뒤에 다시 걸어야 한다.
mount --bind /src /dst
mount -o remount,ro,bind /dst
현장에서 만나는 모습
컨테이너를 지웠는데 마운트가 남는다. 전파 방식이 shared 인 상태에서 컨테이너가 마운트를 만들면 호스트 네임스페이스로 새어 나온다. 컨테이너가 죽어도 그 마운트는 호스트에 남아 디스크를 붙잡는다. findmnt 로 찾아 수동으로 정리해야 한다.
tmpfs 크기를 안 정해 OOM 이 난다. size= 없이 만든 tmpfs 에 로그가 쌓여 메모리를 다 먹었다. 디스크가 아니라 메모리라는 사실을 잊기 쉽다.
다음 실습에서 할 것
/proc/self/mountinfo 를 직접 파싱해 마운트 트리와 전파 방식을 표로 만들고, 바인드 마운트와 tmpfs 의 fstab 줄을 작성하고, .mount 유닛 파일을 정확한 이름으로 만든다.