Backup & Disaster Recovery

Implementasi arsitektur pencadangan otomatis dan pemulihan bencana (Disaster Recovery) menggunakan kombinasi Restic sebagai mesin inti pencadangan yang cepat, efisien, dan terenkripsi kuat, serta Backrest sebagai lapisan antarmuka grafis berbasis web (Web UI Orchestrator).

Contoh skenario Golang REST API sebagai penanganan logika bisnis (backend service), serta database MariaDB sebagai penyimpanan. Seluruh data tersebut dicadangkan secara otomatis dan berkala ke media penyimpanan berbasis objek (Object Storage S3) di Backblaze B2 dengan skema mitigasi kegagalan tanpa waktu henti (zero downtime mitigation).


1. Instalasi Restic dan Backrest di Server

Langkah pertama adalah menyiapkan seluruh komponen inti pada server Linux Ubuntu 26.04 LTS yang berjalan di dalam lingkungan virtual machine Multipass. Mesin utama Restic dipasang langsung dari repositori resmi paket Ubuntu, sedangkan biner Backrest diunduh secara manual dari rilis resmi akun GitHub miliknya karena tidak tersedia di dalam manajer paket bawaan sistem operasi.

# Memperbarui indeks paket sistem dan memasang Restic core engine
sudo apt update
sudo apt install -y restic

# Mengunduh arsip biner Backrest untuk arsitektur Linux AMD64 ke direktori sementara
curl -fLo /tmp/backrest_Linux_x86_64.tar.gz \
  https://github.com/garethgeorge/backrest/releases/latest/download/backrest_Linux_x86_64.tar.gz

# Membuat folder ekstraksi, membongkar arsip tarball, dan menjalankan skrip instalasi internal
mkdir -p /tmp/backrest-install
tar -xzvf /tmp/backrest_Linux_x86_64.tar.gz -C /tmp/backrest-install
cd /tmp/backrest-install
chmod +x install.sh
sudo ./install.sh

Skrip install.sh secara otomatis akan memindahkan file eksekusi utama ke jalur sistem /usr/local/bin/backrest dan mendaftarkan konfigurasi awal pada manajer layanan systemd.


2. Menyesuaikan Layanan Systemd untuk Akses Web Jarak Jauh

Secara default, skrip instalasi membuat unit layanan systemd bernama backrest.service dengan konfigurasi hak akses pengguna biasa (non‑root) dan hanya terikat pada antarmuka lokal (localhost/127.0.0.1). Batasan ini menyebabkan dasbor web tidak bisa dibuka dari komputer luar dan Restic akan mengalami kendala izin ditolak (permission denied) saat mencoba membaca folder database utama. Oleh karena itu, berkas unit layanan tersebut harus dimodifikasi secara total.

Buka berkas konfigurasi unit layanan menggunakan penyunting teks nano:

sudo nano /etc/systemd/system/backrest.service

Hapus seluruh konfigurasi bawaan lama, lalu ganti dengan konfigurasi terstruktur berikut untuk membuka ikatan jaringan ke semua antarmuka (0.0.0.0) pada port 9898 dengan hak akses root:

[Unit]
Description=Backrest Web UI Orchestrator
After=network.target

[Service]
Type=simple
User=root
Group=root
Environment="BACKREST_BIND=0.0.0.0"
Environment="BACKREST_PORT=9898"
ExecStart=/usr/local/bin/backrest
Restart=on-failure

[Install]
WantedBy=multi-user.target

Catatan Penting Mengenai Keamanan Sistem:
Mengubah pengguna eksekusi menjadi root adalah sebuah kewajiban mutlak agar mesin pencadang dapat membaca seluruh berkas sistem dan database yang dienkripsi ketat. Konsekuensinya, dasbor web memiliki wewenang penuh terhadap server. Pastikan Anda mengaktifkan fitur autentikasi kata sandi bawaan pada saat pertama kali dasbor diakses di browser, serta mengunci aksesnya hanya melalui jaringan internal yang aman.

Setelah berkas disimpan, jalankan perintah berikut untuk memuat ulang konfigurasi systemd, mengaktifkan layanan agar berjalan otomatis saat server melakukan proses booting, dan membuka jalur port pada firewall UFW:

# Memuat ulang konfigurasi systemd dan menyalakan layanan secara instan
sudo systemctl daemon-reload
sudo systemctl enable --now backrest

