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'
- 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> <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); } @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())); } private Promise<Void> startHttpServer() { Promise<Void> promise = Promise.promise(); OpenAPI3RouterFactory.create(this.vertx, "/doc.yaml", openAPI3RouterFactoryAsyncResult -> { if (openAPI3RouterFactoryAsyncResult.succeeded()) { OpenAPI3RouterFactory routerFactory = openAPI3RouterFactoryAsyncResult.result();
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
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();
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:
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
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/
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

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-.
?
- , , ( ), git-
- (mvn), (surefire, allure) — fat-jar
- docker- (docker build)
- Push docker- ( docker registry) (docker push)
- k8s (kubectl apply)
CI- ?
, ( ), . :

:
- , master ( master , , , merge' merge request' )
- , dev- (telegram-bot)
- ,
- — maven repository ( nexus blob store )
- fat-jar (mvn package, , — )
- docker image . , , , ( ). registry k8s
- k8s
- , k8s .
- 4- , , maven repository
- ,
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
:
- resource_types , , . ( , docker-, ): telegram tg- , kubernetes k8s- metadata ( , ..)
- resources , . , , docker-registry docker- , . input- ,
- jobs , . put- tg-. — get (, git-), — put — (docker image) , (metadata). task — docker- docker-image', image_resource
- ((parameter-name)) — , , , ( docker-registry).
:
fly -t bih sp -p hello-microservice -c pipeline.yaml -l credentials.yaml
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>
. :
- CI-, :
- ( 1), c fly , CI-:
fly -t bih tj -j hello-microservice/build-hello-microservice -w
- /build_hello_ms -, telegram-group-to-report-build credentials.yaml
- master- (, , : master — , — ;) )
( ) -:
- :

- :

, UI CI-:
Hore! , - , , k8s . :
- docker-hub'


- , 2-

- :
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 (
, , . , . , ( 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> <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())); } private Promise<Void> startHttpServer() { Promise<Void> promise = Promise.promise(); OpenAPI3RouterFactory.create(this.vertx, "/doc.yaml", openAPI3RouterFactoryAsyncResult -> { if (openAPI3RouterFactoryAsyncResult.succeeded()) { OpenAPI3RouterFactory routerFactory = openAPI3RouterFactoryAsyncResult.result();
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();
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"]
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> <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())); } private Promise<Void> startHttpServer() { Promise<Void> promise = Promise.promise(); OpenAPI3RouterFactory.create(this.vertx, "/doc.yaml", openAPI3RouterFactoryAsyncResult -> { if (openAPI3RouterFactoryAsyncResult.succeeded()) { OpenAPI3RouterFactory routerFactory = openAPI3RouterFactoryAsyncResult.result();
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();
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 , :
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)
- — parent pom .
- docker- docker-hub, — , , private registry.
- "" (maven-release-plugin? concourse semver-resource ?), , , .
- API - ( HelloWorld, , , ). - , — :)
, , , , :)
https://github.com/bihero-io/hello-microservice
https://github.com/bihero-io/worldmicroservice
https://github.com/bihero-io/helloworldmicroservice
[UPD] TODO'
- helm-, k8s , on-prem ,