ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 원리부터 파악하는 컨테이너 이미지 PULL (w/ curl)
    Kubernetes 2023. 6. 30. 09:00

    한장요약

    시작 전 잡담 - 1

    본래 프로젝트 라이프사이클 관점에서의 개발 방법론에 대한 글을 쓰고 싶었지만
    현재 진행하고 있는 강의에 대한 홍보글 겸 겸사겸사 적어본다. (많은 관심 부탁드립니다~~)
    이 내용은 실제 강의에서 다뤘던 내용을 기반으로 한다.

    시작 전 잡담 - 2

    이 내용은 단순히 도커의 작동 원리 측면에서 이해가 아닌, 시스템 아키텍처의 하나의 사례로 읽어보면 꽤 흥미롭다. 적어도 개인적으로는 그랬다. 전 세계에서 넷플릭스 다음으로 많은 트래픽을 처리하고 있을(어쩌면 넷플릭스보다 많을지도 모른다) Docker Hub의 구조가 궁금하지 않은가?

    필자는 이 기사를 보고 질문을 떠올렸고, 이 글은 이 질문에 대한 그 내용을 요약한 것이다.

    서론

    정말 감사하게도 docker pull 이라는 명령어를 쓰면 참 쉽게 Docker Hub 혹은 그에 대응하는 GHCR 이라던지 GCR 같은 Container Registry (이하 CR) 로 부터 컨테이너 이미지가 다운받아진다. 이렇게 받아진 이미지를 기반으로 rootfs 를 만들고 [참고: 도커없이 컨테이너 만들기] 해당 가짜 rootfs를 진짜 rootfs라고 믿도록 contained 된 프로세스를 실행하면 실행 중인 컨테이너 완성이다.

    docker pull 하면 알아서 필요한 데이터를 땡겨와주는 감사한 기능

    물론 저것만 하는 건 아니고 세부적으로 들어가면 여러 네임스페이스가 나오기는 하지만 적어도 Borg는 초창기에는 저것만 격리해 두고 컨테이너라고 불렀다고 한다. 컨테이너는 생각만큼 잘 정의된 개념이 아니다.

    여기서 궁금한 것은 docker pull 명령어를 입력했을 때 어디랑 어떻게 통신하며, 특정 레이어를 이미 가지고 있을 때는 어떻게 중복되는 다운로드를 피할 수 있는 것일까? 어떻게 이들을 병렬적으로 다운로드 받을 수 있으며, Private Repository 에서 이미지를 다운받을때 입력하는 Secret 은 실제 어떻게 작동하고 있는것일까?

    배경지식

    컨테이너 세상에서 표준이라고 부를만한 것은 세 가지이다.

    1. OCI Runtime Spec
    2. OCI Image Spec
    3. OCI Distribution Spec

    각각을 엄밀하진 않지만 이해하기 쉽게 요약해 보면
    Runtime Spec 은 RootFS 지정하면 그걸 어떻게 실행할 지에 대한 설정 및 CLI 명령어 표준이 담겨있고
    Image Spec 은 그 RootFS를 만들 정보를 어떻게 정의할지에 대한 파일 규격에 대한 표준이 담겨있고
    Distribution Spec 은 RootFS 를 만들 정보를 정의한 파일들을 어떻게 배포할지에 대한 HTTP 기반 API 표준이 담겨있다.

    이 글에서는 Image Spec과 Distribution Spec에 대해서 다뤄볼 예정이다.

    OCI Image Spec

    OCI Image Spec 정의에 따르면 컨테이너 이미지는 크게 4가지로 나뉜다.

    1. Image Index
    2. Image Manifest
    3. Image Config
    4. Image Layer

    간단하게 설명하면

    1. Index는 각 CPU, OS 환경별로 어떠한 manifest 가 있는지 기록된 JSON 양식을 따르는 파일이다.
    2. manifests는 하나의 컨테이너 이미지에 대한 정보가 담긴 JSON 파일이며 Config와 Layer에 대한 정보가 있다.
    3. config는 이 컨테이너 이미지가 어떻게 만들어졌고, 어떻게 실행 가능한지 (환경변수, 커맨드) 등등에 대한 메타데이터가 담겨있다.
    4. layer는 tar 형식으로 압축된 각 레이어에 대한 파일이다.

    자세히 설명하면, 아래를 참고하자

    Image Spec - Index

    이름: Index
    목표: 각 OS (Linux, Windows, Solaris 등등)와 CPU (amd64, arm64, ppc64le 등등) 별로 manifest의 id 가 어떻게 되는지 알려준다.
    특이사항: Index는 사실 Optional이다. 없어도 된다.
    Media Types: application/vnd.oci.image.index.v1+json
    예시: ubuntu:latest 의 경우

    {
      "mediaType": "application/vnd.oci.image.index.v1+json",
      "schemaVersion": 2,
      "manifests": [
        {
          "digest": "sha256:83f0c2a8d6f266d687d55b5cb1cb2201148eb7ac449e4202d9646b9083f1cee0",
          "mediaType": "application/vnd.oci.image.manifest.v1+json",
          "platform": {
            "architecture": "amd64",
            "os": "linux"
          },
          "size": 424
        },
        {
          "digest": "sha256:25de8a960c338b7d38aa15c012ceee70d8e29239db97596d6ecc50a5085d8f7a",
          "mediaType": "application/vnd.oci.image.manifest.v1+json",
          "platform": {
            "architecture": "arm64",
            "os": "linux",
            "variant": "v8"
          },
          "size": 424
        }
        /* 나머지는 생략 */
      ]
    }

    위와 같은 양식을 가지며 sha256sum 으로 계산된 digest 값으로 manifest를 찾을 수 있도록 digest 값이 명시되어 있다.

    Image Spec - Manifest

    이름: manifest
    목표: 하나의 컨테이너 이미지를 조립하기 위한 정보를 제공한다. 정확히는 Config와 Layer에 대한 정보 제공을 목표로 한다.
    Media Types: application/vnd.oci.image.manifest.v1+json
    예시: ubuntu:latestamd64 / linux 이미지인 sha256:83f0c2a8d6f266d687d55b5cb1cb2201148eb7ac449e4202d9646b9083f1cee0 경우

    {
      "schemaVersion": 2,
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "config": {
        "mediaType": "application/vnd.oci.image.config.v1+json",
        "size": 2297,
        "digest": "sha256:99284ca6cea039c7784d1414608c6e846dd56830c2a13e1341be681c3ffcc8ac"
      },
      "layers": [ // ubuntu 이미지는 base 이미지로 많이 사용되기때문에 단일 레이어로 구성되어있다.
        {
          "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
          "size": 29533050, // 28.16MB
          "digest": "sha256:6b851dcae6ca1461dde247915abc5048061f34332929ca8fb37d9dc18f2e2f44"
        }
      ]
    }

    Image Spec - Config

    이름: config
    목표: 이 컨테이너 이미지를 실행하기 위한 메타데이터를 제공한다.
    Media Types: application/vnd.oci.image.config.v1+json
    예시: 위 예시에서 명시된 sha256:99284ca6cea039c7784d1414608c6e846dd56830c2a13e1341be681c3ffcc8ac 의 경우

    {
      "created": "2023-06-05T17:00:39.361599721Z",
      "docker_version": "20.10.21",
      "architecture": "amd64",
      "os": "linux",
      "rootfs": {
        "type": "layers",
        "diff_ids": [
          "sha256:cdd7c73923174e45ea648d66996665c288e1b17a0f45efdbeca860f6dafdf731"
          // DiffID: 압축 해제한 Layer 에 대한 sha256sum 한 값
          // ChainID(A) = DiffID(A)
          // ChainID(A|B) = Digest(ChainID(A) + " " + DiffID(B))
          // ChainID(A|B|C) = Digest(ChainID(A|B) + " " + DiffID(C))
          // diff_ids = ChainID
          // 각 레이어별로 적용시 무결점을 보장하기위한 메타데이터
        ]
      },
      "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/bash"
        ],
        "Image": "sha256:d17b4ab170168d7b2fbd685124f529e405acf8aada0933843bde9ee6330303ea",// 이 JSON 데이터를 sha256sum 한 것. Immutable 보장용도
        "Volumes": null,
        "WorkingDir": "",
        "Entrypoint": null,
        "OnBuild": null,
        "Labels": {
          "org.opencontainers.image.ref.name": "ubuntu",
          "org.opencontainers.image.version": "22.04"
        }
      },
      "history": [
        {
          "created": "2023-06-05T17:00:37.587967605Z",
          "created_by": "/bin/sh -c #(nop)  ARG RELEASE",
          "empty_layer": true
        },
        {
          "created": "2023-06-05T17:00:37.631302347Z",
          "created_by": "/bin/sh -c #(nop)  ARG LAUNCHPAD_BUILD_ARCH",
          "empty_layer": true
        },
        {
          "created": "2023-06-05T17:00:37.67809659Z",
          "created_by": "/bin/sh -c #(nop)  LABEL org.opencontainers.image.ref.name=ubuntu",
          "empty_layer": true
        },
        {
          "created": "2023-06-05T17:00:37.722501654Z",
          "created_by": "/bin/sh -c #(nop)  LABEL org.opencontainers.image.version=22.04",
          "empty_layer": true
        },
        {
          "created": "2023-06-05T17:00:39.16987586Z",
          "created_by": "/bin/sh -c #(nop) ADD file:0ad2ee2cfb186802f49c9bf4148674d1c6fc201f83478ec01ffaa7086d491323 in / "
        },
        {
          "created": "2023-06-05T17:00:39.361599721Z",
          "created_by": "/bin/sh -c #(nop)  CMD [\"/bin/bash\"]",
          "empty_layer": true
        }
      ],
      // 이 밑 부터는 OCI 표준은 아니지만 Docker 에서 맘대로 사용하는 중
      // 주로 `docker history` 명령어와 관련된 기능이라고 함.
      "container": "d0606c58733ffd6f0353d72893f1dec960e9b551c629c1f250029be012e0771f",
      "container_config": {
        "Hostname": "d0606c58733f",
        "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",
          "-c",
          "#(nop) ",
          "CMD [\"/bin/bash\"]"
        ],
        "Image": "sha256:d17b4ab170168d7b2fbd685124f529e405acf8aada0933843bde9ee6330303ea",
        "Volumes": null,
        "WorkingDir": "",
        "Entrypoint": null,
        "OnBuild": null,
        "Labels": {
          "org.opencontainers.image.ref.name": "ubuntu",
          "org.opencontainers.image.version": "22.04"
        }
      }
    }

    Image Spec - Layer

    이름: layer
    목표: 특정 레이어의 데이터를 제공해서 하나의 완전한 rootfs를 조립할 수 있도록 한다.
    Media Types:

    • application/vnd.oci.image.layer.v1.tar: 단순 tar 일 경우
    • application/vnd.oci.image.layer.v1.tar+gzip: 위 tar을 gzip 한 경우
    • application/vnd.oci.image.layer.nondistributable.v1.tar(+gzip): (주로 라이선스적인 이유로) 재배포되면 안 되는 레이어일 경우
      예시: sha256:6b851dcae6ca1461dde247915abc5048061f34332929ca8fb37d9dc18f2e2f44 레이어를 압축해제하면 다음과 같은 디렉터리 구조를 가진 파일들이 나온다.
    > file layer.tar
    layer.tar: gzip compressed data, was "977b072da3857fb0dbff2784372883885594f9e65793c8346da0f6c6cdd9711b.tar", last modified: Mon Jun  5 17:00:41 2023, max compression, original size modulo 2^32 80334848
    
    > cat layer.tar | sha256sum
    6b851dcae6ca1461dde247915abc5048061f34332929ca8fb37d9dc18f2e2f44  - # Layer Digest 값과 동일
    
    > tar -xvf layer.tar
    > tree
    # 중간 생략
    692 directories, 2820 files
    > tree -L 1
    .
    ├── bin -> usr/bin
    ├── boot
    ├── dev
    ├── etc
    ├── home
    ├── lib -> usr/lib
    ├── lib32 -> usr/lib32
    ├── lib64 -> usr/lib64
    ├── libx32 -> usr/libx32
    ├── media
    ├── mnt
    ├── opt
    ├── proc
    ├── root
    ├── run
    ├── sbin -> usr/sbin
    ├── srv
    ├── sys
    ├── tmp
    ├── usr
    └── var
    
    21 directories, 0 files

    참고로 아래와 같은 Dockerfile을 빌드할 경우

    FROM ubuntu:latest
    RUN apt-get update

    2번째 레이어의 상태는 다음과 같다.

    > tar -xvf layer.tar
    tmp/
    var/
    var/lib/
    var/lib/apt/
    var/lib/apt/lists/
    var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_jammy-backports_InRelease
    var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_jammy-backports_main_binary-amd64_Packages.lz4
    var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_jammy-backports_universe_binary-amd64_Packages.lz4
    var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_jammy-updates_InRelease
    var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_jammy-updates_main_binary-amd64_Packages.lz4
    var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_jammy-updates_multiverse_binary-amd64_Packages.lz4
    var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_jammy-updates_restricted_binary-amd64_Packages.lz4
    var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_jammy-updates_universe_binary-amd64_Packages.lz4
    var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_jammy_InRelease
    var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_jammy_main_binary-amd64_Packages.lz4
    var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_jammy_multiverse_binary-amd64_Packages.lz4
    var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_jammy_restricted_binary-amd64_Packages.lz4
    var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_jammy_universe_binary-amd64_Packages.lz4
    var/lib/apt/lists/auxfiles/
    var/lib/apt/lists/lock
    var/lib/apt/lists/partial/
    var/lib/apt/lists/security.ubuntu.com_ubuntu_dists_jammy-security_InRelease
    var/lib/apt/lists/security.ubuntu.com_ubuntu_dists_jammy-security_main_binary-amd64_Packages.lz4
    var/lib/apt/lists/security.ubuntu.com_ubuntu_dists_jammy-security_multiverse_binary-amd64_Packages.lz4
    var/lib/apt/lists/security.ubuntu.com_ubuntu_dists_jammy-security_restricted_binary-amd64_Packages.lz4
    var/lib/apt/lists/security.ubuntu.com_ubuntu_dists_jammy-security_universe_binary-amd64_Packages.lz4

    이런 느낌으로 이전 Layer 대비 차이점만을 기록해서 tar 파일 형태로 디렉터리구조 통째로 압축을 행한 것이 Layer 기반 이미지의 정체이다.

    TMI 지만 docker pull 할 때 파일 아래 이미지와 같이 첫 번째 레이어부터 "Extracting"을 하는 것은 tar + gz 형태로 압축된 레이어를 첫 번째부터 풀면서 sha256sum 값을 구해 config에 명시된 diff_ids와 값이 동일한지를 검증하는 과정이다.

    OCI Distribution Spec

    위 내용을 통해서 우리는 "컨테이너 이미지"라는 것은 사실

    1. index (json)
    2. manifest (json)
    3. config (json)
    4. layer (tar)

    4가지 종류의 데이터로 구성되어 있다는 사실을 알게 되었다. 그럼 다운로드하아야 하는 데이터도 4가지라는 소리이다.

    OCI Distribution Spec 은 이 4가지 종류의 데이터를 어떻게 다운받게 할지 HTTP 기반 API 스펙을 정의하고 있다. OpenAPI(Swagger) 기반으로 정의되면 정말 매우 많이 좋았겠지만 유감스럽게도 그건 아니고 모든 것은 줄글로 여기에 정의해 뒀다. 다만 줄글로 보기에는 내용이 모호하고 추상적인 부분이 많아서 개인적으로는 conformance test 쪽을 보는 것을 더 추천한다.

    핵심적인 요소 몇 개만 확인해 보면....

    인증과 인가

    확인해 보기 전에 먼저 이해해야 하는 것은 Distribution API의 인증과 인가이다. 결론부터 말하면, 인증과 인가는 OCI Distribution Spec에서 다뤄지지 않는다.

    하지만 Docker hub는 인증과 인가 시스템이 들어가 있으며, 몇 가지 API 가 실제 작동하는지 알아보려면 Docker Hub 가 구현한 (그리고 Distribution 에도 같은 시스템이 적용되어 있으며, 먼저 말해두면 HARBOR 도 Distribution을 쓴다.) 인증 및 인가 시스템을 이해할 필요가 있다.

    https://docs.docker.com/registry/spec/auth/token/

    Docker hub (그리고 Distribution) 은 아키텍처적으로 실제 OCI Distribution Sepc 을 구현하며 이미지를 내려주는 Registry 서버와 사용자에 대해 인증과 인가를 수행하는 Authorization 서버 두 가지로 나눠져 있다.

    Authorization 서버는

    1. 사용자의 인증 정보 (ID & PW / OAuth / Token 등등)
    2. 접근하려는 Repository 정보 / 수행할 Action 정보

    를 받아 인증 및 해당 유저에 대한 인가 여부를 결정한 뒤 하나의 JWT를 만들어준다. 다음은 Authorization 서버로의 요청 예시이다.

    >  curl "https://auth.docker.io/token?service=registry.docker.io&scope=repository:library/ubuntu:pull"
    {"token":"{JWT}","access_token":"{앞과 동일한 JWT, OAuth 표준 맞추려는 용도로 보임}","expires_in":300,"issued_at":"2023-06-29T13:36:19.076088341Z"}

    Docker Hub의 경우 Anonnymous 유저로 Public Repository 접근이 허용되기 때문에 library/ubuntu:pull 에 대한 위 요청은 성공할 것이며 5분 (300초) 간 유효한 JWT 에는 다음과 같은 정보가 담기게 된다.

    // jwt.io
    {
      "access": [
        {
          "type": "repository",
          "name": "library/ubuntu",
          "actions": [
            "pull"
          ],
          "parameters": {
            "pull_limit": "100",
            "pull_limit_interval": "21600"
          }
        }
      ],
      "aud": "registry.docker.io",
      "exp": 1688017290,
      "iat": 1688016990,
      "iss": "auth.docker.io",
      "jti": "dckr_jti_ghuFBB82iRhCEytGqCsNzDPY6ug=",
      "nbf": 1688016690,
      "sub": ""
    }

    보는 것과 같이 library/ubuntu 에 대하여 pull 만 허용을 하는 JWT 가 발급된다. 이제 Registry 서버에 요청을 할 때 Authorization 헤더에 이 토큰을 넣어주기만 하면 library/ubuntu pull 과 관련된 행위에 대해서는 5분간 유효한 요청을 보낼 수 있다.

    index 다운받기

    OCI Image Spec에서 첫 관문인 index를 먼저 다운받아보자.

    > export TOKEN="$(curl --silent --header 'GET' "https://auth.docker.io/token?service=registry.docker.io&scope=repository:library/ubuntu:pull" | jq -r '.token' )"
    > curl -H "Authorization: Bearer $TOKEN" -H "Accept: application/vnd.oci.image.index.v1+json" https://registry.hub.docker.com/v2/library/ubuntu/manifests/latest | jq
    # 앞선 index 예시와 동일

    주의사항은 Accept Header를 제대로 명시하지 않으면 다음과 같은 메시지가 뜬다.

    > curl -H "Authorization: Bearer $TOKEN" -H "Accept: *" "https://registry.hub.docker.com/v2/library/ubuntu/manifests/latest"
    {"errors":[{"code":"MANIFEST_UNKNOWN","message":"OCI index found, but accept header does not support OCI indexes"}]}

    메시지는 OCI index 를 찾았으나 * 로 명시한 accept header 가 OCI indexes 를 지원 안 한다고 한다;;
    업스트림 쪽에 이슈가 올라간 걸로 알고 있는데 뭔가 뭔가 이유로 아직까지 처리 안된 걸로 알고 있다.

    여담이지만 Accept 헤더 없이 지원이 안 되는 이유는 현재 Image와 관련해서 두 가지 표준이 존재하기 때문이다.
    하나는 Open Container Initiative에서 만든 OCI Image Spec,
    나머지 하나는 Docker에서 만든 Image Manifest V 2, Schema 2
    (Schema 1 은 deprecated 되었지만 docker save 명령어로 나오는 게 이 규격으로 알고 있다.)

    두 규격은 상당히 유사하지만 미묘하게 다른 점이 있어서 호환이 안된다. 만약 컨테이너를 만들 때
    OCI Image Spec 에 맞춰 만들었으면 application/vnd.oci.image.* 만 유효하며
    Image Manifest V 2, Schema 2 에 맞춰 만들었으면 application/vnd.docker.* 만 유효하다.

    일례로 ubuntu 이미지의 경우 OCI 표준에 맞게 제작되기 때문에
    Accept: application/vnd.oci.image.index.v1+json 를 통한 요청은 유효하다. 하지만
    Accept: application/vnd.docker.distribution.manifest.v2+json 은 유효하지 않다.

    반대로 nginx 이미지의 경우 Image Manifest V 2 에 따라 제작되기 때문에
    Accept: application/vnd.docker.distribution.manifest.v2+json 만 유효하다.

    보통은

    Accept: application/vnd.oci.image.index.v1+json,application/vnd.docker.distribution.manifest.v2+json

    와 같이 양쪽 다 Accept 한 뒤 Client 단에서 알아서 적절히 처리해 주지만 간혹 매우 옛날 시스템 (AWS ECR 이라던지 AWS Fargate 라던지 AWS Lambda 가 존재한다.) 은 한쪽만 지원을 해서 문제가 되는 경우가 존재했었다.

    대게 docker build 로 생성한 이미지는 vnd.docker* 로 그 외 나머지 (jib, buildpack, podman 등등)으로 생성된 것들은 vnd.oci* 로 생성된다.

    manifest 다운 받기

    본래 index는 optional이다. 멀티아키텍처를 지원하지 않는 경우 위 latest로 요청을 했을 때 index 대신 manifest 가 돌아온다.
    하지만 ubuntu는 멀티아키텍처를 지원하므로 manifest를 받기 위해서는 latest 대신 sha256sum 으로 요청을 보내면 된다.

    > curl -H "Authorization: Bearer $TOKEN" -H "Accept: *" https://registry.hub.docker.com/v2/library/ubuntu/manifests/latest
    {"errors":[{"code":"MANIFEST_UNKNOWN","message":"OCI index found, but accept header does not support OCI indexes"}]}

    layer, config 다운 받기

    layer와 config는 둘 다 blob으로 취급된다.

    > curl -L -H "Authorization: Bearer $TOKEN" https://registry.hub.docker.com/v2/library/ubuntu/blobs/sha256:99284ca6cea039c7784d1414608c6e846dd56830c2a13e1341be681c3ffcc8ac
    # 앞선 config 예시와 동일
    
    > curl -L -H "Authorization: Bearer $TOKEN" https://registry.hub.docker.com/v2/library/ubuntu/blobs/sha256:6b851dcae6ca1461dde247915abc5048061f34332929ca8fb37d9dc18f2e2f44
    # 앞선 layer 예시와 동일

    한 가지 재미있는 것은 curl에 -L 옵션을 추가했다. 그 이유는 위 링크로 요청은 redirect 응답이 나오기 때문이다.

    > curl -H "Authorization: Bearer $TOKEN" https://registry.hub.docker.com/v2/library/ubuntu/blobs/sha256:99284ca6cea039c7784d1414608c6e846dd56830c2a13e1341be681c3ffcc8ac -v
    # 생략
    < HTTP/1.1 307 Temporary Redirect
    < location: https://production.cloudflare.docker.com/registry-v2/docker/registry/v2/blobs/sha256/99/99284ca6cea039c7784d1414608c6e846dd56830c2a13e1341be681c3ffcc8ac/data?verify=1688002890-ZvtnD3I7N88OS5Wd0Z30MkuhHok%3D
    # 생략

    registry.hub.docker.com 요청에 대해 production.cloudflare.docker.com 로 리다이렉트 응답이 왔으며, 이는 딱 1시간 동안 유효한 서명을 받은 요청 링크이다. (verify가 S3 표준은 아닌데 자체구현인지 다른 규격인지는 잘 모르겠다. 서론에 적은 기사에 따르면 실제 데이터는 AWS S3에 저장되어 있고 Cloudflare로 엣지캐싱이 이뤄지고 있다고 한다.)

    즉, 부하분산을 위해서 API에 대한 응답은 registry.hub.docker.com 에서 오지만 실제 대부분의 트래픽이 발생하는 blob 데이터에 대해서는 Cloudflare를 사용하는 구조이다.

    이는 Distribution 에도 존재하는 옵션이고 취향 것 선택해서 적용하면 된다.

    결론

    이 글을 통해서 Container Image는 Index, Manifest, Config, Layer 4가지 요소로 구성되어 있으며
    curl을 통해서 application/vnd.oci*application/vnd.docker* 가 대차게 싸우는 과정을 살펴보았다.

    개인적으로는 application/vnd.oci* 가 이겼으면 좋....

    application/vnd.docker* 가 이겼으면 좋겠다!

    후기

    1. 제발 Registry 가지고 삽질하는 구조 좀 그만보고 싶다 라는 생각으로 이 글을 썼습니다.
    2. 제발 Harbor 찬양글 좀 그만 보고 싶다 라는 생각으로 이 글을 썼습니다.
    3. 진짜 API 스키마 조금만 바꾸면 REST API 의 모범사례로 뽑을정도로 REST 의 모든 아키텍처적인 Constraints 를 만족하는데 HATEOAS 를 충족 못해서 모범사례라고 하긴 미묘하네요. (OpenStack..? 거긴 REST 하지만 API 는 개판입니다.)
    4. 혹시 쿠버네티스 혹은 컨테이너와 관련된 블로그 글 작성하기 재미난 주제 없을까요?
      (아이디어는 몇 개 있는데 별로 글로 작성할 의욕이 없네요...)

    'Kubernetes' 카테고리의 다른 글

    [Kubernetes] Controlplane 죽이기  (1) 2023.06.26
Designed by Tistory.