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

Naučite kako implementirati 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. Kad sam učio svaki novi alat, uvijek sam ga želio isprobati ne samo na lokalnom stroju, već iu realnijim uvjetima. Stoga sam odlučio napraviti pojednostavljenu mikroservisnu aplikaciju, koja se kasnije može "pokriti" raznim zanimljivim tehnologijama. Glavni zahtjev za projekt je njegova maksimalna funkcionalna blizina stvarnom sustavu.

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

  1. Napravite dvije usluge - 'backend' (pozadina) i 'gateway' (pristupnik), 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 sustava u Google Kubernetes Engineu

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

  3. Izrada grafikona s Helmom 3 za bolje upravljanje klasterom

    Oznake: Helm 3, implementacija karte

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

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

Planiram posvetiti poseban članak svakom koraku.

Fokus ove serije članaka nije kako napisati mikroservise, već kako ih natjerati da rade u jednom sustavu. Iako su sve te 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 bezuvjetno važne teme, poput sigurnosti, bit će izostavljene iz ovog projekta, budući da autor malo razumije ovaj sustav stvoren isključivo za osobnu upotrebu. Pozdravljam svako mišljenje i konstruktivnu kritiku.

Izrada mikroservisa

Usluge su napisane u Javi 11 korištenjem Spring Boot-a. Međuservisna interakcija organizirana je pomoću REST-a. Projekt će uključivati ​​minimalan broj testova (tako da se kasnije ima što testirati u Jenkinsu). Izvorni kod za usluge dostupan je na GitHubu: pozadina и Gateway.

Kako biste mogli provjeriti status svake od usluga, njihovim ovisnostima je dodan opružni pokretač. Stvorit će krajnju točku /actuator/health i vratiti status 200 ako je usluga spremna prihvatiti promet ili 504 ako postoji problem. U ovom slučaju radi se o prilično fiktivnoj provjeri, budući da su usluge vrlo jednostavne, te će u slučaju više sile vjerojatnije postati potpuno nedostupne nego ostati djelomično operativne. Ali u stvarnim sustavima, Actuator može pomoći u dijagnosticiranju problema prije nego što se korisnici počnu boriti oko toga. Na primjer, ako postoje problemi s pristupom bazi podataka, možemo automatski odgovoriti na to zaustavljanjem obrade zahtjeva s pokvarenom instancom usluge.

Pozadinska usluga

Pozadinska usluga jednostavno će prebrojati i vratiti broj prihvaćenih zahtjeva.

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

Servisni pristupnik

Gateway će proslijediti zahtjev pozadinskoj usluzi, dopunjujući ga sljedećim informacijama:

  • ID pristupnika. Potreban je kako bi bilo moguće razlikovati jednu instancu pristupnika od druge prema odgovoru poslužitelja
  • 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"

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

Upravljač:

@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 pristupnika:

./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 primijetit će da nas ništa ne sprječava da izravno pristupimo pozadini, zaobilazeći gateway (http://localhost:8081/requests). Da biste to popravili, usluge se moraju kombinirati u jednu mrežu, a samo gateway treba "stršati" van.
Također, oba servisa dijele jedan datotečni sustav, proizvode streamove i u jednom trenutku mogu početi ometati jedan drugog. Bilo bi lijepo izolirati naše mikroservise. To se može postići distribucijom aplikacija na različitim strojevima (puno novca, teško), korištenjem virtualnih strojeva (zahtjevaju resurse, dugo pokretanje) ili korištenjem kontejnerizacije. Očekivano, biramo treću opciju i Lučki radnik kao alat za kontejnerizaciju.

Lučki radnik

Ukratko, docker stvara izolirane spremnike, jedan po aplikaciji. Da biste koristili docker, morate napisati Dockerfile - upute za izgradnju i pokretanje aplikacije. Zatim možete izraditi sliku, učitati je u registar slika (br. Dockerhub) i implementirajte svoju mikrouslugu u bilo kojem dockeriziranom okruženju jednom naredbom.

dockerfile

Jedna od najvažnijih karakteristika slike je njezina veličina. Kompaktna slika će se brže preuzeti iz udaljenog repozitorija, zauzeti će manje prostora, a vaša će se usluga pokrenuti brže. Svaka slika izgrađena je na temelju osnovne slike, a preporuča se odabrati najminimalniju opciju. Dobra opcija je Alpine, kompletna Linux distribucija s minimalnim paketima.

Za početak, pokušajmo napisati Dockerfile "na čelo" (odmah ću reći da je to 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 s već instaliranim JDK-om za izgradnju našeg projekta. Naredbom ADD dodajemo trenutni src direktorij na sliku, označavamo ga kao radni (WORKDIR) i započinjemo izgradnju. Naredba EXPOSE 8080 signalizira dockeru da će aplikacija u spremniku koristiti svoj priključak 8080 (ovo neće učiniti aplikaciju dostupnom izvana, ali će omogućiti pristup aplikaciji, na primjer, iz drugog spremnika na istoj docker mreži ).

Za pakiranje usluga 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 unatoč činjenici da se nastava u našem projektu može nabrojati na prst. Da smanjimo veličinu naše slike:

  • Koristimo montažu u više koraka. U prvom koraku ćemo izgraditi projekt, u drugom koraku ćemo instalirati JRE, au trećem koraku ćemo sve to kopirati u novu čistu Alpine sliku. Ukupno će na konačnoj slici biti samo potrebne komponente.
  • Iskoristimo modularizaciju Jave. Počevši od Jave 9, možete koristiti alat jlink za stvaranje JRE-a samo od modula koji su vam potrebni

Za znatiželjne, ovdje je dobar članak o pristupima smanjenju slike. https://habr.com/ru/company/ruvds/blog/485650/.

Konačna Docker datoteka:

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

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

Usluge zajedničkog vođenja u Dockeru

Za početak, naše usluge moraju biti na istoj mreži. U dockeru postoji nekoliko vrsta mreža, a mi koristimo najprimitivniju od njih - bridge, koja vam omogućuje umrežavanje spremnika koji rade na istom hostu. Stvorite mrežu sljedećom naredbom:

docker network create msvc-network

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

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

Vrijedno je napomenuti da premosnička mreža omogućuje otkrivanje usluge izvan kutije za kontejnere prema njihovim nazivima. To jest, pozadinska usluga bit će dostupna unutar docker mreže na http://backend:8080.

Pokretanje pristupnika:

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 glavnog računala na port 8080 spremnika. Koristimo opcije env za postavljanje varijabli okruženja koje će proljeće automatski čitati i nadjačati svojstva iz application.properties.

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

Zaključak

Kao rezultat toga, stvorili smo dvije jednostavne mikrousluge, upakirali ih u docker spremnike i zajedno pokrenuli na istom stroju. Rezultirajući sustav, međutim, ima brojne nedostatke:

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

Za rješavanje gore navedenih problema postoji niz rješenja kao što su Docker Swarm, Nomad, Kubernetes ili OpenShift. Ako je cijeli sustav napisan u Javi, možete pogledati prema Spring Cloudu (dobar članak).

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

Izvor: www.habr.com

Dodajte komentar