'Hello World' bagimu di awan

Dunia menjadi gila, mendorong kalkulator 2 + 2 ke awan. Kenapa kita lebih buruk? Mari kita dorong Hello World menjadi tiga layanan mikro, menulis beberapa tes, memberikan dokumentasi kepada pengguna, menggambar saluran pipa perakitan yang indah dan menyediakan penyebaran ke produk cloud bersyarat setelah berhasil menyelesaikan tes. Jadi, artikel ini akan menunjukkan contoh bagaimana proses pengembangan produk dapat dibangun dari spesifikasi ke penyebaran ke prod. Menarik? lalu aku bertanya di bawah kat


Di mana Roooo memulai ...?


Tidak ada Tanah Air, tetapi sebuah produk. Benar, produk dimulai dengan sebuah ide. Jadi idenya adalah ini:


  • membutuhkan layanan yang mengembalikan 'Hello World' melalui REST API
  • kata 'Hello' memberikan satu layanan Microsoft yang dirancang, dibuat dan diuji oleh command_1
  • kata 'World' memberi yang kedua, yang dijalankan oleh team_2
  • team_3 menulis layanan integrasi untuk menempelkan 'Hello' dan 'World'

Toolset


  • OS (desktop) - Debian 9 Stretch
  • IDE - IntelliJ IDEA 2019.1
  • Git Repo - GitHub
  • CI - Concourse 5.4.0
  • Maven Repo - Nexus
  • Openjdk 11
  • Maven 3.6.0
  • Kubernetes 1.14 (1 master + 1 pekerja): jaringan calico, nginx-ingress-controller

Catatan penting: artikel ini bukan tentang kode yang indah (codestyle, checkstyle, javadocs, SOLID, dan buzzword lainnya) dan solusi yang dijilat dengan sempurna (Anda dapat terus-menerus berbicara tentang Hello World yang sempurna). Ini tentang cara menyusun kode, spesifikasi, perakitan pipa, dan pengiriman semua yang dirakit dalam produk, tetapi alih-alih dari HelloWorld, pada kenyataannya Anda dapat memiliki beberapa produk yang sangat banyak dengan sekelompok layanan mikro yang kompleks dan keren, dan proses yang dijelaskan dapat diterapkan untuk itu.

Terdiri dari apakah layanan ini?


Layanan dalam bentuk produk akhir harus mengandung:


  • spesifikasi dalam bentuk dokumen yaml dari standar OpenAPI dan dapat memberikannya berdasarkan permintaan (GET / doc)
  • Metode API sebagaimana ditentukan dalam paragraf pertama
  • README.md dengan contoh memulai dan mengonfigurasi layanan

Kami akan menganalisis layanan secara berurutan. Ayo pergi!


Layanan microser 'Halo'


Spesifikasi


Kami menulis spesifikasi di Editor Swagger dan mengonversinya menjadi spesifikasi OpenAPI. Swagger Editor diluncurkan di buruh pelabuhan dengan satu perintah, konversi dok swagger menjadi openapi-doc dilakukan dengan menekan satu tombol di UI editor, yang mengirimkan permintaan POST / api / convert ke http://converter.swagger.io . Spesifikasi akhir dari layanan halo:


openapi: 3.0.1 info: title: Hello ;) description: Hello microservice version: 1.0.0 servers: - url: https://demo1.bihero.io/api/hello tags: - name: hello description: Everything about saying 'Hello' paths: /: x-vertx-event-bus: address: service.hello timeout: 1000c get: tags: - hello summary: Get 'Hello' word operationId: getHelloWord responses: 200: description: OK /doc: x-vertx-event-bus: address: service.hello timeout: 1000c get: tags: - hello_doc summary: Get 'Hello' microservice documentation operationId: getDoc responses: 200: description: OK components: {} 

Implementasi


Layanan dari sudut pandang kode yang akan ditulis terdiri dari 3 kelas:


  • antarmuka dengan metode layanan (nama metode ditentukan dalam spec sebagai operationId )
  • implementasi antarmuka
  • vertx verticle untuk mengikat layanan dengan spec (metode api -> metode antarmuka dari paragraf pertama) dan untuk memulai server http

Struktur file di src terlihat seperti ini:


pom.xml
 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <properties> <main.verticle>io.bihero.hello.HelloVerticle</main.verticle> <vertx.version>3.8.1</vertx.version> <logback.version>1.2.3</logback.version> <junit-jupiter.version>5.3.1</junit-jupiter.version> <maven-surefire-plugin.version>2.19.1</maven-surefire-plugin.version> <junit-platform-surefire-provider.version>1.1.0</junit-platform-surefire-provider.version> <assertj-core.version>3.8.0</assertj-core.version> <allure.version>2.8.1</allure.version> <allure-maven.version>2.10.0</allure-maven.version> <aspectj.version>1.9.2</aspectj.version> <mockito.version>2.21.0</mockito.version> <rest-assured.version>3.0.0</rest-assured.version> </properties> <groupId>io.bihero</groupId> <artifactId>hello-microservice</artifactId> <version>1.0.0-SNAPSHOT</version> <build> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <configuration> ```1.8</source> <target>1.8</target> </configuration> <executions> <execution> <id>default-compile</id> <configuration> <annotationProcessors> <annotationProcessor>io.vertx.codegen.CodeGenProcessor</annotationProcessor> </annotationProcessors> <generatedSourcesDirectory>src/main/generated</generatedSourcesDirectory> <compilerArgs> <arg>-Acodegen.output=${project.basedir}/src/main</arg> </compilerArgs> </configuration> </execution> <execution> <id>default-testCompile</id> <configuration> <annotationProcessors> <annotationProcessor>io.vertx.codegen.CodeGenProcessor</annotationProcessor> </annotationProcessors> <generatedTestSourcesDirectory>src/test/generated</generatedTestSourcesDirectory> <compilerArgs> <arg>-Acodegen.output=${project.basedir}/src/test</arg> </compilerArgs> </configuration> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>${maven-surefire-plugin.version}</version> <configuration> <properties> <property> <name>listener</name> <value>io.qameta.allure.junit5.AllureJunit5</value> </property> </properties> <includes> <include>**/*Test*.java</include> </includes> <argLine> -javaagent:"${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar" -Djdk.net.URLClassPath.disableClassPathURLCheck=true </argLine> <systemProperties> <property> <name>allure.results.directory</name> <value>${project.basedir}/target/allure-results</value> </property> <property> <name>junit.jupiter.extensions.autodetection.enabled</name> <value>true</value> </property> </systemProperties> <reportFormat>plain</reportFormat> </configuration> <dependencies> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>${aspectj.version}</version> </dependency> <dependency> <groupId>org.junit.platform</groupId> <artifactId>junit-platform-surefire-provider</artifactId> <version>${junit-platform-surefire-provider.version}</version> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> <version>${junit-jupiter.version}</version> </dependency> </dependencies> </plugin> <plugin> <groupId>io.qameta.allure</groupId> <artifactId>allure-maven</artifactId> <version>${allure-maven.version}</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-site-plugin</artifactId> <version>3.7.1</version> <dependencies> <dependency> <groupId>org.apache.maven.wagon</groupId> <artifactId>wagon-webdav-jackrabbit</artifactId> <version>2.8</version> </dependency> </dependencies> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-project-info-reports-plugin</artifactId> <version>3.0.0</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>2.3</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <manifestEntries> <Main-Class>io.vertx.core.Launcher</Main-Class> <Main-Verticle>${main.verticle}</Main-Verticle> </manifestEntries> </transformer> <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> <resource>META-INF/services/io.vertx.core.spi.VerticleFactory</resource> </transformer> </transformers> <artifactSet> </artifactSet> <outputFile>${project.build.directory}/${project.artifactId}-fat.jar</outputFile> </configuration> </execution> </executions> </plugin> </plugins> <resources> <resource> <directory>src/main/resources</directory> <filtering>true</filtering> <includes> <include>**/version.txt</include> </includes> </resource> <resource> <directory>src/main/resources</directory> <filtering>false</filtering> <excludes> <exclude>**/version.txt</exclude> </excludes> </resource> </resources> </build> <distributionManagement> <site> <id>reports</id> <url>dav:https://nexus.dev.techedge.pro:8443/repository/reports/${project.artifactId}/</url> </site> </distributionManagement> <reporting> <excludeDefaults>true</excludeDefaults> <plugins> <plugin> <groupId>io.qameta.allure</groupId> <artifactId>allure-maven</artifactId> <configuration> <resultsDirectory>${project.build.directory}/allure-results</resultsDirectory> <reportDirectory>${project.reporting.outputDirectory}/${project.version}/allure</reportDirectory> </configuration> </plugin> </plugins> </reporting> <dependencies> <dependency> <groupId>io.vertx</groupId> <artifactId>vertx-web-api-service</artifactId> <version>${vertx.version}</version> </dependency> <dependency> <groupId>io.vertx</groupId> <artifactId>vertx-codegen</artifactId> <version>${vertx.version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>${logback.version}</version> </dependency> <!-- test &ndash;&gt;--> <dependency> <groupId>io.vertx</groupId> <artifactId>vertx-unit</artifactId> <version>${vertx.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>io.vertx</groupId> <artifactId>vertx-junit5</artifactId> <version>${vertx.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>${junit-jupiter.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> <version>${junit-jupiter.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.assertj</groupId> <artifactId>assertj-core</artifactId> <version>${assertj-core.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>${mockito.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>io.qameta.allure</groupId> <artifactId>allure-junit5</artifactId> <version>${allure.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>io.vertx</groupId> <artifactId>vertx-web-client</artifactId> <version>${vertx.version}</version> <scope>test</scope> </dependency> </dependencies> </project> 

HelloService.java
 package io.bihero.hello; import io.vertx.core.AsyncResult; import io.vertx.core.Handler; import io.vertx.core.Vertx; import io.vertx.ext.web.api.OperationRequest; import io.vertx.ext.web.api.OperationResponse; import io.vertx.ext.web.api.generator.WebApiServiceGen; @WebApiServiceGen public interface HelloService { static HelloService create(Vertx vertx) { return new DefaultHelloService(vertx); } void getHelloWord(OperationRequest context, Handler<AsyncResult<OperationResponse>> resultHandler); void getDoc(OperationRequest context, Handler<AsyncResult<OperationResponse>> resultHandler); } 

DefaultHelloService.java
 package io.bihero.hello; import io.vertx.core.AsyncResult; import io.vertx.core.Future; import io.vertx.core.Handler; import io.vertx.core.Vertx; import io.vertx.core.buffer.Buffer; import io.vertx.ext.web.api.OperationRequest; import io.vertx.ext.web.api.OperationResponse; public class DefaultHelloService implements HelloService { private final Vertx vertx; public DefaultHelloService(Vertx vertx) { this.vertx = vertx; } @Override public void getHelloWord(OperationRequest context, Handler<AsyncResult<OperationResponse>> resultHandler) { resultHandler.handle(Future.succeededFuture(OperationResponse.completedWithPlainText(Buffer.buffer("Hello")))); } @Override public void getDoc(OperationRequest context, Handler<AsyncResult<OperationResponse>> resultHandler) { vertx.fileSystem().readFile("doc.yaml", buffResult -> resultHandler.handle(Future.succeededFuture( OperationResponse.completedWithPlainText(buffResult.result())) )); } } 

HelloVerticle.java
 package io.bihero.hello; import io.vertx.core.AbstractVerticle; import io.vertx.core.Promise; import io.vertx.core.eventbus.MessageConsumer; import io.vertx.core.http.HttpServer; import io.vertx.core.http.HttpServerOptions; import io.vertx.core.json.JsonObject; import io.vertx.ext.web.Router; import io.vertx.ext.web.api.contract.openapi3.OpenAPI3RouterFactory; import io.vertx.serviceproxy.ServiceBinder; public class HelloVerticle extends AbstractVerticle { private HttpServer server; private MessageConsumer<JsonObject> consumer; @Override public void start(Promise<Void> promise) { startHelloService(); startHttpServer().future().setHandler(promise); } /** * This method closes the http server and unregister all services loaded to Event Bus */ @Override public void stop(){ this.server.close(); consumer.unregister(); } private void startHelloService() { consumer = new ServiceBinder(vertx).setAddress("service.hello") .register(HelloService.class, HelloService.create(getVertx())); } /** * This method constructs the router factory, mounts services and handlers and starts the http server * with built router * @return */ private Promise<Void> startHttpServer() { Promise<Void> promise = Promise.promise(); OpenAPI3RouterFactory.create(this.vertx, "/doc.yaml", openAPI3RouterFactoryAsyncResult -> { if (openAPI3RouterFactoryAsyncResult.succeeded()) { OpenAPI3RouterFactory routerFactory = openAPI3RouterFactoryAsyncResult.result(); // Mount services on event bus based on extensions routerFactory.mountServicesFromExtensions(); // Generate the router Router router = routerFactory.getRouter(); int port = config().getInteger("serverPort", 8080); String host = config().getString("serverHost", "localhost"); server = vertx.createHttpServer(new HttpServerOptions().setPort(port).setHost(host)); server.requestHandler(router).listen(ar -> { // Error starting the HttpServer if (ar.succeeded()) promise.complete(); else promise.fail(ar.cause()); }); } else { // Something went wrong during router factory initialization promise.fail(openAPI3RouterFactoryAsyncResult.cause()); } }); return promise; } } 

Tidak ada yang tidak biasa dalam antarmuka layanan dan implementasinya (dengan pengecualian penjelasan @WebApiServiceGen, tetapi Anda dapat membacanya di dokumentasi ), tetapi di sini adalah melihat kode kelas simpul secara lebih rinci.


Dua metode yang disebut di awal vertikal menarik:


  • startHelloService membuat objek dengan implementasi layanan kami dan mengikatnya ke alamat di bus acara (ingat parameter x-vertx-event-bus.tambahkan dari spesifikasi di atas)
  • startHttpServer menciptakan pabrik router berdasarkan spesifikasi layanan, membuat server http dan mengaitkan router yang dibuat ke semua permintaan http yang masuk (jika itu gurbo, maka permintaan GET / akan jatuh ke dalam bus peristiwa puncak dengan layanan alamat. Halo (dan kami mengikat di sana implementasi layanan io.bihero.hello.HelloService ) dan dengan nama metode layanan getHelloWord )

Saatnya untuk mengumpulkan dzharnik dan mencoba menjalankan:


 mvn clean package #   java -Dlogback.configurationFile=./src/conf/logback-console.xml -jar target/hello-microservice-fat.jar -conf ./src/conf/config.json #   

Dua parameter menarik di baris peluncuran:


  • -Dlogback.configurationFile =. / Src / conf / logback-console.xml - path ke file config untuk logback (dependensi proyek harus memiliki slf4j dan logback sebagai implementasi slf4j-api)
  • -conf ./src/conf/config.json - service config, port tempat http REST API akan dibuka adalah penting bagi kami:
     { "type": "file", "format": "json", "scanPeriod": 5000, "config": { "path": "/home/slava/JavaProjects/hello-world-to-cloud/hellomicroservice/src/conf/config.json" }, "serverPort": 8081, "serverHost": "0.0.0.0" } 

Output pakar tidak terlalu menarik bagi kami, tetapi Anda dapat melihat bagaimana layanan dimulai, (dalam pengaturan logger untuk paket io.netty , level = "INFO" diatur )


Cara memulai layanan
 2019-10-03 20:52:45,159 [vert.x-worker-thread-0] DEBUG isvpOpenAPIV3Parser: Loaded raw data: openapi: 3.0.1 info: title: Hello ;) description: Hello microservice version: 1.0.0 servers: - url: https://demo1.bihero.io/api/hello tags: - name: hello description: Everything about saying 'Hello' paths: /: x-vertx-event-bus: address: service.hello timeout: 1000c get: tags: - hello summary: Get 'Hello' word operationId: getHelloWord responses: 200: description: OK /doc: x-vertx-event-bus: address: service.hello timeout: 1000c get: tags: - hello_doc summary: Get 'Hello' microservice documentation operationId: getDoc responses: 200: description: OK components: {} 2019-10-03 20:52:45,195 [vert.x-worker-thread-0] DEBUG isvpOpenAPIV3Parser: Parsed rootNode: {"openapi":"3.0.1","info":{"title":"Hello ;)","description":"Hello microservice","version":"1.0.0"},"servers":[{"url":"https://demo1.bihero.io/api/hello"}],"tags":[{"name":"hello","description":"Everything about saying 'Hello'"}],"paths":{"/":{"x-vertx-event-bus":{"address":"service.hello","timeout":"1000c"},"get":{"tags":["hello"],"summary":"Get 'Hello' word","operationId":"getHelloWord","responses":{"200":{"description":"OK"}}}},"/doc":{"x-vertx-event-bus":{"address":"service.hello","timeout":"1000c"},"get":{"tags":["hello_doc"],"summary":"Get 'Hello' microservice documentation","operationId":"getDoc","responses":{"200":{"description":"OK"}}}}},"components":{}} Oct 03, 2019 8:52:45 PM io.vertx.core.impl.launcher.commands.VertxIsolatedDeployer INFO: Succeeded in deploying verticle 

Hore! Layanan berfungsi, Anda dapat memeriksa:


 curl http://127.0.0.1:8081/ Hello curl -v http://127.0.0.1:8081/doc openapi: 3.0.1 info: title: Hello ;) description: Hello microservice version: 1.0.0 servers: - url: https://demo1.bihero.io/api/hello tags: - name: hello description: Everything about saying 'Hello' paths: /: x-vertx-event-bus: address: service.hello timeout: 1000c get: tags: - hello summary: Get 'Hello' word operationId: getHelloWord responses: 200: description: OK /doc: x-vertx-event-bus: address: service.hello timeout: 1000c get: tags: - hello_doc summary: Get 'Hello' microservice documentation operationId: getDoc responses: 200: description: OK components: {} 

Layanan merespons dengan kata Halo pada GET / permintaan, yang sesuai dengan spesifikasi, dan dapat berbicara tentang apa yang dapat dilakukannya, memberikan spesifikasi untuk permintaan GET / doc . Keren, mari kita pergi ke prod!


Ada yang salah di sini ...


Sebelumnya, saya menulis bahwa output pakar selama perakitan tidak terlalu penting bagi kami. Saya berbohong, kesimpulannya penting dan sangat. Kita perlu pakar untuk menjalankan tes dan ketika tes jatuh, majelis jatuh. Perakitan di atas berlalu, dan ini menunjukkan bahwa salah satu tes lulus, atau tidak. Tentu saja, kami tidak memiliki tes apa pun, saatnya menulisnya (di sini Anda dapat berdebat tentang metodologi, kapan dan bagaimana menulis tes, sebelum atau setelah implementasi, tetapi kami akan mengingat catatan penting di awal artikel dan terus maju - kami akan menulis beberapa tes).
Kelas tes pertama, pada dasarnya, adalah unit test yang menguji dua metode spesifik layanan kami:


HelloServiceTest.java
 package io.bihero.hello; import io.vertx.core.Vertx; import io.vertx.core.json.JsonObject; import io.vertx.ext.web.api.OperationRequest; import io.vertx.junit5.VertxExtension; import io.vertx.junit5.VertxTestContext; import org.apache.commons.io.IOUtils; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import java.io.IOException; import static org.assertj.core.api.Assertions.assertThat; @ExtendWith(VertxExtension.class) public class HelloServiceTest { private HelloService helloService = HelloService.create(Vertx.vertx()); @Test @DisplayName("Test 'getHelloWord' method returns 'Hello' word") public void testHelloMethod(VertxTestContext testContext) { helloService.getHelloWord(new OperationRequest(new JsonObject()), testContext.succeeding(it -> { assertThat(it.getStatusCode()).isEqualTo(200); assertThat(it.getPayload().toString()).isEqualTo("Hello"); testContext.completeNow(); })); } @Test @DisplayName("Test 'getDoc' method returns service documentation in OpenAPI format") public void testDocMethod(VertxTestContext testContext) { helloService.getDoc(new OperationRequest(new JsonObject()), testContext.succeeding(it -> { try { assertThat(it.getStatusCode()).isEqualTo(200); assertThat(it.getPayload().toString()).isEqualTo(IOUtils.toString(this.getClass() .getResourceAsStream("../../../doc.yaml"), "UTF-8")); testContext.completeNow(); } catch (IOException e) { testContext.failNow(e); } })); } } 

Tes kedua adalah tes non-integrasi, memeriksa apakah vertikal naik dan merespons permintaan http yang sesuai dengan status dan teks yang diharapkan:


HelloVerticleTest.java
 package io.bihero.hello; import io.vertx.core.Vertx; import io.vertx.core.json.JsonObject; import io.vertx.ext.web.client.WebClient; import io.vertx.ext.web.codec.BodyCodec; import io.vertx.junit5.Checkpoint; import io.vertx.junit5.VertxExtension; import io.vertx.junit5.VertxTestContext; import org.apache.commons.io.IOUtils; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; @ExtendWith(VertxExtension.class) public class HelloVerticleTest { @Test @DisplayName("Test that verticle is up and respond me by 'Hello' word and doc in OpenAPI format") public void testHelloVerticle(Vertx vertx, VertxTestContext testContext) { WebClient webClient = WebClient.create(vertx); Checkpoint deploymentCheckpoint = testContext.checkpoint(); Checkpoint requestCheckpoint = testContext.checkpoint(2); HelloVerticle verticle = spy(new HelloVerticle()); JsonObject config = new JsonObject().put("serverPort", 8081).put("serverHost", "0.0.0.0"); doReturn(config).when(verticle).config(); vertx.deployVerticle(verticle, testContext.succeeding(id -> { deploymentCheckpoint.flag(); // test GET / webClient.get(8081, "localhost", "/") .as(BodyCodec.string()) .send(testContext.succeeding(resp -> { assertThat(resp.body()).isEqualTo("Hello"); assertThat(resp.statusCode()).isEqualTo(200); requestCheckpoint.flag(); })); // test GET /doc webClient.get(8081, "localhost", "/doc") .as(BodyCodec.string()) .send(testContext.succeeding(resp -> { try { assertThat(resp.body()).isEqualTo(IOUtils.toString(this.getClass() .getResourceAsStream("../../../doc.yaml"), "UTF-8")); assertThat(resp.statusCode()).isEqualTo(200); requestCheckpoint.flag(); } catch (Exception e) { requestCheckpoint.flag(); testContext.failNow(e); } })); })); } } 

Saatnya membangun layanan bersama dengan tes:


 mvn clean package 

Kami sangat tertarik dengan log plugin yang pasti, ini akan terlihat seperti ini (gambar dapat diklik):



Wow! Layanan ini dirakit, tes berjalan dan tidak macet (beberapa saat kemudian kita akan berbicara tentang keindahan bagaimana hasil tes ditampilkan kepada pihak berwenang), sekarang saatnya untuk berpikir tentang bagaimana kami akan mengirimkannya kepada pengguna (yaitu, ke server). Di halaman, akhir 2019, dan, tentu saja, kami akan menggabungkan aplikasi dalam bentuk gambar buruh pelabuhan. Ayo pergi!


Docker dan semuanya


Kami akan membangun gambar Docker untuk layanan pertama kami berdasarkan adoptopenjdk/openjdk11 . Tambahkan ke gambar dzharnik rakitan kami dengan semua konfigurasi yang diperlukan dan tulis dalam docker file perintah untuk memulai aplikasi dalam wadah. Dockerfile yang dihasilkan akan terlihat seperti ini:


 FROM adoptopenjdk/openjdk11:alpine-jre COPY target/hello-microservice-fat.jar app.jar COPY src/conf/config.json . COPY src/conf/logback-console.xml . COPY run.sh . RUN chmod +x run.sh CMD ["./run.sh"] 

Script run.sh terlihat seperti ini:


 #!/bin/sh java ${JVM_OPTS} -Dlogback.configurationFile=./logback-console.xml -jar app.jar -conf config.json 

Kami belum benar-benar membutuhkan variabel lingkungan JVM_OPTS pada tahap ini, tetapi beberapa saat kemudian kami akan secara aktif mengubahnya dan menyesuaikan parameter mesin virtual dan layanan kami. Saatnya merakit gambar dan menjalankan aplikasi dalam wadah:


 docker build -t="hellomicroservice" . docker run -dit --name helloms hellomicroservice #    ,      docker logs -f helloms #  docker logs 2019-10-05 14:55:46,059 [vert.x-worker-thread-0] DEBUG isvpOpenAPIV3Parser: Loaded raw data: openapi: 3.0.1 info: title: Hello ;) description: Hello microservice version: 1.0.0 servers: - url: https://demo1.bihero.io/api/hello tags: - name: hello description: Everything about saying 'Hello' paths: /: x-vertx-event-bus: address: service.hello timeout: 1000c get: tags: - hello summary: Get 'Hello' word operationId: getHelloWord responses: 200: description: OK /doc: x-vertx-event-bus: address: service.hello timeout: 1000c get: tags: - hello_doc summary: Get 'Hello' microservice documentation operationId: getDoc responses: 200: description: OK components: {} 2019-10-05 14:55:46,098 [vert.x-worker-thread-0] DEBUG isvpOpenAPIV3Parser: Parsed rootNode: {"openapi":"3.0.1","info":{"title":"Hello ;)","description":"Hello microservice","version":"1.0.0"},"servers":[{"url":"https://demo1.bihero.io/api/hello"}],"tags":[{"name":"hello","description":"Everything about saying 'Hello'"}],"paths":{"/":{"x-vertx-event-bus":{"address":"service.hello","timeout":"1000c"},"get":{"tags":["hello"],"summary":"Get 'Hello' word","operationId":"getHelloWord","responses":{"200":{"description":"OK"}}}},"/doc":{"x-vertx-event-bus":{"address":"service.hello","timeout":"1000c"},"get":{"tags":["hello_doc"],"summary":"Get 'Hello' microservice documentation","operationId":"getDoc","responses":{"200":{"description":"OK"}}}}},"components":{}} Oct 05, 2019 2:55:46 PM io.vertx.core.impl.launcher.commands.VertxIsolatedDeployer INFO: Succeeded in deploying verticle 

Kami mendapatkan alamat ip wadah dan memeriksa operasi layanan di dalam wadah:


 docker inspect helloms | grep IPAddress "SecondaryIPAddresses": null, "IPAddress": "172.17.0.2", "IPAddress": "172.17.0.2", 

 curl http://172.17.0.2:8081/ #     'Hello'   curl http://172.17.0.2:8081/doc #       OpenAPI 

Jadi, layanan dimulai dalam wadah. Tapi kami tidak akan menjalankannya dengan tangan seperti ini (buruh pelabuhan) dalam lingkungan produksi, untuk ini kami memiliki kubernet yang bagus. Untuk menjalankan aplikasi di kubernetes, kita memerlukan templat, file yml, dengan deskripsi sumber daya apa (penyebaran, layanan, masuknya, dll) yang akan kita jalankan dan berdasarkan pada wadah mana. Tapi, sebelum kita mulai menjelaskan templat untuk meluncurkan aplikasi dalam k8s, dorong gambar yang telah dirakit sebelumnya ke dockerhab:


 docker tag hello bihero/hello docker push bihero/hello 

Kami menulis templat untuk meluncurkan aplikasi di kubernetes (dalam kerangka artikel ini, kami bukan tukang las nyata dan tidak berpura-pura menjadi templat “halal”):


 apiVersion: extensions/v1beta1 kind: Deployment metadata: labels: io.bihero.hello.service: bihero-hello name: bihero-hello spec: replicas: 3 strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 maxUnavailable: 1 template: metadata: labels: io.bihero.hello.service: bihero-hello spec: containers: - image: bihero/hello:${HELLO_SERVICE_IMAGE_VERSION} name: bihero-hello ports: - containerPort: 8081 imagePullPolicy: Always resources: {} restartPolicy: Always --- apiVersion: v1 kind: Service metadata: labels: io.bihero.hello.service: bihero-hello name: bihero-hello spec: ports: - name: "8081" port: 8081 targetPort: 8081 selector: io.bihero.hello.service: bihero-hello status: loadBalancer: {} --- apiVersion: extensions/v1beta1 kind: Ingress metadata: name: bihero-hello annotations: kubernetes.io/ingress.class: nginx nginx.ingress.kubernetes.io/ssl-redirect: "true" nginx.ingress.kubernetes.io/secure-backends: "false" nginx.ingress.kubernetes.io/ssl-passthrough: "false" nginx.ingress.kubernetes.io/rewrite-target: /$2 kubernetes.io/tls-acme: "true" namespace: default spec: tls: - hosts: - ${ID_DOMAIN} secretName: bihero rules: - host: ${ID_DOMAIN} http: paths: - path: /api/hello(/|$)(.*) backend: serviceName: bihero-hello servicePort: 8081 

Secara singkat tentang apa yang kita lihat di templat:


  • Penempatan : di sini kami menggambarkan dari gambar mana kami menyebarkan dan dari berapa banyak contoh kami membuat set replika untuk layanan kami. Penting juga untuk memperhatikan metadata.labels - pada mereka kami akan mengikat Layanan ke Penempatan
  • Layanan : kami mengikat layanan ke penyebaran / replaset. Intinya, layanan di k8s adalah sesuatu yang sudah bisa Anda kirim permintaan http ke dalam sebuah cluster (dan ya - perhatikan pemilih )
  • Ingress : ingress diperlukan untuk mengekspos layanan ke dunia luar. Kami akan membungkus semua permintaan yang dimulai dengan / api / halo pada layanan halo kami ( https://domain.com/api/hello -> http: //bihero-hello.service.internal.domain.local: 8081 / )

Template juga mencakup dua variabel lingkungan:


  • $ {HELLO_SERVICE_IMAGE_VERSION} - tag gambar buruh pelabuhan dengan layanan dari mana kami akan mengumpulkan penyebaran pertama kami
  • $ {ID_DOMAIN} - domain tempat kami akan menggunakan layanan kami

Yang penting tentang https
Cluster uji sudah memiliki rahasia bernama bihero , dibuat berdasarkan sertifikat wildcard dari LetsEncrypt. Singkatnya, tim terlihat seperti ini
 kubectl create secret tls bihero --key keys/privkey.pem --cert keys/fullchain.pem 


di mana privkey.pem dan fullchain.pem adalah file yang dihasilkan oleh letsencrypt
Anda dapat membaca lebih lanjut tentang cara membuat rahasia untuk tls di k8s dengan mengikuti tautan

Inilah saatnya untuk mencoba menggunakan di k8 :) Ayo!


 export HELLO_SERVICE_IMAGE_VERSION=latest export ID_DOMAIN=demo1.bihero.io cat k8s.yaml | envsubst | kubectl apply -f - 

Di stdout Anda akan melihat ini:


 deployment.extensions/bihero-hello created service/bihero-hello created ingress.extensions/bihero-hello created 

Baiklah, mari kita periksa apakah kubernet membawa kita ke sana:


 kubectl get po # ,  pod   po, k8s   


Seperti yang diharapkan - 3 perapian


Mari kita lihat detail satu perapian


 kubectl describe po bihero-hello-5b4759d55b-bf4qc 


Bagaimana layanannya?


 kubectl describe service bihero-hello 


Bagaimana dengan ingress?


 kubectl describe ing bihero-hello 


Wow! Layanan ini berjalan dalam k8s dan meminta untuk diperiksa dengan beberapa permintaan, sesuai dengan spesifikasi.


 curl https://demo1.bihero.io/api/hello Hello 

 curl https://demo1.bihero.io/api/hello/doc openapi: 3.0.1 info: title: Hello ;) description: Hello microservice version: 1.0.0 servers: - url: https://demo1.bihero.io/api/hello tags: - name: hello description: Everything about saying 'Hello' paths: /: x-vertx-event-bus: address: service.hello timeout: 1000c get: tags: - hello summary: Get 'Hello' word operationId: getHelloWord responses: 200: description: OK /doc: x-vertx-event-bus: address: service.hello timeout: 1000c get: tags: - hello_doc summary: Get 'Hello' microservice documentation operationId: getDoc responses: 200: description: OK components: {} 

A - Otomasi


Fiuh ... Kami mencapai yang paling lezat dan menyenangkan. Banyak pekerjaan telah dilakukan dan setiap langkah disertai dengan peluncuran manual beberapa alat, pada setiap tahap mereka sendiri. , , k8s . , !


, , CI-.


?


  1. , , ( ), git-
  2. (mvn), (surefire, allure) — fat-jar
  3. docker- (docker build)
  4. Push docker- ( docker registry) (docker push)
  5. k8s (kubectl apply)

CI- ?


, ( ), . :

:


  1. , master ( master , , , merge' merge request' )
  2. , dev- (telegram-bot)
  3. ,
  4. — maven repository ( nexus blob store )
  5. fat-jar (mvn package, , — )
  6. docker image . , , , ( ). registry k8s
  7. k8s
  8. , k8s .
  9. 4- , , maven repository
  10. ,

Concourse CI

CI- Concourse . Concourse CI:


  • UI ( yaml-, , fly ): — , (mvn, docker, fly, kubectl), , ( tg- )
  • docker container', ( worker- , - environment- ) — , , .

, , :


pipeline.yaml
 resource_types: - name: telegram type: docker-image source: repository: vtutrinov/concourse-telegram-resource tag: latest - name: kubernetes type: docker-image source: repository: zlabjp/kubernetes-resource tag: 1.16 - name: metadata type: docker-image source: repository: olhtbr/metadata-resource tag: 2.0.1 resources: - name: metadata type: metadata - name: sources type: git source: branch: master uri: git@github.com:bihero-io/hello-microservice.git private_key: ((deployer-private-key)) - name: docker-image type: docker-image source: repository: bihero/hello username: ((docker-registry-user)) password: ((docker-registry-password)) - name: telegram type: telegram source: bot_token: ((telegram-ci-bot-token)) chat_id: ((telegram-group-to-report-build)) ci_url: ((ci_url)) command: "/build_hello_ms" - name: kubernetes-demo type: kubernetes source: server: https://178.63.194.241:6443 namespace: default kubeconfig: ((kubeconfig-demo)) jobs: - name: build-hello-microservice serial: true public: true plan: - in_parallel: - get: sources trigger: true - get: telegram trigger: true - put: metadata - put: telegram params: status: Build In Progress - task: unit-tests config: platform: linux image_resource: type: docker-image source: repository: ((docker-registry-uri))/bih/maven tag: 3-jdk-11 username: ((docker-private-registry-user)) password: ((docker-private-registry-password)) inputs: - name: sources outputs: - name: tested-workspace run: path: /bin/sh args: - -c - | output_dir=tested-workspace cp -R ./sources/* "${output_dir}/" mvn -f "${output_dir}/pom.xml" clean test caches: - path: ~/.m2/ on_failure: do: - task: tests-report config: platform: linux image_resource: type: docker-image source: repository: ((docker-registry-uri))/bih/maven tag: 3-jdk-11 username: ((docker-private-registry-user)) password: ((docker-private-registry-password)) inputs: - name: tested-workspace outputs: - name: message run: path: /bin/sh args: - -c - | output_dir=tested-workspace mvn -Dmaven.wagon.http.ssl.insecure=true -Dmaven.wagon.http.ssl.allowall=true -f "${output_dir}/pom.xml" site-deploy version=$(cat $output_dir/target/classes/version.txt) cat >message/msg <<EOL <a href="https://nexus.dev.techedge.pro:8443/repository/reports/hello-microservice/${version}/allure/">Allure report</a> EOL caches: - path: ~/.m2/ - put: telegram params: status: Build Failed (unit-tests) message_file: message/msg - task: tests-report config: platform: linux image_resource: type: docker-image source: repository: ((docker-registry-uri))/bih/maven tag: 3-jdk-11 username: ((docker-private-registry-user)) password: ((docker-private-registry-password)) inputs: - name: tested-workspace outputs: - name: message - name: tested-workspace run: path: /bin/sh args: - -c - | work_dir=tested-workspace mvn -Dmaven.wagon.http.ssl.insecure=true -Dmaven.wagon.http.ssl.allowall=true -f "${work_dir}/pom.xml" site-deploy version=$(cat $work_dir/target/classes/version.txt) cat >message/msg <<EOL <a href="https://nexus.dev.techedge.pro:8443/repository/reports/hello-microservice/${version}/allure/">Allure report</a> EOL caches: - path: ~/.m2/ - task: package config: platform: linux image_resource: type: docker-image source: repository: ((docker-registry-uri))/bih/maven tag: 3-jdk-11 username: ((docker-private-registry-user)) password: ((docker-private-registry-password)) inputs: - name: tested-workspace - name: metadata outputs: - name: app-packaged-workspace - name: metadata run: path: /bin/sh args: - -c - | output_dir=app-packaged-workspace cp -R ./tested-workspace/* "${output_dir}/" mvn -f "${output_dir}/pom.xml" package -Dmaven.main.skip -DskipTests env tag="-"$(cat metadata/build_name) echo $tag >> ${output_dir}/target/classes/version.txt cat ${output_dir}/target/classes/version.txt > metadata/version caches: - path: ~/.m2/ on_failure: do: - put: telegram params: status: Build Failed (package) - put: docker-image params: build: app-packaged-workspace tag_file: app-packaged-workspace/target/classes/version.txt tag_as_latest: true get_params: skip_download: true - task: make-k8s-app-template config: platform: linux image_resource: type: docker-image source: repository: bhgedigital/envsubst inputs: - name: sources - name: metadata outputs: - name: k8s run: path: /bin/sh args: - -c - | export DOMAIN=demo1.bihero.io export HELLO_SERVICE_IMAGE_VERSION=$(cat metadata/version) cat sources/k8s.yaml | envsubst > k8s/hello_app_template.yaml cat k8s/hello_app_template.yaml - put: kubernetes-demo params: kubectl: apply -f k8s/hello_app_template.yaml - put: telegram params: status: Build Success message_file: message/msg 

:


  1. resource_types , , . ( , docker-, ): telegram tg- , kubernetes k8s- metadata ( , ..)
  2. resources , . , , docker-registry docker- , . input- ,
  3. jobs , . put- tg-. — get (, git-), — put — (docker image) , (metadata). task — docker- docker-image', image_resource
  4. ((parameter-name)) — , , , ( docker-registry).

:


 fly -t bih sp -p hello-microservice -c pipeline.yaml -l credentials.yaml # -t - target name # sp - alias to set-pipeline # -p - pipeline name # -c - pipeline config file # -l - file with parameters and credentials 

credentials.yaml :


 docker-registry-user: <dockerhub-user> docker-registry-password: <dockerhub-password> docker-registry-uri: <private-docker-registry-url> docker-private-registry-user: <private-docker-registry-user> docker-private-registry-password: <private-docker-registry-passwordl> telegram-ci-bot-token: <telegram-bot-token> telegram-group-to-report-build: <telegram-group-id> ci_url: <ci-server-url> deployer-private-key: | -----BEGIN OPENSSH PRIVATE KEY----- github-deploy-key -----END OPENSSH PRIVATE KEY----- kubeconfig-demo: | apiVersion: v1 clusters: - cluster: certificate-authority-data: <kube-cert-data> server: <kube-api-server-url> name: kubernetes contexts: - context: cluster: kubernetes user: kubernetes-admin name: kubernetes-admin@kubernetes current-context: kubernetes-admin@kubernetes kind: Config preferences: {} users: - name: kubernetes-admin user: client-certificate-data: <kube-client-cert-data> client-key-data: <kube-client-key-data> 

. :


  1. CI-, :
  2. ( 1), c fly , CI-:
     fly -t bih tj -j hello-microservice/build-hello-microservice -w # tj - alias for 'trigger-job' # -j - job (<piprlinr-name>/<job-name-in-pipeline>) # -w - watch 
  3. /build_hello_ms -, telegram-group-to-report-build credentials.yaml
  4. master- (, , : master — , — ;) )

( ) -:


  1. :
  2. :

    , UI CI-:

Hore! , - , , k8s . :


  1. docker-hub'

  2. , 2-
  3. :

curl https://demo1.bihero.io/api/hello -v
 curl https://demo1.bihero.io/api/hello -v    5350  14:59:04  * Trying 178.63.194.243... * TCP_NODELAY set * Connected to demo1.bihero.io (178.63.194.243) port 443 (#0) * ALPN, offering h2 * ALPN, offering http/1.1 * Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH * successfully set certificate verify locations: * CAfile: /etc/ssl/certs/ca-certificates.crt CApath: /etc/ssl/certs * TLSv1.2 (OUT), TLS header, Certificate Status (22): * TLSv1.2 (OUT), TLS handshake, Client hello (1): * TLSv1.2 (IN), TLS handshake, Server hello (2): * TLSv1.2 (IN), TLS handshake, Certificate (11): * TLSv1.2 (IN), TLS handshake, Server key exchange (12): * TLSv1.2 (IN), TLS handshake, Server finished (14): * TLSv1.2 (OUT), TLS handshake, Client key exchange (16): * TLSv1.2 (OUT), TLS change cipher, Client hello (1): * TLSv1.2 (OUT), TLS handshake, Finished (20): * TLSv1.2 (IN), TLS change cipher, Client hello (1): * TLSv1.2 (IN), TLS handshake, Finished (20): * SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384 * ALPN, server accepted to use h2 * Server certificate: * subject: CN=*.bihero.io * start date: Nov 7 13:59:46 2019 GMT * expire date: Feb 5 13:59:46 2020 GMT * subjectAltName: host "demo1.bihero.io" matched cert's "*.bihero.io" * issuer: C=US; O=Let's Encrypt; CN=Let's Encrypt Authority X3 * SSL certificate verify ok. * Using HTTP2, server supports multi-use * Connection state changed (HTTP/2 confirmed) * Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0 * Using Stream ID: 1 (easy handle 0x55778f779520) > GET /api/hello HTTP/1.1 > Host: demo1.bihero.io > User-Agent: curl/7.52.1 > Accept: */* > * Connection state changed (MAX_CONCURRENT_STREAMS updated)! < HTTP/2 200 < server: nginx/1.15.8 < date: Sun, 01 Dec 2019 11:59:06 GMT < content-type: text/plain < content-length: 5 < strict-transport-security: max-age=15724800; includeSubDomains < * Curl_http_done: called premature == 0 * Connection #0 to host demo1.bihero.io left intact Hello 

, , . , . , ( testcontainers). TODO- ( ). !


'World' microservice


Service specification
 openapi: 3.0.1 info: title: World ;) description: "'World' word microservice" version: 1.0.0 servers: - url: https://demo1.bihero.io/api/world tags: - name: world description: Everything about 'World' word paths: /: x-vertx-event-bus: address: service.world timeout: 1000 get: tags: - world summary: Get 'World' word operationId: getWorldWord responses: 200: description: OK content: {} /doc: x-vertx-event-bus: address: service.world timeout: 1000c get: tags: - world summary: Get 'World' microservice documentation operationId: getDoc responses: 200: description: OK components: {} 

pom.xml
 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <properties> <main.verticle>io.bihero.world.WorldVerticle</main.verticle> <vertx.version>3.8.1</vertx.version> <logback.version>1.2.3</logback.version> <junit-jupiter.version>5.3.1</junit-jupiter.version> <maven-surefire-plugin.version>2.19.1</maven-surefire-plugin.version> <junit-platform-surefire-provider.version>1.1.0</junit-platform-surefire-provider.version> <assertj-core.version>3.8.0</assertj-core.version> <allure.version>2.8.1</allure.version> <allure-maven.version>2.10.0</allure-maven.version> <aspectj.version>1.9.2</aspectj.version> <mockito.version>2.21.0</mockito.version> <rest-assured.version>3.0.0</rest-assured.version> </properties> <groupId>io.bihero</groupId> <artifactId>world-microservice</artifactId> <version>1.0.0-SNAPSHOT</version> <build> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <configuration> ```11</source> <target>11</target> </configuration> <executions> <execution> <id>default-compile</id> <configuration> <annotationProcessors> <annotationProcessor>io.vertx.codegen.CodeGenProcessor</annotationProcessor> </annotationProcessors> <generatedSourcesDirectory>src/main/generated</generatedSourcesDirectory> <compilerArgs> <arg>-Acodegen.output=${project.basedir}/src/main</arg> </compilerArgs> </configuration> </execution> <execution> <id>default-testCompile</id> <configuration> <annotationProcessors> <annotationProcessor>io.vertx.codegen.CodeGenProcessor</annotationProcessor> </annotationProcessors> <generatedTestSourcesDirectory>src/test/generated</generatedTestSourcesDirectory> <compilerArgs> <arg>-Acodegen.output=${project.basedir}/src/test</arg> </compilerArgs> </configuration> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>${maven-surefire-plugin.version}</version> <configuration> <properties> <property> <name>listener</name> <value>io.qameta.allure.junit5.AllureJunit5</value> </property> </properties> <includes> <include>**/*Test*.java</include> </includes> <argLine> -javaagent:"${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar" -Djdk.net.URLClassPath.disableClassPathURLCheck=true </argLine> <systemProperties> <property> <name>allure.results.directory</name> <value>${project.basedir}/target/allure-results</value> </property> <property> <name>junit.jupiter.extensions.autodetection.enabled</name> <value>true</value> </property> </systemProperties> <reportFormat>plain</reportFormat> </configuration> <dependencies> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>${aspectj.version}</version> </dependency> <dependency> <groupId>org.junit.platform</groupId> <artifactId>junit-platform-surefire-provider</artifactId> <version>${junit-platform-surefire-provider.version}</version> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> <version>${junit-jupiter.version}</version> </dependency> </dependencies> </plugin> <plugin> <groupId>io.qameta.allure</groupId> <artifactId>allure-maven</artifactId> <version>${allure-maven.version}</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-site-plugin</artifactId> <version>3.7.1</version> <dependencies> <dependency> <groupId>org.apache.maven.wagon</groupId> <artifactId>wagon-webdav-jackrabbit</artifactId> <version>2.8</version> </dependency> </dependencies> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-project-info-reports-plugin</artifactId> <version>3.0.0</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>2.3</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <manifestEntries> <Main-Class>io.vertx.core.Launcher</Main-Class> <Main-Verticle>${main.verticle}</Main-Verticle> </manifestEntries> </transformer> <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> <resource>META-INF/services/io.vertx.core.spi.VerticleFactory</resource> </transformer> </transformers> <artifactSet> </artifactSet> <outputFile>${project.build.directory}/${project.artifactId}-fat.jar</outputFile> </configuration> </execution> </executions> </plugin> </plugins> <resources> <resource> <directory>src/main/resources</directory> <filtering>true</filtering> <includes> <include>**/version.txt</include> </includes> </resource> <resource> <directory>src/main/resources</directory> <filtering>false</filtering> <excludes> <exclude>**/version.txt</exclude> </excludes> </resource> </resources> </build> <distributionManagement> <site> <id>reports</id> <url>dav:https://nexus.dev.techedge.pro:8443/repository/reports/${project.artifactId}/</url> </site> </distributionManagement> <reporting> <excludeDefaults>true</excludeDefaults> <plugins> <plugin> <groupId>io.qameta.allure</groupId> <artifactId>allure-maven</artifactId> <configuration> <resultsDirectory>${project.build.directory}/allure-results</resultsDirectory> <reportDirectory>${project.reporting.outputDirectory}/${project.version}/allure</reportDirectory> </configuration> </plugin> </plugins> </reporting> <dependencies> <dependency> <groupId>io.vertx</groupId> <artifactId>vertx-web-api-service</artifactId> <version>${vertx.version}</version> </dependency> <dependency> <groupId>io.vertx</groupId> <artifactId>vertx-codegen</artifactId> <version>${vertx.version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>${logback.version}</version> </dependency> <!-- test &ndash;&gt;--> <dependency> <groupId>io.vertx</groupId> <artifactId>vertx-unit</artifactId> <version>${vertx.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>io.vertx</groupId> <artifactId>vertx-junit5</artifactId> <version>${vertx.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>${junit-jupiter.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> <version>${junit-jupiter.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.assertj</groupId> <artifactId>assertj-core</artifactId> <version>${assertj-core.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>${mockito.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>io.qameta.allure</groupId> <artifactId>allure-junit5</artifactId> <version>${allure.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>io.vertx</groupId> <artifactId>vertx-web-client</artifactId> <version>${vertx.version}</version> <scope>test</scope> </dependency> </dependencies> </project> 

