ثلاثة حيل بسيطة للحد من الصور عامل ميناء

الصورة

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


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


ربما تعلم أن معظم ملفات Docker لها ميزات غريبة إلى حد ما ، على سبيل المثال:


 FROM ubuntu RUN apt-get update && apt-get install vim 

حسنًا ، لماذا && هنا؟ ليس من السهل تشغيل بيانين RUN ، مثل هنا؟


 FROM ubuntu RUN apt-get update RUN apt-get install vim 

بدءًا من الإصدار Docker 1.10 ، يضيف مشغلو COPY و ADD و RUN طبقة جديدة إلى الصورة. في المثال السابق ، تم إنشاء طبقتين بدلاً من طبقة واحدة.


الصورة


طبقات كما يرتكب بوابة.


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


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


1. ادمج عدة طبقات في طبقة واحدة باستخدام التجميع المرحلي لصور Docker


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


لنقم بإنشاء حاوية Node.js.


لنبدأ مع index.js :


 const express = require('express') const app = express() app.get('/', (req, res) => res.send('Hello World!')) app.listen(3000, () => { console.log(`Example app listening on port 3000!`) }) 

و package.json :


 { "name": "hello-world", "version": "1.0.0", "main": "index.js", "dependencies": { "express": "^4.16.2" }, "scripts": { "start": "node index.js" } } 

حزمة التطبيق مع Dockerfile التالية:


 FROM node:8 EXPOSE 3000 WORKDIR /app COPY package.json index.js ./ RUN npm install CMD ["npm", "start"] 

قم بإنشاء صورة:


 $ docker build -t node-vanilla . 

تأكد من أن كل شيء يعمل:


 $ docker run -p 3000:3000 -ti --rm --init node-vanilla 

يمكنك الآن اتباع الرابط: http: // localhost: 3000 وراجع "Hello World!" هناك.


في Dockerfile الآن مشغلي COPY و RUN ، لذلك نقوم بإصلاح الزيادة بطبقتين على الأقل ، مقارنة بالصورة الأصلية:


 $ docker history node-vanilla IMAGE CREATED BY SIZE 075d229d3f48 /bin/sh -c #(nop) CMD ["npm" "start"] 0B bc8c3cc813ae /bin/sh -c npm install 2.91MB bac31afb6f42 /bin/sh -c #(nop) COPY multi:3071ddd474429e1… 364B 500a9fbef90e /bin/sh -c #(nop) WORKDIR /app 0B 78b28027dfbf /bin/sh -c #(nop) EXPOSE 3000 0B b87c2ad8344d /bin/sh -c #(nop) CMD ["node"] 0B <missing> /bin/sh -c set -ex && for key in 6A010… 4.17MB <missing> /bin/sh -c #(nop) ENV YARN_VERSION=1.3.2 0B <missing> /bin/sh -c ARCH= && dpkgArch="$(dpkg --print… 56.9MB <missing> /bin/sh -c #(nop) ENV NODE_VERSION=8.9.4 0B <missing> /bin/sh -c set -ex && for key in 94AE3… 129kB <missing> /bin/sh -c groupadd --gid 1000 node && use… 335kB <missing> /bin/sh -c set -ex; apt-get update; apt-ge… 324MB <missing> /bin/sh -c apt-get update && apt-get install… 123MB <missing> /bin/sh -c set -ex; if ! command -v gpg > /… 0B <missing> /bin/sh -c apt-get update && apt-get install… 44.6MB <missing> /bin/sh -c #(nop) CMD ["bash"] 0B <missing> /bin/sh -c #(nop) ADD file:1dd78a123212328bd… 123MB 

كما ترون ، زادت الصورة النهائية بخمس طبقات جديدة: واحدة لكل عامل في Dockerfile لدينا. لنجرب الآن بناء Docker التدريجي. نستخدم نفس Dockerfile ، يتكون من جزأين:


 FROM node:8 as build WORKDIR /app COPY package.json index.js ./ RUN npm install FROM node:8 COPY --from=build /app / EXPOSE 3000 CMD ["index.js"] 

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


الصورة


لنجربها. أولاً ، قم بإنشاء حاوية:


 $ docker build -t node-multi-stage . 

