
Përshëndetje, Habr.
Në këtë artikull dëshiroj të ndaj përvojën time në krijimin e një ambienti mësimor për eksperimente me mikroshërbime. Kur studioja çdo mjet të ri, gjithmonë më pëlqente ta provonja jo vetëm në makinën lokale, por edhe në kushte më realiste. Prandaj, vendosa të krijoj një aplikacion të thjeshtë mikroshërbimi, të cilin më vonë mund ta "pajis" me teknologji interesante. Kërkesa kryesore për projektin është që të jetë sa më funksionalisht afër një sistemi real.
Fillimisht e ndava krijimin e projektit në disa hapa:
Të krijoj dy shërbime - 'backend' dhe 'gateway', t'i paketoj ato në imazhe docker dhe të konfigur në përputhje me njëra-tjetrën.
Fjalë kyçe: Java 11, Spring Boot, Docker, optimizimi i imazheve.
Fjalë kyçe: Kubernetes, GKE, menaxhimi i burimeve, autoscaling, sekrete.
Krijimi i një chart me ndihmën e Helm 3 për menaxhim më efikas të klasterit.
Fjalë kyçe: Helm 3, shpërndarja e chart-eve.
Konfigurimi i Jenkins dhe pipeline për dërgimin automatik të kodit në klaster.
Fjalë kyçe: konfigurimi i Jenkins, plugins, arkivi i konfigurimeve të ndara.
Ădo hapi planifikoj t'i kushtoj njĂ« artikull tĂ« veçantĂ«.
Orientimi i kĂ«tij cikli artikujsh nuk Ă«shtĂ« se si tĂ« shkruhen mikroshĂ«rbimet, por si tâi bĂ«sh ato tĂ« funksionojnĂ« nĂ« njĂ« sistem tĂ« vetme. Edhe pse tĂ« gjitha kĂ«to gjĂ«ra zakonisht janĂ« jashtĂ« pĂ«rgjegjĂ«sisĂ« sĂ« zhvilluesit, mendoj se Ă«shtĂ« ende e dobishme tĂ« jesh i njohur me to, tĂ« paktĂ«n 20% (tĂ« cilat, siç dihet, japin 80% tĂ« rezultateve). Disa tema jashtĂ«zakonisht tĂ« rĂ«ndĂ«sishme, siç Ă«shtĂ« sigurimi i sigurisĂ«, do tĂ« lihen jashtĂ« kĂ«tij projekti, pasi autori ka pak njohuri nĂ« kĂ«tĂ« fushĂ« - sistemi krijohet ekskluzivisht pĂ«r pĂ«rdorim personal. Do tĂ« isha i lumtur pĂ«r çdo mendim dhe kritikĂ« konstruktive.
Krijimi i mikroshërbimeve.
Shërbimet u shkruan në Java 11 duke përdorur Spring Boot. Ndërveprimi ndërmjet shërbimeve u organizua duke përdorur REST. Projekti do të përfshijë një numër minimal testesh (që më vonë do të kishte për të testuar në Jenkins). Kodi burimor i shërbimeve është i disponueshëm në GitHub: dhe .
Për të pasur mundësinë të kontrolloni gjendjen e çdo shërbimi, në varësi të tyre u shtua Spring Actuator. Ai do të krijojë një pikë fundore "/actuator/health" dhe do të kthejë statusin 200, nëse shërbimi është gati për të pranuar trafik, ose 504 në rast problemesh. Në këtë rast, kjo është një kontrollim mjaft fiktiv, pasi shërbimet janë shumë të thjeshta, dhe në rast të ndonjë forcimi, ato shumë probabilisht do të bëhen plotësisht të paaksesueshme, sesa të ruajnë një funksionalitet të pjesshëm. Por në sisteme të vërteta, Actuator mund të ndihmojë në diagnostikimin e problemit para se të fillojnë të përballen me të përdoruesit. Për shembull, në rast të problemeve me aksesin në DB, ne do të mund të reagojmë automatikisht duke ndaluar procesimin e kërkesave me një instancë të dëmtuar të shërbimit.
Shërbimi Backend
Shërbimi i backend-it do të numërojë dhe të kthejë numrin e kërkesave të pranuara.
Kodi i kontrolerit:
@RestController
public class RequestsCounterController {
private final AtomicLong counter = new AtomicLong();
@GetMapping("/requests")
public Long getRequestsCount() {
return counter.incrementAndGet();
}
}Testi për kontrolerin:
@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"));
}
}Shërbimi Gateway
Gateway do të redirektojë kërkesat në shërbimin backend, duke e pasuruar atë me informacionin e mëposhtëm:
- ID e gateway-it. Kjo është e nevojshme për të dalluar një instancë gateway nga një tjetër në përgjigjen e serverit.
- NjĂ« "sekret", i cili do tĂ« luajĂ« rolin e njĂ« fjale kalimi shumĂ« tĂ« rĂ«ndĂ«sishme (â çelĂ«si i enkriptimit tĂ« njĂ« cookie tĂ« rĂ«ndĂ«sishĂ«m)
Konfigurimi në application.properties:
backend.url=http://localhost:8081
instance.id=${random.int}
secret="default-secret"Adaptori për lidhjen me backend-in:
@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();
}
}Kontroleri:
@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("Numri i kërkesave %s (gateway %d, sekret %s)", backendAdapter.getRequests(), instanceId, secret);
}
}Fillimi:
Nisim backend-in:
./mvnw package -DskipTests
java -Dserver.port=8081 -jar target/microservices-backend-1.0.0.jarNisim gateway-in:
./mvnw package -DskipTests
java -jar target/microservices-gateway-1.0.0.jarPo kontrollojmë:
$ curl http://localhost:8080/
Numri i kërkesave 1 (gateway 38560358, sekreti "default-secret")Gjithçka funksionon. Lexuesi i kujdesshëm do të vërejë se nuk ka asgjë që na pengon të drejtohemi drejtpërdrejt në backend duke kaluar përmes gateway-it (). Për ta korrigjuar këtë, shërbimet duhet të bashkohen në një rrjet, dhe jashtë duhet të "dalë" vetëm gateway-i.
Po ashtu, të dy shërbimet ndajnë një sistem të përbashkët skedari, krijojnë procese dhe njëherazi mund të fillojnë të pengojnë njëri-tjetrin. Do të ishte mirë të izoloheshin mikrosherbimet tona. Kjo mund të arrihet duke ndarë aplikacionet në makina të ndryshme (shumë shpenzime, e komplikuar), duke përdorur makina virtuale (burimore, fillim i gjatë) ose duke përdorur konteinerizimin. Siç është e pritur, zgjedhim opsionin e tretë dhe si një mjet për konteinerizimin.
Docker
NĂ«se nĂ« pak fjalĂ«, Docker krijon konteinerĂ« tĂ« izoluar, njĂ« pĂ«r secilin aplikacion. PĂ«r tĂ« pĂ«rdorur Docker, duhet tĂ« shkruani njĂ« Dockerfile â njĂ« udhĂ«zim pĂ«r ndĂ«rtimin dhe ekzekutimin e aplikacionit. MĂ« pas mund tĂ« skloni imazhin, ta ngarkoni nĂ« regjistrin e imazheve (â ) dhe me njĂ« komandĂ« tĂ« vetme tĂ« zhvilloni mikrosherbimin tuaj nĂ« çdo mjedis tĂ« dockerizuar.
Dockerfile
NjĂ« nga karakteristikat mĂ« tĂ« rĂ«ndĂ«sishme tĂ« imazhit Ă«shtĂ« pĂ«rmasa e tij. NjĂ« imazh kompakt shkarkohet mĂ« shpejt nga regjistri i largĂ«t, merr mĂ« pak hapĂ«sirĂ« dhe shĂ«rbimi juaj fillon mĂ« shpejt. Ădo imazh ndĂ«rtoset mbi njĂ« imazh bazĂ«, dhe rekomandohet tĂ« zgjidhni variantin mĂ« minimal. NjĂ« variant i mirĂ« Ă«shtĂ« Alpine â njĂ« distribucion i plotĂ« i Linux me minimumin e pakove.
Fillimisht le të përpiqemi të shkruajmë një Dockerfile "në mënyrë të drejtpërdrejtë" (të them menjëherë, ky është një mënyrë e keqe, mos e bëni kështu):
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"]Këtu përdorim një imazh bazë të bazuar në Alpine me JDK të instaluar për ndërtimin e projektit tonë. Me komandën ADD shtojmë në imazh katalogun aktual src, e cila e shënojmë si të punës (WORKDIR) dhe fillojmë ndërtimin. Komanda EXPOSE 8080 e sinjalizon Docker-it se aplikacioni në konteiner do të përdorë portin 8080 (kjo nuk do ta bëjë aplikacionin të disponueshëm nga jashtë, por do të lejojë qasjen në aplikacionin, për shembull, nga një konteiner tjetër në të njëjtin rrjet Docker).
Për të paketuar shërbimet në imazhe, duhet të ekzekutoni komandat nga në rrënjën e çdo projekti:
docker image build . -t msvc-backend:1.0.0Si arrijmë një imazh me një madhësi prej 456 MB (ku prej të cilave, imazhi bazë JDK zuri 340 MB). Dhe të gjitha këto, ndërkohë që klasat në projektin tonë mund të numërohen me gishta. Për të reduktuar madhësinë e imazhit tonë:
- Përdorim ndërtimin me shumë hapa. Në hapin e parë do të mbledhim projektin, në të dytin do të instalojmë JRE, dhe hapi i tretë do të jetë për të kopjuar gjithçka në një imazh të ri të pastër Alpine. Si rezultat, në imazhin përfundimtar do të përfshihen vetëm komponentet e nevojshme.
- Do të shfrytëzojmë modularizimin e java. Duke filluar nga Java 9, me ndihmën e veglës jlink mund të krijojmë JRE-në vetëm nga modulet e nevojshme.
Për ata dëshiron të dinë më shumë, ja një artikull i mirë për qasjet e zvogëlimit të madhësive të imazhit. .
Dockerfile përfundimtar:
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"]Rikrijojmë imazhin dhe ai përfundimisht ka humbur 6 herë, duke arritur në 77 MB. Jo keq. Më pas, imazhet e gatshme mund të ngarkohen në regjistrin e imazheve, për t'u bërë të disponueshme për shkarkim nga interneti.
Ekzekutimi i përbashkët i shërbimeve në Docker
SĂ« pari, shĂ«rbimet tona duhet tĂ« jenĂ« nĂ« tĂ« njĂ«jtin rrjet. NĂ« Docker ekzistojnĂ« disa lloje rrjetesh, dhe ne po pĂ«rdorim atĂ« mĂ« primitiv â bridge, i cili lejon bashkimin e konteinerĂ«ve tĂ« ekzekutuar nĂ« njĂ« host. Do ta krijojmĂ« rrjetin me komandĂ«n e mĂ«poshtme:
docker network create msvc-networkMë pas, do të ekzekutojmë konteinerin e backend-it me emrin 'backend' me imazhin microservices-backend:1.0.0:
docker run -dit --name backend --network msvc-net microservices-backend:1.0.0Vlen të theksohet se rrjeti bridge ofron në mënyrë të natyrshme zbulimin e shërbimeve për konteinerët sipas emrave të tyre. Pra, shërbimi i backend-it do të jetë i disponueshëm brenda rrjetit të dockerit në adresën .
Nisim gateway-in:
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.0Në këtë komandë, ne tregojmë se po kalojmë portin 80 të hostit tonë në portin 8080 të kontejnerit. Opsionet env i përdorim për të vendosur variablat e mjedisit, të cilat do të lexohen automatikisht nga Spring dhe do të zëvendësojnë pronat nga application.properties.
Pas startimit, thërrasim dhe sigurohemi se gjithçka funksionon si në rastin e kaluar.
Përfundim
Si rezultat, krijuam dy mikrosherbime të thjeshta, i paketuam ato në kontejnerë Docker dhe i filluam së bashku në një makinë. Megjithatë, sistemi që fituam ka disa disavantazhe:
- QĂ«ndrueshmĂ«ri e dobĂ«t â gjithçka funksionon nĂ« njĂ« server tĂ« vetĂ«m
- MundĂ«si e dobĂ«t pĂ«r tĂ« shtrirĂ« â me rritjen e ngarkesĂ«s do tĂ« ishte mirĂ« tĂ« zhvilloheshin automatikisht instanca shtesĂ« tĂ« shĂ«rbimeve dhe tĂ« balancohej ngarkesa mes tyre
- VĂ«shtirĂ«si nĂ« startim â na nevojitet tĂ« japim tĂ« paktĂ«n 3 komanda, me disa parametra (kjo Ă«shtĂ« vetĂ«m pĂ«r 2 shĂ«rbime)
Për të zgjidhur problemet e mësipërme ka disa zgjidhje si Docker Swarm, Nomad, Kubernetes ose OpenShift. Nëse e gjithë sistemi është shkruar në Java, mund të shikojmë në drejtim të Spring Cloud ().
Në do të flas për mënyrën si unë setup Kubernetes dhe depoloj projektin në Google Kubernetes Engine.
Burimi: habr.com