# Memverifikasi bahwa port 9898 telah terbuka dan mendengarkan (LISTEN) di jaringan 0.0.0.0
ss -tulpn | grep 9898

# Membuka aturan Firewall UFW agar laptop pribadi dapat mengakses dasbor
sudo ufw allow 9898/tcp
sudo ufw reload

3. Instalasi Database MariaDB dan phpMyAdmin Dashboard

Sebagai media penyimpanan utama dari aplikasi, kita memasang mesin database MariaDB Server. Selain itu, untuk mempermudah proses manipulasi data, pengelolaan skema, dan pengawasan operasi database secara visual dari peramban, kita juga memasang komponen phpMyAdmin.

# Memasang MariaDB Server beserta dependensi komponen PHP untuk phpMyAdmin
sudo apt install -y mariadb-server phpmyadmin php-mbstring php-zip php-gd php-json php-curl

# Mengaktifkan ekstensi mutlak mbstring pada sistem PHP dan memuat ulang Apache2 web server
sudo phpenmod mbstring
sudo systemctl restart apache2

# Membuat jembatan pintasan (symbolic link) agar phpMyAdmin terdeteksi oleh Apache2 pada Ubuntu 26.04 LTS
sudo ln -s /usr/share/phpmyadmin /var/www/html/phpmyadmin

# Membuka akses port HTTP standar (Port 80) pada sistem keamanan firewall
sudo ufw allow 80/tcp
sudo ufw reload

Secara default, demi alasan keamanan tingkat tinggi, pengguna dasar root MariaDB tidak diizinkan masuk ke dalam sistem menggunakan protokol eksternal maupun antarmuka web. Kita harus masuk ke terminal database untuk membuat database baru serta satu pengguna administratif khusus yang memiliki wewenang penuh:

# Masuk ke dalam prompt administratif MariaDB lokal
sudo mysql

Eksekusi perintah SQL berikut satu per satu ke dalam sistem database:

-- Membuat database utama untuk menampung data aplikasi Golang
CREATE DATABASE db_golang;

-- Membuat pengguna administratif baru yang dapat diakses dari jaringan manapun (%)
GRANT ALL PRIVILEGES ON *.* TO 'admin_server'@'%' IDENTIFIED BY 'PasswordRahasia123';

-- Menerapkan perubahan hak akses dan keluar dari prompt database
FLUSH PRIVILEGES;
EXIT;

4. Konfigurasi Protokol Jaringan Luar MariaDB (TCP/IP)

Aplikasi Golang kita dirancang secara fleksibel agar siap dijalankan pada infrastruktur multi‑server (di mana server aplikasi dan server database terpisah). Konfigurasi bawaan MariaDB mengunci socket jaringan hanya untuk IP lokal (127.0.0.1). Kita harus membukanya agar bersedia menerima paket data eksternal dari luar VM melalui port 3306.

Buka berkas konfigurasi internal MariaDB Server:

sudo nano /etc/mysql/mariadb.conf.d/50-server.cnf

Cari parameter bernama bind-address di bawah blok [mysqld] dan ubah nilainya menjadi seluruh jaringan (0.0.0.0):

bind-address            = 0.0.0.0

Simpan berkas konfigurasi tersebut, lalu jalankan perintah pemuatan ulang layanan database serta pembukaan port firewall di terminal server:

# Memuat ulang layanan MariaDB agar mendengarkan konfigurasi IP baru
sudo systemctl restart mariadb

# Mengizinkan lalu lintas data luar masuk ke port default database 3306
sudo ufw allow 3306/tcp
sudo ufw reload

Sekarang, Anda dapat menguji aksesnya dari peramban laptop dengan membuka tautan http://10.64.142.167/phpmyadmin dan memasukkan nama pengguna admin_server serta kata sandi PasswordRahasia123.


5. Pembuatan Aplikasi Golang REST API Terstruktur (Clean Code)

Aplikasi penanganan data (Backend API) dibangun menggunakan bahasa pemrograman Go (Golang). Kode program ini mengimplementasikan prinsip arsitektur bersih (clean architecture code) yang memanfaatkan Environment Variables untuk memisahkan logika program dengan nilai konfigurasi infrastruktur jaringan. Kode ini juga telah dilengkapi dengan fungsi penanganan linter check (rows.Err()) untuk mendeteksi kegagalan koneksi di tengah jalan.

Simpan kode di bawah ini dengan nama file main.go pada direktori laptop lokal Anda:

package main

