실행 중인 컨테이너의 파일시스템은 어디에 저장되는 것일까요?
mount 명령을 수행하면 나오는 복잡한 디렉토리들은 컨테이너 수행과 어떤 관계가 있을까요?
이 글에서는 컨테이너 파일시스템을 저장하는데 사용되는 오버레이 파일시스템에 대해 알아보겠습니다.
오버레이(Overlay) 파일시스템
오버레이 파일시스템은 Linux 커널 3.18 릴리스에 처음 포함되었고, 컨테이너 이미지를 저장하는데
많이 사용됩니다.
유니온(union) 파일 시스템이라고 불리기도 합니다.
오버레이 파일시스템은 두 개의 레이어 , 상위(Upper) 레이어와 하위(Lower) 레이어를 병합합니다.
두 레이어 모두에 동일 이름의 파일이 있으면 상위 레이어의 파일만 사용되며 디렉터리의 경우
상위 레이어와 병합됩니다.
[ 오버레이파일 시스템의 작동 방식 ]
아래와 같이 mount 명령으로 오버레이 파일시스템을 생성할 수 있습니다.
workdir는 오버레이 파일시스템 내부에서 사용하는 디렉토리로 upper 디렉토리와 같은 파일시스템에 존재하는 빈 디렉토리여야 합니다.
merged 디렉토리에 lower/file1과 upper/file2가 병합된 것을 확인할 수 있습니다.
lower와 upper에 모두 있는 파일(file2)의 경우 upper에 있는 파일로 병합됩니다.
# mkdir lower upper work merged
# echo aaa > lower/file1
# echo aaa > lower/file2
# echo bbb > upper/file2
# mount -t overlay overlay -olowerdir=lower,upperdir=upper,workdir=work merged
# tree
.
├── lower
│ ├── file1
│ └── file2
├── merged
│ ├── file1
│ └── file2
├── upper
│ └── file2
└── work
└── work
# cat merged/file1
aaa
# cat merged/file2
bbb
Bash
복사
병합된 파일 중 하위 레이어의 파일은 읽기 전용이며, 변경하면 상위 레이어로 복사되고 상위 레이어에 변경된 파일이 저장됩니다.
lower 디렉토리에서 병합된 merged/file1을 수정하면 lower/file1은 그대로 있고, upper/file1로 복사된 후 수정이 발생함을 확인할 수 있습니다.
# echo ccc >> merged/file1
# cat merged/file1
aaa
ccc
# cat lower/file1
aaa
# tree
.
├── lower
│ ├── file1
│ └── file2
├── merged
│ ├── file1
│ └── file2
├── upper
│ ├── file1
│ └── file2
└── work
└── work
Bash
복사
하위 레이어의 파일을 삭제하면 삭제되었다는 표시를 하는 파일이 상위 레이어에 생성됩니다.
merged/file1을 삭제하면, 삭제되었다는 표시를 해주는 캐릭터 디바이스 파일(upper/file1)이 생성됨을
확인할 수 있습니다.
# rm merged/file1
# tree
.
├── lower
│ ├── file1
│ └── file2
├── merged
│ └── file2
├── upper
│ ├── file1
│ └── file2
└── work
└── work
# ls -l upper/file1 // 맨 앞의 "c"는 캐릭터 디바이스 파일을 의미
c--------- 1` `root root 0, 0` `4월 29` `13:10` `upper/file1
Bash
복사
컨테이너 이미지와 오버레이 파일시스템
오버레이 파일시스템은 컨테이너 이미지를 저장하는 용도로 많이 사용됩니다.
Docker, Containerd 등의 CRI들은 컨테이너가 오버레이 파일시스템을 사용할 수 있도록 지원합니다.
컨테이너 이미지는 하위 레이어를 구성합니다.
하위 레이어는 읽기 전용이므로 이미지는 변경될 수 없습니다.
모든 변경 사항은 컨테이너 내부에서 발생하며 컨테이너가 삭제되면 없어집니다.
변경 사항이 유지되게 하려면 볼륨을 사용해야 합니다.
Docker 및 오버레이 파일 시스템 구성
하위 레이어는 여러 디렉토리를 지정할 수 있습니다.
예로, 우분투 기본 파일들을 저장한 디렉토리와 Nginx 파일들을 저장한 디렉토리를 하위 레이어로 지정하면,
Nginx가 설치된 우분투 이미지를 컨테이너에 제공할 수 있습니다.
하위 레이어는 읽기 전용이므로 여러 컨테이너에서 공유할 수 있습니다.
이렇게 오버레이 파일 시스템을 사용함으로써 여러 컨테이너에서 공통으로 필요한 파일들을 중복하지 않고, 공유함으로써 디스크를 효율적으로 사용할 수 있습니다.
docker pull로 다운받은 이미지들이 로컬 머신에서 어떻게 저장되는지 확인해 보겠습니다.
$ docker pull nginx:latest
latest: Pulling from library/nginx
09f376ebb190: Pull complete
a11fc495bafd: Pull complete
933cc8470577: Pull complete
999643392fb7: Pull complete
971bb7f4fb12: Pull complete
45337c09cd57: Pull complete
de3b062c0af7: Pull complete
Digest: sha256:a484819eb60211f5299034ac80f6a681b06f89e65866ce91f356ed7c72af059c
Status: Downloaded newer image for` `nginx:latest
Bash
복사
docker info 명령으로 Storage Driver로 오버레이 파일시스템을 사용하고 있는지 확인할 수 있습니다.
$ docker info | grep "Storage Driver"
Storage Driver: overlay2
Bash
복사
docker image inspect [이미지명] 명령 결과에서
LowerDir들을 ls 명령으로 확인해 보면 이미지를 구성하는 실제 파일이 있음을 확인할 수 있습니다.
아래에서부터 OS 이미지, nginx 이미지, 변경된 내용들이 차례로 있음을 알 수 있습니다.
UpperDir에는 이미지에서 마지막으로 변경된 내용이 있습니다.
MergedDir은 존재하지 않는 것을 알 수 있습니다.
$ docker image inspect nginx | jq '.[].GraphDriver'
"GraphDriver": {
"Data": {
"LowerDir": "/var/lib/docker/overlay2/e19e990500af62f111921b3de431ee5209394e27bac1bc5a71f4ec55526d2148/diff:
/var/lib/docker/overlay2/e888a9cfbc7847b15f7d7752bda567a893bbe0d6bcb2fcbc3bc403488236bb7e/diff:
/var/lib/docker/overlay2/f17498d2b8350cb28cd4752a58777f8401f7d495a90a4ea00c8c4b32725a1803/diff:
변경된 파일 => /var/lib/docker/overlay2/eb8296601684ebbe1f86a25e8e704aa7a2f8be6856f5b897435f4612b9330be4/diff:
nginx 이미지=> /var/lib/docker/overlay2/f7148571df60dcb47c2f274ad444b1bc198668e7988384f946585f837a86fcb2/diff:
OS 이미지 => /var/lib/docker/overlay2/e8076829e8a232fa06ff8c417208f411a2c508a1f15e77b0823bf9a6dbcf0a40/diff",
"MergedDir": "/var/lib/docker/overlay2/bb37199d6af20645c1450ffe79f0148b1ef1c25a25ffb590a6ecda792bbbdc9f/merged",
"UpperDir": "/var/lib/docker/overlay2/bb37199d6af20645c1450ffe79f0148b1ef1c25a25ffb590a6ecda792bbbdc9f/diff",
"WorkDir": "/var/lib/docker/overlay2/bb37199d6af20645c1450ffe79f0148b1ef1c25a25ffb590a6ecda792bbbdc9f/work"
},
"Name": "overlay2"
},
# ls /var/lib/docker/overlay2/e8076829e8a232fa06ff8c417208f411a2c508a1f15e77b0823bf9a6dbcf0a40/diff
bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
# ls /var/lib/docker/overlay2/f7148571df60dcb47c2f274ad444b1bc198668e7988384f946585f837a86fcb2/diff
docker-entrypoint.d etc usr var
# ls /var/lib/docker/overlay2/eb8296601684ebbe1f86a25e8e704aa7a2f8be6856f5b897435f4612b9330be4/diff
docker-entrypoint.sh
# ls /var/lib/docker/overlay2/bb37199d6af20645c1450ffe79f0148b1ef1c25a25ffb590a6ecda792bbbdc9f/merged
ls: cannot access '/var/lib/docker/overlay2/bb37199d6af20645c1450ffe79f0148b1ef1c25a25ffb590a6ecda792bbbdc9f/merged': No such file or directory
Bash
복사
다운로드 된 이미지 저장 경로
그러면 실제로 컨테이너 수행시 컨테이너에 어떻게 파일시스템이 제공되는지 확인해 보겠습니다.
한 쉘에서 도커 컨테이너를 수행을 합니다.
$ docker run -it nginx:latest bash
root@d04c92c367f6:/#
Bash
복사
다른 쉘에서 docker inspect 명령으로 컨테이너 ID가 d04c92c367f6 인 컨테이너의 이미지 경로를 보면
LowDir에는 다운로드된 이미지의 LowDir 위에 이미지의 UpperDir이 추가되는 것을 확인 할 수 있습니다.
UpperDir에는 컨테이너에서 변경한 파일이 없으므로 비어 있습니다.
MergedDir에는 LowerDir과 UpperDir의 병합된 파일시스템이 마운트 된 것을 확인할 수 있습니다.
ㅊ$ docker inspect d04c92c367f6 | jq '.[].GraphDriver'
{
"Data": {
"LowerDir": "/var/lib/docker/overlay2/b9075929a1190e8fcb811b062202a4e563d04a092a553c6396f32cba12c433a9-init/diff:
다운로드된 이미지의 UpperDir=> /var/lib/docker/overlay2/bb37199d6af20645c1450ffe79f0148b1ef1c25a25ffb590a6ecda792bbbdc9f/diff:
다운로드된 이미지의 LowDir=> /var/lib/docker/overlay2/e19e990500af62f111921b3de431ee5209394e27bac1bc5a71f4ec55526d2148/diff:
/var/lib/docker/overlay2/e888a9cfbc7847b15f7d7752bda567a893bbe0d6bcb2fcbc3bc403488236bb7e/diff:
/var/lib/docker/overlay2/f17498d2b8350cb28cd4752a58777f8401f7d495a90a4ea00c8c4b32725a1803/diff:
/var/lib/docker/overlay2/eb8296601684ebbe1f86a25e8e704aa7a2f8be6856f5b897435f4612b9330be4/diff:
/var/lib/docker/overlay2/f7148571df60dcb47c2f274ad444b1bc198668e7988384f946585f837a86fcb2/diff:
/var/lib/docker/overlay2/e8076829e8a232fa06ff8c417208f411a2c508a1f15e77b0823bf9a6dbcf0a40/diff",
"MergedDir": "/var/lib/docker/overlay2/b9075929a1190e8fcb811b062202a4e563d04a092a553c6396f32cba12c433a9/merged",
"UpperDir": "/var/lib/docker/overlay2/b9075929a1190e8fcb811b062202a4e563d04a092a553c6396f32cba12c433a9/diff",
"WorkDir": "/var/lib/docker/overlay2/b9075929a1190e8fcb811b062202a4e563d04a092a553c6396f32cba12c433a9/work"
},
"Name": "overlay2"
}
# ls /var/lib/docker/overlay2/b9075929a1190e8fcb811b062202a4e563d04a092a553c6396f32cba12c433a9/merged
bin boot dev docker-entrypoint.d docker-entrypoint.sh etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
# ls /var/lib/docker/overlay2/b9075929a1190e8fcb811b062202a4e563d04a092a553c6396f32cba12c433a9/diff <= UpperDir에는 변경된 내용이 아직 없음
Bash
복사
실행 중인 컨테이너의 이미지 저장 경로
mount 명령으로 확인해 보면 해당 LowerDir, UpperDir이 MergedDir에 마운트된 것을
확인할 수 있습니다.
mount 명령의 경로는 경로 단축을 위해 링크 파일을 사용하고 있는 것을 확인할 수 있습니다.
# mount | grep "b9075929a1190e8fcb811b062202a4e563d04a092a553c6396f32cba12c433a9/merged"
overlay on /var/lib/docker/overlay2/b9075929a1190e8fcb811b062202a4e563d04a092a553c6396f32cba12c433a9/merged type overlay
(rw,relatime,lowerdir=/var/lib/docker/overlay2/l/ADHNUT7S5HOMW6HGGFE5GPAYT4: <= 단축 경로
/var/lib/docker/overlay2/l/6N3F2NJPXFEBME2FNT5WWN6ST3:
/var/lib/docker/overlay2/l/UEIMVPSILNPFNJPC4U66ZIHCKS:
/var/lib/docker/overlay2/l/VCQ7VTF6MLN2KBROWBZNF27PC4:
/var/lib/docker/overlay2/l/RJ24Q6Y5EKQGQH3IJBKGEKJ7IN:
/var/lib/docker/overlay2/l/WELEQ5MREYSYVOW3ZZYEJMRJO4:
/var/lib/docker/overlay2/l/B27DHIBPYABPTFHKLW4B257UMN:
/var/lib/docker/overlay2/l/FSVT7UQBDH6Y5LJVHK3DXKZPIX,
upperdir=/var/lib/docker/overlay2/b9075929a1190e8fcb811b062202a4e563d04a092a553c6396f32cba12c433a9/diff,
workdir=/var/lib/docker/overlay2/b9075929a1190e8fcb811b062202a4e563d04a092a553c6396f32cba12c433a9/work)
# ls -ㅣ /var/lib/docker/overlay2/l/ADHNUT7S5HOMW6HGGFE5GPAYT4
lrwxrwxrwx 1 root root 77 5월 31 14:08 /var/lib/docker/overlay2/l/ADHNUT7S5HOMW6HGGFE5GPAYT4 -> ../b9075929a1190e8fcb811b062202a4e563d04a092a553c6396f32cba12c433a9-init/diff
Bash
복사
이번에는 컨테이너 내에서 파일 변경을 해 보겠습니다.
$ docker run -it nginx:latest bash
root@d04c92c367f6:/# rm /etc/environment
root@d04c92c367f6:/# echo "NEW FILE" > /root/newfile.txt
root@d04c92c367f6:/#
Bash
복사
UpperDir에 변경된 내용을 확인 할 수 있습니다.
# ls -l /var/lib/docker/overlay2/b9075929a1190e8fcb811b062202a4e563d04a092a553c6396f32cba12c433a9/diff/etc
total 0
c--------- 1 root root 0, 0 5월 31 15:08 environment
# cat /var/lib/docker/overlay2/b9075929a1190e8fcb811b062202a4e563d04a092a553c6396f32cba12c433a9/diff/root/newfile.txt
NEW FILE
Bash
복사
해당 컨테이너를 종료한 후에도 변경된 이미지가 남아 있음을 확인 할 수 있습니다.
$ docker kill d04c92c367f6
d04c92c367f6
$ docker inspect d04c92c367f6 | jq '.[].GraphDriver'
..... 생략 .....
# cat /var/lib/docker/overlay2/b9075929a1190e8fcb811b062202a4e563d04a092a553c6396f32cba12c433a9/diff/root/newfile.txt
NEW FILE
Bash
복사
종료된 컨테이너를 삭제하고 나면 변경된 이미지가 삭제되는 것을 확인 할 수 있습니다.
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d04c92c367f6 nginx:latest "/docker-entrypoint.…" 2 hours ago Exited (0) 6 minutes ago quizzical_shaw
$ docker rm d04c92c367f6
d04c92c367f6
$ docker inspect d04c92c367f6
[]
Error: No such object: d04c92c367f6
# cat /var/lib/docker/overlay2/b9075929a1190e8fcb811b062202a4e563d04a092a553c6396f32cba12c433a9/diff/root/newfile.txt
cat: /var/lib/docker/overlay2/b9075929a1190e8fcb811b062202a4e563d04a092a553c6396f32cba12c433a9/diff/root/newfile.txt: No such file or directory
Bash
복사
오버레이 파일시스템의 호환성 문제
오버레이 파일시스템은 POSIX 표준을 모두 만족하지 못합니다.
"foo"라는 파일을 읽기 전용(O_RDONLY)으로 open하고 나서, 읽고/쓰기(O_RDWR) 모드로 open하면
POSIX 표준에서는 같은 파일을 가리켜야 되지만,
오버레이 파일시스템에서는 읽고/쓰기 모드로 open할 때 upper 레이어로 파일 복사가 이루어져
다른 파일을 가리키게 됩니다.
fd1=open("foo", O_RDONLY) // fd1 : 하위 레이어의 foo 파일
fd2=open("foo", O_RDWR) // fd2 : 상위 레이어의 foo 파일
C
복사
이런 문제를 회피하기 위해서는 읽고쓰기 모드로 먼저 open하고 난 다음 읽기 모드로 open하면 됩니다.
fd2=open("foo", O_RDWR) // fd2 : 상위 레이어의 foo 파일
fd1=open("foo", O_RDONLY) // fd1 : 상위 레이어의 foo 파일
C
복사
rename 시스템콜도 완전히 지원되지 않습니다.
이 문제를 회피하기 위해서는,
rename 시스템콜 실패가 발생시 복사후 삭제하는 방식으로 애플리케이션을 구현해야 합니다.



