Moby: ENVおよびARG設定をマルチステージビルドの後のすべてのステージに永続化する

作成日 2018年06月26日  ·  20コメント  ·  ソース: moby/moby

説明

Afaik、ステージ間で変数を共有する方法はありません(間違っている場合は訂正してください)。 あるビルドステージから次のビルドステージに共有するための唯一のオプションは、あるステージのディレクトリから現在のステージにCOPYファイルを移動することです。 ビルド時変数のダンプを使用してJSONファイルのようなものをビルドできますが、この問題はあまりにも頻繁に発生するものであり、多段階ビルドにあらゆる種類のクレイジーなJSON解析の問題が殺到すると思います。 これを自動化できますか?

問題を再現する手順:

FROM alpine:latest as base1
ARG v1=test
ENV v1=$v1
RUN echo ${v1}

FROM alpine:latest as base2
RUN echo ${v1}

受け取った結果を説明してください。

docker build --no-cache  .
Step 4/6 : RUN echo ${v1}
---> Running in b60a3079864b
test

...

Step 5/6 : FROM alpine:latest as base2
---> 3fd9065eaf02
Step 6/6 : RUN echo ${v1}
---> Running in 1147977afd60

期待した結果を説明してください。

docker build --no-cache --multistage-share-env --multistage-share-arg .
Step 4/6 : RUN echo ${v1}
---> Running in b60a3079864b
test

...

Step 5/6 : FROM alpine:latest as base2
---> 3fd9065eaf02
Step 6/6 : RUN echo ${v1}
---> Running in 1147977afd60
test

または、名前空間のようなものを使用して${base1.v1}を補間することもできます

arebuilder kinquestion

最も参考になるコメント

ENV varsとARGを含む正しいDockerfile命令は、ビルドステージごとにスコープされ、次のステージでは保持されません。 これは仕様によるものです。

ただし、_グローバル_ ARG (最初のビルドステージの前に設定)を設定し、各ビルドステージでその値を使用することは_できます_。

ARG version_default=v1

FROM alpine:latest as base1
ARG version_default
ENV version=$version_default
RUN echo ${version}
RUN echo ${version_default}

FROM alpine:latest as base2
ARG version_default
RUN echo ${version_default}

--build-argセットなしでビルドする場合。

docker build --no-cache -<<'EOF'
ARG version_default=v1

FROM alpine:latest as base1
ARG version_default
ENV version=$version_default
RUN echo ${version}
RUN echo ${version_default}

FROM alpine:latest as base2
ARG version_default
RUN echo ${version_default}
EOF

これにより、次のものが生成されます。

Sending build context to Docker daemon  2.048kB
Step 1/9 : ARG version_default=v1
Step 2/9 : FROM alpine:latest as base1
 ---> 3fd9065eaf02
Step 3/9 : ARG version_default
 ---> Running in 702c05d6f294
Removing intermediate container 702c05d6f294
 ---> 1b2cac6e7585
Step 4/9 : ENV version=$version_default
 ---> Running in 6fb73bc8cdb9
Removing intermediate container 6fb73bc8cdb9
 ---> 656d82ccb6d7
Step 5/9 : RUN echo ${version}
 ---> Running in 403c720d0031
v1
Removing intermediate container 403c720d0031
 ---> d6071c5bd329
Step 6/9 : RUN echo ${version_default}
 ---> Running in d5c76d7d3aaa
v1
Removing intermediate container d5c76d7d3aaa
 ---> 554df1d8584b
Step 7/9 : FROM alpine:latest as base2
 ---> 3fd9065eaf02
Step 8/9 : ARG version_default
 ---> Running in 92400e85c722
Removing intermediate container 92400e85c722
 ---> 5f0cb12f4448
Step 9/9 : RUN echo ${version_default}
 ---> Running in f38802f0d690
v1
Removing intermediate container f38802f0d690
 ---> 4b8caab7870a
Successfully built 4b8caab7870a

そして_with_a --build-arg ;

docker build --no-cache --build-arg version_default=v2 -<<'EOF'
ARG version_default=v1

FROM alpine:latest as base1
ARG version_default
ENV version=$version_default
RUN echo ${version}
RUN echo ${version_default}

