External Services dengan Docker Compose
Di jaman sekarang, aplikasi yang saya buat umumnya sudah mengadopsi arsitektur Cloud Native
, yaitu aplikasi yang bisa berjalan dengan baik di environment cloud. Penjelasan detail tentang aplikasi Cloud Native
ini sudah pernah saya jelaskan di Youtube.
Singkatnya, aplikasi cloud native biasanya menggunakan beberapa external services, misalnya:
- Database relasional (MySQL, PostgreSQL, dsb)
- Message Broker (Kafka, dsb)
- Database non-relasional / NoSQL (Redis, Elasticsearch, dsb)
External service ini harus bisa diganti dengan implementasi lain, misalnya dari local ke cloud, dari cloud ke on-premise, dan berbagai skenario lainnya.
Agar kita bisa develop aplikasi dengan external service tersebut, maka development environment kita (misalnya PC atau laptop) harus menyediakan service yang dibutuhkan. Kebutuhan ini berbeda-beda antar project. Misal di project A saya menggunakan MySQL. Project B menggunakan PostgreSQL. Project C menggunakan PostgreSQL dan Kafka. Project D menggunakan MySQL dan Redis. Project E menggunakan ElasticSearch dan MongoDB. Dan seterusnya.
Apabila kita harus menginstal semua service tersebut, dan jalan pada waktu booting, waduh berapa RAM yang harus kita sediakan. Belum lagi nanti kita harus membuatkan database instance untuk masing-masing aplikasi. Bisa-bisa kita instal MySQL yang isinya belasan database sesuai dengan project yang pernah kita tangani.
Ini juga menjadi lebih sulit buat para team leader atau arsitek yang harus melakukan supervisi ke banyak project sekaligus. Oleh karena itu, kita perlu membuat sistem kerja yang baik agar tidak kusut.
Solusi yang biasa saya gunakan, terdiri dari 3 aspek:
- Project harus portable
- Project harus self-contained
- External service yang dibutuhkan, harus dideklarasikan dengan konsep Infrastructure as a Code
Mari kita elaborasi …
Project Portability
Project yang kita kerjakan, harus bisa langsung dibuka dan dibuild di semua environment (Windows, Linux, Mac) sejak kita melakukan git clone
. Semua library yang dibutuhkan harus bisa didapatkan dari repository, baik yang publik di internet, ataupun di server internal kita. Di Java, ini bisa dilakukan dengan menggunakan Maven atau Gradle. Apa itu Maven, bisa dibaca di artikel ini atau ditonton di video berikut:
Dengan menggunakan Maven atau Gradle, maka project kita akan bisa dibuka di segala editor, misalnya Visual Studio Code, Intellij IDEA, Netbeans, Eclipse, dan sebagainya.
Project Self Containment
Project kita harus bisa dijalankan (run) di semua environment, hanya dengan bermodalkan hasil git clone
. Ini artinya yang kita simpan di source repository bukan cuma source code. Tapi juga file lain seperti:
- konfigurasi koneksi database
- script untuk membuat database
- data-data awal agar aplikasi kita bisa dijalankan (misalnya data kota/kabupaten/kecamatan, data tingkat pendidikan, dsb)
- file pendukung lain seperti image untuk icon, API key, credential file, dan sebagainya
Semua file-file di atas harus ikut disimpan di source repository, biasanya jaman now orang pakai Git. Kemudian, script pembuatan database dan pengisian data awal juga harus otomatis dijalankan pada waktu aplikasi di run. Tidak boleh ada kegiatan manual orang login ke database dan menjalankan perintah-perintah. Karena kalau ada kegiatan manual, akan ada orang yang lupa, langkah yang ketinggalan, salah ketik, dan human error lain yang mengakibatkan aplikasi gagal jalan.
Di Java biasanya kita menggunakan migration tools seperti FlywayDB atau Liquibase.
Infrastructure as a Code
Berikutnya kita bahas tentang external services. Jaman dulu, saya menginstal MySQL dan PostgreSQL di laptop, walaupun diset tidak start pada waktu boot. Akan tetapi, ini cukup merepotkan, karena untuk tiap project yang akan dijalankan, saya harus create user dan create database dulu. Nama database, username/password database harus disesuaikan dengan konfigurasi masing-masing project. Demikian juga versinya. MySQL versi 5 tidak kompatibel dengan versi 8. Padahal belum tentu semua project bisa dinaikkan ke MySQL 8. Jadinya kita harus mencatat aplikasi mana pakai MySQL versi berapa.
Ada cara yang lebih efektif daripada membuat catatan tersebut, yaitu langsung saja kita tuliskan dalam bentuk file docker-compose
. Dengan demikian, file ini bisa langsung kita jalankan. Docker akan mengunduh versi yang sesuai, kemudian membuatkan database dengan nama, username, dan password yang kita tentukan.
Berikut adalah contoh file docker-compose.yml
untuk database MySQL
services:
db-belajar:
image: mysql
platform: linux/x86_64
environment:
- MYSQL_RANDOM_ROOT_PASSWORD=yes
- MYSQL_DATABASE=belajardb
- MYSQL_USER=belajar
- MYSQL_PASSWORD=belajar123
ports:
- 3306:3306
volumes:
- ./db-belajar:/var/lib/mysql
Ini untuk database PostgreSQL
services:
db-belajar:
image: postgres
environment:
- POSTGRES_DB=belajar-db
- POSTGRES_USER=belajar
- POSTGRES_PASSWORD=belajar123
ports:
- 5432:5432
volumes:
- ./db-belajar:/var/lib/postgresql/data
Bila aplikasi kita butuh PostgreSQL dan Kafka sekaligus, tinggal kita pasang keduanya seperti ini
services:
db-belajar:
image: postgres:14
environment:
- POSTGRES_DB=belajar-db
- POSTGRES_USER=belajar
- POSTGRES_PASSWORD=belajar123
ports:
- 5432:5432
volumes:
- ./db-belajar:/var/lib/postgresql/data
zookeeper:
image: confluentinc/cp-zookeeper
container_name: zookeeper
environment:
ZOOKEEPER_CLIENT_PORT: 2181
ZOOKEEPER_TICK_TIME: 2000
kafka:
image: confluentinc/cp-kafka
container_name: kafka-broker
depends_on:
- zookeeper
ports:
- 9092:9092
environment:
KAFKA_BROKER_ID: 1
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_INTERNAL:PLAINTEXT
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092,PLAINTEXT_INTERNAL://broker:29092
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
Dengan adanya docker-compose
ini, kita tinggal menjalankan perintah docker-compose up
di masing-masing folder project. Dia akan langsung mengunduh versi yang sesuai, membuatkan database, username/password, dan kita tinggal akses di port yang ditentukan. Setelah selesai, tekan Ctrl-C
, kemudian docker-compose down
, dan hapus file databasenya.
Jangan lupa daftarkan folder yang berisi data testing tadi (db-timezone
, db-authserver
) ke .gitignore
supaya tidak ikut tersimpan di git.
Connect ke Database dalam Docker
Apabila kita hanya bermodalkan Docker
untuk menjalankan database, tanpa menginstal MySQL atau PostgreSQL, kita tidak akan bisa menjalankan aplikasi commandline mysql
dan psql
karena aplikasi tersebut tidak terinstal. Contohnya, bila kita mencoba connect seperti ini
psql -h 127.0.0.1 -U belajar -d belajar-db
Maka kita akan mendapatkan output seperti ini
zsh: command not found: psql
Oleh karena itu, kita harus menjalankan psql
tersebut di dalam docker container postgresql. Kita cari dulu nama containernya dengan perintah docker ps -a
. Outputnya seperti ini
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
0c576d91f55d postgres "docker-entrypoint.s…" About a minute ago Up About a minute 0.0.0.0:5432->5432/tcp belajar-vault-db-belajar-1
Kita bisa connect ke database dengan perintah berikut
docker exec -it belajar-vault-db-belajar-1 psql -U belajar -d belajar-db
Setelah dijalankan, kita akan mendapatkan prompt psql seperti ini
psql (16.3 (Debian 16.3-1.pgdg120+1))
Type "help" for help.
belajar-db=#
Sedangkan untuk MySQL, perintahnya sebagai berikut
docker exec -it nama-container-mysql mysql -u belajar belajardb -p
Kita akan disambut dengan prompt password. Setelah kita masukkan password, maka kita akan mendapati prompt mysql seperti ini
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 9.0.0 MySQL Community Server - GPL
Copyright (c) 2000, 2024, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql>
Demikian tips untuk menyiapkan development environment agar kita mudah menangani banyak project sekaligus.
Untuk yang butuh penjelasan secara visual, bisa menonton video penjelasannya di Youtube.
Docker Compose Komplit
Sebagai referensi, berikut file docker-compose.yml
yang saya gunakan untuk training microservices. Asal laptopnya kuat, kita bisa jalankan semua service ini untuk melayani studi kasus microservice yang kita demokan.
services:
db-rekening:
image: postgres
environment:
- POSTGRES_DB=rekening-db
- POSTGRES_USER=rekening
- POSTGRES_PASSWORD=rekening123
ports:
- 54321:5432
volumes:
- ../rekening/db-rekening:/var/lib/postgresql/data
db-pembayaran:
image: postgres
environment:
- POSTGRES_DB=pembayaran-db
- POSTGRES_USER=pembayaran
- POSTGRES_PASSWORD=pembayaran123
ports:
- 54322:5432
volumes:
- ../pembayaran/db-pembayaran:/var/lib/postgresql/data
prometheus:
image: prom/prometheus
container_name: prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
ports:
- 9090:9090
restart: unless-stopped
volumes:
- ./prometheus/config:/etc/prometheus
- ./prometheus/data:/prometheus
grafana:
image: grafana/grafana
container_name: grafana
ports:
- 3000:3000
restart: unless-stopped
environment:
- GF_SECURITY_ADMIN_USER=admin
- GF_SECURITY_ADMIN_PASSWORD=grafana
volumes:
- ./grafana/config:/etc/grafana/provisioning/datasources
- ./grafana/data:/var/lib/grafana
zipkin:
image: openzipkin/zipkin
ports:
- 9411:9411
elasticsearch:
image: elasticsearch:7.17.23
environment:
discovery.type: single-node
ES_JAVA_OPTS: "-Xmx256m -Xms256m"
ports:
- "9200:9200"
logstash:
image: logstash:7.17.23
command: -f /etc/logstash/conf.d/
environment:
LS_JAVA_OPTS: "-Xmx256m -Xms256m"
volumes:
- ./logstash/config:/etc/logstash/conf.d/
ports:
- "5001:5001"
depends_on:
- elasticsearch
kibana:
image: kibana:7.17.23
ports:
- "5601:5601"
depends_on:
- elasticsearch
kafka:
image: 'bitnami/kafka:latest'
environment:
- KAFKA_CFG_NODE_ID=0
- KAFKA_CFG_PROCESS_ROLES=controller,broker
- KAFKA_CFG_CONTROLLER_QUORUM_VOTERS=0@kafka:9093
- KAFKA_CFG_CONTROLLER_LISTENER_NAMES=CONTROLLER
- KAFKA_CFG_LISTENERS=PLAINTEXT://:9092,CONTROLLER://:9093,EXTERNAL://0.0.0.0:9094
- KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://kafka:9092,EXTERNAL://172.16.2.96:9094
- KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=CONTROLLER:PLAINTEXT,EXTERNAL:PLAINTEXT,PLAINTEXT:PLAINTEXT
ports:
- '9092:9092'
- '9094:9094'
db-keycloak:
image: postgres
volumes:
- ./keycloak-db:/var/lib/postgresql/data
environment:
POSTGRES_DB: keycloakdb
POSTGRES_USER: keycloak
POSTGRES_PASSWORD: keycloak1234
keycloak:
image: quay.io/keycloak/keycloak:23.0.6
command: start
environment:
KC_HOSTNAME: localhost
KC_HOSTNAME_PORT: 20000
KC_HOSTNAME_STRICT_BACKCHANNEL: false
KC_HTTP_ENABLED: true
KC_HOSTNAME_STRICT_HTTPS: false
KC_HEALTH_ENABLED: true
KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: admin1234
KC_DB: postgres
KC_DB_URL: jdbc:postgresql://db-keycloak/keycloakdb
KC_DB_USERNAME: keycloak
KC_DB_PASSWORD: keycloak1234
ports:
- 20000:8080
depends_on:
- db-keycloak
Selamat mencoba, semoga bermanfaat …