一つの Dockerfile でホストに応じたアーキテクチャ (amd64 や arm64) に対応した環境変数を持つイメージを作成する方法
tl;dr
ビルド時に自身で引数を加えなくても、x86 や arm などのアーキテクチャに応じた任意の環境変数を設定するある程度見通しの立てやすい素朴な方法がある。
https://zenn.dev/niku/scraps/fd6e620a0d374b#comment-b8254b6408b7cb を参照のこと。
やりたいこと
ローカル環境向け開発環境用 Dockerfile で docker build で構築した image の環境変数 LD_PRELOAD
に
/usr/lib/x86_64-linux-gnu/libjemalloc.so.2
か
/usr/lib/aarch64-linux-gnu/libjemalloc.so.2
どちらかを設定したい。
docker build のパラメータを変更/追加する必要なくビルドしたい。
環境変数の x86_64
と aarch64
は、ビルドしているホストに応じて自動的に切り替わってほしい。
環境変数は Dockerfile の ENV
で切り替えたい。
これら全てを満たすやり方はあるか探索する。
ビルドは最新の docker desktop で実施するので、docker desktop でデフォルトビルダーとなっている BuildKit の機能を使ってよい。
docker build 時のコマンドに --build-arg
をつければ、以下のように変更は可能。
今回は VSCode の dev container で使いたい。そのときにコマンド発行の引数を変更や追加することはできるかもしれない(調べていない)が、設定後忘れてしまったり変更のときに調べなおす苦労を考えるとやりたくはない。
Dockerfile
FROM debian
ARG MY_LD_PRELOAD
ENV LD_PRELOAD=${MY_LD_PRELOAD}
# echo on building
RUN echo "LD_PRELOAD=${LD_PRELOAD}"
# echo on running
CMD echo "LD_PRELOAD=${LD_PRELOAD}"
ビルド、実行
/usr/lib/x86_64-linux-gnu/libjemalloc.so.2
設定のとき
> docker build . --build-arg="MY_LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.2" --no-cache -t case1
(...snip...)
=> [2/2] RUN echo "LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.2"
(...snip...)
> docker run case1
ERROR: ld.so: object '/usr/lib/x86_64-linux-gnu/libjemalloc.so.2' from LD_PRELOAD cannot be preloaded (cannot open shared object file): ignored.
LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.2
/usr/lib/aarch64-linux-gnu/libjemalloc.so.2
設定のとき
> docker build . --build-arg="MY_LD_PRELOAD=/usr/lib/aarch64-linux-gnu/libjemalloc.so.2" --no-cache -t case1
(...snip...)
=> [2/2] RUN echo "LD_PRELOAD=/usr/lib/aarch64-linux-gnu/libjemalloc.so.2"
(...snip...)
> docker run case1
ERROR: ld.so: object '/usr/lib/aarch64-linux-gnu/libjemalloc.so.2' from LD_PRELOAD cannot be preloaded (cannot open shared object file): ignored.
LD_PRELOAD=/usr/lib/aarch64-linux-gnu/libjemalloc.so.2
環境変数の x86_64
と aarch64
は、ビルドしているホストに応じて自動的に切り替わってほしい。
ビルドしているホストの情報は ARG から取得できる。
ただホストの情報は x86_64
や aarch64
ではなく amd64
や arm64
という名前で取れてくる。
それを Dockerfile 上で変換し RUN や CMD の shell の中で利用する方法はあるが ENV に設定する方法は存在しない ようだ。
ENV で設定できないと心配なのは、以下の例にあるように、通常の docker run 利用時は環境変数を設定しておけるものの、docker run -it /bin/bash などでコマンドを指定して docker run した場合には環境変数に設定されず、デバッグ時など思わぬタイミングでハマる点だ。
起動時にログインシェルが読み込む .bash_profile などのファイルへ、ビルド時に環境変数を書きこんでおくという方法もあるが、コンテキストがわかっていないと何故そうしているか推測が困難で、かつ少ない行数で簡潔な説明が難しいため、読み手にとって Dockerfile の解釈が難しくなってしまうというデメリットがあり避けたい。
Dockerfile
FROM debian
ARG TARGETARCH
ENV TARGETARCH=${TARGETARCH}
# echo on building
RUN echo "TARGETARCH=${TARGETARCH}"
RUN sh -c " \
case ${TARGETARCH} in \
arm64) \
echo \"LD_PRELOAD=/usr/lib/aarch64-linux-gnu/libjemalloc.so.2\" \
;; \
amd64) \
echo \"LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.2\" \
;; \
esac \
"
# echo on running
CMD sh -c " \
case ${TARGETARCH} in \
arm64) \
echo \"LD_PRELOAD=/usr/lib/aarch64-linux-gnu/libjemalloc.so.2\" \
;; \
amd64) \
echo \"LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.2\" \
;; \
esac \
"
ビルド、実行
> docker build . --progress plain --no-cache -t case2
(...snip...)
#6 0.088 LD_PRELOAD=/usr/lib/aarch64-linux-gnu/libjemalloc.so.2
(...snip...)
> docker run case2
LD_PRELOAD=/usr/lib/aarch64-linux-gnu/libjemalloc.so.2
> docker run -it case2 /bin/bash
root@ba5d7f627d37:/# env | grep LD_PRELOAD
root@ba5d7f627d37:/#
全てを満たす方法はあるか?ある。
Multi-stage buildsとBuildKitから自動的に渡されるARGの2つの機能を組み合わせるとDockerfile上でうまく行える。
Dockerfile の FROM 句に ARG の変数を利用できることが、このやり方のポイントとなっている。
https://github.com/docker/buildx/issues/805#issuecomment-946478949 や
https://github.com/moby/buildkit/issues/4168 など buildkit の issue / PR の中でも複数回出てくる方法なので有名なハックなのだろうと考えられる。
Dockerfile
順番としてはまず FROM my-${TARGETARCH}
が評価される。今回はホストが arm な環境でビルド実施したので、依存先の my-arm64
がビルドされる。そのビルド時に ENV を設定している。
これにより amd64
や arm64
から x86_64
や aarch64
への読み替えが行えている。
FROM debian AS base
ARG TARGETARCH
FROM base AS my-amd64
ENV LD_PRELOAD /usr/lib/x86_64-linux-gnu/libjemalloc.so.2
FROM base AS my-arm64
ENV LD_PRELOAD /usr/lib/aarch64-linux-gnu/libjemalloc.so.2
FROM my-${TARGETARCH}
# echo on building
RUN echo "LD_PRELOAD=${LD_PRELOAD}"
# echo on running
CMD echo "LD_PRELOAD=${LD_PRELOAD}"
ビルド、実行
> docker build . --progress plain --no-cache -t case3
(...snip...)
#5 0.097 LD_PRELOAD=/usr/lib/aarch64-linux-gnu/libjemalloc.so.2
(...snip...)
> docker run case3
ERROR: ld.so: object '/usr/lib/aarch64-linux-gnu/libjemalloc.so.2' from LD_PRELOAD cannot be preloaded (cannot open shared object file): ignored.
LD_PRELOAD=/usr/lib/aarch64-linux-gnu/libjemalloc.so.2
> docker run -it case3 /bin/bash
ERROR: ld.so: object '/usr/lib/aarch64-linux-gnu/libjemalloc.so.2' from LD_PRELOAD cannot be preloaded (cannot open shared object file): ignored.
root@6b491f136c0c:/# env | grep LD_PRELOAD
ERROR: ld.so: object '/usr/lib/aarch64-linux-gnu/libjemalloc.so.2' from LD_PRELOAD cannot be preloaded (cannot open shared object file): ignored.
ERROR: ld.so: object '/usr/lib/aarch64-linux-gnu/libjemalloc.so.2' from LD_PRELOAD cannot be preloaded (cannot open shared object file): ignored.
LD_PRELOAD=/usr/lib/aarch64-linux-gnu/libjemalloc.so.2