Pelajari cara menerapkan layanan mikro. Bagian 1. Spring Boot dan Docker

Pelajari cara menerapkan layanan mikro. Bagian 1. Spring Boot dan Docker

Hei Habr.

Pada artikel ini, saya ingin berbicara tentang pengalaman saya dalam menciptakan lingkungan belajar untuk bereksperimen dengan layanan mikro. Ketika saya mempelajari setiap alat baru, saya selalu ingin mencobanya tidak hanya pada mesin lokal, tetapi juga dalam kondisi yang lebih realistis. Oleh karena itu, saya memutuskan untuk membuat aplikasi layanan mikro yang disederhanakan, yang nantinya dapat "ditutupi" dengan segala macam teknologi menarik. Persyaratan utama untuk proyek ini adalah kedekatan fungsional maksimumnya dengan sistem nyata.

Awalnya, saya memecah pembuatan proyek menjadi beberapa langkah:

  1. Buat dua layanan - 'backend' (backend) dan 'gateway' (gateway), kemas menjadi gambar buruh pelabuhan dan atur untuk bekerja bersama

    Kata kunci: Java 11, Spring Boot, Docker, pengoptimalan gambar

  2. Pengembangan konfigurasi Kubernetes dan penerapan sistem di Google Kubernetes Engine

    Kata kunci: Kubernetes, GKE, manajemen sumber daya, penskalaan otomatis, rahasia

  3. Membuat bagan dengan Helm 3 untuk manajemen klaster yang lebih baik

    Tag: Helm 3, penerapan bagan

  4. Menyiapkan Jenkins dan pipeline untuk pengiriman kode secara otomatis ke cluster

    Kata kunci: Konfigurasi Jenkins, plugin, repositori konfigurasi terpisah

Saya berencana untuk mencurahkan artikel terpisah untuk setiap langkah.

Fokus dari rangkaian artikel ini bukanlah bagaimana menulis layanan mikro, tetapi bagaimana membuatnya bekerja dalam satu sistem. Meskipun semua hal ini biasanya di luar tanggung jawab pengembang, menurut saya tetap berguna untuk mengenal mereka setidaknya 20% (yang, seperti yang Anda ketahui, memberikan hasil 80%). Beberapa topik penting tanpa syarat, seperti keamanan, akan ditinggalkan dari proyek ini, karena penulis hanya memahami sedikit tentang sistem ini yang dibuat semata-mata untuk penggunaan pribadi. Saya menyambut setiap pendapat dan kritik yang membangun.

Membuat layanan mikro

Layanan ditulis dalam Java 11 menggunakan Spring Boot. Interaksi antarlayanan diatur menggunakan REST. Proyek ini akan menyertakan jumlah pengujian minimum (sehingga nanti ada sesuatu untuk diuji di Jenkins). Kode sumber untuk layanan tersedia di GitHub: backend ΠΈ gerbang.

Untuk dapat memeriksa status masing-masing layanan, Spring Actuator telah ditambahkan ke dependensinya. Ini akan membuat titik akhir /actuator/health dan mengembalikan status 200 jika layanan siap menerima lalu lintas, atau 504 jika ada masalah. Dalam hal ini, ini adalah pemeriksaan yang agak fiktif, karena layanannya sangat sederhana, dan jika terjadi force majeure, layanan tersebut kemungkinan besar tidak akan tersedia sama sekali daripada tetap beroperasi sebagian. Namun dalam sistem nyata, Actuator dapat membantu mendiagnosis masalah sebelum pengguna mulai mempermasalahkannya. Misalnya, jika ada masalah dalam mengakses database, kami dapat meresponsnya secara otomatis dengan menghentikan pemrosesan permintaan dengan instance layanan yang rusak.

Layanan ujung belakang

Layanan backend hanya akan menghitung dan mengembalikan jumlah permintaan yang diterima.

Kode pengontrol:

@RestController
public class RequestsCounterController {

    private final AtomicLong counter = new AtomicLong();

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

Tes pengontrol:

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

Gerbang Layanan

Gateway akan meneruskan permintaan ke layanan backend, melengkapinya dengan informasi berikut:

