Moby: Persistance des paramètres ENV et ARG à toutes les étapes ultérieures des versions en plusieurs étapes

Créé le 26 juin 2018  ·  20Commentaires  ·  Source: moby/moby

La description

Afaik, il n'y a aucun moyen de partager des variables entre les étapes (corrigez-moi si je me trompe, s'il vous plaît). Pour partager d'une étape de construction à l'autre, la seule option est de COPY fichiers

Étapes pour reproduire le problème:

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

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

Décrivez les résultats que vous avez reçus:

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

Décrivez les résultats attendus:

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

Ou peut-être pouvons-nous utiliser quelque chose comme des espaces de noms pour interpoler ${base1.v1}

arebuilder kinquestion

Commentaire le plus utile

Correct, les instructions Dockerfile, y compris ENV vars et ARG sont étendues par étape de construction, et ne seront pas conservées à l'étape suivante; c'est par conception.

Vous _pouvez_, cependant, définir un _global_ ARG (défini avant la première étape de construction), et utiliser cette valeur dans chaque étape de construction;

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}

Lors de la construction sans un ensemble --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

Cela produit:

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

Et _avec_ un --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

Tous les 20 commentaires

Correct, les instructions Dockerfile, y compris ENV vars et ARG sont étendues par étape de construction, et ne seront pas conservées à l'étape suivante; c'est par conception.

Vous _pouvez_, cependant, définir un _global_ ARG (défini avant la première étape de construction), et utiliser cette valeur dans chaque étape de construction;

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}

Lors de la construction sans un ensemble --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

Cela produit:

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

Et _avec_ un --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

une autre façon consiste à utiliser le conteneur de base pour plusieurs étapes:

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

Permettez-moi de clore ce problème, car cela est intentionnel, mais espérons que les exemples ci-dessus vous aideront davantage; N'hésitez pas non plus à poursuivre la conversation

Si c'est par conception, alors la conception est fausse. Sans une manière concise de partager les variables entre les étapes, il est impossible de SÉCHER. Il existe des situations inévitables et inéliminables où de nombreuses étapes auront besoin d'accéder aux mêmes variables. La duplication des définitions est sujette aux erreurs, tout comme la duplication du passe-partout pour pirater les définitions partagées à chaque étape.

L'approche du «conteneur de base» limite considérablement la puissance expressive car elle corrige le conteneur de base pour chaque étape, alors qu'il existe des cas d'utilisation valides où les étapes intermédiaires doivent chacune utiliser une image de base minimale différente qui fournit un outil requis pour l'étape.

@thaJeztah
Cette conception peut-elle être reconsidérée?

J'ai 11 images de base qui ne sont disponibles qu'en interne et principalement utilisées pour nos différentes versions. J'essaie de créer une image de base «À PARTIR de zéro», que ces 11 images utiliseront dans le cadre d'une construction en plusieurs étapes, car une partie de la logique est la même dans les 11, et cela inclut les variables d'environnement. J'ai donc des variables d'environnement qui doivent être définies dans chaque image et je souhaite les définir dans mon image de base afin que la même logique n'ait pas besoin d'être répliquée sur toutes les autres images.

@ DMaxfield-BDS La portée de ARG ne changera pas, mais vous pourriez envisager d'avoir une cible de construction distincte qui crée l'image de base "FROM scratch"; poussez cela dans le registre et utilisez if pour vos autres images;

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

construisez-le et poussez-le dans votre registre (interne), et ces 11 images de base pourraient l'utiliser comme base

FROM scratch-base
RUN your stuff

Il existe une autre proposition pour avoir EXPORT/IMPORT , qui pourrait convenir à d'autres cas d'utilisation; https://github.com/moby/moby/issues/32100

@thaJeztah

Merci pour votre réponse. La seule chose que je n'ai pas soulignée est que ces 11 autres images ont toutes des images de base différentes (python, npm, postgres, openjdk, etc.). Donc, ce que je cherche à faire est de mettre toute la configuration / préparation commune dans une seule image de base, y compris la configuration des variables d'environnement nécessaires utilisées par l'application de mon entreprise, comme suit:

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

Poussez vers mon registre interne. Ensuite, un exemple de l'une des 11 autres images ferait ce qui suit:

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>

Si je suis capable d'utiliser des variables d'environnement définies dans la 1ère image, je n'ai pas à configurer ces mêmes paramètres dans chacune des 11 autres images. Cela me permet un point de configuration et permet une meilleure automatisation.

Comment suis-je censé faire quelque chose comme ça?

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

Comment puis-je utiliser la variable COMMIT_HASH dans les étapes RUN suivantes? Je suis obligé de définir la variable et de tout utiliser en une seule étape?

À mon avis, cette conception est très limitée. On pourrait peut-être:

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

@ dnk8n voici ce que je fais pour le contourner:

Sur la phase de construction, je fais:

RUN git rev-parse HEAD > commit_hash

puis sur l'autre étape, je copie le fichier avec les données & et définit la variable Environnement avant d'exécuter mon application qui la consomme:

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

Vous devriez pouvoir faire quelque chose de similaire pour définir l'ARG.

À tout le moins, nous devrions être en mesure de faire quelque chose comme ceci pour copier la variable BAR de l'autre étape / image dans la portée de construction actuelle.

ENV --from=stage1 FOO=$BAR

Ceci est encore plus important lorsque vous envisagez d'utiliser une image externe comme scène car il peut y avoir des métadonnées importantes dans les variables d'environnement.

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

Je travaille autour en enregistrant l'environnement dans un fichier à chaque étape, en utilisant ce modèle:

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

Comment cela peut enfin être «sauvegardé» dans «docker» de sorte qu'il soit visible lors de l'exécution de docker inspect Je ne suis pas sûr, mais cela pourrait aller dans / etc / profile par exemple.

image

Vu que le dernier commentaire est si récent, il ne me donne qu'un aperçu du nombre de développeurs qui remettent en question les décisions de conception derrière le fichier docker. Apparemment, "DRY" ne faisait pas partie des compétences maîtrisées. Si vous voulez essayer, ajoutez une autre couche et commencez à créer des modèles pour les fichiers docker ...

Pour être honnête, je suis très surpris à ce sujet ... le "argument global" ne semble pas fonctionner pour moi. Si je déplace les arguments de construction vers le haut avant toute autre chose, c'est comme s'ils n'existaient pas dans les étapes suivantes après FROM.

Edit: Je n'avais pas remarqué que je devais quand même répéter les lignes ARG sans valeurs après chaque FROM ... cela fonctionne mais pas idéal.

Une manière explicite de copier (obtenir) la variable d'environnement à partir d'une autre étape est indispensable. J'ai un cas d'utilisation valide pour cela.
Voici un exemple simplifié:

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

Maintenant, comment suis-je censé connaître la version du fil si elle est définie dans l'image du nœud ( YARN_VERSION ) dans la variable d'environnement?

La construction en plusieurs étapes bénéficie d'énormes avantages dans nos pipelines CI. Une fonctionnalité telle que «l'héritage de l'environnement» le pousserait à un autre niveau de maintenance et en termes de fonctionnalités.

J'ai environ 5 couches avec une utilisation intensive des variables d'environnement et chaque mise à jour est un cauchemar. Le bon côté (?), Je réfléchis plutôt à deux fois avant d'introduire une nouvelle étape.

L'implémentation actuelle d'ARG donne un comportement curieux:

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)

ou

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

Ceci est lié par le fait que les ARG sont des variables d'environnement par étape et non des modèles.

Je sais que vous (et moi) n'avez probablement jamais vu ça dans la vraie vie (manipulation de cordes peut-être ...).
Mais cela met en évidence le fait que la syntaxe du docker est basée sur la syntaxe du shell de sous-couche. Cela fait mentir la documentation: (voir https://docs.docker.com/engine/reference/builder/#environment-replacement et contre-exemple " RUN import ${X:-sys} ")

Pourquoi RUN n'interprète pas ARG avant de lancer la commande comme le fait l'ancien «make»?

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

24 heures sont terminées =)

_Avertissement_ cette solution ne fonctionne pas, même si j'aurais aimé qu'elle le fasse!

Ecrire le var env dans un fichier dans la première étape et copier ce fichier dans la deuxième étape? etc? comme ça:

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, sauf que cela ne fonctionne pas, puisque cm_cc_commit_id devient le littéral $(cat /tmp/env.json) , cette dernière expression n'est pas évaluée, elle reste littérale. Donc, définir des variables d'environnement dynamiques semble impossible?

Cependant, ce qui fonctionne est d'écrire dans un fichier, de faire de votre point d'entrée un fichier bash, puis de faire ceci:

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

Pensez-y plutôt comme ceci:

Dockerfile n'a pas de moyen de déclarer des variables sauf via global ARG . Cela ne se propage pas automatiquement à chaque étape car cela a des effets sur la mise en cache.
Le constructeur pourrait être plus intelligent et déterminer si vous essayez d'utiliser un ARG dans une étape particulière et l'autoriser, mais il doit ensuite deviner si vous vouliez un littéral ou non.

Alors oui, vous devez explicitement importer globale ARG s dans chaque étape que vous voulez utiliser. Peut - être le constructeur ou linter pourrait mettre en œuvre des avertissements pour les cas où il semble que vous pourriez essayer d'utiliser un ARG que vous n'avez pas importés.
Ce n'est pas rompu par conception, c'est un choix explicite de choisir une mise en cache optimale sur moins de lignes de code.

Il est bien entendu que l'utilisation de global ARG s est au départ déroutante.

Cette page vous a été utile?
0 / 5 - 0 notes