Učíme sa nasadzovať mikroslužby. Časť 1. Spring Boot a Docker

Učíme sa nasadzovať mikroslužby. Časť 1. Spring Boot a Docker

Ahoj, Habr.

V tomto článku chcem zdieľať svoju skúsenosť so vytváraním vzdelávacieho prostredia na experimentovanie s mikroslužbami. Pri štúdiu každého nového nástroja som vždy chcel vyskúšať ho nielen na lokálnom počítači, ale aj v realistickejších podmienkach. Preto som sa rozhodol vytvoriť zjednodušenú mikroslužbovú aplikáciu, ktorú neskôr môžem "obohatiť" rôznymi zaujímavými technológiami. Hlavným požiadavkom na projekt je maximálna funkčná podobnosť s reálnym systémom.

Na začiatku som rozdelil vytváranie projektu na niekoľko krokov:

  1. Vytvoriť dva služby — 'backend' a 'gateway', zabaliť ich do docker obrazov a nastaviť ich spoločnú prácu

    Kľúčové slová: Java 11, Spring Boot, Docker, optimalizácia obrázkov

  2. Vývoj Kubernetes konfigurácie a nasadenie systému do Google Kubernetes Engine

    Kľúčové slová: Kubernetes, GKE, správa zdrojov, automatické škálovanie, tajomstvá

  3. Vytvorenie chartu pomocou Helm 3 pre efektívnejšiu správu klastru

    Kľúčové slová: Helm 3, nasadenie chartu

  4. Nastavenie Jenkins a pipeline pre automatické dodávanie kódu do klastru

    Kľúčové slová: konfigurácia Jenkins, pluginy, samostatné repozitáre konfigurácií

Každej fáze plánujem venovať samostatný článok.

Zameranie tohto cyklu článkov nie je v tom, ako napísať mikroslužby, ale ako ich nechať pracovať v jednotnom systéme. Hoci tieto veci zvyčajne ležia mimo zodpovednosti vývojára, myslím, že je stále užitočné byť s nimi oboznámený aspoň na 20% (čo, ako je známe, prinesie 80% výsledku). Niektoré rozhodujúce témy, ako je zabezpečenie, budú mimo rámca tohto projektu, pretože autor v tom má málo znalostí; systém je vytvorený výhradne na osobné použitie. Budem rád za akékoľvek názory a konštruktívnu kritiku.

Tvorba mikroslužieb

Služby boli napísané v Jave 11 s použitím Spring Boot. Medzi službami je komunikácia organizovaná pomocou REST. Projekt bude obsahovať minimálne množstvo testov (aby bolo čo testovať v Jenkins). Zdrojový kód služieb je dostupný na GitHub: backend a gateway.

Aby sme mohli skontrolovať stav každého z mikroservisov, pridali sme do ich závislosti Spring Actuator. Vytvorí endpoint /actuator/health a vráti stav 200, ak je mikroservis pripravený prijímať prevádzku, alebo 504 v prípade problémov. V tomto prípade ide o dosť fiktívnu kontrolu, pretože mikroservisy sú veľmi jednoduché a pri nejakej mimoriadnej situácii sa pravdepodobne stanú úplne nedostupnými, než aby si zachovali čiastočnú funkčnosť. Avšak v reálnych systémoch môže Actuator pomôcť diagnostikovať problém skôr, než sa mu začnú venovať používatelia. Napríklad, ak dôjde k problémom s prístupom k databáze, môžeme na to automaticky reagovať prerušením spracovania požiadaviek poškodeným exemplárom mikroservisu.

Backendový servis

Backendový servis bude jednoducho počítať a vracať počet prijatých požiadaviek.

Kód kontroléra:

@RestController
public class RequestsCounterController {

    private final AtomicLong counter = new AtomicLong();

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

Test kontroléra:

@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 servis

Gateway bude presmerovávať požiadavky na backendový servis a doplní ich nasledujúcimi informáciami:

  • id gateway. Je potrebné, aby sme mohli z odpovede servera rozlíšiť jeden prípad gateway od druhého.
  • Akýsi "tajomstvo", ktoré bude hrať úlohu veľmi dôležitého hesla (číslo šifrovacieho kľúča dôležitej cookie).

Konfigurácia v application.properties:

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

Adaptér pre komunikáciu s backendom:

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

Kontrolér:

@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("Počet požiadaviek %s (gateway %d, tajomstvo %s)", backendAdapter.getRequests(), instanceId, secret);
    }
}

Spustenie:

Spúšťame backend:

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

Spúšťame gateway:

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

Kontrolujeme:

$ curl http://localhost:8080/
Počet požiadaviek 1 (brána 38560358, tajný "default-secret")

