بصفتي مهندس DevOps ، غالبًا ما أعمل على أتمتة عملية التثبيت والتكوين لمجموعة متنوعة من أنظمة تكنولوجيا المعلومات في بيئات مختلفة: من الحاويات إلى السحابة. كان علي أن أعمل مع العديد من الأنظمة القائمة على مكدس جافا: من صغير (مثل Tomcat) إلى نطاق واسع (Hadoop ، Cassandra ، إلخ).
علاوة على ذلك ، كان لكل نظام من هذا النوع تقريبًا ، حتى أبسطه ، نظام تشغيل فريد ومعقد لسبب ما. على الأقل ، كانت هذه مخطوطات غلاف متعددة الأسطر ، كما هو الحال في Tomcat ، وحتى أطر كاملة ، كما هو الحال في Hadoop . "مريضتي" الحالية في هذه السلسلة ، والتي ألهمتني لكتابة هذه المقالة ، هي مستودع Nexus OSS 3 المصنوع من القطع الأثرية ، والذي يستغرق نصه البرمجي حوالي 400 سطر من التعليمات البرمجية.
التعتيم والتكرار وتعقيد البرامج النصية لبدء التشغيل يخلق مشاكل حتى عند تثبيت مكون واحد يدويًا على النظام المحلي. تخيل الآن أنك بحاجة إلى حزم مجموعة من هذه المكونات والخدمات في حاوية Docker ، وكتابة طبقة أخرى من التجريد على طول الخطوط المناسبة ، ونشرها في مجموعة Kubernetes وتنفيذ هذه العملية في شكل خط أنابيب CI / CD ...
باختصار ، دعنا نلقي نظرة على مثال Nexus 3 المذكور ، كيفية العودة من متاهة نصوص الغلاف إلى شيء أشبه بـ java -jar <program.jar>
، نظرًا لتوفر أدوات DevOps الحديثة المناسبة.
من أين يأتي هذا التعقيد؟
باختصار ، في العصور القديمة ، عندما لم يتم ذكر UNIX عند السؤال: "بمعنى Linux؟" ، لم يكن هناك Systemd و Docker ، وما إلى ذلك ، تم استخدام مخطوطات shell المحمولة (مخطوطات init) و PID- للتحكم في العمليات الملفات. تحدد البرامج النصية الأولية إعدادات البيئة اللازمة ، والتي كانت مختلفة في إعدادات UNIX المختلفة ، واعتمادًا على الوسيطات ، بدأت العملية أو أعادت تشغيلها / أوقفتها باستخدام المعرف من ملف PID. النهج بسيط وواضح ، لكن هذه البرامج النصية توقفت عن العمل في كل موقف غير عادي ، مما يتطلب تدخلًا يدويًا ، ولم تسمح لك بتشغيل عدة نسخ من العملية ... ولكن ليس النقطة.
لذلك ، إذا نظرت بعناية إلى البرامج النصية لبدء التشغيل المذكورة أعلاه في مشاريع Java ، يمكنك رؤية العلامات الواضحة لهذا النهج ما قبل التاريخ ، بما في ذلك حتى ذكر SunOS و HP-UX و UNIX s الأخرى. عادة ، تقوم مثل هذه البرامج النصية بشيء من هذا القبيل:
- استخدم بنية POSIX shell مع جميع العكازات الخاصة بها من أجل إمكانية نقل UNIX / Linux
- تحديد إصدار نظام التشغيل والإصدار من خلال
uname
، /etc/*release
، إلخ. - يبحثون عن JRE / JDK في أركان نظام الملفات ويختارون الإصدار "الأنسب" وفقًا لقواعد ذكية ، وأحيانًا خاصة بكل نظام تشغيل
- يتم حساب المعلمات الرقمية لـ JVM ، على سبيل المثال ، حجم الذاكرة (
-Xms
، -Xmx
) ، وعدد سلاسل GC ، وما إلى ذلك. - تحسين JVM من خلال
-XX
المعلمات مع مراعاة تفاصيل الإصدار المحدد من JRE / JDK - ابحث عن مكوناتها ومكتباتها ومساراتها في الدلائل المحيطة وملفات التكوين وما إلى ذلك.
- تخصيص البيئة: ulimits ، متغيرات البيئة ، إلخ.
- إنشاء CLASSPATH مع حلقة مثل:
for f in $path/*.jar; do CLASSPATH="${CLASSPATH}:$f"; done
for f in $path/*.jar; do CLASSPATH="${CLASSPATH}:$f"; done
- وسيطات سطر الأوامر التي تم تحليلها:
start|stop|restart|reload|status|...
- تجميع أمر Java الذي تحتاج في النهاية إلى تنفيذه مما سبق
- وأخيرًا تنفيذ أمر جافا هذا . غالبًا ما يتم استخدام نفس ملفات PID سيئة السمعة
&
nohup
ومنافذ TCP الخاصة والحيل الأخرى من القرن الماضي بشكل صريح أو ضمني (انظر المثال من Karaf )
يعد برنامج تشغيل Nexus 3 المذكور مثالًا مناسبًا لمثل هذا البرنامج النصي.
في الواقع ، كل منطق البرنامج النصي المذكور أعلاه ، كما هو ، يحاول استبدال مسؤول النظام ، الذي يقوم بتثبيت وتكوين كل شيء يدويًا لنظام معين من البداية إلى النهاية. ولكن بشكل عام ، من المستحيل مراعاة أي متطلبات للأنظمة الأكثر تنوعًا. لذلك ، يتبين ، على العكس من ذلك ، صداعًا ، سواء للمطورين الذين يحتاجون إلى دعم هذه النصوص ، ولمهندسي النظام الذين يحتاجون إلى فهم هذه النصوص في وقت لاحق. من وجهة نظري ، يسهل على مهندس النظام فهم معلمات JVM مرة واحدة وتكوينها كما ينبغي ، بدلاً من فهم تعقيدات البرامج النصية لبدء التشغيل في كل مرة تقوم فيها بتثبيت نظام جديد.
ماذا تفعل؟
اغفر! KISS و YAGNI في أيدينا. علاوة على ذلك ، فإن عام 2018 في الفناء ، مما يعني أن:
- مع استثناءات قليلة جدًا ، UNIX == Linux
- يتم حل مشكلة التحكم في العملية لكل من خادم منفصل ( Systemd و Docker ) وللمجموعات ( Kubernetes ، إلخ.)
- هناك مجموعة من أدوات إدارة التهيئة المريحة ( Ansible ، إلخ.)
- لقد أتت الإدارة التلقائية بالكامل وقد عززت نفسها تمامًا بالفعل: بدلاً من إعداد "خوادم ندفة الثلج" الهشة يدويًا ، أصبح من الممكن الآن تجميع الآلات والحاويات الافتراضية الموحدة القابلة لإعادة الإنتاج تلقائيًا باستخدام عدد من الأدوات المناسبة ، بما في ذلك Ansible و Docker المذكور أعلاه
- يتم استخدام أدوات جمع إحصائيات وقت التشغيل على نطاق واسع ، لكل من JVM نفسها (على سبيل المثال ) ولتطبيق Java (على سبيل المثال )
- والأهم من ذلك ، ظهر الخبراء: مهندسي النظام و DevOps الذين يمكنهم استخدام التقنيات المذكورة أعلاه وفهم كيفية تثبيت JVM بشكل صحيح على نظام معين ثم تعديله بناءً على إحصاءات وقت التشغيل التي تم جمعها
لذا دعنا نستعرض وظائف البرامج النصية لبدء التشغيل مرة أخرى ، مع مراعاة النقاط المذكورة أعلاه ، دون محاولة القيام بالعمل لمهندس النظام ، وإزالة جميع النصوص "غير الضرورية" من هناك.
بناء POSIX shell ⇒ /bin/bash
كشف إصدار نظام التشغيل ⇒ UNIX == Linux ، إذا كانت هناك معلمات خاصة بنظام التشغيل ، يمكنك وصفها في الوثائقبحث JRE / JDK ⇒ لدينا الإصدار الوحيد ، وهذا هو OpenJDK (حسنًا ، أو Oracle JDK ، إذا كنت بحاجة إليه حقًا) ، java
والشركة في مسار النظام القياسيحساب المعلمات العددية JVM ، وضبط JVM ⇒ يمكن وصف ذلك في وثائق تحجيم التطبيقابحث عن المكونات والمكتبات الخاصة بك ⇒ وصف هيكل التطبيق وكيفية تكوينه في الوثائقإعداد البيئة ⇒ وصف المتطلبات والميزات في الوثائقجيل CLASSPATH -cp path/to/my/jars/*
أو حتى بشكل عام Uber-JARتحليل الحجج سطر الأوامر ⇒ لن تكون هناك حجج ، لأنه سيهتم مدير العمليات بكل شيء باستثناء الإطلاق- تجميع أوامر Java
- تنفيذ الأمر جافا
نتيجة لذلك ، نحتاج فقط إلى تجميع وتنفيذ أمر Java من النموذج java <opts> -jar <program.jar>
باستخدام مدير العمليات المحدد (Systemd ، Docker ، وما إلى ذلك). يتم ترك جميع المعلمات والخيارات ( <opts>
) لتقدير مهندس النظام ، الذي سيقوم بتعديلها وفقًا لبيئة معينة. إذا كانت قائمة الخيارات <opts>
طويلة جدًا ، يمكنك العودة مرة أخرى إلى فكرة البرنامج النصي لبدء التشغيل ، ولكن في هذه الحالة ، تكون مضغوطة ومعلنة قدر الإمكان ، أي لا يحتوي على أي منطق البرمجيات.
مثال
كمثال ، دعنا نرى كيف يمكنك تبسيط البرنامج النصي لبدء التشغيل Nexus 3 .
الخيار الأسهل ، حتى لا تدخل في غابة هذا البرنامج النصي - فقط قم بتشغيله في ظروف حقيقية ( ./nexus start
) وانظر إلى النتيجة. على سبيل المثال ، يمكنك العثور على القائمة الكاملة لحجج التطبيق قيد التشغيل في جدول العملية (عبر ps -ef
) ، أو تشغيل البرنامج النصي في وضع التصحيح ( bash -x ./nexus start
) لمراقبة العملية الكاملة لتنفيذه ، وفي النهاية ، أمر الإطلاق.
انتهى بي الأمر بالأمر Java التالي /usr/java/jdk1.8.0_171-amd64/bin/java -server -Dinstall4j.jvmDir=/usr/java/jdk1.8.0_171-amd64 -Dexe4j.moduleName=/home/nexus/nexus-3.12.1-01/bin/nexus -XX:+UnlockDiagnosticVMOptions -Dinstall4j.launcherId=245 -Dinstall4j.swt=false -Di4jv=0 -Di4jv=0 -Di4jv=0 -Di4jv=0 -Di4jv=0 -Xms1200M -Xmx1200M -XX:MaxDirectMemorySize=2G -XX:+UnlockDiagnosticVMOptions -XX:+UnsyncloadClass -XX:+LogVMOutput -XX:LogFile=../sonatype-work/nexus3/log/jvm.log -XX:-OmitStackTraceInFastThrow -Djava.net.preferIPv4Stack=true -Dkaraf.home=. -Dkaraf.base=. -Dkaraf.etc=etc/karaf -Djava.util.logging.config.file=etc/karaf/java.util.logging.properties -Dkaraf.data=../sonatype-work/nexus3 -Djava.io.tmpdir=../sonatype-work/nexus3/tmp -Dkaraf.startLocalConsole=false -Di4j.vpt=true -classpath /home/nexus/nexus-3.12.1-01/.install4j/i4jruntime.jar:/home/nexus/nexus-3.12.1-01/lib/boot/nexus-main.jar:/home/nexus/nexus-3.12.1-01/lib/boot/org.apache.karaf.main-4.0.9.jar:/home/nexus/nexus-3.12.1-01/lib/boot/org.osgi.core-6.0.0.jar:/home/nexus/nexus-3.12.1-01/lib/boot/org.apache.karaf.diagnostic.boot-4.0.9.jar:/home/nexus/nexus-3.12.1-01/lib/boot/org.apache.karaf.jaas.boot-4.0.9.jar com.install4j.runtime.launcher.UnixLauncher start 9d17dc87 '' '' org.sonatype.nexus.karaf.NexusMain
أولاً ، قم بتطبيق بضع حيل بسيطة عليه:
- تغيير
/the/long/and/winding/road/to/my/java
إلى java
، لأنه في مسار النظام - ضع قائمة معلمات Java في مصفوفة منفصلة ، وقم بفرزها وإزالة التكرارات
نحصل بالفعل على شيء أكثر قابلية للهضم JAVA_OPTS = ( '-server' '-Dexe4j.moduleName=/home/nexus/nexus-3.12.1-01/bin/nexus' '-Di4j.vpt=true' '-Di4jv=0' '-Dinstall4j.jvmDir=/usr/java/jdk1.8.0_171-amd64' '-Dinstall4j.launcherId=245' '-Dinstall4j.swt=false' '-Djava.io.tmpdir=../sonatype-work/nexus3/tmp' '-Djava.net.preferIPv4Stack=true' '-Djava.util.logging.config.file=etc/karaf/java.util.logging.properties' '-Dkaraf.base=.' '-Dkaraf.data=../sonatype-work/nexus3' '-Dkaraf.etc=etc/karaf' '-Dkaraf.home=.' '-Dkaraf.startLocalConsole=false' '-XX:+LogVMOutput' '-XX:+UnlockDiagnosticVMOptions' '-XX:+UnlockDiagnosticVMOptions' '-XX:+UnsyncloadClass' '-XX:-OmitStackTraceInFastThrow' '-XX:LogFile=../sonatype-work/nexus3/log/jvm.log' '-XX:MaxDirectMemorySize=2G' '-Xms1200M' '-Xmx1200M' '-classpath /home/nexus/nexus-3.12.1-01/.install4j/i4jruntime.jar:/home/nexus/nexus-3.12.1-01/lib/boot/nexus-main.jar:/home/nexus/nexus-3.12.1-01/lib/boot/org.apache.karaf.main-4.0.9.jar:/home/nexus/nexus-3.12.1-01/lib/boot/org.osgi.core-6.0.0.jar:/home/nexus/nexus-3.12.1-01/lib/boot/org.apache.karaf.diagnostic.boot-4.0.9.jar:/home/nexus/nexus-3.12.1-01/lib/boot/' ) java ${JAVA_OPTS[*]} com.install4j.runtime.launcher.UnixLauncher start 9d17dc87 '' '' org.sonatype.nexus.karaf.NexusMain
الآن يمكنك التعمق.
Install4j هو مثبت Java رسومية. يبدو أنه يستخدم للتثبيت الأولي للنظام. لا نحتاجه على الخادم ، نزيله.
نتفق على وضع مكونات وبيانات Nexus في نظام الملفات:
- ضع التطبيق نفسه في
/opt/nexus-<version>
- للراحة ، قم بإنشاء ارتباط رمزي
/opt/nexus -> /opt/nexus-<version>
- ضع النص نفسه بدلاً من النص الأصلي كـ
/opt/nexus/bin/nexus
- سيتم وضع جميع بيانات Nexus على نظام ملفات منفصل تم تركيبه كـ
/data/nexus
إن إنشاء الأدلة والروابط هو مصير أنظمة إدارة التهيئة (لكل شيء حول كل 5-10 خطوط في Ansible) ، لذا دعنا نترك هذه المهمة لمهندسي النظام.
اسمح لبرنامجنا النصي عند بدء التشغيل بتغيير دليل العمل إلى /opt/nexus
- ثم يمكننا تغيير المسارات إلى مكونات Nexus إلى مكونات نسبية.
خيارات النموذج -Dkaraf.*
إعدادات Apache Karaf ، حاوية OSGi التي من الواضح أن Nexus الخاص بها "معبأ". قم بتغيير karaf.home
و karaf.base
و karaf.etc
و karaf.data
وفقًا لوضع المكونات ، باستخدام المسارات النسبية إن أمكن.
بالنظر إلى أن CLASSPATH يتكون من قائمة ملفات jar الموجودة في نفس الدليل lib/
الدليل ، استبدل هذه القائمة بأكملها بـ lib/*
(سيكون عليك أيضًا إيقاف تشغيل توسيع حرف البدل باستخدام set -o noglob
).
قم بتغيير java
إلى exec java
بحيث لا يبدأ برنامجنا النصي java
كعملية java
(لن يرى مدير العملية هذه العملية الفرعية) ، ولكن "استبدل" نفسه بـ java
( وصف exec ).
دعنا نرى ما حدث:
ما مجموعه 27 سطرًا بدلاً من> 400 ، شفاف ، واضح ، إعلاني ، لا منطق غير ضروري. إذا لزم الأمر ، يمكن بسهولة تحويل هذا البرنامج النصي إلى قالب لـ Ansible / Puppet / Chef وإضافة المنطق المطلوب فقط لحالة معينة.
يمكن استخدام هذا النص البرمجي كمدخل في ملف Dockerfile أو استدعاؤه في ملف وحدة Systemd ، وفي نفس الوقت ضبط الأعداد ومعلمات النظام الأخرى هناك ، على سبيل المثال:
[Unit] Description=Nexus After=network.target [Service] Type=simple LimitNOFILE=1048576 ExecStart=/opt/nexus/bin/nexus User=nexus Restart=on-abort [Install] WantedBy=multi-user.target
الخلاصة
ما هي الاستنتاجات التي يمكن استخلاصها من هذه المقالة؟ من حيث المبدأ ، يتعلق الأمر بنقطتين:
- كل نظام له غرضه الخاص ، أي أنه ليس من الضروري أن يدق في المسامير باستخدام المجهر.
- قواعد البساطة (KISS ، YAGNI) - لتنفيذ ما هو مطلوب فقط لحالة معينة.
- والأهم من ذلك: من الرائع وجود متخصصين في تكنولوجيا المعلومات من ملفات شخصية مختلفة. دعونا نتفاعل ونجعل أنظمة تكنولوجيا المعلومات لدينا أكثر بساطة ووضوحًا وأفضل! :)
شكرا لكم على اهتمامكم! سأكون سعيدًا للتعليقات والمناقشة البناءة في التعليقات.