Docker를 쓰다가 이런 에러를 만났다.
exec user process caused 'exec format error'
주로 운영체제 아키텍처 차이 때문에 나는 에러로, 이미지를 빌드한 머신과 실행하는 서버의 아키텍처가 다르면 이렇게 된다. M1 Mac(arm64)에서 빌드한 이미지를 amd64 서버에서 띄우려 할 때가 그렇다.
해결책은 이미지를 실행할 곳의 아키텍처로 빌드하는 것이다. Docker Buildx는 대상 아키텍처를 지정해 이미지를 빌드할 수 있는 Docker CLI다. 이 글에서는 Buildx로 멀티 아키텍처 이미지를 빌드하는 방법과 그걸 GitHub Actions 워크플로에 넣은 설정을 정리한다.
Buildx를 쓰기 전에 확인할 것
Docker 버전: Buildx는 Docker 19.03 이상에서 쓸 수 있다.
Docker Desktop 사용자라면 Buildx가 기본으로 들어 있다.
일부 하위 버전은
~/.docker/config.json에"experimental": "enabled"옵션을 추가해야 한다.
빌더 인스턴스부터 만든다
멀티 아키텍처 이미지를 빌드하려면 먼저 새 빌더 인스턴스가 필요하다.
docker buildx create --name multiarch-builder --driver docker-container --use
| 옵션 | 의미 |
|---|---|
docker buildx create | 새 빌더 인스턴스를 만드는 기본 명령어 |
--name multiarch-builder | 빌더 이름. 다른 이름으로 바꿔도 된다 |
--driver docker-container | 빌더가 쓸 드라이버. 새 Docker 컨테이너 안에서 빌드를 실행해 호스트와 격리된 환경에서 빌드한다 |
--use | 새로 만든 빌더를 바로 현재 활성 빌더로 설정한다. 추가 명령 없이 바로 쓸 수 있다 |
--platform에 적은 아키텍처로 빌드된다
docker buildx build --platform=linux/amd64,linux/arm64 -t your-image:tag --push .
--platform: 빌드할 이미지가 지원해야 할 아키텍처 (예: linux/amd64, linux/arm64)--push: 이미지를 빌드한 뒤 설정된 Docker 레지스트리로 push
아키텍처는 빌드하는 머신이 아니라 --platform이 정한다. 두 개를 적으면 아키텍처별 이미지가 같은 태그 아래 묶여 레지스트리에 올라가고, pull하는 쪽은 자기 아키텍처에 맞는 이미지를 받는다.
빌드된 이미지의 아키텍처는 컨테이너를 띄워서 확인한다.
# libc6 패키지의 아키텍처 확인 (dpkg가 있는 Debian 계열 이미지)
docker exec -it container-name dpkg -s libc6 | grep Arch
# 컨테이너의 아키텍처 확인
docker exec [container-name] uname -m
uname -m의 일반적인 결과 값은 다음과 같다.
| 결과 | 아키텍처 |
|---|---|
x86_64 | 64비트 x86 (Intel 또는 AMD 프로세서) |
aarch64 | 64비트 ARM (예: Apple M1, Amazon Graviton) |
armv7l | 32비트 ARM |
i686 | 32비트 x86 |
ppc64le | 64비트 PowerPC (리틀 엔디안) |
s390x | IBM System z |
빌드는 GitHub Actions 러너에게 맡겼다
로컬에서도 buildx로 특정 아키텍처 이미지를 빌드해 push해 봤지만, 더 중요했던 건 GitHub Actions에 적용해 가상머신이 스스로 buildx로 Build & Push하게 만드는 일이었다. 누구 노트북에서 빌드하느냐에 따라 이미지 아키텍처가 달라지는 일을 없애려면 빌드하는 곳을 한 군데로 모으는 게 가장 확실하다.
실무에 적용한 워크플로 구성 파일 일부다. 앞의 build job이 만든 jar를 받아 이미지로 굽고 ECR에 올린다.
..(생략)..
docker:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Download artifact
uses: actions/download-artifact@v4
with:
name: ${{env.APP_NAME}}-application-jar
path: target/
# OIDC 인증
- name: configure aws credentials
uses: aws-actions/configure-aws-credentials@v3
with:
role-to-assume: ${{secrets.AWS_OIDC_ROLE}}
role-session-name: ecrRoleSession
aws-region: ${{secrets.AWS_REGION}}
# ECR 로그인
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2
# Docker 이미지 빌드 & ECR에 Push
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Build and push Docker image
uses: docker/build-push-action@v3
with:
context: .
file: ./Dockerfile
push: true
tags: ${{ secrets.AWS_ECR_URL }}/${{ env.APP_NAME }}:${{ env.BRANCH_NAME }}-${{ github.sha }}
platforms: linux/amd64
update-deployment:
needs: docker
runs-on: ubuntu-latest
..(생략)..
로컬에서 치던 명령과 대응시키면 이렇다. docker/setup-buildx-action이 docker buildx create --use를, docker/build-push-action의 platforms와 push: true가 --platform과 --push를 대신한다. 태그에는 브랜치 이름과 커밋 SHA를 붙여 어떤 커밋에서 나온 이미지인지 바로 알 수 있게 했다.
이 워크플로는 linux/amd64 하나만 빌드한다. arm64까지 함께 빌드하려면 Buildx 설정 앞에 docker/setup-qemu-action을 추가해 에뮬레이션을 켜야 한다.