نحوه استقرار میکروسرویس ها را بیاموزید. قسمت 1. بوت و داکر بهار

نحوه استقرار میکروسرویس ها را بیاموزید. قسمت 1. بوت و داکر بهار

هی هابر

در این مقاله می‌خواهم در مورد تجربه خود در ایجاد یک محیط یادگیری برای آزمایش میکروسرویس‌ها صحبت کنم. وقتی هر ابزار جدیدی را یاد گرفتم، همیشه می خواستم آن را نه تنها در ماشین محلی، بلکه در شرایط واقعی تر نیز امتحان کنم. بنابراین، تصمیم گرفتم یک برنامه میکروسرویس ساده ایجاد کنم، که بعداً می تواند با انواع فن آوری های جالب "پوشانده شود". نیاز اصلی پروژه حداکثر نزدیکی عملکردی آن به سیستم واقعی است.

در ابتدا، من ایجاد پروژه را به چند مرحله تقسیم کردم:

  1. دو سرویس ایجاد کنید - "backend" (backend) و "gateway" (دروازه)، آنها را در تصاویر docker بسته بندی کنید و آنها را برای کار با هم تنظیم کنید.

    کلمات کلیدی: جاوا 11، بوت بهار، داکر، بهینه سازی تصویر

  2. توسعه پیکربندی Kubernetes و استقرار سیستم در Google Kubernetes Engine

    کلمات کلیدی: Kubernetes، GKE، مدیریت منابع، مقیاس خودکار، اسرار

  3. ایجاد نمودار با Helm 3 برای مدیریت بهتر خوشه

    برچسب ها: Helm 3, استقرار نمودار

  4. تنظیم جنکینز و خط لوله برای تحویل خودکار کد به خوشه

    کلمات کلیدی: پیکربندی جنکینز، پلاگین ها، مخزن تنظیمات جداگانه

من قصد دارم به هر مرحله یک مقاله جداگانه اختصاص دهم.

تمرکز این سری از مقالات نحوه نوشتن میکروسرویس ها نیست، بلکه نحوه کارکرد آنها در یک سیستم واحد است. اگرچه همه این موارد معمولاً خارج از مسئولیت توسعه دهنده است، اما فکر می کنم هنوز هم مفید است که حداقل 20٪ با آنها آشنا باشید (که همانطور که می دانید 80٪ نتیجه را می دهد). برخی از موضوعات مهم بدون قید و شرط، مانند امنیت، از این پروژه حذف خواهند شد، زیرا نویسنده درک کمی در مورد این سیستم دارد که صرفاً برای استفاده شخصی ایجاد شده است. از هرگونه نظر و انتقاد سازنده استقبال می کنم.

ایجاد میکروسرویس ها

سرویس ها در جاوا 11 با استفاده از Spring Boot نوشته شده اند. تعامل بین سرویس با استفاده از REST سازماندهی می شود. این پروژه شامل حداقل تعداد تست خواهد بود (به طوری که بعدا چیزی برای آزمایش در جنکینز وجود دارد). کد منبع خدمات در GitHub موجود است: باطن и دروازه.

برای اینکه بتوانید وضعیت هر یک از سرویس ها را بررسی کنید، یک Spring Actuator به وابستگی های آنها اضافه شده است. اگر سرویس آماده پذیرش ترافیک باشد، یک /actuator/health endpoint ایجاد می‌کند و در صورت آماده‌بودن سرویس برای پذیرش ترافیک، وضعیت 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"

آداپتور Backend:

@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). برای رفع این مشکل، سرویس‌ها باید در یک شبکه ترکیب شوند و فقط دروازه باید به بیرون «بیرون بیاید».
همچنین، هر دو سرویس یک سیستم فایل را به اشتراک می گذارند، جریان تولید می کنند و در یک لحظه می توانند شروع به تداخل با یکدیگر کنند. خوب است که میکروسرویس های خود را ایزوله کنیم. این را می توان با توزیع برنامه ها در ماشین های مختلف (پول زیاد، دشوار)، با استفاده از ماشین های مجازی (منابع فشرده، راه اندازی طولانی) یا استفاده از کانتینری به دست آورد. همانطور که انتظار می رفت گزینه سوم را انتخاب می کنیم و کارگر بارانداز به عنوان ابزاری برای کانتینر سازی

