Ikasi mikrozerbitzuak zabaltzen. 1. zatia. Spring Boot eta Docker

Ikasi mikrozerbitzuak zabaltzen. 1. zatia. Spring Boot eta Docker

Kaixo Habr.

Artikulu honetan, nire esperientziari buruz hitz egin nahi dut mikrozerbitzuekin esperimentatzeko ikasteko ingurune bat sortzean. Tresna berri bakoitza ikastean, beti probatu nahi izan dut nire tokiko makinan ez ezik, baldintza errealagoetan ere. Hori dela eta, mikrozerbitzuen aplikazio sinplifikatu bat sortzea erabaki nuen, gerora teknologia interesgarri guztiekin "zintzilikatu" ahal izateko. Proiektuaren baldintza nagusia sistema errealarekiko gehieneko hurbiltasun funtzionala da.

Hasieran, proiektuaren sorrera hainbat urratsetan banatu nuen:

  1. Sortu bi zerbitzu: 'backend' eta 'gateway', ontziratu docker irudietan eta konfiguratu elkarrekin lan egiteko

    Gako-hitzak: Java 11, Spring Boot, Docker, irudien optimizazioa

  2. Kubernetes konfigurazio eta inplementazio sistemaren garapena Google Kubernetes Engine-n

    Gako-hitzak: Kubernetes, GKE, baliabideen kudeaketa, eskalatze automatikoa, sekretuak

  3. Sortu diagrama Helm 3 erabiliz kluster kudeaketa eraginkorragoa izateko

    Gako-hitzak: Helm 3, diagramen hedapena

  4. Jenkins eta pipeline konfiguratzea kodea automatikoki klusterera emateko

    Gako-hitzak: Jenkins konfigurazioa, pluginak, konfigurazio-biltegi bereizia

Urrats bakoitzari artikulu bana eskaintzeko asmoa dut.

Artikulu sorta honen ardatza ez da mikrozerbitzuak nola idatzi, sistema bakar batean funtzionatzea baizik. Gauza hauek guztiak garatzailearen arduratik kanpo egon ohi diren arren, uste dut oraindik ere erabilgarria dela haiek gutxienez % 20 ezagutzea (emaitzaren % 80 hartzen duela jakina). Erabat garrantzitsuak diren gai batzuk, hala nola, segurtasuna, proiektu honetatik kanpo geratuko dira, egileak ezer gutxi ulertzen baitu honi buruz; sistema erabilera pertsonalerako soilik sortzen ari da. Pozten ditut edozein iritzi eta kritika eraikitzaile.

Mikrozerbitzuak sortzea

Zerbitzuak Java 11n idatzi ziren Spring Boot erabiliz. Zerbitzuen arteko komunikazioa REST erabiliz antolatzen da. Proiektuak gutxieneko proba kopuru bat barne hartuko du (gero Jenkinsen probatzeko zerbait egongo da). Zerbitzuen iturburu kodea GitHub-en dago eskuragarri: backend ΠΈ Pasabidea.

Zerbitzu bakoitzaren egoera egiaztatu ahal izateko, Spring Actuator bat gehitu zitzaion haien menpekotasunari. Amaiera-puntua /eragintzailea/osasuna sortuko du eta 200 egoera itzuliko du zerbitzua trafikoa onartzeko prest badago, edo 504 arazoak izanez gero. Kasu honetan, fikziozko egiaztapena da, zerbitzuak oso sinpleak baitira, eta ezinbesteko kasuren baten ondorioz guztiz erabilgarri ez egotea litekeena da partzialki funtzionatzen jarraitzea baino. Baina benetako sistemetan, Erabiltzaileak arazo bat diagnostikatzen lagun dezake erabiltzaileak jotzen hasi aurretik. Adibidez, datu-baserako sarbidearekin arazoak sortzen badira, horri automatikoki erantzuteko gai izango gara eskaerak prozesatzeari utziz zerbitzuaren instantzia hautsi batekin.

Backend zerbitzua

Backend zerbitzuak onartutako eskaera kopurua zenbatu eta itzuliko du.

Kontrolagailuaren kodea:

@RestController
public class RequestsCounterController {

    private final AtomicLong counter = new AtomicLong();

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

Kontrolagailuaren proba:

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

Pasabide zerbitzua

Pasabideak backend zerbitzura helaraziko du eskaera, informazio honekin osatuz:

  • atebidearen id. Beharrezkoa da zerbitzariaren erantzunaren arabera atebidearen instantzia bat beste batetik bereizteko
  • Pasahitz oso garrantzitsu baten papera beteko duen "sekretu" jakin bat (cookie garrantzitsu bat enkriptatzeko gako-zenbakia)

Konfigurazioa application.properties-en:

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

Backend-ekin komunikaziorako egokitzailea:

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

Kontrolatzailea:

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

Abiarazi:

Abiarazi dezagun backend-a:

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

Has gaitezen atea:

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

Egiaztatzen dugu:

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

Dena dabil. Irakurle adiak ohartuko da ezerk ez digula eragozten backend-era zuzenean sartzea, atebidea saihestuz (http://localhost:8081/requests). Hori konpontzeko, zerbitzuak sare batean konbinatu behar dira, eta atebidea bakarrik kanpoan "irten" behar da.
Gainera, bi zerbitzuek fitxategi-sistema bera partekatzen dute, hariak sortzen dituzte eta une batean elkar oztopatzen has daitezke. Polita litzateke gure mikrozerbitzuak isolatzea. Hori lortu daiteke aplikazioak makina ezberdinetan banatuz (diru asko, zailak), makina birtualak erabiliz (baliabide asko behar dituztenak, abiarazte luzea) edo edukiontzien bidez. Espero bezala, hirugarren aukera aukeratzen dugu eta Docker edukiontzirako tresna gisa.

Docker

Laburbilduz, Docker-ek edukiontzi isolatuak sortzen ditu, bat aplikazio bakoitzeko. Docker erabiltzeko, Dockerfile bat idatzi behar duzu - aplikazioa eraikitzeko eta exekutatzeko argibideak. Ondoren, irudia eraiki dezakezu, irudien erregistrora igo (Zk. Dockerhub) eta inplementatu zure mikrozerbitzua dokeratutako edozein ingurunetan komando bakarrean.

Dockerfile

Irudi baten ezaugarri garrantzitsuenetako bat bere tamaina da. Irudi trinko batek azkarrago deskargatuko du urruneko biltegi batetik, leku gutxiago hartuko du eta zure zerbitzua azkarrago hasiko da. Edozein irudi oinarrizko irudi batean oinarrituta eraikitzen da, eta aukerarik minimalistena aukeratzea gomendatzen da. Aukera ona da Alpine, pakete gutxien dituen Linux banaketa osoa.

Lehenik eta behin, saia gaitezen Dockerfile bat "buruz" idazten (berehala esango dut hori modu txarra dela, ez egin):

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

Hemen Alpine oinarritutako oinarrizko irudi bat erabiltzen ari gara dagoeneko instalatuta dagoen JDK-a gure proiektua eraikitzeko. ADD komandoa erabiliz, uneko src direktorioa gehitzen dugu irudiari, funtzionatzen duen moduan markatzen dugu (WORKDIR) eta eraikitzen hasten dugu. EXPOSE 8080 komandoak docker-ri adierazten dio edukiontziko aplikazioak bere 8080 ataka erabiliko duela (horrela ez da aplikazioa kanpotik eskuragarri egongo, baina aplikazioa atzitzeko aukera emango du, adibidez, docker sare bereko beste edukiontzi batetik ).

Zerbitzuak irudietan paketatzeko, proiektu bakoitzaren errotik komandoak exekutatu behar dituzu:

docker image build . -t msvc-backend:1.0.0

Ondorioz, 456 MB-ko irudia lortzen dugu (horietatik oinarrizko JDK 340 irudiak MB hartzen zuen). Eta dena gure proiektuko klaseak hatz bakarrean zenbatu daitezkeen arren. Gure irudiaren tamaina murrizteko:

  • Urrats anitzeko muntaia erabiltzen dugu. Lehenengo urratsean proiektua muntatuko dugu, bigarrenean JRE instalatuko dugu, eta hirugarren urratsean hau guztia Alpetar irudi garbi berri batean kopiatuko dugu. Guztira, azken irudiak beharrezko osagaiak baino ez ditu izango.
  • Erabili dezagun java modularizazioa. Java 9tik hasita, jlink tresna erabil dezakezu JRE bat sortzeko behar dituzun moduluetatik soilik

Jakin-mina dutenentzat, hona hemen irudien tamainak murrizteko planteamenduei buruzko artikulu on bat https://habr.com/ru/company/ruvds/blog/485650/.

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

Irudia birsortu genuen, eta azkenean 6 aldiz meheagoa bihurtu zen, 77 MB-koa. Ez dago gaizki. Ondoren, amaitutako irudiak irudien erregistrora igo daitezke, zure irudiak Internetetik deskargatzeko eskuragarri egon daitezen.

Docker-en zerbitzuak elkarrekin exekutatu

Hasteko, gure zerbitzuek sare berean egon behar dute. Docker-en hainbat sare mota daude, eta horietako primitiboena erabiltzen dugu: zubia, ostalari berean exekutatzen diren edukiontziak saretzeko aukera ematen duena. Sortu dezagun sare bat komando honekin:

docker network create msvc-network

Ondoren, abiarazi dezagun 'backend' izeneko backend edukiontzi bat microservices-backend:1.0.0 irudiarekin:

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

Azpimarratzekoa da zubi-sareak edukiontziak kutxatik kanpo aurkitzea eskaintzen duela haien izenekin. Hau da, backend zerbitzua Docker sarearen barruan egongo da eskuragarri http://backend:8080.

Has gaitezen atea:

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

Komando honetan gure ostalariaren 80 ataka edukiontziaren 8080 atakara birbidaltzen ari garela adierazten dugu. Env aukerak erabiltzen ditugu Spring-ek automatikoki irakurriko diren ingurune-aldagaiak ezartzeko eta application.properties-eko propietateak gainidazteko.

Abiarazi ondoren, deitu http://localhost/ eta ziurtatu dena funtzionatzen duela, aurreko kasuan bezala.

Ondorioa

Ondorioz, bi mikrozerbitzu sinple sortu genituen, docker edukiontzietan paketatu eta elkarrekin abiarazi genituen makina berean. Sortutako sistemak, ordea, desabantaila batzuk ditu:

  • Akatsen tolerantzia eskasa - dena zerbitzari batean funtzionatzen du
  • Eskalagarritasun eskasa - karga handitzen den heinean, ona izango litzateke automatikoki zerbitzu-instantzia gehigarriak zabaltzea eta haien arteko karga orekatzea.
  • Abiarazi konplexutasuna - gutxienez 3 komando sartu behar genituen, parametro jakin batzuekin (hau 2 zerbitzutarako bakarrik da)

Aurreko arazoak konpontzeko, hainbat irtenbide daude, hala nola Docker Swarm, Nomad, Kubernetes edo OpenShift. Sistema osoa Javan idatzita badago, Spring Cloud aldera begiratu dezakezu (artikulu ona).

Π’ hurrengo zatia Kubernetes konfiguratu eta Google Kubernetes Engine-en proiektua nola zabaldu dudan kontatuko dizut.

Iturria: www.habr.com

Gehitu iruzkin berria