COLOPL Tech Blog

コロプラのエンジニアブログです

Google Cloud の Arm インスタンスで Apple Silicon Mac 用イメージビルドを 30 倍速くしてみた

サーバー基盤グループで SRE として活動している工藤です。

つい先日、 Google Cloud 上で念願の Arm インスタンス T2A が利用可能になりました。まだプレビュー扱いで限られたリージョンでしか利用できませんが、既に Kubernetes Engine も動作します。

cloud.google.com

今回はそんな Google Cloud の Arm インスタンスを試してみました。

広がる Arm アーキテクチャの世界

Arm アーキテクチャといえば、現在販売されているスマートフォンの大半が Arm アーキテクチャの CPU を採用しています。もちろんコロプラのゲームも Arm アーキテクチャ向けにビルドされています。

コンピュータの世界では、 AppleMac に採用した Apple Silicon も Arm アーキテクチャの一つです。 Windows マシンも MicrosoftSurface X が Qualcomm 製の Arm アーキテクチャ CPU 搭載チップセットを採用するなど、近年勢いを増しています。

Arm for Server

Arm の躍進は今やサーバーの世界にも波及し、 Amazon Web Services (以下: AWS) が Graviton で採用したのを皮切りにOracle Cloud や Microsoft Azure も Arm アーキテクチャ CPU のインスタンスを提供しています。IntelAMD などの x86_64 (amd64) アーキテクチャインスタンスに比べ、安価に利用できるのが強みです。

AWSMicrosoft Azure が対応する中、 Google Cloud は Arm サポートについて沈黙を貫いてきましたが、先日ついに T2A インスタンスとして利用できるようになりました。

cloud.google.com

Google Cloud の T2A インスタンスは Ampere Computing 社の Altera CPU を採用しています。 Oracle Cloud や Microsoft Azure と同様です。

もちろん Intel / AMD に比べ安価なのは Google Cloud でも共通しており、 Compute Engine での 1 時間あたりの価格を比べると以下のようになります。

Arch 1 時間あたり 1 時間あたり (spot)
n1-standard-8 (Intel Xeon SKL) x86_64 $0.379998 $0.08
n2-standard-8 (Intel Xeon ICL) x86_64 $0.388472 $0.09416
n2d-standard-8 (AMD EPYC Zen2/3) x86_64 $0.337968 $0.034088
t2a-standard-8 (Ampere Altra) AArch64 $0.308 $0.0924

Pricing  |  Compute Engine: Virtual Machines (VMs)  |  Google Cloud (region: us-central1, 2022-07-15 時点)

安いのはとても良いことです。現在はまだプレビューですが、将来的にはプロダクション環境のコスト削減にも活用できそうです。

Arm インスタンスApple Silicon Mac

前述の通りまだまだサービスに使うには早い感じのある Arm インスタンスですが、弊社ではバックエンドエンジニアのマシンとして MacBook Pro を利用していることもあり、 AppleApple Silicon (Arm アーキテクチャ) へ移行を進めている関係で Arm アーキテクチャ向けのイメージへの需要が高まってきました。

というのも、 Docker Desktop for Mac では QEMU (CPU エミュレータ) を利用して x86_64 向けのイメージを動作させることができますが、これが非常に遅いのです。

どのくらい遅いのか、 PHP 付属の Zend/bench.php を実行してみました。環境は以下の通りです。

  • OS: macOS Monterey 12.4
  • CPU: Apple M1 Pro 8core (6: firestorm, 2: icestorm)
  • RAM: 32GB
  • Docker Desktop: v4.9.1
amd64/php:8.1-cli (QEMU) arm64v8/php:8.1-cli (Native)
simple 0.078 0.012
simplecall 0.030 0.005
simpleucall 0.097 0.015
simpleudcall 0.106 0.013
mandel 1.020 0.049
mandel2 1.074 0.047
ackermann(7) 0.134 0.011
ary(50000) 0.019 0.003
ary2(50000) 0.017 0.002
ary3(2000) 0.331 0.019
fibo(30) 0.404 0.034
hash1(50000) 0.044 0.003
hash2(500) 0.058 0.004
heapsort(20000) 0.185 0.012
matrix(20) 0.184 0.011
nestedloop(12) 0.142 0.010
sieve(30) 0.105 0.006
strcat(200000) 0.035 0.002
Total 4.063 0.259

なんと 15 倍くらいかかっています。流石につらい...

docker buildx + QEMU によるクロスビルド