FROM alpine:latest as base2
ARG version_default
RUN echo ${version_default}
EOF
Sending build context to Docker daemon  2.048kB
Step 1/9 : ARG version_default=v1
Step 2/9 : FROM alpine:latest as base1
 ---> 3fd9065eaf02
Step 3/9 : ARG version_default
 ---> Running in 7f5dd5885859
Removing intermediate container 7f5dd5885859
 ---> 482ffb014095
Step 4/9 : ENV version=$version_default
 ---> Running in b6c6e9aa3489
Removing intermediate container b6c6e9aa3489
 ---> 83f1c0b82986
Step 5/9 : RUN echo ${version}
 ---> Running in 0805ec04fd20
v2
Removing intermediate container 0805ec04fd20
 ---> ef39d4bd6306
Step 6/9 : RUN echo ${version_default}
 ---> Running in f8747a5bfeeb
v2
Removing intermediate container f8747a5bfeeb
 ---> 72d497d25306
Step 7/9 : FROM alpine:latest as base2
 ---> 3fd9065eaf02
Step 8/9 : ARG version_default
 ---> Running in 57aa2e097787
Removing intermediate container 57aa2e097787
 ---> 45e167d234ce
Step 9/9 : RUN echo ${version_default}
 ---> Running in 8615cd6f6ab6
v2
Removing intermediate container 8615cd6f6ab6
 ---> 1674ad8d3b88
Successfully built 1674ad8d3b88

全てのコメント20件

ENV varsとARGを含む正しいDockerfile命令は、ビルドステージごとにスコープされ、次のステージでは保持されません。 これは仕様によるものです。

ただし、_グローバル_ ARG (最初のビルドステージの前に設定)を設定し、各ビルドステージでその値を使用することは_できます_。

ARG version_default=v1

FROM alpine:latest as base1
ARG version_default
ENV version=$version_default
RUN echo ${version}
RUN echo ${version_default}

FROM alpine:latest as base2
ARG version_default
RUN echo ${version_default}

--build-argセットなしでビルドする場合。

docker build --no-cache -<<'EOF'
ARG version_default=v1

FROM alpine:latest as base1
ARG version_default
ENV version=$version_default
RUN echo ${version}
RUN echo ${version_default}

FROM alpine:latest as base2
ARG version_default
RUN echo ${version_default}
EOF

これにより、次のものが生成されます。

Sending build context to Docker daemon  2.048kB
Step 1/9 : ARG version_default=v1
Step 2/9 : FROM alpine:latest as base1
 ---> 3fd9065eaf02
Step 3/9 : ARG version_default
 ---> Running in 702c05d6f294
Removing intermediate container 702c05d6f294
 ---> 1b2cac6e7585
Step 4/9 : ENV version=$version_default
 ---> Running in 6fb73bc8cdb9
Removing intermediate container 6fb73bc8cdb9
 ---> 656d82ccb6d7
Step 5/9 : RUN echo ${version}
 ---> Running in 403c720d0031
v1
Removing intermediate container 403c720d0031
 ---> d6071c5bd329
Step 6/9 : RUN echo ${version_default}
 ---> Running in d5c76d7d3aaa
v1
Removing intermediate container d5c76d7d3aaa
 ---> 554df1d8584b
Step 7/9 : FROM alpine:latest as base2
 ---> 3fd9065eaf02
Step 8/9 : ARG version_default
 ---> Running in 92400e85c722
Removing intermediate container 92400e85c722
 ---> 5f0cb12f4448
Step 9/9 : RUN echo ${version_default}
 ---> Running in f38802f0d690
v1
Removing intermediate container f38802f0d690
 ---> 4b8caab7870a
Successfully built 4b8caab7870a

そして_with_a --build-arg ;

docker build --no-cache --build-arg version_default=v2 -<<'EOF'
ARG version_default=v1

FROM alpine:latest as base1
ARG version_default
ENV version=$version_default
RUN echo ${version}
RUN echo ${version_default}

FROM alpine:latest as base2
ARG version_default
RUN echo ${version_default}
EOF
Sending build context to Docker daemon  2.048kB
Step 1/9 : ARG version_default=v1
Step 2/9 : FROM alpine:latest as base1
 ---> 3fd9065eaf02
Step 3/9 : ARG version_default
 ---> Running in 7f5dd5885859
Removing intermediate container 7f5dd5885859
 ---> 482ffb014095
Step 4/9 : ENV version=$version_default
 ---> Running in b6c6e9aa3489