التحقق من التاريخ:


 $ docker history node-multi-stage IMAGE CREATED BY SIZE 331b81a245b1 /bin/sh -c #(nop) CMD ["index.js"] 0B bdfc932314af /bin/sh -c #(nop) EXPOSE 3000 0B f8992f6c62a6 /bin/sh -c #(nop) COPY dir:e2b57dff89be62f77… 1.62MB b87c2ad8344d /bin/sh -c #(nop) CMD ["node"] 0B <missing> /bin/sh -c set -ex && for key in 6A010… 4.17MB <missing> /bin/sh -c #(nop) ENV YARN_VERSION=1.3.2 0B <missing> /bin/sh -c ARCH= && dpkgArch="$(dpkg --print… 56.9MB <missing> /bin/sh -c #(nop) ENV NODE_VERSION=8.9.4 0B <missing> /bin/sh -c set -ex && for key in 94AE3… 129kB <missing> /bin/sh -c groupadd --gid 1000 node && use… 335kB <missing> /bin/sh -c set -ex; apt-get update; apt-ge… 324MB <missing> /bin/sh -c apt-get update && apt-get install… 123MB <missing> /bin/sh -c set -ex; if ! command -v gpg > /… 0B <missing> /bin/sh -c apt-get update && apt-get install… 44.6MB <missing> /bin/sh -c #(nop) CMD ["bash"] 0B <missing> /bin/sh -c #(nop) ADD file:1dd78a123212328bd… 123MB 

معرفة ما إذا كان حجم الملف قد تغير:


 $ docker images | grep node- node-multi-stage 331b81a245b1 678MB node-vanilla 075d229d3f48 679MB 

نعم ، لقد أصبح أصغر ، ولكن ليس بشكل كبير حتى الآن.


2. نزيل كل ما لا لزوم له من الحاوية باستخدام distroless


توفر لنا الصورة الحالية Node.js yarn و npm و bash والعديد من الثنائيات المفيدة الأخرى. أيضا ، لأنه يقوم على أوبونتو. وبالتالي ، عند نشره ، نحصل على نظام تشغيل متكامل مع العديد من الثنائيات والمرافق المفيدة.


ومع ذلك ، نحن لسنا بحاجة لهم لتشغيل الحاوية. التبعية الوحيدة المطلوبة هي Node.js.


يجب أن تدعم حاويات الرصيف تشغيل عملية واحدة وتحتوي على الحد الأدنى الضروري من الأدوات لتشغيلها. نظام التشغيل بأكمله غير مطلوب لهذا الغرض.


حتى نتمكن من الحصول على كل شيء منه باستثناء Node.js.


لكن كيف؟


لقد توصلت Google بالفعل إلى حل مماثل - GoogleCloudPlatform / distroless .


يقرأ وصف المستودع:


تحتوي الصور غير الموزعة فقط على التطبيق وتبعياته. لا يوجد مديرو حزم أو أصداف أو برامج أخرى توجد عادةً في توزيع Linux القياسي.


هذا هو ما تحتاجه!


قم بتشغيل Dockerfile للحصول على صورة جديدة:


 FROM node:8 as build WORKDIR /app COPY package.json index.js ./ RUN npm install FROM gcr.io/distroless/nodejs COPY --from=build /app / EXPOSE 3000 CMD ["index.js"] 

نجمع الصورة كالمعتاد:


 $ docker build -t node-distroless . 

يجب أن يعمل التطبيق بشكل جيد. للتحقق ، قم بتشغيل الحاوية:


 $ docker run -p 3000:3000 -ti --rm --init node-distroless 

وانتقل إلى http: // localhost: 3000 . هل أصبحت الصورة أسهل بدون ثنائيات إضافية؟


 $ docker images | grep node-distroless node-distroless 7b4db3b7f1e5 76.7MB 

تماما مثل ذلك! الآن يزن 76.7 ميغابايت فقط ، بقدر أقل من 600 ميغابايت!


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


 $ docker exec -ti <insert_docker_id> bash 

الاتصال بحاوية قيد التشغيل وبدء تشغيل bash يشبه إلى حد بعيد إنشاء جلسة SSH.


ولكن نظرًا لأن distroless هي نسخة مجردة من نظام التشغيل الأصلي ، فلا توجد ثنائيات إضافية ، أو في الواقع ، shell!


كيفية الاتصال بحاوية التشغيل إذا لم يكن هناك قذيفة؟


الشيء الأكثر إثارة للاهتمام هو أن لا شيء.


هذا ليس جيدًا جدًا ، حيث يمكن تنفيذ الثنائيات فقط في حاوية. والوحيد الذي يمكن إطلاقه هو Node.js:


 $ docker exec -ti <insert_docker_id> node 

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


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


ولكن ماذا لو كنا لا نزال بحاجة إلى تصحيح الأخطاء ، ومع ذلك نريد أن تكون صورة عامل الإرساء أصغر؟


3. تقليل الصور الأساسية مع جبال الألب


