Naučite se uvajati mikrostoritve. 1. del. Pomladni zagon in Docker

Naučite se uvajati mikrostoritve. 1. del. Pomladni zagon in Docker

Hej Habr.

V tem članku želim govoriti o svojih izkušnjah pri ustvarjanju učnega okolja za eksperimentiranje z mikrostoritvami. Ko sem se naučil vsakega novega orodja, sem ga vedno želel preizkusiti ne samo na lokalnem stroju, ampak tudi v bolj realističnih pogojih. Zato sem se odločil izdelati poenostavljeno mikrostoritveno aplikacijo, ki jo lahko kasneje »pokrijemo« z najrazličnejšimi zanimivimi tehnologijami. Glavna zahteva za projekt je njegova maksimalna funkcionalna bližina realnemu sistemu.

Na začetku sem ustvarjanje projekta razdelil na več korakov:

  1. Ustvarite dve storitvi - 'backend' (backend) in 'gateway' (gateway), ju zapakirajte v slike dockerja in ju nastavite za skupno delovanje

    Ključne besede: Java 11, Spring Boot, Docker, optimizacija slike

  2. Razvoj konfiguracije Kubernetes in uvedba sistema v Google Kubernetes Engine

    Ključne besede: Kubernetes, GKE, upravljanje virov, samodejno skaliranje, skrivnosti

  3. Ustvarjanje grafikona s Helm 3 za boljše upravljanje gruče

    Oznake: Helm 3, uvedba grafikona

  4. Nastavitev Jenkinsa in cevovoda za samodejno dostavo kode v gručo

    Ključne besede: konfiguracija Jenkins, vtičniki, ločeno skladišče konfiguracij

Vsakemu koraku nameravam posvetiti ločen članek.

Težišče te serije člankov ni, kako napisati mikrostoritve, ampak kako narediti, da delujejo v enem samem sistemu. Čeprav vse te stvari običajno niso v pristojnosti razvijalca, menim, da je še vedno koristno, če jih poznate vsaj 20% (kar, kot veste, daje 80% rezultata). Nekatere brezpogojno pomembne teme, kot je varnost, bodo izpuščene iz tega projekta, saj avtor malo razume, da je ta sistem ustvarjen izključno za osebno uporabo. Vesela sem vsakega mnenja in konstruktivne kritike.

Ustvarjanje mikrostoritev

Storitve so bile napisane v Javi 11 z uporabo Spring Boot. Interservisna interakcija je organizirana z uporabo REST. Projekt bo vključeval minimalno število testov (tako da bo kasneje v Jenkinsu kaj testirati). Izvorna koda za storitve je na voljo na GitHubu: backend и Prehod.

Da bi lahko preverili status vsake od storitev, je bil njihovim odvisnostim dodan vzmetni aktuator. Ustvaril bo končno točko /actuator/health in vrnil status 200, če je storitev pripravljena sprejeti promet, ali 504, če pride do težave. V tem primeru gre za precej fiktivno preverjanje, saj so storitve zelo enostavne in v primeru višje sile obstaja večja verjetnost, da postanejo popolnoma nedosegljive kot da ostanejo delno delujoče. Toda v resničnih sistemih lahko Actuator pomaga diagnosticirati težavo, preden se uporabniki o njej začnejo prepirati. Na primer, če pride do težav pri dostopu do baze podatkov, se lahko na to samodejno odzovemo tako, da prekinemo obdelavo zahtev z okvarjenim primerkom storitve.

Zadnja storitev

Zaledna storitev bo preprosto preštela in vrnila število sprejetih zahtev.

Koda krmilnika:

@RestController
public class RequestsCounterController {

    private final AtomicLong counter = new AtomicLong();

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

Test krmilnika:

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

Storitveni prehod

Prehod bo zahtevo posredoval zaledni storitvi in ​​jo dopolnil z naslednjimi informacijami:

  • id prehoda. Potreben je, da je mogoče razlikovati en primerek prehoda od drugega z odzivom strežnika
  • Neka "skrivnost", ki bo igrala vlogo zelo pomembnega gesla (številka šifrirnega ključa pomembnega piškotka)

Konfiguracija v aplikaciji.properties:

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

Zaledni adapter:

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

krmilnik:

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

Kosilo:

Začnemo backend:

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

Zagon prehoda:

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

Preverjamo:

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

Vse dela. Pozoren bralec bo opazil, da nam nič ne preprečuje neposrednega dostopa do ozadja, mimo prehoda (http://localhost:8081/requests). Če želite to popraviti, je treba storitve združiti v eno omrežje, zunaj pa naj "štrli" le prehod.
Prav tako si obe storitvi delita en datotečni sistem, proizvajata tokove in v enem trenutku lahko začneta druga drugo motiti. Lepo bi bilo izolirati naše mikrostoritve. To je mogoče doseči z distribucijo aplikacij na različnih strojih (veliko denarja, težko), z uporabo virtualnih strojev (zahtevajo vire, dolg zagon) ali z uporabo kontejnerjev. Po pričakovanju izberemo tretjo možnost in Lučki delavec kot orodje za kontejnerizacijo.

Lučki delavec

Skratka, docker ustvari izolirane vsebnike, enega na aplikacijo. Če želite uporabljati docker, morate napisati datoteko Dockerfile - navodila za izdelavo in zagon aplikacije. Nato lahko sestavite sliko, jo naložite v register slik (št. Dockerhub) in z enim ukazom razmestite svojo mikrostoritev v katerem koli dockeriziranem okolju.

Dockerfile

Ena najpomembnejših lastnosti slike je njena velikost. Kompaktna slika se bo hitreje prenesla iz oddaljenega repozitorija, zavzela manj prostora in vaša storitev se bo začela hitreje. Vsaka slika je zgrajena na podlagi osnovne slike, zato je priporočljivo izbrati najbolj minimalistično možnost. Dobra možnost je Alpine, popolna distribucija Linuxa z minimalnimi paketi.

Za začetek poskusimo napisati Dockerfile "na čelo" (takoj bom rekel, da je to slab način, ne delajte tega):

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

Tukaj uporabljamo osnovno sliko Alpine z že nameščenim JDK za izdelavo našega projekta. Z ukazom ADD sliki dodamo trenutni imenik src, ga označimo kot delujočega (WORKDIR) in začnemo z gradnjo. Ukaz EXPOSE 8080 sporoča dockerju, da bo aplikacija v vsebniku uporabila svoja vrata 8080 (s tem aplikacija ne bo dostopna od zunaj, bo pa omogočila dostop do aplikacije, na primer, iz drugega vsebnika v istem omrežju dockerjev ).

Če želite zapakirati storitve v slike, morate zagnati ukaze iz korena vsakega projekta:

docker image build . -t msvc-backend:1.0.0

Rezultat je 456 MB velika slika (od tega je osnovna slika JDK zasedla 340 MB). In vse to kljub dejstvu, da je razrede v našem projektu mogoče prešteti na prst. Če želite zmanjšati velikost naše slike:

  • Uporabljamo večstopenjsko montažo. V prvem koraku bomo zgradili projekt, v drugem koraku bomo namestili JRE, v tretjem koraku pa bomo vse skupaj skopirali v novo čisto sliko Alpine. Na končni sliki bodo skupaj prikazane le potrebne komponente.
  • Uporabimo modularizacijo Jave. Začenši z Javo 9, lahko uporabite orodje jlink za ustvarjanje JRE samo iz modulov, ki jih potrebujete

Za radovedne je tukaj dober članek o pristopih k zmanjšanju slike. https://habr.com/ru/company/ruvds/blog/485650/.

Končna datoteka Docker:

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

Poustvarimo sliko in posledično je izgubila 6-kratno težo, kar znaša 77 MB. Ni slabo. Po tem lahko že pripravljene slike naložite v register slik, tako da so vaše slike na voljo za prenos z interneta.

Storitve soupravljanja v Dockerju

Za začetek morajo biti naše storitve v istem omrežju. V dockerju obstaja več vrst omrežij, mi pa uporabljamo najbolj primitivno med njimi - most, ki omogoča omrežno povezovanje vsebnikov, ki tečejo na istem gostitelju. Ustvarite omrežje z naslednjim ukazom:

docker network create msvc-network

Nato zaženite zaledni vsebnik z imenom 'backend' s sliko microservices-backend:1.0.0:

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

Treba je omeniti, da premostitveno omrežje zagotavlja takojšnje odkrivanje storitev za vsebnike po njihovih imenih. To pomeni, da bo zaledna storitev na voljo znotraj docker omrežja na http://backend:8080.

Zagon prehoda:

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 tem ukazu nakazujemo, da posredujemo vrata 80 našega gostitelja na vrata 8080 vsebnika. Možnosti env uporabljamo za nastavitev spremenljivk okolja, ki jih bo spring samodejno prebral in preglasil lastnosti iz application.properties.

Po zagonu pokličemo http://localhost/ in se prepričajte, da vse deluje, kot v prejšnjem primeru.

Zaključek

Kot rezultat smo ustvarili dve preprosti mikrostoritvi, ju zapakirali v docker vsebnike in ju skupaj zagnali na istem računalniku. Vendar ima nastali sistem številne pomanjkljivosti:

  • Slaba toleranca napak - vse nam deluje na enem strežniku
  • Slaba razširljivost - ko se obremenitev poveča, bi bilo lepo samodejno razmestiti dodatne instance storitve in uravnotežiti obremenitev med njimi
  • Zapletenost zagona - morali smo vnesti vsaj 3 ukaze in z določenimi parametri (to je samo za 2 storitvi)

Za odpravo zgornjih težav obstajajo številne rešitve, kot so Docker Swarm, Nomad, Kubernetes ali OpenShift. Če je celoten sistem napisan v Javi, lahko pogledate proti Spring Cloud (dober članek).

В naslednji del Govoril bom o tem, kako sem nastavil Kubernetes in uvedel projekt v Google Kubernetes Engine.

Vir: www.habr.com

Dodaj komentar