주희아빠의 헝그리 라이딩

ARM 환경을 Docker 로 구성해 보자 본문

나름 IT 이야기

ARM 환경을 Docker 로 구성해 보자

도림천 버섯돌이 2023. 12. 1. 18:47

2023.12.5

1. 개요

docker 환경으로 독립된 개발 환경을 구성해 놓았는데 이것은 x86 을 위한 것이었습니다. 무심코 '라즈베리파이' 쪽에서 실행을 했는데 에러가 나더군요. 내부에서 돌아가는 코드가 x86 용 바이너리일테니 arm 에서는 에러가 나는게 당연하다고 생각을 했습니다. 

간단하게 시험을 해봤습니다. docker 환경은 이미 구성되어 있다고 가정합니다. 설치는 뒷 부분에서 다루겠습니다.

-- arm 용 라즈베리파이 OS 인 '라즈비안' 을 실행해 봅니다.
$ docker run -it navikey/raspbian-buster /bin/bash
Unable to find image 'navikey/raspbian-buster:latest' locally
latest: Pulling from navikey/raspbian-buster
4bedcfaf3e9f: Pull complete
Digest: sha256:e966cdb354444f1eb9fbb333df56bd63cc18b965254818173a1ec3cba63a2e81
Status: Downloaded newer image for navikey/raspbian-buster:latest
exec /bin/bash: exec format error

 

/bin/bash 쉘이 실행되지 않고 에러가 납니다. 이게 당연한거 아닌가?

그런데 또 생각을 해보니, 요즘에는 aws 같은 cloud 환경에서도 arm 을 지원하고 서비스용으로 docker(k8s) 도 많이 이용을 하는데 배포되는 플랫폼에 따라서 패키징을 따로 해야한다고 생각하니 뭔가 docker 의 사상과 맞지 않는 듯 싶었습니다.

그래서 좀 더 고민을 하다가 인터넷을 찾아보니 arm 환경용으로 패키징을 하기도 하지만 x86 으로 패키징을 한 것 역시 arm 에서 동작이 가능하다고 합니다. 잉? 정말?

다음 명령을 통해 현재 지원 가능한 플랫폼을 확인하고 추가로 설치를 할 수 있습니다.

-- 현재 실행 가능한 플랫폼 확인
$ docker buildx ls
NAME/NODE DRIVER/ENDPOINT STATUS  BUILDKIT             PLATFORMS
default * docker
  default default         running v0.11.7+d3e6c1360f6e linux/amd64, linux/amd64/v2, linux/386

$ docker buildx inspect --bootstrap
Name:   default
Driver: docker

Nodes:
Name:      default
Endpoint:  default
Status:    running
Buildkit:  v0.11.7+d3e6c1360f6e
Platforms: linux/amd64, linux/amd64/v2, linux/386
Labels:
$


-- 실행 가능한 플랫폼 환경을 추가로 설치해 줍니다.
$ docker run --privileged --rm tonistiigi/binfmt --install all
Unable to find image 'tonistiigi/binfmt:latest' locally
latest: Pulling from tonistiigi/binfmt
8d4d64c318a5: Pull complete
e9c608ddc3cb: Pull complete
Digest: sha256:66e11bea77a5ea9d6f0fe79b57cd2b189b5d15b93a2bdb925be22949232e4e55
Status: Downloaded newer image for tonistiigi/binfmt:latest
installing: arm64 OK
installing: ppc64le OK
installing: riscv64 OK
installing: arm OK
installing: s390x OK
installing: mips64le OK
installing: mips64 OK
{
  "supported": [
    "linux/amd64",
    "linux/arm64",
    "linux/riscv64",
    "linux/ppc64le",
    "linux/s390x",
    "linux/386",
    "linux/mips64le",
    "linux/mips64",
    "linux/arm/v7",
    "linux/arm/v6"
  ],
  "emulators": [
    "WSLInterop",
    "WSLInterop-late",
    "qemu-aarch64",
    "qemu-arm",
    "qemu-mips64",
    "qemu-mips64el",
    "qemu-ppc64le",
    "qemu-riscv64",
    "qemu-s390x"
  ]
}

