Mikro hizmetleri dağıtmayı öğrenme. Bölüm 1. Spring Boot ve Docker

Mikro hizmetleri dağıtmayı öğrenme. Bölüm 1. Spring Boot ve Docker

Merhaba Habr.

Bu yazıda mikro hizmetleri denemek için bir öğrenme ortamı oluşturma deneyimimden bahsetmek istiyorum. Her yeni aracı öğrenirken, onu sadece yerel makinemde değil, daha gerçekçi koşullarda da denemek istedim. Bu nedenle, daha sonra her türlü ilginç teknolojiyle "asılabilecek" basitleştirilmiş bir mikro hizmet uygulaması oluşturmaya karar verdim. Projenin temel gereksinimi, gerçek sisteme maksimum işlevsel yakınlıktır.

Başlangıçta projenin oluşturulmasını birkaç adıma ayırdım:

  1. 'Arka uç' ve 'ağ geçidi' olmak üzere iki hizmet oluşturun, bunları liman işçisi görüntülerine paketleyin ve birlikte çalışacak şekilde yapılandırın

    Anahtar Kelimeler: Java 11, Spring Boot, Docker, görüntü optimizasyonu

  2. Google Kubernetes Engine'de Kubernetes yapılandırma ve dağıtım sisteminin geliştirilmesi

    Anahtar Kelimeler: Kubernetes, GKE, kaynak yönetimi, otomatik ölçeklendirme, sırlar

  3. Daha verimli küme yönetimi için Helm 3'ü kullanarak bir grafik oluşturun

    Anahtar Kelimeler: Dümen 3, grafik dağıtımı

  4. Kümeye otomatik olarak kod teslim etmek için Jenkins ve işlem hattını ayarlama

    Anahtar Kelimeler: Jenkins yapılandırması, eklentiler, ayrı yapılandırma deposu

Her adıma ayrı bir makale ayırmayı planlıyorum.

Bu yazı serisinin odak noktası mikro hizmetlerin nasıl yazılacağı değil, bunların tek bir sistemde nasıl çalıştırılacağıdır. Bunların hepsi genellikle geliştiricinin sorumluluğu dışında olsa da, yine de en az %20 oranında bunlara aşina olmanın faydalı olduğunu düşünüyorum (ki bunun sonucun %80'ini oluşturduğu biliniyor). Güvenlik gibi kesinlikle önemli olan bazı konular bu projenin dışında bırakılacaktır çünkü yazar bu konuda çok az şey anlıyor; sistem yalnızca kişisel kullanım için yaratılıyor. Her türlü görüşe ve yapıcı eleştiriye açığım.

Mikro hizmetler oluşturma

Hizmetler Java 11'de Spring Boot kullanılarak yazılmıştır. Servisler arası iletişim REST kullanılarak düzenlenir. Proje minimum sayıda test içerecektir (böylece daha sonra Jenkins'te test edilecek bir şeyler olacaktır). Hizmetlerin kaynak kodu GitHub'da mevcuttur: arka uç и geçit.

Her hizmetin durumunu kontrol edebilmek için bağımlılıklarına bir Yaylı Aktüatör eklendi. Bir uç nokta/aktüatör/sağlık oluşturacak ve hizmet trafiği kabul etmeye hazırsa 200, sorun olması durumunda 504 durumunu döndürecektir. Bu durumda, hizmetler çok basit olduğundan ve bazı mücbir sebepler altında kısmen çalışır durumda kalmaktansa tamamen kullanılamaz hale gelme olasılıkları daha yüksek olduğundan, bu oldukça hayali bir kontroldür. Ancak gerçek sistemlerde Actuator, kullanıcılar sorunu çözmeye başlamadan önce sorunu teşhis etmeye yardımcı olabilir. Örneğin, veritabanına erişimde sorunlar ortaya çıkarsa, hizmetin bozuk bir örneğiyle isteklerin işlenmesini durdurarak buna otomatik olarak yanıt verebileceğiz.

