64bit ARM チップを積んだ Edge device 用に Docker image をビルドする


この記事は ABEJA Platform Advent Calendar の 13 日目の記事です。

昨日までの Advent Calendar では、比較的 ABEJA Platform を利用する内容の記事が多かったので、本記事では、ABEJA Platform を開発するのにこんなことしてますよ(の一部)という内容を書きます。
以前、ABEJA Arts Blog にてJetson 上で Docker イメージをビルドするのが辛かったので EC2 上にビルド環境を作った という話を書きましたが、今回はその記事の焼き直しです。

AWS EC2 A1 インスタンスファミリー

焼き直し記事を書かざるを得なかった理由がこれです。
そう、AWS EC2 に、ARM ベースのインスタンスファミリーが登場したので、以前書いた記事は要らない子になってしまったのでした。
というわけで今回は、A1 インスタンスを使った ARM 用 Docker image 作成方法について記載することにしました。

A1インスタンスセットアップ

choose ami for arm

AMI の検索窓にarmと入力すれば、arm用のamiを見つけられます。

arm 用の AMI を選択すると、「Choose an Amazon Machine Image」ページでも、A1インスタンスファミリーのみが選択できるようになってますね。当然といえば当然ですが、地味に便利。

起動したインスタンスのアーキテクチャを見てみると、 aarch64 となっていて、間違いなく 64bit ARM であることがわかります。

command-line
$ uname -m
aarch64

Docker CE のインストール(必要な場合)

選択した AMI によっては、 docker がインストールされていない場合があります。 ubuntu 16.04 とか。
せっかくなので最新の docker をインストールしたくなりますが、Docker の Documentation(たとえば Ubuntu)を見ると、aarch64(arm64) について記載がなく、まさかの docker のビルドからか、、、と一気にモチベーションが下がりますが、実は download.docker.com に aarch64 用のバイナリパッケージも配置してありました。
Ubuntu 16.04 用なら、 https://download.docker.com/linux/ubuntu/dists/xenial/pool/stable/arm64/ 配下に deb パッケージがあります。

あとは、 sudo usermod -aG docker ubuntu して logout/login すれば、 sudo 不要で ubuntu ユーザーから docker コマンドを利用できるようになります。

パフォーマンス比較してみる

せっかくなので、以前まで使っていた QEMU 環境とのパフォーマンス比較もしてみました。
比較環境としては、それぞれ以下のインスタンスを利用しました。

  • QEMU 環境側は c5.4xlarge
  • A1 側は a1.4xlarge

コンピュート最適化インスタンスである C5 インスタンスファミリーから、 A1 インスタンスファミリー最多 vCPU を誇る a1.4xlarge と同じ vCPU 数の c5.4xlarge をチョイス。
Docker image のビルド用に作った環境なので、 docker build にかかる時間を比較します。
比較に利用する Dockerfile は、先日業務で必要になった llvmlite ビルド用の Dockerfile をほぼそのまま使いました。

Dockerfile
FROM arm64v8/ubuntu:16.04                # for a1 instance
FROM multiarch/ubuntu-core:arm64-xenial  # for c5 instance
WORKDIR /tmp

ARG CPU_COUNT=16
ARG LLVM_VERSION=6.0.1
ARG LLVMLITE_VERSION=0.25.0

##############################################################################
# base tools
##############################################################################
RUN apt-get update \
    && apt-get install -y --no-install-recommends software-properties-common \
        unzip \
        wget \
        xz-utils \
        g++ \
        make \
        cmake \
        libedit-dev \
        patch \
    && add-apt-repository ppa:deadsnakes/ppa \
    && apt update \
    && apt install -y --no-install-recommends \
        python3.6 \
        python3.6-dev \
    && wget -O ~/get-pip.py https://bootstrap.pypa.io/get-pip.py \
    && python3.6 ~/get-pip.py \
    && ln -s /usr/bin/python3.6 /usr/local/bin/python3 \
    && ln -s /usr/bin/python3.6 /usr/local/bin/python \
    && rm ~/get-pip.py \
    && python -m pip --no-cache-dir install --upgrade setuptools \

##############################################################################
# build llvm
##############################################################################
    && wget https://github.com/numba/llvmlite/archive/v${LLVMLITE_VERSION}.zip \
    && unzip v${LLVMLITE_VERSION}.zip \
    && wget http://releases.llvm.org/${LLVM_VERSION}/llvm-${LLVM_VERSION}.src.tar.xz \
    && tar xf llvm-${LLVM_VERSION}.src.tar.xz \
    && cd llvm-${LLVM_VERSION}.src \
    && patch include/llvm/Analysis/CFGPrinter.h /tmp/llvmlite-${LLVMLITE_VERSION}/conda-recipes/twine_cfg_undefined_behavior.patch \
    && sed -i -e "/^# CPU_COUNT=1$/a _cmake_config\+=\(-DLLVM_TARGETS_TO_BUILD=\"ARM;X86;AArch64\"\)" /tmp/llvmlite-${LLVMLITE_VERSION}/conda-recipes/llvmdev/build.sh \
    && export CPU_COUNT=${CPU_COUNT} \
    && chmod 755 /tmp/llvmlite-${LLVMLITE_VERSION}/conda-recipes/llvmdev/build.sh \
    && /tmp/llvmlite-${LLVMLITE_VERSION}/conda-recipes/llvmdev/build.sh \

##############################################################################
# build llvmlite
##############################################################################
    && cd /tmp/llvmlite-${LLVMLITE_VERSION} \
    && python setup.py bdist_wheel

LLVM_TARGETS_TO_BUILD に X86 とか入ってますが気にしないでください。
make じゃなくて ninja でしょ、とか clang 使おうぜ、とかも見なかったことにしていただきたく。
あと、FROM 行を 2 行書いているのは、コメントでおわかりの通り、環境によって切り替えています。

上記 Dockerfile を、以下のコマンドで3回実行し、 real の時間で比較します。

command-line
$ time docker build -t llvmlite:latest -f Dockerfile --no-cache .

計測結果がこちら。

instance type 1回目 2回目 3回目 平均 時間単価(Oregon)
a1.4xlarge 15m13.276s 15m57.977s 15m22.979s 15m31.411s $0.408/h
c5.4xlarge 58m 14.567s 58m54.770s 59m42.746s 58m57.361s $0.68/h

なんと a1.4xlarge の方が 4倍近く速いという結果に。
それでいてお値段は 2/3 以下。

c5.18xlarge とか使えば、単純計算では a1.4xlarge を越えそうですが、 Dockerfile 内の処理が常にマルチコアを有効に使えるわけではないし、単価も跳ね上がるので、 64bit ARM 用の 作業をするなら A1 インスタンスファミリーが最適解かと思います。

まとめ

  • 64bit ARM 向けの Docker image のビルドは A1 インスタンスファミリーを使いましょう
  • でもわざわざビルドしなくても ABEJA Platform ならビルド済みのイメージがイロイロ揃ってますよ(宣伝)