-- 다시 한번 확인해 보면 많이 늘었네요.
$ docker buildx ls
NAME/NODE DRIVER/ENDPOINT STATUS  BUILDKIT             PLATFORMS
default * docker
  default default         running v0.11.7+d3e6c1360f6e linux/amd64, linux/amd64/v2, linux/386, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/mips64le, linux/mips64, linux/arm/v7, linux/arm/v6

 

아까 실패했던 라즈비안을 다시 호출해 봅니다. 오, 실행이 되었습니다. 반응 속도 역시 지난번 qemu 로 돌렸던 '라즈베리파이' 에뮬레이터에 비하면 하늘과 땅 차이입니다.

$ docker run -it navikey/raspbian-buster /bin/bash
root@a7825a0b5330:/# arch
armv7l

root@a7825a0b5330:/# cat /etc/os-release
PRETTY_NAME="Raspbian GNU/Linux 10 (buster)"
NAME="Raspbian GNU/Linux"
VERSION_ID="10"
VERSION="10 (buster)"
VERSION_CODENAME=buster
ID=raspbian
ID_LIKE=debian
HOME_URL="http://www.raspbian.org/"
SUPPORT_URL="http://www.raspbian.org/RaspbianForums"
BUG_REPORT_URL="http://www.raspbian.org/RaspbianBugs"

 

너무나도 손쉽게 arm 환경을 얻었습니다.

어떻게 이런일이 가능한 것일까요? docker 에서 내부적으로 qemu 를 사용하여 cpu 환경을 에뮬레이션 해준다고 합니다. 지난번 공부한 qemu 를 이렇게 만나니 정말로 반갑군요.

실제로 쉘안에서 ps 정보를 보면 다음처럼 qemu-arm 이 떠 있는 것을 볼 수 있습니다. 이 qemu 는 WSL 에 설치된 버전이 아니라 docker 에서 제공되는 것입니다.

root@a7825a0b5330:/# ps -ef
UID          PID    PPID  C STIME TTY          TIME CMD
root           1       0  0 12:58 pts/0    00:00:00 /usr/bin/qemu-arm /bin/bash /bin/bash
root          13       1  0 13:00 ?        00:00:00 ps -ef
root@a7825a0b5330:/#

 

docker 를 통해 다른 플랫폼의 바이너리를 실행시킬 수는 있지만 본격적으로 사용자 서비스를 하는 곳이라면 추가적인 부하가 생긴다는 것은 감수를 해야 할 듯 합니다.

라즈비안은 사실 본격적인 개발환경으로는 별로 좋아 보이지는 않습니다. arm 64bit 에서 돌아가는 ubuntu 22.04 환경을 한번 구성해 보겠습니다.

-- 명시적으로 platform 옵션을 통해 실행되는 아키텍처를 지정해 줄 수 있습니다.
$ docker run -it --platform=arm64 ubuntu:latest /bin/bash
Unable to find image 'ubuntu:latest' locally
latest: Pulling from library/ubuntu
32ba3a3141d2: Pull complete
Digest: sha256:8eab65df33a6de2844c9aefd19efe8ddb87b7df5e9185a4ab73af936225685bb
Status: Downloaded newer image for ubuntu:latest

root@12f37a0bc585:/# arch
aarch64

root@12f37a0bc585:/# cat /etc/os-release
PRETTY_NAME="Ubuntu 22.04.3 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04.3 LTS (Jammy Jellyfish)"
VERSION_CODENAME=jammy
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=jammy

root@12f37a0bc585:/# ps -ef
UID          PID    PPID  C STIME TTY          TIME CMD
root           1       0  0 13:08 pts/0    00:00:00 /usr/bin/qemu-aarch64 /bin/bash /bin/bash
root        1146       1  0 13:12 ?        00:00:00 ps -ef

 

그대로 사용해도 되지만 docker 로 설치되는 버전은 너무 최소로만 설치되기 때문에 약간의 기본작업을 해줍니다.

-- 기본으로 한번 때려줍니다. 설치된게 없어서 업데이트도 거의 없습니다.
root@12f37a0bc585:/# apt update && apt upgrade

-- 몇가지 패키지를 설치해 줍니다.
root@12f37a0bc585:/# apt install sudo vim file


-- root 를 사용하는게 이뻐보이지 않으니 일반 유저를 등록해 줍니다.
root@12f37a0bc585:/# adduser multitab
Adding user `multitab' ...
Adding new group `multitab' (1000) ...
Adding new user `multitab' (1000) with group `multitab' ...
Creating home directory `/home/multitab' ...
Copying files from `/etc/skel' ...
New password:
Retype new password:
passwd: password updated successfully
Changing the user information for multitab
Enter the new value, or press ENTER for the default
        Full Name []: 이름
        Room Number []:
        Work Phone []:
        Home Phone []:
        Other []:
Is the information correct? [Y/n]


root@12f37a0bc585:/# vi /etc/sudoers
-- 아래 부분에 유저를 추가해 줍니다. : w! 로 강제 저장하고 나옵니다.
--# User privilege specification
--root    ALL=(ALL:ALL) ALL
--multitab        ALL=(ALL:ALL) ALL

-- 일반 유저로 변신
root@12f37a0bc585:/# su - multitab
multitab@12f37a0bc585:~$ 

-- 이후 사용은 일반 ubuntu 와 같습니다.
multitab@12f37a0bc585:~$ sudo apt install build-essential
[sudo] password for multitab:
Reading package lists... 48%

multitab@12f37a0bc585:~$ cc --version
cc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0
Copyright (C) 2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

multitab@12f37a0bc585:~$ file /bin/bash
/bin/bash: ELF 64-bit LSB pie executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, BuildID[sha1]=4dadac332a3aaef2b0eca910734ed6f8834d0b9b, for GNU/Linux 3.7.0, stripped
multitab@12f37a0bc585:~$

 

wsl2  보다 다양한 버전의 리눅스와 gcc 등으로 입맛에 맞게 구성할 수 있습니다. 다만 이렇게 수동으로 구성한 docker 는 빠져 나오면 없어져 버리니 주의해야 합니다. -_-; 뭬이야?

Dockerfile 을 통해 위 명령을 스크립트로 구성하면 고정적인 개발머신으로 운영할 수 있습니다. 

dockerbuild 를 위한 Dockerfile 을 생성해 줍니다. 사용할 이미지 이름과 기본적인 설치 패키지를 적어줍니다. 필수는 아니지만 유저 등록과 sudo 등록해 해 주었습니다.

$ vi Dockerfile
FROM ubuntu:22.04

RUN apt update && apt install -y \
    build-essential \
    sudo \
    vim

RUN useradd -d /home/multitab -m multitab -s /bin/bash
RUN echo multitab:your_password | chpasswd
RUN echo "multitab  ALL=(ALL:ALL) ALL" >> /etc/sudoers

 

크로스 플랫폼 구성을 위한 내용이므로 docker buildx build 명령을 수행해 줍니다. --platform=linux/arm64 옵션을 통해 구성할 플랫폼 종류를 지정해 줄 수 있습니다. 여러 종류를 한번에 줄 수도 있는데 약간 더 복잡해지더군요. 이번엔 생략.

저는 여러번 시도를 해서 시간이 짧아 보이는데 처음 수행할 때는 패키지 설치하느라 시간이 약간 걸립니다.

$ docker buildx build --platform=linux/arm64 -t ubuntu-22.04 .
[+] Building 1.3s (9/9) FINISHED                                                 docker:default
 => [internal] load build definition from Dockerfile                                       0.1s
 => => transferring dockerfile: 289B                                                       0.0s
 => [internal] load .dockerignore                                                          0.1s
 => => transferring context: 2B                                                            0.0s
 => [internal] load metadata for docker.io/library/ubuntu:22.04                            0.9s
 => [1/5] FROM docker.io/library/ubuntu:22.04@sha256:c340a09ae7e54d8eabe1e5c68a719f13bc36  0.1s
 => => resolve docker.io/library/ubuntu:22.04@sha256:c340a09ae7e54d8eabe1e5c68a719f13bc36  0.1s
 => CACHED [2/5] RUN apt update && apt install -y     build-essential     sudo     vim     0.0s
 => CACHED [3/5] RUN useradd -d /home/multitab -m multitab -s /bin/bash                    0.0s
 => CACHED [4/5] RUN echo multitab:your_password | chpasswd                                0.0s
 => CACHED [5/5] RUN echo "multitab  ALL=(ALL:ALL) ALL" >> /etc/sudoers                    0.0s
 => exporting to image                                                                     0.0s
 => => exporting layers                                                                    0.0s
 => => writing image sha256:eed49702be6407ebc804a300ded22e5f21b73a6e249c9479ed3b9f3a1bb52  0.0s
 => => naming to docker.io/library/ubuntu-22.04                                            0.0s