Všetko funguje. Pozorný čitateľ si všimne, že nás nič nebráni priamo sa obrátiť na backend obchádzajúc bránu (http://localhost:8081/requests). Aby sme to napravili, služby musia byť zjednotené do jednej siete a vonku by mala "vystupovať" len brána.
Obe služby zdieľajú aj jedným súborovým systémom, vytvárajú vlákna a v jeden moment si môžu navzájom prekážať. Nejaká izolácia našich mikroslužieb by nebola na škodu. Toho môžeme dosiahnuť rozdelením aplikácií na rôzne stroje (veľa peňazí, zložité), použitím virtuálnych strojov (náročne na zdroje, dlhé spúšťanie) alebo prostredníctvom kontajnerizácie. Očakávane vyberáme tretiu možnosť a Docker ako nástroj na kontajnerizáciu.

Docker

Krátko povedané, Docker vytvára izolované kontajnery, po jednom na aplikáciu. Na použitie Dockera je potrebné napísať Dockerfile – inštrukciu na zostavenie a spustenie aplikácie. Následne bude možné zostaviť obraz, nahrať ho do registru obrazov (č. DockerHub) a jedným príkazom rozbehnúť svoju mikroslužbu v akejkoľvek dockerizovanej prostredí.

Dockerfile

Jednou z najdôležitejších charakteristík obrazu je jeho veľkosť. Kompaktný obraz sa rýchlejšie stiahne z vzdialeného repozitára, zaberá menej miesta a vaša služba sa rýchlejšie spustí. Akýkoľvek obraz sa vytvára na základe základného obrazu a odporúča sa vybrať najminimalistickejšiu variantu. Dobrým variantom je Alpine – plnohodnotná distribúcia Linuxu s minimom balíkov.

Na začiatok sa pokúsime napísať Dockerfile "na rovinu" (hned poviem, že je to zlý prístup, nerobte to):

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

Tu používame základný obraz na báze Alpine s už nainštalovaným JDK pre zostavenie nášho projektu. Príkazom ADD pridávame do obrazu aktuálnu adresáru src, označujeme ju ako pracovnú (WORKDIR) a spúšťame zostavenie. Príkaz EXPOSE 8080 informuje Dockera, že aplikácia v kontejnery bude používať jeho port 8080 (to neurobí aplikáciu dostupnou zvonku, ale umožní prístup k aplikácii, napríklad z iného kontajnera v tej istej sieti Dockera).

Aby sme zabalili služby do obrazov, musíme vykonať príkazy z koreňa každého projektu:

docker image build . -t msvc-backend:1.0.0

Výsledkom je obraz s veľkosťou 456 MB (z toho základný obraz JDK 340 zaberá MB). A to všetko, pritom tried v našom projekte je možné spočítať na prstoch. Aby sme zmenšili veľkosť nášho obrazu:

  • Použijeme viackrokovú zostavu. V prvom kroku zostavíme projekt, v druhom nainštalujeme JRE a v treťom kroku všetko skopírujeme do nového čistého Alpine obrazu. Nakoniec v konečnom obraze ostanú len potrebné komponenty.
  • Využijeme modularizáciu javy. Od verzie Java 9 je možné pomocou nástroja jlink vytvoriť JRE len z potrebných modulov.

Pre zvedavých, tu je dobrý článok o prístupoch na zmenšovanie veľkostí obrazov. https://habr.com/ru/company/ruvds/blog/485650/.

Konečný 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"]

Znovu vytvoríme obraz a ten nakoniec schudol o 6-násobok na 77 MB. Nie zlé. Následne môžeme pripravené obrazy nahrať do registra obrazov, aby boli vaše obrazy dostupné na stiahnutie z internetu.

Spoločný beh služieb v Dockeri.

Na začiatok musia byť naše služby v jednej sieti. V Dockeri existuje niekoľko typov sietí a my používame ten najzákladnejší - bridge, ktorý umožňuje spájať kontajnery bežiace na jednom hostiteľovi do siete. Vytvoríme sieť nasledujúcim príkazom:

docker network create msvc-network

Ďalej spustíme kontajner backendu s názvom 'backend' s obrazom microservices-backend:1.0.0:

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

Je potrebné poznamenať, že bridge-sieť poskytuje automaticky objavovanie služieb pre kontajnery podľa ich mien. To znamená, že backendová služba bude dostupná v rámci siete Dockera na adrese http://backend:8080.

Spúšťame 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

V tomto príkaze uvádzame, že prenášame port 80 nášho hostiteľa na port 8080 kontajnera. Možnosti env používame na nastavenie premenných prostredia, ktoré budú automaticky načítané Springom a prekonajú nastavenia z application.properties.

Po spustení zavoláme http://localhost/ a uistíme sa, že všetko funguje rovnako ako v predchádzajúcom prípade.

Záver

Nakoniec sme vytvorili dve jednoduché mikroservisy, zabalili ich do Docker kontajnerov a spustili ich spoločne na jednom stroji. Systém, ktorý sme dostali, má však niekoľko nedostatkov:

  • Slabá odolnosť voči poruchám — všetko funguje na jednom serveri.
  • Slabá škálovateľnosť — pri zvýšení zaťaženia by bolo dobré automaticky nasadiť ďalšie instance služieb a vyvážiť medzi nimi zaťaženie.
  • Zložitosti spustenia — potrebovali sme zadať aspoň 3 príkazy, pričom s určitými parametrami (to je len pre 2 služby).

Na riešenie vyššie uvedených problémov existuje niekoľko riešení, ako napríklad Docker Swarm, Nomad, Kubernetes alebo OpenShift. Ak bude celý systém napísaný v Jave, môžete sa pozrieť na Spring Cloud (dobrý článok).

V následujúcej časti poviem vám, ako som nakonfiguroval Kubernetes a nasadil projekt na Google Kubernetes Engine.

Zdroj: habr.com

Kúpte si spoľahlivý hosting pre webové stránky s ochranou proti DDoS, VPS VDS servery 🔥 Kúpte si spoľahlivý hosting pre webové stránky s ochranou proti DDoS, VPS VDS servery | ProHoster