بدأت قصة تحسين الصور لتطبيقات جافا بمقال الربيع Spring Spring in a Container . وناقش الجوانب المختلفة لإنشاء صور عامل ميناء لتطبيقات التمهيد الربيع ، بما في ذلك قضية مثيرة للاهتمام مثل تقليل حجم الصور. بالنسبة إلى فرقنا ، كان هذا مناسبًا لعدة أسباب ، لذلك قررنا تطبيق هذا الحل على طلباتنا.
كما يحدث غالبًا ، لم يتم إقلاع كل شيء في المرة الأولى ، فقد كانت هناك فروق دقيقة في المشروعات متعددة الوحدات ومحاولة لدفع كل هذا على نظام CI ، لذلك ستجد في هذه المقالة حلاً لهذه المشكلات.
الهدف من التحسين هو تقليل الفرق بين الصور الناتجة من التجميع إلى التجميع ، مما يعطي نتيجة جيدة في عملية التسليم المستمر ، لذلك إذا كنت مهتمًا بتقليل حجم الصورة على هذا النحو ، يمكنك الرجوع إلى مقالات أخرى على المحور
إذا لم يكن عليك توضيح سبب قيامك بشيء ما باستخدام تطبيق التمهيد متعدد الأمتار قبل وضعه في الصورة ، فيمكنك الانتقال فورًا إلى وصف طريقة التحسين . إذا تمكنت من التعرف على المقالة من مدونة Spring ، فيمكنك متابعة حل المشكلات الموجودة .
لماذا هذا كل شيء ، أو الجانب الآخر من جرة الدهون
بشكل افتراضي ، فإن الجرة التي تنتجها Spring Boot هي ملف جرة قابل للتنفيذ يحتوي على رمز التطبيق وجميع تبعياته.
ميزة هذا النهج واضحة: إنه مناسب للعمل مع ملف واحد ، ولديه كل ما تحتاجه للتشغيل من خلال java -jar <myapp>.jar
. Dockerfile تافهة وليس من الفائدة.
الجانب السلبي هو عدم كفاءة التخزين. في تطبيق الإقلاع الكلاسيكي ، من الواضح أن نسبة الشفرات والمكتبات لا تؤيد الشفرة الخاصة بنا. على سبيل المثال ، سوف يستغرق تطبيق فارغ يحتوي على جزء ويب ومكتبات للعمل مع قاعدة البيانات ، والذي يمكن إنشاؤه عبر start.spring.io ، 20 ميغابايت ، منها 98٪ ستكون مكتبات. وهذه النسبة لا تتغير كثيرا خلال عملية التطوير.
لكننا نجمع التطبيق أكثر من مرة ، ولكن بشكل منتظم على خادم CI ، ومن ثم نشره على سلسلة من البيئات. وبالتالي ، تنمو 10 مجموعات بسرعة 200 ميجابايت ، و 100 - بسرعة 2 جيجابايت ، والتي سوف تستغرق التعديلات منها القليل جدًا.
يمكن القول أنه بالنسبة للتكلفة الحالية للتخزين ، فهذه أرقام مثيرة للسخرية ولا يتعين عليك قضاء بعض الوقت في تحسينات كهذه ، لكن كل هذا يتوقف على حجم المؤسسة وعدد التطبيقات التي تحتاج إلى تخزين صورها. يمكن أن تحفز شروط النشر أيضًا بقوة: عندما يكون السجل والخادم في مكان قريب ، فإن اختلافًا قدره 100 ميجابايت ليس ملحوظًا جدًا ، ولكن في الأنظمة الموزعة ، قد يكون هذا الأمر أكثر أهمية ، خاصة عندما تحتاج إلى النشر في بلدان معينة مثل الصين بجدار الحماية الخاص بها والقنوات غير المستقرة. إلى العالم الخارجي.
لذلك ، مع معرفة الأسباب ، حان الوقت للتحسين.
نقوم بتحسين التجميع ، أو ما الذي يمكن تعلمه من مدونة الربيع
تقدم هذه المقالة حلاً معقولًا: بدلاً من طبقة واحدة تم إنشاؤها بواسطة COPY my-jar.jar app.jar
، نحتاج إلى عمل طبقات متعددة.
تحتوي الطبقة على مكتبات ، والثاني هو الكود الخاص بنا. للقيام بذلك ، تحتاج إلى فك ضغط ملف jar ونسخ المحتويات إلى طبقات مختلفة من الصورة.
البرنامج النصي لإعداد ملف jar يشبه هذا:
قد يبدو الإرساء باستخدام بناء متعدد المراحل بهذا الشكل
FROM openjdk:8-jdk-alpine as build WORKDIR /wd COPY prepare_for_docker.sh /usr/local/bin/prepare_for_docker COPY target/demo.jar /wd/app.jar RUN prepare_for_docker /wd/app.jar FROM openjdk:8-jdk-alpine COPY --from=build /wd/docker-dist/BOOT-INF/lib /app/lib COPY --from=build /wd/docker-dist/META-INF /app/META-INF COPY --from=build /wd/docker-dist/BOOT-INF/classes /app ENTRYPOINT ["java","-cp","app:app/lib/*","com.example.demo.DemoApplication"]
في المرحلة الأولى ، نقوم بنسخ كل ما نحتاجه ، ونقوم بتشغيل البرنامج النصي لفك ضغط ملف jar ، وفي المرحلة الثانية ، نضع مكتبات منفصلة ورمزنا بشكل منفصل في طبقات.
من السهل التأكد من قابلية التشغيل:
- جمع لأول مرة
- قم بإجراء أي تغيير على الكود الخاص بنا.
- نطلق
docker build
مرة أخرى ونرى الخطوط العزيزة Using cache
عند نسخ دليل lib بأكمله
... Step 5/10 : RUN prepare_for_docker app.jar ---> Running in c8e422491eb2 Removing intermediate container c8e422491eb2 ---> c7dcec4ae18a Step 6/10 : FROM openjdk:8-jdk-alpine ---> a3562aa0b991 Step 7/10 : COPY --from=build /wd/docker-dist/BOOT-INF/lib /app/lib ---> Using cache ---> 01b600d7e350 Step 8/10 : COPY --from=build /wd/docker-dist/META-INF /app/META-INF ---> Using cache ---> 5c0c03a3c8f1 Step 9/10 : COPY --from=build /wd/docker-dist/BOOT-INF/classes /app ---> 5ffed6ee5696 Step 10/10 : ENTRYPOINT ["java","-cp","app:app/lib/*","com.example.demo.DemoApplication"] ---> Running in 99957250fe5d Removing intermediate container 99957250fe5d ---> 6735799d9f32 Successfully built 6735799d9f32 Successfully tagged boot2-sample:latest
إحدى الطرق الواضحة لتحسين هذا النهج هي إنشاء صورة أساسية صغيرة باستخدام برنامج نصي حتى لا يتم سحبها من مشروع إلى مشروع. وبالتالي ، تصبح الطبقة الأولى أكثر إيجازًا.
FROM zeldigas/java-layered-builder as build COPY target/demo.jar app.jar RUN prepare_for_docker app.jar
نحن بصدد الانتهاء من الحل
كما ذكرنا سابقًا في بداية المقالة ، يعمل الحل ، ولكن أثناء العملية ، تم العثور على مشكلتين سيتم مناقشتهما لاحقًا.
ليست كل الملفات الموجودة في lib
مكتبة متساوية
إذا كان مشروعك متعدد الوحدات (على الأقل ، هناك وحدة A ، التي تعتمد عليها الوحدة ب ، ويتم تجميعها كجرار دسم زنبركي) ، مع تطبيق حل أصلي عليه ، ستجد أنه لا يوجد تخزين مؤقت للطبقة. ما الخطأ الذي حدث؟
المسألة في وحدات إضافية: فهي مصادر للتغييرات المستمرة للطبقة ، حتى لو لم تقم بإجراء أي تغييرات على رمز الوحدة النمطية. ويرجع ذلك إلى خصوصية إنشاء ملفات جرة maven (مع الوضع ، الوضع أفضل قليلاً ، ولكن غير متأكد). إن مهمة الحصول على قطع أثرية قابلة للاستنساخ ليست موضوع هذا المقال (على الرغم من أنها مثيرة للاهتمام وقابلة للتحقيق بالطبع) ، لذلك سننتقل إلى حل بسيط إلى حد ما.
نقوم بتوزيع محتويات lib
إلى lib
، بعد تفريغها ، مع فصل وحدات المشروع عن المكتبات الأخرى. دعونا ننهي البرنامج النصي لتفريغ جرة الدهون:
ونتيجة لذلك ، بدأ البرنامج النصي في دعم نقل المعلمات الإضافية (انظر 1 و 2). إذا تم تمرير وسيطات إضافية (3) ، فسيتم اعتبار كل واحدة منها بادئة لاسم الملف الذي ننقله (4) إلى دليل منفصل.
مثال Dockerfile لسيناريو مع واحد إضافي. shared-module
وإصدار 1.0-SNAPSHOT
FROM openjdk:8-jdk-alpine as build COPY target/demo.jar /wd/app.jar RUN prepare_for_docker /wd/app.jar shared-module-1.0 FROM openjdk:8-jdk-alpine COPY --from=build /wd/docker-dist/BOOT-INF/lib /app/lib COPY --from=build /wd/docker-dist/app-lib /app/lib COPY --from=build /wd/docker-dist/META-INF /app/META-INF COPY --from=build /wd/docker-dist/BOOT-INF/classes /app ENTRYPOINT ["java","-cp","app:app/lib/*","com.example.demo.DemoApplication"]
تعمل على خادم CI
بعد تصحيح كل شيء محليًا ، بالرضا عن النتيجة ، بدأنا العمل على خادم CI ومن سجلات البناء وجدنا أن المعجزة لم تحدث ، أو بالأحرى لم تكن النتائج ثابتة: في بعض الحالات ، تم تنفيذ التخزين المؤقت ، وفي المرة التالية كانت جميع الطبقات جديدة.
نتيجة لذلك ، تم اكتشاف الجاني - ذاكرة التخزين المؤقت لرسو السفن ، أو بالأحرى ، غيابه في حالة العوامل المختلفة (لم يتم تجميع مجموعتنا على وكيل محدد لنظام CI). كما اتضح فيما بعد ، إذا لم تكن هناك طبقات مناسبة في ذاكرة التخزين المؤقت لرسو السفن ، فسيتم الحصول على طبقات ذات مجموعة اختبارية مختلفة من نفس مجموعة الملفات. يمكنك التحقق من ذلك محليًا ، عن طريق تشغيل --no-cache
الخيار --no-cache
، أو عن طريق --no-cache
مرة ثانية عن طريق حذف الصورة أولاً وجميع الطبقات الوسيطة. نتيجة لذلك ، تحصل على طبقة اختبارية مختلفة تمامًا ، والتي تنكر كل الجهود السابقة.

