Описание
Афаик, нет возможности разделить переменные между этапами (поправьте меня, если я ошибаюсь, пожалуйста). Для совместного использования от одного этапа сборки к другому единственный вариант - это 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}
Правильно, инструкции 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.
То, что последний комментарий был таким свежим, только дает мне представление о том, сколько разработчиков ставят под сомнение дизайнерские решения, лежащие в основе файла 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
изначально сбивает с толку.
Самый полезный комментарий
Правильно, инструкции Dockerfile, включая
ENV
vars иARG
, привязаны к этапу сборки и не будут сохранены на следующем этапе; это задумано.Однако вы _ можете_ установить _global_
ARG
(установленный перед первым этапом сборки) и использовать это значение на каждом этапе сборки;При сборке без набора
--build-arg
;Это производит:
И _с_
--build-arg
;