Dockerfile में मल्टीपल रन बनाम सिंगल रैन, बेहतर क्या है?


132

Dockerfile.1कई निष्पादित करता है RUN:

FROM busybox
RUN echo This is the A > a
RUN echo This is the B > b
RUN echo This is the C > c

Dockerfile.2 उनसे जुड़ता है:

FROM busybox
RUN echo This is the A > a &&\
    echo This is the B > b &&\
    echo This is the C > c

प्रत्येक RUNएक परत बनाता है, इसलिए मैंने हमेशा माना कि कम परतें बेहतर हैं और इस तरह Dockerfile.2बेहतर है।

यह स्पष्ट रूप से सच है जब एक RUNपिछले RUN(यानी yum install nano && yum clean all) द्वारा जोड़े गए कुछ को हटाता है , लेकिन ऐसे मामलों में जहां हर RUNकोई कुछ जोड़ता है, ऐसे कुछ बिंदु हैं जिन पर हमें विचार करने की आवश्यकता है:

  1. परतों को केवल पिछले एक के ऊपर एक अंतर जोड़ने के लिए माना जाता है, इसलिए यदि बाद की परत पिछले एक में जोड़े गए कुछ को नहीं हटाती है, तो दोनों तरीकों के बीच बहुत अधिक डिस्क स्थान की बचत का लाभ नहीं होना चाहिए ...

  2. डॉकर हब से समानांतर में परतें खींची जाती हैं, इसलिए Dockerfile.1, हालांकि शायद थोड़ा बड़ा है, सैद्धांतिक रूप से तेजी से डाउनलोड हो जाएगा।

  3. यदि 4 वाँ वाक्य (यानी echo This is the D > d) और स्थानीय रूप से पुनर्निर्माण किया जाता है, Dockerfile.1तो कैश के लिए तेज़ी से धन्यवाद का निर्माण होगा, लेकिन Dockerfile.2फिर से सभी 4 कमांडों को चलाना होगा।

तो, सवाल: डॉकफाइल करने का कौन सा बेहतर तरीका है?


1
सामान्य रूप से इसका उत्तर नहीं दिया जा सकता है क्योंकि यह स्थिति और छवि के उपयोग पर निर्भर करता है (आकार, डाउनलोड गति या भवन की गति के लिए अनुकूलन)
हेनरी

जवाबों:


99

जब संभव हो, मैं हमेशा एक साथ कमांड का विलय करता हूं जो कमांड के साथ फाइल बनाते हैं जो उन्हीं फाइलों को एक RUNलाइन में हटाते हैं । ऐसा इसलिए है क्योंकि प्रत्येक RUNपंक्ति छवि में एक परत जोड़ती है, आउटपुट काफी शाब्दिक रूप से फाइल सिस्टम में परिवर्तन होता है जिसे आप docker diffअस्थायी कंटेनर के साथ देख सकते हैं जो इसे बनाता है। यदि आप किसी भिन्न परत में बनाई गई फ़ाइल को हटाते हैं, तो सभी यूनियन फ़ाइल सिस्टम एक नई परत में फाइल सिस्टम परिवर्तन को पंजीकृत करता है, फ़ाइल अभी भी पिछली परत में मौजूद है और इसे नेटवर्क पर भेज दिया जाता है और डिस्क पर संग्रहीत किया जाता है। इसलिए यदि आप स्रोत कोड डाउनलोड करते हैं, तो इसे निकालें, इसे एक बाइनरी में संकलित करें, और फिर अंत में tgz और स्रोत फ़ाइलों को हटा दें, आप वास्तव में छवि आकार को कम करने के लिए यह सब एक ही परत में करना चाहते हैं।

अगला, मैं व्यक्तिगत रूप से अन्य छवियों और पुन: उपयोग कैशिंग उपयोग में उनकी क्षमता के आधार पर परतों को विभाजित करता हूं। यदि मेरे पास 4 छवियां हैं, तो सभी एक ही आधार छवि (जैसे डेबियन) के साथ, मैं उन सभी छवियों में से अधिकांश के लिए आम उपयोगिताओं का एक संग्रह खींच सकता हूं ताकि पहले रन कमांड में अन्य छवियों को कैशिंग से लाभ हो।

