Lernu kiel disfaldi mikroservojn. Parto 1. Spring Boot kaj Docker

Lernu kiel disfaldi mikroservojn. Parto 1. Spring Boot kaj Docker

Hej Habr.

En ĉi tiu artikolo, mi volas paroli pri mia sperto pri kreado de lerna medio por eksperimenti kun mikroservoj. Kiam mi lernis ĉiun novan ilon, mi ĉiam volis provi ĝin ne nur sur la loka maŝino, sed ankaŭ en pli realismaj kondiĉoj. Tial mi decidis krei simpligitan mikroservan aplikaĵon, kiu poste povas esti "kovrita" per ĉiaj interesaj teknologioj. La ĉefa postulo por la projekto estas ĝia maksimuma funkcia proksimeco al la reala sistemo.

Komence, mi dividis la kreadon de la projekto en plurajn paŝojn:

  1. Kreu du servojn - 'backend' (backend) kaj 'gateway' (enirejo), paku ilin en docker-bildojn kaj agordu ilin por labori kune

    Ŝlosilvortoj: Java 11, Spring Boot, Docker, bilda optimumigo

  2. Disvolviĝo de Kubernetes-agordo kaj sistema deplojo en Google Kubernetes Engine

    Ŝlosilvortoj: Kubernetes, GKE, administrado de rimedoj, aŭtoskalo, sekretoj

  3. Krei diagramon kun Helm 3 por pli bona administrado de clusteroj

    Etikedoj: Helm 3, diagramo deplojo

  4. Agordante Jenkins kaj dukto por aŭtomata livero de kodo al la areto

    Ŝlosilvortoj: Jenkins-agordo, aldonaĵoj, apartaj agordaj deponejo

Mi planas dediĉi apartan artikolon al ĉiu paŝo.

La fokuso de ĉi tiu serio de artikoloj ne estas kiel skribi mikroservojn, sed kiel fari ilin funkcii en ununura sistemo. Kvankam ĉiuj ĉi aferoj estas kutime ekster la respondeco de la programisto, mi pensas, ke ankoraŭ utilas konatiĝi kun ili almenaŭ 20% (kio, kiel vi scias, donas 80% de la rezulto). Iuj senkondiĉe gravaj temoj, kiel sekureco, estos forlasitaj de ĉi tiu projekto, ĉar la aŭtoro komprenas malmulton pri ĉi tiu sistemo estas kreita nur por persona uzo. Mi bonvenigas ajnajn opiniojn kaj konstruajn kritikojn.

Kreante mikroservojn

La servoj estis skribitaj en Java 11 uzante Spring Boot. Interserva interago estas organizita uzante REST. La projekto inkluzivos minimuman nombron da testoj (por ke poste estu io por testi en Jenkins). La fontkodo por la servoj estas havebla sur GitHub: backend и Enirejo.

Por povi kontroli la staton de ĉiu el la servoj, Spring Actuator estis aldonita al iliaj dependecoj. Ĝi kreos /actuator/health finpunkton kaj resendos 200-statuson se la servo pretas akcepti trafikon, aŭ 504 se estas problemo. En ĉi tiu kazo, ĉi tio estas sufiĉe fikcia kontrolo, ĉar la servoj estas tre simplaj, kaj en kazo de iu forto-majoro, ili pli verŝajne fariĝos tute neatingeblaj ol restos parte funkciaj. Sed en realaj sistemoj, Actuator povas helpi diagnozi problemon antaŭ ol uzantoj komencas batali pri ĝi. Ekzemple, se estas problemoj alirantaj la datumbazon, ni povas aŭtomate respondi al tio ĉesigante prilaboradon de petoj kun rompita servokazaĵo.

Malantaŭa servo

La backend-servo simple kalkulos kaj resendos la nombron da akceptitaj petoj.

Kodo de regilo:

@RestController
public class RequestsCounterController {

    private final AtomicLong counter = new AtomicLong();

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

Testo de regilo:

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

Serva Enirejo

La enirejo plusendos la peton al la backend-servo, kompletigante ĝin per la sekvaj informoj:

