얼마전 회사에서 트러블슈팅을 진행하면서 docker exec 명령어를 이용해서 container의 bash쉘을 연 다음에, 바로 컨테이너 내부의 파일을 변경한 적이 있었습니다. 그런데 저는 여기서 의아한 부분이 하나 있었습니다. 과연 Docker 컨테이너 내부의 파일을 저렇게 바로 변경해도 되는가? 였습니다. 이에 대해서 공부를 하던 중에, Ubuntu의 docker는 기본적인 파일시스템으로 Overlay2를 채택하고 있었고, 또한 Overlay2 스토리지 시스템의 특징으로 인해서 컨테이너 내부의 파일을 바로 변경한다고 해서, 근간이 되는 image의 레이어가 훼손되지는 않는다는 것을 알게되었습니다.
Overlay2 스토리지 시스템에 대해서 공부한 내용들을 이 포스트를 통해서 공유드려볼까 합니다.
1. 먼저 도커 이미지의 레이어부터 알아봅시다
먼저 nginx를 docker registry로부터 다운받아보는걸로 시작하겠습니다.
$ docker pull nginx:latest
그리고 아래의 inspect 명령어를 입력해보면 아래의 결과를 얻어볼 수 있을 것입니다.
$ docker inspect --format='{{json .RootFS.Layers}}' nginx:latest
[
"sha256:8450f74cd36b8dee83cbb893350e167acb792b321fad0c2180abfd1ef0fcdf55",
"sha256:b0ff37385ae938a3bcb5241789f33ddf9e3841cdbb16e51c9f5bf12d556899ab",
"sha256:1e451eaa30b322397195118e9c7e2fe22ae01e463d21afe36be988cb82ccd001",
"sha256:3c6148273d487a83886a7412441cbd39d142a7ab15b223a5f86cf5753efc53e2",
"sha256:482f7b7b1b0385901b7dd61fe06c63a9b8218725edcac24bb19c8eb82c01e495",
"sha256:83cb32b689ff60bf5aa47efdd1714c27f0ba4a160524f8e110725ea43461ae1e",
"sha256:facf79a780de4bb891d6a7beeb8e160d6201e46703cf7ddcc1ed5edd0760a7e8"
]
위의 커맨드가 시사하는 바는, 도커 이미지는 여러개의 레이어를 조합한 구조로 구성되어있다는 것입니다. 그리고 또한 이러한 레이어들로 구성된 이미지가 도커 명령어를 통해서 dockerd에 컨테이너로 올라가면, 해당 레이어들을 이용하여 컨테이너를 구성한다는 뜻이 되기도 합니다.
그리고 이러한 layer들은 모두 /var/lib/docker/overylay2 라는 폴더 내부에 저장이 되는데요, 이제 overlay2 폴더에 대해서 탐구해보는 시간을 가져보도록합시다.
2. Union FS에 대해서 알아봅시다.
먼저 Union FS를 설명하기 이전에 하나의 재밌는 예시를 들어보고 시작하겠습니다.
도커 파일시스템이 만약에 컨테이너 별로 독립적이라고 가정해봅시다.
첫번째로 mysql + ubuntu 로 결합된 이미지를 하나 컨테이너로 올린다고 합시다. 그리고 첫번째 이미지를 컨테이너로 올린 다음에, mysql + ubuntu + nginx 로 구성된 이미지를 하나 컨테이너로 올린다고 가정해봅시다. 그러면 첫번째 컨테이너는 mysql, ubuntu에 존재하는 layer들을 컨테이너 내부에 가지고 있어야하고, 두번째 컨테이너는 mysql, ubuntu, nginx에 존재하는 layer들을 컨테이너 내부에 가지고 있어야할 것입니다.
이러한 경우에는, 두 컨테이너가 mysql, ubuntu 가 가지고 있는 레이어들이 중복됨에도 불구하고 별개로 관리를 해야하기 때문에 용량 낭비가 발생할 것입니다.
그러면 다른 가정으로, 도커 파일시스템이 공통된 레이어에 한해서는 통합해서 관리하는 파일시스템으로 운영된다고 가정해봅시다. 그렇게되면 위의 사례에서 mysql, ubuntu에서 사용하게되는 layer들이 공통으로 관리되기 때문에 용량이 낭비되는 일은 줄어들것으로 예상이됩니다. 이게 union FS를 설명하기 위한 첫걸음입니다.
Union FS는 상속파일시스템 으로, 여러개의 파일시스템을 여러개를 쌓고, 밑에서부터 레이어를 쌓아서 새로운 하나의 파일시스템을 구성하는 방식으로 동작합니다. 그러면 이제 Union FS의 특징에 대해서 알아보도록 하겠습니다.
- 앞서 설명드렸듯이, 여러개의 파일 시스템을 하나로 합쳐서 마운트시킵니다.
- 레이어가 쌓이는 순서도 중요합니다. 만일 쓰기가 발생하여 파일이 수정되는 경우, 전 파일과 이후 파일의 이름은 동일하기 때문에 어느게 덮어쓰여지는지가 중요하기 때문입니다.
- 유니온 파일시스템은 CoW(Copy on Write), RoW(Redirect on Write) 방식 중에서 CoW를 채택합니다. 즉, 읽기 전용 Lower layer에 대해서 쓰기 명령이 발생 시에 스냅샷을 발행하여 관리합니다.
이는 CD-ROM 과 HDD의 관계를 비유해보면 매우 쉽습니다. CD-ROM은 한번 구워지는 순간 모든 내용물이 read-only로 관리가 됩니다. 그리고 CD를 컴퓨터에 넣고 CD의 내용물에 수정이 필요한 상황이 발생했다고 가정을 해봅시다.
CD의 모든 내용물은 read-only이기 때문에 수정이 불가능합니다. 그렇기 때문에 대부분의 사람들은 HDD에 CD의 내용을 복제한 다음에, 해당 복제본을 HDD에서 수정하여 사용을 하게 될것입니다.
여기서 CD의 내용물을 Lower layer, HDD를 Upper layer라고 생각하면 Overlay FS에 대해서 이해하기 쉬워질거라 예상합니다.
3. 이제 Overlay2 FS에 대해서 알아보면 될것같네요!
결론은, Union FS의 구현체 중의 하나가 Overlay2 FS입니다.
먼저 저희가 pull 받은 nginx를 먼저 컨테이너로 띄우고, 해당 컨테이너를 inspect 명령어를 이용해서 내부를 분석해보도록합시다.
$ docker run -d -p 8080:80 --name my_nginx nginx:latest
130ebbc9a31ab4c305254db974d09d5059ca805e28b8242b6d908777da7724c0
$ docker inspect --format='{{json .GraphDriver.Data}}' my_nginx
{
"LowerDir": "/var/lib/docker/overlay2/a8b3396ddd52e5caf32f387cd6f177e371310761b70b61bc73577ddcd29f122b-init/diff:/var/lib/docker/overlay2/3a8dc89d35fc702bb8e734e29b4021fb747e0acf2676b7dcb17150b009307db4/diff:/var/lib/docker/overlay2/31fa1902467ed3d2b5df30df8f2470c3f4490626864cad96c8d713257e4cd5e0/diff:/var/lib/docker/overlay2/b0615c3cce8ef25a67ed47591da92d01b82b82474ac882597f8ba6318de01492/diff:/var/lib/docker/overlay2/78d6b85b181259f3d2e192c3ab3697ff94e4b6aaa324c7955a86596d18105c96/diff:/var/lib/docker/overlay2/9e88426594e2d1f283e16c49057e6d8c985ba56b9bb42dde2c52f916e371a7c1/diff:/var/lib/docker/overlay2/766857ac768b4b06fe325f72eea091a6ff86bd047561b9152ee98cea0e4b878a/diff:/var/lib/docker/overlay2/0fce8340c6fd9f3a79f1966cdb5b31cd0f4087019ed786cd0c3e0478fbce8cdb/diff",
"MergedDir": "/var/lib/docker/overlay2/a8b3396ddd52e5caf32f387cd6f177e371310761b70b61bc73577ddcd29f122b/merged",
"UpperDir": "/var/lib/docker/overlay2/a8b3396ddd52e5caf32f387cd6f177e371310761b70b61bc73577ddcd29f122b/diff",
"WorkDir": "/var/lib/docker/overlay2/a8b3396ddd52e5caf32f387cd6f177e371310761b70b61bc73577ddcd29f122b/work"
}
여기서 LowerDir, UpperDir, MergedDir에 집중을 하시면 됩니다.
LowerDIr은 이미지가 가지고있는 레이어에 대한 정보로, 이는 read-only(읽기 전용)으로 관리가 이루어집니다.
그런데 컨테이너를 운영하다보면, Lower layer에 존재하는 파일을 수정할 일이 한번 생길수도 있는데요(호스트에서 삭제하는 경우는 생각하지 않기로 합니다), Lower layer의 파일은 read-only로 운영되며, Overlay2 스토리지 시스템은 CoW로 동작하기 때문에 아래의 방식으로 파일 수정이 이루어집니다.
- 먼저 Lower layer에 있는 원본 파일을 복제하여 Upper layer에 싣습니다.
- Upper layer에 적재된 파일을 수정하여 보관합니다. (시작하세요! 도커/쿠버네티스 서적에는 overlayfile 이라는 파일을 이용해서 파일의 수정내역을 보관한다고 하지만, 실제 docker 공식문서를 확인해보면 overlayfile에 대한 언급은 존재하지 않습니다. 참고 부탁드립니다.)
- 그리고 Lower layer, Upper layer 두 개의 파일시스템을 합쳐서 마운트하는 방식으로 Merged layer를 구성합니다. 그 과정에서 수정된 파일은 Upper layer의 파일로 덮어씌우는 과정을 거치고, Merged layer를 실제 container가 사용하게됩니다.
4. Docker가 Overlay2를 이용해서 용량 절약을 이뤄내는 방법
눈치가 빠르신 분들은 알아채실 수 있지만, Docker는 overlay2 라는 폴더에 lower-layer에 속하는 레이어 파일들을 여러 컨테이너가 공유하는 방식으로 용량 절약을 이뤄냅니다.
사실 지금까지는 container level만 다뤘는데요, image level에 대해서도 설명을 일부 드려볼까합니다.
이전에 도커 이미지를 다운받아서 inspect 명령어를 통해 분석해보면, 도커 이미지는 여러개의 layer의 조합으로 구성된다고 알려드린 바 있습니다. 그러면 image에 소속된 layer들은 모두 어디에 존재하는걸까요?
docker는 이미지에 소속된 layer들을 로컬에 보관하기 위해서 LayerDB 를 사용하고 있습니다. 해당 디렉토리를 확인하기 위해선 아래의 커맨드를 입력해보면 조회가 될것입니다. (overlay2 FS를 사용한다는 가정 하에)
$ ls /var/lib/docker/image/overlay2/layerdb/sha256
그러면 sha256 폴더 내부에 레이어 정보들이 담긴 여러개의 디렉토리들이 발견되는 모습을 보이는데요, 각 디렉토리에는 cache-id, parent, diff 라는 파일이 존재합니다.
여기에서 cache-id, parent, diff는 아래를 의미한다고 합니다.
- cache-id: 레이어가 저장된 로컬 경로의 id
- parent: 부모 레이어를 가리키는 포인터로, 해당 레이어가 어느 레이어 위로 쌓여야할지 순서를 결정해주는 파일입니다. (docker image또한 inspect로 분석해보면 순서가 존재한다는 것을 알 수 있었을겁니다!)
- diff: 레이어의 식별자입니다. 해당 정보를 통해서 container의 Lower layer에 레이어가 들어갑니다.
$ docker inspect --format='{{json .GraphDriver.Data}}' my_nginx
{
"LowerDir": "/var/lib/docker/overlay2/a8b3396ddd52e5caf32f387cd6f177e371310761b70b61bc73577ddcd29f122b-init/diff:/var/lib/docker/overlay2/3a8dc89d35fc702bb8e734e29b4021fb747e0acf2676b7dcb17150b009307db4/diff:/var/lib/docker/overlay2/31fa1902467ed3d2b5df30df8f2470c3f4490626864cad96c8d713257e4cd5e0/diff:/var/lib/docker/overlay2/b0615c3cce8ef25a67ed47591da92d01b82b82474ac882597f8ba6318de01492/diff:/var/lib/docker/overlay2/78d6b85b181259f3d2e192c3ab3697ff94e4b6aaa324c7955a86596d18105c96/diff:/var/lib/docker/overlay2/9e88426594e2d1f283e16c49057e6d8c985ba56b9bb42dde2c52f916e371a7c1/diff:/var/lib/docker/overlay2/766857ac768b4b06fe325f72eea091a6ff86bd047561b9152ee98cea0e4b878a/diff:/var/lib/docker/overlay2/0fce8340c6fd9f3a79f1966cdb5b31cd0f4087019ed786cd0c3e0478fbce8cdb/diff",
"MergedDir": "/var/lib/docker/overlay2/a8b3396ddd52e5caf32f387cd6f177e371310761b70b61bc73577ddcd29f122b/merged",
"UpperDir": "/var/lib/docker/overlay2/a8b3396ddd52e5caf32f387cd6f177e371310761b70b61bc73577ddcd29f122b/diff",
"WorkDir": "/var/lib/docker/overlay2/a8b3396ddd52e5caf32f387cd6f177e371310761b70b61bc73577ddcd29f122b/work"
}
여기서 LowerDir 키를 보시게 되면, 로컬의 diff 라는 파일이 모두 실리는 것을 확인할 수 있습니다.
여기까지 설명한 구조로, docker는 image의 레이어들과, container의 레이어들을 관리하고 있으며, 이러한 파일시스템 구조를 통해서 용량을 절약하면서 docker engine을 돌릴 수 있다는 것을 확인해볼 수 있었습니다.
5. References (참고 문서)
'devOps' 카테고리의 다른 글
서비스 장애 잘 이해하고 대비하기 (0) | 2024.07.06 |
---|---|
Redis OOM 장애 (0) | 2024.06.25 |
Kubernetes 워커노드의 OOM에 의한 클러스터 장애 (0) | 2023.04.11 |
Jenkins on K8S를 설정하며 겪은 일들 (0) | 2023.03.04 |
내가 쿠버네티스 설정하며 겪은 삽질들 (alb-controller, jenkins, monitoring) (0) | 2023.02.27 |