catsridingCATSRIDING|OCEANWAVES
Ops

Docker 멀티 플랫폼 이미지 빌드하기

jynn@catsriding.com
Apr 30, 2024
Published byJynn
999
Docker 멀티 플랫폼 이미지 빌드하기

Multi-Platform Images build with Docker

멀티 플랫폼 이미지 빌드는 Docker의 핵심 기능 중 하나로, 다양한 운영 시스템과 하드웨어 아키텍처에서 애플리케이션을 효율적으로 실행할 수 있도록 지원합니다. 예를 들어, ARM과 x86 아키텍처를 모두 지원해야 할 경우, Docker의 buildx 기능을 사용하면 단일 빌드 프로세스를 통해 여러 플랫폼 대상으로 이미지를 생성할 수 있습니다. 이 접근법은 개발 과정을 간소화하고 호환성 문제를 최소화할 뿐만 아니라, 클라우드, 모바일, IoT 환경에서의 애플리케이션 배포에 큰 이점을 제공합니다.

실제로 macOS 기반의 Apple Silicon에서 빌드한 이미지를 다른 CPU에서 실행하려는 경우에는 아래와 같은 경고 메시지가 발생할 수 있습니다:

console
WARNING: The requested image's platform (linux/arm64/v8) does not match the detected host platform (linux/amd64/v3) and no specific platform was requested
8e2wer1232131d4a012890aslke1q908wqlj19288301

이는 이미지의 플랫폼이 호스트 플랫폼과 일치하지 않을 때 발생하는 문제입니다. 이를 해결하기 위해서는 해당 환경에 적절한 이미지를 빌드하여 배포해야 합니다. 그러나 로컬 머신, 개발 서버 및 운영 서버마다 다른 환경이라면 매번 이미지를 여러 번 빌드해야 하는 불편함이 있습니다. 이러한 번거로움을 해결하기 위해 Docker에서는 멀티 플랫폼 이미지 빌드를 지원하게 되었습니다.

Docker Buildx

플랫폼 간 이식성을 향상시키려면 Docker의 buildx를 사용하여 멀티 아키텍처 이미지를 빌드할 수 있습니다. 이는 이미지가 여러 CPU 아키텍처에서 실행될 수 있도록 합니다. buildx는 Docker 빌드 명령의 확장된 버전으로, 완전한 BuildKit 기능을 사용하며 다양한 출력 유형과 멀티 플랫폼 빌드를 지원합니다.

Docker BuildKit
BuildKit은 Docker에서 개발한 현대적인 이미지 빌드 도구로, Docker의 빌드 성능을 향상시키기 위해 설계되었습니다. 이 도구는 병렬 처리와 향상된 캐싱 메커니즘을 제공하여 빌드 시간을 단축시키고, 보안성을 높이며, 불필요한 데이터 전송을 최소화합니다. 또한, BuildKit은 다양한 출력 옵션과 플랫폼을 지원합니다. 이러한 특징 때문에 특히 대규모 프로젝트와 복잡한 빌드 환경에서 그 성능 향상이 크게 나타납니다. BuildKit의 고급 기능들은 개발자들이 더욱 효율적이고 안전하게 애플리케이션을 배포할 수 있도록 지원하며, Docker 생태계 내에서 핵심적인 역할을 수행하고 있습니다.

Docker Buildx Commands

Docker의 buildx 기능을 활용하는 방법은 다음과 같습니다:

$ docker buildx [OPTIONS] COMMAND

주요 buildx 명령어를 아래에 나열하였습니다:

  • bake: 파일에서 빌드를 시작합니다. 빌드 프로세스의 구성은 파일에서 읽어옵니다.
  • build: Docker 이미지를 빌드합니다. 여기서는 Dockerfile을 사용하여 빌드를 시작합니다.
  • create: 새로운 빌더 인스턴스를 생성합니다. 빌더 인스턴스는 멀티 플랫폼 빌드를 실행하는 데 사용됩니다.
  • dial-stdio: 현재의 stdio 스트림을 빌더 인스턴스로 프록시합니다. 이를 통해 빌더 인스턴스의 입력 및 출력을 관리할 수 있습니다.
  • du: Docker 빌드 캐시에 사용된 디스크 공간의 사용량을 확인합니다.
  • inspect: 현재 설정된 빌더 인스턴스의 구성 및 상태를 검사합니다.
  • ls: 현재 생성된 빌더 인스턴스의 목록을 표시합니다.
  • prune: 사용하지 않는 빌드 캐시를 정리하여 디스크 공간을 확보합니다.
  • rm: 하나 이상의 빌더 인스턴스를 제거합니다.
  • stop: 실행 중인 빌더 인스턴스를 중지합니다.
  • use: 현재 사용할 빌더 인스턴스를 설정합니다.
  • version: 현재 설치된 Docker Buildx의 버전 정보를 표시합니다.