کارگر بارانداز

به طور خلاصه، داکر کانتینرهای ایزوله را ایجاد می کند، یکی در هر برنامه. برای استفاده از docker، باید یک Dockerfile بنویسید - دستورالعمل های ساخت و اجرای برنامه. بعد، می توانید تصویر را بسازید، آن را در رجیستری تصویر آپلود کنید (شماره. داکر هاب) و میکروسرویس خود را در هر محیط داکر شده با یک دستور مستقر کنید.

dockerfile

یکی از مهم ترین ویژگی های یک تصویر اندازه آن است. یک تصویر فشرده سریعتر از یک مخزن راه دور دانلود می شود، فضای کمتری را اشغال می کند و سرویس شما سریعتر شروع می شود. هر تصویری بر اساس تصویر پایه ساخته شده است و توصیه می شود حداقل ترین گزینه را انتخاب کنید. یک گزینه خوب Alpine است، یک توزیع کامل لینوکس با حداقل بسته ها.

اول، بیایید سعی کنیم یک 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"]

در اینجا ما از یک تصویر پایه مبتنی بر Alpine با JDK از قبل نصب شده برای ساخت پروژه خود استفاده می کنیم. با دستور ADD دایرکتوری src فعلی را به تصویر اضافه می کنیم و آن را به عنوان کار (WORKDIR) علامت گذاری می کنیم و ساخت را شروع می کنیم. دستور EXPOSE 8080 به داکر سیگنال می دهد که برنامه در کانتینر از پورت 8080 خود استفاده می کند (این باعث نمی شود برنامه از خارج قابل دسترسی باشد، اما به برنامه اجازه می دهد برای مثال از کانتینر دیگری در همان شبکه داکر به برنامه دسترسی پیدا کند. ).

برای بسته بندی خدمات در تصاویر، باید دستورات را از ریشه هر پروژه اجرا کنید:

docker image build . -t msvc-backend:1.0.0

نتیجه یک تصویر 456 مگابایتی است (که تصویر پایه JDK 340 مگابایت را اشغال کرده است). و همه با وجود این واقعیت که کلاس های پروژه ما را می توان روی انگشت شمارش کرد. برای کاهش اندازه تصویر:

  • ما از مونتاژ چند مرحله ای استفاده می کنیم. در مرحله اول پروژه را می سازیم، در مرحله دوم JRE را نصب می کنیم و در مرحله سوم همه آن را در یک تصویر تمیز آلپاین جدید کپی می کنیم. در مجموع، تنها اجزای لازم در تصویر نهایی قرار خواهند گرفت.
  • بیایید از ماژولارسازی جاوا استفاده کنیم. با شروع با جاوا 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 وجود دارد، و ما از ابتدایی ترین آنها استفاده می کنیم - bridge، که به شما امکان می دهد کانتینرهایی را که روی یک میزبان اجرا می شوند، شبکه کنید. با دستور زیر یک شبکه ایجاد کنید:

docker network create msvc-network

در مرحله بعد، محفظه پشتیبان با نام "backend" را با تصویر microservices-backend:1.0.0 شروع کنید:

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

شایان ذکر است که شبکه پل کشف سرویس خارج از جعبه را برای کانتینرها با نام آنها فراهم می کند. یعنی سرویس Backend در داخل شبکه docker در دسترس خواهد بود 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 دستور و با پارامترهای خاص وارد کنیم (این فقط برای 2 سرویس است)

برای رفع مشکلات فوق، تعدادی راه حل مانند Docker Swarm، Nomad، Kubernetes یا OpenShift وجود دارد. اگر کل سیستم در جاوا نوشته شده باشد، می توانید به سمت Spring Cloud نگاه کنید (مقاله خوب).

В قسمت بعدی من در مورد نحوه راه اندازی Kubernetes و استقرار پروژه در Google Kubernetes Engine صحبت خواهم کرد.

منبع: www.habr.com

اضافه کردن نظر