Obteniu informació sobre com implementar microserveis. Part 1. Spring Boot i Docker

Obteniu informació sobre com implementar microserveis. Part 1. Spring Boot i Docker

Hola Habr.

En aquest article, vull parlar de la meva experiència en la creació d'un entorn d'aprenentatge per experimentar amb microserveis. Quan vaig aprendre cada eina nova, sempre vaig voler provar-la no només a la màquina local, sinó també en condicions més realistes. Per tant, vaig decidir crear una aplicació de microservei simplificada, que després es pugui "cobrir" amb tot tipus de tecnologies interessants. El principal requisit del projecte és la seva màxima proximitat funcional al sistema real.

Inicialment, vaig dividir la creació del projecte en diversos passos:

  1. Creeu dos serveis: "backend" (backend) i "gateway" (gateway), empaqueteu-los en imatges de Docker i configureu-los perquè funcionin junts

    Paraules clau: Java 11, Spring Boot, Docker, optimització d'imatges

  2. Desenvolupament de la configuració de Kubernetes i el desplegament del sistema a Google Kubernetes Engine

    Paraules clau: Kubernetes, GKE, gestió de recursos, autoscaling, secrets

  3. Creació d'un gràfic amb Helm 3 per a una millor gestió del clúster

    Etiquetes: Helm 3, desplegament de gràfics

  4. Configuració de Jenkins i pipeline per al lliurament automàtic de codi al clúster

    Paraules clau: configuració de Jenkins, connectors, dipòsit de configuracions independents

Penso dedicar un article independent a cada pas.

El focus d'aquesta sèrie d'articles no és com escriure microserveis, sinó com fer-los funcionar en un únic sistema. Tot i que totes aquestes coses solen estar fora de la responsabilitat del desenvolupador, crec que encara és útil conèixer-les almenys un 20% (que, com sabeu, donen un 80% del resultat). Alguns temes d'importància incondicional, com ara la seguretat, quedaran fora d'aquest projecte, ja que l'autor entén poc que aquest sistema està creat exclusivament per a ús personal. Agraeixo qualsevol opinió i crítica constructiva.

Creació de microserveis

Els serveis es van escriure en Java 11 mitjançant Spring Boot. La interacció entre serveis s'organitza mitjançant REST. El projecte inclourà un nombre mínim de proves (perquè més endavant hi hagi alguna cosa per provar a Jenkins). El codi font dels serveis està disponible a GitHub: backend и Porta d'entrada.

Per poder comprovar l'estat de cadascun dels serveis, s'ha afegit un Spring Actuator a les seves dependències. Crearà un punt final /actuator/health i retornarà un estat 200 si el servei està preparat per acceptar trànsit, o un 504 si hi ha un problema. En aquest cas, es tracta d'un control més aviat fictici, ja que els serveis són molt senzills i, en cas de força major, és més probable que quedin totalment indisponibles que no pas que romanguin parcialment operatius. Però en sistemes reals, Actuator pot ajudar a diagnosticar un problema abans que els usuaris comencin a lluitar-hi. Per exemple, si hi ha problemes per accedir a la base de dades, podem respondre-hi automàticament aturant el processament de sol·licituds amb una instància de servei trencada.

Servei de backend

El servei de backend simplement comptarà i retornarà el nombre de sol·licituds acceptades.

Codi del controlador:

@RestController
public class RequestsCounterController {

    private final AtomicLong counter = new AtomicLong();

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

Prova del controlador:

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

Passarel·la de servei

La passarel·la reenviarà la sol·licitud al servei backend, complementant-la amb la informació següent:

  • identificador de la passarel·la. És necessari perquè sigui possible distingir una instància de la passarel·la d'una altra per la resposta del servidor
  • Alguns "secrets" que jugaran el paper d'una contrasenya molt important (número de la clau de xifratge d'una galeta important)

Configuració a application.properties:

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

Adaptador de fons:

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

Controlador:

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

Llançament:

Comencem el backend:

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

Inici de la passarel·la:

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

Comprovem:

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