छवि कैश पुन: उपयोग को देखते हुए डॉकरीफाइल में ऑर्डर महत्वपूर्ण है। मैं किसी भी घटक को देखता हूं जो शायद ही कभी अपडेट करेगा, संभवतः केवल जब आधार छवि अपडेट होती है और उन को डॉर्कफाइल में डाल दिया जाता है। Dockerfile के अंत की ओर, मैं किसी भी कमांड को शामिल करता हूं जो त्वरित चलेगा और बार-बार बदल सकता है, उदाहरण के लिए एक होस्ट विशिष्ट यूआईडी के साथ एक उपयोगकर्ता जोड़ना या फ़ोल्डर बनाना और अनुमतियां बदलना। यदि कंटेनर में व्याख्यायित कोड (उदाहरण के लिए जावास्क्रिप्ट) शामिल है जिसे सक्रिय रूप से विकसित किया जा रहा है, तो यह यथासंभव देर से जोड़ा जाता है ताकि एक पुनर्निर्माण केवल उस एकल परिवर्तन को चलाता है।

इन परिवर्तनों के प्रत्येक समूह में, मैं परतों को कम से कम करने के लिए सबसे अच्छे रूप में समेकित करता हूं। इसलिए यदि 4 अलग-अलग स्रोत कोड फ़ोल्डर हैं, तो वे एक एकल फ़ोल्डर के अंदर रखे जाते हैं, इसलिए इसे एकल कमांड के साथ जोड़ा जा सकता है। Apt-get जैसी किसी चीज से कोई भी पैकेज इंस्टाल हो जाता है जब पैकेज मैनेजर ओवरहेड (अपडेट और सफाई) की मात्रा को कम करने के लिए एकल RUN में विलय कर दिया जाता है।


मल्टी-स्टेज बिल्ड के लिए अपडेट:

मैं बहु-मंच निर्माण के गैर-अंतिम चरणों में छवि के आकार को कम करने के बारे में बहुत कम चिंता करता हूं। जब इन चरणों को टैग नहीं किया जाता है और अन्य नोड्स के लिए भेज दिया जाता है, तो आप प्रत्येक कमांड को एक अलग RUNलाइन में विभाजित करके कैश पुन: उपयोग की संभावना को अधिकतम कर सकते हैं ।

हालाँकि, यह परतों को स्क्वैश करने का एक सही समाधान नहीं है क्योंकि आप चरणों के बीच कॉपी करते हैं फाइलें हैं, और बाकी छवि मेटा-डेटा जैसे पर्यावरण चर सेटिंग्स, एंट्रीपॉइंट, और कमांड नहीं। और जब आप एक लिनक्स वितरण में पैकेज स्थापित करते हैं, तो लाइब्रेरीज़ और अन्य निर्भरताएँ पूरे सिस्टम में बिखरी हो सकती हैं, जिससे सभी निर्भरताओं की एक प्रतिलिपि मुश्किल हो जाती है।

इस वजह से, मैं एक CI / CD सर्वर पर बायनेरिज़ के निर्माण के लिए एक प्रतिस्थापन के रूप में मल्टी-स्टेज बिल्ड का उपयोग करता हूं, ताकि मेरे CI / CD सर्वर को चलाने के लिए टूलिंग की आवश्यकता हो docker build, और jdk, नोडज, गो, और न हो। कोई अन्य संकलित उपकरण स्थापित किया गया।


30

उनकी सर्वोत्तम प्रथाओं में सूचीबद्ध आधिकारिक उत्तर (आधिकारिक चित्र इनका पालन करना चाहिए)

लेयर की संख्या कम से कम करें