WorldService.java
 package io.bihero.world; import io.vertx.core.AsyncResult; import io.vertx.core.Handler; import io.vertx.core.Vertx; import io.vertx.ext.web.api.OperationRequest; import io.vertx.ext.web.api.OperationResponse; import io.vertx.ext.web.api.generator.WebApiServiceGen; @WebApiServiceGen public interface WorldService { static WorldService create(Vertx vertx) { return new DefaultWorldService(vertx); } void getWorldWord(OperationRequest context, Handler<AsyncResult<OperationResponse>> resultHandler); void getDoc(OperationRequest context, Handler<AsyncResult<OperationResponse>> resultHandler); } 

DefaultWorldService.java
 package io.bihero.world; import io.vertx.core.AsyncResult; import io.vertx.core.Future; import io.vertx.core.Handler; import io.vertx.core.Vertx; import io.vertx.core.buffer.Buffer; import io.vertx.ext.web.api.OperationRequest; import io.vertx.ext.web.api.OperationResponse; public class DefaultWorldService implements WorldService { private final Vertx vertx; public DefaultWorldService(Vertx vertx) { this.vertx = vertx; } public void getWorldWord(OperationRequest context, Handler<AsyncResult<OperationResponse>> resultHandler) { resultHandler.handle(Future.succeededFuture(OperationResponse.completedWithPlainText(Buffer.buffer("World")))); } @Override public void getDoc(OperationRequest context, Handler<AsyncResult<OperationResponse>> resultHandler) { vertx.fileSystem().readFile("doc.yaml", buffResult -> resultHandler.handle(Future.succeededFuture( OperationResponse.completedWithPlainText(buffResult.result())) )); } } 

WorldVerticle.java
 package io.bihero.world; import io.vertx.core.AbstractVerticle; import io.vertx.core.Promise; import io.vertx.core.eventbus.MessageConsumer; import io.vertx.core.http.HttpServer; import io.vertx.core.http.HttpServerOptions; import io.vertx.core.json.JsonObject; import io.vertx.ext.web.Router; import io.vertx.ext.web.api.contract.openapi3.OpenAPI3RouterFactory; import io.vertx.serviceproxy.ServiceBinder; public class WorldVerticle extends AbstractVerticle { HttpServer server; MessageConsumer<JsonObject> consumer; public void startWorldService() { consumer = new ServiceBinder(vertx).setAddress("service.world") .register(WorldService.class, WorldService.create(getVertx())); } /** * This method constructs the router factory, mounts services and handlers and starts the http server * with built router * @return */ private Promise<Void> startHttpServer() { Promise<Void> promise = Promise.promise(); OpenAPI3RouterFactory.create(this.vertx, "/doc.yaml", openAPI3RouterFactoryAsyncResult -> { if (openAPI3RouterFactoryAsyncResult.succeeded()) { OpenAPI3RouterFactory routerFactory = openAPI3RouterFactoryAsyncResult.result(); // Mount services on event bus based on extensions routerFactory.mountServicesFromExtensions(); // Generate the router Router router = routerFactory.getRouter(); int port = config().getInteger("serverPort", 8080); String host = config().getString("serverHost", "localhost"); server = vertx.createHttpServer(new HttpServerOptions().setPort(port).setHost(host)); server.requestHandler(router).listen(ar -> { // Error starting the HttpServer if (ar.succeeded()) promise.complete(); else promise.fail(ar.cause()); }); } else { // Something went wrong during router factory initialization promise.fail(openAPI3RouterFactoryAsyncResult.cause()); } }); return promise; } @Override public void start(Promise<Void> promise) { startWorldService(); startHttpServer().future().setHandler(promise); } /** * This method closes the http server and unregister all services loaded to Event Bus */ @Override public void stop(){ this.server.close(); consumer.unregister(); } } 

WorldServiceTest.java
 package io.bihero.world; import io.vertx.core.Vertx; import io.vertx.core.json.JsonObject; import io.vertx.ext.web.api.OperationRequest; import io.vertx.junit5.VertxExtension; import io.vertx.junit5.VertxTestContext; import org.apache.commons.io.IOUtils; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import java.io.IOException; import static org.assertj.core.api.Assertions.assertThat; @ExtendWith(VertxExtension.class) public class WorldServiceTest { private WorldService worldService = WorldService.create(Vertx.vertx()); @Test @DisplayName("Test 'getWorldWord' method returns 'World' word") public void testHelloMethod(VertxTestContext testContext) { worldService.getWorldWord(new OperationRequest(new JsonObject()), testContext.succeeding(it -> { assertThat(it.getStatusCode()).isEqualTo(200); assertThat(it.getPayload().toString()).isEqualTo("World"); testContext.completeNow(); })); } @Test @DisplayName("Test 'getDoc' method returns service documentation in OpenAPI format") public void testDocMethod(VertxTestContext testContext) { worldService.getDoc(new OperationRequest(new JsonObject()), testContext.succeeding(it -> { try { assertThat(it.getStatusCode()).isEqualTo(200); assertThat(it.getPayload().toString()).isEqualTo(IOUtils.toString(this.getClass() .getResourceAsStream("../../../doc.yaml"), "UTF-8")); testContext.completeNow(); } catch (IOException e) { testContext.failNow(e); } })); } } 

WorldVerticleTest.java
 package io.bihero.world; import io.vertx.core.Vertx; import io.vertx.core.json.JsonObject; import io.vertx.ext.web.client.WebClient; import io.vertx.ext.web.codec.BodyCodec; import io.vertx.junit5.Checkpoint; import io.vertx.junit5.VertxExtension; import io.vertx.junit5.VertxTestContext; import org.apache.commons.io.IOUtils; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; @ExtendWith(VertxExtension.class) public class WorldVerticleTest { @Test @DisplayName("Test that verticle is up and respond me by 'World' word and doc in OpenAPI format") public void testHelloVerticle(Vertx vertx, VertxTestContext testContext) { WebClient webClient = WebClient.create(vertx); Checkpoint deploymentCheckpoint = testContext.checkpoint(); Checkpoint requestCheckpoint = testContext.checkpoint(2); WorldVerticle verticle = spy(new WorldVerticle()); JsonObject config = new JsonObject().put("serverPort", 8082).put("serverHost", "0.0.0.0"); doReturn(config).when(verticle).config(); vertx.deployVerticle(verticle, testContext.succeeding(id -> { deploymentCheckpoint.flag(); // test GET / webClient.get(8082, "localhost", "/") .as(BodyCodec.string()) .send(testContext.succeeding(resp -> { assertThat(resp.body()).isEqualTo("World"); assertThat(resp.statusCode()).isEqualTo(200); requestCheckpoint.flag(); })); // test GET /doc webClient.get(8082, "localhost", "/doc") .as(BodyCodec.string()) .send(testContext.succeeding(resp -> { try { assertThat(resp.body()).isEqualTo(IOUtils.toString(this.getClass() .getResourceAsStream("../../../doc.yaml"), "UTF-8")); assertThat(resp.statusCode()).isEqualTo(200); requestCheckpoint.flag(); } catch (Exception e) { requestCheckpoint.flag(); testContext.failNow(e); } })); })); } } 

Dockerfile
 FROM adoptopenjdk/openjdk11:alpine-jre COPY target/world-microservice-fat.jar app.jar COPY src/conf/config.json . COPY src/conf/logback-console.xml . COPY run.sh . RUN chmod +x run.sh CMD ["./run.sh"] 

run.sh
 #!/bin/sh java ${JVM_OPTS} -Dlogback.configurationFile=./logback-console.xml -jar app.jar -conf config.json 

pipeline.yaml
 resource_types: - name: telegram type: docker-image source: repository: vtutrinov/concourse-telegram-resource tag: latest - name: kubernetes type: docker-image source: repository: zlabjp/kubernetes-resource tag: 1.16 - name: metadata type: docker-image source: repository: olhtbr/metadata-resource tag: 2.0.1 resources: - name: metadata type: metadata - name: sources type: git source: branch: master uri: git@github.com:bihero-io/worldmicroservice.git private_key: ((deployer-private-key)) - name: docker-image type: docker-image source: repository: bihero/world username: ((docker-registry-user)) password: ((docker-registry-password)) - name: telegram type: telegram source: bot_token: ((telegram-ci-bot-token)) chat_id: ((telegram-group-to-report-build)) ci_url: ((ci_url)) command: "/build_world_ms" - name: kubernetes-demo type: kubernetes source: server: ((k8s-api-server)) namespace: default kubeconfig: ((kubeconfig-demo)) jobs: - name: build-world-microservice serial: true public: true plan: - in_parallel: - get: sources trigger: true - get: telegram trigger: true - put: metadata - put: telegram params: status: Build In Progress - task: unit-tests config: platform: linux image_resource: type: docker-image source: repository: ((docker-registry-uri))/bih/maven-dind tag: 3-jdk-11 username: ((docker-private-registry-user)) password: ((docker-private-registry-password)) inputs: - name: sources outputs: - name: tested-workspace run: path: /bin/sh args: - -c - | output_dir=tested-workspace cp -R ./sources/* "${output_dir}/" mvn -f "${output_dir}/pom.xml" clean test caches: - path: ~/.m2/ on_failure: do: - task: tests-report config: platform: linux image_resource: type: docker-image source: repository: ((docker-registry-uri))/bih/maven-dind tag: 3-jdk-11 username: ((docker-private-registry-user)) password: ((docker-private-registry-password)) inputs: - name: tested-workspace outputs: - name: message run: path: /bin/sh args: - -c - | output_dir=tested-workspace mvn -Dmaven.wagon.http.ssl.insecure=true -Dmaven.wagon.http.ssl.allowall=true -f "${output_dir}/pom.xml" site-deploy version=$(cat $output_dir/target/classes/version.txt) cat >message/msg <<EOL <a href="https://nexus.dev.techedge.pro:8443/repository/reports/hello-microservice/${version}/allure/">Allure report</a> EOL caches: - path: ~/.m2/ - put: telegram params: status: Build Failed (unit-tests) message_file: message/msg - task: tests-report config: platform: linux image_resource: type: docker-image source: repository: ((docker-registry-uri))/bih/maven-dind tag: 3-jdk-11 username: ((docker-private-registry-user)) password: ((docker-private-registry-password)) inputs: - name: tested-workspace outputs: - name: message - name: tested-workspace run: path: /bin/sh args: - -c - | work_dir=tested-workspace mvn -Dmaven.wagon.http.ssl.insecure=true -Dmaven.wagon.http.ssl.allowall=true -f "${work_dir}/pom.xml" site-deploy version=$(cat $work_dir/target/classes/version.txt) cat >message/msg <<EOL <a href="https://nexus.dev.techedge.pro:8443/repository/reports/world-microservice/${version}/allure/">Allure report</a> EOL caches: - path: ~/.m2/ - task: package config: platform: linux image_resource: type: docker-image source: repository: ((docker-registry-uri))/bih/maven-dind tag: 3-jdk-11 username: ((docker-private-registry-user)) password: ((docker-private-registry-password)) inputs: - name: tested-workspace - name: metadata outputs: - name: app-packaged-workspace - name: metadata run: path: /bin/sh args: - -c - | output_dir=app-packaged-workspace cp -R ./tested-workspace/* "${output_dir}/" mvn -f "${output_dir}/pom.xml" package -Dmaven.main.skip -DskipTests tag="-"$(cat metadata/build_name) echo $tag >> ${output_dir}/target/classes/version.txt cat ${output_dir}/target/classes/version.txt > metadata/version caches: - path: ~/.m2/ on_failure: do: - put: telegram params: status: Build Failed (package) - put: docker-image params: build: app-packaged-workspace tag_file: app-packaged-workspace/target/classes/version.txt tag_as_latest: true get_params: skip_download: true - task: make-k8s-app-template config: platform: linux image_resource: type: docker-image source: repository: bhgedigital/envsubst inputs: - name: sources - name: metadata outputs: - name: k8s run: path: /bin/sh args: - -c - | export DOMAIN=demo1.bihero.io export WORLD_SERVICE_IMAGE_VERSION=$(cat metadata/version) cat sources/k8s.yaml | envsubst > k8s/world_app_template.yaml cat k8s/world_app_template.yaml - put: kubernetes-demo params: kubectl: apply -f k8s/world_app_template.yaml - put: telegram params: status: Build Success message_file: message/msg 

k8s app template
 apiVersion: extensions/v1beta1 kind: Deployment metadata: labels: io.bihero.hello.service: bihero-world name: bihero-world spec: replicas: 3 strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 maxUnavailable: 1 template: metadata: labels: io.bihero.hello.service: bihero-world spec: containers: - image: bihero/world:${WORLD_SERVICE_IMAGE_VERSION} name: bihero-world ports: - containerPort: 8082 imagePullPolicy: Always resources: {} restartPolicy: Always --- apiVersion: v1 kind: Service metadata: labels: io.bihero.hello.service: bihero-world name: bihero-world spec: ports: - name: "8082" port: 8082 targetPort: 8082 selector: io.bihero.hello.service: bihero-world status: loadBalancer: {} --- apiVersion: extensions/v1beta1 kind: Ingress metadata: name: bihero-world annotations: kubernetes.io/ingress.class: nginx nginx.ingress.kubernetes.io/ssl-redirect: "true" nginx.ingress.kubernetes.io/secure-backends: "false" nginx.ingress.kubernetes.io/ssl-passthrough: "false" nginx.ingress.kubernetes.io/rewrite-target: /$2 kubernetes.io/tls-acme: "true" namespace: default spec: tls: - hosts: - ${DOMAIN} secretName: bihero rules: - host: ${DOMAIN} http: paths: - path: /api/world(/|$)(.*) backend: serviceName: bihero-world servicePort: 8082 

'HelloWorld' microservice


, . , , . testcontainers, .


Service specification
 openapi: 3.0.1 info: title: Hello World ;) description: "Hello World microservice. Aggregate 'Hello World' by hellomicroservice and worldmicroservice" version: 1.0.0 servers: - url: https://demo1.bihero.io/api/helloworld tags: - name: helloworld description: Everything about 'Hello World' paths: /: x-vertx-event-bus: address: service.helloworld timeout: 1000 get: tags: - helloworld summary: Aggregate 'Hello World' operationId: getHelloWorld responses: 200: description: OK content: {} /doc: x-vertx-event-bus: address: service.helloworld timeout: 1000c get: tags: - world summary: Get 'Hello World' microservice documentation operationId: getDoc responses: 200: description: OK components: {} 

pom.xml
 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <properties> <main.verticle>io.bihero.helloworld.HelloWorldVerticle</main.verticle> <vertx.version>3.8.1</vertx.version> <logback.version>1.2.3</logback.version> <junit-jupiter.version>5.3.1</junit-jupiter.version> <maven-surefire-plugin.version>2.19.1</maven-surefire-plugin.version> <junit-platform-surefire-provider.version>1.1.0</junit-platform-surefire-provider.version> <assertj-core.version>3.8.0</assertj-core.version> <allure.version>2.8.1</allure.version> <allure-maven.version>2.10.0</allure-maven.version> <aspectj.version>1.9.2</aspectj.version> <mockito.version>2.21.0</mockito.version> <rest-assured.version>3.0.0</rest-assured.version> <testcontainers.version>1.12.3</testcontainers.version> </properties> <groupId>io.bihero</groupId> <artifactId>hello-world-microservice</artifactId> <version>1.0.0-SNAPSHOT</version> <build> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <configuration> ```11</source> <target>11</target> </configuration> <executions> <execution> <id>default-compile</id> <configuration> <annotationProcessors> <annotationProcessor>io.vertx.codegen.CodeGenProcessor</annotationProcessor> </annotationProcessors> <generatedSourcesDirectory>src/main/generated</generatedSourcesDirectory> <compilerArgs> <arg>-Acodegen.output=${project.basedir}/src/main</arg> </compilerArgs> </configuration> </execution> <execution> <id>default-testCompile</id> <configuration> <annotationProcessors> <annotationProcessor>io.vertx.codegen.CodeGenProcessor</annotationProcessor> </annotationProcessors> <generatedTestSourcesDirectory>src/test/generated</generatedTestSourcesDirectory> <compilerArgs> <arg>-Acodegen.output=${project.basedir}/src/test</arg> </compilerArgs> </configuration> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>${maven-surefire-plugin.version}</version> <configuration> <properties> <property> <name>listener</name> <value>io.qameta.allure.junit5.AllureJunit5</value> </property> </properties> <includes> <include>**/*Test.java</include> </includes> <argLine> -javaagent:"${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar" -Djdk.net.URLClassPath.disableClassPathURLCheck=true </argLine> <systemProperties> <property> <name>allure.results.directory</name> <value>${project.basedir}/target/allure-results</value> </property> <property> <name>junit.jupiter.extensions.autodetection.enabled</name> <value>true</value> </property> </systemProperties> <reportFormat>plain</reportFormat> </configuration> <dependencies> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>${aspectj.version}</version> </dependency> <dependency> <groupId>org.junit.platform</groupId> <artifactId>junit-platform-surefire-provider</artifactId> <version>${junit-platform-surefire-provider.version}</version> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> <version>${junit-jupiter.version}</version> </dependency> </dependencies> </plugin> <plugin> <groupId>io.qameta.allure</groupId> <artifactId>allure-maven</artifactId> <version>${allure-maven.version}</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-site-plugin</artifactId> <version>3.7.1</version> <dependencies> <dependency> <groupId>org.apache.maven.wagon</groupId> <artifactId>wagon-webdav-jackrabbit</artifactId> <version>2.8</version> </dependency> </dependencies> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-project-info-reports-plugin</artifactId> <version>3.0.0</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>2.3</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <manifestEntries> <Main-Class>io.vertx.core.Launcher</Main-Class> <Main-Verticle>${main.verticle}</Main-Verticle> </manifestEntries> </transformer> <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> <resource>META-INF/services/io.vertx.core.spi.VerticleFactory</resource> </transformer> </transformers> <artifactSet> </artifactSet> <outputFile>${project.build.directory}/${project.artifactId}-fat.jar</outputFile> </configuration> </execution> </executions> </plugin> </plugins> <resources> <resource> <directory>src/main/resources</directory> <filtering>true</filtering> <includes> <include>**/version.txt</include> </includes> </resource> <resource> <directory>src/main/resources</directory> <filtering>false</filtering> <excludes> <exclude>**/version.txt</exclude> </excludes> </resource> </resources> </build> <distributionManagement> <site> <id>reports</id> <url>dav:https://nexus.dev.techedge.pro:8443/repository/reports/${project.artifactId}/</url> </site> </distributionManagement> <reporting> <excludeDefaults>true</excludeDefaults> <plugins> <plugin> <groupId>io.qameta.allure</groupId> <artifactId>allure-maven</artifactId> <configuration> <resultsDirectory>${project.build.directory}/allure-results</resultsDirectory> <reportDirectory>${project.reporting.outputDirectory}/${project.version}/allure</reportDirectory> </configuration> </plugin> </plugins> </reporting> <dependencies> <dependency> <groupId>io.vertx</groupId> <artifactId>vertx-web-api-service</artifactId> <version>${vertx.version}</version> </dependency> <dependency> <groupId>io.vertx</groupId> <artifactId>vertx-web-client</artifactId> <version>${vertx.version}</version> </dependency> <dependency> <groupId>io.vertx</groupId> <artifactId>vertx-codegen</artifactId> <version>${vertx.version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>${logback.version}</version> </dependency> <!-- test &ndash;&gt;--> <dependency> <groupId>io.vertx</groupId> <artifactId>vertx-unit</artifactId> <version>${vertx.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>io.vertx</groupId> <artifactId>vertx-junit5</artifactId> <version>${vertx.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>${junit-jupiter.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> <version>${junit-jupiter.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.assertj</groupId> <artifactId>assertj-core</artifactId> <version>${assertj-core.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>${mockito.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>io.qameta.allure</groupId> <artifactId>allure-junit5</artifactId> <version>${allure.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.testcontainers</groupId> <artifactId>junit-jupiter</artifactId> <version>${testcontainers.version}</version> </dependency> </dependencies> </project> 

HelloWorldService.java
 package io.bihero.helloworld; import io.vertx.core.AsyncResult; import io.vertx.core.Handler; import io.vertx.core.Vertx; import io.vertx.core.json.JsonObject; import io.vertx.ext.web.api.OperationRequest; import io.vertx.ext.web.api.OperationResponse; import io.vertx.ext.web.api.generator.WebApiServiceGen; @WebApiServiceGen public interface HelloWorldService { static HelloWorldService create(Vertx vertx, JsonObject config) { return new DefaultHelloWorldService(vertx, config); } void getHelloWorld(OperationRequest context, Handler<AsyncResult<OperationResponse>> resultHandler); void getDoc(OperationRequest context, Handler<AsyncResult<OperationResponse>> resultHandler); } 

DefaultHelloWorldService.java
 package io.bihero.helloworld; import io.vertx.core.*; import io.vertx.core.buffer.Buffer; import io.vertx.core.json.JsonObject; import io.vertx.ext.web.api.OperationRequest; import io.vertx.ext.web.api.OperationResponse; import io.vertx.ext.web.client.WebClient; public class DefaultHelloWorldService implements HelloWorldService { private final Vertx vertx; private final JsonObject config; private final WebClient webClient; public DefaultHelloWorldService(Vertx vertx, JsonObject config) { this.vertx = vertx; this.config = config; this.webClient = WebClient.create(this.vertx); } @Override public void getHelloWorld(OperationRequest context, Handler<AsyncResult<OperationResponse>> resultHandler) { getHelloWord().compose(this::getHelloWorld).setHandler(v -> resultHandler.handle( Future.succeededFuture(OperationResponse.completedWithPlainText(Buffer.buffer(v.result()))) )); } @Override public void getDoc(OperationRequest context, Handler<AsyncResult<OperationResponse>> resultHandler) { vertx.fileSystem().readFile("doc.yaml", buffResult -> resultHandler.handle(Future.succeededFuture( OperationResponse.completedWithPlainText(buffResult.result())) )); } private Future<String> getHelloWord() { Future<String> future = Future.future(); webClient.get(config.getInteger("hello-service-port"), config.getString("hello-service-host"), "/").send(ar -> future.handle(Future.succeededFuture(ar.result().bodyAsString()))); return future; } private Future<String> getHelloWorld(String helloWord) { Future<String> future = Future.future(); webClient.get(config.getInteger("world-service-port"), config.getString("world-service-host"), "/").send(ar -> future.handle(Future.succeededFuture(helloWord + " " + ar.result().bodyAsString()))); return future; } } 

HelloWorldVerticle.java
 package io.bihero.helloworld; import io.vertx.core.AbstractVerticle; import io.vertx.core.Promise; import io.vertx.core.eventbus.MessageConsumer; import io.vertx.core.http.HttpServer; import io.vertx.core.http.HttpServerOptions; import io.vertx.core.json.JsonObject; import io.vertx.ext.web.Router; import io.vertx.ext.web.api.contract.openapi3.OpenAPI3RouterFactory; import io.vertx.serviceproxy.ServiceBinder; public class HelloWorldVerticle extends AbstractVerticle { HttpServer server; MessageConsumer<JsonObject> consumer; public void startWorldService() { consumer = new ServiceBinder(vertx).setAddress("service.helloworld") .register(HelloWorldService.class, HelloWorldService.create(vertx, config())); } /** * This method constructs the router factory, mounts services and handlers and starts the http server * with built router * @return */ private Promise<Void> startHttpServer() { Promise<Void> promise = Promise.promise(); OpenAPI3RouterFactory.create(this.vertx, "/doc.yaml", openAPI3RouterFactoryAsyncResult -> { if (openAPI3RouterFactoryAsyncResult.succeeded()) { OpenAPI3RouterFactory routerFactory = openAPI3RouterFactoryAsyncResult.result(); // Mount services on event bus based on extensions routerFactory.mountServicesFromExtensions(); // Generate the router Router router = routerFactory.getRouter(); int port = config().getInteger("serverPort", 8080); String host = config().getString("serverHost", "localhost"); server = vertx.createHttpServer(new HttpServerOptions().setPort(port).setHost(host)); server.requestHandler(router).listen(ar -> { // Error starting the HttpServer if (ar.succeeded()) promise.complete(); else promise.fail(ar.cause()); }); } else { // Something went wrong during router factory initialization promise.fail(openAPI3RouterFactoryAsyncResult.cause()); } }); return promise; } @Override public void start(Promise<Void> promise) { startWorldService(); startHttpServer().future().setHandler(promise); } /** * This method closes the http server and unregister all services loaded to Event Bus */ @Override public void stop(){ this.server.close(); consumer.unregister(); } } 

HelloWorldServiceTest.java
 package io.bihero.helloworld; import io.vertx.core.DeploymentOptions; import io.vertx.core.Vertx; import io.vertx.core.json.JsonObject; import io.vertx.ext.web.client.WebClient; import io.vertx.ext.web.codec.BodyCodec; import io.vertx.junit5.Checkpoint; import io.vertx.junit5.VertxExtension; import io.vertx.junit5.VertxTestContext; import org.apache.commons.io.IOUtils; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.testcontainers.containers.GenericContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.spy; @Testcontainers @ExtendWith(VertxExtension.class) public class HelloWorldServiceTest { @Container private static final GenericContainer helloServiceContainer = new GenericContainer("bihero/hello") .withExposedPorts(8081); @Container private static final GenericContainer worldServiceContainer = new GenericContainer("bihero/world") .withExposedPorts(8082); @Test @DisplayName("Test 'helloworld' microservice respond by 'Hello World' string and doc in OpenAPI format") public void testHelloWorld(Vertx vertx, VertxTestContext testContext) { WebClient webClient = WebClient.create(vertx); Checkpoint deploymentCheckpoint = testContext.checkpoint(); Checkpoint requestCheckpoint = testContext.checkpoint(2); HelloWorldVerticle verticle = spy(new HelloWorldVerticle()); JsonObject config = new JsonObject().put("serverPort", 8083) .put("serverHost", "0.0.0.0") .put("hello-service-host", helloServiceContainer.getContainerIpAddress()) .put("world-service-host", worldServiceContainer.getContainerIpAddress()) .put("hello-service-port", helloServiceContainer.getMappedPort(8081)) .put("world-service-port", worldServiceContainer.getMappedPort(8082)); DeploymentOptions deploymentOptions = new DeploymentOptions().setConfig(config); vertx.deployVerticle(verticle, deploymentOptions, testContext.succeeding(id -> { deploymentCheckpoint.flag(); // test GET / webClient.get(8083, "localhost", "/") .as(BodyCodec.string()) .send(testContext.succeeding(resp -> { assertThat(resp.body()).isEqualTo("Hello World"); assertThat(resp.statusCode()).isEqualTo(200); requestCheckpoint.flag(); })); // test GET /doc webClient.get(8083, "localhost", "/doc") .as(BodyCodec.string()) .send(testContext.succeeding(resp -> { try { assertThat(resp.body()).isEqualTo(IOUtils.toString(this.getClass() .getResourceAsStream("../../../doc.yaml"), "UTF-8")); assertThat(resp.statusCode()).isEqualTo(200); requestCheckpoint.flag(); } catch (Exception e) { requestCheckpoint.flag(); testContext.failNow(e); } })); })); } } 

Dockerfile — / , /usr/local , ConfigMap' k8s


Dockerfile
 FROM adoptopenjdk/openjdk11:alpine-jre COPY target/hello-world-microservice-fat.jar app.jar COPY src/conf/config.json /usr/local/config.json COPY src/conf/logback-console.xml . COPY run.sh . RUN chmod +x run.sh CMD ["./run.sh"] 

, , CI . Concourse , , worker- docker-compose' ( ui- postgresql). — docker-, docker docker'. testcontainers ( hello world ). ? : ! docker', maven' 11- . , Dockerfile:


 FROM alpine:3.7 ENV DOCKER_CHANNEL=stable \ DOCKER_VERSION=17.12.1-ce \ DOCKER_COMPOSE_VERSION=1.19.0 \ DOCKER_SQUASH=0.2.0 # Install Docker, Docker Compose, Docker Squash RUN apk --update --no-cache add \ bash \ curl \ device-mapper \ py-pip \ iptables \ util-linux \ ca-certificates \ maven \ openjdk11 --repository=http://dl-cdn.alpinelinux.org/alpine/edge/community \ && \ apk upgrade && \ curl -fL "https://download.docker.com/linux/static/${DOCKER_CHANNEL}/x86_64/docker-${DOCKER_VERSION}.tgz" | tar zx && \ mv /docker/* /bin/ && chmod +x /bin/docker* && \ pip install docker-compose==${DOCKER_COMPOSE_VERSION} && \ curl -fL "https://github.com/jwilder/docker-squash/releases/download/v${DOCKER_SQUASH}/docker-squash-linux-amd64-v${DOCKER_SQUASH}.tar.gz" | tar zx && \ mv /docker-squash* /bin/ && chmod +x /bin/docker-squash* && \ rm -rf /var/cache/apk/* && \ rm -rf /root/.cache COPY repository /root/.m2/repository #      -,          COPY settings.xml /root/.m2/settings.xml #   maven'      COPY entrypoint.sh /bin/entrypoint.sh #  -,         ENV JAVA_HOME=/usr/lib/jvm/java-11-openjdk/ ENTRYPOINT ["entrypoint.sh"] 

entrypoint.sh , :


entrypoint.sh
 #!/usr/bin/env bash # Inspired by concourse/docker-image-resource: # https://github.com/concourse/docker-image-resource/blob/master/assets/common.sh set -o errexit -o pipefail -o nounset # Waits DOCKERD_TIMEOUT seconds for startup (default: 60) DOCKERD_TIMEOUT="${DOCKERD_TIMEOUT:-60}" # Accepts optional DOCKER_OPTS (default: --data-root /scratch/docker) DOCKER_OPTS="${DOCKER_OPTS:-}" # Constants DOCKERD_PID_FILE="/tmp/docker.pid" DOCKERD_LOG_FILE="/tmp/docker.log" sanitize_cgroups() { local cgroup="/sys/fs/cgroup" mkdir -p "${cgroup}" if ! mountpoint -q "${cgroup}"; then if ! mount -t tmpfs -o uid=0,gid=0,mode=0755 cgroup "${cgroup}"; then echo >&2 "Could not make a tmpfs mount. Did you use --privileged?" exit 1 fi fi mount -o remount,rw "${cgroup}" # Skip AppArmor # See: https://github.com/moby/moby/commit/de191e86321f7d3136ff42ff75826b8107399497 export container=docker # Mount /sys/kernel/security if [[ -d /sys/kernel/security ]] && ! mountpoint -q /sys/kernel/security; then if ! mount -t securityfs none /sys/kernel/security; then echo >&2 "Could not mount /sys/kernel/security." echo >&2 "AppArmor detection and --privileged mode might break." fi fi sed -e 1d /proc/cgroups | while read sys hierarchy num enabled; do if [[ "${enabled}" != "1" ]]; then # subsystem disabled; skip continue fi grouping="$(cat /proc/self/cgroup | cut -d: -f2 | grep "\\<${sys}\\>")" if [[ -z "${grouping}" ]]; then # subsystem not mounted anywhere; mount it on its own grouping="${sys}" fi mountpoint="${cgroup}/${grouping}" mkdir -p "${mountpoint}" # clear out existing mount to make sure new one is read-write if mountpoint -q "${mountpoint}"; then umount "${mountpoint}" fi mount -n -t cgroup -o "${grouping}" cgroup "${mountpoint}" if [[ "${grouping}" != "${sys}" ]]; then if [[ -L "${cgroup}/${sys}" ]]; then rm "${cgroup}/${sys}" fi ln -s "${mountpoint}" "${cgroup}/${sys}" fi done # Initialize systemd cgroup if host isn't using systemd. # Workaround for https://github.com/docker/for-linux/issues/219 if ! [[ -d /sys/fs/cgroup/systemd ]]; then mkdir "${cgroup}/systemd" mount -t cgroup -o none,name=systemd cgroup "${cgroup}/systemd" fi } # Setup container environment and start docker daemon in the background. start_docker() { echo >&2 "Setting up Docker environment..." mkdir -p /var/log mkdir -p /var/run sanitize_cgroups # check for /proc/sys being mounted readonly, as systemd does if grep '/proc/sys\s\+\w\+\s\+ro,' /proc/mounts >/dev/null; then mount -o remount,rw /proc/sys fi local docker_opts="${DOCKER_OPTS:-}" # Pass through `--garden-mtu` from gardian container if [[ "${docker_opts}" != *'--mtu'* ]]; then local mtu="$(cat /sys/class/net/$(ip route get 8.8.8.8|awk '{ print $5 }')/mtu)" docker_opts+=" --mtu ${mtu}" fi # Use Concourse's scratch volume to bypass the graph filesystem by default if [[ "${docker_opts}" != *'--data-root'* ]] && [[ "${docker_opts}" != *'--graph'* ]]; then docker_opts+=' --data-root /scratch/docker' fi rm -f "${DOCKERD_PID_FILE}" touch "${DOCKERD_LOG_FILE}" echo >&2 "Starting Docker..." dockerd ${docker_opts} &>"${DOCKERD_LOG_FILE}" & echo "$!" > "${DOCKERD_PID_FILE}" } # Wait for docker daemon to be healthy # Timeout after DOCKERD_TIMEOUT seconds await_docker() { local timeout="${DOCKERD_TIMEOUT}" echo >&2 "Waiting ${timeout} seconds for Docker to be available..." local start=${SECONDS} timeout=$(( timeout + start )) until docker info &>/dev/null; do if (( SECONDS >= timeout )); then echo >&2 'Timed out trying to connect to docker daemon.' if [[ -f "${DOCKERD_LOG_FILE}" ]]; then echo >&2 '---DOCKERD LOGS---' cat >&2 "${DOCKERD_LOG_FILE}" fi exit 1 fi if [[ -f "${DOCKERD_PID_FILE}" ]] && ! kill -0 $(cat "${DOCKERD_PID_FILE}"); then echo >&2 'Docker daemon failed to start.' if [[ -f "${DOCKERD_LOG_FILE}" ]]; then echo >&2 '---DOCKERD LOGS---' cat >&2 "${DOCKERD_LOG_FILE}" fi exit 1 fi sleep 1 done local duration=$(( SECONDS - start )) echo >&2 "Docker available after ${duration} seconds." } # Gracefully stop Docker daemon. stop_docker() { if ! [[ -f "${DOCKERD_PID_FILE}" ]]; then return 0 fi local docker_pid="$(cat ${DOCKERD_PID_FILE})" if [[ -z "${docker_pid}" ]]; then return 0 fi echo >&2 "Terminating Docker daemon." kill -TERM ${docker_pid} local start=${SECONDS} echo >&2 "Waiting for Docker daemon to exit..." wait ${docker_pid} local duration=$(( SECONDS - start )) echo >&2 "Docker exited after ${duration} seconds." } start_docker trap stop_docker EXIT await_docker # do not exec, because exec disables traps if [[ "$#" != "0" ]]; then "$@" else bash --login fi 

pipeline.yaml
 resource_types: - name: telegram type: docker-image source: repository: vtutrinov/concourse-telegram-resource tag: latest - name: kubernetes type: docker-image source: repository: zlabjp/kubernetes-resource tag: 1.16 - name: metadata type: docker-image source: repository: olhtbr/metadata-resource tag: 2.0.1 resources: - name: metadata type: metadata - name: sources type: git source: branch: master uri: git@github.com:bihero-io/helloworldmicroservice.git private_key: ((deployer-private-key)) - name: docker-image type: docker-image source: repository: bihero/helloworld username: ((docker-registry-user)) password: ((docker-registry-password)) - name: telegram type: telegram source: bot_token: ((telegram-ci-bot-token)) chat_id: ((telegram-group-to-report-build)) ci_url: ((ci_url)) command: "/build_helloworld_ms" - name: kubernetes-demo type: kubernetes source: server: ((k8s-api-server)) namespace: default kubeconfig: ((kubeconfig-demo)) jobs: - name: build-helloworld-microservice serial: true public: true plan: - in_parallel: - get: sources trigger: true - get: telegram trigger: true - put: metadata - put: telegram params: status: Build In Progress - task: tests privileged: true config: platform: linux image_resource: type: docker-image source: repository: ((docker-registry-uri))/bih/dind #    ,    ,    , maven  11-  tag: latest username: ((docker-private-registry-user)) password: ((docker-private-registry-password)) inputs: - name: sources outputs: - name: tested-workspace run: path: entrypoint.sh args: - bash - -ceux - | #               testcontainers output_dir=tested-workspace cp -R ./sources/* "${output_dir}/" mvn -f "${output_dir}/pom.xml" clean test caches: - path: ~/.m2/ on_failure: do: - task: tests-report config: platform: linux image_resource: type: docker-image source: repository: ((docker-registry-uri))/bih/maven-dind tag: 3-jdk-11 username: ((docker-private-registry-user)) password: ((docker-private-registry-password)) inputs: - name: tested-workspace outputs: - name: message run: path: /bin/sh args: - -c - | output_dir=tested-workspace mvn -Dmaven.wagon.http.ssl.insecure=true -Dmaven.wagon.http.ssl.allowall=true -f "${output_dir}/pom.xml" site-deploy version=$(cat $output_dir/target/classes/version.txt) cat >message/msg <<EOL <a href="https://nexus.dev.techedge.pro:8443/repository/reports/hello-world-microservice/${version}/allure/">Allure report</a> EOL caches: - path: ~/.m2/ - put: telegram params: status: Build Failed (unit-tests) message_file: message/msg - task: tests-report config: platform: linux image_resource: type: docker-image source: repository: ((docker-registry-uri))/bih/maven-dind tag: 3-jdk-11 username: ((docker-private-registry-user)) password: ((docker-private-registry-password)) inputs: - name: tested-workspace outputs: - name: message - name: tested-workspace run: path: /bin/sh args: - -c - | work_dir=tested-workspace mvn -Dmaven.wagon.http.ssl.insecure=true -Dmaven.wagon.http.ssl.allowall=true -f "${work_dir}/pom.xml" site-deploy version=$(cat $work_dir/target/classes/version.txt) cat >message/msg <<EOL <a href="https://nexus.dev.techedge.pro:8443/repository/reports/hello-world-microservice/${version}/allure/">Allure report</a> EOL caches: - path: ~/.m2/ - task: package config: platform: linux image_resource: type: docker-image source: repository: ((docker-registry-uri))/bih/maven-dind tag: 3-jdk-11 username: ((docker-private-registry-user)) password: ((docker-private-registry-password)) inputs: - name: tested-workspace - name: metadata outputs: - name: app-packaged-workspace - name: metadata run: path: /bin/sh args: - -c - | output_dir=app-packaged-workspace cp -R ./tested-workspace/* "${output_dir}/" mvn -f "${output_dir}/pom.xml" package -Dmaven.main.skip -DskipTests tag="-"$(cat metadata/build_name) echo $tag >> ${output_dir}/target/classes/version.txt cat ${output_dir}/target/classes/version.txt > metadata/version caches: - path: ~/.m2/ on_failure: do: - put: telegram params: status: Build Failed (package) - put: docker-image params: build: app-packaged-workspace tag_file: app-packaged-workspace/target/classes/version.txt tag_as_latest: true get_params: skip_download: true - task: make-k8s-app-template config: platform: linux image_resource: type: docker-image source: repository: bhgedigital/envsubst inputs: - name: sources - name: metadata outputs: - name: k8s run: path: /bin/sh args: - -c - | export DOMAIN=demo1.bihero.io export HELLO_WORLD_SERVICE_IMAGE_VERSION=$(cat metadata/version) cat sources/k8s.yaml | envsubst > k8s/helloworld_app_template.yaml cat k8s/helloworld_app_template.yaml - put: kubernetes-demo params: kubectl: apply -f k8s/helloworld_app_template.yaml - put: telegram params: status: Build Success message_file: message/msg 

k8s. hello world k8s-. k8s <service-name>..default.svc.cluster.local , , , API. , :


k8s
 apiVersion: v1 kind: ConfigMap metadata: name: hello-world-config data: config.json: | { "type": "file", "format": "json", "scanPeriod": 5000, "config": { "path": "/config.json" }, "serverPort": 8083, "serverHost": "0.0.0.0", "hello-service-host": "bihero-hello.default.svc.cluster.local", "hello-service-port": 8081, "world-service-host": "bihero-world.default.svc.cluster.local", "world-service-port": 8082 } --- apiVersion: extensions/v1beta1 kind: Deployment metadata: labels: io.bihero.hello.service: bihero-helloworld name: bihero-helloworld spec: replicas: 3 strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 maxUnavailable: 1 template: metadata: labels: io.bihero.hello.service: bihero-helloworld spec: containers: - image: bihero/helloworld:${HELLO_WORLD_SERVICE_IMAGE_VERSION} name: bihero-helloworld ports: - containerPort: 8083 imagePullPolicy: Always resources: {} volumeMounts: #  /usr/local       ConfigMap'  - mountPath: /usr/local/ name: hello-world-config restartPolicy: Always volumes: - name: hello-world-config configMap: name: hello-world-config --- apiVersion: v1 kind: Service metadata: labels: io.bihero.hello.service: bihero-helloworld name: bihero-helloworld spec: ports: - name: "8083" port: 8083 targetPort: 8083 selector: io.bihero.hello.service: bihero-helloworld status: loadBalancer: {} --- apiVersion: extensions/v1beta1 kind: Ingress metadata: name: bihero-helloworld annotations: kubernetes.io/ingress.class: nginx nginx.ingress.kubernetes.io/ssl-redirect: "true" nginx.ingress.kubernetes.io/secure-backends: "false" nginx.ingress.kubernetes.io/ssl-passthrough: "false" nginx.ingress.kubernetes.io/rewrite-target: /$2 kubernetes.io/tls-acme: "true" namespace: default spec: tls: - hosts: - ${DOMAIN} secretName: bihero rules: - host: ${DOMAIN} http: paths: - path: /api/helloworld(/|$)(.*) backend: serviceName: bihero-helloworld servicePort: 8083 

, — , , , , :


 curl https://demo1.bihero.io/api/helloworld Hello World 

 curl https://demo1.bihero.io/api/helloworld/doc openapi: 3.0.1 info: title: Hello World ;) description: "Hello World microservice. Aggregate 'Hello World' by hellomicroservice and worldmicroservice" version: 1.0.0 servers: - url: https://demo1.bihero.io/api/helloworld tags: - name: helloworld description: Everything about 'Hello World' paths: /: x-vertx-event-bus: address: service.helloworld timeout: 1000 get: tags: - helloworld summary: Aggregate 'Hello World' operationId: getHelloWorld responses: 200: description: OK content: {} /doc: x-vertx-event-bus: address: service.helloworld timeout: 1000c get: tags: - world summary: Get 'Hello World' microservice documentation operationId: getDoc responses: 200: description: OK components: {} 

Hore! ! , …


TODO' (backlog)


  1. — parent pom .
  2. docker- docker-hub, — , , private registry.
  3. "" (maven-release-plugin? concourse semver-resource ?), , , .
  4. API - ( HelloWorld, , , ). - , — :)

, , , , :)



https://github.com/bihero-io/hello-microservice
https://github.com/bihero-io/worldmicroservice
https://github.com/bihero-io/helloworldmicroservice


[UPD] TODO'


  1. helm-, k8s , on-prem ,

Source: https://habr.com/ru/post/id465149/


All Articles