L
PHP 🚨 Post-Mortem & Incident Runbook • Literally Web ID

Manajemen Aset Website Skala Enterprise: Integrasi MinIO Object Storage dengan PHP dan CDN untuk Performa Optimal

⏱️ 15 menit baca • 📅 20 Oct 2026 • 👁️ 4 views
Manajemen Aset Website Skala Enterprise: Integrasi MinIO Object Storage dengan PHP dan CDN untuk Performa Optimal

Post-Mortem & Incident Runbook: Manajemen Aset Website Skala Enterprise dengan MinIO, PHP, dan CDN

1. Kronologi Insiden & Gejala Awal (The Outage)

Pukul 03:14 UTC, sistem monitoring enterprise kami memicu Critical Alert secara serentak. Aplikasi web berbasis PHP-FPM dan Nginx mengalami lonjakan response time (TTFB) dari rata-rata 120ms menjadi lebih dari 8.50onda. Layanan unggah gambar produk gagal total dengan kode HTTP 504 Gateway Time-out.

Gejala di Sisi Klien & Server:

  • Client Side: Pengguna melaporkan tombol Upload berputar tanpa henti, diikuti pesan kesalahan “Failed to upload media asset”.
  • Web Server (Nginx): Log akses dipenuhi dengan status 504 dan pesan error pada error.log:
    2026/03/30 03:14:22 [error] 1420#1420: *89121 upstream timed out (110: Connection timed out) while reading response header from upstream, client: 192.168.1.50, server: example.com, request: "POST /api/v1/media/upload HTTP/1.1", upstream: "fastcgi://unix:/run/php/php8.2-fpm.sock"
    
  • Backend PHP: Proses PHP-FPM mengalami process pool starvation. Seluruh worker sibuk menunggu respons sinkron dari penyimpanan lokal disk server yang mengalami I/O block akibat kuota disk penuh (100% usage).

[!WARNING] Menyimpan file media langsung di direktori lokal server web enterprise adalah bentuk anti-pattern fatal yang mengabaikan skalabilitas horisontal dan ketahanan infrastruktur.


2. Analisis Log Sistem & Anomali Metrik

Melalui dashboard Grafana dan Prometheus, kami melakukan bedah mendalam terhadap metrik infrastruktur saat insiden:

  • I/O Wait (%iowait): Melonjak hingga 89% pada partisi /var/www/html/storage. Disk IOPS mencapai batas maksimal hardware NVMe.
  • PHP-FPM Active Processes: Mencapai batas maksimal pm.max_children = 50. Antrean koneksi menumpuk di socket Unix.
  • TCP Connections: Terdeteksi lonjakan status CLOSE_WAIT dan TIME_WAIT pada koneksi TCP ke layanan penyimpanan eksternal yang sebelumnya dikonfigurasi secara tidak stabil.

Bedah konfigurasi awal yang keliru pada backend PHP yang memicu kemacetan:

// KODE LAMA (PENYEBAB BENCANA) - Menyimpan file langsung ke disk lokal
public function handleUpload($file) {
    $destination = '/var/www/html/public/uploads/' . $file->getClientOriginalName();
    // Operasi I/O sinkronous yang membebani disk lokal dan merusak arsitektur stateless
    move_uploaded_file($file->getPathname(), $destination);
    return response()->json(['url' => asset('uploads/' . $file->getClientOriginalName())]);
}

3. Root Cause Analysis (RCA & 5-Whys)

Kami menerapkan metode 5-Whys untuk menemukan akar permasalahan secara komprehensif:

  1. Mengapa aplikasi mengalami timeout saat upload file?
    • Karena PHP-FPM worker kehabisan kapasitas dan mengalami antrean panjang.
  2. Mengapa PHP-FPM worker kehabisan kapasitas?
    • Karena setiap proses memakan waktu lebih lama untuk menulis file berukuran besar langsung ke disk lokal.
  3. Mengapa proses tulis disk lokal sangat lambat?
    • Karena partisi disk /var/www telah mencapai 100% kapasitas maksimum.
  4. Mengapa disk bisa penuh tanpa peringatan dini?
    • Karena tidak ada arsitektur Offloading aset ke Object Storage terpusat, sehingga seluruh file multimedia menumpuk di satu node server tunggal.
  5. Mengapa arsitektur Offloading belum diimplementasikan sebelumnya?
    • Karena tim mengandalkan penyimpanan monolitik lokal demi kecepatan pengembangan awal tanpa perencanaan skalabilitas enterprise.

