Leer hoe om mikrodienste te ontplooi. Deel 1. Spring Boot en Docker

Leer hoe om mikrodienste te ontplooi. Deel 1. Spring Boot en Docker

Haai Habr.

In hierdie artikel wil ek praat oor my ervaring om 'n leeromgewing te skep vir eksperimentering met mikrodienste. Toe ek elke nuwe instrument leer, wou ek dit altyd nie net op my plaaslike masjien probeer nie, maar ook in meer realistiese omstandighede. Daarom het ek besluit om 'n vereenvoudigde mikrodienstoepassing te skep, wat later met allerhande interessante tegnologieë "gehang" kon word. Die hoofvereiste vir die projek is sy maksimum funksionele nabyheid aan die werklike stelsel.

Aanvanklik het ek die skepping van die projek in verskeie stappe verdeel:

  1. Skep twee dienste - 'backend' en 'gateway', pak dit in docker-beelde en stel dit op om saam te werk

    Sleutelwoorde: Java 11, Spring Boot, Docker, beeldoptimalisering

  2. Ontwikkeling van Kubernetes-konfigurasie en -ontplooiingstelsel in Google Kubernetes Engine

    Sleutelwoorde: Kubernetes, GKE, hulpbronbestuur, outoskaling, geheime

  3. Skep 'n grafiek deur Helm 3 te gebruik vir meer doeltreffende groepbestuur

    Sleutelwoorde: Roer 3, grafiekontplooiing

  4. Stel Jenkins en pyplyn op om kode outomaties aan die groepering te lewer

    Sleutelwoorde: Jenkins-konfigurasie, plugins, aparte konfigurasie-bewaarplek

Ek beplan om 'n aparte artikel aan elke stap te wy.

Die fokus van hierdie reeks artikels is nie hoe om mikrodienste te skryf nie, maar hoe om dit in 'n enkele stelsel te laat werk. Alhoewel al hierdie dinge gewoonlik buite die ontwikkelaar se verantwoordelikheid is, dink ek dit is steeds nuttig om ten minste 20% daarvan vertroud te wees (wat bekend is dat dit 80% van die resultaat uitmaak). Sommige absoluut belangrike onderwerpe, soos sekuriteit, sal uit hierdie projek gelaat word, aangesien die skrywer min hiervan verstaan; die stelsel word uitsluitlik vir persoonlike gebruik geskep. Ek verwelkom enige opinies en konstruktiewe kritiek.

Skep mikrodienste

Die dienste is in Java 11 geskryf met Spring Boot. Interdienskommunikasie word georganiseer deur gebruik te maak van REST. Die projek sal 'n minimum aantal toetse insluit (sodat daar later iets sal wees om in Jenkins te toets). Die bronkode vir die dienste is beskikbaar op GitHub: agterkant и Gateway.

Om die toestand van elk van die dienste te kan nagaan, is 'n Spring Actuator by hul afhanklikheid gevoeg. Dit sal 'n eindpunt /aktuator/gesondheid skep en sal 'n status van 200 gee as die diens gereed is om verkeer te aanvaar, of 504 in geval van probleme. In hierdie geval is dit 'n taamlik fiktiewe tjek, aangesien die dienste baie eenvoudig is, en onder 'n soort force majeure is dit meer geneig om heeltemal onbeskikbaar te word as om gedeeltelik operasioneel te bly. Maar in regte stelsels kan Actuator help om 'n probleem te diagnoseer voordat gebruikers daarop begin slaan. Byvoorbeeld, as probleme met toegang tot die databasis opduik, sal ons outomaties hierop kan reageer deur die verwerking van versoeke met 'n stukkende instansie van die diens te stop.

Backend diens

Die backend-diens sal eenvoudig die aantal aanvaarde versoeke tel en terugstuur.

Kontroleur kode:

@RestController
public class RequestsCounterController {

    private final AtomicLong counter = new AtomicLong();

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

Kontroleerder toets:

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

Gateway diens

Die poort sal die versoek na die backend-diens aanstuur, en dit aanvul met die volgende inligting:

  • poort-ID. Dit is nodig sodat een geval van die poort deur die bedienerreaksie van 'n ander onderskei kan word
  • 'n Sekere "geheim" wat die rol sal speel van 'n baie belangrike wagwoord (sleutelnommer vir enkripsie van 'n belangrike koekie)

Konfigurasie in application.properties:

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

Adapter vir kommunikasie met 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();
    }
}

Beheerder:

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

Begin:

Kom ons begin die agterkant:

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

Kom ons begin die poort:

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

Ons kyk na:

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

