오픈소스 인사이트
home
오픈소스 기술 동향
home
🧑‍🔧

컨테이너 이미지는 어디에 저장되나요?

7 more properties
실행 중인 컨테이너의 파일시스템은 어디에 저장되는 것일까요?
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 시스템콜 실패가 발생시 복사후 삭제하는 방식으로 애플리케이션을 구현해야 합니다.

📗 참조