multitab@MULTITAB-T420S:~/tmp3$

 

생성된 docker images 에 접속을 해 봅니다. 다음처럼 정보를 확인해 볼 수 있습니다. arch64 아키텍처이고 gcc 11.4.0 버전이 설치된 것을 확인할 수 있습니다.

$ docker run -it ubuntu-22.04 /bin/bash
WARNING: The requested image''s platform (linux/arm64) does not match the detected host platform (linux/amd64/v2) and no specific platform was requested
root@7f8151b68b52:/# su - multitab

multitab@7f8151b68b52:~$ arch
aarch64

multitab@7f8151b68b52:~$ cat /etc/os-release
PRETTY_NAME="Ubuntu 22.04.3 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04.3 LTS (Jammy Jellyfish)"
VERSION_CODENAME=jammy
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=jammy

multitab@7f8151b68b52:~$ cc -v
Using built-in specs.
COLLECT_GCC=cc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/aarch64-linux-gnu/11/lto-wrapper
Target: aarch64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 11.4.0-1ubuntu1~22.04' --with-bugurl=file:///usr/share/doc/gcc-11/README.Bugs --enable-languages=c,ada,c++,go,d,fortran,objc,obj-c++,m2 --prefix=/usr --with-gcc-major-version-only --program-suffix=-11 --program-prefix=aarch64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --enable-bootstrap --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-libquadmath --disable-libquadmath-support --enable-plugin --enable-default-pie --with-system-zlib --enable-libphobos-checking=release --with-target-system-zlib=auto --enable-objc-gc=auto --enable-multiarch --enable-fix-cortex-a53-843419 --disable-werror --enable-checking=release --build=aarch64-linux-gnu --host=aarch64-linux-gnu --target=aarch64-linux-gnu --with-build-config=bootstrap-lto-lean --enable-link-serialization=2
Thread model: posix
Supported LTO compression algorithms: zlib zstd
gcc version 11.4.0 (Ubuntu 11.4.0-1ubuntu1~22.04)
multitab@7f8151b68b52:~$

 

위처럼 사용을 해도 좋지만 docker 내부에서 수정한 파일들은 docker 내부에서만 영향을 미치게 됩니다. 즉, 뭔가 만들어진 결과물을 밖으로 빼오기도 귀찮습니다. 다음처럼 디스크 볼륨을 연결해 주면 docker 내부에서 수정한 파일들을 밖에서도 그대로 사용할 수 있습니다.

$ docker run -it --privileged -v /home/multitab/tmp:/home/multitab/tmp ubuntu-22.04 /bin/bash

root@81aee52f1f8e:/# su - multitab

