Μάθετε πώς να αναπτύσσετε μικροϋπηρεσίες. Μέρος 1. Spring Boot and Docker

Μάθετε πώς να αναπτύσσετε μικροϋπηρεσίες. Μέρος 1. Spring Boot and Docker

Γεια σου Χαμπρ.

Σε αυτό το άρθρο, θέλω να μιλήσω για την εμπειρία μου στη δημιουργία ενός μαθησιακού περιβάλλοντος για πειραματισμούς με μικροϋπηρεσίες. Όταν μάθαινα κάθε νέο εργαλείο, πάντα ήθελα να το δοκιμάσω όχι μόνο στο τοπικό μηχάνημα, αλλά και σε πιο ρεαλιστικές συνθήκες. Ως εκ τούτου, αποφάσισα να δημιουργήσω μια απλοποιημένη εφαρμογή microservice, η οποία μπορεί αργότερα να «καλυφθεί» με κάθε είδους ενδιαφέρουσες τεχνολογίες. Η κύρια απαίτηση για το έργο είναι η μέγιστη λειτουργική του εγγύτητα στο πραγματικό σύστημα.

Αρχικά, χώρισα τη δημιουργία του έργου σε διάφορα βήματα:

  1. Δημιουργήστε δύο υπηρεσίες - "backend" (backend) και "gateway" (gateway), συσκευάστε τις σε εικόνες docker και ρυθμίστε τις ώστε να συνεργάζονται

    Λέξεις-κλειδιά: Java 11, Spring Boot, Docker, βελτιστοποίηση εικόνας

  2. Ανάπτυξη της διαμόρφωσης του Kubernetes και της ανάπτυξης συστήματος στο Google Kubernetes Engine

    Λέξεις-κλειδιά: Kubernetes, GKE, διαχείριση πόρων, αυτόματη κλιμάκωση, μυστικά

  3. Δημιουργία γραφήματος με το Helm 3 για καλύτερη διαχείριση συμπλέγματος

    Ετικέτες: Helm 3, ανάπτυξη χάρτη

  4. Ρύθμιση Jenkins και pipeline για αυτόματη παράδοση κώδικα στο σύμπλεγμα

    Λέξεις-κλειδιά: Διαμόρφωση Jenkins, πρόσθετα, ξεχωριστό αποθετήριο ρυθμίσεων

Σκοπεύω να αφιερώσω ένα ξεχωριστό άρθρο σε κάθε βήμα.

Το επίκεντρο αυτής της σειράς άρθρων δεν είναι πώς να γράψετε microservices, αλλά πώς να τις κάνετε να λειτουργούν σε ένα ενιαίο σύστημα. Αν και όλα αυτά τα πράγματα είναι συνήθως έξω από την ευθύνη του προγραμματιστή, νομίζω ότι είναι ακόμα χρήσιμο να είναι εξοικειωμένοι με αυτά τουλάχιστον το 20% (το οποίο, όπως γνωρίζετε, δίνει το 80% του αποτελέσματος). Ορισμένα άνευ όρων σημαντικά θέματα, όπως η ασφάλεια, θα παραμείνουν εκτός αυτού του έργου, καθώς ο συγγραφέας καταλαβαίνει ελάχιστα σχετικά με αυτό το σύστημα που δημιουργήθηκε αποκλειστικά για προσωπική χρήση. Χαιρετίζω κάθε γνώμη και εποικοδομητική κριτική.

Δημιουργία μικροϋπηρεσιών

Οι υπηρεσίες γράφτηκαν σε Java 11 χρησιμοποιώντας Spring Boot. Η διυπηρεσιακή αλληλεπίδραση οργανώνεται χρησιμοποιώντας το REST. Το έργο θα περιλαμβάνει έναν ελάχιστο αριθμό δοκιμών (ώστε αργότερα να υπάρχει κάτι για δοκιμή στο Jenkins). Ο πηγαίος κώδικας για τις υπηρεσίες είναι διαθέσιμος στο GitHub: backend и πύλη.

