Naučite kako da implementirate mikroservise. Dio 1. Spring Boot i Docker

Naučite kako da implementirate mikroservise. Dio 1. Spring Boot i Docker

Hej Habr.

U ovom članku želim govoriti o svom iskustvu u stvaranju okruženja za učenje za eksperimentiranje s mikroservisima. Kada sam učio novi alat, uvijek sam želio da ga isprobam ne samo na lokalnoj mašini, već iu realnijim uslovima. Stoga sam odlučio napraviti pojednostavljenu mikroservisnu aplikaciju, koja se kasnije može "pokriti" svim vrstama zanimljivih tehnologija. Glavni zahtjev za projekat je njegova maksimalna funkcionalna blizina stvarnom sistemu.

U početku sam kreiranje projekta razbio u nekoliko koraka:

  1. Kreirajte dvije usluge - 'backend' (backend) i 'gateway' (gateway), upakirajte ih u docker slike i postavite ih da rade zajedno

    Ključne riječi: Java 11, Spring Boot, Docker, optimizacija slike

  2. Razvoj Kubernetes konfiguracije i implementacije sistema u Google Kubernetes Engine

    Ključne riječi: Kubernetes, GKE, upravljanje resursima, automatsko skaliranje, tajne

  3. Kreiranje grafikona sa Helmom 3 za bolje upravljanje klasterima

    Oznake: Helm 3, implementacija karte

  4. Postavljanje Jenkinsa i cjevovoda za automatsku isporuku koda u klaster

    Ključne riječi: Jenkins konfiguracija, dodaci, odvojeno spremište konfiguracija

Planiram da svakom koraku posvetim poseban članak.

Fokus ove serije članaka nije kako napisati mikroservise, već kako ih natjerati da rade u jednom sistemu. Iako su sve ove stvari obično izvan odgovornosti programera, mislim da je ipak korisno biti upoznat s njima barem 20% (što, kao što znate, daje 80% rezultata). Neke bezuslovno važne teme, poput bezbednosti, biće izostavljene iz ovog projekta, jer autor malo razume da je ovaj sistem kreiran isključivo za ličnu upotrebu. Pozdravljam svako mišljenje i konstruktivnu kritiku.

Kreiranje mikroservisa

Usluge su napisane u Javi 11 koristeći Spring Boot. Interservisna interakcija je organizirana korištenjem REST-a. Projekat će uključivati ​​minimalan broj testova (kako bi kasnije bilo šta testirati u Jenkinsu). Izvorni kod za usluge je dostupan na GitHubu: backend и Gateway.

Da biste mogli provjeriti status svake od usluga, njihovim ovisnostima je dodan Spring Actuator. To će kreirati /actuator/health krajnju tačku i vratiti status 200 ako je usluga spremna za primanje saobraćaja, ili 504 ako postoji problem. U ovom slučaju radi se o prilično fiktivnoj provjeri, jer su usluge vrlo jednostavne, a u slučaju više sile vjerojatnije je da će postati potpuno nedostupne nego ostati djelomično u funkciji. Ali u stvarnim sistemima, Actuator može pomoći u dijagnosticiranju problema prije nego što se korisnici počnu boriti oko njega. Na primjer, ako postoje problemi s pristupom bazi podataka, možemo automatski odgovoriti na to zaustavljanjem obrade zahtjeva sa pokvarenom instancom usluge.

Back end service

Pozadinski servis će jednostavno prebrojati i vratiti broj prihvaćenih zahtjeva.

Šifra kontrolera:

@RestController
public class RequestsCounterController {

    private final AtomicLong counter = new AtomicLong();

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

Test kontrolera:

@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 će proslijediti zahtjev backend servisu, dopunivši ga sljedećim informacijama:

  • gateway id. Potreban je kako bi bilo moguće razlikovati jednu instancu gatewaya od druge po odgovoru servera
  • Neka "tajna" koja će igrati ulogu vrlo važne lozinke (broj ključa za šifriranje važnog kolačića)

Konfiguracija u aplikaciji.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();
    }
}

Kontroler:

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

Pokreni:

Pokrećemo backend:

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

Pokretanje gatewaya:

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

Provjeravamo:

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