Tot funciona. Un lector atent notarà que res no ens impedeix accedir directament al backend, obviant la passarel·la (http://localhost:8081/requests). Per solucionar-ho, els serveis s'han de combinar en una sola xarxa i només la passarel·la hauria de "sortir" a l'exterior.
A més, tots dos serveis comparteixen un sistema de fitxers, produeixen fluxos i en un moment poden començar a interferir entre ells. Seria bo aïllar els nostres microserveis. Això es pot aconseguir distribuïnt aplicacions en diferents màquines (molts diners, difícils), utilitzant màquines virtuals (intensives en recursos, engegada llarga) o utilitzant la contenidorització. Com era d'esperar, escollim la tercera opció i estibador com a eina de contenidorització.

estibador

En resum, Docker crea contenidors aïllats, un per aplicació. Per utilitzar Docker, heu d'escriure un Dockerfile: instruccions per crear i executar l'aplicació. A continuació, podeu crear la imatge, carregar-la al registre d'imatges (núm. hub docker) i implementeu el vostre microservei en qualsevol entorn acoblat en una ordre.

Dockerfile

Una de les característiques més importants d'una imatge és la seva mida. Una imatge compacta es baixarà més ràpidament des d'un dipòsit remot, ocuparà menys espai i el vostre servei començarà més ràpidament. Qualsevol imatge es construeix a partir de la imatge base i es recomana triar l'opció més minimalista. Una bona opció és Alpine, una distribució Linux completa amb paquets mínims.

Primer, intentem escriure un Dockerfile "al front" (de seguida diré que aquesta és una mala manera, no ho feu):

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

Aquí estem utilitzant una imatge base basada en Alpine amb el JDK ja instal·lat per construir el nostre projecte. Amb l'ordre ADD, afegim el directori src actual a la imatge, el marquem com a treballant (WORKDIR) i comencem la compilació. L'ordre EXPOSE 8080 indica a docker que l'aplicació del contenidor utilitzarà el seu port 8080 (això no farà que l'aplicació sigui accessible des de l'exterior, però permetrà accedir a l'aplicació, per exemple, des d'un altre contenidor de la mateixa xarxa docker). ).

Per empaquetar serveis en imatges, heu d'executar ordres des de l'arrel de cada projecte:

docker image build . -t msvc-backend:1.0.0

El resultat és una imatge de 456 MB (de la qual la imatge JDK base ocupava 340 MB). I tot malgrat que les classes del nostre projecte es poden comptar amb un dit. Per reduir la mida de la nostra imatge:

  • Utilitzem el muntatge de diversos passos. En el primer pas construirem el projecte, en el segon instal·larem el JRE i en el tercer pas ho copiarem tot en una nova imatge neta d'Alpine. En total, només els components necessaris seran a la imatge final.
  • Utilitzem la modularització de java. A partir de Java 9, podeu utilitzar l'eina jlink per crear un JRE només a partir dels mòduls que necessiteu

Per als curiosos, aquí teniu un bon article sobre els enfocaments de reducció d'imatges. https://habr.com/ru/company/ruvds/blog/485650/.

Dockerfile final:

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

Recreem la imatge i, com a resultat, va perdre 6 vegades el seu pes, amb 77 MB. No està malament. Després d'això, les imatges ja fetes es poden carregar al registre d'imatges perquè les vostres imatges estiguin disponibles per a la seva baixada d'Internet.

Co-execució de serveis a Docker

Per començar, els nostres serveis han d'estar a la mateixa xarxa. Hi ha diversos tipus de xarxes a Docker, i fem servir la més primitiva d'elles: el pont, que us permet connectar contenidors en xarxa que s'executen al mateix host. Creeu una xarxa amb l'ordre següent:

docker network create msvc-network

A continuació, inicieu el contenidor de backend anomenat "backend" amb la imatge microservices-backend:1.0.0:

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

Val la pena assenyalar que la xarxa de ponts ofereix un servei de descoberta immediata de contenidors pel seu nom. És a dir, el servei de backend estarà disponible dins de la xarxa Docker a http://backend:8080.

Inici de la passarel·la:

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 aquesta ordre, indiquem que estem reenviant el port 80 del nostre host al port 8080 del contenidor. Utilitzem les opcions d'env per establir variables d'entorn que seran llegides automàticament per spring i anul·lar les propietats de application.properties.

Després de començar, truquem http://localhost/ i assegurar-se que tot funciona, com en el cas anterior.

Conclusió

Com a resultat, vam crear dos microserveis senzills, els vam empaquetar en contenidors Docker i els vam llançar junts a la mateixa màquina. El sistema resultant, però, té una sèrie d'inconvenients:

  • Poca tolerància a errors: tot funciona per a nosaltres en un sol servidor
  • Poca escalabilitat: quan la càrrega augmenta, seria bo desplegar automàticament instàncies de servei addicionals i equilibrar la càrrega entre elles
  • La complexitat del llançament: havíem d'introduir almenys 3 ordres i amb determinats paràmetres (només per a 2 serveis)

Per solucionar els problemes anteriors, hi ha una sèrie de solucions com ara Docker Swarm, Nomad, Kubernetes o OpenShift. Si tot el sistema està escrit en Java, podeu mirar cap a Spring Cloud (bon article).

В part següent Parlaré de com he configurat Kubernetes i he desplegat el projecte a Google Kubernetes Engine.

Font: www.habr.com

Afegeix comentari