Mësojmë të zhvillojmë mikrosherbime. Pjesa 1. Spring Boot dhe Docker

Mësojmë të zhvillojmë mikrosherbime. Pjesa 1. Spring Boot dhe Docker

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:

  1. 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

  2. Zhvillimi i konfigurimeve Kubernetes dhe deployimi i sistemit në Google Kubernetes Engine

    Fjalët kyçe: Kubernetes, GKE, menaxhimi i burimeve, autoskalimi, sekretet

  3. 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

  4. 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: backend dhe gateway.

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.jar

Nisim gateway-in:

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

Po 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 (http://localhost:8081/requests). 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 Docker 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 (№ DockerHub) 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.0

Si 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 https://habr.com/ru/company/ruvds/blog/485650/.

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-network

MĂ« 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 http://backend:8080.

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.0

Në 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 http://localhost/ 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 (artikull i mirë).

Në pjesën e ardhshme do t'ju tregoj se si e kam konfiguruar Kubernetes dhe e kam deploy-uar projektin në Google Kubernetes Engine.

Burimi: habr.com

Bleni hostim tĂ« besueshĂ«m pĂ«r faqe me mbrojtje nga DDoS, serverĂ« VPS VDS đŸ”„ Bleni hostim tĂ« besueshĂ«m pĂ«r faqe me mbrojtje nga DDoS, serverĂ« VPS VDS | ProHoster