Moby: Beibehalten der ENV- und ARG-Einstellungen für alle späteren Phasen in mehrstufigen Builds

Erstellt am 26. Juni 2018  ·  20Kommentare  ·  Quelle: moby/moby

Beschreibung

Afaik, es gibt keine Möglichkeit, Variablen zwischen Stufen zu teilen (korrigieren Sie mich bitte, wenn ich falsch liege). Um von einer Build-Phase zur nächsten zu wechseln, besteht die einzige Option darin, COPY Dateien aus dem Verzeichnis einer Phase in die aktuelle Phase zu übertragen. Wir können so etwas wie JSON-Dateien mit einem Speicherauszug von Build-Time-Variablen erstellen, aber ich denke, dass dieses Problem zu häufig auftritt und unsere mehrstufigen Builds mit allen möglichen verrückten JSON-Analyseproblemen überfluten wird. Können wir das automatisieren?

Schritte zum Reproduzieren des Problems:

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

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

Beschreiben Sie die Ergebnisse, die Sie erhalten haben:

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

Beschreiben Sie die erwarteten Ergebnisse:

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

Oder vielleicht können wir so etwas wie Namespaces verwenden, um ${base1.v1} zu interpolieren

arebuilder kinquestion

Hilfreichster Kommentar

Die korrekten Dockerfile-Anweisungen, einschließlich ENV vars und ARG gelten pro Erstellungsphase und werden in der nächsten Phase nicht beibehalten. Dies ist beabsichtigt.

Sie können jedoch ein globales ARG (vor der ersten Erstellungsphase festgelegt) festlegen und diesen Wert in jeder Erstellungsphase verwenden.

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}

Beim Bauen ohne --build-arg set;

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

Dies erzeugt:

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

Und _mit_ einem --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

Alle 20 Kommentare

Die korrekten Dockerfile-Anweisungen, einschließlich ENV vars und ARG gelten pro Erstellungsphase und werden in der nächsten Phase nicht beibehalten. Dies ist beabsichtigt.

Sie können jedoch ein globales ARG (vor der ersten Erstellungsphase festgelegt) festlegen und diesen Wert in jeder Erstellungsphase verwenden.

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}

Beim Bauen ohne --build-arg set;

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

Dies erzeugt:

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

Und _mit_ einem --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

Eine andere Möglichkeit besteht darin, den Basiscontainer für mehrere Stufen zu verwenden:

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

Lassen Sie mich dieses Problem schließen, da dies beabsichtigt ist, aber ich hoffe, dass die obigen Beispiele Ihnen weiterhelfen. Fühlen Sie sich auch frei, das Gespräch fortzusetzen

Wenn dies beabsichtigt ist, ist das Design falsch. Ohne eine präzise Möglichkeit, Variablen zwischen Stufen auszutauschen, ist es unmöglich, zu trocknen. Es gibt unvermeidbare, nicht ausschließbare Situationen, in denen viele Phasen Zugriff auf dieselben Variablen benötigen. Das Duplizieren von Definitionen ist fehleranfällig, ebenso wie das Duplizieren von Boilerplates zum Hacken der gemeinsam genutzten Definitionen in jede Phase.

Der "Basiscontainer" -Ansatz schränkt die Ausdruckskraft stark ein, da er den Basiscontainer für jede Stufe festlegt, während es gültige Anwendungsfälle gibt, in denen Zwischenstufen jeweils ein anderes minimales Basisbild verwenden müssen, das ein für die Stufe erforderliches Werkzeug bereitstellt.

@ thaJeztah
Kann dieses Design überdacht werden?

Ich habe 11 Basis-Images, die nur intern verfügbar sind und hauptsächlich für unsere verschiedenen Builds verwendet werden. Ich versuche, ein Basis-"From Scratch" -Bild zu erstellen, das diese 11 Bilder als Teil eines Builds in mehreren Phasen verwenden, da ein Teil der Logik für alle 11 gleich ist und dies Umgebungsvariablen umfasst. Ich habe also Umgebungsvariablen, die in jedem Image festgelegt werden müssen, und möchte diese in meinem Basis-Image festlegen, damit nicht dieselbe Logik auf jedes andere Image repliziert werden muss.

