تعرف على كيفية نشر الخدمات المصغرة. الجزء 1. الربيع التمهيد و Docker

تعرف على كيفية نشر الخدمات المصغرة. الجزء 1. الربيع التمهيد و Docker

يا هبر.

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

في البداية ، قسمت إنشاء المشروع إلى عدة خطوات:

  1. قم بإنشاء خدمتين - "الواجهة الخلفية" (الواجهة الخلفية) و "البوابة" (البوابة) ، وحزمهما في صور عامل الإرساء وقم بإعدادهما للعمل معًا

    الكلمات الرئيسية: Java 11 ، Spring Boot ، Docker ، تحسين الصورة

  2. تطوير تكوين Kubernetes ونشر النظام في Google Kubernetes Engine

    الكلمات الرئيسية: Kubernetes ، GKE ، إدارة الموارد ، القياس التلقائي ، الأسرار

  3. إنشاء مخطط باستخدام Helm 3 لتحسين إدارة الكتلة

    العلامات: Helm 3 ، نشر الرسم البياني

  4. إعداد Jenkins و pipeline للتسليم التلقائي للرمز إلى المجموعة

    الكلمات الرئيسية: تكوين Jenkins ، الإضافات ، مستودع التكوينات المنفصلة

أخطط لتخصيص مقال منفصل لكل خطوة.

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

إنشاء خدمات مصغرة

تمت كتابة الخدمات بلغة Java 11 باستخدام Spring Boot. يتم تنظيم تفاعل Interservice باستخدام REST. سيتضمن المشروع عددًا أدنى من الاختبارات (بحيث يكون هناك شيء للاختبار لاحقًا في جينكينز). الكود المصدري للخدمات متاح على GitHub: الخلفية и بوابة.

لتكون قادرًا على التحقق من حالة كل خدمة ، تمت إضافة Spring Actuator إلى تبعياتها. ستنشئ نقطة نهاية / مشغل / صحة وستعيد الحالة 200 إذا كانت الخدمة جاهزة لقبول حركة المرور ، أو 504 إذا كانت هناك مشكلة. في هذه الحالة ، يعد هذا فحصًا وهميًا إلى حد ما ، نظرًا لأن الخدمات بسيطة جدًا ، وفي حالة وجود بعض القوة القاهرة ، فمن المرجح أن تصبح غير متوفرة تمامًا بدلاً من أن تظل تعمل جزئيًا. ولكن في الأنظمة الحقيقية ، يمكن لـ Actuator المساعدة في تشخيص المشكلة قبل أن يبدأ المستخدمون في القتال بشأنها. على سبيل المثال ، إذا كانت هناك مشكلات في الوصول إلى قاعدة البيانات ، فيمكننا الرد تلقائيًا على ذلك عن طريق إيقاف معالجة الطلبات باستخدام نسخة خدمة معطلة.

خدمة النهاية الخلفية

ستقوم الخدمة الخلفية ببساطة بحساب عدد الطلبات المقبولة وإرجاعها.

كود تحكم:

@RestController
public class RequestsCounterController {

    private final AtomicLong counter = new AtomicLong();

    @GetMapping("/requests")
    public Long getRequestsCount() {
        return counter.incrementAndGet();
    }
}

اختبار تحكم:

@WebMvcTest(RequestsCounterController.class)
public class RequestsCounterControllerTests {

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void firstRequest_one() throws Exception {
        mockMvc.perform(get("/requests"))
            .andExpect(status().isOk())
            .andExpect(MockMvcResultMatchers.content().string("1"));
    }
}

بوابة الخدمة

ستقوم البوابة بإعادة توجيه الطلب إلى خدمة الواجهة الخلفية ، مع استكمالها بالمعلومات التالية:

  • معرف البوابة. وهي ضرورية بحيث يمكن التمييز بين مثيل للبوابة ومثيل آخر من خلال استجابة الخادم
  • بعض "السرية" التي ستلعب دور كلمة مرور مهمة جدًا (رقم مفتاح التشفير لملف تعريف ارتباط مهم)