Alles werk. Die oplettende leser sal opmerk dat niks ons verhoed om direk toegang tot die backend te verkry en die poort te omseil (http://localhost:8081/requests). Om dit reg te stel, moet die dienste in een netwerk gekombineer word, en slegs die poort moet buite "uitsteek".
Beide dienste deel ook dieselfde lêerstelsel, genereer drade, en kan op 'n stadium met mekaar begin inmeng. Dit sal lekker wees om ons mikrodienste te isoleer. Dit kan bereik word deur toepassings oor verskillende masjiene te versprei (baie geld, moeilik), virtuele masjiene te gebruik (hulpbron-intensief, lang opstart) of houerisering te gebruik. Soos verwag, kies ons die derde opsie en Docker as 'n hulpmiddel vir containerisering.

Docker

Kortom, Docker skep geïsoleerde houers, een per toepassing. Om Docker te gebruik, moet jy 'n Dockerfile skryf - instruksies vir die bou en bestuur van die toepassing. Vervolgens kan u die prent bou, dit oplaai na die prentregister (nr. Dockerhub) en ontplooi jou mikrodiens in enige gedokteriseerde omgewing in een opdrag.

dockerfile

Een van die belangrikste kenmerke van 'n beeld is sy grootte. 'n Kompakte prent sal vinniger van 'n afgeleë bewaarplek aflaai, minder spasie opneem, en jou diens sal vinniger begin. Enige beeld word gebou op grond van 'n basiese beeld, en dit word aanbeveel om die mees minimalistiese opsie te kies. 'n Goeie opsie is Alpine, 'n volwaardige Linux-verspreiding met 'n minimum van pakkette.

Kom ons probeer eers om 'n Dockerfile "kop-aan" te skryf (ek sal dadelik sê dat dit 'n slegte manier is, moenie dit doen nie):

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

Hier gebruik ons ​​'n Alpine-gebaseerde basisbeeld met die JDK reeds geïnstalleer om ons projek te bou. Deur die ADD-opdrag te gebruik, voeg ons die huidige src-gids by die prent, merk dit as werkend (WORKDIR) en begin die bou. Die EXPOSE 8080-opdrag gee aan die docker te kenne dat die toepassing in die houer sy poort 8080 sal gebruik (dit sal nie die toepassing van buite toeganklik maak nie, maar sal toelaat dat toegang tot die toepassing verkry word, byvoorbeeld vanaf 'n ander houer op dieselfde docker-netwerk ).

Om dienste in beelde te verpak, moet u die opdragte vanaf die wortel van elke projek uitvoer:

docker image build . -t msvc-backend:1.0.0

As gevolg hiervan kry ons 'n beeld van 456 MB groot (waarvan die basis JDK 340-prent MB geneem het). En dit alles ten spyte van die feit dat die klasse in ons projek op een vinger getel kan word. Om die grootte van ons beeld te verminder:

  • Ons gebruik multi-stap samestelling. In die eerste stap sal ons die projek saamstel, in die tweede sal ons die JRE installeer, en in die derde stap sal ons dit alles na 'n nuwe skoon Alpynse beeld kopieer. In totaal sal die finale prent slegs die nodige komponente bevat.
  • Kom ons gebruik Java-modularisering. Begin met Java 9, kan jy die jlink-nutsding gebruik om 'n JRE te skep uit slegs die modules wat jy nodig het

Vir die nuuskieriges is hier 'n goeie artikel oor benaderings om beeldgroottes te verklein https://habr.com/ru/company/ruvds/blog/485650/.

Finale Docker-lêer:

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

Ons het die prent herskep, en dit het uiteindelik 6 keer dunner geword, tot 77 MB. Nie sleg nie. Daarna kan die voltooide beelde na die beeldregister opgelaai word sodat jou beelde beskikbaar is vir aflaai vanaf die internet.

Werk saam dienste in Docker

Om mee te begin, moet ons dienste op dieselfde netwerk wees. Daar is verskeie soorte netwerke in Docker, en ons gebruik die mees primitiewe daarvan - brug, wat jou toelaat om houers te netwerk wat op dieselfde gasheer loop. Kom ons skep 'n netwerk met die volgende opdrag:

docker network create msvc-network

Kom ons begin dan 'n backend-houer genaamd 'backend' met die beeld microservices-backend:1.0.0:

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

Dit is opmerklik dat die brugnetwerk diensontdekking uit die boks verskaf vir houers op hul name. Dit wil sê, die backend-diens sal beskikbaar wees binne die Docker-netwerk by http://backend:8080.

Kom ons begin die poort:

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

In hierdie opdrag dui ons aan dat ons poort 80 van ons gasheer na poort 8080 van die houer aanstuur. Ons gebruik env-opsies om omgewingsveranderlikes te stel wat outomaties teen die lente gelees sal word en die eienskappe van application.properties ignoreer.

Na bekendstelling, bel http://localhost/ en maak seker dat alles werk, soos in die vorige geval.

Gevolgtrekking

Gevolglik het ons twee eenvoudige mikrodienste geskep, dit in dokhouers verpak en dit saam op dieselfde masjien bekendgestel. Die gevolglike stelsel het egter 'n aantal nadele:

  • Swak foutverdraagsaamheid - alles werk vir ons op een bediener
  • Swak skaalbaarheid - soos die vrag toeneem, sal dit lekker wees om outomaties bykomende diensgevalle te ontplooi en die las daartussen te balanseer
  • Begin kompleksiteit - ons moes ten minste 3 opdragte invoer, met sekere parameters (dit is slegs vir 2 dienste)

Om bogenoemde probleme op te los, is daar 'n aantal oplossings soos Docker Swarm, Nomad, Kubernetes of OpenShift. As die hele stelsel in Java geskryf is, kan jy na Spring Cloud kyk (goeie artikel).

В volgende deel Ek sal jou vertel hoe ek Kubernetes opgestel het en die projek na Google Kubernetes Engine ontplooi het.

Bron: will.com

Voeg 'n opmerking