컨테이너 이미지
컨테이너를 띄우기 전에 내려받는 파일 묶음입니다. 프로그램이 놓일 파일들과 그것을 어떻게 실행할지 적은 설정이 함께 들어 있습니다. 조각마다 내용을 해시로 이름 붙입니다. 그래서 받은 것이 보낸 것과 같은지 대조할 수 있습니다.
상세
OCI(Open Container Initiative) 이미지 명세는 이미지를 네 부분으로 정의합니다. 이미지 매니페스트, 선택 사항인 이미지 인덱스, 파일시스템 레이어 묶음, 그리고 설정입니다. 명세가 밝힌 목표는 이미지를 만들고 나르고 실행 준비하는 도구들이 서로 통하게 하는 것입니다. 한 번 만들어진 이미지는 이름으로 찾고, 내려받고, 해시로 검증하고, 서명으로 신뢰하고, OCI 런타임 번들로 풀어낼 수 있습니다.
이 네 부분은 하나의 파일이 아닙니다. 서로를 가리키는 조각들의 그래프입니다. 명세는 이것을 머클 DAG(Directed Acyclic Graph, 방향 비순환 그래프)라고 적습니다. 조각 사이의 참조는 전부 콘텐츠 디스크립터라는 작은 JSON(JavaScript Object Notation) 덩이가 맡습니다.
flowchart TD
IDX["이미지 인덱스"] -->|디스크립터| MF["이미지 매니페스트"]
MF -->|config| CFG["설정 JSON"]
MF -->|layers| L1["레이어 1 · tar"]
MF -->|layers| L2["레이어 2 · tar"]
CFG -->|diff_ids| L1
CFG -->|diff_ids| L2
위에서 아래로 가리킵니다. 인덱스가 매니페스트를 가리킵니다. 매니페스트가 설정 하나와 레이어 여럿을 가리킵니다. 설정 안의 rootfs.diff_ids 도 레이어 내용의 해시를 담습니다.
디스크립터에 반드시 있어야 하는 칸은 셋입니다. mediaType 은 가리키는 내용의 종류입니다. digest 는 그 내용의 다이제스트입니다. size 는 원본 내용의 바이트 수입니다. 명세는 size 가 왜 있는지도 밝힙니다. 클라이언트가 내용을 처리하기 전에 크기를 미리 알게 하려는 것입니다. 받은 길이가 적힌 길이와 다르면 그 내용을 믿지 않기를 권합니다. 믿을 수 없는 곳에서 받았다면 다이제스트로 대조할 것도 권합니다.
매니페스트의 config 는 필수 항목입니다. 컨테이너 설정 객체를 다이제스트로 가리킵니다. 구현은 최소한 application/vnd.oci.image.config.v1+json 미디어타입을 지원해야 합니다. layers 는 디스크립터 배열입니다. 이식성을 위해 항목이 하나 이상 있기를 권합니다. 설정 미디어타입이 위의 값일 때 규칙이 더 붙습니다. 배열의 0번 자리는 베이스 레이어여야 합니다. 뒤의 레이어들은 쌓인 순서대로 이어져야 합니다. 최종 파일시스템 배치는 빈 디렉토리에 레이어를 차례로 적용한 결과와 같아야 합니다.
설정 JSON 이 이미지의 신원을 만듭니다. rootfs 는 필수입니다. 그 안의 type 은 반드시 layers 여야 합니다. 검증하거나 풀어내다 모르는 값을 만나면 구현이 오류를 내야 합니다. diff_ids 는 레이어 내용 해시를 첫 번째부터 마지막까지 순서대로 담은 배열입니다. 명세는 이 구조가 무엇을 뜻하는지 짚습니다. 이미지 설정의 해시가 파일시스템의 해시에 의존하게 됩니다. 이미지 ID 는 설정 JSON 의 SHA-256(Secure Hash Algorithm 256, 보안 해시 알고리즘 256비트) 해시입니다. 설정 JSON 이 각 레이어의 해시를 품습니다. 그래서 이 방식이 이미지를 콘텐츠 주소로 만듭니다.
history 는 선택 사항입니다. 레이어마다의 내력을 첫 번째부터 마지막까지 순서대로 적습니다. created_by 에는 그 레이어를 만든 명령이 들어갑니다. empty_layer 는 그 내력 항목이 파일시스템 차이를 만들었는지 표시합니다. rootfs 의 실제 레이어에 대응하지 않는 항목이면 참입니다. Dockerfile 의 ENV 명령처럼 파일시스템을 바꾸지 않는 경우가 그렇습니다.
갈래
갈리는 자리는 미디어타입 문자열입니다. 어느 규격의 어느 판인지, 그리고 무엇을 담은 조각인지가 전부 이 문자열 하나로 구분됩니다. Docker 문서는 클라이언트가 응답의 Content-Type 을 보고 매니페스트 목록인지 이미지 매니페스트인지 가린다고 적습니다.
OCI 이미지 매니페스트
application/vnd.oci.image.manifest.v1+json 입니다. 설정 하나와 레이어 배열을 담습니다. 같은 계열에 다른 조각들의 미디어타입도 함께 정의돼 있습니다. 콘텐츠 디스크립터는 application/vnd.oci.descriptor.v1+json, 이미지 인덱스는 application/vnd.oci.image.index.v1+json, 설정은 application/vnd.oci.image.config.v1+json, 디스크 배치 헤더는 application/vnd.oci.layout.header.v1+json 입니다. 쓰이지 않는 디스크립터 자리를 위한 application/vnd.oci.empty.v1+json 도 있습니다.
Docker Image Manifest V2, Schema 2
application/vnd.docker.distribution.manifest.v2+json 입니다. 앞선 판인 schema 1 은 Docker 데몬 v1.3.0 릴리스에서 들어왔습니다. 지금은 폐기된 판입니다. 두 번째 판에는 목표가 둘 있습니다. 첫째는 멀티 아키텍처 이미지입니다. 플랫폼별 이미지 매니페스트를 가리키는 fat manifest 로 이룹니다. 둘째는 콘텐츠 주소 이미지로의 이동입니다. 이미지 설정을 해시해 이미지 ID 를 만드는 모델을 지원합니다.
레이어와 설정의 미디어타입도 따로 있습니다. 설정은 application/vnd.docker.container.image.v1+json 입니다. 레이어는 application/vnd.docker.image.rootfs.diff.tar.gzip 입니다. 절대 밀어 올리면 안 되는 레이어를 위한 application/vnd.docker.image.rootfs.foreign.diff.tar.gzip 도 있습니다.
이미지 인덱스
application/vnd.oci.image.index.v1+json 입니다. Docker 쪽 이름은 매니페스트 목록입니다. 미디어타입은 application/vnd.docker.distribution.manifest.list.v2+json 입니다. 특정 플랫폼용 매니페스트들을 한 이름 아래 묶는 자리입니다.
manifests 는 필수 항목입니다. 있어야 하되 배열 크기가 0 이어도 됩니다. 각 항목의 platform 은 선택 사항입니다. 대상이 플랫폼에 묶여 있으면 넣기를 권합니다. 그 안에서 architecture 와 os 는 필수입니다. os.version 과 variant 는 선택입니다. 여러 매니페스트가 클라이언트의 요구에 맞으면 첫 번째로 맞은 것을 쓰기를 권합니다. Docker 문서는 매니페스트 목록을 쓰는 것이 선택이라고 적습니다. 비교적 적은 수의 이미지만 이 매니페스트를 쓴다고도 덧붙입니다.
레이어 미디어타입
레이어는 tar 아카이브입니다. 압축 방식이 갈래를 만듭니다. application/vnd.oci.image.layer.v1.tar 는 압축하지 않은 tar 입니다. application/vnd.oci.image.layer.v1.tar+gzip 은 gzip 으로, application/vnd.oci.image.layer.v1.tar+zstd 는 zstd 로 압축한 판입니다. 배포 제한이 붙은 nondistributable 계열도 같은 세 벌로 있습니다. 명세는 이 계열을 폐기 대상으로 표시합니다. 앞으로는 쓰지 않기를 권합니다.
표현 한계
담지 못하는 것부터 봅니다.
삭제를 담는 자리
레이어는 tar 아카이브입니다. tar 에는 이 경로를 지우라는 뜻을 적을 칸이 없습니다. 명세는 이름 규칙으로 대신합니다. whiteout 파일은 빈 파일입니다. 이름 앞에 .wh. 를 붙여 그 경로가 지워져야 함을 나타냅니다. 아래 레이어가 이렇다고 해 봅니다.
file1
a/file2
b/
c/file3
다음 레이어에서 file1 과 a/file2 와 b/ 를 지우고 file4 를 더하면 이렇게 적힙니다.
.wh.file1
a/.wh.file2
.wh.b
file4
지워지는 대상이 디렉토리여도 whiteout 자체는 아카이브 안의 평범한 파일입니다.
이 규칙이 그대로 한계가 됩니다. .wh. 로 시작하는 이름의 파일이나 디렉토리를 가진 파일시스템은 만들 수 없습니다. 그 이름이 이미 표식으로 예약돼 있기 때문입니다. .wh. 뒤에 지울 이름이 없는 항목은 잘못된 것입니다. 만나면 오류를 내기를 권합니다. whiteout 은 아래 레이어나 부모 레이어의 자원에만 적용돼야 합니다. 같은 레이어 안의 파일은 그 레이어의 whiteout 으로 숨길 수 없습니다. 뒤에 오는 레이어의 whiteout 만 그것을 숨깁니다. 한 디렉토리의 형제 항목 전부를 숨기려면 .wh..wh..opq 라는 불투명 whiteout 항목을 씁니다. 구현은 명시적 whiteout 으로 레이어를 만들기를 권하되, 두 방식 다 받아들여야 합니다.
삭제가 포맷에 담긴 것은 아니라는 점이 남습니다. 아래 레이어의 바이트는 그대로입니다. 위 레이어의 표식이 그 경로를 가릴 뿐입니다. 표식이 적용되고 나면 표식 자체도 숨겨져야 합니다.
tar 가 못 담는 것
레이어 체인지셋은 그냥 풀지 않습니다. 적용합니다. whiteout 파일을 따로 다뤄야 하기 때문입니다. whiteout 이 하나도 없는 체인지셋이면 평범한 tar 아카이브처럼 풀어냅니다.
tar 구현마다 갈리는 자리가 그대로 이미지의 한계가 됩니다. 희소 파일은 tar 구현들 사이에 일관된 지원이 없습니다. 명세는 쓰지 않기를 권합니다. 하드링크도 아픈 자리입니다. 유니온 파일시스템 구현은 하드링크 지원이 제한적이거나 아예 없을 수 있습니다. 하드링크된 파일이 바뀌거나 아래 파일시스템의 파일로 하드링크를 만들 때 특히 그렇습니다. 레이어 밖의 파일을 가리키는 하드링크가 든 레이어는 풀다가 실패할 수 있습니다.
명세가 구현에 맡긴 자리
레이어를 적용할 처음 빈 디렉토리의 소유자와 권한, 그 밖의 속성은 정해져 있지 않습니다. blob 에는 스키마가 없습니다. 명세는 blob 을 불투명한 것으로 보라고 적습니다. 디스크 배치에도 느슨한 자리가 있습니다. blobs 디렉토리에는 어떤 참조에도 걸리지 않는 blob 이 있어도 됩니다. 반대로 참조된 blob 이 빠져 있어도 됩니다. 그때는 바깥의 blob 저장소가 채우기를 권합니다. 배치 디렉토리에 모르는 파일이 들어 있어도 구현이 오류를 내지 않아야 합니다. docker save 형식의 manifest.json 이 그런 예로 적혀 있습니다.
런타임 번들로 옮길 때도 맡긴 자리가 있습니다. 명세가 정하지 않은 런타임 설정 속성의 값은 구현이 정합니다. layers 배열에 항목이 하나 이상 있어야 한다는 것도 의무가 아닙니다. 이식성을 위한 권고입니다.
다이제스트가 걸리는 대상
다이제스트 계산은 단순합니다. 고른 해시 함수 H 를 내용 바이트 C 에 적용하고, 앞에 알고리즘 이름을 붙입니다.
let ID(C) = Descriptor.digest
let C = <bytes>
let D = '<alg>:' + Encode(H(C))
let verified = ID(C) == D
이 식에 들어가는 것은 내용 바이트 C 하나입니다. 다이제스트가 걸리는 대상은 파일 목록이 아니라 바이트열입니다. 대조가 맞으면 받은 바이트가 그 다이제스트로 지목된 내용임이 확인됩니다. 확인되는 것은 거기까지입니다. 이미지 ID 는 이것과 별개로 설정 JSON 의 해시로 정해집니다. 다이제스트 문자열의 알고리즘 칸은 여러 해시 함수를 허용합니다. 명세는 규격을 따르는 구현이 SHA-256 을 쓰기를 권합니다. 모르는 알고리즘이 붙은 다이제스트라도 문법만 맞으면 검증을 통과시키기를 권합니다.
예시
이미지 매니페스트 한 벌
{
"schemaVersion": 2,
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"config": {
"mediaType": "application/vnd.oci.image.config.v1+json",
"digest": "sha256:b5b2b2c507a0944348e0303114d8d93aaaa081732b86451d9bce1f432a537bc7",
"size": 7023
},
"layers": [
{
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
"digest": "sha256:9834876dcfb05cb167a5c24953eba58c4ac89b1adf57f28f2f9d09af107ee8f0",
"size": 32654
},
{
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
"digest": "sha256:3c3a4604a545cdc127456d94e421cd355bca5b528f4a9c1905b15da2eb4a4c6b",
"size": 16724
},
{
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
"digest": "sha256:ec4b8955958665577945c89419d1af06b5f7636b4ac3da7f12184802ad867736",
"size": 73109
}
],
"annotations": {
"com.example.key1": "value1",
"com.example.key2": "value2"
}
}
config 가 7023 바이트짜리 설정 JSON 을 sha256:b5b2b2c5… 로 가리킵니다. 레이어는 셋입니다. 셋 다 tar+gzip 입니다. 크기는 각각 32654 · 16724 · 73109 바이트입니다. 배열의 0번 자리가 베이스 레이어입니다. annotations 에는 임의의 키가 붙습니다.
디스크에 놓인 모양
$ cd example.com/app/
$ find . -type f
./index.json
./oci-layout
./blobs/sha256/3588d02542238316759cbf24502f4344ffcc8a60c803870022f335d1390c13b4
./blobs/sha256/4b0bc1c4050b03c95ef2a8e36e25feac42fd31283e8c30b3ee5df6b043155d3c
./blobs/sha256/7968321274dc6b6171697c33df7815310468e694ac5be0ec03ff053bb135e768
index.json 과 oci-layout 이 최상위에 놓입니다. 나머지 조각은 전부 blobs/sha256/ 아래로 갑니다. 파일 이름이 곧 그 내용의 다이제스트입니다. 명세는 이것을 규칙으로 못 박습니다. blobs/<alg>/<encoded> 의 내용은 다이제스트 <alg>:<encoded> 와 일치해야 합니다. 그래서 shasum -a 256 으로 잰 값과 파일 이름이 같습니다.
oci-layout 의 내용은 한 줄짜리 JSON 입니다.
{
"imageLayoutVersion": "1.0.0"
}
이미지 설정 JSON
{
"created": "2015-10-31T22:22:56.015925234Z",
"author": "Alyssa P. Hacker <[email protected]>",
"architecture": "amd64",
"os": "linux",
"config": {
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
],
"Entrypoint": [
"/bin/my-app-binary"
],
"Cmd": [
"--foreground",
"--config",
"/etc/my-app.d/default.cfg"
],
"WorkingDir": "/home/alice"
},
"rootfs": {
"diff_ids": [
"sha256:c6f988f4874bb0add23a778f753c65efe992244e148a1d2ec2a8b664fb66bbd1",
"sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef"
],
"type": "layers"
},
"history": [
{
"created": "2015-10-31T22:22:54.690851953Z",
"created_by": "/bin/sh -c #(nop) ADD file:a3bc1e842b69636f9df5256c49c5374fb4eef1e281fe3f282c65fb853ee171c5 in /"
},
{
"created": "2015-10-31T22:22:55.613815829Z",
"created_by": "/bin/sh -c #(nop) CMD [\"sh\"]",
"empty_layer": true
}
]
}
architecture 는 amd64, os 는 linux 입니다. 이 한 벌이 어느 플랫폼용인지를 여기서 읽습니다. Entrypoint 는 /bin/my-app-binary 입니다. Cmd 의 세 값이 그 뒤에 붙는 인자입니다. rootfs.type 은 layers 입니다. diff_ids 에 레이어 내용 해시 둘이 순서대로 있습니다. history 항목도 둘입니다. 두 번째 항목의 created_by 는 CMD ["sh"] 입니다. 그 항목의 empty_layer 가 참입니다. 파일시스템 차이를 만들지 않았다는 표시입니다.
관련 항목
이미지를 이루는 조각
레이어 · 이미지 매니페스트 · 이미지 인덱스 · 콘텐츠 디스크립터 · 다이제스트 · 미디어타입 · blob · 스키마 · rootfs · whiteout · 이미지 ID · 파일시스템 체인지셋
이 규격을 구현한 소프트웨어
Docker · containerd · podman · 컨테이너 런타임
이미지가 거치는 처리 단계
Dockerfile · 컨테이너 레지스트리 · OCI Distribution Specification · 이미지 서명 · OCI 런타임 번들
이미지가 의존하는 밑바탕 기술
콘텐츠 주소 저장소 · 머클 트리 · SHA-256 · tar · gzip · zstd · 유니온 파일시스템 · 하드링크 · 희소 파일
헷갈리는 이웃
컨테이너 · 멀티 아키텍처 이미지 · fat manifest
다른 이름: container image · OCI Image · OCI 이미지 · image manifest