Pag-aaral na mag-deploy ng mga microservice. Bahagi 1. Spring Boot at Docker

Pag-aaral na mag-deploy ng mga microservice. Bahagi 1. Spring Boot at Docker

Hoy Habr.

Sa artikulong ito, gusto kong pag-usapan ang aking karanasan sa paglikha ng learning environment para sa pag-eksperimento sa mga microservice. Kapag nag-aaral ng bawat bagong tool, gusto kong subukan ito hindi lamang sa aking lokal na makina, kundi pati na rin sa mas makatotohanang mga kondisyon. Samakatuwid, nagpasya akong lumikha ng isang pinasimple na microservice application, na maaaring "mabitin" sa ibang pagkakataon sa lahat ng uri ng mga kagiliw-giliw na teknolohiya. Ang pangunahing kinakailangan para sa proyekto ay ang pinakamataas na functional proximity nito sa tunay na sistema.

Sa una, hinati ko ang paglikha ng proyekto sa ilang hakbang:

  1. Lumikha ng dalawang serbisyo - 'backend' at 'gateway', i-pack ang mga ito sa mga imahe ng docker at i-configure ang mga ito upang gumana nang magkasama

    Mga Keyword: Java 11, Spring Boot, Docker, pag-optimize ng imahe

  2. Pagbuo ng Kubernetes configuration at deployment system sa Google Kubernetes Engine

    Mga Keyword: Kubernetes, GKE, pamamahala ng mapagkukunan, autoscaling, mga lihim

  3. Gumawa ng chart gamit ang Helm 3 para sa mas mahusay na pamamahala ng cluster

    Mga Keyword: Helm 3, deployment ng chart

  4. Pagse-set up ng Jenkins at pipeline para awtomatikong maghatid ng code sa cluster

    Mga Keyword: Jenkins configuration, plugins, hiwalay na configs repository

Plano kong maglaan ng isang hiwalay na artikulo sa bawat hakbang.

Ang pokus ng seryeng ito ng mga artikulo ay hindi kung paano magsulat ng mga microservice, ngunit kung paano gawin ang mga ito na gumana sa iisang sistema. Bagama't ang lahat ng mga bagay na ito ay karaniwang nasa labas ng responsibilidad ng developer, sa tingin ko kapaki-pakinabang pa rin na maging kahit 20% pamilyar sa kanila (na kung saan ay kilala sa account para sa 80% ng resulta). Ang ilang mga ganap na mahalagang paksa, tulad ng seguridad, ay maiiwan sa proyektong ito, dahil ang may-akda ay kakaunti ang naiintindihan tungkol dito; ang sistema ay nilikha ng eksklusibo para sa personal na paggamit. Tinatanggap ko ang anumang mga opinyon at nakabubuo na pagpuna.

Paglikha ng mga microservice

Ang mga serbisyo ay isinulat sa Java 11 gamit ang Spring Boot. Ang komunikasyon sa pagitan ng serbisyo ay isinaayos gamit ang REST. Ang proyekto ay magsasama ng isang minimum na bilang ng mga pagsubok (upang may isang bagay na susubok sa Jenkins mamaya). Ang source code para sa mga serbisyo ay available sa GitHub: backend ΠΈ Gateway.

Upang masuri ang estado ng bawat isa sa mga serbisyo, isang Spring Actuator ang idinagdag sa kanilang dependency. Ito ay lilikha ng endpoint /actuator/health at magbabalik ng status na 200 kung ang serbisyo ay handa nang tumanggap ng trapiko, o 504 kung sakaling magkaroon ng mga problema. Sa kasong ito, ito ay isang medyo kathang-isip na pagsusuri, dahil ang mga serbisyo ay napaka-simple, at sa ilalim ng ilang uri ng force majeure ay mas malamang na maging ganap silang hindi magagamit kaysa manatiling bahagyang gumagana. Ngunit sa mga totoong system, makakatulong ang Actuator sa pag-diagnose ng isang problema bago simulan ng mga user ang pagpindot dito. Halimbawa, kung may mga problema sa pag-access sa database, awtomatiko kaming makakatugon dito sa pamamagitan ng paghinto sa pagpoproseso ng mga kahilingan na may sirang instance ng serbisyo.

Serbisyo sa backend