상기 명령어들 외에도, 다음의 추가적인 옵션들을 활용할 수 있습니다:

  • --builder: 특정한 빌더 인스턴스를 지정하여 해당 인스턴스에서 빌드를 실행합니다.
  • --progress: 빌드 진행 상황을 표시할 때 사용되는 플래그입니다. 여기에는 auto, plain, tty 등이 포함될 수 있습니다.
  • --output: 빌드의 출력을 지정합니다. 주로 type=type,dest=path의 형식으로 사용됩니다.
  • --platform: 빌드할 대상 플랫폼을 지정합니다. 이를 통해 여러 플랫폼에서 동작하는 이미지를 빌드할 수 있습니다.
  • --cache-to: 빌드 캐시를 지정된 레지스트리에 푸시합니다. 이를 통해 캐시를 재사용할 수 있습니다.
  • --cache-from: 빌드 캐시를 지정된 레지스트리에서 가져옵니다. 이를 통해 이전 빌드의 캐시를 재사용할 수 있습니다.
  • --pull: 빌드 전에 베이스 이미지를 항상 최신 버전으로 업데이트합니다.
  • --push: 빌드된 이미지를 레지스트리에 푸시합니다.

이러한 명령어들과 추가적인 옵션들을 활용하면, 빌드 과정을 보다 세밀하게 조절하고 제어할 수 있습니다.

Docker Multi-Platform Image Build

멀티 플랫폼 이미지를 빌드하려면 먼저 Docker CLI를 buildx로 설정해야 합니다:

$ docker buildx create --name waves-builder
$ docker buildx use waves-builder

그런 다음 docker buildx build 명령을 사용하여 이미지를 빌드합니다.

$ docker buildx build \
--platform {PLATFORMS} \
-t {DOCKER_USERNAME}/{IMAGE_NAME}:{TAG} .
  • --platform: 대상 플랫폼을 지정하고 여러 플랫폼을 지정하려면 쉼표로 구분하여 나열합니다.

실제로 다음과 같이 이미지 빌드 작업을 실행할 수 있습니다:

console
$ docker buildx build \
--platform linux/amd64,linux/arm64 \
-t catsriding/hello-docker:latest \
-t catsriding/hello-docker:1.0.0 \
--push .