@ DMaxfield-BDS Der Umfang von ARG wird sich nicht ändern, aber Sie könnten ein separates Build-Ziel in Betracht ziehen, das das Basis-Image "FROM Scratch" erstellt. Schieben Sie das in die Registrierung und verwenden Sie if für Ihre anderen Bilder.

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

Erstellen Sie es und übertragen Sie es in Ihre (interne) Registrierung. Diese 11 Basisimages können es als Basis verwenden

FROM scratch-base
RUN your stuff

Es gibt einen weiteren Vorschlag für EXPORT/IMPORT , der möglicherweise für andere Anwendungsfälle geeignet ist. https://github.com/moby/moby/issues/32100

@ thaJeztah

Danke für Ihre Antwort. Das einzige, worauf ich nicht hingewiesen habe, ist, dass diese anderen 11 Bilder alle unterschiedliche Basisbilder haben (Python, npm, postgres, openjdk usw.). Ich möchte also alle gängigen Einstellungen / Vorbereitungen in einem Basis-Image zusammenfassen, einschließlich der Einstellung der erforderlichen Umgebungsvariablen, die von meiner Unternehmensanwendung verwendet werden, wie folgt:

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

Push zu meiner internen Registrierung. Ein Beispiel für eines der anderen 11 Bilder würde dann Folgendes bewirken:

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>

Wenn ich Umgebungsvariablen verwenden kann, die im ersten Bild festgelegt wurden, muss ich nicht dieselben Einstellungen in jedem der anderen 11 Bilder konfigurieren. Dies ermöglicht mir einen Konfigurationspunkt und eine bessere Automatisierung.

Wie soll ich so etwas machen?

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

Wie darf ich die Variable COMMIT_HASH in nachfolgenden RUN-Schritten verwenden? Ich bin gezwungen, die Variable zu definieren und alles in einem Schritt zu verwenden?

Meiner Meinung nach ist dieses Design sehr begrenzt. Vielleicht könnte man:

RUN COMMIT_HASH = $ (git rev-parse --short HEAD) ALS Commit-Hash
ARG --from = Commit-Hash COMMIT_HASH
RUN echo $ {COMMIT_HASH}

@ dnk8n das ist was ich tue um es zu

Auf der Bauphase mache ich:

RUN git rev-parse HEAD > commit_hash

Dann kopiere ich auf der anderen Stufe die Datei mit den Daten & und setze die Umgebungsvariable, bevor ich meine App starte, die sie verbraucht:

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

Sie sollten in der Lage sein, etwas Ähnliches zu tun, um das ARG einzustellen.

Zumindest sollten wir in der Lage sein, so etwas zu tun, um die Variable BAR von der anderen Stufe / dem anderen Image in den aktuellen Build-Bereich zu kopieren.

ENV --from=stage1 FOO=$BAR

Dies ist umso wichtiger, wenn Sie die Verwendung eines externen Images als Bühne in Betracht ziehen, da Umgebungsvariablen wichtige Metadaten enthalten können.

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

Ich arbeite daran, indem ich die Umgebung in jeder Phase mit diesem Muster in einer Datei speichere:

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

Wie das endlich in "Docker" "gespeichert" werden kann, so dass es sichtbar ist, wenn docker inspect Ich bin mir nicht sicher, aber es könnte zum Beispiel in / etc / profile gehen.

image

Da der letzte Kommentar so frisch ist, kann ich nur sehen, wie viele Entwickler die Entwurfsentscheidungen hinter der Docker-Datei in Frage stellen. Anscheinend gehörte "DRY" nicht zu den beherrschten Fähigkeiten. Wenn Sie es versuchen möchten, fügen Sie eine weitere Ebene hinzu und beginnen Sie mit der Vorlage der Docker-Dateien ...

