Leer hoe u microservices implementeert. Deel 1. Spring Boot en Docker

Leer hoe u microservices implementeert. Deel 1. Spring Boot en Docker

Hé Habr.

In dit artikel wil ik het hebben over mijn ervaring met het creëren van een leeromgeving voor het experimenteren met microservices. Toen ik elk nieuw gereedschap leerde, wilde ik het altijd niet alleen op de lokale machine proberen, maar ook in meer realistische omstandigheden. Daarom besloot ik een vereenvoudigde microservice-applicatie te maken, die later kan worden "bedekt" met allerlei interessante technologieën. De belangrijkste vereiste voor het project is de maximale functionele nabijheid van het echte systeem.

Aanvankelijk brak ik de creatie van het project in verschillende stappen:

  1. Maak twee services - 'backend' (backend) en 'gateway' (gateway), verpak ze in docker-images en stel ze in om samen te werken

    Sleutelwoorden: Java 11, Spring Boot, Docker, beeldoptimalisatie

  2. Ontwikkeling van Kubernetes-configuratie en systeemimplementatie in Google Kubernetes Engine

    Sleutelwoorden: Kubernetes, GKE, resourcebeheer, automatisch schalen, geheimen

  3. Een grafiek maken met Helm 3 voor beter clusterbeheer

    Tags: Helm 3, kaartimplementatie

  4. Jenkins en pijplijn instellen voor automatische levering van code aan het cluster

    Sleutelwoorden: Jenkins-configuratie, plug-ins, aparte repository voor configuraties

Ik ben van plan om aan elke stap een apart artikel te wijden.

De focus van deze reeks artikelen ligt niet op het schrijven van microservices, maar op het laten werken ervan in één systeem. Hoewel al deze dingen meestal buiten de verantwoordelijkheid van de ontwikkelaar vallen, denk ik dat het toch nuttig is om er minimaal 20% mee bekend te zijn (wat, zoals u weet, 80% van het resultaat geeft). Sommige onvoorwaardelijk belangrijke onderwerpen, zoals beveiliging, zullen buiten dit project worden weggelaten, aangezien de auteur er weinig van begrijpt dat dit systeem uitsluitend voor persoonlijk gebruik is gemaakt. Ik verwelkom alle meningen en opbouwende kritiek.

Microservices maken

De services zijn geschreven in Java 11 met behulp van Spring Boot. Interservice-interactie wordt georganiseerd met behulp van REST. Het project zal een minimum aantal tests omvatten (zodat er later iets te testen valt in Jenkins). De broncode voor de services is beschikbaar op GitHub: back-end и poort.

Om de status van elk van de services te kunnen controleren, is een Spring Actuator toegevoegd aan hun afhankelijkheden. Het creëert een /actuator/health-eindpunt en retourneert een 200-status als de service klaar is om verkeer te accepteren, of een 504 als er een probleem is. In dit geval is dit een nogal fictieve controle, aangezien de diensten heel eenvoudig zijn en in geval van overmacht eerder volledig onbeschikbaar zullen zijn dan gedeeltelijk operationeel blijven. Maar in echte systemen kan Actuator helpen bij het diagnosticeren van een probleem voordat gebruikers erover beginnen te vechten. Als er bijvoorbeeld problemen zijn met de toegang tot de database, kunnen we hier automatisch op reageren door het verwerken van aanvragen met een kapotte service-instance te stoppen.

Back-end service

De backend-service telt eenvoudigweg het aantal geaccepteerde verzoeken en retourneert het.

Controllercode:

@RestController
public class RequestsCounterController {

    private final AtomicLong counter = new AtomicLong();

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

Controller-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

De gateway stuurt het verzoek door naar de backend-service en vult het aan met de volgende informatie:

  • gateway-id. Het is nodig zodat het mogelijk is om de ene instantie van de gateway van de andere te onderscheiden door de serverreactie
  • Een "geheim" dat de rol van een zeer belangrijk wachtwoord zal spelen (nummer van de coderingssleutel van een belangrijke cookie)

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

controleur:

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

Launch:

We starten de backend:

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

De gateway starten:

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

controleren:

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

Alles werkt. Een oplettende lezer zal opmerken dat niets ons ervan weerhoudt om rechtstreeks toegang te krijgen tot de backend, waarbij we de gateway omzeilen (http://localhost:8081/requests). Om dit op te lossen, moeten de services worden gecombineerd tot één netwerk en mag alleen de gateway buiten "uitsteken".
Beide services delen ook één bestandssysteem, produceren streams en kunnen op een gegeven moment met elkaar beginnen te interfereren. Het zou leuk zijn om onze microservices te isoleren. Dit kan worden bereikt door applicaties op verschillende machines te distribueren (veel geld, moeilijk), virtuele machines te gebruiken (resource-intensief, lang opstarten) of door containerisatie te gebruiken. Zoals verwacht kiezen we voor de derde optie en havenarbeider als tool voor containerisatie.

havenarbeider

Kortom, docker maakt geïsoleerde containers, één per applicatie. Om docker te gebruiken, moet u een Dockerfile schrijven - instructies voor het bouwen en uitvoeren van de applicatie. Vervolgens kunt u de afbeelding bouwen, deze uploaden naar het afbeeldingsregister (nr. Docker-hub) en implementeer uw microservice in elke dockerized omgeving met één opdracht.

Dockerfile

Een van de belangrijkste kenmerken van een afbeelding is de grootte. Een compacte afbeelding wordt sneller gedownload van een externe opslagplaats, neemt minder ruimte in beslag en uw service start sneller. Elke afbeelding wordt gebouwd op basis van de basisafbeelding en het wordt aanbevolen om de meest minimalistische optie te kiezen. Een goede optie is Alpine, een complete Linux-distributie met minimale pakketten.

Laten we eerst proberen een Dockerfile "op het voorhoofd" te schrijven (ik zal meteen zeggen dat dit een slechte manier is, doe het niet):

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 gebruiken we een op Alpine gebaseerd basisbeeld met de JDK al geïnstalleerd om ons project te bouwen. Met de ADD-opdracht voegen we de huidige src-directory toe aan de afbeelding, markeren deze als werkend (WORKDIR) en beginnen met bouwen. De opdracht EXPOSE 8080 signaleert aan docker dat de applicatie in de container zijn poort 8080 zal gebruiken (hierdoor wordt de applicatie niet van buitenaf toegankelijk, maar kan de applicatie bijvoorbeeld worden benaderd vanuit een andere container op hetzelfde docker-netwerk ).

Om services in afbeeldingen te verpakken, moet u opdrachten uitvoeren vanuit de hoofdmap van elk project:

docker image build . -t msvc-backend:1.0.0

Het resultaat is een afbeelding van 456 MB (waarvan de basis JDK-afbeelding 340 MB in beslag nam). En dat ondanks het feit dat de lessen in ons project op een vinger te tellen zijn. Om de grootte van onze afbeelding te verkleinen:

  • We gebruiken meerstapsmontage. In de eerste stap bouwen we het project, in de tweede stap installeren we de JRE en in de derde stap kopiëren we alles naar een nieuwe, schone Alpine-image. In totaal zullen alleen de benodigde componenten in de uiteindelijke afbeelding staan.
  • Laten we de modularisatie van Java gebruiken. Vanaf Java 9 kunt u de jlink-tool gebruiken om een ​​JRE te maken van alleen de modules die u nodig hebt

Voor de nieuwsgierigen is hier een goed artikel over benaderingen voor beeldreductie. https://habr.com/ru/company/ruvds/blog/485650/.

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

We maken de afbeelding opnieuw en als resultaat verloor hij 6 keer zijn gewicht, wat neerkomt op 77 MB. Niet slecht. Daarna kunnen kant-en-klare afbeeldingen worden geüpload naar het afbeeldingsregister, zodat uw afbeeldingen beschikbaar zijn om te downloaden van internet.

Co-running services in Docker

Om te beginnen moeten onze diensten op hetzelfde netwerk zitten. Er zijn verschillende soorten netwerken in docker, en we gebruiken de meest primitieve daarvan - bridge, waarmee je containers kunt netwerken die op dezelfde host draaien. Maak een netwerk aan met het volgende commando:

docker network create msvc-network

Start vervolgens de backend-container met de naam 'backend' met de afbeelding microservices-backend:1.0.0:

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

Het is vermeldenswaard dat het bridge-netwerk out-of-the-box servicedetectie biedt voor containers op naam. Dat wil zeggen, de backend-service zal beschikbaar zijn binnen het docker-netwerk op http://backend:8080.

De gateway starten:

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 dit commando geven we aan dat we poort 80 van onze host doorsturen naar poort 8080 van de container. We gebruiken de env-opties om omgevingsvariabelen in te stellen die automatisch door spring worden gelezen en eigenschappen van application.properties te overschrijven.

Na de start bellen we http://localhost/ en zorg ervoor dat alles werkt, zoals in het vorige geval.

Conclusie

Als gevolg hiervan hebben we twee eenvoudige microservices gemaakt, deze in docker-containers verpakt en samen op dezelfde machine gelanceerd. Het resulterende systeem heeft echter een aantal nadelen:

  • Slechte fouttolerantie - alles werkt voor ons op één server
  • Slechte schaalbaarheid - wanneer de belasting toeneemt, zou het prettig zijn om automatisch extra service-instanties te implementeren en de belasting daartussen te verdelen
  • De complexiteit van de lancering - we moesten minimaal 3 commando's invoeren, en met bepaalde parameters (dit is alleen voor 2 services)

Om bovenstaande problemen op te lossen zijn er een aantal oplossingen zoals Docker Swarm, Nomad, Kubernetes of OpenShift. Als het hele systeem in Java is geschreven, kun je kijken naar Spring Cloud (goed artikel).

В volgend deel Ik zal het hebben over hoe ik Kubernetes heb opgezet en het project heb geïmplementeerd in Google Kubernetes Engine.

Bron: www.habr.com

Voeg een reactie