Removing intermediate container b6c6e9aa3489
 ---> 83f1c0b82986
Step 5/9 : RUN echo ${version}
 ---> Running in 0805ec04fd20
v2
Removing intermediate container 0805ec04fd20
 ---> ef39d4bd6306
Step 6/9 : RUN echo ${version_default}
 ---> Running in f8747a5bfeeb
v2
Removing intermediate container f8747a5bfeeb
 ---> 72d497d25306
Step 7/9 : FROM alpine:latest as base2
 ---> 3fd9065eaf02
Step 8/9 : ARG version_default
 ---> Running in 57aa2e097787
Removing intermediate container 57aa2e097787
 ---> 45e167d234ce
Step 9/9 : RUN echo ${version_default}
 ---> Running in 8615cd6f6ab6
v2
Removing intermediate container 8615cd6f6ab6
 ---> 1674ad8d3b88
Successfully built 1674ad8d3b88

別の方法は、複数のステージにベースコンテナを使用することです。

FROM alpine:latest as base
ARG version_default
ENV version=$version_default

FROM base
RUN echo ${version}

FROM base
RUN echo ${version}
docker build --build-arg=version_default=123 --no-cache .   
Sending build context to Docker daemon  92.67kB
Step 1/7 : FROM alpine:latest as base
 ---> 3fd9065eaf02
Step 2/7 : ARG version_default
 ---> Running in a1ebfdf79f07
Removing intermediate container a1ebfdf79f07
 ---> 3e78800ed9ea
Step 3/7 : ENV version=$version_default
 ---> Running in 105d94baac3f
Removing intermediate container 105d94baac3f
 ---> a14276ddc77b
Step 4/7 : FROM base
 ---> a14276ddc77b
Step 5/7 : RUN echo ${version}
 ---> Running in d92f9b48a6cc
123
Removing intermediate container d92f9b48a6cc
 ---> 6505fe2a14bb
Step 6/7 : FROM base
 ---> a14276ddc77b
Step 7/7 : RUN echo ${version}
 ---> Running in 1b748eea4ef3
123
Removing intermediate container 1b748eea4ef3
 ---> f3311d3ad27e
Successfully built f3311d3ad27e
Time: 0h:00m:04s

これは仕様によるものであるため、この問題を閉じさせてください。ただし、上記の例がさらに役立つことを願っています。 気軽に会話を続けてください

これが設計によるものである場合、設計は間違っています。 ステージ間で変数を共有する簡潔な方法がなければ、乾燥することは不可能です。 多くのステージが同じ変数にアクセスする必要がある、避けられない、排除できない状況があります。 定義を複製するとエラーが発生しやすく、共有定義をすべての段階にハッキングするための定型文を複製します。

「ベースコンテナ」アプローチは、ステージごとにベースコンテナを固定するため、表現力を大幅に制限しますが、中間ステージごとに、ステージに必要なツールを提供する異なる最小限のベースイメージを使用する必要がある有効なユースケースがあります。

@thaJeztah
このデザインを再考することはできますか?

内部でのみ利用可能で、主にさまざまなビルドに使用される11のベースイメージがあります。 一部のロジックは11個すべてで同じであり、これには環境変数が含まれているため、これらの11個のイメージがマルチステージビルドの一部として使用するベースの「FROMスクラッチ」イメージを作成しようとしています。 そのため、各イメージ内で設定する必要のある環境変数があり、同じロジックを他のすべてのイメージに複製する必要がないように、これらをベースイメージ内に設定したいと考えています。

@DMaxfield- ARG BDSスコープは変更されませんが、「FROMスクラッチ」ベースイメージを作成する別のビルドターゲットを用意することを検討できます。 それをレジストリにプッシュし、ifを他のイメージに使用します。

FROM scratch AS scratch-base
ENV foo=bar
ENV bar=baz

それをビルドし、それを(内部)レジストリにプッシュすると、それらの11のベースイメージがベースとして使用できます

FROM scratch-base
RUN your stuff

EXPORT/IMPORTを持つという別の提案があります。これは、他のいくつかのユースケースに適合する可能性があります。 https://github.com/moby/moby/issues/32100

@thaJeztah