आपको Dockerfile की पठनीयता (और इस प्रकार दीर्घकालिक रख-रखाव) के बीच संतुलन खोजने की जरूरत है और इसके उपयोग की जाने वाली परतों की संख्या को कम करना। आपके द्वारा उपयोग की जाने वाली परतों की संख्या के बारे में रणनीतिक और सतर्क रहें।

डोकर 1.10 के बाद से COPY, ADDऔर RUNबयानों अपनी छवि को एक नई परत जोड़ें। इन कथनों का उपयोग करते समय सतर्क रहें। आदेशों को एक RUNकथन में संयोजित करने का प्रयास करें । पठनीयता के लिए आवश्यक होने पर ही इसे अलग करें।

अधिक जानकारी: https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/#/minimize-the-number-of-layers

अपडेट: डॉकटर में बहु मंच> 17.05

मल्टी-स्टेज बिल्ड के साथ आप FROMअपने डॉकरीफाइल में कई स्टेटमेंट का उपयोग कर सकते हैं । प्रत्येक FROMकथन एक चरण है और इसकी अपनी आधार छवि हो सकती है। अंतिम चरण में आप अल्पाइन जैसी न्यूनतम आधार छवि का उपयोग करते हैं, पिछले चरणों से बिल्ड कलाकृतियों की नकल करते हैं और रनटाइम आवश्यकताओं को स्थापित करते हैं। इस चरण का अंतिम परिणाम आपकी छवि है। तो यह वह जगह है जहाँ आप परतों के बारे में चिंता करते हैं जैसा कि पहले बताया गया है।

हमेशा की तरह, डॉकटर के पास मल्टी-स्टेज बिल्ड पर शानदार डॉक्स हैं । यहाँ एक त्वरित अंश है:

मल्टी-स्टेज बिल्ड के साथ, आप अपने Dockerfile में कई FROM स्टेटमेंट का उपयोग करते हैं। प्रत्येक FROM अनुदेश एक अलग आधार का उपयोग कर सकता है, और उनमें से प्रत्येक निर्माण का एक नया चरण शुरू करता है। आप चुनिंदा कलाकृतियों को एक चरण से दूसरे चरण में कॉपी कर सकते हैं, जो आपको अंतिम छवि में नहीं चाहिए।

इस बारे में एक महान ब्लॉग पोस्ट यहाँ पाया जा सकता है: https://blog.alexellis.io/mutli-stage-docker-builds/

आपकी बातों का जवाब देने के लिए:

  1. हां, परतें भिन्न की तरह हैं। मुझे नहीं लगता कि वहाँ परतें हैं अगर वहाँ बिल्कुल शून्य परिवर्तन है। समस्या यह है कि एक बार जब आप परत # 2 में कुछ स्थापित / डाउनलोड करते हैं, तो आप इसे परत # 3 में नहीं निकाल सकते। इसलिए एक बार कुछ परत में लिखे जाने के बाद, उसको हटाकर छवि का आकार कम नहीं किया जा सकता है।

  2. हालांकि परतों को समानांतर में खींचा जा सकता है, इसे संभावित रूप से तेज बना देता है, प्रत्येक परत निस्संदेह छवि का आकार बढ़ाती है, भले ही वे फाइलें निकाल रहे हों।

  3. यदि आप अपनी डॉक फ़ाइल को अपडेट कर रहे हैं तो हाँ, कैशिंग उपयोगी है। लेकिन यह एक दिशा में काम करता है। यदि आपके पास 10 परतें हैं, और आप परत # 6 बदलते हैं, तो आपको अभी भी परत # 6- # 10 से सब कुछ पुनर्निर्माण करना होगा। तो यह बहुत बार नहीं है कि यह निर्माण प्रक्रिया को गति देगा, लेकिन यह अनावश्यक रूप से आपकी छवि के आकार को बढ़ाने की गारंटी है।


इस जवाब को अपडेट करने के लिए मुझे याद दिलाने के लिए @ मोहान को धन्यवाद ।


1
यह अब पुराना हो गया है - नीचे उत्तर देखें।
मोहन