هناك عدة طرق لحل المشكلة:
- إذا كان نظام CI يدعم هذا خارج الصندوق (على سبيل المثال ، فإن دائرة CI في جزء الخطط لها دعم مدمج لذاكرة التخزين المؤقت المشتركة أثناء التجميعات)
- خلط قسم مع ذاكرة التخزين المؤقت عامل ميناء بين وكلاء
- استفد من عامل إدارة التخزين المؤقت المدمج (- ذاكرة التخزين المؤقت
--cache-from
)
ذهبنا في الطريق الثالث ، لأنه في حالتنا كان الأمر أبسط. يسمح لك هذا الخيار بإخبار البرنامج الخفي عن الصور التي يجب أن يأخذها في الاعتبار ومحاولة استخدامها للتخزين المؤقت أثناء التجميع. يمكنك تحديد العديد من الصور التي تراها ضرورية ، الشيء الرئيسي هو أنها موجودة في نظام الملفات. إذا لم تكن الصورة المحددة موجودة ، فسيتم تجاهلها ببساطة ، لذا يجب عليك السحب قبل البناء.
إليك ما يشبه تجميع الحاوية مع هذا الأسلوب:
set -e version=...
نحن نحاول إعادة استخدام الطبقات فقط من أحدث الصور ، وهو ما يكفي في كثير من الأحيان ، ولكن لا أحد يكترث ليختتم منطقًا أكثر تعقيدًا ويتراجع عن بعض الإصدارات أو يعتمد على معرف vcs المرتكب.
نعمل على تكييف هذا النهج مع إمكانيات CI الخاصة بك والحصول على إعادة استخدام موثوقة للطبقات مع المكتبات.
في المجموع
يُظهر الحل نتائج جيدة ، خاصةً عند استخدامه في المشروعات ذات المرحلة النشطة من التطوير وخط أنابيب CD مضبوط. يوضح الرسم البياني أدناه نتيجة تطبيق التحسين على أحد التطبيقات. من الواضح أن النمو الخطي قد تغير إلى بداية متقطعة من التجميع السبعين (ترتبط حالات الفشل في الستينيات بالتحديد بأعمال تصحيح الأخطاء على عوامل البناء). الانبعاثات بعد ترتبط بتحديث الصورة الأساسية (عالية) والمكتبات (أقل)

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