Um ehrlich zu sein, bin ich sehr überrascht darüber ... das "globale Argument" scheint für mich nicht zu funktionieren. Wenn ich die Build-Argumente vor allem anderen nach oben verschiebe, ist es so, als ob sie in den folgenden Schritten nach FROM nicht vorhanden sind.

Edit: Mir war nicht aufgefallen, dass ich die ARG-Zeilen ohnehin nach jedem FROM ohne Werte wiederholen muss ... es funktioniert aber nicht ideal.

Das explizite Kopieren (Abrufen) von Umgebungsvariablen aus einer anderen Phase ist ein Muss. Ich habe einen gültigen Anwendungsfall dafür.
Hier ist ein vereinfachtes Beispiel:

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

Wie soll ich nun die Garnversion kennen, wenn sie im Knotenbild ( YARN_VERSION ) in der Umgebungsvariablen definiert ist?

Der mehrstufige Bau bietet enorme Vorteile in unseren CI-Pipelines. Eine Funktion wie "Umgebungsvererbung" würde die Wartung auf eine andere Ebene bringen und auch in Bezug auf Funktionen.

Ich habe ungefähr 5 Ebenen mit starkem Einsatz von env vars und jedes Update ist ein Albtraum. Die gute Seite (?) Überlege ich mir lieber zweimal, bevor ich eine neue Bühne einführe.

Die aktuelle ARG-Implementierung bietet ein merkwürdiges Verhalten:

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)

oder

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

Dies ist durch die Tatsache verbunden, dass ARGs Umgebungsvariablen pro Stufe sind und keine Vorlagen enthalten.

Ich weiß, dass Sie (und ich) das im wirklichen Leben wahrscheinlich nie gesehen haben (String-Manipulation vielleicht ...).
Dies unterstreicht jedoch die Tatsache, dass die Docker-Syntax auf der Underlayer-Shell-Syntax basiert. Das lässt die Dokumentation lügen: (siehe https://docs.docker.com/engine/reference/builder/#environment-replacement und Gegenbeispiel " RUN import ${X:-sys} ")

Warum interpretiert RUN ARG nicht, bevor der Befehl gestartet wird, wie es das alte 'make' tut?

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

24 Stunden sind vorbei =)

_Warning_ diese Lösung funktioniert nicht, obwohl ich wünschte, es würde!

Schreiben Sie die env var in der ersten Stufe in eine Datei und kopieren Sie diese eine Datei in der zweiten Stufe? usw? so was:

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)"

Boom, außer es funktioniert nicht, da cm_cc_commit_id zum Literal $(cat /tmp/env.json) , der letztere Ausdruck wird nicht ausgewertet, er bleibt wörtlich. Das Einstellen dynamischer Umgebungsvariablen scheint also unmöglich?

Was jedoch funktioniert, ist, in eine Datei zu schreiben, Ihren Einstiegspunkt zu einer Bash-Datei zu machen und dann Folgendes zu tun:

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

Denken Sie eher so darüber nach:

Dockerfile kann nur über globales ARG Variablen deklarieren. Dies wird nicht automatisch auf jede Stufe übertragen, da dies Auswirkungen auf das Caching hat.
Der Builder könnte schlauer sein und bestimmen, ob Sie versuchen, ein ARG in einer bestimmten Phase zu verwenden und es zuzulassen, aber dann muss er raten, ob Sie ein Literal wollten oder nicht.

Ja, Sie müssen explizit globale ARG in jede Phase importieren, in der Sie sie verwenden möchten. Möglicherweise könnte der Builder oder ein Linter Warnungen für Fälle implementieren, in denen es so aussieht, als würden Sie versuchen, ein ARG , die Sie nicht importiert haben.
Dies ist nicht beabsichtigt, es ist eine explizite Wahl, das optimale Caching über weniger Codezeilen zu wählen.

Es versteht sich definitiv, dass die Verwendung von globalen ARG s zunächst verwirrend ist.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen