Moby: Mempertahankan setelan ENV dan ARG ke semua tahapan selanjutnya dalam build multi-tahap

Dibuat pada 26 Jun 2018  ·  20Komentar  ·  Sumber: moby/moby

Deskripsi

Afaik, tidak ada cara untuk membagi variabel antar tahap (tolong koreksi saya jika saya salah). Untuk berbagi dari satu tahap pembuatan ke tahap berikutnya, satu-satunya pilihan adalah COPY file dari direktori satu tahap ke tahap saat ini. Kita dapat membangun sesuatu seperti file JSON dengan dump variabel waktu build, tetapi menurut saya masalah ini terlalu sering terjadi, dan akan membanjiri build multi-tahap kita dengan semua jenis masalah penguraian JSON yang gila. Bisakah kita mengotomatiskan ini?

Langkah-langkah untuk mereproduksi masalah:

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

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

Jelaskan hasil yang Anda terima:

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

Jelaskan hasil yang Anda harapkan:

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

Atau mungkin kita bisa menggunakan sesuatu seperti namespace untuk menginterpolasi ${base1.v1}

arebuilder kinquestion

Komentar yang paling membantu

Benar, instruksi Dockerfile, termasuk ENV vars dan ARG dicakup per tahap pembuatan, dan tidak akan disimpan di tahap berikutnya; ini memang disengaja.

Anda _can_, bagaimanapun, menyetel _global_ ARG (disetel sebelum tahap pembangunan pertama), dan menggunakan nilai itu di setiap tahap pembangunan;

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}

Saat membangun tanpa set --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

Ini menghasilkan:

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

Dan _dengan_ --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

Semua 20 komentar

Benar, instruksi Dockerfile, termasuk ENV vars dan ARG dicakup per tahap pembuatan, dan tidak akan disimpan di tahap berikutnya; ini memang disengaja.

Anda _can_, bagaimanapun, menyetel _global_ ARG (disetel sebelum tahap pembangunan pertama), dan menggunakan nilai itu di setiap tahap pembangunan;

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}

Saat membangun tanpa set --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

Ini menghasilkan:

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

Dan _dengan_ --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

cara lain adalah dengan menggunakan wadah dasar untuk beberapa tahap:

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

Izinkan saya menutup masalah ini, karena ini memang disengaja, tetapi semoga contoh di atas membantu Anda lebih jauh; juga jangan ragu untuk melanjutkan percakapan

Jika ini memang disengaja, maka desainnya salah. Tanpa cara yang ringkas untuk membagi variabel antar tahapan, tidak mungkin KERING. Ada situasi yang tidak dapat dihindari dan tidak dapat dihilangkan di mana banyak tahapan memerlukan akses ke variabel yang sama. Menduplikasi definisi rawan kesalahan, begitu juga dengan menduplikasi boilerplate untuk meretas definisi bersama ke setiap tahap.

Pendekatan "wadah dasar" sangat membatasi daya ekspresif karena ia memperbaiki wadah dasar untuk setiap tahapan, sementara ada kasus penggunaan yang valid di mana tahapan perantara masing-masing perlu menggunakan gambar dasar minimal yang berbeda yang menyediakan alat yang diperlukan untuk tahapan tersebut.

@tokopedia
Bisakah desain ini dipertimbangkan kembali?

Saya memiliki 11 gambar dasar yang hanya tersedia secara internal dan terutama digunakan untuk berbagai build kami. Saya mencoba membuat gambar dasar "FROM scratch", yang akan digunakan 11 gambar itu sebagai bagian dari build mulit-stage, karena beberapa logikanya sama di semua 11, dan ini termasuk variabel lingkungan. Jadi saya memiliki variabel lingkungan yang perlu disetel dalam setiap gambar, dan ingin menyetelnya dalam gambar dasar saya sehingga logika yang sama tidak perlu direplikasi di setiap gambar lainnya.

@ DMaxfield-BDS Cakupan ARG tidak akan berubah, tetapi Anda dapat mempertimbangkan untuk memiliki target build terpisah yang membuat gambar dasar "FROM scratch"; dorong itu ke registri, dan gunakan if untuk gambar Anda yang lain;

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

membangunnya, dan mendorongnya ke registri (internal) Anda, dan 11 gambar dasar tersebut dapat menggunakannya sebagai basis

FROM scratch-base
RUN your stuff

Ada proposal lain untuk memiliki EXPORT/IMPORT , yang mungkin cocok dengan beberapa kasus penggunaan lainnya; https://github.com/moby/moby/issues/32100

@tokopedia

Terima kasih atas tanggapan Anda. Satu hal yang tidak saya tunjukkan adalah bahwa 11 gambar lainnya ini semuanya memiliki gambar dasar yang berbeda (python, npm, postgres, openjdk, dll). Jadi yang ingin saya lakukan adalah meletakkan semua pengaturan / persiapan umum menjadi satu gambar dasar, termasuk pengaturan variabel lingkungan yang diperlukan yang digunakan oleh aplikasi perusahaan saya, seperti berikut:

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

Dorong ke registri internal saya. Kemudian, salah satu contoh dari 11 gambar lainnya akan melakukan hal berikut:

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>

Jika saya dapat menggunakan variabel lingkungan yang diatur pada gambar pertama, saya tidak perlu mengonfigurasi pengaturan yang sama ini di masing-masing dari 11 gambar lainnya. Ini memungkinkan saya satu titik konfigurasi, dan memungkinkan otomatisasi yang lebih baik.

Bagaimana saya bisa melakukan sesuatu seperti ini?

JALANKAN COMMIT_HASH = $ (git rev-parse --KEPALA pendek)