ご返信ありがとうございます。 私が指摘しなかったことの1つは、これらの他の11個のイメージはすべて異なるベースイメージ(python、npm、postgres、openjdkなど)を持っているということです。 したがって、私が探しているのは、次のように、会社のアプリケーションで使用される必要な環境変数の設定を含め、すべての一般的なセットアップ/準備を1つのベースイメージにまとめることです。

FROM scratch AS scratch-base
ARG JAR_DIR='/Input/jars/'
ENV JAR_DOWNLOAD_DIR=$JAR_DIR

内部レジストリにプッシュします。 次に、他の11個の画像の1つの例は、次のようになります。

FROM scratch-base AS scratch-base
FROM openjdk:8-jdk
COPY --from=scratch-base $JAR_DOWNLOAD_DIR .
RUN <stuff that also uses the JAR_DOWNLOAD_DIR environment variable>

最初のイメージで設定された環境変数を使用できる場合は、他の11個のイメージのそれぞれでこれらの同じ設定を構成する必要はありません。 これにより、1つの構成ポイントが可能になり、自動化が向上します。

どうすればこのようなことをすることができますか?

RUN COMMIT_HASH = $(git rev-parse --short HEAD)

後続のRUNステップでCOMMIT_HASH変数を使用するにはどうすればよいですか? 変数を定義して、すべてを1つのステップで使用する必要がありますか?

私の意見では、このデザインは非常に限られています。 多分1つはできます:

RUN COMMIT_HASH = $(git rev-parse --short HEAD)AS commit-hash
ARG --from = commit-hash COMMIT_HASH
RUN echo $ {COMMIT_HASH}

@ dnk8nこれは私がそれを回避するために行うことです:

ビルド段階で私はします:

RUN git rev-parse HEAD > commit_hash

次に、もう一方のステージで、データを含むファイルをコピーし、それを使用するアプリを実行する前に環境変数を設定します。

COPY --from=builder /<build_folder>/commit_hash /<other_stage_folder>/commit_hash

CMD export COMMIT_HASH=$(cat /<other_stage_folder>/commit_hash); java -jar myApp.jar

ARGを設定するのと同様のことができるはずです。

少なくとも、このようなことを実行して、 BAR変数を他のステージ/イメージから現在のビルドスコープにコピーできるはずです。

ENV --from=stage1 FOO=$BAR

環境変数に重要なメタデータが存在する可能性があるため、外部イメージをステージとして使用することを検討する場合、これはさらに重要です。

ENV --from=nginx:latest FOO=$BAR

このパターンを使用して、各段階で環境をファイルに保存することで回避策を講じています。

FROM base1:version1 AS BUILDER1
WORKDIR /build
RUN env | sort > env.BUILDER1

FROM base2:version2 AS BUILDER2
WORKDIR /build
RUN env | sort > env.BUILDER2

FROM finalbase:version AS FINAL
WORKDIR /build
ENV LANG=C.UTF-8
COPY --from=BUILDER1 /build/env.BUILDER1 .
COPY --from=BUILDER2 /build/env.BUILDER2 .
# Use ". ./env.BUILDER" instead of "source" if /bin/sh is true-POSIX
RUN set -eux ; \
        source ./env.BUILDER1 ; \
        source ./env.BUILDER2 ; \
        env | sort

docker inspectを実行しているときに表示されるように、最終的に「docker」に「保存」する方法はわかりませんが、たとえば/ etc / profileに移動できます。

image

最後のコメントがとても新鮮であるのを見て、dockerfileの背後にある設計上の決定に疑問を呈している開発者の数を垣間見ることができます。 どうやら「DRY」は習得したスキルの中にはありませんでした。 試してみたい場合は、別のレイヤーを追加して、dockerfilesのテンプレートを作成してください...

正直なところ、私はこれに非常に驚いています...「グローバル引数」は私にはうまくいかないようです。 ビルド引数を何よりも先に一番上に移動すると、FROMの後の次の手順でビルド引数が存在しないようになります。

編集:とにかく、各FROMの後に値のないARG行を繰り返す必要があることに気づいていませんでした...それは機能しますが、理想的ではありません。

他のステージから環境変数をコピー(取得)する明示的な方法は必須です。 有効なユースケースがあります。
簡単な例を次に示します。

FROM node:lts-alpine AS node
FROM php:7-fpm-alpine AS php

# Here goes some build instructions for PHP image, then

