Moby: Сохранение настроек ENV и ARG на всех более поздних этапах в многоэтапных сборках

Созданный на 26 июн. 2018  ·  20Комментарии  ·  Источник: moby/moby

Описание

Афаик, нет возможности разделить переменные между этапами (поправьте меня, если я ошибаюсь, пожалуйста). Для совместного использования от одного этапа сборки к другому единственный вариант - это 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

Самый полезный комментарий

Правильно, инструкции Dockerfile, включая ENV vars и ARG , привязаны к этапу сборки и не будут сохранены на следующем этапе; это задумано.

Однако вы _ можете_ установить _global_ 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

И _с_ --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 Комментарий

Правильно, инструкции Dockerfile, включая ENV vars и ARG , привязаны к этапу сборки и не будут сохранены на следующем этапе; это задумано.

Однако вы _ можете_ установить _global_ 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

И _с_ --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, включая переменные среды. Итак, у меня есть переменные среды, которые необходимо установить в каждом изображении, и я хочу установить их в моем базовом образе, чтобы не нужно было повторять ту же логику для каждого другого изображения.

@ DMaxfield-BDS Объем ARG не изменится, но вы можете рассмотреть возможность создания отдельной цели сборки, которая создает базовое изображение «FROM scratch»; поместите это в реестр и используйте 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

Благодарю за ваш ответ. Единственное, что я не указал, это то, что все эти 11 изображений имеют разные базовые изображения (python, npm, postgres, openjdk и т. Д.). Итак, что я хочу сделать, так это поместить всю общую настройку / подготовку в один базовый образ, включая настройку необходимых переменных среды, используемых приложением моей компании, например:

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

Нажать в мой внутренний реестр. Затем пример одного из других 11 изображений будет делать следующее:

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>

Если я могу использовать переменные среды, установленные в 1-м изображении, мне не нужно настраивать эти же параметры для каждого из других 11 изображений. Это дает мне одну точку конфигурации и обеспечивает лучшую автоматизацию.

Как мне сделать что-то подобное?

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

Как мне разрешить использовать переменную COMMIT_HASH в последующих шагах RUN? Я вынужден определить переменную и использовать все за один шаг?

На мой взгляд, этот дизайн очень ограничен. Может быть, можно:

RUN COMMIT_HASH = $ (git rev-parse --short HEAD) КАК коммит-хеш
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 я не уверен, но это может быть, например, в / etc / profile.

image

То, что последний комментарий был таким свежим, только дает мне представление о том, сколько разработчиков ставят под сомнение дизайнерские решения, лежащие в основе файла dockerfile. Судя по всему, «СУХОЙ» не входил в число освоенных навыков. Если хотите попробовать, добавьте еще один слой и начните создавать шаблоны файлов докеров ...

Честно говоря, я очень удивлен этим ... "global arg", похоже, не работает для меня. Если я перемещаю аргументы сборки наверх, прежде чем что-либо еще, будет похоже, что они не существуют в следующих шагах после FROM.

Изменить: я не заметил, что мне все равно приходится повторять строки ARG без значений после каждого FROM ... он работает, но не идеально.

Явный способ копирования (получения) переменной окружения с другого этапа является обязательным. У меня есть подходящий вариант использования.
Вот упрощенный пример:

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. Такая функция, как «наследование среды», подтолкнет ее к другому уровню обслуживания и функциональности.

У меня около 5 слоев с интенсивным использованием env vars, и каждое обновление - кошмар. Хорошая сторона (?), Я лучше подумаю дважды, прежде чем представить новый этап.

Текущая реализация 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 - это переменные среды для каждого этапа, а не для шаблонов.

Я знаю, что вы (и я), вероятно, никогда не видели этого в реальной жизни (возможно, манипуляции со строками ...).
Но это подчеркивает тот факт, что синтаксис докера основан на синтаксисе оболочки нижнего уровня. Это заставляет документацию лгать: (см. Https://docs.docker.com/engine/reference/builder/#environment-replacement и контрпример « RUN import ${X:-sys} »)

Почему RUN не интерпретирует ARG перед запуском команды, как это делает устаревшая команда make?

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

24 часа прошли =)

_Внимание_, это решение не работает, хотя я бы хотел, чтобы это было!

Записать переменную env в файл на первом этапе и скопировать этот файл на втором этапе? так далее? как это:

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 s на каждый этап, на котором вы хотите его использовать. Возможно, построитель или линтер могут реализовать предупреждения для случаев, когда похоже, что вы пытаетесь использовать ARG которые вы не импортировали.
Это не нарушено дизайном, это явный выбор для выбора оптимального кеширования для меньшего количества строк кода.

Понятно, что использование global ARG изначально сбивает с толку.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги