Tampilan
Kenapa penting
Kode yang lulus test belum tentu siap melayani pengguna. Production menuntut hal yang tidak terlihat di laptop: image yang aman, deploy yang bisa dibatalkan, perilaku jelas saat beban naik atau satu service mati, dan dokumentasi agar orang lain (termasuk kamu enam bulan lagi) bisa mengoperasikannya. Bab ini menutup capstone dengan membawa enam service ke cluster kind, mengujinya dengan beban dan kegagalan, lalu menilai kesiapannya dengan checklist yang sama seperti tim platform di tempat kerja. Bagian inilah yang paling membedakan portofolio "pernah coba microservices" dari "bisa menjalankan microservices".
Konsep inti
Image per service
Spring Boot bisa membuat image tanpa Dockerfile lewat Cloud Native Buildpacks, tapi Dockerfile multi-stage memberi kontrol penuh dan mudah dijelaskan. Pola yang dipakai semua service:
text
# Potongan — services/order-service/Dockerfile
FROM eclipse-temurin:25-jdk AS build
WORKDIR /src
COPY . .
RUN ./mvnw -q -pl services/order-service -am package -DskipTests
FROM eclipse-temurin:25-jre
RUN useradd --system --uid 10001 app
USER 10001
WORKDIR /app
COPY --from=build /src/services/order-service/target/*.jar app.jar
ENTRYPOINT ["java", "-XX:MaxRAMPercentage=75", "-jar", "app.jar"]Poin penting: build dengan -pl ... -am agar hanya module itu dan dependensinya yang dibangun; jalankan sebagai user non-root; MaxRAMPercentage agar heap mengikuti limit memori container. Tag image memakai SHA commit, bukan latest, sehingga tiap deploy bisa dilacak dan di-rollback.
Helm chart per service
Keenam service bentuknya mirip, jadi buat satu chart umum deploy/charts/service dan file values per service.
yaml
# deploy/values/order-service.yaml
image:
repository: ghcr.io/kamu/order-service
tag: "3f2c9ab"
replicas: 2
env:
SPRING_PROFILES_ACTIVE: k8s
SPRING_DATASOURCE_URL: jdbc:postgresql://order-db:5432/orders
secretEnv:
SPRING_DATASOURCE_PASSWORD: { secret: order-db, key: password }
resources:
requests: { cpu: 250m, memory: 512Mi }
limits: { memory: 768Mi }yaml
# Potongan — deploy/charts/service/templates/deployment.yaml
spec:
replicas: {{ .Values.replicas }}
strategy:
rollingUpdate: { maxUnavailable: 0, maxSurge: 1 }
template:
spec:
terminationGracePeriodSeconds: 40
containers:
- name: app
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
ports: [{ containerPort: 8080 }]
readinessProbe:
httpGet: { path: /actuator/health/readiness, port: 8080 }
periodSeconds: 5
livenessProbe:
httpGet: { path: /actuator/health/liveness, port: 8080 }
initialDelaySeconds: 30
resources: {{- toYaml .Values.resources | nindent 12 }}maxUnavailable: 0 menjamin versi lama tetap melayani sampai pod baru ready. Kalau pod baru tidak pernah ready, rolling update berhenti dengan sendirinya — itu jaring pengaman pertama.
Pipeline CI/CD per service
Di monorepo, tiap service punya workflow sendiri yang dipicu filter path. Contoh GitHub Actions:
yaml
# Potongan — .github/workflows/order-service.yaml
on:
push:
branches: [main]
paths:
- "services/order-service/**"
- "platform/**"
- "contracts/**"
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with: { distribution: temurin, java-version: "25", cache: maven }
- run: ./mvnw -pl services/order-service -am verify # unit + Testcontainers + kontrak
- run: docker build -f services/order-service/Dockerfile -t ghcr.io/kamu/order-service:${{ github.sha }} .
- run: docker push ghcr.io/kamu/order-service:${{ github.sha }}
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- run: >
helm upgrade --install order-service deploy/charts/service
-f deploy/values/order-service.yaml
--set image.tag=${{ github.sha }} --atomic --timeout 5m--atomic membuat Helm otomatis rollback bila release gagal dalam batas waktu. Untuk cluster lokal kind, langkah deploy dijalankan manual (atau runner self-hosted), dan image dimuat dengan kind load docker-image.
Deploy ke kind
bash
kind create cluster --name shop
kubectl create namespace shop
helm install kafka oci://registry-1.docker.io/bitnamicharts/kafka -n shop # atau manifest KRaft sendiri
for svc in catalog order payment inventory notification; do
kind load docker-image ghcr.io/kamu/${svc}-service:dev --name shop
helm upgrade --install ${svc}-service deploy/charts/service \
-f deploy/values/${svc}-service.yaml --set image.tag=dev -n shop
done
kubectl -n shop get pods -wLoad test checkout dengan k6
k6 menulis skenario dalam JavaScript dan memberi thresholds yang membuat test gagal bila target tidak tercapai — cocok untuk CI.
text
// checkout.js — dijalankan: k6 run checkout.js
import http from 'k6/http';
import { check } from 'k6';
export const options = {
stages: [
{ duration: '1m', target: 50 },
{ duration: '3m', target: 50 },
{ duration: '30s', target: 0 },
],
thresholds: {
http_req_failed: ['rate<0.01'],
http_req_duration: ['p(95)<500'],
},
};
export default function () {
const res = http.post('http://localhost:8080/api/orders',
JSON.stringify({ items: [{ sku: 'SKU-1', qty: 1 }] }),
{ headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${__ENV.TOKEN}` } });
check(res, { 'status 201': (r) => r.status === 201 });
}Cara membaca hasil:
| Gejala | Dugaan pertama | Cara memastikan |
|---|---|---|
p(95) jauh di atas p(50) | antrean: pool koneksi habis, lock baris stok | metrik hikaricp_connections_pending, trace lambat |
| error 5xx naik seiring VU | pod OOMKilled atau timeout downstream | kubectl get pods, event, log |
| throughput datar walau VU naik | CPU limit/throttling atau satu partisi Kafka | metrik CPU throttling, lag per partisi |
HTTP cepat, pesanan lama CONFIRMED | consumer lag di saga | metrik consumer lag Kafka |
Baris terakhir penting untuk sistem event-driven: endpoint checkout cepat karena hanya menulis PENDING + outbox, tapi pengalaman pengguna ditentukan oleh waktu sampai konfirmasi. Ukur juga itu, misalnya lewat metrik custom selisih created_at dan waktu konfirmasi.
Chaos kecil
Tujuannya membuktikan desain resilience, bukan merusak cluster. Dua eksperimen wajib:
- Matikan pembayaran:
kubectl -n shop scale deploy payment-service --replicas=0saat k6 berjalan. Harapan: checkout tetap201, event menumpuk, pesanan yang melewati timeout saga menjadiCANCELLEDdan stok dilepas. Setelah di-scale kembali, lag habis dan sistem stabil. - Perlambat katalog: suntik latensi (misalnya profil
chaosyang menambah delay) padacatalog-serviceyang dipanggil sinkron oleh gateway/order. Harapan: circuit breaker Resilience4j terbuka, response cepat gagal denganProblemDetail503 alih-alih menunggu, dan menutup kembali setelah katalog pulih.
yaml
# Potongan — konfigurasi Resilience4j di order-service
resilience4j:
circuitbreaker:
instances:
catalog:
sliding-window-size: 20
failure-rate-threshold: 50
slow-call-duration-threshold: 1s
slow-call-rate-threshold: 50
wait-duration-in-open-state: 10s
timelimiter:
instances:
catalog:
timeout-duration: 2sCatat hipotesis, langkah, hasil, dan perbaikan untuk tiap eksperimen. Catatan ini bahan cerita interview yang kuat.
Production readiness review
| Area | Item checklist |
|---|---|
| Keamanan | secret di Kubernetes Secret (bukan Git), image non-root, JWT divalidasi di gateway & service, dependency discan |
| Observability | log JSON + traceId, dashboard RED per service, alert error rate & consumer lag |
| Backup | backup PostgreSQL terjadwal dan restore pernah dicoba |
| Rollback | helm rollback diuji; migrasi Flyway backward-compatible (expand → contract) |
| Runbook | tiap alert punya runbook: gejala, dampak, langkah diagnosis, mitigasi |
| Kapasitas | hasil k6 tercatat, resource request/limit berdasarkan data, HPA bila perlu |
Setiap baris dinilai dengan bukti (link dashboard, output perintah), bukan "rasanya sudah".
Dokumentasi arsitektur
- README root: gambaran sistem, cara menjalankan lokal (Compose) dan di kind, peta service.
- Diagram: konteks (C4 level 1) dan container (level 2) — cukup teks/Mermaid di repo.
- ADR: satu file per keputusan — monorepo, koreografi saga, outbox polling vs CDC, Helm chart umum.
- README per service dan runbook di
docs/runbooks/.
Contoh di dunia backend
Hasil nyata eksperimen pertama sering memperlihatkan bug desain. Misalnya, saat payment-service mati 5 menit, pesanan tetap PENDING selamanya karena belum ada timeout saga. Perbaikannya adalah job di order-service yang membatalkan pesanan menggantung — juga lewat outbox agar kompensasi terkirim.
java
// Potongan — order-service
@Component
class SagaTimeoutJob {
private final OrderRepository orders;
private final OutboxRepository outbox;
private final Clock clock;
@Scheduled(fixedDelay = 30_000)
@Transactional
void cancelStuckOrders() {
Instant batas = clock.instant().minus(Duration.ofMinutes(15));
for (Order o : orders.findTop100ByStatusAndCreatedAtBefore(OrderStatus.PENDING, batas)) {
o.cancel("PAYMENT_TIMEOUT");
outbox.save(OutboxEntry.of(o.getId(), "OrderCancelled", toJson(o))); // inventory melepas stok
}
}
}Karena payment-service mungkin tetap memproses event lama saat hidup kembali, ia harus mengecek status pesanan atau order-service harus menolak PaymentSucceeded untuk pesanan yang sudah CANCELLED dan memicu refund palsu. Kasus balapan seperti ini hanya muncul bila kamu benar-benar menjalankan chaos.
Pertanyaan interview
1. Bagaimana kamu memastikan deploy bisa dibatalkan dengan aman?
Image ditag SHA commit, rolling update dengan maxUnavailable: 0 dan readiness probe, serta helm upgrade --atomic. Migrasi database mengikuti pola expand–contract sehingga versi lama tetap kompatibel dengan skema baru, jadi helm rollback tidak merusak data.
2. Apa yang kamu pelajari dari load test capstone-mu?
Jawab dengan angka dan sebab: misalnya p(95) naik karena pool koneksi habis pada 50 VU, terlihat di metrik Hikari dan trace; diperbaiki dengan menyesuaikan ukuran pool dan mengurangi query per checkout, lalu diukur ulang. Interviewer mencari proses, bukan angka besar.
3. Apa yang terjadi bila satu service di saga mati?
Karena koreografi lewat Kafka, producer tetap jalan dan event menunggu. Timeout saga membatalkan pesanan yang menggantung, kompensasi melepas stok, dan consumer idempoten mencegah efek ganda saat service hidup lagi. Untuk panggilan sinkron, circuit breaker memotong kegagalan agar tidak merambat.
Jebakan umum
- Tag image
latest— tidak bisa dilacak dan rollback jadi tebakan. - Liveness probe memeriksa database; saat database lambat semua pod di-restart bersamaan.
- Filter path pipeline lupa
platform/**dancontracts/**. - Load test hanya mengukur latensi HTTP, padahal konfirmasi pesanan bergantung pada consumer lag.
- Saga tanpa timeout sehingga pesanan menggantung saat satu service mati.
- Backup ada tapi restore tidak pernah dicoba.
Ringkasan
- Satu chart Helm umum + values per service; image non-root ditag SHA commit.
- Pipeline per service dipicu filter path yang mencakup module bersama, dengan
--atomicuntuk rollback otomatis. - k6 dengan
thresholdsmengukur checkout; baca persentil, error rate, dan consumer lag. - Chaos kecil membuktikan kompensasi saga, timeout, dan circuit breaker — dan sering menemukan bug desain.
- Production readiness review berbasis bukti, ditutup dengan README, diagram, ADR, dan runbook.