😭

M1 MacのDockerでどうにかこうにかProphetを動かせた話

2022/03/16に公開

結論

  • Docker for Macの代わりにlimaを使う
  • limaのインストールには「パッチを当てる…」系の記事が多く出ているが、2022/03/16現在のlimaであれば、brew install limaで入れたものが普通に使える
    • ただし、下記に注意!
      • uname -m → 出力が arm64 であることを確認
        • X86_64 と出る場合、ターミナルがX86_64版で起動している
      • which brew → 出力が /opt/homebrew/bin/brew であることを確認
        • /usr/local/bin/brew と出る場合、X86_64版のhomebrewになっている
        • homebrewにはX86_64版とARM64版があり、X86_64版の方になっていると、limaのインストールには成功するが後段で失敗する
      • 参考: lima公式のissue

経緯

普段はPoetryを利用していて、あまりDockerは使わずにAWSへの展開はできるだけLambda等で済ませる形で開発しているが、先日Prophetを扱うことになった。

とりあえずローカルの検証ではPoetryでProphetが入ったが、Lambdaだとサイズオーバーなので、AWSに展開するためにはEC2に展開するか、ECSを使わないといけない。
(マネージドのAmazon Forecastとかも迷いましたが、今回は用途に合わなかったので不採用。)

EC2は嫌だったので、ECSを使う…となるとDocker化が必須に。
M1チップ出始めの頃にDockerを使おうとして全然うまく行かず、そのとき以来苦手意識がある状態ではあったが、今後もDockerは使っていくことになるだろうということで、Docker化することに決定。
やろうとしたらやっぱりハマりまくったので、今思い出しながら記事を書いている。

ハマりポイント1: docker build中、prophetの依存パッケージであるpystanのインストールで止まる

DockerFileを書いていざdocker buildすると、pystanのinstallが全然終わらない。

謎だったのは「Poetryで入れたローカルのprophetはM1 Mac上で動いているのに、なぜDocker上では動かないのか?」というところ。
Dockerイメージの選択が悪いのか?と思いUbuntuベースにしたり、動いているPoetry環境をコピーして利用(参考: 仕事でPythonコンテナをデプロイする人向けのDockerfile (1): オールマイティ編)したりしようとしたが、変わらず…

調べているとこちらの記事を発見
M1 Pro + Docker for Macが遅い
ではやってみよう、ということで、limaを試すことに。
(この時点で brew uninstall docker --cask した。)

ハマりポイント2: limaでVMが立ち上がらない

※注: このポイントは、M1 Macを買ってイチから環境構築をした人はハマらずに済むはずです。(ARM64版のhomebrewでlimaをinstallできればOK)

先程の記事や、Docker on Limaで脱Docker Desktop for Macなどにある手順に従ってlimaをインストール。
limactl start [設定ファイル名].ymlでVMを立ち上げようとすると

FATA[00xx] exiting, status={Running:false Degraded:false Exiting:true Errors:[] SSHLocalPort:0} (hint: see "/Users/xxx/.lima/xxx/ha.stderr.log")

と出る。

先程の記事の例ではポートが既に専有されていたということだったが、どうもそうでは無かったので、調べていくと「limaが利用しているqemuというプロフェッサエミュレータに、HVFというHypervisor.frameworkを使ったアクセラレータが入っていないとダメ」らしい。(ヨコモジワカラナイ)

qemu-system-aarch64 -accel helpというコマンドでチェックできるということで、確認。

$ qemu-system-aarch64 -accel help
Accelerators supported in QEMU binary:
tcg

hvfと出ないので、これが原因っぽい。

「パッチを当ててインストールする」という記事がいくつか(こちら1こちら2こちら3こちら4)あったので、「最新版ならパッチ不要」という情報も見かけていたが、一応それぞれトライ。が、やっぱりダメ。
(今よく読んだら、こちら3に「If you already installed x86_64 Homebrew in /usr/local, please uninstall it. It’s not possible to build QEMU with x86_64 Homebrew」って書いてますね…気づかなかった…)

しんどい…と思いながら調べていると行き当たったのがこちらのissue。
qemu-system-x86_64: Unknown Error (Was: 60022: Connection refused SSH)
#543

