CloudFront & S3
Untuk aplikasi yang didominasi pembaca, CloudFront adalah keputusan arsitektur paling berdampak: sebagian besar permintaan berhenti di edge dan tidak pernah menyentuh task ECS-mu sama sekali.
Intisari
- Satu distribusi bisa punya beberapa origin: ALB untuk halaman dinamis, S3 untuk media dan aset.
- Kebijakan kunci cache menentukan segalanya โ query pelacakan dan cookie yang ikut masuk kunci menghancurkan rasio hit.
- Bucket S3 tidak pernah publik. Akses lewat Origin Access Control (OAC).
- Unggahan langsung ke S3 dengan presigned URL โ jangan lewat aplikasi Go-mu.
- AWS WAF dipasang di CloudFront: memblokir sebelum trafik menyentuh origin adalah yang termurah.
Bentuk distribusinya
CloudFront
โโโ Default (/*) โ ALB (halaman & API)
โ kebijakan cache : CachingDisabled untuk /api/*, kustom untuk halaman
โ kebijakan origin req: teruskan Host, cookie sesi, header yang perlu
โ
โโโ /statis/* โ S3 (bundel CSS/JS ber-hash)
โ kebijakan cache : CachingOptimized
โ TTL : 1 tahun, immutable
โ
โโโ /media/* โ S3 (gambar unggahan)
kebijakan cache : CachingOptimized
TTL : 30 hari
Kunci cache: yang menentukan rasio hit
Kunci cache = host + path + (query/header/cookie yang DIPILIH)
โ Meneruskan semua query string:
/artikel?utm_source=fb โ entri terpisah
/artikel?utm_source=ig โ entri lain
/artikel?fbclid=xyz โ entri lain lagi
โ rasio hit hancur; hampir semuanya menembus ke ALB
โ
Daftar putih parameter yang MEMENGARUHI keluaran:
halaman, urut, kategori โ masuk kunci
utm_*, fbclid, gclid โ diabaikan
โ Meneruskan semua cookie di halaman publik:
setiap pengunjung punya cookie analitik unik โ hit rate ~0%
โ
Teruskan cookie sesi HANYA pada perilaku cache untuk rute privat
Rasio hit di bawah 90% pada situs konten hampir selalu berarti kunci cache-nya salah. Ini pemeriksaan pertama saat trafik naik dan biaya ECS-mu ikut naik padahal seharusnya tidak (Fase 9). Perbaikannya biasanya satu kebijakan cache, bukan satu baris kode.
Halaman dinamis pun bisa di-cache
// Halaman publik yang sama untuk semua pembaca
w.Header().Set("Cache-Control",
"public, max-age=60, s-maxage=300, stale-while-revalidate=3600, stale-if-error=86400")
// Halaman personal โ CloudFront TIDAK BOLEH menyimpannya
w.Header().Set("Cache-Control", "private, no-store")
Pasang private, no-store sebagai default di middleware (Fase 9), lalu longgarkan
secara eksplisit hanya pada rute publik. Kesalahan ke arah sebaliknya โ default publik, lalu lupa
memperketat satu rute โ berarti dasbor satu pengguna disajikan ke pengguna lain, dan itu insiden
keamanan yang sungguhan.
S3 tanpa bucket publik
Origin Access Control (OAC):
โข bucket memblokir SELURUH akses publik
โข kebijakan bucket hanya mengizinkan principal cloudfront.amazonaws.com
dari ARN distribusi TERTENTU
โข objek hanya bisa diakses lewat CloudFront
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "Service": "cloudfront.amazonaws.com" },
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::toko-media/*",
"Condition": {
"StringEquals": {
"AWS:SourceArn": "arn:aws:cloudfront::123456789012:distribution/E1ABCDEF"
}
}
}]
}
Bucket S3 publik adalah salah satu penyebab kebocoran data paling sering di industri ini. Nyalakan Block Public Access di tingkat akun, bukan hanya per bucket โ dengan begitu tidak ada bucket baru yang bisa dibuat publik secara tidak sengaja.
Unggahan langsung ke S3
// Jangan mengalirkan unggahan lewat task ECS: ia memakan memori,
// menahan goroutine, dan membatasi ukuran berkas pada memori container.
func (h *Handler) URLUnggah(w http.ResponseWriter, r *http.Request) {
u := auth.DariContext(r.Context())
// Nama objek dibuat SERVER โ jangan pernah memakai nama dari klien (Fase 6).
kunci := fmt.Sprintf("unggahan/%d/%s.jpg", u.ID, uuid.NewString())
req, err := h.s3presign.PresignPutObject(r.Context(), &s3.PutObjectInput{
Bucket: aws.String(h.bucket),
Key: aws.String(kunci),
ContentType: aws.String("image/jpeg"),
ContentLength: aws.Int64(req.Ukuran),
}, s3.WithPresignExpires(5*time.Minute))
if err != nil {
h.tulisError(w, r, err)
return
}
h.tulisJSON(w, http.StatusOK, map[string]string{
"url": req.URL,
"kunci": kunci,
})
}
Alur lengkapnya:
1. Klien meminta URL unggah โ aplikasi Go memvalidasi & membuat presigned URL
2. Klien PUT langsung ke S3 โ tidak menyentuh task ECS sama sekali
3. S3 memicu event (opsional) โ SQS โ worker membuat varian ukuran (Fase 5)
4. Klien memberi tahu aplikasi โ aplikasi mencatat kunci objeknya
Presigned URL berumur pendek dan terbatas. Beri masa berlaku beberapa menit, kunci
ContentType, dan batasi ContentLength โ kalau tidak, URL itu bisa dipakai
mengunggah apa saja, seukuran apa saja, sampai kedaluwarsa.
Invalidasi
# Mahal dan lambat; 1000 path pertama per bulan gratis, sisanya ditagih.
aws cloudfront create-invalidation --distribution-id E1ABCDEF --paths "/artikel/*"
Cara yang jauh lebih baik daripada invalidasi: versioning lewat nama. Aset ber-hash
nama (styles.9ea43564.css) tidak pernah perlu diinvalidasi โ isi baru berarti nama baru.
Untuk halaman, s-maxage yang pendek plus stale-while-revalidate membuat
perubahan menyebar sendiri dalam hitungan menit tanpa satu pun invalidasi.
AWS WAF
| Aturan | Menangani |
|---|---|
| Rate-based (2000/5 menit per IP) | Lapisan pertama sebelum rate limit aplikasi (Fase 6) |
AWSManagedRulesCommonRuleSet | Pola serangan umum |
AWSManagedRulesKnownBadInputsRuleSet | Muatan berbahaya yang dikenal |
| Aturan geografis | Kalau layananmu memang satu negara |
| Bot Control | Perayap agresif; berbiaya tambahan |
Memblokir di CloudFront jauh lebih murah daripada di aplikasi. Permintaan yang ditolak di edge tidak memakai task ECS, tidak memakai koneksi database, dan tidak menambah data transfer keluar dari region. Dan pastikan ALB hanya menerima trafik dari CloudFront (materi ALB) โ kalau tidak, semua aturan WAF-mu bisa dilewati dengan memanggil ALB langsung.
Latihan: pasang CloudFront di depan ALB dengan kebijakan cache yang mengabaikan parameter
utm_*. Panggil halaman publik dua kali dan periksa header X-Cache. Lalu
tambahkan ?utm_source=x dan periksa apakah ia tetap hit. Terakhir, unggah berkas lewat
presigned URL dan buktikan bucket-nya tidak bisa diakses langsung.
Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.