Mësoni se si të vendosni mikroshërbime. Pjesa 1. Pranvera Boot dhe Docker

Mësoni se si të vendosni mikroshërbime. Pjesa 1. Pranvera Boot dhe Docker

Hej Habr.

Në këtë artikull, dua të flas për përvojën time në krijimin e një mjedisi mësimor për eksperimentimin me mikroshërbime. Kur mësova çdo mjet të ri, gjithmonë doja ta provoja jo vetëm në makinën lokale, por edhe në kushte më realiste. Prandaj, vendosa të krijoj një aplikacion mikroservice të thjeshtuar, i cili më vonë mund të "mbulohet" me të gjitha llojet e teknologjive interesante. Kërkesa kryesore për projektin është afërsia maksimale funksionale me sistemin real.

Fillimisht, e ndava krijimin e projektit në disa hapa:

  1. Krijoni dy shërbime - 'backend' (backend) dhe 'gateway' (portë), paketoni ato në imazhe docker dhe konfiguroni ato që të punojnë së bashku

    Fjalë kyçe: Java 11, Spring Boot, Docker, optimization imazhi

  2. Zhvillimi i konfigurimit të Kubernetes dhe vendosja e sistemit në Google Kubernetes Engine

    Fjalë kyçe: Kubernetes, GKE, menaxhimi i burimeve, shkallëzimi automatik, sekretet

  3. Krijimi i një grafiku me Helm 3 për menaxhim më të mirë të grupimeve

    Etiketa: Helm 3, vendosja e grafikut

  4. Vendosja e Jenkins dhe tubacioni për dërgimin automatik të kodit në grup

    Fjalë kyçe: Konfigurimi Jenkins, shtojca, depo e veçantë e konfigurimeve

Kam në plan t'i kushtoj një artikull të veçantë secilit hap.

Fokusi i kësaj serie artikujsh nuk është se si të shkruani mikroshërbimet, por si t'i bëni ato të funksionojnë në një sistem të vetëm. Megjithëse të gjitha këto gjëra zakonisht janë jashtë përgjegjësisë së zhvilluesit, mendoj se është ende e dobishme të njiheni me to të paktën 20% (që, siç e dini, japin 80% të rezultatit). Disa tema të rëndësishme pa kushte, si siguria, do të lihen jashtë këtij projekti, pasi autori kupton pak se ky sistem është krijuar vetëm për përdorim personal. Unë mirëpres çdo mendim dhe kritikë konstruktive.

Krijimi i mikroshërbimeve

Shërbimet janë shkruar në Java 11 duke përdorur Spring Boot. Ndërveprimi ndërmjet shërbimeve organizohet duke përdorur REST. Projekti do të përfshijë një numër minimal testesh (në mënyrë që më vonë të ketë diçka për të testuar në Jenkins). Kodi burimor për shërbimet është i disponueshëm në GitHub: backend и Porta.

Për të qenë në gjendje të kontrolloni statusin e secilit prej shërbimeve, një Spring Actuator është shtuar në varësitë e tyre. Ai do të krijojë një pikë përfundimtare /aktuator/shëndeti dhe do të kthejë një status 200 nëse shërbimi është gati të pranojë trafikun, ose një 504 nëse ka një problem. Në këtë rast, ky është një kontroll mjaft fiktiv, pasi shërbimet janë shumë të thjeshta, dhe në rast të ndonjë force madhore, ka më shumë gjasa të bëhen plotësisht të padisponueshme sesa të mbeten pjesërisht funksionale. Por në sistemet reale, Actuator mund të ndihmojë në diagnostikimin e një problemi përpara se përdoruesit të fillojnë të luftojnë për të. Për shembull, nëse ka probleme me aksesin në bazën e të dhënave, ne mund t'i përgjigjemi automatikisht kësaj duke ndaluar përpunimin e kërkesave me një shembull shërbimi të prishur.

Shërbimi në fund të fundit