toshitanianさんの気になるコメントを発見。

I didn't know I need to install Homebrew for two different environment of x86 and arm.

これか…?と思い、調べると、この記事に行き当たった。

https://docs.brew.sh/Installation によれば、このスクリプトを動作させると、
macOS Intel では /usr/local に、 macOS Apple Silicon では /opt/homebrew にインストールされる。

チェック。

$ which brew
/usr/local/bin/brew

/usr/local/bin/brewということは、どうやらX86_64版が入ってしまっている!

ハマりポイント3: ターミナルがなぜX86_64で起動しているかわからない

私の場合、Intel版のMacのBackupからM1のMacへ移行していたので、HomebrewがX86_64版のままだった。そのため、X86_64版のlimaがインストールされてしまい、うまく動いていなかったということらしい。

先程のbrewのインストールに関する記事に「インストーラがCPUを識別してうまく動作するようだ。」とあったので、とりあえずbrewを再インストールしてみるが、/usr/local/bin/brewのまま。
なぜ…と思って一応確認すると、

$ uname -m
X86_64

ターミナルがX86_64で起動していた。

Rosetta 2が働いてしまっているのかなー、と思いながら、iTerm2の「情報を見る」も、Rosetta 2を使う設定にはなっていない。
スクショ

ではなぜ…?と調べていると、発見。
Rosettaを解除したのにarm64にならない原因

原因は、親プロセスがx86_64で作動しているからである。親プロセスがx86_64で作動していた場合、そこから発生するコマンドの全てがx86_64で作動する。

なるほど…!
私の場合、X86_64版のHomebrewでインストールしたfishシェルを使っていたので、fishもX86_64版が入っていた。結果、ターミナルがX86_64扱いになってしまっていた。

やっと解決へ…

というわけで、brew uninstall fishしてから公式のインストーラからfishを再インストール。

iTerm2を再起動すると、

$ uname -m
arm64

OK!

homebrewも再インストール。(fish環境でbrew(Homebrew)をインストールする

$ which brew
/opt/homebrew/bin/brew

OK!

あとは先程のこの記事や、この記事などにある手順に従って、dockerを利用する環境が整った。

私の場合はPoetryをそのまま使いたかったので、動いているPoetry環境をコピーして利用する方向でDockerFileを作成。

# 参考:https://future-architect.github.io/articles/20200513/
# ここはビルド用のコンテナ
FROM python:3.9-buster as builder

WORKDIR /opt/app
RUN pip install -U pip poetry
COPY pyproject.toml .
COPY poetry.lock .

RUN poetry config virtualenvs.in-project true && \
    poetry install --no-dev --no-interaction -vvv

# ここからは実行用コンテナの準備
FROM python:3.9-slim-buster as runner

ENV LANG ja_JP.UTF-8
ENV LANGUAGE ja_JP:ja
ENV LC_ALL ja_JP.UTF-8

COPY --from=builder /opt/app/.venv /opt/app/.venv
ENV PATH=/opt/app/.venv/bin:$PATH

WORKDIR /opt/app

# 以降はやりたいように

docker build ...したら、docker run -it ...で中に入り、Prophetが入ったかどうかチェック。

$ python
Python 3.9.10 (main, Mar  1 2022, 21:02:54)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import prophet
>>> prophet.__version__
'1.0'
>>>

OK!!!

これでやっと、使える状態に…!疲れた…

感想

  • CPUアーキテクチャまわりもDockerまわりもVMまわりも何もわからんだったので、しんどみがヤバすぎた
  • 新しくPCを買ったら、開発環境は再構築したほうがいい
    • これやってればハマりポイント2,3がほぼ無かったはず
  • 今後はDockerも使って開発していく…!
  • Docker for Macからの卒業…!

この記事が少しでも皆様のハマりポイント解消の手助けになれば幸いです。

追加の補足: DockerイメージをAWS CDK等でECRにプッシュして利用する場合の注意点

  • docker-credential-desktop not installed or not available in PATH... 的なエラーが出る場合は、~/.docker/config.jsonを削除する必要がある
  • ECSのタスク定義でCPUアーキテクチャを指定できる。Dockerイメージをビルドしたアーキテクチャと同じかどうかを要確認

Discussion