import (
    "database/sql"
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "os"

    _ "github.com/go-sql-driver/mysql" // Driver resmi untuk koneksi MariaDB/MySQL
)

// Task mendefinisikan struktur data untuk tabel tugas
type Task struct {
    ID   int    `json:"id"`
    Name string `json:"name"`
}

var db *sql.DB

// getEnv berfungsi membaca variabel lingkungan OS dengan opsi nilai default
func getEnv(key, defaultValue string) string {
    value := os.Getenv(key)
    if len(value) == 0 {
        return defaultValue
    }
    return value
}

// initDB mengonfigurasi koneksi dinamis ke database jarak jauh secara fleksibel
func initDB() {
    var err error

    // Membaca parameter jaringan dari Environment Variables secara dinamis
    username := getEnv("DB_USER", "admin_server")
    password := getEnv("DB_PASSWORD", "PasswordRahasia123")
    host := getEnv("DB_HOST", "10.64.142.167") // IP target VM MariaDB Server
    port := getEnv("DB_PORT", "3306")
    database := getEnv("DB_NAME", "db_golang")

    // Menyusun runtunan Data Source Name (DSN) untuk protokol TCP/IP
    dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?parseTime=true", username, password, host, port, database)

    log.Printf("Mencoba menghubungkan ke database di %s:%s...\n", host, port)

    db, err = sql.Open("mysql", dsn)
    if err != nil {
        log.Fatal("Gagal mengonfigurasi driver database:", err)
    }

    // Memastikan server database target memberikan respon aktif (ping)
    err = db.Ping()
    if err != nil {
        log.Fatal("Database tidak merespon! Periksa kembali IP Remote/Username/Password Anda. Error: ", err)
    }

    log.Println("Berhasil terhubung ke MariaDB via jaringan TCP/IP!")
}

// getTasks menangani permintaan HTTP GET untuk membaca seluruh data tugas (READ)
func getTasks(w http.ResponseWriter, _ *http.Request) {
    w.Header().Set("Content-Type", "application/json")

    rows, err := db.Query("SELECT id, name FROM tasks")
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    defer rows.Close()

    var tasks []Task

    for rows.Next() {
        var t Task
        if err := rows.Scan(&t.ID, &t.Name); err != nil {
            continue // Lewati baris data jika terjadi anomali pemindaian file
        }
        tasks = append(tasks, t)
    }

    // Pengecekan akhir pasca-looping untuk memastikan tidak ada putusnya koneksi di tengah jalan
    if err = rows.Err(); err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    // Mengembalikan array kosong [] alih-alih null jika isi tabel database kosong
    if tasks == nil {
        tasks = []Task{}
    }

    json.NewEncoder(w).Encode(tasks)
}

// createTask menangani permintaan HTTP POST untuk menyimpan data tugas baru (CREATE)
func createTask(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")

    var task Task
    if err := json.NewDecoder(r.Body).Decode(&task); err != nil {
        http.Error(w, "Invalid JSON", http.StatusBadRequest)
        return
    }

    // Menyisipkan entri teks baru ke dalam tabel database
    result, err := db.Exec("INSERT INTO tasks (name) VALUES (?)", task.Name)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    // Mengambil nomor urut ID terakhir yang diatur secara otomatis oleh mesin database
    lastID, err := result.LastInsertId()
    if err != nil {
        http.Error(w, "Gagal mendapatkan ID terakhir", http.StatusInternalServerError)
        return
    }
    task.ID = int(lastID)

    w.WriteHeader(http.StatusCreated)
    json.NewEncoder(w).Encode(task)
}

// taskHandler bertindak sebagai pengatur rute berdasarkan metode protokol HTTP
func taskHandler(w http.ResponseWriter, r *http.Request) {
    switch r.Method {
    case http.MethodGet:
        getTasks(w, r)
    case http.MethodPost:
        createTask(w, r)
    default:
        http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
    }
}

func main() {
    // Menjalankan inisialisasi koneksi jaringan database
    initDB()
    defer db.Close()

    // Mendaftarkan fungsi penanganan rute endpoint url
    http.HandleFunc("/tasks", taskHandler)

    appPort := getEnv("APP_PORT", "8080")
    fmt.Printf("Server API running on http://0.0.0.0:%s\n", appPort)

    // Menjalankan HTTP server secara lokal pada port 8080
    log.Fatal(http.ListenAndServe(":"+appPort, nil))
}

