Closed4

一つの Dockerfile でホストに応じたアーキテクチャ (amd64 や arm64) に対応した環境変数を持つイメージを作成する方法

nikuniku

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_64aarch64 は、ビルドしているホストに応じて自動的に切り替わってほしい。

環境変数は Dockerfile の ENV で切り替えたい。

これら全てを満たすやり方はあるか探索する。

ビルドは最新の docker desktop で実施するので、docker desktop でデフォルトビルダーとなっている BuildKit の機能を使ってよい。

nikuniku

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
nikuniku

環境変数の x86_64aarch64 は、ビルドしているホストに応じて自動的に切り替わってほしい。
ビルドしているホストの情報は ARG から取得できる

ただホストの情報は x86_64aarch64 ではなく amd64arm64 という名前で取れてくる。
それを 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:/#
nikuniku

全てを満たす方法はあるか?ある。
Multi-stage buildsBuildKitから自動的に渡される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 を設定している。
これにより amd64arm64 から x86_64aarch64 への読み替えが行えている。

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
このスクラップは2024/05/12にクローズされました