التكوين في application.properties:

backend.url=http://localhost:8081
instance.id=${random.int}
secret="default-secret"

محول الواجهة الخلفية:

@Service
public class BackendAdapter {

    private static final String REQUESTS_ENDPOINT = "/requests";

    private final RestTemplate restTemplate;

    @Value("${backend.url}")
    private String backendUrl;

    public BackendAdapter(RestTemplateBuilder builder) {
        restTemplate = builder.build();
    }

    public String getRequests() {
        ResponseEntity<String> response = restTemplate.getForEntity(
backendUrl + REQUESTS_ENDPOINT, String.class);
        return response.getBody();
    }
}

مراقب:

@RestController
@RequiredArgsConstructor
public class EndpointController {

    private final BackendAdapter backendAdapter;

    @Value("${instance.id}")
    private int instanceId;

    @Value("${secret}")
    private String secret;

    @GetMapping("/")
    public String getRequestsCount() {
        return String.format("Number of requests %s (gateway %d, secret %s)", backendAdapter.getRequests(), instanceId, secret);
    }
}

تشغيل:

نبدأ الخلفية:

./mvnw package -DskipTests
java -Dserver.port=8081 -jar target/microservices-backend-1.0.0.jar

بدء البوابة:

./mvnw package -DskipTests
java -jar target/microservices-gateway-1.0.0.jar

نتحقق من:

$ curl http://localhost:8080/
Number of requests 1 (gateway 38560358, secret "default-secret")