Jalankan program di terminal laptop Anda menggunakan perintah:

go run main.go

Aplikasi akan memancarkan pesan sukses: Berhasil terhubung ke MariaDB via jaringan TCP/IP!.


6. Integrasi Repositori Backrest ke Cloud Storage Backblaze B2

Dengan database dan aplikasi yang sudah aktif beroperasi, langkah selanjutnya adalah melakukan pengaturan visual pada dashboard Backrest dengan membuka alamat http://10.64.142.167:9898 dari browser laptop Anda.

A. Pengaturan Instansiasi Awal

Pada tampilan pertama kali diluncurkan, isi bagian Instance ID dengan pengenal unik server Anda (misalnya: ubuntu-backrest-server). Pada menu Authentication, buat nama pengguna serta kata sandi administratif baru untuk memproteksi dasbor agar tidak bisa diakses sembarangan orang dari luar.

B. Menghubungkan Repositori Penyimpanan Cloud S3

Buka menu navigasi Repositories pada bilah kiri dasbor web, klik tombol Add Repo, lalu isikan parameter detail repositori menggunakan data kredensial Application Key dari Backblaze B2 Anda:

  • Repository Name: b2-mariadb-backup
  • Repository URI: b2:Mariadb-Backup-2026 (Format pemanggilan nama bucket asli milik Restic)
  • Environment Variables: Masukkan baris kredensial otentikasi tanpa menyertakan karakter spasi sama sekali:
B2_ACCOUNT_ID=005778aef7f28c50000000008
B2_ACCOUNT_KEY=K005lxzrdSNIwTMYu/gBJhgLniuXk90
  • Password: Masukkan kata sandi enkripsi repositori yang baru dan kuat. Kata sandi ini berfungsi sebagai kunci utama bagi algoritma kriptografi Restic untuk mengacak data secara lokal di sisi server sebelum dikirim ke awan.

Klik tombol Test Connection. Setelah sistem berhasil memunculkan lampu indikator berwarna hijau, klik tombol Save untuk menyimpan konfigurasi repositori.


7. Otomatisasi Skenario Dump via Pre‑Backup Hook

Sebelum membuat rencana pencadangan (Backup Plan), kita harus memastikan kebenaran lokasi direktori fisik di dalam server tempat menyimpan file cadangan database. Pada server VM, folder penyimpanan diatur pada alamat mutlak /home/ubuntu/backup-mariadb-go.

Buka menu Plans pada dashboard Backrest, klik tombol Add Plan, lalu atur parameter penanganan data otomatis berikut:

  1. Tab Details: Berikan nama rencana cadangan yang spesifik, misalnya b2-mariadb-golang. Pilih target lokasi penyimpanan pada repositori b2-mariadb-backup.
  2. Tab Scope: Pada bagian kolom Paths, ketik alamat direktori fisik target di server, yaitu: /home/ubuntu/backup-mariadb-go.
  3. Tab Schedule: Pilih metode pengisian ekspresi cron dan ketik rumusan 0 * * * * untuk memerintahkan sistem melakukan pencadangan otomatis secara berkala setiap satu jam sekali tepat pada menit ke-0.
  4. Tab Advanced (Otomatisasi Kritis Hooks): Klik tombol Add Hook dan ubah konfigurasinya menjadi fungsi pemicu eksekusi internal (Lifecycle Hooks):
  5. Runs when...: Pilih opsi CONDITION_SNAPSHOT_START. Pilihan peristiwa ini memastikan bahwa script akan dipaksa berjalan sukses terlebih dahulu tepat sesaat sebelum Restic mulai mengangkut file ke awan.
  6. Script: Hapus fungsi teks bawaan di dalam kotak hitam, lalu isikan perintah satu baris ekspor database berikut:

    bash mysqldump db_golang > /home/ubuntu/backup-mariadb-go/db_golang_backup.sql

  7. Error Behavior: Pilih opsi Fail the operation. Ini adalah langkah pengamanan kritis; jika database MariaDB sedang macet sehingga proses ekspor gagal, Backrest akan otomatis membatalkan backup agar file kosong yang rusak tidak diunggah ke cloud untuk menimpa backup lama yang aman.

Klik tombol Submit. Untuk menguji keandalan sistem otomatisasi baru ini, klik tombol Backup Now langsung pada halaman plan. Periksa bucket Backblaze B2 Anda via web browser; Anda akan mendapati struktur folder biner terenkripsi Restic (data, index, keys, snapshots) sudah mendarat dengan selamat dengan kapasitas file terproses yang valid.