ではどうするか、というと Arm アーキテクチャネイティブなイメージを作成する必要があります。幸いにも Docker は buildx を用いて異なるアーキテクチャ向けのイメージをビルドすることができるようになっているため、それを利用すれば良い...かに思えました。

実際に n1-standard-8 でイメージをビルドしてみます。今回は検証のため、 DockerHub で公開されている php:8.1-cli-alpine のイメージに対し gRPC 拡張を追加したイメージを焼くことにします (PHP 自体は既にビルドされているのでそこそこ巨大な gRPC を pecl を使ってビルドします)

環境構築手順は省略します (結構ややこしいので需要があればまた別記事で...とりあえずコマンドの羅列だけ残しておきます)

Dockerfile はこんな感じ

FROM php:8.1-cli-alpine

# pecl_mt_install: pecl install 時にマルチコアでビルドするためのおまじない

RUN set -eux \
  && pecl_mt_install() { \
    extension="${1}" \
    && extension_name="${extension%-*}" \
    && shift \
    && temporary="/tmp/pear/temp/${extension_name}" \
    && apk_delete="" \
    && if [ -n "${PHPIZE_DEPS}" ] && ! apk info --installed .phpize-deps > /dev/null; then \
        apk add --no-cache --virtual .phpize-deps ${PHPIZE_DEPS}; \
        apk_delete='.phpize-deps'; \
    fi \
    && if [ -n "${HTTP_PROXY:-}" ]; then \
        pear config-set http_proxy ${HTTP_PROXY}; \
    fi \
    && pecl install --onlyreqdeps --nobuild "${extension}" \
    && cd "${temporary}" \
    && phpize \
    && CFLAGS="${PHP_CFLAGS:-}" CPPFLAGS="${PHP_CPPFLAGS:-}" CXXFLAGS="${PHP_CXXFLAGS:-}" LDFLAGS="${PHP_LDFLAGS:-}" ./configure \
    && make -j$(nproc) \
    && make install \
    && rm -rf "${temporary}" \
    && if [ -n "${apk_delete}" ]; then apk del --purge --no-network ${apk_delete}; fi; \
  } \
  && apk add --no-cache "linux-headers" "zlib-dev" \
  && pecl_mt_install "grpc"

結果は以下のような感じ

$ time sudo docker buildx build --platform "linux/arm64" .
(省略)
                                                                                                               
real    88m59.975s                                                                                             
user    0m0.558s                                                                                               
sys     0m1.395s 

びっくりするくらいかかりました。つらい...

GCE T2A を用いたネイティブビルド

次に t2a-standard-8 でネイティブにビルドしてみます。
環境構築手順は同様に省略

結果はこうなりました

$ time sudo docker build .
(省略)
Successfully built 667e8e7d7b00

real    3m33.002s
user    0m0.033s
sys     0m0.088s

は、速い...!さすがネイティブ...!

速度・費用比較

比較してみると差は歴然です。あまりにかかるので n1-highcpu-32 でも計測してみました。

Arch コア数 ビルド時間 概算費用 ※1 概算費用 ※2
n1-standard-8 x86_64 8 88m59.975s $33.819822 (4565.67597 円) $7.12 (961.2 円)
n1-highcpu-32 x86_64 32 29m28.323s $34.007328 (4590.98928 円) $7.159488 (966.53088 円)
t2a-standard-8 AArch64 8 3m33.002s $1.232 (166.32 円) $0.3696 (49.896 円)

※1 : us-central1, 非プリエンプティブ, 分単位切り上げを 1USD = 135JPY で換算した場合
※2 : us-central1, プリエンプティブ, 分単位切り上げを 1USD = 135JPY で換算した場合

やはり圧倒的な差です。コストはプリエンプティブインスタンスを使えば大幅に下げられるとはいえ、それでも大差がついています。

まとめ

Arm インスタンスはサービスのインフラ費用を低減させるだけでなく、 Arm 化の進むソフトウェアエンジニアの開発環境構築においても非常に喜ばしいものだということがわかりました。

今回は Compute Engine での検証となりましたが、今後 Cloud Build でも T2A インスタンスが利用できるようになることでより利用しやすくなり、 CI / CD の選択肢も広がってくることでしょう。

また Arm インスタンスはそもそもの費用が低いということもあり、 CPU より I/O バウンドな Web アプリケーションのリソースとして非常に魅力的な選択肢になりうると思えます。 GA が待ち遠しいですね。