Ang serbisyo ng backend ay bibilangin at ibabalik ang bilang ng mga tinatanggap na kahilingan.

Code ng controller:

@RestController
public class RequestsCounterController {

    private final AtomicLong counter = new AtomicLong();

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

Pagsubok ng controller:

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

Serbisyo ng gateway

Ipapasa ng gateway ang kahilingan sa serbisyo ng backend, na dagdagan ito ng sumusunod na impormasyon:

  • gateway id. Ito ay kinakailangan upang ang isang halimbawa ng gateway ay maaaring makilala mula sa isa pa sa pamamagitan ng tugon ng server
  • Isang tiyak na "lihim" na gaganap sa papel ng isang napakahalagang password (pangunahing numero para sa pag-encrypt ng isang mahalagang cookie)

Configuration sa application.properties:

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

Adapter para sa komunikasyon sa 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<String> response = restTemplate.getForEntity(
backendUrl + REQUESTS_ENDPOINT, String.class);
        return response.getBody();
    }
}

Controller:

@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("Number of requests %s (gateway %d, secret %s)", backendAdapter.getRequests(), instanceId, secret);
    }
}

Ilunsad:

Ilunsad natin ang backend:

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

Simulan natin ang gateway:

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

Namin suriin:

$ curl http://localhost:8080/
Number of requests 1 (gateway 38560358, secret "default-secret")