  • id gerbang. Diperlukan agar dimungkinkan untuk membedakan satu contoh gateway dari yang lain dengan respons server
  • Beberapa "rahasia" yang akan berperan sebagai kata sandi yang sangat penting (nomor kunci enkripsi cookie penting)

Konfigurasi di application.properties:

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

Adaptor ujung belakang:

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

Pengontrol:

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

Meluncurkan:

Kami memulai backend:

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

Memulai gerbang:

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

Kami memeriksa:

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

Semuanya bekerja. Pembaca yang penuh perhatian akan mencatat bahwa tidak ada yang menghalangi kami untuk mengakses backend secara langsung, melewati gateway (http://localhost:8081/requests). Untuk memperbaikinya, layanan harus digabungkan menjadi satu jaringan, dan hanya gateway yang harus "menonjol" di luar.
Juga, kedua layanan berbagi satu sistem file, menghasilkan aliran dan pada satu saat dapat mulai saling mengganggu. Akan menyenangkan untuk mengisolasi layanan mikro kami. Ini dapat dicapai dengan mendistribusikan aplikasi pada mesin yang berbeda (banyak uang, sulit), menggunakan mesin virtual (intensif sumber daya, mulai lama), atau menggunakan containerisasi. Seperti yang diharapkan, kami memilih opsi ketiga dan Buruh pelabuhan sebagai alat untuk wadahisasi.

Buruh pelabuhan

Singkatnya, buruh pelabuhan membuat wadah yang terisolasi, satu per aplikasi. Untuk menggunakan docker, Anda perlu menulis Dockerfile - instruksi untuk membuat dan menjalankan aplikasi. Selanjutnya, Anda dapat membuat gambar, mengunggahnya ke registri gambar (No. Dockerhub) dan terapkan layanan mikro Anda di lingkungan docker apa pun dalam satu perintah.

Dockerfile

Salah satu karakteristik terpenting dari sebuah gambar adalah ukurannya. Gambar yang ringkas akan mengunduh lebih cepat dari repositori jarak jauh, menghabiskan lebih sedikit ruang, dan layanan Anda akan mulai lebih cepat. Gambar apa pun dibuat berdasarkan gambar dasar, dan disarankan untuk memilih opsi yang paling minimalis. Pilihan yang bagus adalah Alpine, distribusi Linux lengkap dengan paket minimal.

Untuk memulainya, mari kita coba menulis Dockerfile "di dahi" (saya akan langsung mengatakan bahwa ini cara yang buruk, jangan lakukan itu):

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

Di sini kami menggunakan gambar dasar berbasis Alpine dengan JDK yang sudah terpasang untuk membangun proyek kami. Dengan perintah ADD, kami menambahkan direktori src saat ini ke gambar, menandainya sebagai berfungsi (WORKDIR) dan memulai pembangunan. Perintah EXPOSE 8080 memberi sinyal kepada buruh pelabuhan bahwa aplikasi dalam wadah akan menggunakan port 8080 miliknya (ini tidak akan membuat aplikasi dapat diakses dari luar, tetapi akan memungkinkan aplikasi untuk diakses, misalnya, dari wadah lain di jaringan buruh pelabuhan yang sama ).

Untuk mengemas layanan menjadi gambar, Anda perlu menjalankan perintah dari akar setiap proyek:

docker image build . -t msvc-backend:1.0.0

Hasilnya adalah gambar 456 MB (di mana gambar dasar JDK menempati 340 MB). Dan terlepas dari kenyataan bahwa kelas-kelas dalam proyek kami dapat dihitung dengan satu jari. Untuk memperkecil ukuran gambar kita:

  • Kami menggunakan perakitan multi-langkah. Pada langkah pertama kita akan membangun proyek, pada langkah kedua kita akan menginstal JRE, dan pada langkah ketiga kita akan menyalin semuanya ke dalam gambar Alpine baru yang bersih. Secara total, hanya komponen yang diperlukan yang akan ada di gambar akhir.
  • Mari kita gunakan modularisasi java. Dimulai dengan Java 9, Anda dapat menggunakan alat jlink untuk membuat JRE hanya dari modul yang Anda butuhkan

Untuk yang ingin tahu, berikut adalah artikel bagus tentang pendekatan pengurangan gambar. https://habr.com/ru/company/ruvds/blog/485650/.

File Docker Terakhir:

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

Kami membuat ulang gambar tersebut, dan sebagai hasilnya, beratnya turun 6 kali lipat, menjadi 77 MB. Tidak buruk. Setelah itu, gambar yang sudah jadi dapat diunggah ke registri gambar sehingga gambar Anda tersedia untuk diunduh dari Internet.

Layanan yang berjalan bersama di Docker

Pertama-tama, layanan kami harus berada di jaringan yang sama. Ada beberapa jenis jaringan di buruh pelabuhan, dan kami menggunakan yang paling primitif di antaranya - jembatan, yang memungkinkan Anda untuk membuat wadah jaringan yang berjalan di host yang sama. Buat jaringan dengan perintah berikut:

docker network create msvc-network

Selanjutnya, jalankan wadah backend bernama 'backend' dengan gambar microservices-backend:1.0.0 :

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

Perlu dicatat bahwa jaringan jembatan menyediakan penemuan layanan siap pakai untuk wadah dengan namanya. Artinya, layanan backend akan tersedia di dalam jaringan buruh pelabuhan di http://backend:8080.

Memulai gerbang:

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

Dalam perintah ini, kami menunjukkan bahwa kami meneruskan port 80 dari host kami ke port 8080 dari wadah. Kami menggunakan opsi env untuk mengatur variabel lingkungan yang secara otomatis akan dibaca oleh pegas dan menimpa properti dari application.properties.

Setelah memulai, kami menelepon http://localhost/ dan pastikan semuanya berfungsi, seperti pada kasus sebelumnya.

Kesimpulan

Hasilnya, kami membuat dua layanan mikro sederhana, mengemasnya dalam wadah buruh pelabuhan dan meluncurkannya bersama di mesin yang sama. Sistem yang dihasilkan, bagaimanapun, memiliki sejumlah kelemahan:

  • Toleransi kesalahan yang buruk - semuanya berfungsi untuk kami di satu server
  • Skalabilitas yang buruk - saat beban meningkat, akan lebih baik jika secara otomatis menerapkan instans layanan tambahan dan menyeimbangkan beban di antara keduanya
  • Kompleksitas peluncuran - kami harus memasukkan setidaknya 3 perintah, dan dengan parameter tertentu (ini hanya untuk 2 layanan)

Untuk memperbaiki masalah di atas, ada beberapa solusi seperti Docker Swarm, Nomad, Kubernetes, atau OpenShift. Jika seluruh sistem ditulis dalam Java, Anda dapat melihat ke arah Spring Cloud (artikel bagus).

Π’ bagian berikutnya Saya akan berbicara tentang cara saya menyiapkan Kubernetes dan menerapkan proyek ke Google Kubernetes Engine.

Sumber: www.habr.com

Tambah komentar