지난번 t4g 타입의 EC2 인스턴스를 소개하며 Arm 기반의 AWS Graviton2 프로세서에 대해 간략히 소개해드린 바가 있습니다. 이와 같이 Graviton 프로세서가 적용된 인스턴스 유형은 Amazon EC2 T4g, M6g, C6g 및 R6g 인스턴스 유형이 있는데요. Amazon EKS의 노드 그룹 생성 시에도 T4g, M6g, C6g 및 R6g 인스턴스 유형을 선택할 수 있기에 Docker 이미지 빌드 및 배포 과정에서 발생할 수 있는 CPU 아키텍쳐 관련 문제 상황 및 해결 방법에 대해 소개해드리고자 합니다.
Docker 이미지와 CPU 아키텍쳐
컨테이너에서 사용되는 이미지와 컨테이너의 개념은 어플리케이션 실행에 필요한 바이너리와 라이브러리를 패키징하여 만든 이미지를 Docker 이미지, Docker 이미지를 격리된 공간에서 실행한 것을 컨테이너라고 부릅니다.
때문에 Docker 컨테이너 환경에서는 하나의 서버에서 구동되는 여러 어플리케이션 간 라이브러리와 같은 의존성 패키지의 버전 충돌 문제가 생기지 않으며, 개발 환경에서 빌드된 어플리케이션을 프로덕션 환경으로 그대로 가져왔을 때에도 발생할 수 있는 라이브러리 버전 문제도 해결할 수 있습니다.
하지만 이러한 Docker 환경에서도 이미지에 따라 지원하는 CPU 아키텍쳐의 종류가 다릅니다. 예를 들어 Docker hub에서 MySQL을 검색 시 MySQL 이미지의 경우 x86_64(일반적인 인텔이나 AMD CPU) 아키텍쳐만 지원하는 것을 알 수 있으며, 바로 아래 있는 MariaDB 이미지의 경우 x86_64 뿐만 아니라 x86(32Bit), Arm64(모바일이나 Mac M1 칩, AWS Graviton2 프로세서) 등 여러 CPU 아키텍쳐를 지원하는 것을 알 수 있습니다.
실제로 AWS Graviton2 프로세서 환경인 t4g 타입의 인스턴스에서 Arm64 아키텍쳐를 지원하지 않는 MySQL 도커 이미지를 사용하여 컨테이너를 만들려고 하면 아래와 같이 에러가 발생하는 것을 알 수 있습니다.
때문에 Docker 이미지를 빌드할 때 이러한 CPU 아키텍쳐 문제를 해결하려면 크게 두 가지 해결책을 생각할 수 있습니다.
1) Docker 컨테이너를 실행할 환경과 동일한 CPU 아키텍쳐 환경에서 이미지를 빌드한다.
-> 가장 확실한 해결책이지만 만약 기존 이미지 빌드하는 환경(Jenkins 등)을 변경할 수 없는 상태라면 해결이 어렵습니다. 또한 사용하는 아키텍쳐의 종류가 많아질때마다 빌드 환경을 새로 세팅해야하는 불편함이 있을 것입니다.
예) x86_64 노트북을 사용하고 있었는데 도커 이미지 빌드를 위해 Arm64 아키텍쳐의 노트북을 새로 산다.
예) x86_64 환경의 Jenkins를 통해 이미지를 빌드하고 있었는데 Arm64 환경의 Jenkins를 새로 세팅한다.
2) Docker 이미지 빌드 시 여러 CPU 아키텍쳐를 지원하도록 멀티 플랫폼 빌드 환경을 구축한다.
-> 이번 게시글에서 다룰 내용으로 Docker buildx라는 CLI 플러그인을 통해 하나의 빌드 환경에서 다수의 플랫폼 환경에서 구동되는 이미지를 빌드하도록 구성합니다.
예) x86_64 환경에서 x86_64, x86(32Bit), Arm64, Arm(32Bit) 환경의 이미지를 모두 빌드한다.
테스트 환경과 문제 상황
테스트 환경
먼저 테스트를 진행한 환경을 소개해드리고자 합니다. 멀티 플랫폼 빌드의 경우 커널 버전의 최소 제한이 있어 CentOS AMI는 사용이 불가합니다.(사전에 커널 업데이트를 진행한다면 가능하겠지만, 번거롭기에 Amazon Linux 환경에서 테스트 진행하였습니다.)
1) Docker 이미지 빌드용 인스턴스
인스턴스 타입 - t3a.small 타입
CPU 아키텍쳐 - x86_64
OS - Amazon Linux
2) Docker 컨테이너 구동용(서비스 용도) 인스턴스
인스턴스 타입 - t4g.small 타입
CPU 아키텍쳐 - Arm64
OS - Amazon Linux
3) Docker 이미지 레지스트리 -> Amazon ECR
레포지토리 이름 - apache-alpine
레포지토리 타입 - 퍼블릭
문제 상황 연출
일반적인 build 명령어를 통해 이미지를 빌드하여 레지스트리에 푸쉬하고, 서비스 구동 환경에서 이미지를 풀링하여 컨테이너를 구동해보겠습니다. 이미지 빌드에 사용한 Dockerfile 소스는 "Alpine Linux로 Docker 이미지 빌드 게시물에서 사용했던 Dockerfile을 그대로 사용했습니다.
먼저 이미지를 푸쉬하기 위해 ECR에 로그인합니다. ECR 로그인 명령어는 AWS 콘솔에서 해당 레포지터리를 선택 후 "푸쉬 명령 보기"라는 버튼을 클릭하면 상세히 나와 있으니 참고하여 로그인 합니다.
이제 apache-alpine이라는 디렉토리를 하나 생성하여 해당 디렉토리에 안에 아래 내용으로 Dockerfile을 작성합니다.
# 작업 디렉토리 생성
mkdir apache-alpine && cd apache-alpine
# Dockerfile 작성
vim Dockerfile
FROM alpine:latest
RUN apk update --no-cache && apk add --no-cache apache2
ENTRYPOINT [ "httpd","-D","FOREGROUND" ]
EXPOSE 80
# 이미지 빌드
docker build -t public.ecr.aws/s1v1h3u2/apache-alpine:1.0 ./
이미지의 용량이 작기 때문에 금방 빌드가 완료되었습니다.
이제 빌드한 이미지를 ECR 레포지토리로 푸쉬합니다.
# 이미지 푸쉬
docker push public.ecr.aws/s1v1h3u2/apache-alpine:1.0
이제 t4g 타입의 서비스 구동용 인스턴스에서 해당 이미지를 풀링하여 컨테이너를 생성해봅니다. 컨테이너는 바로 "exec user process caused: exec format error" 에러가 발생하며 종료됩니다.
앞서 설명한 것처럼 Docker 이미지를 빌드한 환경은 x86_64 아키텍쳐 환경이고, 이미지를 실행한 환경은 Arm64 아키텍쳐 환경이기 때문입니다. 이제 아래 내용을 통해 Docker buildx 플러그인을 설치하고 QEMU 에뮬레이팅 환경을 구성하여 다수 플랫폼에서 실행 가능한 이미지를 만들어보도록 하겠습니다.
Docker buildx
Docker 공식 가이드
https://docs.docker.com/buildx/working-with-buildx/
Docker buildx 공식 Github 및 가이드
https://github.com/docker/buildx
Docker buildx란 기존 docker build 보다 다양한 기능을 제공하는 Docker의 CLI 플러그인입니다. 구글에 올라와있는 다수 게시글에는 Docker Desktop(Windows, Mac)을 통해 Docker buildx를 사용하는 내용이 많은데, 이번 게시글에서는 리눅스 빌드 환경에서 사용하는 예를 소개해드리려고 합니다.
Docker buildx 설치
먼저 공식 가이드에 있는 아래 방법으로 Docker buildx를 다운로드하여 설치합니다.
# 최신 릴리스 확인
https://github.com/docker/buildx/releases/latest
# 다운로드
mkdir -p ~/.docker/cli-plugins/
curl -L https://github.com/docker/buildx/releases/download/v0.6.3/buildx-v0.6.3.linux-amd64 -o ~/.docker/cli-plugins/docker-buildx
chmod 755 ~/.docker/cli-plugins/docker-buildx
# 확인
docker buildx version
# buildx를 기본 build 명령어로 사용하기
docker buildx install
이 상태에서도 docker buildx build 명령어를 통해 이미지를 빌드할 수 있지만, 아직 멀티 플랫폼 빌드를 위한 설정이 완료되지 않았기에 아래 세팅을 먼저 진행합니다.
멀티 플랫폼 빌드 환경 구성을 위한 세팅
x86_64 환경에서 여러 플랫폼의 이미지를 빌드하려면 QEMU 에뮬레이터를 통해 가상의 하드웨어를 에뮬레이팅할 수 있도록 패키지 설치가 필요합니다. 아래 명령어를 통해 필요한 패키지를 모두 설치한 뒤, 인스턴스를 재부팅합니다.
# 요구 사항(커널 버전이 낮아 CentOS7 AMI에서는 불가하여 Amazon Linux 환경에서 테스트)
kernel >= 4.8
binfmt-support >= 2.1.7
# QEMU 관련 패키지 설치(설치 후 재부팅 필요)
yum install -y qemu-user qemu-*-static qemu-kvm-tools qemu-kvm virt-install libvirt libvirt-python libguestfs-tools-c qemu-system-aarch64 glib2-devel pixman-devel
# 멀티 플랫폼 지원 추가(Docker Desktop 환경에서는 설치 필요 없음)
-> 빌드를 지원하는 플랫폼이 추가됨
docker run --privileged --rm tonistiigi/binfmt --install all
지원하는 플랫폼의 종류를 추가하도록 위의 docker run 명령어를 수행 시 아래와 같은 출력이 보여집니다.(참고용)
멀티 플랫폼 빌드 실행
멀티 플랫폼 빌드는 별도의 빌더를 생성해서 진행해야하기에 빌더를 생성하고 활성화합니다.
# 빌더 생성
docker buildx create --use --name my-build
# 생성된 빌더 정보 확인
docker buildx inspect --bootstrap
빌더 생성 후 활성화 시 아래와 같이 보여진다면 정상적으로 빌더가 생성된 것입니다. 만약 Platforms 부분에 아래 이미지와 달리 "linux/amd64, linux/386"만 보여진다면 커널 버전이 낮아 지원하지 않는 것이기 때문에 커널 업데이트 혹은 다른 환경에서 작업이 필요합니다.
이제 이미지 빌드를 실행합니다. docker buildx의 경우 --load(로컬 docker 이미지로 이미지를 로드), --push(원격 이미지 레지스트리로 이미지를 푸쉬)하는 두 가지 옵션 중 하나를 지정하지 않으면 이미지를 빌드까지만 하고 빌드된 이미지를 처리(로드/푸쉬)하지 않습니다. 멀티 플랫폼 이미지를 빌드하는 경우에는 --push 옵션만 지원하는 점에 유의합니다.
또한 --platform 옵션을 통해 어떤 플랫폼의 이미지를 빌드할지 지정합니다. 아래 커맨드에서는 linux/amd64(x86_64), linux/386(x86), linux/arm64(Arm64), linux/arm/v7(Arm) 플랫폼을 지정하였습니다.
docker buildx build --platform=linux/amd64,linux/386,linux/arm64,linux/arm/v7 -t public.ecr.aws/s1v1h3u2/apache-alpine:1.1 ./ --push
아래와 같이 빌드가 진행되며 빌드가 완료되면 태그한 레지스트리(public.ecr.aws/s1v1h3u2/apache-alpine)로 이미지를 푸쉬합니다.
(중략)
아래와 같이 1.1 버전의 이미지가 빌드되어 업로드된 것을 확인할 수 있습니다. "untagged" 라고 태그된 부분은 플랫폼 별 이미지로 위의 커맨드에서 4개 플랫폼의 이미지를 빌드했기 때문에 "untagged" 라고 태그된 이미지가 4개 보이는 것을 확인할 수 있습니다.
이제 빌드한 이미지로 Arm64 환경에서 컨테이너를 구동하여 정상적으로 작동하는지 확인합니다.
docker rm apache-alpine
docker run -d --name apache-alpine -p 80:80 public.ecr.aws/s1v1h3u2/apache-alpine:1.1
아래와 같이 정상적으로 컨테이너가 실행 중인지 확인한 뒤 80 포트를 통해 웹 접속을 해봅니다.
마지막으로 현재 실행되고 있는 컨테이너의 아키텍쳐를 arch 명령어로 확인해봅니다.
결론
위의 과정을 통해 x86_64 환경에서 x86_64, x86, Arm64, Arm 총 4개의 플랫폼에서 실행 가능한 멀티 플랫폼 이미지를 빌드하고 빌드된 이미지가 Arm64 환경에서 정상적으로 작동하는지 확인했습니다.
T4g, M6g, C6g 및 R6g 타입의 경우 기존 Intel, AMD 기반의 x86_64 환경의 인스턴스 타입보다 비용 측면에서 10~20% 저렴하기 때문에 EC2에서 동작하는 Docker 환경이나 EKS 클러스터, ECS 클러스터에서 동작하는 노드(인스턴스)의 환경으로 점차 많이 사용될 것으로 생각됩니다. 때문에 다른 아키텍쳐 환경 간 발생할 수 있는 Docker 이미지 아키텍쳐 이슈에 대처할 수 있는 방법을 미리 숙지하는 것이 도움이 될 것으로 생각합니다.
'docker' 카테고리의 다른 글
Alpine Linux로 Docker 이미지 빌드 (0) | 2023.03.13 |
---|---|
traefik 리버스 프록시 로드밸런서 (1) | 2023.03.06 |
docker swarm (0) | 2023.03.06 |
docker buildx (0) | 2023.03.06 |