1
@ अनुस्मारक के लिए धन्यवाद! मैंने उपयोगकर्ताओं की सहायता के लिए पोस्ट अपडेट की।
मेंज़ो विजेंगा

19

ऐसा लगता है कि उपरोक्त उत्तर पुराने हैं। डॉक्स नोट:

डॉकर 17.05 से पहले, और इससे भी अधिक, डॉकर 1.10 से पहले, आपकी छवि में परतों की संख्या को कम करना महत्वपूर्ण था। निम्नलिखित सुधारों ने इस आवश्यकता को कम कर दिया है:

[...]

डॉकर 17.05 और उच्च-चरण समर्थन बहु-चरण बिल्ड के लिए, जो आपको केवल उन कलाकृतियों को कॉपी करने की अनुमति देता है जिनकी आपको अंतिम छवि में आवश्यकता है। यह आपको अंतिम छवि का आकार बढ़ाए बिना अपने मध्यवर्ती बिल्ड चरणों में उपकरण और डिबग जानकारी शामिल करने की अनुमति देता है।

https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/#minimize-the-number-of-layers

तथा

ध्यान दें कि इस उदाहरण ने कृत्रिम रूप से एक अतिरिक्त परत बनाने से बचने के लिए, Bash && ऑपरेटर का उपयोग करके दो RUN कमांड को कृत्रिम रूप से संपीड़ित किया है। यह विफलता-प्रवण और बनाए रखने में कठिन है।

https://docs.docker.com/engine/userguide/eng-image/multistage-build/

मल्टीस्टेज बिल्ड का उपयोग करने और Dockerfileएस को पढ़ने योग्य बनाए रखने के लिए सबसे अच्छा अभ्यास बदल गया है ।


जबकि मल्टीस्टेज बिल्ड बैलेंस रखने के लिए एक अच्छा विकल्प लगता है, इस सवाल का वास्तविक निर्धारण तब होगा जब docker image build --squashविकल्प प्रयोगात्मक के बाहर चला जाएगा ।
यज्ञो

2
@Yajo - मैं squashपिछले प्रयोगात्मक होने के बारे में उलझन में हूँ । इसमें कई नौटंकी हैं और केवल बहु-मंच बनाने से पहले इसका अर्थ है। बहु मंच के साथ आपको केवल अंतिम चरण को अनुकूलित करने की आवश्यकता होती है जो बहुत आसान है।
मेंज़ो विजेंगा

1
@Yajo उस पर विस्तार करने के लिए, अंतिम चरण में केवल परतें अंतिम छवि के आकार से कोई अंतर रखती हैं। इसलिए यदि आप अपने सभी बिल्डर gubbins को पहले के चरणों में रखते हैं और अंतिम चरण में सिर्फ पैकेज स्थापित करते हैं और पहले चरणों से फ़ाइलों की प्रतिलिपि बनाते हैं, तो सब कुछ खूबसूरती से काम करता है और स्क्वैश की आवश्यकता नहीं होती है।
मोहन

3

यह waht पर निर्भर करता है जिसे आप अपनी छवि परतों में शामिल करते हैं।

मुख्य बिंदु संभव के रूप में कई परतों को साझा कर रहा है:

खराब उदाहरण:

Dockerfile.1

RUN yum install big-package && yum install package1

Dockerfile.2

RUN yum install big-package && yum install package2

अच्छा उदाहरण:

Dockerfile.1

RUN yum install big-package
RUN yum install package1

Dockerfile.2

RUN yum install big-package
RUN yum install package2

एक अन्य सुझाव हटा रहा है केवल इतना उपयोगी नहीं है अगर यह एक ही परत पर होता है जैसे कि जोड़ने / स्थापित करने की क्रिया।


क्या ये 2 वास्तव में RUN yum install big-packageकैश से साझा होंगे ?
यजो

हां, वे एक ही परत साझा करेंगे, बशर्ते वे एक ही आधार से शुरू करें।
ओंद्र kaइस्का
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.