Sve radi. Pažljivi čitatelj će primijetiti da nas ništa ne sprječava da direktno pristupimo backendu, zaobilazeći gateway (http://localhost:8081/requests). Da bi se ovo popravilo, servisi moraju biti spojeni u jednu mrežu, a samo gateway treba da "štrči" izvana.
Također, oba servisa dijele jedan sistem datoteka, proizvode streamove i u jednom trenutku mogu početi ometati jedni druge. Bilo bi lijepo izolirati naše mikroservise. Ovo se može postići distribucijom aplikacija na različite mašine (puno novca, teško), korištenjem virtuelnih mašina (zahtjevne resurse, dugo pokretanje) ili korištenjem kontejnerizacije. Očekivano, biramo treću opciju i doker kao alat za kontejnerizaciju.

doker

Ukratko, docker kreira izolirane kontejnere, jedan po aplikaciji. Da biste koristili docker, potrebno je da napišete Dockerfile - upute za izgradnju i pokretanje aplikacije. Zatim možete napraviti sliku, otpremiti je u registar slika (br. Dockerhub) i implementirajte svoj mikroservis u bilo koje dokerizirano okruženje u jednoj naredbi.

dockerfile

Jedna od najvažnijih karakteristika slike je njena veličina. Kompaktna slika će se preuzeti brže iz udaljenog spremišta, zauzeti manje prostora, a vaša usluga će početi brže. Bilo koja slika je izgrađena na osnovu osnovne slike, a preporučuje se odabir najminimalističnije opcije. Dobra opcija je Alpine, kompletna Linux distribucija sa minimalnim paketima.

Za početak, pokušajmo napisati Dockerfile "na čelo" (odmah ću reći da je ovo loš način, nemojte to činiti):

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

Ovdje koristimo osnovnu sliku baziranu na Alpineu sa već instaliranim JDK-om za izgradnju našeg projekta. Sa naredbom ADD, dodajemo trenutni src direktorij slici, označavamo ga kao radni (WORKDIR) i pokrećemo build. Naredba EXPOSE 8080 signalizira docker-u da će aplikacija u kontejneru koristiti svoj port 8080 (ovo neće učiniti aplikaciju dostupnom izvana, ali će omogućiti pristup aplikaciji, na primjer, iz drugog kontejnera na istoj docker mreži ).

Da biste upakovali usluge u slike, morate pokrenuti naredbe iz korijena svakog projekta:

docker image build . -t msvc-backend:1.0.0

Rezultat je slika od 456 MB (od čega je osnovna JDK slika zauzimala 340 MB). I sve to uprkos činjenici da se časovi u našem projektu mogu izbrojati na prst. Da smanjite veličinu naše slike:

  • Koristimo montažu u više koraka. U prvom koraku ćemo izgraditi projekat, u drugom ćemo instalirati JRE, au trećem koraku sve ćemo kopirati u novi čisti Alpine image. Ukupno, samo potrebne komponente će biti na konačnoj slici.
  • Koristimo modularizaciju java. Počevši od Jave 9, možete koristiti jlink alat da kreirate JRE samo od modula koji su vam potrebni

Za radoznale, evo dobrog članka o pristupima smanjenja slike. https://habr.com/ru/company/ruvds/blog/485650/.

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

Rekreirali smo sliku i kao rezultat toga, izgubila je 6 puta svoju težinu, što je iznosilo 77 MB. Nije loše. Nakon toga, gotove slike se mogu učitati u registar slika kako bi vaše slike bile dostupne za preuzimanje sa Interneta.

Usluge zajedničkog vođenja u Dockeru

Za početak, naše usluge moraju biti na istoj mreži. Postoji nekoliko tipova mreža u dockeru, a mi koristimo najprimitivniju od njih - bridge, koji vam omogućava da umrežite kontejnere koji rade na istom hostu. Kreirajte mrežu sa sljedećom naredbom:

docker network create msvc-network

Zatim pokrenite backend kontejner pod nazivom 'backend' sa slikom microservices-backend:1.0.0:

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

Vrijedi napomenuti da mreža mostova pruža gotovu uslugu otkrivanja kontejnera po njihovim imenima. To jest, backend usluga će biti dostupna unutar docker mreže na adresi http://backend:8080.

Pokretanje gatewaya:

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

U ovoj naredbi ukazujemo da prosljeđujemo port 80 našeg hosta na port 8080 kontejnera. Koristimo env opcije da postavimo varijable okruženja koje će se automatski čitati od strane spring i nadjačati svojstva iz application.properties.

Nakon početka, zovemo http://localhost/ i uvjerite se da sve radi, kao u prethodnom slučaju.

zaključak

Kao rezultat toga, kreirali smo dva jednostavna mikroservisa, spakovali ih u docker kontejnere i zajedno ih pokrenuli na istoj mašini. Rezultirajući sistem, međutim, ima niz nedostataka:

  • Slaba tolerancija grešaka - sve nam radi na jednom serveru
  • Loša skalabilnost - kada se opterećenje poveća, bilo bi lijepo automatski implementirati dodatne instance usluge i balansirati opterećenje između njih
  • Složenost pokretanja - morali smo unijeti najmanje 3 komande, i sa određenim parametrima (ovo je samo za 2 usluge)

Da biste riješili gore navedene probleme, postoji niz rješenja kao što su Docker Swarm, Nomad, Kubernetes ili OpenShift. Ako je cijeli sistem napisan na Javi, možete pogledati prema Spring Cloud (dobar članak).

В sljedeći dio Govorit ću o tome kako sam postavio Kubernetes i implementirao projekat na Google Kubernetes Engine.

izvor: www.habr.com

Dodajte komentar