Lahat ay gumagana. Mapapansin ng matulungin na mambabasa na walang pumipigil sa amin na direktang ma-access ang backend, na lampasan ang gateway (http://localhost:8081/requests). Upang ayusin ito, ang mga serbisyo ay dapat pagsamahin sa isang network, at ang gateway lamang ang dapat na "lumabas" sa labas.
Gayundin, ang parehong mga serbisyo ay nagbabahagi ng parehong sistema ng file, bumubuo ng mga thread, at sa isang punto ay maaaring magsimulang makagambala sa isa't isa. Mainam na ihiwalay ang aming mga microservice. Magagawa ito sa pamamagitan ng pamamahagi ng mga application sa iba't ibang machine (maraming pera, mahirap), paggamit ng mga virtual machine (resource-intensive, long startup) o paggamit ng containerization. Gaya ng inaasahan, pipiliin namin ang pangatlong opsyon at Manggagawa sa pantalan bilang isang tool para sa containerization.

Manggagawa sa pantalan

Sa madaling salita, ang Docker ay gumagawa ng mga nakahiwalay na lalagyan, isa bawat application. Upang magamit ang Docker, kailangan mong magsulat ng Dockerfile - mga tagubilin para sa pagbuo at pagpapatakbo ng application. Susunod, maaari kang bumuo ng imahe, i-upload ito sa pagpapatala ng imahe (No. Dockerhub) at i-deploy ang iyong microservice sa anumang dockerized na kapaligiran sa isang utos.

dockerfile

Ang isa sa pinakamahalagang katangian ng isang imahe ay ang laki nito. Ang isang compact na imahe ay magda-download nang mas mabilis mula sa isang remote na imbakan, kukuha ng mas kaunting espasyo, at ang iyong serbisyo ay magsisimula nang mas mabilis. Ang anumang imahe ay binuo batay sa isang pangunahing imahe, at inirerekumenda na piliin ang pinaka-minimalistiko na opsyon. Ang isang mahusay na pagpipilian ay ang Alpine, isang ganap na pamamahagi ng Linux na may pinakamababang mga pakete.

Una, subukan nating magsulat ng Dockerfile na "head-on" (Sasabihin ko kaagad na ito ay isang masamang paraan, huwag gawin ito):

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

Dito kami ay gumagamit ng Alpine based na base na imahe na may naka-install na JDK para buuin ang aming proyekto. Gamit ang ADD command, idinaragdag namin ang kasalukuyang src directory sa imahe, markahan ito bilang gumagana (WORKDIR) at simulan ang build. Ang EXPOSE 8080 command ay nagpapahiwatig sa docker na gagamitin ng application sa container ang port 8080 nito (hindi nito gagawing accessible ang application mula sa labas, ngunit papayagan ang application na ma-access, halimbawa, mula sa isa pang container sa parehong docker network. ).

Upang mag-package ng mga serbisyo sa mga imahe, kailangan mong patakbuhin ang mga utos mula sa ugat ng bawat proyekto:

docker image build . -t msvc-backend:1.0.0

Bilang resulta, nakakakuha kami ng imahe na 456 MB ang laki (kung saan kinuha ng batayang JDK 340 na imahe ang MB). At lahat sa kabila ng katotohanan na ang mga klase sa aming proyekto ay mabibilang sa isang daliri. Upang bawasan ang laki ng aming larawan:

  • Gumagamit kami ng multi-step na pagpupulong. Sa unang hakbang ay tipunin namin ang proyekto, sa pangalawa ay i-install namin ang JRE, at sa ikatlong hakbang ay kokopyahin namin ang lahat ng ito sa isang bagong malinis na imahe ng Alpine. Sa kabuuan, ang huling larawan ay maglalaman lamang ng mga kinakailangang bahagi.
  • Gamitin natin ang java modularization. Simula sa Java 9, maaari mong gamitin ang jlink tool upang lumikha ng JRE mula lamang sa mga module na kailangan mo

Para sa mga mausisa, narito ang isang magandang artikulo tungkol sa mga diskarte sa pagbabawas ng mga laki ng larawan https://habr.com/ru/company/ruvds/blog/485650/.

Panghuling 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 [email protected]"
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"]

Nilikha namin muli ang imahe, at sa kalaunan ay naging 6 na beses na mas payat, na umaabot sa 77 MB. Hindi masama. Pagkatapos, ang mga natapos na larawan ay maaaring i-upload sa pagpapatala ng imahe upang ang iyong mga larawan ay magagamit para sa pag-download mula sa Internet.

Pagpapatakbo ng mga serbisyo nang magkasama sa Docker

Upang magsimula, ang aming mga serbisyo ay dapat nasa parehong network. Mayroong ilang mga uri ng mga network sa Docker, at ginagamit namin ang pinaka-primitive sa mga ito - tulay, na nagbibigay-daan sa iyo upang mag-network ng mga lalagyan na tumatakbo sa parehong host. Gumawa tayo ng network gamit ang sumusunod na command:

docker network create msvc-network

Susunod, maglunsad tayo ng backend container na pinangalanang 'backend' na may larawang microservices-backend:1.0.0:

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

Kapansin-pansin na ang network ng tulay ay nagbibigay ng pagtuklas ng serbisyo sa labas ng kahon para sa mga lalagyan sa pamamagitan ng kanilang mga pangalan. Ibig sabihin, ang serbisyo ng backend ay magiging available sa loob ng Docker network sa http://backend:8080.

Simulan natin ang 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

Sa command na ito, ipinapahiwatig namin na ipinapasa namin ang port 80 ng aming host sa port 8080 ng container. Gumagamit kami ng mga opsyon sa env upang magtakda ng mga variable ng kapaligiran na awtomatikong babasahin sa tagsibol at i-override ang mga katangian mula sa application.properties.

Pagkatapos ilunsad, tumawag http://localhost/ at tiyaking gumagana ang lahat, tulad ng sa nakaraang kaso.

Konklusyon

Bilang resulta, gumawa kami ng dalawang simpleng microservice, ibinalot ang mga ito sa mga docker container at inilunsad ang mga ito nang magkasama sa iisang makina. Ang resultang sistema, gayunpaman, ay may ilang mga disadvantages:

  • Hindi magandang pagpapahintulot sa kasalanan - gumagana ang lahat sa isang server para sa amin
  • Hindi magandang scalability - habang tumataas ang load, mainam na awtomatikong mag-deploy ng mga karagdagang instance ng serbisyo at balansehin ang load sa pagitan ng mga ito
  • Ang pagiging kumplikado ng paglunsad - kailangan naming magpasok ng hindi bababa sa 3 mga utos, na may ilang mga parameter (ito ay para lamang sa 2 mga serbisyo)

Upang malutas ang mga problema sa itaas, mayroong isang bilang ng mga solusyon tulad ng Docker Swarm, Nomad, Kubernetes o OpenShift. Kung ang buong sistema ay nakasulat sa Java, maaari kang tumingin patungo sa Spring Cloud (magandang artikulo).

Π’ susunod na bahagi Sasabihin ko sa iyo kung paano ko na-set up ang Kubernetes at na-deploy ang proyekto sa Google Kubernetes Engine.

Pinagmulan: www.habr.com

Magdagdag ng komento