← Semua pembelajaran / Laravel Nol β†’ Enterprise
Fase 9 Β· Deploy di AWS

Deploy tanpa jeda & observability

Deploy tanpa jeda bukan satu fitur yang dinyalakan, melainkan hasil dari beberapa setelan yang selaras. Kalau salah satu tidak sinkron, penggunamu melihat 502 setiap kali kamu merilis.

Intisari

  • Rolling update: task baru menyala dan lulus health check sebelum task lama dihentikan.
  • Deployment circuit breaker mengembalikan versi sebelumnya otomatis saat task baru gagal menyala.
  • Empat setelan harus selaras: health check, deregistration delay, stopTimeout, dan SIGTERM.
  • Migrasi harus kompatibel dua arah β€” kode lama dan baru berjalan bersamaan saat deploy.
  • Log JSON ke CloudWatch, dengan request_id supaya satu permintaan bisa ditelusuri utuh.

Anatomi rolling update

minimumHealthyPercent = 100     maximumPercent = 200
(desiredCount = 4)

1. ECS menyalakan 4 task BARU        β†’ total 8 task
2. Task baru lulus health check ALB  β†’ mulai menerima lalu lintas
3. ALB berhenti mengirim ke task lama, menunggu deregistration delay
4. ECS mengirim SIGTERM ke task lama
5. Task lama menyelesaikan pekerjaannya dalam stopTimeout, lalu berhenti
                                      β†’ total kembali 4 task
SetelanNilaiArtinya
minimumHealthyPercent100Kapasitas tidak pernah turun selama deploy
maximumPercent200Boleh menjalankan dua kali lipat sementara
Keduanya 100/100β€”Task lama dimatikan dulu β€” ada jeda
50/100β€”Hemat, tapi kapasitas separuh saat deploy

Empat setelan yang harus selaras

ALB health check          : interval 15s, healthy threshold 2  β†’ ~30 detik untuk sehat
ALB deregistration delay  : 30 detik
ECS stopTimeout           : 60 detik  (web) / 120 detik (worker)
Aplikasi                  : menangani SIGTERM dengan benar

Aturannya: stopTimeout > deregistration delay. Kalau ECS membunuh task sebelum ALB selesai menunggu, permintaan yang sedang berjalan terputus β€” dan pengguna melihat 502 tepat pada saat kamu merilis. Ini penyebab paling umum dari "deploy kita selalu menghasilkan sedikit error".

Circuit breaker

aws ecs update-service --cluster portal --service portal-web \
  --deployment-configuration '{
    "deploymentCircuitBreaker": { "enable": true, "rollback": true },
    "minimumHealthyPercent": 100,
    "maximumPercent": 200
  }'

Dengan ini, kalau task baru gagal menyala atau gagal health check berulang kali, ECS menghentikan deploy dan mengembalikan task definition sebelumnya secara otomatis. Kamu mendapat rollback tanpa harus ada orang yang memperhatikan dasbor.

Circuit breaker hanya menangkap kegagalan yang terlihat oleh health check. Bug yang membuat aplikasi tetap menjawab 200 tapi menghasilkan halaman yang salah tidak akan tertangkap. Untuk itu, tambahkan alarm pada tingkat error 5xx dan latensi setelah deploy β€” dan pemeriksaan asap di akhir pipeline.

Migrasi yang aman selama deploy

PerubahanAman saat kode lama masih jalan?
Tambah kolom nullableYa
Tambah tabelYa
Tambah indexYa (perhatikan penguncian di tabel besar)
Tambah kolom NOT NULL tanpa defaultTidak β€” insert kode lama gagal
Ganti nama kolomTidak
Hapus kolomTidak
Ubah tipe kolomBiasanya tidak

Untuk baris-baris "tidak", pecah jadi tiga deploy sesuai pola expand & contract dari Fase 2. Aturannya tidak berubah, tapi di sini alasannya jadi konkret: selama beberapa menit, dua versi kodemu benar-benar berjalan bersamaan di atas satu database.

Log terstruktur

// app/Http/Middleware/KonteksLog.php
public function handle(Request $request, Closure $next): Response
{
    $id = $request->header('X-Amzn-Trace-Id') ?? (string) Str::uuid();

    Log::withContext([
        'request_id' => $id,
        'user_id'    => $request->user()?->id,
        'rute'       => $request->route()?->getName(),
    ]);

    return $next($request)->header('X-Request-Id', $id);
}
{"message":"pesanan dibuat","level":"info","request_id":"Root=1-abc-def",
 "user_id":42,"rute":"pesanan.store","pesanan_id":1183,"ms":84}

Log::withContext() membuat setiap baris log dalam satu permintaan bisa disatukan. Saat ada keluhan, kamu punya X-Request-Id dari respons, dan satu query Logs Insights mengembalikan seluruh jejak permintaan itu β€” termasuk baris log dari job yang dipicunya, kalau ID-nya ikut diteruskan.

CloudWatch Logs Insights

fields @timestamp, level, message, rute, ms
| filter level = "error"
| sort @timestamp desc
| limit 50
fields rute, ms
| filter ispresent(ms)
| stats count(*) as jumlah,
        avg(ms) as rata,
        pct(ms, 95) as p95,
        pct(ms, 99) as p99
  by rute
| sort p95 desc
| limit 20

Alarm yang benar-benar perlu

AlarmAmbangTingkat
ALB HTTPCode_ELB_5XX> 10 dalam 5 menitBangunkan orang
ALB TargetResponseTime p95> 2 detikBangunkan orang
ECS RunningTaskCount< minimumBangunkan orang
SQS pesan di DLQ> 0Bangunkan orang
Aurora CPUUtilization> 80% selama 10 menitPeringatan
Redis memori> 75%Peringatan
Kedalaman antrean SQS> 1000 selama 10 menitPeringatan
Tingkat error aplikasiNaik tajam setelah deployBangunkan orang

Alarm yang terlalu banyak sama buruknya dengan tidak ada alarm. Kalau setiap lonjakan kecil memicu notifikasi, orang berhenti membacanya β€” dan alarm yang sungguhan ikut terlewat. Delapan alarm di atas menutup hampir semua kegagalan yang benar-benar merugikan pengguna. Tambahkan yang lain hanya setelah sebuah insiden membuktikan bahwa kamu memang membutuhkannya.

Pemeriksaan asap setelah deploy

set -e
BASE=https://portal.example.com

test "$(curl -s -o /dev/null -w '%{http_code}' $BASE/up)" = "200"
test "$(curl -s -o /dev/null -w '%{http_code}' $BASE/)" = "200"
curl -sI $BASE/ | grep -qi 'x-cache'
curl -s $BASE/api/v1/artikel | jq -e '.data | length > 0' > /dev/null

echo "Pemeriksaan asap lolos."

Latihan: selaraskan keempat setelan deploy, lalu jalankan while true; do curl -s -o /dev/null -w '%{http_code}\n' https://portal…/; sleep 0.2; done sambil memicu deploy. Hitung berapa respons yang bukan 200. Targetnya nol; setiap angka di atas itu menunjuk ke satu setelan yang belum sinkron.

Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.