4. Langkah Penyelamatan Darurat (Emergency Runbook)

Berikut adalah eksekusi perintah darurat 1-baris dan langkah mitigasi kilat untuk memulihkan layanan dalam waktu kurang dari 10 menit:

1. Bersihkan Ruang Disk Sementara (Emergency Disk Space Reclamation)

find /var/www/html/public/uploads/ -type f -mmin +120 -exec rm -f {} + && systemctl restart php8.2-fpm nginx

2. Setup Cepat MinIO Menggunakan Docker Compose

Buat file docker-compose.yml di server dedicated storage terpisah:

version: '3.8'
services:
  minio:
    image: minio/minio:RELEASE.2023-03-20T20-16-18Z
    container_name: enterprise_minio
    ports:
      - "9000:9000"
      - "9001:9001"
    environment:
      MINIO_ROOT_USER: "minio_admin_secure_key"
      MINIO_ROOT_PASSWORD: "minio_super_secret_password_2026"
    command: server /data --console-address ":9001"
    volumes:
      - minio_data:/data
    restart: always

volumes:
  minio_data:

Jalankan container dengan perintah:

docker compose up -d

5. Solusi Permanen & Hardening Arsitektur

Untuk mencegah insiden berulang, kami merombak total arsitektur penyimpanan menggunakan integrasi AWS SDK for PHP untuk menghubungkan aplikasi ke MinIO Object Storage, dipadukan dengan Cloudflare CDN.

Implementasi PHP untuk Upload ke MinIO (S3-Compatible)

Install SDK melalui Composer:

composer require aws/aws-sdk-php

Buat Service Class MinioStorageService.php:

<?php

declare(strict_types=1);

namespace AppServices;

use AwsS3S3Client;
use AwsExceptionAwsException;

class MinioStorageService {
    private S3Client $s3Client;
    private string $bucketName;

    public function __construct() {
        $this->s3Client = new S3Client([
            'version'     => 'latest',
            'region'      => 'us-east-1',
            'endpoint'    => 'https://s3.internal.enterprise.com',
            'use_path_style_endpoint' => true,
            'credentials' => [
                'key'    => 'minio_admin_secure_key',
                'secret' => 'minio_super_secret_password_2026',
            ],
        ]);
        $this->bucketName = 'enterprise-media-assets';
    }

    public function uploadFile(string $fileKey, string $filePath): string {
        try {
            $result = $this->s3Client->putObject([
                'Bucket'     => $this->bucketName,
                'Key'        => $fileKey,
                'SourceFile' => $filePath,
                'ACL'        => 'public-read',
            ]);

            return $result['ObjectURL'];
        } catch (AwsException $e) {
            // Log error secara terstruktur
            error_log("MinIO Upload Error: " . $e->getMessage());
            throw new RuntimeException("Gagal mengunggah aset ke object storage.");
        }
    }

    public function getSignedUrl(string $fileKey, int $expires = 3600): string {
        $cmd = $this->s3Client->getCommand('GetObject', [
            'Bucket' => $this->bucketName,
            'Key'    => $fileKey
        ]);

        $request = $this->s3Client->createPresignedRequest($cmd, "+{$expires} seconds");
        return (string) $request->getUri();
    }
}

[!TIP] Selalu aktifkan parameter 'use_path_style_endpoint' => true saat menghubungkan AWS SDK PHP ke MinIO mandiri untuk menghindari masalah resolusi DNS virtual-hosted style.

Kebijakan Keamanan & Public Access Policy (MinIO JSON Policy)

Terapkan Bucket Policy berikut agar aset dapat diakses publik melalui CDN tanpa membocorkan kredensial admin:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": "*",
      "Action": ["s3:GetObject"],
      "Resource": ["arn:aws:s3:::enterprise-media-assets/*"]
    }
  ]
}

6. Monitoring, Alerting & Preventative Checklist

Agar sistem tetap resilien, integrasikan aturan Alerting berikut pada Prometheus dan Alertmanager:

groups:
  - name: MinioStorageAlerts
    rules:
      - alert: MinIOStorageHighLatency
        expr: http_request_duration_seconds{job="minio",quantile="0.95\
🏷️ Tags: #Nginx #High Concurrency #Performance #Sysadmin #Web Server