multitab@81aee52f1f8e:~$ cd tmp
multitab@81aee52f1f8e:~/tmp$ ll
total 52296
drwxr-xr-x 15 multitab multitab    4096 Dec 16 09:07 ./
drwxr-x---  1 multitab multitab    4096 Dec 16 09:33 ../
drwxr-xr-x 14 multitab multitab    4096 Nov 16 11:26 SDL-1.2/
drwxr-xr-x 16 multitab multitab    4096 Nov 27 13:34 buildroot-2020.05.3/
-rw-rw-r--  1 multitab multitab 6607875 Oct 12  2020 buildroot-2020.05.3.tar.gz
drwxr-xr-x 16 multitab multitab    4096 Nov 27 14:45 buildroot-2020.08.1/
-rw-rw-r--  1 multitab multitab 6687472 Oct 12  2020 buildroot-2020.08.1.tar.gz
drwxr-xr-x 16 multitab multitab    4096 Nov 27 13:44 buildroot-2020.08.3/
-rw-rw-r--  1 multitab multitab 6730659 Dec 27  2020 buildroot-2020.08.3.tar.gz
drwxr-xr-x 16 multitab multitab    4096 Nov 18 14:52 buildroot-2023.02.6/
-rw-r--r--  1 multitab multitab 7249412 Oct 16 08:54 buildroot-2023.02.6.tar.gz
drwxr-xr-x 16 multitab multitab    4096 Nov 18 11:23 buildroot-2023.02.7/
-rw-r--r--  1 multitab multitab 7269977 Nov 14 19:54 buildroot-2023.02.7.tar.gz
drwxr-xr-x 16 multitab multitab    4096 Nov 19 06:44 buildroot-arm/
drwxrwxr-x 14 multitab multitab    4096 Feb 15  2021 buildroot-f45925a951318e9e53bead80b363e004301adc6f/
-rw-rw-r--  1 multitab multitab 4846412 Nov 27 14:44 buildroot-f45925a951318e9e53bead80b363e004301adc6f.tar.bz2
drwxr-xr-x 16 multitab multitab    4096 Dec 16 09:11 buildroot-x86/
drwxr-xr-x  7 multitab multitab   20480 Nov 14 12:16 openssh-8.9p1/
-rw-r--r--  1 multitab multitab 1820282 Feb 23  2022 openssh-8.9p1.tar.gz
drwxr-xr-x 19 multitab multitab    4096 Nov 14 12:40 openssl-1.1.1w/
-rw-r--r--  1 multitab multitab 9893384 Sep 11 14:46 openssl-1.1.1w.tar.gz
drwxr-xr-x  9 multitab multitab    4096 Nov 27 23:58 prboom-2.5.0/
-rw-r--r--  1 multitab multitab 1049505 Nov  9  2008 prboom-2.5.0.tar.gz
-rw-rw-r--  1 multitab multitab       0 Dec 16 09:07 test.t
drwxr-xr-x 14 multitab multitab    4096 Nov 14 12:28 zlib-1.3/
-rw-r--r--  1 multitab multitab 1295740 Aug 18 10:08 zlib-1.3.tar.xz
multitab@81aee52f1f8e:~/tmp$

 

 

3. 아직 없으신 분을 위한 Docker 설치 안내

환경:  x86 이고 Ubuntu 22.04 LTS 에서 진행하였습니다.  Windows 10 에 있는 WSL2 환경입니다. 

          예전에는 WSL2 에서도 docker 기동에 문제가 있던 것으로 아는데 이번에는 별 이슈 없이 실행이 잘되네요.

          https://docs.docker.com/engine/install/ubuntu/  ubuntu 외에도 여러 환경에 대한 설치 안내

-- 참고 사이트. 다른 배포판을 위한 정보도 많이 있습니다.
-- https://docs.docker.com/engine/install/ubuntu/

-- Add Docker''s official GPG key:
$ sudo apt-get update
$ sudo apt-get install ca-certificates curl gnupg
$ sudo install -m 0755 -d /etc/apt/keyrings
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
$ sudo chmod a+r /etc/apt/keyrings/docker.gpg

-- Add the repository to Apt sources:
$ echo \
  "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
$ sudo apt-get update

-- 설치
$ sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

-- 설치후 hello-world 로 잘 나오는지 확인해 봅니다.
$ sudo docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
719385e32844: Pull complete
Digest: sha256:c79d06dfdfd3d3eb04cafd0dc2bacab0992ebc243e083cabe208bac4dd7759e0
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.
...중략...

-- docker 명령어를 sudo 없이 실행 가능하게 해줍니다. 재로그인 이후 적용됩니다.
$  sudo usermod -aG docker $USER

hello-world 실행 결과

 

4. 마무리

x86 과 arm 을 오가며 쉽게 테스트 환경을 구성할 수 있으며 gcc 버전이나 glibc 버전 등도 변경해 가면서 테스트 할 수 있습니다. glibc 링크는 아차하는 순간 os 한벌 버리게 되고 복구도 힘들지요. 

docker 의 가장 큰 장점인 적은 용량으로 필요한 구성을 최소로 할 수 있으며 환경 구성중 잘못된 경우 쉽게 버리고 다시 만들 수도 있습니다.

docker 를 활용하여 유용한 개발 환경 구성하시기 바랍니다.

반응형