Για να μπορείτε να ελέγξετε την κατάσταση καθεμιάς από τις υπηρεσίες, έχει προστεθεί ένας ενεργοποιητής άνοιξης στις εξαρτήσεις τους. Θα δημιουργήσει ένα τελικό σημείο /ενεργοποιητή/υγεία και θα επιστρέψει μια κατάσταση 200 εάν η υπηρεσία είναι έτοιμη να δεχτεί κίνηση ή 504 εάν υπάρχει πρόβλημα. Σε αυτήν την περίπτωση, πρόκειται για έναν μάλλον εικονικό έλεγχο, καθώς οι υπηρεσίες είναι πολύ απλές και σε περίπτωση ανωτέρας βίας, είναι πιο πιθανό να καταστούν εντελώς μη διαθέσιμες παρά να παραμείνουν εν μέρει λειτουργικές. Αλλά σε πραγματικά συστήματα, το Actuator μπορεί να βοηθήσει στη διάγνωση ενός προβλήματος πριν οι χρήστες αρχίσουν να μάχονται για αυτό. Για παράδειγμα, εάν υπάρχουν προβλήματα με την πρόσβαση στη βάση δεδομένων, μπορούμε να ανταποκριθούμε αυτόματα σε αυτό διακόπτοντας την επεξεργασία αιτημάτων με μια κατεστραμμένη παρουσία υπηρεσίας.

Back end υπηρεσία

Η υπηρεσία υποστήριξης απλώς θα μετρήσει και θα επιστρέψει τον αριθμό των αποδεκτών αιτημάτων.

Κωδικός ελεγκτή:

@RestController
public class RequestsCounterController {

    private final AtomicLong counter = new AtomicLong();

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

Δοκιμή ελεγκτή:

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

Service Gateway

Η πύλη θα προωθήσει το αίτημα στην υπηρεσία υποστήριξης, συμπληρώνοντάς το με τις ακόλουθες πληροφορίες:

  • αναγνωριστικό πύλης. Απαιτείται έτσι ώστε να είναι δυνατή η διάκριση μιας παρουσίας της πύλης από την άλλη από την απόκριση διακομιστή
  • Κάποιο «μυστικό» που θα παίξει το ρόλο ενός πολύ σημαντικού κωδικού πρόσβασης (αριθμός του κλειδιού κρυπτογράφησης ενός σημαντικού cookie)

Διαμόρφωση στο application.properties:

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

Προσαρμογέας υποστήριξης:

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

Ελεγκτής:

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

Εκκίνηση:

Ξεκινάμε το backend:

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

Εκκίνηση της πύλης:

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

Ελέγχουμε:

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

Όλα λειτουργούν. Ένας προσεκτικός αναγνώστης θα σημειώσει ότι τίποτα δεν μας εμποδίζει να έχουμε άμεση πρόσβαση στο backend, παρακάμπτοντας την πύλη (http://localhost:8081/requests). Για να διορθωθεί αυτό, οι υπηρεσίες πρέπει να συνδυαστούν σε ένα δίκτυο και μόνο η πύλη θα πρέπει να "ξεκολλάει" έξω.
Επίσης, και οι δύο υπηρεσίες μοιράζονται ένα σύστημα αρχείων, παράγουν ροές και σε μια στιγμή μπορούν να αρχίσουν να παρεμβαίνουν μεταξύ τους. Θα ήταν ωραίο να απομονώσουμε τις μικροϋπηρεσίες μας. Αυτό μπορεί να επιτευχθεί με τη διανομή εφαρμογών σε διαφορετικά μηχανήματα (πολλά χρήματα, δύσκολα), με τη χρήση εικονικών μηχανών (με ένταση πόρων, μεγάλη εκκίνηση) ή με τη χρήση κοντέινερ. Όπως ήταν αναμενόμενο, επιλέγουμε την τρίτη επιλογή και Λιμενεργάτης ως εργαλείο για δοχεία.

Λιμενεργάτης

Εν ολίγοις, το docker δημιουργεί μεμονωμένα δοχεία, ένα ανά εφαρμογή. Για να χρησιμοποιήσετε το docker, πρέπει να γράψετε ένα Dockerfile - οδηγίες για τη δημιουργία και την εκτέλεση της εφαρμογής. Στη συνέχεια, μπορείτε να δημιουργήσετε την εικόνα, να την ανεβάσετε στο μητρώο εικόνων (αρ. Dockerhub) και αναπτύξτε τη μικρουπηρεσία σας σε οποιοδήποτε περιβάλλον σύνδεσης με βάση με μία εντολή.

Dockerfile

Ένα από τα πιο σημαντικά χαρακτηριστικά μιας εικόνας είναι το μέγεθός της. Μια συμπαγής εικόνα θα κατεβάσει πιο γρήγορα από ένα απομακρυσμένο αποθετήριο, θα καταλαμβάνει λιγότερο χώρο και η υπηρεσία σας θα ξεκινήσει πιο γρήγορα. Οποιαδήποτε εικόνα είναι χτισμένη με βάση τη βασική εικόνα και συνιστάται να επιλέξετε την πιο μινιμαλιστική επιλογή. Μια καλή επιλογή είναι το Alpine, μια πλήρης διανομή Linux με ελάχιστα πακέτα.

Αρχικά, ας προσπαθήσουμε να γράψουμε ένα Dockerfile "στο μέτωπο" (θα πω αμέσως ότι αυτός είναι ένας κακός τρόπος, μην το κάνετε):

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

Εδώ χρησιμοποιούμε μια βασική εικόνα που βασίζεται σε Alpine με ήδη εγκατεστημένο το JDK για την κατασκευή του έργου μας. Με την εντολή ADD προσθέτουμε τον τρέχοντα κατάλογο src στην εικόνα, τον επισημαίνουμε ότι λειτουργεί (WORKDIR) και ξεκινάμε την κατασκευή. Η εντολή EXPOSE 8080 σηματοδοτεί στο docker ότι η εφαρμογή στο κοντέινερ θα χρησιμοποιήσει τη θύρα 8080 (αυτό δεν θα κάνει την εφαρμογή προσβάσιμη από το εξωτερικό, αλλά θα επιτρέψει την πρόσβαση στην εφαρμογή, για παράδειγμα, από άλλο κοντέινερ στο ίδιο δίκτυο docker ).

Για να συσκευάσετε υπηρεσίες σε εικόνες, πρέπει να εκτελέσετε εντολές από τη ρίζα κάθε έργου:

docker image build . -t msvc-backend:1.0.0

Το αποτέλεσμα είναι μια εικόνα 456 MB (εκ των οποίων η βασική εικόνα JDK καταλάμβανε 340 MB). Και όλα αυτά παρά το γεγονός ότι οι τάξεις στο έργο μας μπορούν να μετρηθούν στο δάχτυλο. Για να μειώσετε το μέγεθος της εικόνας μας:

  • Χρησιμοποιούμε συναρμολόγηση πολλαπλών βημάτων. Στο πρώτο βήμα θα δημιουργήσουμε το έργο, στο δεύτερο βήμα θα εγκαταστήσουμε το JRE και στο τρίτο βήμα θα το αντιγράψουμε όλο σε μια νέα καθαρή εικόνα των Άλπεων. Συνολικά, μόνο τα απαραίτητα στοιχεία θα υπάρχουν στην τελική εικόνα.
  • Ας χρησιμοποιήσουμε το modularization της java. Ξεκινώντας με την Java 9, μπορείτε να χρησιμοποιήσετε το εργαλείο jlink για να δημιουργήσετε ένα JRE μόνο από τις μονάδες που χρειάζεστε

Για τους περίεργους, εδώ είναι ένα καλό άρθρο σχετικά με τις προσεγγίσεις μείωσης εικόνας. https://habr.com/ru/company/ruvds/blog/485650/.

Τελικό αρχείο 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"]

Αναδημιουργούμε την εικόνα και ως αποτέλεσμα έχασε 6 φορές το βάρος της, φτάνοντας τα 77 MB. Δεν είναι κακό. Μετά από αυτό, οι έτοιμες εικόνες μπορούν να μεταφορτωθούν στο μητρώο εικόνων, ώστε οι εικόνες σας να είναι διαθέσιμες για λήψη από το Διαδίκτυο.

Υπηρεσίες από κοινού στο Docker

Αρχικά, οι υπηρεσίες μας πρέπει να βρίσκονται στο ίδιο δίκτυο. Υπάρχουν διάφοροι τύποι δικτύων στο docker και χρησιμοποιούμε τον πιο πρωτόγονο από αυτούς - το bridge, το οποίο σας επιτρέπει να δικτυώνετε κοντέινερ που τρέχουν στον ίδιο κεντρικό υπολογιστή. Δημιουργήστε ένα δίκτυο με την ακόλουθη εντολή:

docker network create msvc-network

Στη συνέχεια, ξεκινήστε το κοντέινερ backend με το όνομα "backend" με την εικόνα microservices-backend:1.0.0:

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

Αξίζει να σημειωθεί ότι το δίκτυο γεφυρών παρέχει out of the box υπηρεσία εντοπισμού για κοντέινερ με το όνομά τους. Δηλαδή, η υπηρεσία υποστήριξης θα είναι διαθέσιμη εντός του δικτύου docker στο http://backend:8080.

Εκκίνηση της πύλης:

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

Σε αυτήν την εντολή, υποδεικνύουμε ότι προωθούμε τη θύρα 80 του κεντρικού υπολογιστή μας στη θύρα 8080 του κοντέινερ. Χρησιμοποιούμε τις επιλογές env για να ορίσουμε μεταβλητές περιβάλλοντος που θα διαβάζονται αυτόματα από το ελατήριο και θα παρακάμπτουν τις ιδιότητες από το application.properties.

Αφού ξεκινήσουμε, καλούμε http://localhost/ και βεβαιωθείτε ότι όλα λειτουργούν, όπως στην προηγούμενη περίπτωση.

Συμπέρασμα

Ως αποτέλεσμα, δημιουργήσαμε δύο απλές microservices, τις συσκευάσαμε σε docker containers και τις λανσάραμε μαζί στο ίδιο μηχάνημα. Το σύστημα που προκύπτει, ωστόσο, έχει μια σειρά από μειονεκτήματα:

  • Κακή ανοχή σφαλμάτων - όλα λειτουργούν για εμάς σε έναν διακομιστή
  • Κακή επεκτασιμότητα - όταν το φορτίο αυξάνεται, θα ήταν ωραίο να αναπτύξετε αυτόματα πρόσθετες παρουσίες υπηρεσιών και να εξισορροπήσετε το φορτίο μεταξύ τους
  • Η πολυπλοκότητα της εκκίνησης - χρειάστηκε να εισαγάγουμε τουλάχιστον 3 εντολές και με ορισμένες παραμέτρους (αυτό είναι μόνο για 2 υπηρεσίες)

Για να διορθώσετε τα παραπάνω προβλήματα, υπάρχουν διάφορες λύσεις όπως το Docker Swarm, το Nomad, το Kubernetes ή το OpenShift. Εάν ολόκληρο το σύστημα είναι γραμμένο σε Java, μπορείτε να κοιτάξετε προς το Spring Cloud (καλό άρθρο).

В επόμενο μέρος Θα μιλήσω για το πώς ρύθμισα το Kubernetes και ανέπτυξα το έργο στο Google Kubernetes Engine.

Πηγή: www.habr.com

Προσθέστε ένα σχόλιο