Finn ut hvordan du distribuerer mikrotjenester. Del 1. Spring Boot og Docker

Finn ut hvordan du distribuerer mikrotjenester. Del 1. Spring Boot og Docker

Hei Habr.

I denne artikkelen vil jeg snakke om min erfaring med å skape et læringsmiljø for å eksperimentere med mikrotjenester. Når jeg lærte hvert nytt verktøy, ønsket jeg alltid å prøve det ikke bare på den lokale maskinen, men også under mer realistiske forhold. Derfor bestemte jeg meg for å lage en forenklet mikrotjenesteapplikasjon, som senere kan "dekkes" med alle slags interessante teknologier. Hovedkravet for prosjektet er dets maksimale funksjonelle nærhet til det virkelige systemet.

Til å begynne med delte jeg opprettelsen av prosjektet inn i flere trinn:

  1. Lag to tjenester - 'backend' (backend) og 'gateway' (gateway), pakk dem inn i docker-bilder og sett dem opp til å fungere sammen

    Nøkkelord: Java 11, Spring Boot, Docker, bildeoptimalisering

  2. Utvikling av Kubernetes-konfigurasjon og systemimplementering i Google Kubernetes Engine

    Nøkkelord: Kubernetes, GKE, ressursstyring, autoskalering, hemmeligheter

  3. Lage et diagram med Helm 3 for bedre klyngeadministrasjon

    Tags: Helm 3, kartdistribusjon

  4. Sette opp Jenkins og pipeline for automatisk levering av kode til klyngen

    Nøkkelord: Jenkins-konfigurasjon, plugins, separat konfigurasjonslager

Jeg planlegger å vie en egen artikkel til hvert trinn.

Fokuset i denne artikkelserien er ikke hvordan man skriver mikrotjenester, men hvordan man får dem til å fungere i et enkelt system. Selv om alle disse tingene vanligvis ligger utenfor utviklerens ansvar, tror jeg det fortsatt er nyttig å være kjent med dem minst 20% (som, som du vet, gir 80% av resultatet). Noen ubetinget viktige emner, som sikkerhet, vil bli utelatt i dette prosjektet, siden forfatteren forstår lite om at dette systemet er laget utelukkende for personlig bruk. Jeg tar gjerne imot alle meninger og konstruktiv kritikk.

Opprette mikrotjenester

Tjenestene ble skrevet i Java 11 med Spring Boot. Samhandling mellom tjenestene organiseres ved hjelp av REST. Prosjektet vil inkludere et minimum antall tester (slik at det senere er noe å teste i Jenkins). Kildekoden for tjenestene er tilgjengelig på GitHub: baksiden и Inngangsport.

For å kunne sjekke statusen til hver av tjenestene, er en fjæraktuator lagt til deres avhengigheter. Den vil opprette et /aktuator/helseendepunkt og returnere en 200-status hvis tjenesten er klar til å akseptere trafikk, eller en 504 hvis det er et problem. I dette tilfellet er dette en ganske fiktiv sjekk, siden tjenestene er veldig enkle, og i tilfelle force majeure er det mer sannsynlig at de blir helt utilgjengelige enn å forbli delvis operative. Men i virkelige systemer kan Actuator hjelpe med å diagnostisere et problem før brukere begynner å kjempe om det. For eksempel, hvis det er problemer med å få tilgang til databasen, kan vi automatisk svare på dette ved å stoppe behandlingen av forespørsler med en ødelagt tjenesteforekomst.

Back-end service

Backend-tjenesten vil ganske enkelt telle og returnere antall aksepterte forespørsler.

Kontrollerkode:

@RestController
public class RequestsCounterController {

    private final AtomicLong counter = new AtomicLong();

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

Kontroller test:

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

Service Gateway

Gatewayen vil videresende forespørselen til backend-tjenesten, og supplere den med følgende informasjon:

  • gateway-id. Det er nødvendig slik at det er mulig å skille en forekomst av gatewayen fra en annen ved hjelp av serversvaret
  • Noen "hemmeligheter" som vil spille rollen som et veldig viktig passord (nummeret på krypteringsnøkkelen til en viktig informasjonskapsel)

Konfigurasjon i application.properties:

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

Backend adapter:

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

Kontroller:

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

Lansering:

Vi starter backend:

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

Starte gatewayen:

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

Vi sjekker:

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

Alt fungerer. En oppmerksom leser vil merke seg at ingenting hindrer oss i å få tilgang til backend direkte, utenom gatewayen (http://localhost:8081/requests). For å fikse dette må tjenestene kombineres til ett nettverk, og kun gatewayen skal «stikke ut» utenfor.
Dessuten deler begge tjenestene ett filsystem, produserer strømmer og kan i ett øyeblikk begynne å forstyrre hverandre. Det ville vært fint å isolere mikrotjenestene våre. Dette kan oppnås ved å distribuere applikasjoner på forskjellige maskiner (mye penger, vanskelig), bruke virtuelle maskiner (ressurskrevende, lang oppstart) eller bruke containerisering. Som forventet velger vi det tredje alternativet og Docker som et verktøy for containerisering.

Docker

Kort sagt, docker lager isolerte containere, én per applikasjon. For å bruke docker, må du skrive en Dockerfile - instruksjoner for å bygge og kjøre programmet. Deretter kan du bygge bildet, laste det opp til bilderegisteret (nr. Dockerhub) og distribuer mikrotjenesten din i et hvilket som helst dokkerisert miljø med én kommando.

Dockerfile

En av de viktigste egenskapene til et bilde er størrelsen. Et kompakt bilde lastes ned raskere fra et eksternt arkiv, tar mindre plass, og tjenesten din starter raskere. Ethvert bilde bygges oppå et basisbilde, og det anbefales å velge det mest minimalistiske alternativet. Et godt alternativ er Alpine, en fullfunksjonell distribusjon. Linux med et minimum av pakker.

Først, la oss prøve å skrive en Dockerfile "på pannen" (jeg vil si med en gang at dette er en dårlig måte, ikke gjør det):

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

Her bruker vi et alpinbasert basisbilde med JDK allerede installert for å bygge prosjektet vårt. Med ADD-kommandoen legger vi til den gjeldende src-katalogen til bildet, merker den som fungerer (WORKDIR) og starter byggingen. EXPOSE 8080-kommandoen signaliserer til docker at applikasjonen i containeren vil bruke port 8080 (dette vil ikke gjøre applikasjonen tilgjengelig fra utsiden, men vil tillate tilgang til applikasjonen, for eksempel fra en annen container på samme docker-nettverk ).

For å pakke tjenester inn i bilder, må du kjøre kommandoer fra roten til hvert prosjekt:

docker image build . -t msvc-backend:1.0.0

Resultatet er et 456 MB bilde (hvorav basis JDK-bildet okkuperte 340 MB). Og alt til tross for at klassene i prosjektet vårt kan telles på en finger. For å redusere størrelsen på bildet vårt:

  • Vi bruker flertrinns montering. I det første trinnet vil vi bygge prosjektet, i det andre trinnet vil vi installere JRE, og i det tredje trinnet vil vi kopiere det hele til et nytt rent Alpine-bilde. Totalt vil kun de nødvendige komponentene være i det endelige bildet.
  • La oss bruke modularisering av java. Fra og med Java 9 kan du bruke jlink-verktøyet til å lage en JRE fra bare modulene du trenger

For den nysgjerrige, her er en god artikkel om tilnærminger til bildereduksjon. https://habr.com/ru/company/ruvds/blog/485650/.

Endelig dockerfil:

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 anshelen@yandex.ru"
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"]

Vi gjenskaper bildet, og som et resultat mistet det 6 ganger vekten, noe som tilsvarer 77 MB. Ikke verst. Deretter kan ferdige bilder lastes opp til bilderegisteret slik at bildene dine er tilgjengelige for nedlasting fra Internett.

Samkjørende tjenester i Docker

Til å begynne med må tjenestene våre være på samme nettverk. Det finnes flere typer nettverk i docker, og vi bruker den mest primitive av dem - bridge, som lar deg nettverksbeholdere som kjører på samme vert. Opprett et nettverk med følgende kommando:

docker network create msvc-network

Deretter starter du backend-beholderen kalt 'backend' med microservices-backend:1.0.0-bildet:

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

Det er verdt å merke seg at bronettverket gir ut av esken tjenesteoppdagelse for containere ved navn. Det vil si at backend-tjenesten vil være tilgjengelig inne i docker-nettverket kl http://backend:8080.

Starte gatewayen:

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

I denne kommandoen indikerer vi at vi videresender port 80 til verten vår til port 8080 til containeren. Vi bruker env-alternativene til å angi miljøvariabler som automatisk skal leses av spring og overstyre egenskaper fra application.properties.

Etter oppstart ringer vi http://localhost/ og sørg for at alt fungerer, som i forrige tilfelle.

Konklusjon

Som et resultat laget vi to enkle mikrotjenester, pakket dem i docker-containere og lanserte dem sammen på samme maskin. Det resulterende systemet har imidlertid en rekke ulemper:

  • Dårlig feiltoleranse - alt fungerer for oss på én server
  • Dårlig skalerbarhet - når belastningen øker, ville det være fint å automatisk distribuere ytterligere tjenesteforekomster og balansere belastningen mellom dem
  • Kompleksiteten til lanseringen - vi trengte å angi minst 3 kommandoer, og med visse parametere (dette er bare for 2 tjenester)

For å fikse problemene ovenfor finnes det en rekke løsninger som Docker Swarm, Nomad, Kubernetes eller OpenShift. Hvis hele systemet er skrevet i Java, kan du se mot Spring Cloud (god artikkel).

В neste del Jeg vil snakke om hvordan jeg satte opp Kubernetes og distribuerte prosjektet til Google Kubernetes Engine.

Kilde: www.habr.com

Kjøp pålitelig hosting for nettsteder med DDoS-beskyttelse, VPS VDS-servere 🔥 Kjøp pålitelig webhotell med DDoS-beskyttelse, VPS VDS-servere | ProHoster