Study cases: Microservice apps (Springboot Rest API)
Hai semuanya, di materi study cases untuk Pod and Container specification kali ini adalah lanjutan dari sebelumnya yang lebih advanced lagi yaitu Create and build microservices dengan framework Springboot Rest API dengan architecture seperti berikut:
Okay nah terlihat sedikit berbeda dengan application monolith sebelumnya, disini setiap service akan saling berkomunikasi dengan menggunakan protocol yang lightweight (ringan) seperti Rest API, grpc, messaging bus, database shared dan lain-lain. Pada study kasus kali ini terlihat pada diagram tersebut masih menggunakan physical / virtual-machine deployement kita akan migrasikan menggunakan orchestration container system dengan Kubernetes. Adapun tahap-tahap yang perlu kita lakukan yaitu
- Develop aplikasi
- How code works (Code Review)
- The new architecture for orchestration container system
- Containerize apps
- Deploy to Kubernetes
- Running as a Pod with namespace
- Connecting other service from the another namespace
- Specify container probes (health check)
- Specify resource request and limit
- Implement API Gateway using nginx reverse proxy
Ok tanpa berlama-lama yuk langsung aja kita bahas materi yang pertama:
Development aplikasi
Dalam mendevelop aplikasi, ada beberapa hal yang perlu di persiapakan yaitu Software Development Kit dan backing service seperti Database, source-code version control dan DevTools yaitu
- Git
- Java Development Kit (JDK) versi 17 keatas
- MySQL Database
- PostgreSQL Database
- Apache Maven
- Text editor seperti InteliJ IDEA, Visual Studio Code dan lain-lain
- Rest API client seperti Postman, curl dan lain-lain
- Docker
Jadi temen-temen perlu install software development kita tersebut untuk cara installnya sudah pernah saya bahas di kelas DevOps - Docker untuk Pemula s/d Mahir. Jika sudah temen bisa check dengan command seperti berikut:
~ » java -version
java version "19.0.1" 2022-10-18
Java(TM) SE Runtime Environment (build 19.0.1+10-21)
Java HotSpot(TM) 64-Bit Server VM (build 19.0.1+10-21, mixed mode, sharing)
~ » mvn -v
Apache Maven 3.9.0 (9b58d2bad23a66be161c4664ef21ce219c2c8584)
Maven home: /usr/local/Cellar/maven/3.9.0/libexec
Java version: 19.0.1, vendor: Oracle Corporation, runtime: /Library/Java/JavaVirtualMachines/jdk-19.jdk/Contents/Home
Default locale: en_ID, platform encoding: UTF-8
OS name: "mac os x", version: "13.2", arch: "x86_64", family: "mac"
~ » psql --version
psql (PostgreSQL) 14.7 (Homebrew)
~ » mysql --version
mysql Ver 8.0.32 for macos13.0 on x86_64 (Homebrew)
~ » curl --version
curl 7.86.0 (x86_64-apple-darwin22.0) libcurl/7.86.0 (SecureTransport) LibreSSL/3.3.6 zlib/1.2.11 nghttp2/1.47.0
Release-Date: 2022-10-26
Setelah temen-temen menginstall semua Software Development Kit, kemudian yang kita butuhkan adalah source-code. Untuk source-code temen-temen bisa clone dari github repo berikut perintahnya seperti berikut:
Setelah di clone kita coba jalankan projectnya, tetapi pertama kita perlu configure dulu databasenya. supaya gak ribet kita akan menggunakan docker-compose.yaml
seperti berikut:
Kemudian coba jalankan dengan perintah berikut:
Maka hasilnya seperti berikut:
devops/k8s-springboot-microservice [main●] » docker compose ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
k8s-springboot-microservice-mysql-1 mysql:8.0 "docker-entrypoint.s…" mysql 47 seconds ago Up 41 seconds 0.0.0.0:3306->3306/tcp, 33060/tcp
k8s-springboot-microservice-postgres-1 postgres:15 "docker-entrypoint.s…" postgres 47 seconds ago Up 42 seconds 0.0.0.0:5432->5432/tcp
Setelah semua database running, sekarang kita jalankan masing-masing service dengan menggunakan perintah mvn clean -pl <module-name> spring-boot:run
seperti berikut
maka outputnya seperti berikut:
devops/k8s-springboot-microservice [main●] » mvn clean -pl customer spring-boot:run
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.0.2)
2023-02-12T12:05:11.295+07:00 INFO 9448 --- [ restartedMain] c.m.d.udemy.customer.MainApplication : Starting MainApplication using Java 19.0.1 with PID 9448 (/Users/dimasm93/Developer/dimas-maryanto.com/youtube/_projects/devops/k8s-springboot-microservice/customer/target/classes started by dimasm93 in /Users/dimasm93/Developer/dimas-maryanto.com/youtube/_projects/devops/k8s-springboot-microservice/customer)
2023-02-12T12:05:11.298+07:00 INFO 9448 --- [ restartedMain] c.m.d.udemy.customer.MainApplication : No active profile set, falling back to 1 default profile: "default"
2023-02-12T12:05:11.392+07:00 INFO 9448 --- [ restartedMain] .e.DevToolsPropertyDefaultsPostProcessor : Devtools property defaults active! Set 'spring.devtools.add-properties' to 'false' to disable
2023-02-12T12:05:13.303+07:00 INFO 9448 --- [ restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 9090 (http)
2023-02-12T12:05:13.319+07:00 INFO 9448 --- [ restartedMain] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2023-02-12T12:05:13.319+07:00 INFO 9448 --- [ restartedMain] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.5]
2023-02-12T12:05:13.400+07:00 INFO 9448 --- [ restartedMain] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2023-02-12T12:05:13.402+07:00 INFO 9448 --- [ restartedMain] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 2007 ms
2023-02-12T12:05:13.612+07:00 INFO 9448 --- [ restartedMain] o.f.c.internal.license.VersionPrinter : Flyway Community Edition 9.5.1 by Redgate
2023-02-12T12:05:14.208+07:00 INFO 9448 --- [ restartedMain] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
2023-02-12T12:05:14.248+07:00 INFO 9448 --- [ restartedMain] o.f.c.i.database.base.BaseDatabaseType : Database: jdbc:mysql://localhost:3306/customer4p1 (MySQL 8.0)
2023-02-12T12:05:14.373+07:00 INFO 9448 --- [ restartedMain] o.f.core.internal.command.DbValidate : Successfully validated 1 migration (execution time 00:00.030s)
2023-02-12T12:05:14.435+07:00 INFO 9448 --- [ restartedMain] o.f.c.i.s.JdbcTableSchemaHistory : Creating Schema History table `customer4p1`.`flyway_schema_history` ...
2023-02-12T12:05:14.575+07:00 INFO 9448 --- [ restartedMain] o.f.core.internal.command.DbMigrate : Current version of schema `customer4p1`: << Empty Schema >>
2023-02-12T12:05:14.590+07:00 INFO 9448 --- [ restartedMain] o.f.core.internal.command.DbMigrate : Migrating schema `customer4p1` to version "20230211170102 - schema-customer"
2023-02-12T12:05:14.694+07:00 INFO 9448 --- [ restartedMain] o.f.core.internal.command.DbMigrate : Successfully applied 1 migration to schema `customer4p1`, now at version v20230211170102 (execution time 00:00.131s)
2023-02-12T12:05:14.806+07:00 INFO 9448 --- [ restartedMain] o.hibernate.jpa.internal.util.LogHelper : HHH000204: Processing PersistenceUnitInfo [name: default]
2023-02-12T12:05:16.662+07:00 INFO 9448 --- [ restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 9090 (http) with context path ''
2023-02-12T12:05:16.666+07:00 INFO 9448 --- [ restartedMain] o.s.b.d.a.OptionalLiveReloadServer : LiveReload server is running on port 35729
2023-02-12T12:05:16.686+07:00 WARN 9448 --- [ restartedMain] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2023-02-12T12:05:16.706+07:00 INFO 9448 --- [ restartedMain] c.m.d.udemy.customer.MainApplication : Started MainApplication in 6.034 seconds (process running for 6.653)
Sekarang kita coba check service custemer bisa meresponse api yang kita request dengan menggunakan perintah curl seperti berikut:
Maka jika dijalankan outputnya seperti berikut:
Last login: Sun Feb 12 11:33:28 on ttys002
~ » curl --location --request GET 'localhost:9090/api/customer/v1/findById/cust01' -v
Note: Unnecessary use of -X or --request, GET is already inferred.
* Trying 127.0.0.1:9090...
* Connected to localhost (127.0.0.1) port 9090 (#0)
> GET /api/customer/v1/findById/cust01 HTTP/1.1
< HTTP/1.1 200
< Content-Type: application/json
< Transfer-Encoding: chunked
< Date: Sun, 12 Feb 2023 05:12:26 GMT
{"id":"cust01","userId":"dimasm93","fullname":"Dimas Maryanto","alamat":"Bandung, Jawa Barat"}%
Itu artinya sudah ok, selanjutnya kita coba jalankan service order dengan perintah seperti berikut:
Maka outputnya seperti berikut:
devops/k8s-springboot-microservice [main●] » mvn clean -pl orders spring-boot:run
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.0.2)
2023-02-12T12:16:07.346+07:00 INFO 11255 --- [ restartedMain] c.m.dimas.udemy.orders.MainApplication : Starting MainApplication using Java 19.0.1 with PID 11255 (/Users/dimasm93/Developer/dimas-maryanto.com/youtube/_projects/devops/k8s-springboot-microservice/orders/target/classes started by dimasm93 in /Users/dimasm93/Developer/dimas-maryanto.com/youtube/_projects/devops/k8s-springboot-microservice/orders)
2023-02-12T12:16:07.350+07:00 INFO 11255 --- [ restartedMain] c.m.dimas.udemy.orders.MainApplication : No active profile set, falling back to 1 default profile: "default"
2023-02-12T12:16:09.296+07:00 INFO 11255 --- [ restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 9091 (http)
2023-02-12T12:16:09.322+07:00 INFO 11255 --- [ restartedMain] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2023-02-12T12:16:09.608+07:00 INFO 11255 --- [ restartedMain] o.f.c.internal.license.VersionPrinter : Flyway Community Edition 9.5.1 by Redgate
2023-02-12T12:16:10.187+07:00 INFO 11255 --- [ restartedMain] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
2023-02-12T12:16:10.217+07:00 INFO 11255 --- [ restartedMain] o.f.c.i.database.base.BaseDatabaseType : Database: jdbc:postgresql://localhost:5432/order4p1 (PostgreSQL 15.2)
2023-02-12T12:16:10.364+07:00 INFO 11255 --- [ restartedMain] o.f.core.internal.command.DbValidate : Successfully validated 1 migration (execution time 00:00.047s)
2023-02-12T12:16:10.460+07:00 INFO 11255 --- [ restartedMain] o.f.c.i.s.JdbcTableSchemaHistory : Creating Schema History table "public"."flyway_schema_history" ...
2023-02-12T12:16:10.605+07:00 INFO 11255 --- [ restartedMain] o.f.core.internal.command.DbMigrate : Current version of schema "public": << Empty Schema >>
2023-02-12T12:16:10.627+07:00 INFO 11255 --- [ restartedMain] o.f.core.internal.command.DbMigrate : Migrating schema "public" to version "20230211171555 - create-order"
2023-02-12T12:16:10.696+07:00 INFO 11255 --- [ restartedMain] o.f.core.internal.command.DbMigrate : Successfully applied 1 migration to schema "public", now at version v20230211171555 (execution time 00:00.107s)
2023-02-12T12:16:12.807+07:00 INFO 11255 --- [ restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 9091 (http) with context path ''
2023-02-12T12:16:12.813+07:00 WARN 11255 --- [ restartedMain] o.s.b.d.a.OptionalLiveReloadServer : Unable to start LiveReload server
2023-02-12T12:16:12.866+07:00 INFO 11255 --- [ restartedMain] c.m.dimas.udemy.orders.MainApplication : Started MainApplication in 6.225 seconds (process running for 6.76)
Sekarang kita coba check service order dengan menggunakan perintah curl berikut:
Ok ini artinya sudah okay semua.
How code works (Code Review)
Setelah kita mencoba menjalankan program tersebut tahap selanjutnya adalah memahami bagaimana aplikasi bisa running dengan cara code review. Hal ini juga menjadi yang terpenting ketika proses deliver dari Programmer/Developer ke DevOps Engineer untuk di deploy ke environment.
Sebagai seorang DevOps kita harus mengetahui dan mengerti setiap service dari microservice yang telah di deliver oleh programmer untuk di implementasikan, Dengan cara melakukan assesment, diskusi, atau technical meeting dengan team Developer/Programmer. Salah satu prosedure yang biasanya saya lakukan adalah
- Developer/Programmer menjelaskan overview architecture service
- Developer/Programmer menjelaskan service communication
- Developer/Programmer menjelaskan how to configure communication between service
- DevOps menyimpulkan & Memberikan saran terkait perancangan architecture baru
- DevOps mengimplementasikan perancangan architecture tersebut ke environment
- Developer/Programmer dan DevOps melakukan Testing functional secara berdampingan
- DevOps melakukan Performance/Stress testing
Okay kita tidak akan membahas semuanya ya, karena keterbatasan waktu. Jadi kita bahas beberapa yang dirasa penting seperti point no 1
, 2
, 3
, dan 4
. Pada point no 1, kita sudah bahas dibahas section awal jadi kita skip. Selanjutnya kita bahas point no 2 yaitu Service Communication.
Service communication ini pada dasarnya menjelaskan setiap service memiliki dependency ke mana saja dan seperti apa komunikasinya. Pada dasarnya service communication ada beberapa cara yaitu menggunakan Shared Database, Rest API, RPC (khususnya grpc), dan Messaging bus. Untuk service customer
dan orders
ini basicly kita menggunakan Rest API, Okay untuk lebih jelas kita lihat diagram berikut:
-
Customer API - find by id
-
Orders API - create new order
Jadi klo kita perhatikan diagram no 1, flownya sangat simple hanya menggunakan database tetapi untuk no 2 selain database perlu call service customerAPI melalui Rest API. Yang jadi pertanyaan selanjutnya bagaimana konfigurasi koneksinya? Okay sekarang kita coba bedah kodingnya / code review.
Kalo kita lihat di project orders
seperti berikut:
Dan selain itu juga, berikut adalah file application.yaml
untuk menyimpan semua environment variable yang dipanggil pada source code tersebut:
Okay sekarang perhatikan penggalan code diatas, jadi cara kita mengconfigurasi koneksi ke service customerAPI
yaitu menggunakan envionment variable yang tertera pada application.yaml
seperti SERVICE_CUSTOMER_HOST
, SERVICE_CUSTOMER_PORT
, SERVICE_CUSTOMER_CONTEXT_PATH
, SERVICE_CUSTOMER_PROTO
So kita bisa pasang/override nilainya pada saat dijalankan diatas container.
Nah setelah kita breakdown cara kerja atau code review, semoga temen-temen bisa memahami dan mulai merumuskan architecture yang bisa menunjang workload tersebut.
The new architecture
Setelah kita melihat, diskusi, technical meeting, serta code review tahap selanjutnya adalah merumuskan architecture yang sesuai dengan workload dari service tersebut.
Karena aplikasi yang dibuat merupakan microservice architecture yang memiliki backing service (dependency) seperti database, atau bahkan service lainnya Maka kita perlu buat scope dan boundaries dari services tersebut. Adapun scope dan boundaries tersebut dibagi menjadi Primary dan Secondary backing service
Primary backing service yaitu Aplikasi yang tidak akan bisa startup ketika di running dalam suatu environment jika tanpa/menggunakan service tersebut (Mandatory), sedangkan Secondary backing service yaitu Aplikasi masih bisa berjalan tetapi secara functional belum lengkap.
Karena tujuan kita adalah deploy ke orchestration container system menggunakan kubernetes jadi, langsung aja disini saya gambarkan menggunakan pendekatan kubernetes object resource seperti berikut:
Berdasarkan diagram tersebut kita akan bagi menjadi 3 namespace yaitu default
, orders
dan customer
. Dalam masing-masing namespace memiliki service yang kita pisahkan berdasarkan scopenya yaitu
- Namespace
default
, terdiri dari pod dengan workload:nginx
berfungsi untuk routing (API Gateway) ke service-service sepertiorder
dancustomer
- Namespace
customer
, terdiri dari pod dengan workload:customerAPI
danMySQL
sebagai primary backing service - Namespace
order
, terdiri dari pod dengan workload:orderAPI
danPostgreSQL
sebagai primary backing service.
Containerize apps
Setelah kita men-design architecturenya untuk deploy ke orchestration container system seperti kubernetes. Tahap awal meng-implementasikan semua konsep tersebut adalah melakukan kontainerisasi (container image). Di tahap ini adalah paling dasar sebelum kita deploy diatas kubernetes, jika service/aplikasi tidak bisa dibuild ke container image maka sudah dipastikan tidak akan bisa lanjut ke tahap selanjutnya.
Okay langsung aja kita mulai buat containernya. Tetapi sebelum itu kita lihat lagi bagaimana cara deploy manual seperti section sebelumnya. Apa yang kita perlukan untuk menjalankan service tersebut???
- Java Development Kit (jdk-17 or later)
- Binary execute (jar)
Untuk vendor JDK yang kita gunakan di local environment menggunakan Oracle JDK 19, nah ini sebisa mungkin untuk versi dari SDK harus sama persis dengan yang terdapat di container. Karena Oracle JDK tidak tersedia secara public di docker hub, kita akan menggunakan vendor yang open-source yaitu OpenJDK dengan version yang sama yaitu openjdk-19
Sedangkan untuk binary execute atau file bundle yang telah dicomple kita bisa peroleh dengan menjalankan perintah:
Jika dijalankan hasilnya seperti berikut:
devops/k8s-springboot-microservice [main] » mvn clean -DskipTests package
[INFO] Reactor Summary for springboot-microservice 0.0.1-SNAPSHOT:
[INFO]
[INFO] springboot-microservice ............................ SUCCESS [ 1.777 s]
[INFO] --- jar:3.3.0:jar (default-jar) @ customer-api ---
[INFO] Building jar: /Users/dimasm93/Developer/dimas-maryanto.com/youtube/_projects/devops/k8s-springboot-microservice/customer/target/customer-api.jar
[INFO] customer-api ....................................... SUCCESS [ 6.182 s]
[INFO] --- jar:3.3.0:jar (default-jar) @ orders-api ---
[INFO] Building jar: /Users/dimasm93/Developer/dimas-maryanto.com/youtube/_projects/devops/k8s-springboot-microservice/orders/target/orders-api.jar
[INFO] orders-api ......................................... SUCCESS [ 2.735 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
Okay setelah semua kebutuhan terpenuhi, tahap selanjutnya kita buat Dockerfile
seperti berikut:
-
Dockerfile untuk customerAPI, simpan dalam folder
customer/Dockerfile
seperti berikut: -
Dockerfile untuk orderAPI, simpan dalam folder
order/Dockerfile
seperti berikut: -
Dan yang terakhir, tambahkan service
customerAPI
danorderAPI
dalamdocker-compose.yaml
seperti berikut:
Sekerang coba jalankan perintah berikut:
Jika dijalankan hasilnya seperti berikut:
devops/k8s-springboot-microservice [main] » docker compose build customerAPI orderAPI
[+] Building 5.1s (7/7) FINISHED
=> [internal] load metadata for docker.io/library/openjdk:19-oraclelinux8 4.7s
=> [1/2] FROM docker.io/library/openjdk:19-oraclelinux8@sha256:a5f2327c217367af3729670628c7ad66799cd890bde4e14fad02c2ae59552424 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 145B 0.0s
=> CACHED [2/2] ADD target/orders-api.jar spring-boot.jar 0.0s
=> exporting to image 0.1s
=> => naming to repository.dimas-maryanto.com:8087/dimmaryanto93/example/order-api:latest
=> [internal] load metadata for docker.io/library/openjdk:19-oraclelinux8 4.7s
=> [1/2] FROM docker.io/library/openjdk:19-oraclelinux8@sha256:a5f2327c217367af3729670628c7ad66799cd890bde4e14fad02c2ae59552424 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 145B
=> CACHED [2/2] ADD target/customer-api.jar spring-boot.jar 0.0s
=> exporting to image 1.8s
=> => naming to repository.dimas-maryanto.com:8087/dimmaryanto93/example/customer-api:latest
devops/k8s-springboot-microservice [main] » docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
repository.dimas-maryanto.com:8087/dimmaryanto93/example/order-api latest f27ba96797db 17 minutes ago 522MB
repository.dimas-maryanto.com:8087/dimmaryanto93/example/customer-api latest 9d589fe904f0 26 minutes ago 523MB
Setelah container image di build, sekarang coba jalankan dengan perintah berikut:
Maka seperti berikut outputnya:
devops/k8s-springboot-microservice [main] » docker compose up -d
[+] Running 7/7
⠿ Network k8s-springboot-microservice_default Created 0.2s
⠿ Volume "k8s-springboot-microservice_pg_data" Created 0.0s
⠿ Volume "k8s-springboot-microservice_mysql_data" Created 0.0s
⠿ Container k8s-springboot-microservice-mysql-1 Started 3.2s
⠿ Container k8s-springboot-microservice-postgres-1 Started 3.0s
⠿ Container k8s-springboot-microservice-customerAPI-1 Started 4.9s
⠿ Container k8s-springboot-microservice-orderAPI-1 Started 4.9s
devops/k8s-springboot-microservice [main] » docker compose ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
k8s-springboot-microservice-mysql-1 repository.dimas-maryanto.com:8086/mysql:8.0 "docker-entrypoint.s…" mysql About a minute ago Up 58 seconds 0.0.0.0:3306->3306/tcp, 33060/tcp
k8s-springboot-microservice-orderAPI-1 repository.dimas-maryanto.com:8087/dimmaryanto93/example/order-api:latest "java -Djava.securit…" orderAPI About a minute ago Up 56 seconds 0.0.0.0:9091->9091/tcp
k8s-springboot-microservice-postgres-1 repository.dimas-maryanto.com:8086/postgres:15 "docker-entrypoint.s…" postgres About a minute ago Up 58 seconds 0.0.0.0:5432->5432/tcp
Jika semua service sudah running dengan baik, sekarang coba temen-temen test lagi service tersebut apakah berjalan dengan baik di atas single container? jika sudah ok. Yeeey selamat temen-temen udah melewati level 1.
Berikutnya adalah temen-temen bisa push ke container registry. boleh ke DockerHub atau private registry dengan menggunakan perintah berikut:
Create kubernetes cluster on local
Setelah container image di-build dan publish ke container registry, tahap selanjutnya adalah deploy ke kubernetes cluster. Tetapi sebelum itu siapkan dulu kubernetes cluster untuk workload tersebut. Settingan cluster untuk microservice ini agak berbeda dengan sebelumnya yaitu seperti berikut:
nodes:
controlplane:
cpus: '2 cores'
memory: '2 GB'
worker-1:
cpus: '2 cores'
memory: '4 GB'
worker-2:
cpus: '2 cores'
memory: '4 GB'
Jadi kita akan buat menggunakan perintah berikut:
Jika dijalankan hasilnya seperti berikut:
~ » minikube start -p springboot-microservice \
--cpus 2 \
--memory 4G \
--insecure-registry 192.168.88.50:8086 \
--nodes 3
😄 [springboot-microservice] minikube v1.29.0 on Darwin 13.2.1
✨ Using the hyperkit driver based on user configuration
👍 Starting control plane node springboot-microservice in cluster springboot-microservice
🔥 Creating hyperkit VM (CPUs=2, Memory=4096MB, Disk=20000MB) ...
📦 Preparing Kubernetes v1.26.1 on containerd 1.6.15 ...
▪ Generating certificates and keys ...
▪ Booting up control plane ...
▪ Configuring RBAC rules ...
🔗 Configuring CNI (Container Networking Interface) ...
▪ Using image gcr.io/k8s-minikube/storage-provisioner:v5
🔎 Verifying Kubernetes components...
🌟 Enabled addons: default-storageclass, storage-provisioner
👍 Starting worker node springboot-microservice-m02 in cluster springboot-microservice
🔥 Creating hyperkit VM (CPUs=2, Memory=4096MB, Disk=20000MB) ...
👍 Starting worker node springboot-microservice-m03 in cluster springboot-microservice
🔥 Creating hyperkit VM (CPUs=2, Memory=4096MB, Disk=20000MB) ...
🔎 Verifying Kubernetes components...
🏄 Done! kubectl is now configured to use "springboot-microservice" cluster and "default" namespace by default
~ » minikube profile springboot-microservice
✅ minikube profile was successfully set to springboot-microservice
~ » minikube addons enable registry-creds
❗ registry-creds is a 3rd party addon and is not maintained or verified by minikube maintainers, enable at your own risk.
❗ registry-creds does not currently have an associated maintainer.
▪ Using image docker.io/upmcenterprises/registry-creds:1.10
🌟 The 'registry-creds' addon is enabled
~ » minikube addons configure registry-creds
Do you want to enable AWS Elastic Container Registry? [y/n]: n
Do you want to enable Google Container Registry? [y/n]: n
Do you want to enable Docker Registry? [y/n]: y
-- Enter docker registry server url: 192.168.88.50:8086
-- Enter docker registry username: admin
-- Enter docker registry password:
Do you want to enable Azure Container Registry? [y/n]: n
✅ registry-creds was successfully configured
~ » kubectl get node
NAME STATUS ROLES AGE VERSION
springboot-microservice Ready control-plane 4m52s v1.26.1
springboot-microservice-m02 Ready <none> 3m4s v1.26.1
springboot-microservice-m03 Ready <none> 86s v1.26.1
~ » minikube addons enable metrics-server
💡 metrics-server is an addon maintained by Kubernetes. For any concerns contact minikube on GitHub.
You can view the list of minikube maintainers at: https://github.com/kubernetes/minikube/blob/master/OWNERS
▪ Using image registry.k8s.io/metrics-server/metrics-server:v0.6.2
🌟 The 'metrics-server' addon is enabled
Jika pada pod registry-creds
error, temen-temen bisa menggunakan alternative lain yaitu dengan membuat sebuah secret dengan perintah serperti berikut:
where:
<your-registry-server>
is your Private Docker Registry FQDN. Usehttps://index.docker.io/v1/
for DockerHub otherwise192.168.88.50:8086
for private registry<your-name>
is your Docker username.<your-pword>
is your Docker password.<your-email>
is your Docker email.
Dan setelah itu, jika mau menjalankan pod dari insecure registry temen-temen perlu nambahkan property imagePullSecrets
seperti berikut contohnya:
Setelah cluster kubernetes ready, kita coba membuat simple 2 pod yang simple menggunakan nginx
dan httpd
setelah itu kita coba test cni antara ke dua pod tersebut dengan perintah seperti berikut:
Jika dijalankan maka hasilnya seperti berikut:
~ » kubectl run web1 --image 192.168.88.50:8086/nginx:mainline --port 80 && \
kubectl expose pod/web1 --port 80 --type ClusterIP
pod/web1 created
service/web1 exposed
~ » kubectl get pod
NAME READY STATUS RESTARTS AGE
web1 1/1 Running 0 56s
~ » kubectl run web2 --image 192.168.88.50:8086/httpd:latest --port 80 && \
kubectl expose pod/web2 --port 80 --type ClusterIP
pod/web2 created
service/web2 exposed
~ » kubectl get pod
NAME READY STATUS RESTARTS AGE
web1 1/1 Running 0 71s
web2 1/1 Running 0 30s
~ » kubectl exec web1 -- curl http://web2
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 45 100 45 0 0 918 0 --:--:-- --:--:<html><body><h1>It works!</h1></body></html>
-- --:--:-- 918
Jika temen-temen perhatikan pada saat membuat cluster, mengapa cluster ini menggunakan memory yang lebih besar yaitu 4G
di setiap nodenya, Jadi menentukan resource tidak hanya di sisi Pod dan Containernya saja tetapi juga pada cluster nodenya juga kita harus sesuaikan.
Deploy to Kubernetes cluster
Setelah cluster kubernetes siap dan kita coba test network connection antar pod bisa juga, tahap selanjutnya adalah kita buat Kubernetes resourcesnya sepert Pod, dan Service yang masing-masing di kelompokan berdasarkan Namespace berdasarkan design architecture Kubernetes resources sebelumnya.
Secara sturuktur kita kelompokan seperti berikut:
namespaces:
- default:
- nginx
- customer:
- customer-api
- mysql
- orders:
- orders-api
- postgresql
Seperti berikut untuk kubernetes resources dengan namespace customer-module
Kemudian kita coba jalankan dengan perintah berikut:
Maka hasilnya seperti berikut:
~ » kubectl apply -f kubernetes/ns-customer-api.yaml
namespace/customer-module created
configmap/mysql created
secret/mysql created
pod/mysql created
service/mysql created
pod/customer-api created
service/customer-api created
~ » kubectl get pod -n customer-module
NAME READY STATUS RESTARTS AGE
customer-api 1/1 Running 0 13s
mysql 1/1 Running 0 6m36s
~ » kubectl logs customer-api -n customer-module
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.0.2)
2023-02-25T06:49:26.684Z INFO 1 --- [ main] c.m.d.udemy.customer.MainApplication : Starting MainApplication v0.0.1-SNAPSHOT using Java 19 with PID 1 (/spring-boot.jar started by root in /)
2023-02-25T06:49:31.550Z INFO 1 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 9090 (http)
2023-02-25T06:49:33.287Z INFO 1 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
2023-02-25T06:49:33.356Z INFO 1 --- [ main] o.f.c.i.database.base.BaseDatabaseType : Database: jdbc:mysql://mysql:3306/customer_api (MySQL 8.0)
2023-02-25T06:49:33.728Z INFO 1 --- [ main] o.f.core.internal.command.DbValidate : Successfully validated 1 migration (execution time 00:00.090s)
2023-02-25T06:49:33.815Z INFO 1 --- [ main] o.f.c.i.s.JdbcTableSchemaHistory : Creating Schema History table `customer_api`.`flyway_schema_history` ...
2023-02-25T06:49:34.140Z INFO 1 --- [ main] o.f.core.internal.command.DbMigrate : Current version of schema `customer_api`: << Empty Schema >>
2023-02-25T06:49:34.184Z INFO 1 --- [ main] o.f.core.internal.command.DbMigrate : Migrating schema `customer_api` to version "20230211170102 - schema-customer"
2023-02-25T06:49:34.298Z INFO 1 --- [ main] o.f.core.internal.command.DbMigrate : Successfully applied 1 migration to schema `customer_api`, now at version v20230211170102 (execution time 00:00.190s)
2023-02-25T06:49:38.606Z INFO 1 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 9090 (http) with context path ''
2023-02-25T06:49:38.639Z WARN 1 --- [ main] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2023-02-25T06:49:38.685Z INFO 1 --- [ main] c.m.d.udemy.customer.MainApplication : Started MainApplication in 15.321 seconds (process running for 17.433)
~ » kubectl exec mysql -n customer-module -it -- mysql -e 'show tables;' --database customer_api -u customer_api -p
Enter password:
+------------------------+
| Tables_in_customer_api |
+------------------------+
| customer |
| flyway_schema_history |
+------------------------+
~ » kubectl exec customer-api -n customer-module -- curl --location --request GET 'localhost:9090/api/customer/v1/findById/cust01' -v
Note: Unnecessary use of -X or --request, GET is already inferred.
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 9090 (#0)
> GET /api/customer/v1/findById/cust01 HTTP/1.1
> Host: localhost:9090
> User-Agent: curl/7.61.1
> Accept: */*
>
0 0 0 0 0 0 0 0 --:--:-- 0:00:02 --:--:-- 0< HTTP/1.1 200
< Content-Type: application/json
< Transfer-Encoding: chunked
< Date: Sat, 25 Feb 2023 07:14:25 GMT
<
{ [100 bytes data]
100 94 0 94 0 0 30 0 --:--:--
{"id":"cust01","userId":"dimasm93","fullname":"Dimas Maryanto","alamat":"Bandung, Jawa Barat"}
Selanjutnya kita deploy untuk namespace orders
seperti berikut:
Kemudian kita coba jalankan dengan perintah berikut:
Jika dijalankan maka hasilnya seperti berikut:
~ » kubectl apply -f kubernetes/ns-orders-api.yaml
namespace/orders-module created
configmap/postgresql created
secret/postgresql created
pod/postgresql created
service/postgresql created
pod/orders-api created
service/orders-api created
~ » kubectl get pod -n orders-module
NAME READY STATUS RESTARTS AGE
orders-api 1/1 Running 0 15s
postgresql 1/1 Running 0 15s
~ » kubectl logs orders-api -n orders-module
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.0.2)
2023-02-25T07:05:01.905Z INFO 1 --- [ main] c.m.dimas.udemy.orders.MainApplication : Starting MainApplication v0.0.1-SNAPSHOT using Java 19 with PID 1 (/spring-boot.jar started by root in /)
2023-02-25T07:05:06.506Z INFO 1 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 9091 (http)
2023-02-25T07:05:07.808Z INFO 1 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
2023-02-25T07:05:07.888Z INFO 1 --- [ main] o.f.c.i.database.base.BaseDatabaseType : Database: jdbc:postgresql://postgresql:5432/order_api (PostgreSQL 15.1)
2023-02-25T07:05:07.985Z INFO 1 --- [ main] o.f.core.internal.command.DbValidate : Successfully validated 1 migration (execution time 00:00.041s)
2023-02-25T07:05:08.013Z INFO 1 --- [ main] o.f.c.i.s.JdbcTableSchemaHistory : Creating Schema History table "public"."flyway_schema_history" ...
2023-02-25T07:05:08.127Z INFO 1 --- [ main] o.f.core.internal.command.DbMigrate : Current version of schema "public": << Empty Schema >>
2023-02-25T07:05:08.160Z INFO 1 --- [ main] o.f.core.internal.command.DbMigrate : Migrating schema "public" to version "20230211171555 - create-order"
2023-02-25T07:05:08.225Z INFO 1 --- [ main] o.f.core.internal.command.DbMigrate : Successfully applied 1 migration to schema "public", now at version v20230211171555 (execution time 00:00.121s)
2023-02-25T07:05:24.876Z INFO 1 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 9091 (http) with context path ''
2023-02-25T07:05:25.288Z INFO 1 --- [ main] c.m.dimas.udemy.orders.MainApplication : Started MainApplication in 25.583 seconds (process running for 28.329)
~ » kubectl exec orders-api -n orders-module -- curl --location --request POST 'localhost:9091/api/order/v1/checkout' \
--header 'Content-Type: application/json' \
--data-raw '{
"userId": "cust01",
"item": "Macbook Pro 13\" (A1723)",
"qty": "2"
}' -v
Note: Unnecessary use of -X or --request, POST is already inferred.
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 9091 (#0)
> POST /api/order/v1/checkout HTTP/1.1
>
} [82 bytes data]
* upload completely sent off: 82 out of 82 bytes
< HTTP/1.1 500
< Content-Type: application/json
{"timestamp":"2023-02-25T08:05:26.037+00:00","status":500,"error":"Internal Server Error","path":"/api/order/v1100 204 0 122 100 82 163 109 --:--:-- --:--:-- --:--:-- 273
* Closing connection 0
Connecting other service from the another namespace
Setelah kita deploy ke kubernetes cluster, jika temen-temen perhatikan kembali output yang dihasilkan dari service orders-api
kita melakukan request ke endpoint /api/order/v1/checkout
hasilnya HTTP/Status 500, Internal Server Error
klo kita lihat dari log errornya pada service tersebut seperti berikut:
~ » kubectl logs orders-api -n orders-module
ith path [] threw exception [Request processing failed: org.springframework.web.client.ResourceAccessException: I/O error on GET request for "http://localhost:9090/api/customer/v1/findById/cust01": Connection refused] with root cause
java.net.ConnectException: Connection refused
at java.base/sun.nio.ch.Net.pollConnect(Native Method) ~[na:na]
at java.base/sun.nio.ch.Net.pollConnectNow(Net.java:672) ~[na:na]
at java.base/sun.nio.ch.NioSocketImpl.timedFinishConnect(NioSocketImpl.java:535) ~[na:na]
at java.base/sun.nio.ch.NioSocketImpl.connect(NioSocketImpl.java:585) ~[na:na]
at java.base/java.net.Socket.connect(Socket.java:666) ~[na:na]
at java.base/sun.net.NetworkClient.doConnect(NetworkClient.java:178) ~[na:na]
Nah jika temen-temen perhatikan pada log tersebut, terlihat ada error gak bisa connect ke localhost:9090/api/xxx/xxx
dari orders-api
, Sedangkan kita mau ngambil data dari service customer-api
. Lantas gimana caranya?
Kita sebelumnya udah membuat service untuk masing-masing pod seperti berikut:
~ » kubectl get service -n orders-module
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
orders-api NodePort 10.109.82.183 <none> 9091:31963/TCP 144m
postgresql NodePort 10.100.81.138 <none> 5432:32759/TCP 144m
~ » kubectl get service -n customer-module
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
customer-api NodePort 10.99.232.190 <none> 9090:31920/TCP 122m
mysql NodePort 10.96.9.233 <none> 3306:32554/TCP 172m
Nah jadi kalau dari kasus sebelumnya kita mau koneksi ke database maka kita bisa menggunakan hostname dari nama service tersebut misalnya jdbc:postgresql://postgresql:5432/xxxx
nah serupa dengan hal tersebut dengan mengkases service customer-api
dari orders-api
tapi bagaimana dengan berbeda namespase.
Kita bisa menggunakan Kubernetes DNS queries untuk memangil service lain yang terletak pada namespace lain, seperti berikut:
# /etc/resolv.conf
search <namespace>.svc.cluster.local <service>.<namespace>.cluster.local
Kita coba panggil service customer-api
dari pod order-api
dengan simple curl
seperti berikut:
Coba klo kita jalankan outputnya seperti berikut:
~ » kubectl exec orders-api -n orders-module -- curl --location --request GET 'http://customer-api.customer-module:9090/api/customer/v1/findById/cust01' -v
Note: Unnecessary use of -X or --request, GET is already inferred.
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0* Trying 10.99.232.190...
* TCP_NODELAY set
* Connected to customer-api.customer-module (10.99.232.190) port 9090 (#0)
> GET /api/customer/v1/findById/cust01 HTTP/1.1
> Host: customer-api.customer-module:9090
> User-Agent: curl/7.61.1
> Accept: */*
>
< HTTP/1.1 200
< Content-Type: application/json
< Transfer-Encoding: chunked
100 94 0 94 0 0 209 0 --:--:-- --:--:-- --:--:-- 209
* Connection #0 to host customer-api.customer-module left intact
{"id":"cust01","userId":"dimasm93","fullname":"Dimas Maryanto","alamat":"Bandung, Jawa Barat"}
Nah sekarang sudah okay bisa mendapatkan response, kita coba update specifikasi podnya dengan menambahkan variable SERVICE_CUSTOMER_HOST
, SERVICE_CUSTOMER_PORT
, SERVICE_CUSTOMER_PROTO
dengan membuat configmap dan tambahkan ke spec pod orders-api
seperti berikut:
Sekarang kita coba jalankan dengan perintah berikut:
Jika dijalankan outputnya seperti berikut:
~ » kubectl delete pod/orders-api -n orders-module
pod "orders-api" deleted
~ » kubectl apply -f kubernetes/ns-orders-api.yaml
configmap/orders-api created
pod/orders-api created
~ » kubectl get pod -n orders-module
NAME READY STATUS RESTARTS AGE
orders-api 1/1 Running 0 40s
postgresql 1/1 Running 0 166m
~ » kubectl logs orders-api -n orders-module
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.0.2)
2023-02-25T09:51:19.825Z INFO 1 --- [ main] c.m.dimas.udemy.orders.MainApplication : Started MainApplication in 42.845 seconds (process running for 46.726)
~ » kubectl exec orders-api -n orders-module -- curl --location --request POST 'localhost:9091/api/order/v1/checkout' \
--header 'Content-Type: application/json' \
--data-raw '{
"userId": "cust01",
"item": "Macbook Pro 13\" (A1723)",
"qty": "2"
}' -v
* TCP_NODELAY set
* Connected to localhost (::1) port 9091 (#0)
> POST /api/order/v1/checkout HTTP/1.1
> Host: localhost:9091
> User-Agent: curl/7.61.1
> Accept: */*
> Content-Type: application/json
> Content-Length: 82
>
} [82 bytes data]
* upload completely sent off: 82 out of 82 bytes
100 82 0 0 100 82 0 19 0:00:04 0:00:04 --:--:-- 19
{"id":"bb9f7d1e-92ef-4307-b412-ce112fbc01f6","createdDate":"2023-02-25T09:52:58.443768159","customer":{"id":"cust01","userId":"dimasm93","fullname":"Dimas Maryanto","alamat":"Bandung, Jawa Barat"},"item":"Macbook Pro 13\" (A1723)","qty":2}
< HTTP/1.1 200
Okay sampai sini kita sudah berhasil membuat microservice berjalan dengan baik di kubernetes, selanjutnya adalah kita buat feature kubernetes lebih advance lagi.
Specify container probes
Okay, sebelumnya kita khan sudah deploy container springboot untuk service customer-api
dan orders-api
ke Kubernetes cluster, hanya masih belum optimal contohnya startup service masih lama, kemudian jika database connection mati belum ada feature auto recover. Nah jadi untuk menambahkan feature tersebut ada yang perlu kita tambahkan pada framework springboot tersebut yaitu menggunakan lib spring-boot-starter-actuator
yang kita tambahkan ke file pom.xml
seperti berikut:
Dan edit/tambahkan property management.health
pada file src/main/resources/application.yaml
seperti berikut:
Jika sudah coba jalankan dengan menggunakan perintah berikut
Jika sudah coba akses endpoint localhost:9090/actuator/health hasilnya seperti berikut:
~ » curl localhost:9090/actuator/health
{"status":"UP","components":{"db":{"status":"UP"},"diskSpace":{"status":"UP"},"livenessState":{"status":"UP"},"ping":{"status":"UP"},"readinessState":{"status":"UP"}},"groups":["liveness","readiness"]}%
Jika sudah seperti itu outputnya, lakukan hal yang sama dengan service orders-api
dan kemudian coba re-build container image kemudian push menggunakan perintah berikut:
Setelah kita push container image dari kedua service tersebut, sekarang kita bisa tambahkan feature container probe di kubernetes resource object pod seperti berikut:
Sekarang coba jalankan dengan perintah berikut:
Maka hasilnya seperti berikut:
~ » kubectl delete -f kubernetes/pod-container-probes.yaml
pod "orders-api" deleted
pod "customer-api" deleted
~ » kubectl apply -f kubernetes/pod-container-probes.yaml
pod/orders-api created
pod/customer-api created
~ » kubectl get pod -A
NAMESPACE NAME READY STATUS RESTARTS AGE
customer-module customer-api 0/1 ContainerCreating 0 6s
customer-module mysql 1/1 Running 0 9m29s
orders-module orders-api 0/1 Running 0 6s
orders-module postgresql 1/1 Running 0 9m12s
~ » kubectl describe pod customer-api -n customer-module
Name: customer-api
Namespace: customer-module
Priority: 0
Service Account: default
Node: springboot-microservice-m03/192.168.64.25
Start Time: Sat, 25 Feb 2023 23:53:15 +0700
Labels: app=customer-api
project=customer
tier=backend
Status: Running
IP: 10.244.2.9
IPs:
IP: 10.244.2.9
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 20s default-scheduler Successfully assigned customer-module/customer-api to springboot-microservice-m03
Normal Pulled 18s kubelet Container image "192.168.88.50:8086/dimmaryanto93/example/customer-api:v2" already present on machine
Normal Created 17s kubelet Created container customer-api
Normal Started 17s kubelet Started container customer-api
~ » kubectl get pod -n customer-module
NAME READY STATUS RESTARTS AGE
customer-api 1/1 Running 0 57s
mysql 1/1 Running 0 19m
~ » kubectl get pod -n orders-module
NAME READY STATUS RESTARTS AGE
orders-api 0/1 Running 0 14s
postgresql 1/1 Running 0 20m
~ » kubectl describe pod orders-api -n orders-module
Name: orders-api
Namespace: orders-module
Priority: 0
Service Account: default
Node: springboot-microservice-m03/192.168.64.25
Start Time: Sun, 26 Feb 2023 00:03:58 +0700
Labels: app=orders-api
project=orders-api
tier=backend
Annotations: <none>
Status: Running
IP: 10.244.2.14
IPs:
IP: 10.244.2.14
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 26s default-scheduler Successfully assigned orders-module/orders-api to springboot-microservice-m03
Normal Pulled 25s kubelet Container image "192.168.88.50:8086/dimmaryanto93/example/order-api:v2" already present on machine
Normal Created 25s kubelet Created container orders-api
Normal Started 25s kubelet Started container orders-api
~ » kubectl get pod -n orders-module
NAME READY STATUS RESTARTS AGE
orders-api 1/1 Running 0 70s
postgresql 1/1 Running 0 20m
Specify resource request and limit
Tahap selanjutnya untuk mengoptimalkan penggunaan resource node/worker kita harus specifikasi resource request dan limit. Resource request dan limit ini sangat membantu untuk menambahkan alert pada system kubernetes agar pod tidak menggunakan resource yang berlebih atau masih bisa mencukupi resource yang ada (resource availablity).
Untuk menentukan resource request dan limit ini kita harus mengetahui dulu titik minimum dari workload aplikasi kita dan titik optimal (rata-rata operational) dengan cara melihat activity process, metrics untuk penggunaan cpus dan memory. Sebagai contoh disini saya menggunakan Activity Monitor untuk melihat penggunaan system memory dan cpus.
Berikut adalah cpu yang terpakai dari workload customer-api
:
Berikut adalah memory yang terpakai:
Atau selain itu juga kita bisa expose dari service tersebut dengan menambahkan beberapa property metrics
pada src/main/resources/application.yaml
seperti berikut:
Kemudian coba jalankan dengan perintah:
Jika dijalankan hasilnya seperti berikut:
~ » curl localhost:9090/actuator/metrics
{
"names": [
"application.ready.time",
"application.started.time",
"disk.free",
"disk.total",
"hikaricp.connections",
"hikaricp.connections.acquire",
"hikaricp.connections.active",
"hikaricp.connections.creation",
"hikaricp.connections.idle",
"hikaricp.connections.max",
"hikaricp.connections.min",
"hikaricp.connections.pending",
"hikaricp.connections.timeout",
"hikaricp.connections.usage",
"http.server.requests.active",
"jdbc.connections.active",
"jdbc.connections.idle",
"jdbc.connections.max",
"jdbc.connections.min",
"jvm.buffer.count",
"jvm.buffer.memory.used",
"jvm.buffer.total.capacity",
"jvm.classes.loaded",
"jvm.classes.unloaded",
"jvm.compilation.time",
"jvm.gc.live.data.size",
"jvm.gc.max.data.size",
"jvm.gc.memory.allocated",
"jvm.gc.memory.promoted",
"jvm.gc.overhead",
"jvm.info",
"jvm.memory.committed",
"jvm.memory.max",
"jvm.memory.usage.after.gc",
"jvm.memory.used",
"jvm.threads.daemon",
"jvm.threads.live",
"jvm.threads.peak",
"jvm.threads.states",
"logback.events",
"process.cpu.usage",
"process.files.max",
"process.files.open",
"process.start.time",
"process.uptime",
"system.cpu.count",
"system.cpu.usage",
"system.load.average.1m",
"tomcat.sessions.active.current",
"tomcat.sessions.active.max",
"tomcat.sessions.alive.max",
"tomcat.sessions.created",
"tomcat.sessions.expired",
"tomcat.sessions.rejected"
]
}%
~ » curl localhost:9090/actuator/metrics/jvm.info
{
"name": "jvm.info",
"description": "JVM version info",
"measurements": [
{
"statistic": "VALUE",
"value": 1
}
],
"availableTags": [
{
"tag": "vendor",
"values": [
"Oracle Corporation"
]
},
{
"tag": "runtime",
"values": [
"Java(TM) SE Runtime Environment"
]
},
{
"tag": "version",
"values": [
"19.0.1+10-21"
]
}
]
}
Temen-temen bisa check menggunakan beberapa property seperti jvm.memory.used
, system.cpu.usage
, process.cpu.usage
dan lain-lain. Okay selanjutnya kita build ulang container imagenya supaya metrics tersebut terexpose dengan perintah
Okay jadi disini saya simpulkan bahwa batas minimum atau resource request yaitu cpu = 100m
dan memory = 250Mi
sedangkan untuk maximum limit yaitu cpu = 1500m
dan memory = 2000Mi
. Sekarang kita coba update file pod.yaml menjadi seperti berikut:
Sekarang kita coba jalankan file tersebut dengan perintah berikut:
Maka hasilnya seperti berikut:
~ » kubectl delete pod customer-api -n customer-module && \
kubectl delete pod orders-api -n orders-module
pod "customer-api" deleted
pod "orders-api" deleted
~ » kubectl top node
NAME CPU(cores) CPU% MEMORY(bytes) MEMORY%
springboot-microservice 497m 24% 1360Mi 34%
springboot-microservice-m02 266m 13% 1571Mi 40%
springboot-microservice-m03 491m 24% 919Mi 23%
~ » kubectl apply -f kubernetes/pod-resource-request-limit.yaml
pod/customer-api created
pod/orders-api created
~ » kubectl get pod -n customer-module
NAME READY STATUS RESTARTS AGE
customer-api 1/1 Running 0 55s
mysql 1/1 Running 0 6m53s
~ » kubectl top pod customer-api -n customer-module
NAME CPU(cores) MEMORY(bytes)
customer-api 536m 260Mi
~ » kubectl get pod -n orders-module
NAME READY STATUS RESTARTS AGE
orders-api 1/1 Running 0 91s
postgresql 1/1 Running 0 7m6s
~ » kubectl top pod orders-api -n orders-module
NAME CPU(cores) MEMORY(bytes)
orders-api 9m 273Mi
~ » kubectl top node
NAME CPU(cores) CPU% MEMORY(bytes) MEMORY%
springboot-microservice 151m 7% 1349Mi 34%
springboot-microservice-m02 62m 3% 1861Mi 47%
springboot-microservice-m03 45m 2% 1184Mi 30%
Implement API Gateway using nginx reverse proxy
Okay sebelumnya kita sudah deploy workload ke kubernetes cluster, kemudian juga sudah lakukan tuning workloadnya supaya bisa berjalan dengan baik di kubernetes dengan menambahkan container probe dan resource request & limit. Ini adalah ujung dari studikasus ini yaitu kita akan memasang API Gateway. okay temen-temen ada yang tau apa itu API Gateway? mengapa harus menggunakan API Gateway?
An API gateway is an API management tool that sits between a client and a collection of backend services. An API gateway acts as a reverse proxy to accept all application programming interface (API) calls, aggregate the various services required to fulfill them, and return the appropriate result.
Nah karena kita punya banyak services (microservices) jadi pada implementasinya exposing service tersebut tidak satu-per-satu atau setiap service memiliki port sendiri tetapi biasanya menggunakan methode DMZ (Demilitarized Zone) atau satu gerbang untuk beberapa service jika kita ilustrasikan seperti berikut:
Untuk implementasi API Gateway ini ada lumayan banyak salah satunya adalah apisix, nginx, KrakenD dan lain-lain. Untuk kasus kali ini kita akan menggunakan yang simple dulu ya yaitu Nginx reverse proxy dengan configurasi seperti berikut:
Sekarang coba jalankan dengan perintah berikut:
Maka hasilnya seperti berikut:
~ » kubectl apply -f kubernetes/api-gateway
configmap/api-gateway unchanged
pod/api-gateway created
service/api-gateway configured
~ » kubectl get pod,service
NAME READY STATUS RESTARTS AGE
pod/api-gateway 1/1 Running 0 54s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/api-gateway NodePort 10.107.243.20 <none> 80:30001/TCP 167m
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 27h
~ » kubectl cluster-info
Kubernetes control plane is running at https://192.168.64.23:8443
CoreDNS is running at https://192.168.64.23:8443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
~ » curl http://192.168.64.23:30001/customers/api/customer/v1/findById/cust01 -v
* Trying 192.168.64.23:30001...
* Connected to 192.168.64.23 (192.168.64.23) port 30001 (#0)
> GET /customer/api/customer/v1/findById/cust01 HTTP/1.1
{"id":"cust01","userId":"dimasm93","fullname":"Dimas Maryanto","alamat":"Bandung, Jawa Barat"}
~ » curl --location --request POST 'http://192.168.64.23:30001/orders/api/order/v1/checkout' \
--header 'Content-Type: application/json' \
--data-raw '{
"userId": "cust01",
"item": "Macbook Pro 13\" (A1723)",
"qty": "2"
}' -v
Note: Unnecessary use of -X or --request, POST is already inferred.
* Trying 192.168.64.23:30001...
* Connected to 192.168.64.23 (192.168.64.23) port 30001 (#0)
> POST /order/api/order/v1/checkout HTTP/1.1
{"id":"2c25434d-00ec-46e6-b37f-c080291019b2","createdDate":"2023-02-26T09:15:03.570489716","customer":{"id":"cust01","userId":"dimasm93","fullname":"Dimas Maryanto","alamat":"Bandung, Jawa Barat"},"item":"Macbook Pro 13\" (A1723)","qty":2}%
Nah jadi disini kesimpulannya yang kita expose adalah ip 192.168.64.23
dengan port 30001
saja ke router/loadbalancer yang selanjutnya kita bisa lakukan untuk port forwarding ke ip public.
-
Referensi
https://kubernetes.io/docs/home/
https://hub.docker.com/_/openjdk
https://spring.io/projects/spring-boot
https://www.urosht.dev/blog/kubernetes-probes-with-spring-boot/
https://www.redhat.com/en/topics/api/what-does-an-api-gateway-do#:~:text=and%20serverless%20environments-,Overview,and%20return%20the%20appropriate%20result.