Bagaimana saya diizinkan untuk menggunakan variabel COMMIT_HASH dalam langkah-langkah RUN berikutnya? Saya dipaksa untuk mendefinisikan variabel dan menggunakan semuanya dalam satu langkah?

Menurut saya desain ini sangat terbatas. Mungkin seseorang bisa:

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

@ dnk8n inilah yang saya lakukan untuk mengatasinya:

Pada tahap pembuatan, saya melakukan:

RUN git rev-parse HEAD > commit_hash

kemudian di tahap lain saya menyalin file dengan data & dan mengatur variabel Lingkungan sebelum menjalankan aplikasi saya yang mengkonsumsinya:

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

Anda harus dapat melakukan sesuatu yang serupa untuk menyetel ARG.

Setidaknya kita harus bisa melakukan sesuatu seperti ini untuk menyalin variabel BAR dari tahap / gambar lain ke dalam lingkup build saat ini.

ENV --from=stage1 FOO=$BAR

Ini bahkan lebih penting ketika mempertimbangkan untuk menggunakan gambar eksternal sebagai panggung karena mungkin terdapat metadata penting dalam variabel lingkungan.

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

Saya bekerja dengan menyimpan lingkungan ke file di setiap tahap, menggunakan pola ini:

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

Bagaimana yang akhirnya bisa 'disimpan' ke 'buruh pelabuhan' sedemikian rupa sehingga terlihat saat menjalankan docker inspect Saya tidak yakin, tetapi bisa masuk ke / etc / profile misalnya.

image

Terlihat komentar terakhir begitu segar, hanya memberi saya gambaran sekilas tentang berapa banyak pengembang yang mempertanyakan keputusan desain di balik file dok. Rupanya "KERING" tidak termasuk dalam keterampilan yang dikuasai. Jika Anda ingin mencoba, tambahkan layer lain dan mulai membuat template dockerfiles ...

Sejujurnya saya sangat terkejut tentang ini ... "argumen global" sepertinya tidak berhasil untuk saya. Jika saya memindahkan build args ke atas sebelum yang lain, sepertinya mereka tidak ada dalam mengikuti langkah-langkah setelah FROM.

Sunting: Saya tidak memperhatikan bahwa saya harus mengulangi garis ARG tanpa nilai setelah masing-masing DARI ... itu berfungsi tetapi tidak ideal.

Cara eksplisit menyalin (mendapatkan) variabel lingkungan dari tahap lain adalah suatu keharusan. Saya memiliki kasus penggunaan yang valid untuk itu.
Berikut adalah contoh yang disederhanakan:

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

Sekarang, bagaimana saya bisa mengetahui versi benang jika itu didefinisikan dalam gambar node ( YARN_VERSION ) dalam variabel lingkungan?

Build multi-tahap diakomodasi dengan manfaat besar dalam pipeline CI kami. Fitur seperti "pewarisan lingkungan" akan mendorongnya ke tingkat pemeliharaan dan fitur yang bijaksana juga.

Saya memiliki sekitar 5 lapisan dengan penggunaan env vars yang berat dan setiap pembaruan adalah mimpi buruk. Sisi baiknya (?), Saya agak berpikir dua kali sebelum saya memperkenalkan panggung baru.

Implementasi ARG saat ini memberikan beberapa perilaku aneh:

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)

atau

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

Ini ditautkan oleh fakta bahwa ARG adalah variabel lingkungan per tahap, bukan pembuatan template.

Saya tahu bahwa Anda (dan saya) mungkin tidak pernah melihatnya dalam kehidupan nyata (manipulasi string mungkin ...).
Tapi itu menyoroti fakta bahwa sintaks buruh pelabuhan didasarkan pada sintaks shell underlayer. Itu membuat dokumentasinya berbohong: (lihat https://docs.docker.com/engine/reference/builder/#environment-replacement dan counter-example " RUN import ${X:-sys} ")

Mengapa RUN tidak menafsirkan ARG sebelum meluncurkan perintah seperti yang dilakukan 'make' sebelumnya?

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

24 jam sudah berakhir =)

_Warning_ solusi ini tidak berfungsi, meskipun saya berharap itu berhasil!

Tuliskan env var ke file di tahap pertama dan salin satu file itu di tahap kedua? dll? seperti ini:

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, kecuali itu tidak berfungsi, karena cm_cc_commit_id menjadi literal $(cat /tmp/env.json) , ekspresi terakhir tidak dievaluasi, tetap literal. Jadi pengaturan dynamic env vars sepertinya tidak mungkin?

Namun yang berhasil adalah menulis ke file, menjadikan entrypoint Anda file bash, lalu melakukan ini:

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

Pikirkan lebih lanjut seperti ini:

Dockerfile tidak memiliki sarana untuk mendeklarasikan variabel kecuali melalui global ARG . Ini tidak secara otomatis menyebar ke setiap tahap karena ini berpengaruh pada cache.
Pembangun bisa lebih pintar dan menentukan apakah Anda mencoba menggunakan ARG dalam tahap tertentu dan mengizinkannya, tetapi kemudian harus menebak apakah Anda menginginkan literal atau tidak.

Jadi ya, Anda perlu secara eksplisit mengimpor global ARG s ke dalam setiap tahap tempat Anda ingin menggunakannya. Mungkin builder atau linter dapat mengimplementasikan peringatan untuk kasus di mana Anda terlihat seperti mencoba menggunakan ARG yang belum Anda impor.
Ini tidak rusak oleh desain, ini adalah pilihan eksplisit untuk memilih caching yang optimal melalui lebih sedikit baris kode.

Sangatlah dipahami bahwa penggunaan global ARG s awalnya membingungkan.

Apakah halaman ini membantu?
0 / 5 - 0 peringkat