Moby: استمرار إعدادات ENV و ARG في جميع المراحل اللاحقة في عمليات الإنشاء متعددة المراحل

تم إنشاؤها على ٢٦ يونيو ٢٠١٨  ·  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

التعليق الأكثر فائدة

صحيح ، تعليمات 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 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

ينتج عن هذا:

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_ --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 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

ينتج عن هذا:

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_ --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

اسمحوا لي أن أغلق هذه المشكلة ، لأن هذا حسب التصميم ، ولكن آمل أن تساعدك الأمثلة أعلاه بشكل أكبر ؛ لا تتردد أيضًا في مواصلة المحادثة

إذا كان هذا حسب التصميم ، فإن التصميم خاطئ. بدون طريقة موجزة لمشاركة المتغيرات بين المراحل ، من المستحيل التجفيف. هناك حالات لا مفر منها ولا يمكن التغلب عليها حيث ستحتاج العديد من المراحل إلى الوصول إلى نفس المتغيرات. تعد التعريفات المكررة عرضة للخطأ ، وكذلك تكرار نموذج معياري لاختراق التعريفات المشتركة في كل مرحلة.

يحد نهج "الحاوية الأساسية" بشدة من القوة التعبيرية لأنه يصلح الحاوية الأساسية لكل مرحلة ، بينما توجد حالات استخدام صالحة حيث تحتاج كل مرحلة من المراحل الوسيطة إلى استخدام صورة أساسية مختلفة الحد الأدنى توفر أداة مطلوبة للمرحلة.

تضمين التغريدة
هل يمكن إعادة النظر في هذا التصميم؟

لدي 11 صورة أساسية متوفرة فقط داخليًا وتستخدم بشكل أساسي في تصميماتنا المختلفة. أحاول إنشاء صورة أساسية "من نقطة الصفر" ، والتي ستستخدمها هذه الصور الإحدى عشرة كجزء من بناء متعدد المراحل ، لأن بعض المنطق هو نفسه عبر جميع 11 ، وهذا يشمل متغيرات البيئة. لذلك لدي متغيرات البيئة التي يجب تعيينها داخل كل صورة ، وأريد تعيينها داخل صورتي الأساسية بحيث لا يلزم تكرار نفس المنطق عبر كل صورة أخرى.

@ DMaxfield-BDS لن يتغير نطاق ARG ، ولكن يمكنك التفكير في وجود هدف بناء منفصل يقوم بإنشاء الصورة الأساسية "FROM scratch" ؛ دفع ذلك إلى التسجيل ، واستخدمه لصورك الأخرى ؛

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

قم ببنائه ، وادفعه إلى السجل (الداخلي) الخاص بك ، ويمكن لهذه الصور الأساسية الإحدى عشر استخدامها كقاعدة

FROM scratch-base
RUN your stuff

هناك اقتراح آخر بأن يكون لديك EXPORT/IMPORT ، والذي قد يناسب بعض حالات الاستخدام الأخرى ؛ https://github.com/moby/moby/issues/32100

تضمين التغريدة

شكرا لردكم. الشيء الوحيد الذي لم أشير إليه هو أن هذه الصور الـ 11 الأخرى تحتوي جميعها على صور أساسية مختلفة (python ، npm ، postgres ، openjdk ، إلخ). لذلك ما أتطلع إلى القيام به هو وضع كل الإعداد / الإعداد المشترك في صورة أساسية واحدة ، بما في ذلك إعداد متغيرات البيئة المطلوبة التي يستخدمها تطبيق شركتي ، مثل ما يلي:

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

ادفع إلى السجل الداخلي الخاص بي. ثم ، مثال على إحدى الصور الإحدى عشرة الأخرى سيفعل ما يلي:

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 الأخرى. هذا يسمح لي بنقطة تكوين واحدة ، ويسمح بأتمتة أفضل.

كيف يفترض بي أن أفعل شيئًا كهذا؟

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}

@ 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. يبدو أن "جاف" لم يكن من بين المهارات المتقنة. إذا كنت تريد المحاولة ، أضف طبقة أخرى وابدأ في تشكيل ملفات الرصيف ...

لأكون صادقًا ، أنا مندهش جدًا من هذا ... لا يبدو أن "الجدال العالمي" يناسبني. إذا قمت بنقل هياكل البناء إلى الأعلى قبل أي شيء آخر ، يبدو الأمر كما لو أنها غير موجودة في الخطوات التالية بعد 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 الخاصة بنا. ميزة مثل "وراثة البيئة" ستدفعها إلى مستوى آخر من الصيانة والميزة الحكيمة أيضًا.

لدي حوالي 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 

ويرتبط هذا بحقيقة أن ARGs هي متغيرات بيئة لكل مرحلة ، وليست قوالب.

أعلم أنك (وأنا) ربما لم نتمكن من رؤية ذلك في الحياة الواقعية (التلاعب بالسلسلة ربما ...).
لكن هذا يسلط الضوء على حقيقة أن بناء جملة عامل الإرساء يعتمد على بناء جملة غلاف الطبقة السفلية. هذا يجعل التوثيق يكذب: (راجع 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 var إلى ملف في المرحلة الأولى وانسخ هذا الملف في المرحلة الثانية؟ إلخ؟ مثله:

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 ، إلا أنه لا يعمل ، حيث أن cm_cc_commit_id يصبح هو $(cat /tmp/env.json) ، التعبير الأخير لا يتم تقييمه ، فإنه يظل حرفية. لذا فإن تحديد بيئة ديناميكية تبدو مستحيلة

ومع ذلك ، فإن ما ينجح هو الكتابة إلى ملف ، وجعل نقطة الدخول الخاصة بك ملف bash ، ثم القيام بذلك:

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

فكر في الأمر أكثر مثل هذا:

ليس لدى Dockerfile وسيلة للإعلان عن المتغيرات إلا عبر ARG . لا يتم نشر هذا تلقائيًا في كل مرحلة لأن هذا له تأثيرات على التخزين المؤقت.
يمكن أن يكون المنشئ أكثر ذكاءً ويحدد ما إذا كنت تحاول استخدام ARG في مرحلة معينة والسماح به ، ولكن بعد ذلك يجب أن يخمن ما إذا كنت تريد حرفياً أم لا.

لذا ، نعم ، تحتاج إلى استيراد ARG s عالميًا بشكل صريح إلى كل مرحلة تريد استخدامها فيها. ربما يمكن للمُنشئ أو linter تنفيذ تحذيرات للحالات التي يبدو أنك قد تحاول فيها استخدام ARG التي لم تستوردها.
لم يتم كسر هذا حسب التصميم ، فهو خيار واضح لاختيار التخزين المؤقت الأمثل على عدد أقل من سطور التعليمات البرمجية.

من المفهوم بالتأكيد أن استخدام ARG s العالمي أمر محير في البداية.

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات