
Përshëndetje, Habr.
Në këtë artikull do të flas për përvojën time në krijimin e një mjedisi studimor për eksperimentimin me mikroshërbimet. Gjatë studimit të çdo mjeti të ri, gjithmonë kam dashur ta provoj jo vetëm në makinën lokale, por edhe në kushte më realiste. Prandaj, vendosa të krijoj një aplikacion të thjeshur mikroshërbimesh, i cili më pas mund të "përforcohet" me teknologji interesante. Kërkesa kryesore për projektin është që të jetë sa më funksionalisht i afërt me një sistem të vërtetë.
Fillimisht, e ndava krijimin e projektit në disa hapa:
Krijo dy shĂ«rbime â 'bekend' (backend) dhe 'shkallĂ«' (gateway), paketojini nĂ« imazhe docker dhe konfiguroni bashkĂ«punimin e tyre
Fjalët kyçe: Java 11, Spring Boot, Docker, optimizimi i imazheve
Fjalët kyçe: Kubernetes, GKE, menaxhimi i burimeve, autoskalimi, sekretet
Krijimi i një chart-i me Helm 3 për menaxhim më efikas të klasterit
Fjalët kyçe: Helm 3, deployimi i chart-it
Konfigurimi i Jenkins dhe pipeline për dorëzimin automatik të kodit në klaster
Fjalët kyçe: konfigurimi i Jenkins, plugins, repositori i konfigurationsve të ndara
Ădo hapi do ta planifikoj tĂ« dedikoj njĂ« artikull tĂ« veçantĂ«.
Qëllimi i këtij cikli artikujsh nuk është se si të shkruhen mikroservisat, por si t'i bëjmë ato të punojnë në një sistem të vetëm. Megjithëse këto gjëra zakonisht janë jashtë përgjegjësisë së zhvilluesit, mendoj se është e dobishme të jesh i njohur me to edhe për 20% (të cilat, siç dihet, japin 80% të rezultatit). Disa tema me rëndësi të madhe, si 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 mikroservisave
Shërbimet janë shkruar në Java 11 duke përdorur Spring Boot. Ndërveprimi midis shërbimeve është organizuar duke përdorur REST. Projekti do të përfshijë sasinë më minimale të testeve (në mënyrë që më pas të ketë diçka për të testuar në Jenkins). Kodi burimor i shërbimeve është i disponueshëm në GitHub: dhe .
Për të qenë në gjendje të kontrolloni statusin e çdo shërbimi, Spring Actuator është shtuar në varësitë e tyre. Ai do të krijojë një pikë lidhjeje /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ë kontroll mjaft i thjeshtë, pasi shërbimet janë shumë të thjeshta, dhe në rast një force majeure, ato që tërësisht do të bëhen të paqëndrueshme sesa të ruajnë pjesërisht funksionalitetin. Por në sisteme reale, Actuator mund të ndihmojë në diagnostikimin e problemeve përpara se përdoruesit të nisin të shqetësohen për to. Për shembull, në rast të problemeve me aksesin në DB, do të kemi mundësinë të reagojmë automatikisht duke ndaluar përpunimin e kërkesave nga një instancë të thyer të shërbimit.
Shërbimi Backend
Shërbimi i backend do të numërojë thjesht dhe do të kthejë numrin e kërkesave të pranuara.
Kodi i kontrolluesit:
@RestController
public class RequestsCounterController {
private final AtomicLong counter = new AtomicLong();
@GetMapping("/requests")
public Long getRequestsCount() {
return counter.incrementAndGet();
}
}Testi në kontrollues:
@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ë përcjellë kërkesën te shërbimi i backend-it, duke e pasuruar atë me informacionin e mëposhtëm:
- id e gateway-it. Kjo është e nevojshme që të mund të dallojmë një instancë gateway-i nga tjetra sipas përgjigjes së serverit
- Një "sekret", i cili do të luajë rolin e një gjejverysh shumë të rëndësishëm (numri i çelësit të enkriptimit për një cookie të rëndësishme)
Konfigurimi në application.properties:
backend.url=http://localhost:8081
instance.id=${random.int}
secret="default-secret"Adapteri 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();
}
}Kontrolleri:
@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);
}
}Nisja:
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, sekret "default-secret")Gjithçka funksionon. Lexuesit e kujdesshëm do të vërejnë se nuk ka asgjë që na pengon të qasemi drejtpërdrejt në backend duke anashkaluar gateway-in (). Për ta korrigjuar këtë, shërbimet duhet të jenë të lidhura në një rrjet, dhe jashtë "të dalin" vetëm gateway-i.
Gjithashtu, të dy shërbimet ndajnë një sistem filesh, që krijojnë procese dhe njëherësh mund të ndërhyjnë njëri te tjetri. Do të ishte mirë të izolojmë mikroshërbimet tona. Kjo mund të arrihet duke shpërndarë aplikacionet në makina të ndryshme (shumë shpenzime, e vështirë), duke përdorur makina virtuale (burim i kërkuar, start i gjatë) ose përmes kontejnerizimit. Siç pritej, zgjedhim opsionin e tretë dhe si një mjet për kontejnerizimin.
Docker
NĂ«se flasim shkurt, Docker krijon kontejnerĂ« tĂ« izoluar, njĂ« pĂ«r secilĂ«n aplikacion. PĂ«r ta pĂ«rdorur Docker, nevojitet tĂ« shkruhet njĂ« Dockerfile â njĂ« udhĂ«zim pĂ«r ndĂ«rtimin dhe ekzekutimin e aplikacionit. MĂ« pas, mund tĂ« ndĂ«rtohet njĂ« imazh, tĂ« ngarkohet nĂ« regjistrin e imazheve (â ) dhe nĂ« njĂ« komandĂ« tĂ« shpĂ«rndahen mikroshĂ«rbimi juaj nĂ« çdo mjedis tĂ« dockerizuar.
Dockerfile
NjĂ« nga karakteristikat mĂ« tĂ« rĂ«ndĂ«sishme tĂ« imazhit Ă«shtĂ« madhĂ«sia 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Ă«rtohet mbi njĂ« imazh bazĂ«, dhe rekomandohet tĂ« zgjidhet opsioni mĂ« minimal. NjĂ« mundĂ«si e mirĂ« Ă«shtĂ« Alpine â njĂ« distribucion i plotĂ« i Linux me minimumin e paketave.
Për të filluar, le të provoni të shkruajmë një Dockerfile "drejt për drejt" ( duhet ta them se kjo është një mënyrë e keqe, mos e bëni kë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"]Këtu përdorim një bazë imazhi të bazuar në Alpine me JDK të instaluar për ndërtimin e projektit tonë. Me komandën ADD, shtojmë në imazh direktoriumin aktual src, e shënojmë atë si vendpunuese (WORKDIR) dhe fillojmë ndërtimin. Komanda EXPOSE 8080 sinjalizon Docker-it se aplikacioni në kontejner do të përdorë portin e tij 8080 (kjo nuk do ta bëjë aplikacionin të disponueshëm jashtë, por do të lejojë qasjen në aplikacion, për shembull, nga një kontejner tjetër në të njëjtën rrjetë Docker).
Për të paketuar shërbimet në imazhe, duhet të ekzekutoni komandat nga rrënjës së çdo projekti:
docker image build . -t msvc-backend:1.0.0Si rezultat, marrim një imazh me madhësi 456 MB (nga të cilat baza imazhi JDK zuri 340 MB). Dhe gjithçka në atë kohë, klasat në projektin tonë mund të numërohen me gishta. Për të zvogëluar madhësinë e imazhit tonë:
- PĂ«rdorim ndĂ«rtimin me shumĂ« hapa. NĂ« hapin e parĂ« do tĂ« ndĂ«rtosh projektin, nĂ« tĂ« dytin do tĂ« instalojmĂ« JRE, dhe nĂ« hapin e tretĂ« do tâi kopjojmĂ« tĂ« gjitha kĂ«to nĂ« njĂ« imazh tĂ« ri tĂ« pastĂ«r Alpine. PĂ«rfundimisht, nĂ« imazhin pĂ«rfundimtar do tĂ« gjenden vetĂ«m komponentĂ«t e nevojshĂ«m.
- Do të shfrytëzojmë modularizimin e Java. Duke filluar nga Java 9, mund të krijoni JRE vetëm nga modulet e nevojshme përmes instrumentit jlink.
Për të kureshtarët, 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 u zvogëlua 6 herë, duke arritur 77 MB. Jo keq. Më pas, imazhet e gatshme mund të ngarkohen në regjistrin e imazheve, në mënyrë që imazhet tuaja të jenë 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Ă« njĂ« rrjet tĂ« vetĂ«m. NĂ« Docker ekzistojnĂ« disa lloje rrjetesh, dhe ne pĂ«rdorim mĂ« primitivin prej tyre â bridge, qĂ« lejon tĂ« bashkohen nĂ« njĂ« rrjet kontejnerĂ« tĂ« nisur nĂ« tĂ« njĂ«jtin host. Do tĂ« krijojmĂ« rrjetin me komandĂ«n e mĂ«poshtme:
docker network create msvc-networkMĂ« pas do tĂ« nisim kontejnerin e backend-it me emrin âbackendâ duke pĂ«rdorur imazhin microservices-backend:1.0.0:
docker run -dit --name backend --network msvc-net microservices-backend:1.0.0ĂshtĂ« e rĂ«ndĂ«sishme tĂ« theksohet se rrjeti bridge ofron nga kutia zbulim shĂ«rbimi pĂ«r kontejnerĂ«t sipas emrave tĂ« tyre. Pra, shĂ«rbimi i backend-it do tĂ« jetĂ« i accesueshĂ«m brenda rrjetit tĂ« docker-it 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 ne e kalojmë portin 80 të host-it 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ë overrride-nin pronat nga application.properties.
Pas nisjes, thërrasim dhe sigurohemi që gjithçka funksionon, ashtu siç ka ndodhur në rastin e kaluar.
Përfundimi
Si përfundim, ne kemi krijuar dy mikrosistema të thjeshta, i kemi paketuar ato në kontejnerë docker dhe i kemi nisur së bashku në një makinë. Megjithatë, sistemi i marrë ka disa disavantazhe:
- Qëndrueshmëri e dobët - gjithçka funksionon në një server
- Shkallëzim i dobët - me rritjen e ngarkesës do të ishte mirë të vendosnim automatikisht ekzemplarë të shtesë të shërbimeve dhe të balancojmë ngarkesën ndërmjet tyre.
- VĂ«shtirĂ«sia e nisjes â na nevojiteshin tĂ« paktĂ«n 3 komanda, dhe ato me disa parametra tĂ« caktuar (kjo Ă«shtĂ« vetĂ«m pĂ«r 2 shĂ«rbime)
Për të zgjidhur problemet e lartpërmendura, ekzistojnë disa zgjidhje si Docker Swarm, Nomad, Kubernetes ose OpenShift. Nëse e gjithë sistemi do të shkruhet në Java, mund të shqyrtohet drejtimi i Spring Cloud ().
Në do t'ju tregoj se si e kam konfiguruar Kubernetes dhe e kam deploy-uar projektin në Google Kubernetes Engine.
Burimi: habr.com
