Mācāmies izstrādāt mikroservisus. 1. daļa. Spring Boot un Docker

Mācāmies izstrādāt mikroservisus. 1. daļa. Spring Boot un Docker

Sveiki, Habr.

Å ajā rakstā es vēlos dalÄ«ties ar savu pieredzi mācÄ«bu vides izveidē eksperimentiem ar mikroservisiem. Mācoties katru jauno rÄ«ku, man vienmēr ir bijusi vēlēŔanās to izmēģināt ne tikai vietējā maŔīnā, bet arÄ« reālistiskākos apstākļos. Tāpēc es nolēmu izveidot vienkārÅ”u mikroservisu lietotni, kuru vēlāk varēŔu "apgādāt" ar dažādām interesantām tehnoloÄ£ijām. Galvenais projekta prasÄ«ba ir maksimāla funkcionalitāte reālās sistēmas tuvumā.

Sākotnēji es sadalÄ«ju projekta radīŔanu vairākos posmos:

  1. Izveidot divus servisus — 'backend' un 'gateway', iepakot tos Docker attēlos un konfigurēt to savstarpējo darbu.

    Atslēgvārdi: Java 11, Spring Boot, Docker, attēla optimizācija

  2. Kubernetes konfigurācijas izstrāde un sistēmas izvietoÅ”ana Google Kubernetes Engine

    Atslēgvārdi: Kubernetes, GKE, resursu pārvaldÄ«ba, automātiskā mērogēŔana, slepenie dati

  3. Čarta izveide ar Helm 3, lai efektīvāk pārvaldītu klasteri

    Atslēgvārdi: Helm 3, čarta izvietoÅ”ana

  4. Jenkins konfigurācija un pipelines iestatīŔana automātiskai koda piegādei klasterī

    Atslēgvārdi: Jenkins konfigurācija, spraudņi, atseviŔķs konfigurāciju repozitorijs

Katram solim es plānoju veltīt atseviŔķu rakstu.

Å Ä« rakstu cikla mērÄ·is nav stāstÄ«t, kā rakstÄ«t mikroservisus, bet gan kā likt tiem strādāt vienotā sistēmā. Lai gan visi Å”ie aspekti parasti ir ārpus izstrādātāju atbildÄ«bas, es domāju, ka tomēr ir noderÄ«gi bÅ«t vismaz 20% ar tiem iepazinuÅ”amies (kas, kā zināms, sniedz 80% rezultāta). Dažas bez Å”aubām svarÄ«gas tēmas, piemēram, droŔības nodroÅ”ināŔana, Ŕī projekta ietvaros netiks apskatÄ«tas, jo autors tam maz ko saprot; sistēma tiek izveidota tikai personÄ«gai lietoÅ”anai. Es būŔu pateicÄ«gs par jebkādām atsauksmēm un konstruktÄ«vu kritiku.

Mikroservisu izveide

Servisi tika rakstīti, izmantojot Java 11 ar Spring Boot. Starpservisu mijiedarbība tika organizēta, izmantojot REST. Projekts ietvers minimālo testu skaitu (lai vēlāk būtu, ko testēt Jenkins). Tīkla kods ir pieejams GitHub: backend un gateway.

Lai pārbaudÄ«tu katra pakalpojuma stāvokli, tam tika pievienots Spring Actuator. Tas izveidos endpointu /actuator/health un atgriezÄ«s 200 statusu, ja pakalpojums ir gatavs pieņemt satiksmi, vai 504 gadÄ«jumā, ja ir problēmas. Å ajā gadÄ«jumā tas ir diezgan fikcionāla pārbaude, jo pakalpojumi ir ļoti vienkārÅ”i, un kādā ārkārtas situācijā tie drÄ«zāk kļūs pilnÄ«gi nepieejami, nevis saglabās daļēju darbÄ«bu. Bet reālās sistēmās Actuator var palÄ«dzēt diagnosticēt problēmu pirms tās sāk skart lietotājus. Piemēram, ja rodas problēmas ar piekļuvi DB, mēs varēsim automātiski reaģēt, pārtraucot apstrādāt pieprasÄ«jumus ar bojātu pakalpojuma eksemplāru.

Backend pakalpojums

Backend pakalpojums vienkārÅ”i saskaitÄ«s un atgriezÄ«s saņemto pieprasÄ«jumu skaitu.

Kontroliera kods:

@RestController
public class RequestsCounterController {

    private final AtomicLong counter = new AtomicLong();

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

Tests kontrolierim:

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

Gateway pakalpojums

Gateway pāradresēs pieprasÄ«jumu uz backend pakalpojumu, papildinot to ar sekojoÅ”o informāciju:

  • gateway id. Tas ir nepiecieÅ”ams, lai varētu pēc servera atbildes atŔķirt vienu gateway eksemplāru no otra
  • Kāds "noslēpums", kas bÅ«s ļoti svarÄ«ga parole (Å”ifrēŔanas atslēgas numurs svarÄ«gai sÄ«kdatnei)

Konfigurācija failā application.properties:

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

Adapteris saziņai ar 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 response = restTemplate.getForEntity(
backendUrl + REQUESTS_ENDPOINT, String.class);
        return response.getBody();
    }
}

Kontrolieris:

@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("Pieprasījumu skaits %s (gateway %d, noslēpums %s)", backendAdapter.getRequests(), instanceId, secret);
    }
}

PalaiŔana:

Uzsākam backend:

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

Uzsākam gateway:

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

Pārbaudām:

$ curl http://localhost:8080/
Pieprasījumu skaits 1 (vārteja 38560358, slepenais "default-secret")

Viss strādā. VērÄ«gs lasÄ«tājs pamanÄ«s, ka nekas netraucē tieÅ”i piekļūt aizmugurējai daļai, apejot vārteju (http://localhost:8081/requests). Lai to labotu, pakalpojumiem jābÅ«t apvienotiem vienā tÄ«klā, un ārpuses "jāspÄ«d" tikai vārtejai.
Abiem pakalpojumiem ir arÄ« kopÄ«ga failu sistēma, kas rada procesus, un vienlaikus tie var traucēt viens otram. BÅ«tu labi izolēt mÅ«su mikroservisus. To var panākt, izmantojot dažādas maŔīnas (daudz naudas, sarežģīti), izmantojot virtuālās maŔīnas (resursu intensÄ«vi, ilgs palaiÅ”anas laiks) vai, izmantojot konteinerizāciju. Kā gaidÄ«ts, izvēlamies treÅ”o variantu un Docker kā konteinerizācijas rÄ«ku.

Docker

ÄŖsi sakot, Docker rada izolētus konteinerus, pa vienam katrai lietojumprogrammām. Lai izmantotu Docker, ir jāuzraksta Dockerfile — instrukcija, kā izveidot un palaist lietojumprogrammu. Pēc tam varēsiet izveidot attēlu, augÅ”upielādēt to attēlu reÄ£istrā (ā„– DockerHub) un vienā komandā izvērst savu mikroservisu jebkurā dockerizētā vidē.

Dockerfile

Viena no svarÄ«gākajām attēla Ä«paŔībām ir tā izmērs. Kompaktāks attēls ātrāk tiks palaists no attālā repozitorija, aizņems mazāk vietas, un jÅ«su pakalpojums ātrāk startēs. Ikviens attēls tiek veidots, pamatojoties uz bāzes attēlu, un ieteicams izvēlēties vismazākās iespējamās variants. Laba izvēle ir Alpine — pilnÄ«gs Linux distribÅ«cija ar minimālo pakotņu skaitu.

Sāksim ar Dockerfile rakstīŔanu "no malas" (uzreiz saku, ka tā ir slikta pieeja, nerakstiet tā):

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

Å eit mēs izmantojam bāzes attēlu, kas balstÄ«ts uz Alpine ar jau uzstādÄ«tu JDK, lai izveidotu mÅ«su projektu. Ar komandu ADD pievienojam attēlam paÅ”reizējo direktoriju src, nosakām to par darba direktoriju (WORKDIR) un uzsākam bÅ«vniecÄ«bu. Komanda EXPOSE 8080 informē Docker, ka lietojumprogramma konteinerā izmantos tā 8080 portu (tas nepadarÄ«s lietojumprogrammu pieejamu no ārpuses, bet ļaus piekļūt lietojumprogrammai, piemēram, no cita konteineru tajā paŔā Docker tÄ«klā).

Lai iepakotu pakalpojumus attēlos, ir jāizpilda komandas no katra projekta saknes:

docker image build . -t msvc-backend:1.0.0

Rezultātā iegūstam attēlu, kura izmērs ir 456 MB (no tiem pamata attēls JDK 340 aizņēma MB). Un tas viss, neskatoties uz to, ka mūsu projektā ir skaitāmi pirkstiņiem. Lai samazinātu mūsu attēla izmēru:

  • Izmantojam daudzsolÄ«gu bÅ«ves metodi. Pirmajā solÄ« apkoposim projektu, otrajā instalēsim JRE, bet treÅ”ajā solÄ« visu to pārsÅ«tÄ«sim uz jaunu, tÄ«ru Alpine attēlu. Tādējādi gala attēlā nonāks tikai nepiecieÅ”amie komponenti.
  • Izmantosim Java modularizāciju. Sākot ar Java 9, izmantojot rÄ«ku jlink, var izveidot JRE tikai no vajadzÄ«gajiem moduļiem.

ZiņkārÄ«gajiem, Å”eit ir laba raksts par pieejām attēlu izmēru samazināŔanai. https://habr.com/ru/company/ruvds/blog/485650/.

Gala 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 anshelen@yandex.ru"
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"]

Pārveidojam attēlu, un tas galu galā ir samazinājies par 6 reizēm, sasniedzot 77 MB. Labi. Pēc tam gatavos attēlus var augÅ”upielādēt attēlu reÄ£istrā, lai jÅ«su attēli bÅ«tu pieejami lejupielādei no interneta.

Kopīga pakalpojumu palaiŔana Docker

Vispirms mÅ«su pakalpojumiem jāatrodas vienā tÄ«klā. DockerÄ« ir vairāki tÄ«klu veidi, un mēs izmantojam vienkārŔāko no tiem — bridge, kas ļauj apvienot tÄ«klā konteinerus, kas palaisti uz viena hosta. Izveidosim tÄ«klu ar sekojoÅ”o komandu:

docker network create msvc-network

Tālāk palaidÄ«sim backend konteineru ar nosaukumu ā€˜backend’ ar attēlu microservices-backend:1.0.0:

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

JāatzÄ«mē, ka bridge-tÄ«kls nodroÅ”ina pakalpojumu atklāŔanu konteineriem pēc to nosaukumiem. Tas ir, backend pakalpojums bÅ«s pieejams iekÅ”pusē Docker tÄ«kla adresē http://backend:8080.

Uzsākam gateway:

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

Å ajā komandā mēs norādām, ka pārsÅ«tām mÅ«su hosta 80. portu uz konteineru 8080. portu. Env parametri tiek izmantoti, lai iestatÄ«tu vides mainÄ«gus, kurus automātiski nolasÄ«s Spring un pārrakstÄ«s Ä«paŔības no application.properties.

Pēc palaiÅ”anas izsaucam http://localhost/ un pārliecināmies, ka viss darbojas kā iepriekŔējā reizē.

Secinājums

Galu galā mēs izveidojām divus vienkārÅ”us mikroservisus, iesaiņojām tos Docker konteineros un palaidām kopā vienā maŔīnā. Tomēr iegÅ«tajai sistēmai ir vairāki trÅ«kumi:

  • Slikta pieejamÄ«ba — viss darbojas uz viena servera
  • Slikta mērogojamÄ«ba — palielinoties slodzei, bÅ«tu labi automātiski izvietot papildu pakalpojumu instancēm un lÄ«dzsvarot slodzi starp tām
  • Sarežģīts palaiÅ”ana — mums bija jāievada vismaz 3 komandas, turklāt ar noteiktiem parametriem (tas tikai 2 pakalpojumiem)

Lai novērstu iepriekÅ” minētās problēmas, ir vairākas risinājumu iespējas, piemēram, Docker Swarm, Nomad, Kubernetes vai OpenShift. Ja visa sistēma tiks rakstÄ«ta Java, var skatÄ«ties uz Spring Cloud (laba raksts).

No nākamajā daļā es pastāstīŔu par to, kā es konfigurēju Kubernetes un izvietoju projektu Google Kubernetes Engine.

Avots: habr.com

Iegādājieties uzticamu tÄ«mekļa hostingu ar DDoS aizsardzÄ«bu, VPS VDS serveriem šŸ”„ Iegādājieties uzticamu tÄ«mekļa hostingu ar DDoS aizsardzÄ«bu, VPS VDS serveriem | ProHoster