컨테이너 이미지의 CPU 아키텍처는 빌드하는 환경에 의해 기본적으로 결정되고 옵션이나 설정으로 변경할 수 있다.
CPU 아키텍처와 실행 환경이 달라지면 실행이 안 되므로 빌드하는 환경과 실행하는 환경이 다른 경우 주의할 필요가 있다.
PC나 서버 환경은 대부분 x86_64 아키텍처이며 보통 amd64라는 코드명으로 부르곤 한다. x86이란 인텔에서 개발한 CPU 아키텍처를 의미하는데 AMD에서 주도하여 64비트로 확장시킨 것이다. 80386(386) 또는 i386이라고 부르는 32비트 아키텍쳐도 있지만 이제는 더 이상 널리 사용되지 않고 있으니 여기서는 다루지 않는다.
최근 애플이 M1, M2 등의 Silicon Mac을 시장에 내놓고 있는데, 이것은 ARM의 64비트 CPU를 사용하는 것이다. arm64라는 코드명으로 부를 수 있다.
기본적으로는 컨테이너 이미지를 빌드하는 환경에 따라 이미지의 CPU 아키텍처가 결정된다. amd64 환경에서 만들어진 이미지는 amd64 환경에서 실행 가능한 바이너리가 되고, arm64 환경에서 만들어진 이미지는 arm64 환경에서 실행 가능한 바이너리가 된다. 상호 호환성은 없다.
| 실행 환경 \ 빌드 환경 | amd64 | arm64 |
|---|---|---|
| amd64 | O | X |
| arm64 | X | O |
빌드 환경과 실행 환경이 다른 개발 방식을 일반적으로 "크로스 플랫폼 개발"이라고 하는데, 컨테이너는 일반적인 크로스 플랫폼 개발과 약간 다른 점이 있다.
컨테이너 이미지는 기본적으로 베이스(base) 이미지를 지정해서 그 위에 추가 설치나 설정을 통해 자신만의 커스텀 이미지를 빌드하는 방식으로 개발된다. Dockerfile에 FROM 명령을 이용해서 베이스 이미지를 지정할 수 있다.
FROM nginx:latest
RUN apt-get update -y > /dev/null
RUN apt-get install -y curl > /dev/null
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
docker build -t my_username/my_nginx .
이렇게 실행하게 되면 docker 런타임은 nginx 컨테이너 이미지를 가져올 때, 빌드 환경의 CPU 아키텍처를 참고하여 그 아키텍처로 만들어진 nginx 이미지를 가져오게 된다.
Docker Hub의 nginx 태그 정보를 살펴보면 다음과 같다.
https://hub.docker.com/_/nginx/tags
OS/ARCH 필드에 linux/386, linux/amd64, linux/arm64 등의 값으로 구분되어 있고, 컨테이너는 Linux 커널의 기능에 의존하는 것이기 때문에 OS는 모두 linux로 되어 있다. 아키텍처를 의미하는 ARCH 필드에 amd64, arm64 등이 있는데, Silicon Mac에서 빌드하면 arm64로 빌드되어 있는 nginx 이미지를 선택하고 amd64 Linux에서 빌드하면 amd64로 빌드되어 있는 nginx 이미지를 선택하여 PC로 pull/다운로드하게 된다.
위 예제에서 빌드한 이미지의 아키텍처를 확인해볼 수 있다.
docker image inspect my_username/my_nginx | grep -i arch
만약, Silicon Mac에서 Linux 서버용 amd64 이미지를 빌드하려고 한다면 여러 방법이 있다.
우선, docker 명령의 기본 빌드킷(buildkit)인 build로도 가능은 한데, buildx라는 빌드킷을 추가로 설치하는 것이 바람직하다. 2024년 초 기준으로 기본 기능이 deprecate되어 buildkit으로 buidx를 이용하라는 경고가 나오고 있으니 buildx로 전환하자.
DEPRECATED: The legacy builder is deprecated and will be removed in a future release.
Install the buildx component to build images with BuildKit:
https://docs.docker.com/go/buildx/
Docker Engine 23.0 및 Docker Desktop 4.19부터 buildx가 기본이 되었다고 하는데, Docker CE를 사용할 경우 별도로 설치해줘야 할 수도 있다.
brew install docker
brew install docker-buildx
docker-buildx를 설치하면 docker 명령의 plugin으로 사용할 수 있도록 추가 설정 작업을 해야 한다는 안내가 나온다. brew info 명령으로 다시 확인할 수도 있다.
brew info docker-buildx
sudo apt install docker-buildx
이제부터는 docker build 대신에 docker buildx build 명령을 사용하는 게 좋다. docker build가 실제로는 docker buildx build를 실행하기도 하지만, 환경이나 옵션에 영향을 받아서 기본 docker build로 동작할 수도 있다.
환경변수 DOCKER_BUILDKIT을 참조하는 런타임도 있는데, buildx가 잘 실행되지 않는 경우 다음 명령이 필요하다.
export DOCKER_BUILDKIT=1
FROM --platform=linux/amd64 nginx:latest
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
FROM 명령의 옵션으로 --platform=linux/amd64를 지정했다. 이러면 docker build 명령을 실행할 때, amd64로 빌드되어 있는 nginx 이미지를 가져다가 my-nginx 이미지를 amd64로 빌드한다.
docker buildx build -t my_username/my_nginx .
docker image inspect my_username/my_nginx | grep -i arch
혹시 캐싱만 되고 docker images 목록에 노출되지 않으면 build 명령에 --load 옵션을 추가해서 실행해본다.
WARNING: No output specified with docker-container driver. Build result will only remain in the build cache. To push result image into registry use --push or to load image into docker use --load
docker buildx build -t my_username/my_nginx --load .
Dockerfile의 FROM 명령의 옵션을 지정하는 대신 docker build 명령행의 옵션으로 지정할 수도 있다.
docker buildx build -t my_username/my_nginx --platform=linux/amd64 .
기본 빌드킷인 docker build는 아키텍처별로 따로 빌드를 해주고 manifest로 합쳐서 push를 해야 한다.
docker build -t my_username/my_nginx:amd64 --platform=linux/amd64 .
docker build -t my_username/my_nginx:arm64 --platform=linux/arm64 .
docker manifest create my_username/my_nginx:latest \
my_username/my_nginx:amd64 \
my_username/my_nginx:arm64
buildx는 --platform 옵션을 이용하여 여러 CPU 아키텍처를 지정하면 한 번에 "멀티 플랫폼 빌드"가 가능하다.
docker buildx build --platform linux/amd64,linux/arm64 -t my_username/my_nginx .
그러나 이 명령행은 여러가지 경고나 에러 메시지를 출력하고 실패할 것이다. 멀티 아키텍처 이미지를 만들면 어딘가로 내보내야(output or export)하는데 이걸 지정하지 않으면 빌드 단계에서 실패 처리된다. --output 옵션을 이용해서 exporter의 타입을 지정해줘야 한다.
docker buildx build --platform linux/amd64,linux/arm64 -t my_username/my_nginx --output="type=image" .
빌드는 성공하지만 이미지 목록이 조회되지 않을 수도 있다. 이런 경우, 물리적인 출력이 필요한데, 아래 트러블슈팅 두번째 항목을 살펴보자.
ERROR: Multiple platforms feature is currently not supported for docker driver. Please switch to a different driver (eg. "docker buildx create --use")
기본 빌더(default)로는 멀티 플랫폼 빌드가 되지 않는다. 별도 빌더를 생성해서 사용할 필요가 있다.
docker buildx create --use
docker buildx ls
WARNING: No output specified with docker-container driver. Build result will only remain in the build cache. To push result image into registry use --push or to load image into docker use --load
--load 옵션으로는 해결이 안 된다. Docker hub이나 registry로 push를 하든가 아니면 출력(output)을 지정해서 export해야 한다. exporter의 종류에는 다음과 갈은 것들이 있다.
--load 옵션과 마찬가지로 --output="type:docker"는 멀티 플랫폼 빌드에서는 사용 불가하다.
local exporter를 이용해서 현재 디렉토리에 이미지를 출력해보고 어떤 아키텍처의 바이너리가 만들어지는지 확인해볼 수 있다. tar 출력도 비슷하다.
docker buildx build --platform linux/amd64,linux/arm64 -t my_username/my_nginx --output="type=local,dest=." .
file linux_amd64/bin/mv
file linux_arm64/bin/mv