يمكنك استبدال distroless مع صورة جبال الألب.


Alpine Linux هو توزيع خفيف الوزن موجه للأمان يعتمد على musl libc و busybox . لكننا لن نأخذ كلمة ، بل نراجعها.


Dockerfile باستخدام node:8-alpine Dockerfile :


 FROM node:8 as build WORKDIR /app COPY package.json index.js ./ RUN npm install FROM node:8-alpine COPY --from=build /app / EXPOSE 3000 CMD ["npm", "start"] 

قم بإنشاء صورة:


 $ docker build -t node-alpine . 

تحقق الحجم:


 $ docker images | grep node-alpine node-alpine aa1f85f8e724 69.7MB 

في الإخراج ، لدينا 69.7 ميغابايت - وهذا أقل من صورة غير منتظمة.


دعونا نتحقق مما إذا كان من الممكن الاتصال بحاوية عاملة (في حالة صورة المنحل ، لم نتمكن من القيام بذلك).


إطلاق الحاوية:


 $ docker run -p 3000:3000 -ti --rm --init node-alpine Example app listening on port 3000! 

والاتصال:


 $ docker exec -ti 9d8e97e307d7 bash OCI runtime exec failed: exec failed: container_linux.go:296: starting container process caused "exec: \"bash\": executable file not found in $PATH": unknown 

دون جدوى. ولكن ربما تحتوي الحاوية على ...


 $ docker exec -ti 9d8e97e307d7 sh / # 

عظيم! لقد نجحنا في الاتصال بالحاوية ، وفي نفس الوقت تكون صورتها أصغر أيضًا. ولكن هنا كانت هناك بعض الفروق الدقيقة.


تعتمد صور جبال الألب على muslc ، وهي مكتبة قياسية بديلة لـ C. بينما تعتمد معظم توزيعات Linux ، مثل Ubuntu و Debian و CentOS على glibc. ويعتقد أن كل من هاتين المكتبات توفر واجهة واحدة للعمل مع النواة.


ومع ذلك ، لديهم أهداف مختلفة: glibc هو الأكثر شيوعًا وسريعًا ، بينما يشغل muslc مساحة أقل ويتم كتابته بانحياز أمان. عندما يجمع تطبيق ما ، كقاعدة عامة ، يتم تجميعه إلى مكتبة C. معينة ، وإذا كنت بحاجة إلى استخدامه مع مكتبة أخرى ، فسيتعين عليك إعادة الترجمة.


بمعنى آخر ، قد يؤدي إنشاء حاويات على صور جبال الألب إلى أحداث غير متوقعة ، لأن مكتبة C القياسية المستخدمة فيها مختلفة. سيكون الاختلاف ملحوظًا عند العمل مع الثنائيات المترجمة مسبقًا ، مثل امتدادات Node.js لـ C ++.


على سبيل المثال ، لا تعمل حزمة PhantomJS الجاهزة على جبال الألب.


فما هي الصورة الأساسية للاختيار؟


نظرة على جبال الألب أو العفة أو الفانيليا - بالطبع ، من الأفضل أن تقرر وفقًا للموقف.


إذا كنت تتعامل مع همز وكان الأمن مهمًا ، فربما يكون النقل غير المناسب هو الأفضل.


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


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


إذا كان حجم صورة عامل الإرساء مهمًا للغاية بالنسبة لك لسبب ما ، فمن المؤكد أنه يستحق إلقاء نظرة فاحصة على الصور المستندة إلى جبال الألب.


إنها صغيرة جدًا ، ولكن على حساب التوافق. يستخدم Alpine مكتبة C مختلفة مختلفة قليلاً ، muslc ، لذلك في بعض الأحيان سوف تظهر المشاكل. يمكن العثور على الأمثلة على الرابطين: https://github.com/grpc/grpc/issues/8528 و https://github.com/grpc/grpc/issues/6126 .


تعتبر صور الفانيليا مثالية للاختبار والتطوير.


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


تلخيص حجم صور Docker المستلمة:


node:8 681 ميجابايت
node:8 مع 678MB بناء تزايدي
gcr.io/distroless/nodejs 76.7 ميجابايت
node:8-alpine 69.7 ميغابايت


فراق الكلمات من المترجم


اقرأ مقالات أخرى على مدونتنا:


النسخ الاحتياطية الدولة في Kubernetes


النسخ الاحتياطي لعدد كبير من مشاريع الويب غير المتجانسة


الروبوت برقية ل Redmine. كيفية تبسيط الحياة لنفسك والناس

Source: https://habr.com/ru/post/ar437372/


All Articles