8. Simulasi Bencana Total dan Prosedur Pemulihan Keadaan

Inti terpenting dari sebuah sistem manajemen pencadangan adalah pembuktian keberhasilan proses pemulihan data ketika bencana hancurnya infrastruktur benar‑benar terjadi di dunia nyata.

Pengisian Data Awal (Kondisi Normal)

Gunakan perintah curl dari terminal laptop pribadi Anda untuk memasukkan data transaksi baru ke dalam database melalui API Golang:

curl -X POST -H "Content-Type: application/json" -d '{"name":"Konfigurasi Repositori S3 di Backrest"}' http://localhost:8080/tasks
curl -X POST -H "Content-Type: application/json" -d '{"name":"Membuat Pre-Backup Hook untuk MariaDB"}' http://localhost:8080/tasks

Verifikasi data dengan memanggil endpoint GET:

curl http://localhost:8080/tasks

Data JSON berisi dua tugas akan muncul utuh, dan pada tab Browse phpMyAdmin, data tersebut juga terdokumentasi rapi dalam penyimpanan hardisk server.

Serangan Bencana (Skenario Kerusakan Total)

Simulasikan serangan fatal atau ketidaksengajaan fatal admin dengan menghapus database secara permanen langsung dari terminal VM server:

sudo mysql -e "DROP DATABASE db_golang;"

Lakukan pengecekan ulang dari terminal laptop Anda menggunakan fungsi baca:

curl http://localhost:8080/tasks

Aplikasi Go akan langsung memancarkan kode kesalahan HTTP Error 500 dengan pesan spesifik: Error 1146 (42S02): Table 'db_golang.tasks' doesn't exist. Pada tahap ini, sistem layanan bisnis Anda dinyatakan mati total (down).

Pemulihan Kilat Berkas SQL via Backrest

  1. Masuk kembali ke dashboard Web Backrest Anda.
  2. Klik ID Snapshot terakhir Anda yang sukses dicadangkan ke Backblaze B2 (misalnya ID b702fdf7).
  3. Buka menu lungsur Snapshot Browser yang terletak di bawah detail teks snapshot tersebut.
  4. Centang nama file db_golang_backup.sql seberat 2.14 KiB yang berada di dalam bagan navigasi visual, lalu klik tombol Restore.
  5. Pada jendela sembulan yang muncul, pilih opsi Restore to path dan kosongkan sepenuhnya kotak isian teks jalurnya (leave empty to restore to original path). Dengan cara ini, Backrest dan Restic akan otomatis menarik file dari cloud Backblaze, mendekripsinya, dan mengembalikannya ke folder asalnya semula di /home/ubuntu/backup-mariadb-go/db_golang_backup.sql tanpa mengubah izin akses ataupun kepemilikan file sistem asli. Klik Start.

Penyuntikan Data Kembali ke Mesin Database

Setelah Backrest memunculkan notifikasi pemulihan file fisik sukses, jalankan dua perintah pemulihan ini di terminal VM server Anda untuk memasukkan skema struktur tabel kembali ke dalam sistem:

# 1. Membangun kembali database kosong yang sempat musnah
sudo mysql -e "CREATE DATABASE db_golang;"

# 2. Menyuntikkan kembali data biner mentah dari file hasil dekompresi restore Backrest
sudo mysql db_golang < /home/ubuntu/backup-mariadb-go/db_golang_backup.sql

Verifikasi Hasil Akhir Pemulihan

Prosedur penanganan bencana selesai dilakukan. Sekarang, jalankan kembali pengujian pemanggilan data dari terminal laptop lokal Anda:

curl http://localhost:8080/tasks

Hasil Akhir Uji Coba:

[
  {"id":1,"name":"Konfigurasi Repositori S3 di Backrest"},
  {"id":2,"name":"Membuat Pre-Backup Hook untuk MariaDB"}
]

Data JSON tugas yang sempat hilang akibat perintah DROP DATABASE kini kembali muncul secara utuh, valid, dan tidak cacat sedikit pun. Aplikasi REST API Golang Anda langsung dapat melayani aktivitas pengguna kembali secara normal tanpa perlu dilakukan proses restart layanan.

Siklus pengujian implementasi strategi keamanan infrastruktur data Automated Backup & Disaster Recovery dinyatakan berhasil dengan sempurna (100% Sukses).