Shërbimi mbështetës thjesht do të numërojë dhe kthejë numrin e kërkesave të pranuara.

Kodi i kontrolluesit:

@RestController
public class RequestsCounterController {

    private final AtomicLong counter = new AtomicLong();

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

Testi i kontrolluesit:

@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"));
    }
}

Porta e Shërbimit

Gateway do ta përcjellë kërkesën te shërbimi backend, duke e plotësuar atë me informacionin e mëposhtëm:

  • id i portës. Është e nevojshme në mënyrë që të jetë e mundur të dallohet një shembull i portës nga një tjetër nga përgjigja e serverit
  • Disa "sekret" që do të luajnë rolin e një fjalëkalimi shumë të rëndësishëm (numri i çelësit të enkriptimit të një cookie të rëndësishme)

Konfigurimi në application.properties:

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

Përshtatës i prapavijës:

@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();
    }
}

Kontrolluesi:

@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);
    }
}

Nisja:

Ne fillojmë backend-in:

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

Fillimi i portës:

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

kontrolloni:

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

Gjithçka po funksionon. Një lexues i vëmendshëm do të vërejë se asgjë nuk na pengon të qasemi drejtpërdrejt në backend, duke anashkaluar portën (http://localhost:8081/requests). Për ta rregulluar këtë, shërbimet duhet të kombinohen në një rrjet, dhe vetëm porta duhet të "dalë" jashtë.
Gjithashtu, të dy shërbimet ndajnë një sistem skedari, prodhojnë transmetime dhe në një moment mund të fillojnë të ndërhyjnë me njëri-tjetrin. Do të ishte mirë të izolonim mikroshërbimet tona. Kjo mund të arrihet duke shpërndarë aplikacione në makina të ndryshme (shumë para, e vështirë), duke përdorur makina virtuale (me burime intensive, nisje e gjatë), ose duke përdorur kontejnerizim. Siç pritej, ne zgjedhim opsionin e tretë dhe prerës si mjet për kontejnerizimin.

prerës

Me pak fjalë, docker krijon kontejnerë të izoluar, një për aplikim. Për të përdorur docker, duhet të shkruani një Dockerfile - udhëzime për ndërtimin dhe ekzekutimin e aplikacionit. Tjetra, mund të ndërtoni imazhin, ta ngarkoni në regjistrin e imazheve (Nr. Dockerhub) dhe vendoseni mikroshërbimin tuaj në çdo mjedis të dokerizuar me një komandë.

dockerfile

Një nga karakteristikat më të rëndësishme të një imazhi është madhësia e tij. Një imazh kompakt do të shkarkohet më shpejt nga një depo e largët, do të zërë më pak hapësirë ​​dhe shërbimi juaj do të fillojë më shpejt. Çdo imazh është ndërtuar në bazë të imazhit bazë, dhe rekomandohet të zgjidhni opsionin më minimalist. Një opsion i mirë është Alpine, një shpërndarje e plotë Linux me paketa minimale.

Së pari, le të përpiqemi të shkruajmë një Dockerfile "në ballë" (Unë do të them menjëherë se kjo është një mënyrë e keqe, mos e bëni):

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"]

Këtu ne po përdorim një imazh bazë të bazuar në Alpine me JDK të instaluar tashmë për të ndërtuar projektin tonë. Me komandën ADD shtojmë direktorinë aktuale src në imazh, e shënojmë si funksionale (WORKDIR) dhe nisim ndërtimin. Komanda EXPOSE 8080 i sinjalizon dokerit se aplikacioni në kontejner do të përdorë portin e tij 8080 (kjo nuk do ta bëjë aplikacionin të aksesueshëm nga jashtë, por do të lejojë që aplikacioni të aksesohet, për shembull, nga një kontejner tjetër në të njëjtin rrjet dokeri ).

Për të paketuar shërbimet në imazhe, duhet të ekzekutoni komanda nga rrënja e çdo projekti:

docker image build . -t msvc-backend:1.0.0

Rezultati është një imazh 456 MB (nga të cilat imazhi bazë JDK zinte 340 MB). Dhe gjithçka përkundër faktit se klasat në projektin tonë mund të numërohen me gisht. Për të zvogëluar madhësinë e imazhit tonë:

  • Ne përdorim montim me shumë hapa. Në hapin e parë do të ndërtojmë projektin, në hapin e dytë do të instalojmë JRE dhe në hapin e tretë do ta kopjojmë të gjithën në një imazh të ri të pastër Alpin. Në total, vetëm përbërësit e nevojshëm do të jenë në imazhin përfundimtar.
  • Le të përdorim modularizimin e java. Duke filluar me Java 9, mund të përdorni mjetin jlink për të krijuar një JRE vetëm nga modulet që ju nevojiten

Për kureshtarët, këtu është një artikull i mirë mbi qasjet e reduktimit të imazhit. https://habr.com/ru/company/ruvds/blog/485650/.

Skedari përfundimtar i 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"]

Ne rikrijojmë imazhin, dhe si rezultat, ai humbi 6 herë peshën e tij, duke arritur në 77 MB. Jo keq. Pas kësaj, imazhet e gatshme mund të ngarkohen në regjistrin e imazheve në mënyrë që imazhet tuaja të jenë të disponueshme për shkarkim nga Interneti.

Shërbimet e përbashkëta në Docker

Si fillim, shërbimet tona duhet të jenë në të njëjtin rrjet. Ekzistojnë disa lloje të rrjeteve në docker, dhe ne përdorim më primitive prej tyre - bridge, e cila ju lejon të rrjetizoni kontejnerët që funksionojnë në të njëjtin host. Krijo një rrjet me komandën e mëposhtme:

docker network create msvc-network

Më pas, filloni kontejnerin e prapavijës të quajtur 'backend' me imazhin microservices-backend:1.0.0:

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

Vlen të theksohet se rrjeti i urave ofron shërbimin e zbulimit jashtë kutisë për kontejnerët me emrat e tyre. Kjo do të thotë, shërbimi mbështetës do të jetë i disponueshëm brenda rrjetit docker në http://backend:8080.

Fillimi i portës:

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

Në këtë komandë, ne tregojmë se po përcjellim portin 80 të hostit tonë në portin 8080 të kontejnerit. Ne përdorim opsionet env për të vendosur variabla të mjedisit që do të lexohen automatikisht nga pranvera dhe do të anashkalojnë vetitë nga application.properties.

Pas fillimit, ne telefonojmë http://localhost/ dhe sigurohuni që gjithçka funksionon, si në rastin e mëparshëm.

Përfundim

Si rezultat, ne krijuam dy mikroshërbime të thjeshta, i paketuam në kontejnerë docker dhe i lëshuam së bashku në të njëjtën makinë. Sidoqoftë, sistemi që rezulton ka një sërë disavantazhesh:

  • Toleranca e dobët e gabimeve - gjithçka funksionon për ne në një server
  • Shkallueshmëri e dobët - kur ngarkesa rritet, do të ishte mirë që automatikisht të vendosnin shembuj shtesë të shërbimit dhe të balanconi ngarkesën midis tyre
  • Kompleksiteti i nisjes - na duhej të futnim të paktën 3 komanda, dhe me parametra të caktuar (kjo është vetëm për 2 shërbime)

Për të rregulluar problemet e mësipërme, ekzistojnë një sërë zgjidhjesh si Docker Swarm, Nomad, Kubernetes ose OpenShift. Nëse i gjithë sistemi është i shkruar në Java, mund të shikoni drejt Spring Cloud (artikull i mirë).

В pjesa tjetër Unë do të flas se si konfigurova Kubernetes dhe vendosa projektin në Google Kubernetes Engine.

Burimi: www.habr.com

Shto një koment