Arka uç hizmeti

Arka uç hizmeti yalnızca kabul edilen istekleri sayar ve sayısını döndürür.

Denetleyici kodu:

@RestController
public class RequestsCounterController {

    private final AtomicLong counter = new AtomicLong();

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

Denetleyici testi:

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

Ağ geçidi hizmeti

Ağ geçidi, isteği aşağıdaki bilgilerle destekleyerek arka uç hizmetine iletecektir:

  • ağ geçidi kimliği. Ağ geçidinin bir örneğinin sunucu yanıtıyla diğerinden ayırt edilebilmesi için gereklidir
  • Çok önemli bir şifre rolünü oynayacak belirli bir “sır” (önemli bir çerezin şifrelenmesi için anahtar numarası)

Application.properties'teki yapılandırma:

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

Arka uçla iletişim için adaptör:

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

Denetleyici:

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

Başlatmak:

Arka ucu başlatalım:

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

Ağ geçidini başlatalım:

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

Kontrol ediyoruz:

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

Her şey çalışıyor. Dikkatli okuyucu, ağ geçidini atlayarak arka uca doğrudan erişmemizi hiçbir şeyin engellemediğini fark edecektir (http://localhost:8081/requests). Bunu düzeltmek için hizmetlerin tek bir ağda birleştirilmesi ve yalnızca ağ geçidinin dışarıda "dışarı çıkması" gerekir.
Ayrıca her iki hizmet de aynı dosya sistemini paylaşır, iş parçacıkları oluşturur ve bir noktada birbirlerine müdahale etmeye başlayabilir. Mikro hizmetlerimizi izole etmek güzel olurdu. Bu, uygulamaları farklı makinelere dağıtarak (çok para gerektiren, zor), sanal makineler kullanarak (kaynak yoğun, uzun başlangıç) veya konteynerizasyon kullanarak başarılabilir. Beklendiği gibi üçüncü seçeneği seçiyoruz ve liman işçisi konteynerleştirme için bir araç olarak.

liman işçisi

Kısacası Docker, uygulama başına bir tane olmak üzere izole kaplar oluşturur. Docker'ı kullanmak için, uygulamayı oluşturma ve çalıştırma talimatlarını içeren bir Docker dosyası yazmanız gerekir. Daha sonra görüntüyü oluşturabilir, görüntü kayıt defterine yükleyebilirsiniz (No. Dockerhub) ve mikro hizmetinizi herhangi bir docker ortamında tek komutla dağıtın.

Dockerfile

Bir görselin en önemli özelliklerinden biri boyutudur. Kompakt bir görüntü uzak bir depodan daha hızlı indirilecek, daha az yer kaplayacak ve hizmetiniz daha hızlı başlayacaktır. Herhangi bir görüntü, temel bir görüntü temel alınarak oluşturulur ve en minimalist seçeneğin seçilmesi önerilir. İyi bir seçenek, minimum pakete sahip tam teşekküllü bir Linux dağıtımı olan Alpine'dir.

Öncelikle Dockerfile’ı “başa baş” yazmayı deneyelim (Bunun kötü bir yol olduğunu hemen söyleyeceğim, yapmayın):

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

Burada projemizi oluşturmak için JDK'nın zaten kurulu olduğu Alpine tabanlı bir temel imaj kullanıyoruz. ADD komutunu kullanarak mevcut src dizinini image’a ekliyoruz, çalışıyor (WORKDIR) olarak işaretliyoruz ve build’i başlatıyoruz. EXPOSE 8080 komutu docker'a, konteynerdeki uygulamanın 8080 numaralı bağlantı noktasını kullanacağını bildirir (bu, uygulamayı dışarıdan erişilebilir hale getirmeyecek, ancak uygulamaya örneğin aynı docker ağındaki başka bir konteynerden erişilmesine izin verecektir) ).

Hizmetleri görüntüler halinde paketlemek için komutları her projenin kökünden çalıştırmanız gerekir:

docker image build . -t msvc-backend:1.0.0

Sonuç olarak, 456 MB boyutunda bir görüntü elde ediyoruz (bunun temel JDK 340 görüntüsü MB aldı). Ve hepsi projemizdeki sınıfların tek parmakla sayılabilmesine rağmen. Resmimizin boyutunu küçültmek için:

  • Çok adımlı montaj kullanıyoruz. İlk adımda projeyi birleştireceğiz, ikinci adımda JRE'yi kuracağız ve üçüncü adımda tüm bunları yeni, temiz bir Alpine görüntüsüne kopyalayacağız. Toplamda, son görüntü yalnızca gerekli bileşenleri içerecektir.
  • Java modülerleştirmesini kullanalım. Java 9'dan itibaren jlink aracını kullanarak yalnızca ihtiyacınız olan modüllerden bir JRE oluşturabilirsiniz.

Meraklısı için, görüntü boyutlarını küçültme yaklaşımları hakkında güzel bir makale https://habr.com/ru/company/ruvds/blog/485650/.

Son Docker dosyası:

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

Görüntüyü yeniden oluşturduk ve sonunda 6 kat daha ince hale gelerek 77 MB'a ulaştı. Fena değil. Daha sonra, bitmiş görüntüler, görüntü kayıt defterine yüklenebilir, böylece görüntüleriniz İnternet'ten indirilebilir hale gelir.

Docker'da hizmetleri birlikte çalıştırma

Başlangıç ​​olarak hizmetlerimizin aynı ağ üzerinde olması gerekir. Docker'da çeşitli ağ türleri vardır ve biz bunların en ilkelini kullanırız; bu, aynı ana bilgisayarda çalışan kapsayıcıları ağ oluşturmanıza olanak tanır. Aşağıdaki komutla bir ağ oluşturalım:

docker network create msvc-network

Ardından, microservices-backend:1.0.0 görseliyle 'backend' adında bir arka uç kapsayıcısı başlatalım:

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

Köprü ağının, konteynerler için adlarına göre alışılmışın dışında hizmet keşfi sağladığını belirtmekte fayda var. Yani, arka uç hizmeti şu adreste Docker ağı içinde mevcut olacaktır: http://backend:8080.

Ağ geçidini başlatalım:

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

Bu komutta hostumuzun 80 numaralı portunu Container’ın 8080 numaralı portuna ilettiğimizi belirtiyoruz. Spring tarafından otomatik olarak okunacak ortam değişkenlerini ayarlamak ve application.properties dosyasındaki özellikleri geçersiz kılmak için env seçeneklerini kullanırız.

Başlattıktan sonra arayın http://localhost/ ve önceki durumda olduğu gibi her şeyin çalıştığından emin olun.

Sonuç

Sonuç olarak iki basit mikro hizmet oluşturduk, bunları docker konteynerlerinde paketledik ve aynı makinede birlikte başlattık. Ancak ortaya çıkan sistemin bir takım dezavantajları vardır:

  • Zayıf hata toleransı - bizim için her şey tek bir sunucuda çalışıyor
  • Zayıf ölçeklenebilirlik - yük arttıkça, ek hizmet örneklerini otomatik olarak dağıtmak ve aralarındaki yükü dengelemek güzel olurdu
  • Başlatma karmaşıklığı - belirli parametrelerle en az 3 komut girmemiz gerekiyordu (bu yalnızca 2 hizmet içindir)

Yukarıdaki sorunları çözmek için Docker Swarm, Nomad, Kubernetes veya OpenShift gibi bir takım çözümler bulunmaktadır. Sistemin tamamı Java ile yazılmışsa Spring Cloud'a bakabilirsiniz (iyi makale).

В sonraki bölüm Sizlere Kubernetes'i nasıl kurduğumu ve projeyi Google Kubernetes Engine'e nasıl dağıttığımı anlatacağım.

Kaynak: habr.com

Yorum ekle