
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:
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
Atslēgvārdi: Kubernetes, GKE, resursu pārvaldība, automātiskā mērogēšana, slepenie dati
Čarta izveide ar Helm 3, lai efektīvāk pārvaldītu klasteri
Atslēgvārdi: Helm 3, čarta izvietošana
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: un .
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.jarUzsākam gateway:
. /mvnw package -DskipTests
java -jar target/microservices-gateway-1.0.0.jarPā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 (). 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 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ā (№ ) 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.0Rezultā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. .
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-networkTā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.0Jā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ē .
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 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 ().
No es pastāstīšu par to, kā es konfigurēju Kubernetes un izvietoju projektu Google Kubernetes Engine.
Avots: habr.com