[+] Building 193.0s (25/25) FINISHED                                                                                                                                    docker-container:waves-builder
 => [internal] load build definition from Dockerfile                                                                                                                                              0.0s
 => => transferring dockerfile: 437B                                                                                                                                                              0.0s
 => [linux/arm64 internal] load metadata for docker.io/library/gradle:8.6.0-jdk17                                                                                                                 2.2s
 => [linux/arm64 internal] load metadata for docker.io/library/openjdk:17                                                                                                                         2.8s
 => [linux/amd64 internal] load metadata for docker.io/library/gradle:8.6.0-jdk17                                                                                                                 2.2s
 => [linux/amd64 internal] load metadata for docker.io/library/openjdk:17                                                                                                                         2.8s
 => [auth] library/gradle:pull token for registry-1.docker.io                                                                                                                                     0.0s
 => [auth] library/openjdk:pull token for registry-1.docker.io                                                                                                                                    0.0s
 => [internal] load .dockerignore                                                                                                                                                                 0.0s
 => => transferring context: 2B                                                                                                                                                                   0.0s
 => [linux/amd64 stage-1 1/3] FROM docker.io/library/openjdk:17@sha256:528707081fdb9562eb819128a9f85ae7fe000e2fbaeaf9f87662e7b3f38cb7d8                                                          11.4s
 => => resolve docker.io/library/openjdk:17@sha256:528707081fdb9562eb819128a9f85ae7fe000e2fbaeaf9f87662e7b3f38cb7d8                                                                               0.0s
 => => sha256:a7203ca35e75e068651c9907d659adc721dba823441b78639fde66fc988f042f 187.53MB / 187.53MB                                                                                                9.0s
 => => sha256:de849f1cfbe60b1c06a1db83a3129ab0ea397c4852b98e3e4300b12ee57ba111 13.53MB / 13.53MB                                                                                                  1.0s
 => => sha256:38a980f2cc8accf69c23deae6743d42a87eb34a54f02396f3fcfd7c2d06e2c5b 42.11MB / 42.11MB                                                                                                  4.0s
 => => extracting sha256:38a980f2cc8accf69c23deae6743d42a87eb34a54f02396f3fcfd7c2d06e2c5b                                                                                                         1.5s
 => => extracting sha256:de849f1cfbe60b1c06a1db83a3129ab0ea397c4852b98e3e4300b12ee57ba111                                                                                                         0.6s
 => => extracting sha256:a7203ca35e75e068651c9907d659adc721dba823441b78639fde66fc988f042f                                                                                                         2.3s
 => [linux/amd64 build 1/4] FROM docker.io/library/gradle:8.6.0-jdk17@sha256:27ed98487dd9c155d555955084dfd33f32d9f7ac5a90a79b1323ab002a1a8b6e                                                     0.0s
 => => resolve docker.io/library/gradle:8.6.0-jdk17@sha256:27ed98487dd9c155d555955084dfd33f32d9f7ac5a90a79b1323ab002a1a8b6e                                                                       0.0s
 => [linux/arm64 build 1/4] FROM docker.io/library/gradle:8.6.0-jdk17@sha256:27ed98487dd9c155d555955084dfd33f32d9f7ac5a90a79b1323ab002a1a8b6e                                                     0.0s
 => => resolve docker.io/library/gradle:8.6.0-jdk17@sha256:27ed98487dd9c155d555955084dfd33f32d9f7ac5a90a79b1323ab002a1a8b6e                                                                       0.0s
 => [internal] load build context                                                                                                                                                                 0.1s
 => => transferring context: 398.24kB                                                                                                                                                             0.0s
 => [linux/arm64 stage-1 1/3] FROM docker.io/library/openjdk:17@sha256:528707081fdb9562eb819128a9f85ae7fe000e2fbaeaf9f87662e7b3f38cb7d8                                                          11.0s
 => => resolve docker.io/library/openjdk:17@sha256:528707081fdb9562eb819128a9f85ae7fe000e2fbaeaf9f87662e7b3f38cb7d8                                                                               0.0s
 => => sha256:fe66142579ff5bb0bb5cf989222e2bc77a97dcbd0283887dec04d5b9dfd48cfa 14.29MB / 14.29MB                                                                                                  1.1s
 => => sha256:1250d2aa493e8744c8f6cb528c8a882c14b6d7ff0af6862bbbfe676f60ea979e 186.36MB / 186.36MB                                                                                                4.8s
 => => sha256:416105dc84fc8cf66df5d2c9f81570a2cc36a6cae58aedd4d58792f041f7a2f5 42.02MB / 42.02MB                                                                                                  5.1s
 => => extracting sha256:416105dc84fc8cf66df5d2c9f81570a2cc36a6cae58aedd4d58792f041f7a2f5                                                                                                         1.7s
 => => extracting sha256:fe66142579ff5bb0bb5cf989222e2bc77a97dcbd0283887dec04d5b9dfd48cfa                                                                                                         0.3s
 => => extracting sha256:1250d2aa493e8744c8f6cb528c8a882c14b6d7ff0af6862bbbfe676f60ea979e                                                                                                         2.6s
 => CACHED [linux/amd64 build 2/4] WORKDIR /app                                                                                                                                                   0.0s
 => [linux/amd64 build 3/4] COPY . .                                                                                                                                                              0.1s
 => CACHED [linux/arm64 build 2/4] WORKDIR /app                                                                                                                                                   0.0s
 => [linux/arm64 build 3/4] COPY . .                                                                                                                                                              0.1s
 => [linux/arm64 build 4/4] RUN gradle clean build --no-daemon                                                                                                                                   26.1s
 => [linux/amd64 build 4/4] RUN gradle clean build --no-daemon                                                                                                                                  151.0s
 => [linux/arm64 stage-1 2/3] WORKDIR /app                                                                                                                                                        0.2s
 => [linux/amd64 stage-1 2/3] WORKDIR /app                                                                                                                                                        0.0s
 => [linux/arm64 stage-1 3/3] COPY --from=build /app/build/libs/*.jar /app/application.jar                                                                                                        0.0s
 => [linux/amd64 stage-1 3/3] COPY --from=build /app/build/libs/*.jar /app/application.jar                                                                                                        0.0s
 => exporting to image                                                                                                                                                                           38.5s
 => => exporting layers                                                                                                                                                                           0.5s
 => => exporting manifest sha256:f6a344e9f8c24268f5bd7340de64b22106b952491a34c7b4d84e9f70f5d2ec82                                                                                                 0.0s
 => => exporting config sha256:3136868ba0c53a71ec1b4aa0f2db6acdf23bf29f3875ac0e368d5538e5674e9e                                                                                                   0.0s
 => => exporting attestation manifest sha256:84ff71291fa072ef3a2b5d33f6047435897a6f88e131989c826fbcedf2bc6fa8                                                                                     0.0s
 => => exporting manifest sha256:75c4fda6ab7aa28f571e79db3eed54c083e0792559662516efec5d8bc9b9b5b4                                                                                                 0.0s
 => => exporting config sha256:e06d71be7d33dcb653966a1b8f9c9779889fed327d8330ba933055fdbe0bf87a                                                                                                   0.0s
 => => exporting attestation manifest sha256:73902fdf9d077b7ea3ec18646a2ba86b1ae4b5013ba868639c291f13f68f24ba                                                                                     0.0s
 => => exporting manifest list sha256:e788a4906c2053685d3458cbce44487483cd1597fa791a025208f93da5d5bfda                                                                                            0.0s
 => => pushing layers                                                                                                                                                                             2.5s
 => => pushing manifest for docker.io/catsriding/hello-docker:latest@sha256:e788a4906c2053685d3458cbce44487483cd1597fa791a025208f93da5d5bfda                                                      3.2s
 => => pushing manifest for docker.io/catsriding/hello-docker:1.0.0@sha256:e788a4906c2053685d3458cbce44487483cd1597fa791a025208f93da5d5bfda                                                       2.0s
 => [auth] catsriding/hello-docker:pull,push token for registry-1.docker.io                                                                                                                       0.0s

View build details: docker-desktop://dashboard/build/waves-builder/waves-builder0/p9m665wfrsfjt6vhybzlbk4qt

Build multi-platform images faster with Docker Build Cloud: https://docs.docker.com/go/docker-build-cloud

위의 명령은 linux/amd64linux/arm64 아키텍처를 대상으로 이미지를 빌드하고, 해당 이미지를 Docker Hub로 푸시합니다.

arm64 vs. amd64
arm64는 ARMv8 아키텍처를 기반으로 한 64-bit 프로세서를 나타냅니다. 이 아키텍처는 모바일, 태블릿, 임베디드 시스템 등에서 널리 사용되며, 최근에는 애플의 M1 칩 등에서도 사용되고 있습니다. amd64는 x86-64라고도 불리며, 64-bit 메모리 주소를 처리할 수 있는 프로세서를 나타냅니다. 이 아키텍처는 AMD에서 처음 개발되어 Intel과 AMD 등 대부분의 데스크탑 및 랩탑 CPU에서 사용됩니다.

Docker 멀티 플랫폼 이미지가 성공적으로 빌드되었는지 확인하려면, docker manifest inspect 명령어를 사용해 이미지 메타데이터를 확인해볼 수 있습니다. 이를 통해 이미지에 대한 상세한 정보를 살펴볼 수 있습니다:

$ docker manifest inspect {DOCKER_USERNAME}/{IMAGE_NAME}:{TAG}

이미지 메타데이터 출력 결과를 확인하면 두 플랫폼에 대한 이미지가 빌드된 것을 확인할 수 있습니다:

console
{
   "schemaVersion": 2,
   "mediaType": "application/vnd.oci.image.index.v1+json",
   "manifests": [
      {
         "mediaType": "application/vnd.oci.image.manifest.v1+json",
         "size": 1249,
         "digest": "sha256:f6a344e9f8c24268f5bd7340de64b22106b952491a34c7b4d84e9f70f5d2ec82",
         "platform": {
            "architecture": "amd64",
            "os": "linux"
         }
      },
      {
         "mediaType": "application/vnd.oci.image.manifest.v1+json",
         "size": 1249,
         "digest": "sha256:75c4fda6ab7aa28f571e79db3eed54c083e0792559662516efec5d8bc9b9b5b4",
         "platform": {
            "architecture": "arm64",
            "os": "linux"
         }
      }
   ]
}

Docker Hub에 로그인 한 후 해당 이미지의 상세 페이지로 이동하면, 이미지명 옆에 MULTI-PLATFORM 뱃지와 OS/ARCH 옵션이 추가된 것을 확인할 수 있습니다:

multi-platform-images-build-with-docker_00.png

Docker Multi-Platform Image Pull

빌드해서 업로드한 멀티 플랫폼 이미지를 Docker Hub에서 다운로드 받을 때는 해당 머신의 환경에 맞는 이미지가 기본적으로 선택됩니다. 하지만 특정 플랫폼을 지정하여 이미지를 다운로드 받고자 한다면 다음과 같이 할 수 있습니다:

console
$ docker pull {DOCKER_USERNAME}/{IMAGE_NAME}:{TAG} --platform={PLATFORM}

예를 들어, ARM 아키텍처를 사용하는 머신에서 x86 아키텍처를 사용하는 이미지를 다운로드 받으려면 다음과 같이 명령어를 실행할 수 있습니다:

console
$ docker pull catsriding/hello-docker:latest --platform=linux/amd64

이렇게 함으로써 특정 플랫폼에 맞는 이미지를 선택하여 다운로드 받을 수 있습니다.

  • Docker