Aflați cum să implementați microservicii. Partea 1. Spring Boot și Docker

Aflați cum să implementați microservicii. Partea 1. Spring Boot și Docker

Hei Habr.

În acest articol, vreau să vorbesc despre experiența mea în crearea unui mediu de învățare pentru experimentarea cu microservicii. Când am învățat fiecare unealtă nouă, mi-am dorit întotdeauna să o încerc nu numai pe mașina locală, ci și în condiții mai realiste. Prin urmare, am decis să creez o aplicație simplificată de microservicii, care ulterior poate fi „acoperită” cu tot felul de tehnologii interesante. Principala cerință pentru proiect este proximitatea funcțională maximă a sistemului real.

Inițial, am împărțit crearea proiectului în mai mulți pași:

  1. Creați două servicii - „backend” (backend) și „gateway” (gateway), împachetați-le în imagini docker și configurați-le pentru a funcționa împreună

    Cuvinte cheie: Java 11, Spring Boot, Docker, optimizare a imaginii

  2. Dezvoltarea configurației Kubernetes și implementarea sistemului în Google Kubernetes Engine

    Cuvinte cheie: Kubernetes, GKE, gestionarea resurselor, scalare automată, secrete

  3. Crearea unei diagrame cu Helm 3 pentru o mai bună gestionare a clusterului

    Etichete: Helm 3, desfășurare diagramă

  4. Configurarea Jenkins și pipeline pentru livrarea automată a codului către cluster

    Cuvinte cheie: configurație Jenkins, pluginuri, depozit separat de configurații

Plănuiesc să dedic un articol separat fiecărui pas.

Accentul acestei serii de articole nu este cum să scrieți microservicii, ci cum să le faceți să funcționeze într-un singur sistem. Deși toate aceste lucruri sunt de obicei în afara responsabilității dezvoltatorului, cred că este totuși util să le cunoaștem cel puțin 20% (care, după cum știți, dau 80% din rezultat). Unele subiecte necondiționat importante, cum ar fi securitatea, vor fi lăsate în afara acestui proiect, deoarece autorul înțelege puțin despre acest sistem este creat exclusiv pentru uz personal. Apreciez orice opinii și critici constructive.

Crearea de microservicii

Serviciile au fost scrise în Java 11 folosind Spring Boot. Interacțiunea interservicii este organizată folosind REST. Proiectul va include un număr minim de teste (pentru ca mai târziu să fie ceva de testat în Jenkins). Codul sursă pentru servicii este disponibil pe GitHub: backend и Gateway.

Pentru a putea verifica starea fiecăruia dintre servicii, un Spring Actuator a fost adăugat la dependențele acestora. Acesta va crea un punct final /actuator/health și va returna o stare 200 dacă serviciul este gata să accepte trafic sau un 504 dacă există o problemă. În acest caz, aceasta este o verificare destul de fictivă, deoarece serviciile sunt foarte simple, iar în caz de forță majoră, este mai probabil ca acestea să devină complet indisponibile decât să rămână parțial operaționale. Dar, în sistemele reale, Actuator poate ajuta la diagnosticarea unei probleme înainte ca utilizatorii să înceapă să se lupte pentru aceasta. De exemplu, dacă există probleme la accesarea bazei de date, putem răspunde automat la aceasta prin oprirea procesării cererilor cu o instanță de serviciu defectă.

Serviciu de back-end

Serviciul backend va număra și va returna pur și simplu numărul de solicitări acceptate.

Cod controler:

@RestController
public class RequestsCounterController {

    private final AtomicLong counter = new AtomicLong();

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

Testul controlerului:

@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

Gateway-ul va redirecționa cererea către serviciul de backend, completând-o cu următoarele informații:

  • ID gateway. Este necesar astfel încât să fie posibil să se distingă o instanță a gateway-ului de alta prin răspunsul serverului
  • Un „secret” care va juca rolul unei parole foarte importante (numărul cheii de criptare a unui cookie important)

Configurare în application.properties:

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

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

Controlor:

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

Lansa:

Începem backend-ul:

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

Pornirea gateway-ului:

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

Verificăm:

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

Totul merge. Un cititor atent va observa că nimic nu ne împiedică să accesăm direct backend-ul, ocolind gateway-ul (http://localhost:8081/requests). Pentru a remedia acest lucru, serviciile trebuie să fie combinate într-o singură rețea și numai gateway-ul ar trebui să „iasă” afară.
De asemenea, ambele servicii partajează un sistem de fișiere, produc fluxuri și la un moment dat pot începe să interfereze între ele. Ar fi bine să ne izolăm microserviciile. Acest lucru poate fi realizat prin distribuirea aplicațiilor pe diferite mașini (mulți bani, dificil), folosind mașini virtuale (intensive de resurse, pornire lungă) sau folosind containerizarea. După cum era de așteptat, alegem a treia opțiune și Docher ca instrument de containerizare.

Docher

Pe scurt, docker creează containere izolate, câte unul pentru fiecare aplicație. Pentru a utiliza docker, trebuie să scrieți un Dockerfile - instrucțiuni pentru construirea și rularea aplicației. Apoi, puteți crea imaginea, o puteți încărca în registrul de imagini (nr. Dockerhub) și implementați microserviciul dvs. în orice mediu dockerizat într-o singură comandă.

Dockerfile

Una dintre cele mai importante caracteristici ale unei imagini este dimensiunea acesteia. O imagine compactă se va descărca mai repede dintr-un depozit de la distanță, va ocupa mai puțin spațiu, iar serviciul dvs. va începe mai repede. Orice imagine este construită pe baza imaginii de bază și este recomandat să alegeți cea mai minimalistă opțiune. O opțiune bună este Alpine, o distribuție Linux completă cu pachete minime.

Mai întâi, să încercăm să scriem un Dockerfile „pe frunte” (voi spune imediat că aceasta este o modalitate proastă, nu o face):

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

Aici folosim o imagine de bază bazată pe Alpine cu JDK-ul deja instalat pentru a construi proiectul nostru. Cu comanda ADD, adăugăm directorul src curent la imagine, îl marchem ca funcțional (WORKDIR) și începem construcția. Comanda EXPOSE 8080 semnalează docker-ului că aplicația din container își va folosi portul 8080 (acest lucru nu va face aplicația accesibilă din exterior, dar va permite accesarea aplicației, de exemplu, dintr-un alt container din aceeași rețea docker). ).

Pentru a împacheta serviciile în imagini, trebuie să rulați comenzi de la rădăcina fiecărui proiect:

docker image build . -t msvc-backend:1.0.0

Rezultatul este o imagine de 456 MB (din care imaginea JDK de bază a ocupat 340 MB). Și totul în ciuda faptului că orele din proiectul nostru pot fi numărate pe un deget. Pentru a reduce dimensiunea imaginii noastre:

  • Folosim asamblarea în mai multe etape. În primul pas vom construi proiectul, în al doilea pas vom instala JRE, iar în al treilea pas vom copia totul într-o nouă imagine Alpine curată. În total, doar componentele necesare vor fi în imaginea finală.
  • Să folosim modularizarea java. Începând cu Java 9, puteți utiliza instrumentul jlink pentru a crea un JRE doar din modulele de care aveți nevoie

Pentru cei curios, iată un articol bun despre abordările de reducere a imaginii. 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"]

Recreăm imaginea și, ca urmare, a pierdut de 6 ori greutatea ei, însumând 77 MB. Nu-i rău. După aceea, imaginile gata făcute pot fi încărcate în registrul de imagini, astfel încât imaginile dvs. să fie disponibile pentru descărcare de pe Internet.

Servicii de co-running în Docker

Pentru început, serviciile noastre trebuie să fie în aceeași rețea. Există mai multe tipuri de rețele în docker și o folosim pe cea mai primitivă dintre ele - bridge, care vă permite să conectați containerele care rulează pe aceeași gazdă. Creați o rețea cu următoarea comandă:

docker network create msvc-network

Apoi, porniți containerul backend numit „backend” cu imaginea microservices-backend:1.0.0:

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

Este demn de remarcat faptul că rețeaua de poduri oferă servicii de descoperire imediată pentru containere după numele lor. Adică, serviciul backend va fi disponibil în interiorul rețelei docker la http://backend:8080.

Pornirea gateway-ului:

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

În această comandă, indicăm că redirecționăm portul 80 al gazdei noastre către portul 8080 al containerului. Folosim opțiunile env pentru a seta variabilele de mediu care vor fi citite automat de spring și pentru a anula proprietățile din application.properties.

După pornire, sunăm http://localhost/ și asigurați-vă că totul funcționează, ca în cazul precedent.

Concluzie

Drept urmare, am creat două microservicii simple, le-am ambalat în containere docker și le-am lansat împreună pe aceeași mașină. Cu toate acestea, sistemul rezultat are o serie de dezavantaje:

  • Toleranță slabă la erori - totul funcționează pentru noi pe un singur server
  • Scalabilitate slabă - atunci când sarcina crește, ar fi bine să implementați automat instanțe de servicii suplimentare și să echilibrați sarcina între ele
  • Complexitatea lansării - trebuia să introducem cel puțin 3 comenzi și cu anumiți parametri (acest lucru este doar pentru 2 servicii)

Pentru a remedia problemele de mai sus, există o serie de soluții precum Docker Swarm, Nomad, Kubernetes sau OpenShift. Dacă întregul sistem este scris în Java, puteți privi spre Spring Cloud (bun articol).

В următoarea parte Voi vorbi despre cum am configurat Kubernetes și am implementat proiectul pe Google Kubernetes Engine.

Sursa: www.habr.com

Adauga un comentariu