# Install nodejs, npm, yarn
COPY --from=node /usr/local/bin/node /usr/local/bin/node
COPY --from=node /usr/local/lib/node_modules /usr/local/lib/node_modules
COPY --from=node /opt /opt

# Create symlinks to npm, yarn binaries
RUN \
    ln -s "/usr/local/lib/node_modules/npm/bin/npm-cli.js" /usr/local/bin/npm \
    && ln -s "/usr/local/lib/node_modules/npm/bin/npx-cli.js" /usr/local/bin/npx \
    && ln -s /opt/yarn-v1.??.?/bin/yarn /usr/local/bin/yarn \
    && ln -s /opt/yarn-v1.??.?/bin/yarnpkg /usr/local/bin/yarnpkg

# Some other instructions

さて、環境変数のノードイメージ( YARN_VERSION )で定義されている場合、糸のバージョンをどのように知る必要がありますか?

マルチステージビルドは、CIパイプラインの大きなメリットに対応しています。 「環境継承」のような機能は、それを別のレベルのメンテナンスにプッシュし、機能的にも賢明です。

私はenv変数を多用する約5つのレイヤーを持っており、すべての更新は悪夢です。 明るい面(?)、新しいステージを紹介する前に、私はむしろよく考えます。

現在のARG実装では、いくつかの奇妙な動作が見られます。

FROM python
ARG X=os    # or ENV, the same  
RUN echo import ${X:-sys}   # Proof that the syntax works with default SHELL
SHELL ["python", "-c"]
RUN import os
RUN import ${X}  # Fails
RUN import ${X:-sys}  # Fails too, but this is a documented syntax (for ENV)

または

FROM alpine
ARG X=ab.cd
RUN echo ${X%.*}  # it's ok but it looks like magic 
WORKDIR ${X%.*}  # Fails here 

これは、ARGがテンプレートではなく、ステージごとの環境変数であるという事実によってリンクされています。

あなた(そして私)はおそらく実際の生活ではそれを見たことがないでしょう(文字列操作は多分...)。
しかし、それは、docker構文が下層シェル構文に基づいているという事実を浮き彫りにします。 それはドキュメントを嘘にします:(https://docs.docker.com/engine/reference/builder/#environment-replacementと反例 " RUN import ${X:-sys} "を参照してください)

従来の「make」のように、コマンドを起動する前にRUNがARGを解釈しないのはなぜですか?

$ cat Makefile
SHELL=python
X=os
test:
    import os
    import ${X}
$ make test
import os
import os

24時間以上です=)

_警告_このソリューションは機能しませんが、機能することを望みます。

env varを最初の段階でファイルに書き込み、その1つのファイルを2番目の段階でコピーしますか? 等? このような:

FROM golang:1.14 as cm_base
ARG commit_id
RUN echo "$commit_id" > /tmp/env.json
FROM golang:1.14
COPY --from=cm_base "/tmp/env.json" "/tmp/env.json"
ENV cm_cc_commit_id="$(cat /tmp/env.json)"

ブームは、機能しないことを除いて、 cm_cc_commit_idがリテラル$(cat /tmp/env.json)になるため、後者の式は評価されず、リテラルのままになります。 では、動的なenv変数を設定することは不可能に思えますか?

ただし、機能するのは、ファイルへの書き込み、エントリポイントのbashファイルの作成、そしてこれを行うことです。

#!/usr/bin/env bash
echo "the docker entrypoint args:" "$@"
export cm_cc_commit_id="$(cat /tmp/env.json)"
"$@"

次のように考えてください。

Dockerfileには、グローバルARGを経由する場合を除いて、変数を宣言する手段がありません。 これはキャッシュに影響を与えるため、これは各ステージに自動的に伝播されません。
ビルダーはより賢く、特定の段階でARGを使用しようとしているかどうかを判断して許可することができますが、リテラルが必要かどうかを推測する必要があります。

そうです、グローバルARGを、それを使用する各ステージに明示的にインポートする必要があります。おそらく、ビルダーまたはリンターは、 ARGを使用しようとしているように見える場合に警告を実装できます。インポートしていない
これは設計によるものではありません。より少ないコード行で最適なキャッシュを選択することは明確な選択です。

グローバルARGの使用法は、最初は混乱を招くことは間違いありません。

このページは役に立ちましたか?
0 / 5 - 0 評価