كل شيء يعمل. سيلاحظ القارئ اليقظ أنه لا شيء يمنعنا من الوصول إلى الواجهة الخلفية مباشرةً ، وتجاوز البوابة (http://localhost:8081/requests). لإصلاح ذلك ، يجب دمج الخدمات في شبكة واحدة ، ويجب أن "تظل" البوابة فقط في الخارج.
أيضًا ، تشترك كلتا الخدمتين في نظام ملفات واحد ، وتنتج تدفقات وفي لحظة واحدة يمكن أن تبدأ في التداخل مع بعضها البعض. سيكون من الجيد عزل خدماتنا المصغرة. يمكن تحقيق ذلك من خلال توزيع التطبيقات على أجهزة مختلفة (الكثير من المال ، صعب) ، باستخدام الأجهزة الافتراضية (كثيفة الموارد ، بدء التشغيل لفترة طويلة) ، أو استخدام الحاويات. كما هو متوقع ، نختار الخيار الثالث و عامل في حوض السفن كأداة للحاويات.

عامل في حوض السفن

باختصار ، يقوم عامل التحميل بإنشاء حاويات معزولة ، واحدة لكل تطبيق. لاستخدام عامل الإرساء ، تحتاج إلى كتابة Dockerfile - تعليمات لإنشاء التطبيق وتشغيله. بعد ذلك ، يمكنك إنشاء الصورة وتحميلها إلى سجل الصور (لا. Docker Hub) ونشر خدمتك المصغرة في أي بيئة مُرسَمة بأمر واحد.

Dockerfile

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

بادئ ذي بدء ، دعنا نحاول كتابة Dockerfile "على الجبهة" (سأقول على الفور أن هذه طريقة سيئة ، لا تفعل ذلك):

FROM adoptopenjdk/openjdk11:jdk-11.0.5_10-alpine
ADD . /src
WORKDIR /src
RUN ./mvnw package -DskipTests
EXPOSE 8080
ENTRYPOINT ["java","-jar","target/microservices-gateway-1.0.0.jar"]

نحن هنا نستخدم صورة أساسية تستند إلى جبال الألب مع تثبيت JDK بالفعل لبناء مشروعنا. باستخدام الأمر ADD ، نضيف دليل src الحالي إلى الصورة ، ونضع علامة على أنه عامل (WORKDIR) ونبدأ الإنشاء. يشير الأمر EXPOSE 8080 إلى عامل الإرساء أن التطبيق الموجود في الحاوية سيستخدم المنفذ 8080 الخاص به (لن يؤدي ذلك إلى تمكين الوصول إلى التطبيق من الخارج ، ولكنه سيسمح بالوصول إلى التطبيق ، على سبيل المثال ، من حاوية أخرى على نفس شبكة عامل الإرساء ).

لحزم الخدمات في الصور ، تحتاج إلى تشغيل الأوامر من جذر كل مشروع:

docker image build . -t msvc-backend:1.0.0

والنتيجة هي صورة بحجم 456 ميجابايت (احتلت صورة JDK الأساسية منها 340 ميجابايت). وكل ذلك على الرغم من حقيقة أن الفصول الدراسية في مشروعنا يمكن حسابها بإصبع. لتقليل حجم صورتنا:

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

بالنسبة للفضوليين ، إليك مقالة جيدة عن طرق تقليل الصور. https://habr.com/ru/company/ruvds/blog/485650/.

ملف Docker النهائي:

FROM adoptopenjdk/openjdk11:jdk-11.0.5_10-alpine as builder
ADD . /src
WORKDIR /src
RUN ./mvnw package -DskipTests

FROM alpine:3.10.3 as packager
RUN apk --no-cache add openjdk11-jdk openjdk11-jmods
ENV JAVA_MINIMAL="/opt/java-minimal"
RUN /usr/lib/jvm/java-11-openjdk/bin/jlink 
    --verbose 
    --add-modules 
        java.base,java.sql,java.naming,java.desktop,java.management,java.security.jgss,java.instrument 
    --compress 2 --strip-debug --no-header-files --no-man-pages 
    --release-info="add:IMPLEMENTOR=radistao:IMPLEMENTOR_VERSION=radistao_JRE" 
    --output "$JAVA_MINIMAL"

FROM alpine:3.10.3
LABEL maintainer="Anton Shelenkov [email protected]"
ENV JAVA_HOME=/opt/java-minimal
ENV PATH="$PATH:$JAVA_HOME/bin"
COPY --from=packager "$JAVA_HOME" "$JAVA_HOME"
COPY --from=builder /src/target/microservices-backend-*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java","-jar","/app.jar"]

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

خدمات مشتركة في Docker

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

docker network create msvc-network

بعد ذلك ، ابدأ حاوية الواجهة الخلفية المسماة "الواجهة الخلفية" باستخدام microservices-backend: 1.0.0 image:

docker run -dit --name backend --network msvc-net microservices-backend:1.0.0

ومن الجدير بالذكر أن شبكة الجسر توفر خدمة اكتشاف خارج الصندوق للحاويات بأسمائها. أي أن خدمة الواجهة الخلفية ستكون متاحة داخل شبكة عامل الإرساء في http://backend:8080.

بدء البوابة:

docker run -dit -p 80:8080 --env secret=my-real-secret --env BACKEND_URL=http://backend:8080/ --name gateway --network msvc-net microservices-gateway:1.0.0

في هذا الأمر ، نشير إلى أننا نعيد توجيه المنفذ 80 لمضيفنا إلى المنفذ 8080 من الحاوية. نستخدم خيارات env لتعيين متغيرات البيئة التي ستتم قراءتها تلقائيًا بحلول الربيع وتجاوز الخصائص من application.properties.

بعد البدء ، نتصل http://localhost/ وتأكد من أن كل شيء يعمل كما في الحالة السابقة.

اختتام

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

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

لإصلاح المشكلات المذكورة أعلاه ، هناك عدد من الحلول مثل Docker Swarm أو Nomad أو Kubernetes أو OpenShift. إذا كان النظام بأكمله مكتوبًا بلغة Java ، فيمكنك التطلع إلى Spring Cloud (مادة جيدة).

В الجزء التالي سأتحدث عن كيفية إعداد Kubernetes ونشر المشروع على Google Kubernetes Engine.

المصدر: www.habr.com

إضافة تعليق