  • enirejo id. Ĝi estas bezonata por ke eblas distingi unu okazon de la enirejo de alia per la servila respondo
  • Iu "sekreto" kiu ludos la rolon de tre grava pasvorto (nombro de la ĉifrada ŝlosilo de grava kuketo)

Agordo en application.properties:

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

Backend adaptilo:

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

Regilo:

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

Lanĉo:

Ni komencas la backend:

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

Komencante la enirejon:

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

Ni kontrolas:

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

Ĉio funkcias. Atenta leganto rimarkos, ke nenio malhelpas nin rekte aliri la backend, preterirante la enirejon (http://localhost:8081/requests). Por ripari ĉi tion, la servoj devas esti kombinitaj en unu reton, kaj nur la enirejo devus "elsterniĝi" ekstere.
Ankaŭ ambaŭ servoj dividas unu dosiersistemon, produktas fluojn kaj en unu momento povas komenci malhelpi unu la alian. Estus bone izoli niajn mikroservojn. Ĉi tio povas esti atingita distribuante aplikaĵojn sur malsamaj maŝinoj (multe da mono, malfacilaj), uzante virtualajn maŝinojn (rimedonintensaj, longa ekfunkciigo), aŭ uzante kontenerigon. Kiel atendite, ni elektas la trian opcion kaj Docker kiel ilo por kontenerigo.

Docker

Mallonge, docker kreas izolitajn ujojn, unu per aplikaĵo. Por uzi docker, vi devas skribi Dockerfile - instrukciojn por konstrui kaj ruli la aplikaĵon. Poste, vi povas konstrui la bildon, alŝuti ĝin al la bilda registro (Nr. Dockerhub) kaj disfaldi vian mikroservon en iu ajn dokerigita medio en unu komando.

dockerfile

Unu el la plej gravaj trajtoj de bildo estas ĝia grandeco. Kompakta bildo elŝutos pli rapide el fora deponejo, okupos malpli da spaco, kaj via servo komenciĝos pli rapide. Ajna bildo estas konstruita surbaze de la baza bildo, kaj rekomendas elekti la plej minimumisman opcion. Bona elekto estas Alpine, kompleta Linuksa distribuo kun minimumaj pakaĵoj.

Unue, ni provu skribi Dockerfile "sur la frunto" (mi tuj diros, ke ĉi tio estas malbona maniero, ne faru ĝin):

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

Ĉi tie ni uzas bazbildon bazitan sur Alpa kun la JDK jam instalita por konstrui nian projekton. Kun la komando ADD, ni aldonas la nunan src-dosierujon al la bildo, markas ĝin kiel funkcianta (WORKDIR) kaj komencas la konstruon. La komando EXPOSE 8080 signalas al docker, ke la aplikaĵo en la ujo uzos sian havenon 8080 (ĉi tio ne igos la aplikaĵon alirebla de ekstere, sed permesos al la aplikaĵo aliri, ekzemple, de alia ujo sur la sama docker-reto. ).

Por paki servojn en bildojn, vi devas ruli komandojn de la radiko de ĉiu projekto:

docker image build . -t msvc-backend:1.0.0

La rezulto estas 456 MB-bildo (el kiu la baza JDK-bildo okupis 340 MB). Kaj ĉio malgraŭ tio, ke la klasoj en nia projekto povas esti kalkulitaj per fingro. Por redukti la grandecon de nia bildo:

  • Ni uzas plurpaŝan asembleon. En la unua paŝo ni konstruos la projekton, en la dua paŝo ni instalos la JRE, kaj en la tria paŝo ni kopios ĉion en novan puran Alpan bildon. Entute, nur la necesaj komponantoj estos en la fina bildo.
  • Ni uzu la moduligon de java. Komencante kun Java 9, vi povas uzi la jlink-ilon por krei JRE nur el la moduloj, kiujn vi bezonas

Por la scivolemaj, jen bona artikolo pri bildaj reduktaj aliroj. https://habr.com/ru/company/ruvds/blog/485650/.

Fina Dockerfile:

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

Ni rekreas la bildon, kaj kiel rezulto, ĝi perdis 6 fojojn sian pezon, sumiĝante al 77 MB. Ne malbona. Post tio, pretaj bildoj povas esti alŝutitaj al la bildregistro por ke viaj bildoj estu disponeblaj por elŝuti de la Interreto.

Kunfunkciaj servoj en Docker

Komence, niaj servoj devas esti en la sama reto. Estas pluraj specoj de retoj en docker, kaj ni uzas la plej primitivan el ili - ponto, kiu ebligas al vi reti ujojn kurantajn sur la sama gastiganto. Kreu reton per la sekva komando:

docker network create msvc-network

Poste, lanĉu la backend-ujon nomitan 'backend' kun la bildo microservices-backend:1.0.0:

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

Indas noti, ke la ponta reto provizas eltroveblan servon por ujoj laŭ siaj nomoj. Tio estas, la backend-servo estos disponebla ene de la docker-reto ĉe http://backend:8080.

Komencante la enirejon:

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

En ĉi tiu komando, ni indikas, ke ni plusendas la havenon 80 de nia gastiganto al la haveno 8080 de la ujo. Ni uzas la env-opciojn por agordi mediajn variablojn, kiuj aŭtomate estos legitaj per printempo kaj superregi ecojn de application.properties.

Post komenci, ni vokas http://localhost/ kaj certigu, ke ĉio funkcias, kiel en la antaŭa kazo.

konkludo

Kiel rezulto, ni kreis du simplajn mikroservojn, enpakis ilin en docker-ujoj kaj lanĉis ilin kune sur la sama maŝino. La rezulta sistemo, tamen, havas kelkajn malavantaĝojn:

  • Malbona toleremo al misfunkciadoj - ĉio funkcias por ni sur unu servilo
  • Malbona skaleblo - kiam la ŝarĝo pliiĝas, estus bone aŭtomate deploji pliajn servajn okazojn kaj ekvilibrigi la ŝarĝon inter ili.
  • La komplekseco de la lanĉo - ni bezonis enigi almenaŭ 3 komandojn, kaj kun certaj parametroj (ĉi tio estas nur por 2 servoj)

Por ripari la suprajn problemojn, ekzistas kelkaj solvoj kiel Docker Swarm, Nomad, Kubernetes aŭ OpenShift. Se la tuta sistemo estas skribita en Java, vi povas rigardi al Spring Cloud (bona artikolo).

В sekva parto Mi parolos pri kiel mi starigis Kubernetes kaj deplojis la projekton al Google Kubernetes Engine.

fonto: www.habr.com

Aldoni komenton