OSSCA ArgoWorkflows 프로젝트 멘토님께서 본인이 이전에 작성한 블로그로 컨테이너 구조에대해 발표를 해주셨다.
https://ykarma1996.tistory.com/192
컨테이너의 구조와 오픈소스의 생태계에 관한 리서치(feat. 도커는 적폐인가?)
컨테이너 이미지의 빌드 및 배포에 관한 성능을 개선하기 위해 리서치를 하다보니, 혼자 알기에 너무 재밌는 배경들이 많아서 정리해 보기로 했다. 오늘은 컨테이너와 이미지의 구조 및 원리(특
ykarma1996.tistory.com
내용이 정말 대박이다.... 리눅스의 네임스페이스까지는 격리해서 실습을 해보았지만, 컨테이너 오버레이에 대해서 까지는 생각을 못했었다.
이김에 멘토님의 블로그를 따라하며 오버레이 실습을 해보려고 한다! (**내용은 위의 근삼님 블로그 내용과 동일하다)
멘토님이 해당 블로그를 작성하게 된 계기는 컨테이너 이미지 리모트 캐시에 대해 알아보다가 컨테이너가 어떻게 구성되고 동작하는지 이해하기 위해서 였다고 한다.
docker in docker 빌드, Argo Workflows 또는 tekton으로 빌드할 경우 빌드시 매번 초기화돼서 깔끔하고 좋다는 장점이 있지만, 빌드시 캐시를 잃어버려 빌드할 때마다 시간이 오래걸린다. 특히 jvm의 경우 빌드 시간이 정말 오래걸리는데, 빌드캐시간 안되면 10분씩 손해를 보기도 한다. 이 문제를 해결하는 방법 중 하나가 리모트 캐시를 활용하는것이다. 리모트 캐시를 지원하는 빌더도 다양하다. 뭘 쓰는 것이 좋을지 찾기 위해 리서치를 시작했다.
OverlayFS
컨테이너 생태계는 OverlayFS의 개념을 적극적으로 활용하여 컨테이너 이미지를 만들고 주고 받을 때, 필요한 부분만 주고받아서, 공통되는 레이어는 작업하지 않도록 하여 작업량을 최대한 줄인다고 한다.
OverlayFS라는 개념은 컨테이너만을 위한 개념이 아니라고 한다. 리눅스에서 하나의 디렉터리 지점에 여러개의 디렉터리를 마운트하는 방식으로, 마치 하나의 통합된 디렉터리처럼 보이게 하는 마운트 방식을 유니온 마운트 파일 시스템이라고 부르는데, OverlayFS는 그에 대한 구현체중 하나라고 한다.
Overlay는 크게 4가지 디렉터리 레이어로 구성된다.
- lower dir : 아래쪽에 깔려있는 수정이 불가능한 R/O 형태의 티렉터리이며, 여러개일 수 있다.
- upper dir: lower dir들의 위에 위치하는 수정 사항이 반영되는 R/W 형태의 디렉터리이며, 하나이다.
- merge dir: 위의 그림에서, 모든 레어이를 겹쳐서 보는 통합 뷰(view)에 해당하는 디렉터리이며, 사용자의 실질적인 작업영역이 된다.
- work dir: 통합 뷰의 원자성을 보장하기 위해 존재하는 중간 계층이며, 사용자에게는 중요하지 않다.
snchoi@snchoi:~/overlayfs-example$ mkdir lower1 lower2 upper merge work
snchoi@snchoi:~/overlayfs-example$ echo nyeong > lower1/file1
snchoi@snchoi:~/overlayfs-example$ echo nyeongnyeong > lower2/file2
두 개의 lower dir을 준비했고, 각 디렉터리에 file1, file2를 생성해두었다.
overlay 형태로 마운트를 시켜보자!
snchoi@snchoi:~/overlayfs-example$ sudo mount -t overlay overlay -o lowerdir=lower1/:lower2/,upperdir=upper/,workdir=work/ merge/
https://wiki.archlinux.org/title/Overlay_filesystem
Overlay filesystem - ArchWiki
From the initial kernel commit: Overlayfs allows one, usually read-write, directory tree to be overlaid onto another, read-only directory tree. All modifications go to the upper, writable layer. This type of mechanism is most often used for live CDs but th
wiki.archlinux.org
해당 명령어를 치자마자 아래와 같이 merge 폴더에 lower에 존재하던 파일이 생성되었다. overlay 형태로 마운트가 성공한 것이다!
snchoi@snchoi:~/overlayfs-example$ echo test > upper/test3
upper 폴더에 파일을 추가하면
upper 폴더에 추가됨과 동시에 merge에도 추가되었다.
여기서 merge의 file1을 지우게 되면?
snchoi@snchoi:~/overlayfs-example$ rm -rf merge/file1
merge에서는 제거되고 lower1에는 그대로 남아있으며 upper에 file1이 새로 쌓였다.
파일의 권한을 확인해보니 file1의 맨 앞자리가 c로 변경되었다.
- 맨 앞자리가 의미하는 바를 찾아보니 파일 타입을 나타낸다고 한다.
- d: 디렉토리, 가장 많은 유형
- p: 파이프파일
- b, c -> 하드웨어와 반응하는 파일
컨테이너 이미지의 구조
컨테이너가 실행될 때 활용되는 파일시스템인 OverlayFS의 원리에 대해서는 대충 이해가 됐다.
그렇다면 이제 이미지 구조를 까보자! 어떻게 이러한 겹겹의 레이어들을 효율적으로 주고 받을 수 있을까?
alpine 이미지를 베이스 이미지로 하는 매우 간단한 이미지를 만들어보자.
snchoi@snchoi:~/overlayfs-example$ cat Dockerfile
FROM alpine:3.16.3
RUN echo "Hello Nyeong" > /tmp/test1
Dockerfile을 간단히 작성하고 빌드를 진행했다.
snchoi@snchoi:~/overlayfs-example$ sudo docker build -t sn0716/parseimage:v1 .
[+] Building 3.5s (6/6) FINISHED
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 94B 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [internal] load metadata for docker.io/library/alpine:3.16.3 2.6s
=> [1/2] FROM docker.io/library/alpine:3.16.3@sha256:b95359c2505145f16c6aa384f9cc74eeff78eb36d308ca4fd902eeeb0a0b161b 0.5s
=> => resolve docker.io/library/alpine:3.16.3@sha256:b95359c2505145f16c6aa384f9cc74eeff78eb36d308ca4fd902eeeb0a0b161b 0.0s
=> => sha256:b95359c2505145f16c6aa384f9cc74eeff78eb36d308ca4fd902eeeb0a0b161b 1.64kB / 1.64kB 0.0s
=> => sha256:559254f7ee68d88649077bd0cc6dfb94c337aadb8411d0fe5eae3b037578ec13 528B / 528B 0.0s
=> => sha256:2b4661558fb8cf1ec295ccd9c6d1cd42067ef517b0e538c9de65f733a8e3dd7e 1.49kB / 1.49kB 0.0s
=> => sha256:6875df1f535433e5affe18ecfde9acb7950ab5f76887980ff06c5cdd48cf98f4 2.71MB / 2.71MB 0.3s
=> => extracting sha256:6875df1f535433e5affe18ecfde9acb7950ab5f76887980ff06c5cdd48cf98f4 0.1s
=> [2/2] RUN echo "Hello Nyeong" > /tmp/test1 0.3s
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:478aa764196f9af929ad78385e0a8a7dfdbbfc35d0b97f4006a1d668a976972c 0.0s
=> => naming to docker.io/sn0716/parseimage:v1 0.0s
snchoi@snchoi:~/overlayfs-example$ sudo docker save -o parseimage.tar sn0716/parseimage:v1
docker save 명령으로 이미지를 tar 파일로 떨구었다.
snchoi@snchoi:~/overlayfs-example$ mkdir contents
snchoi@snchoi:~/overlayfs-example$ sudo tar xf parseimage.tar -C ./contents
tar로 생성된 이미지를 풀어서 tree로 확인해보자.
압축을 풀어보니, 위와 같은 구조가 보인다. 다음과 같이 구조화할 수 있다. 공식문서가 존재하니 확인해보자!
- manifest.json: 최상위 이미지에 관한 정보
- [sha256해시].json: Image JSON file 이라고 불리고, 이미지와 관련한 메타데이터들이 들어있다.
- repository: 이미지의 이름 및 태그와 관련한 메타데이터가 기록된다.
- layer 디렉터리: 각각의 디렉터리들이 레이어를 의미한다.
- VERSION: json file에 관한 스키마 버전
- json: 레이어에 관한 메타데이터
- layer.tar: 파일시스템 변경사항이 반영된 tar 파일. 실질적인 layer
snchoi@snchoi:~/overlayfs-example/contents$ cat manifest.json | jq .
[
{
"Config": "478aa764196f9af929ad78385e0a8a7dfdbbfc35d0b97f4006a1d668a976972c.json",
"RepoTags": [
"sn0716/parseimage:v1"
],
"Layers": [
"72528bc5c69b9f4f8dd8d685d1ffa658a69a9eb815aeea6f9f54a3a070b7b556/layer.tar",
"896bc5695f506c242c6efc442feb0a362bbb1474a912fca88a9bb95aa341a09d/layer.tar"
]
}
]
snchoi@snchoi:~/overlayfs-example/contents$ cat 478aa764196f9af929ad78385e0a8a7dfdbbfc35d0b97f4006a1d668a976972c.json | jq .
{
"architecture": "arm64",
"config": {
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
],
"Cmd": [
"/bin/sh"
],
"OnBuild": null
},
"created": "2023-08-15T11:05:07.950123001Z",
"history": [
{
"created": "2022-11-12T03:39:38.442492606Z",
"created_by": "/bin/sh -c #(nop) ADD file:57d621536158358b14d15155826ef2dd4ca034278044111ec0aaf6717016e569 in / "
},
{
"created": "2022-11-12T03:39:38.551417892Z",
"created_by": "/bin/sh -c #(nop) CMD [\"/bin/sh\"]",
"empty_layer": true
},
{
"created": "2023-08-15T11:05:07.950123001Z",
"created_by": "RUN /bin/sh -c echo \"Hello Nyeong\" > /tmp/test1 # buildkit",
"comment": "buildkit.dockerfile.v0"
}
],
"os": "linux",
"rootfs": {
"type": "layers",
"diff_ids": [
"sha256:17bec77d7fdc6988cd96b3051b4ad4d3cd6031b2faf0581468be64aac0acc20b",
"sha256:d45db941eac9b1f5764ee8fec34615a4a27f064547a9c5b9389b54df525ec6be"
]
},
"variant": "v8"
}
snchoi@snchoi:~/overlayfs-example/contents$ cat repositories | jq .
{
"sn0716/parseimage": {
"v1": "896bc5695f506c242c6efc442feb0a362bbb1474a912fca88a9bb95aa341a09d"
}
}
snchoi@snchoi:~/overlayfs-example/contents$ cat 72528bc5c69b9f4f8dd8d685d1ffa658a69a9eb815aeea6f9f54a3a070b7b556/json | jq .
{
"id": "72528bc5c69b9f4f8dd8d685d1ffa658a69a9eb815aeea6f9f54a3a070b7b556",
"created": "1970-01-01T00:00:00Z",
"container_config": {
"Hostname": "",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": null,
"Cmd": null,
"Image": "",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": null,
"OnBuild": null,
"Labels": null
},
"os": "linux"
}
snchoi@snchoi:~/overlayfs-example/contents$ cat 896bc5695f506c242c6efc442feb0a362bbb1474a912fca88a9bb95aa341a09d/json | jq .
{
"id": "896bc5695f506c242c6efc442feb0a362bbb1474a912fca88a9bb95aa341a09d",
"parent": "72528bc5c69b9f4f8dd8d685d1ffa658a69a9eb815aeea6f9f54a3a070b7b556",
"created": "2023-08-15T11:05:07.950123001Z",
"container_config": {
"Hostname": "",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": null,
"Cmd": null,
"Image": "",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": null,
"OnBuild": null,
"Labels": null
},
"config": {
"Hostname": "",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
],
"Cmd": [
"/bin/sh"
],
"Image": "",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": null,
"OnBuild": null,
"Labels": null
},
"architecture": "arm64",
"variant": "v8",
"os": "linux"
}
sha256sum 명령어를 통해 각 layer 폴더의 layer.tar의 해시값을 확인해보면
snchoi@snchoi:~/overlayfs-example/contents$ find ./ -name layer.tar -exec sha256sum {} \;
17bec77d7fdc6988cd96b3051b4ad4d3cd6031b2faf0581468be64aac0acc20b ./72528bc5c69b9f4f8dd8d685d1ffa658a69a9eb815aeea6f9f54a3a070b7b556/layer.tar
d45db941eac9b1f5764ee8fec34615a4a27f064547a9c5b9389b54df525ec6be ./896bc5695f506c242c6efc442feb0a362bbb1474a912fca88a9bb95aa341a09d/layer.tar
Image Json file의 diff_ids가 위에서 확인한 해시값과 같은 것을 확인할 수 있다. diff_ids 인덱스 순서가 layer의 순서와 같다고 한다. 또한 각 layer 폴더의 json 파일의 내용을 통해서 상위 layer에 대한 정보도 알 수 있다.
layer.tar를 풀어서 어떤 파일들이 있는지 확인해보면 RUN echo "Hello Nyeong" > /tmp/test1 구문으로 추가된 레이러를 확인할 수 있다.
snchoi@snchoi:~/overlayfs-example$ mkdir layerdump
snchoi@snchoi:~/overlayfs-example$ tar xf contents/896bc5695f506c242c6efc442feb0a362bbb1474a912fca88a9bb95aa341a09d/layer.tar -C layerdump/
snchoi@snchoi:~/overlayfs-example/layerdump$ cd tmp/
snchoi@snchoi:~/overlayfs-example/layerdump/tmp$ ls
test1
snchoi@snchoi:~/overlayfs-example/layerdump/tmp$ cat test1
Hello Nyeong
이제 컨테이너 구조의 규칙을 알았으니, 툴 없이 이미지에 레이어를 추가해볼 수 있다.
snchoi@snchoi:~/overlayfs-example/contents$ mkdir -p newlayer/tmp/
snchoi@snchoi:~/overlayfs-example/contents$ echo "Did it work?" > newlayer/tmp/test2
snchoi@snchoi:~/overlayfs-example/contents$ cd newlayer/ && tar cf ../layer.tar . && rm -rf newlayer
snchoi@snchoi:~/overlayfs-example/contents$ ls
478aa764196f9af929ad78385e0a8a7dfdbbfc35d0b97f4006a1d668a976972c.json layer.tar
72528bc5c69b9f4f8dd8d685d1ffa658a69a9eb815aeea6f9f54a3a070b7b556 manifest.json
896bc5695f506c242c6efc442feb0a362bbb1474a912fca88a9bb95aa341a09d repositories
snchoi@snchoi:~/overlayfs-example/contents$ sha256sum layer.tar
facb9dbc0daf07dd9edd02d1a9d2e7717996c5ae6e07e625a376ae2c7bc309fc layer.tar
확인한 해시값 이름으로 폴더를 만들고(폴더 이름도 나름의 규칙에 의해 생성된다고 하지만, 여기서는 패스!), VERSION 파일에는 1.0이라는 값을 넣어주고, json 파일은 id, parent 필드만 규격대로 작성해주고 나머지는 구색만 갖추어 본다.
{
"id": "폴더명",
"parent": "마지막 레이어의 폴더명",
"created": "2023-04-10T16:19:28.050827879Z",
"container_config": {},
"config": {},
"architecture": "arm64",
"variant": "v8",
"os": "linux"
}
repositories, manifest.json, [sha256해시].json 파일도 아래와 같이 수정해주었다.
snchoi@snchoi:~/overlayfs-example/contents$ cat repositories | jq .
{
"sn0716/parseimage": {
"v2": "facb9dbc0daf07dd9edd02d1a9d2e7717996c5ae6e07e625a376ae2c7bc309fc"
}
}
snchoi@snchoi:~/overlayfs-example/contents$ cat manifest.json | jq .
[
{
"Config": "478aa764196f9af929ad78385e0a8a7dfdbbfc35d0b97f4006a1d668a976972c.json",
"RepoTags": [
"sn0716/parseimage:v2"
],
"Layers": [
"72528bc5c69b9f4f8dd8d685d1ffa658a69a9eb815aeea6f9f54a3a070b7b556/layer.tar",
"896bc5695f506c242c6efc442feb0a362bbb1474a912fca88a9bb95aa341a09d/layer.tar",
"facb9dbc0daf07dd9edd02d1a9d2e7717996c5ae6e07e625a376ae2c7bc309fc/layer.tar"
]
}
]
snchoi@snchoi:~/overlayfs-example/contents$ cat 478aa764196f9af929ad78385e0a8a7dfdbbfc35d0b97f4006a1d668a976972c.json | jq .
{
"architecture": "arm64",
"config": {
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
],
"Cmd": [
"/bin/sh"
],
"OnBuild": null
},
"created": "2023-08-15T11:05:07.950123001Z",
"history": [
{
"created": "2022-11-12T03:39:38.442492606Z",
"created_by": "/bin/sh -c #(nop) ADD file:57d621536158358b14d15155826ef2dd4ca034278044111ec0aaf6717016e569 in / "
},
{
"created": "2022-11-12T03:39:38.551417892Z",
"created_by": "/bin/sh -c #(nop) CMD [\"/bin/sh\"]",
"empty_layer": true
},
{
"created": "2023-08-15T11:05:07.950123001Z",
"created_by": "RUN /bin/sh -c echo \"Hello Nyeong\" > /tmp/test1 # buildkit",
"comment": "buildkit.dockerfile.v0"
}
],
"os": "linux",
"rootfs": {
"type": "layers",
"diff_ids": [
"sha256:17bec77d7fdc6988cd96b3051b4ad4d3cd6031b2faf0581468be64aac0acc20b",
"sha256:d45db941eac9b1f5764ee8fec34615a4a27f064547a9c5b9389b54df525ec6be",
"sha256:facb9dbc0daf07dd9edd02d1a9d2e7717996c5ae6e07e625a376ae2c7bc309fc"
]
},
"variant": "v8"
}
최종적으로 다른 레이어와 완전히 같은 모양이 되었다.
이제 전체 폴더의 내용을 tar로 묶고, docker load 명령어를 사용하여 새로 추가된 레이어가 잘 반영되었는지 확인해보자.
snchoi@snchoi:~/overlayfs-example/contents$ tar cf ../parseimage2.tar .
snchoi@snchoi:~/overlayfs-example/contents$ cd ..
snchoi@snchoi:~/overlayfs-example$ ls
contents Dockerfile layerdump parseimage2.tar parseimage.tar
snchoi@snchoi:~/overlayfs-example$ file parseimage2.tar
parseimage2.tar: POSIX tar archive (GNU)
snchoi@snchoi:~/overlayfs-example$ sudo docker load < parseimage2.tar
Loaded image: sn0716/parseimage:v2
snchoi@snchoi:~/overlayfs-example$ sudo docker run -it sn0716/parseimage:v2
/ # exit
snchoi@snchoi:~/overlayfs-example$ sudo docker run -it sn0716/parseimage:v2 cat /tmp/test2
Did it work?
도구 하나 없이 새로운 컨테이너 이미지까지 만들어 보는것에 성공했다!
'리눅스(Linux) > 컨테이너(Container)' 카테고리의 다른 글
VM과 컨테이너 (0) | 2023.07.29 |
---|---|
리눅스의 컨테이너 격리 (cgroups, namespace) (1) | 2023.07.28 |
[Docker] docker inspect (0) | 2021.09.11 |
[도커] 스웜모드 & 스웜모드의서비스 장애 복구 & 서비스 컨테이너에 설정 정보 전달(secret,config) & 도커 스웜 네트워크 & 서비스 디스커버리 (0) | 2020.09.19 |
[도커] 쉘스크립트에 도커 명령어 작성 & Docker compose & swarm (0) | 2020.09.16 |