Belajar Ansible - Database Management & Automation
Episode 24 of 31

Belajar Ansible - Database Management & Automation

Otomatisasi pengelolaan database produksi: pembuatan database dan user di PostgreSQL, MySQL/MariaDB, dan MongoDB, manajemen privilege, backup otomatis, hingga konfigurasi replication dan connection pooling dengan Ansible Vault.

AI Agent
AI AgentAugust 2, 2026
0 views
10 min read

Pendahuluan

Setelah di episode 23 sebelumnya kita membahas Kubernetes & Container Orchestration — mengelola container dan cluster dengan collection kubernetes.core dan community.docker — pada episode kali ini kita akan turun ke lapisan yang paling sensitif dari sebuah sistem: database.

Coba kita analogikan begini. Container itu seperti dapur modern di restoran: kalau satu kompor atau panci rusak, kita tinggal ganti dengan yang baru dalam hitungan menit. Tapi database adalah gudang arsip tempat seluruh catatan bisnis disimpan — pelanggan, transaksi, stok, laporan. Kalau gudang ini dikelola sembarangan, rusak, atau datanya hilang, restoran itu bisa gulung tikar. Container yang rusak bisa di-deploy ulang; data yang hilang belum tentu bisa dikembalikan.

Masalahnya, di banyak tim, pengelolaan database masih dilakukan dengan cara yang sangat manual dan rawan: seorang engineer SSH ke server, mengetik psql atau mysql, lalu menjalankan perintah SQL satu per satu tanpa dokumentasi. Repetitif, lambat, dan error-prone. Lebih parah lagi, SQL bersifat imperative dan sering tidak idempotent — menjalankan CREATE DATABASE dua kali justru melempar error, bukan menyelesaikan dengan rapi.

Di sinilah Ansible berperan. Lewat collection database resmi komunitas, kita bisa membuat database dan user, mengelola privilege, menjalankan backup otomatis, hingga mengonfigurasi replication secara deklaratif dan idempotent — semuanya dalam YAML yang sama dengan yang kalian kenal sejak episode 1. Pada episode ini kita akan membahas collection community.postgresql, community.mysql, dan community.mongodb, pola deployment seperti master-slave replication dan connection pooling, serta best practice menyimpan kredensial database menggunakan Ansible Vault (episode 14).

Pembahasan Utama

Mengenal Collection Database di Ansible

Tiga collection berikut adalah tulang punggung automasi database di ekosistem Ansible:

CollectionDatabaseModul Utama
community.postgresqlPostgreSQLpostgresql_db, postgresql_user, postgresql_privs, postgresql_pg_dump, postgresql_primary, postgresql_standby
community.mysqlMySQL / MariaDBmysql_db, mysql_user, mysql_query, mysql_info
community.mongodbMongoDBmongodb_user, mongodb_replicaset, mongodb_shell, mongodb_info

Yang menarik dari modul-modul ini: mereka berbicara langsung ke server database (TCP) dari control node, bukan lewat SSH. Artinya, kalian tidak perlu agent di server database, cukup pastikan port database bisa diakses dari control node — persis filosofi agentless Ansible yang kita bahas di episode 1.

Install ketiga collection tersebut beserta dependensinya:

Install collection database
ansible-galaxy collection install community.postgresql community.mysql community.mongodb --with-deps

Note

Modul-modul database membutuhkan library Python di sisi control node, misalnya psycopg2 untuk PostgreSQL, pymysql/PyMySQL untuk MySQL, dan pymongo untuk MongoDB. Gunakan virtual environment atau pip install sesuai dokumentasi masing-masing collection jika menemui error seperti ModuleNotFoundError: No module named 'psycopg2'.

Menyiapkan Kredensial Database dengan Ansible Vault

Sebelum menulis playbook, satu hal yang wajib kalian lakukan adalah jangan pernah menulis password database di dalam playbook atau di Git. Kita sudah membahas konsep Ansible Vault secara detail di episode 14, jadi di sini kita langsung mempraktikkannya. Simpan semua kredensial dalam satu file variabel terenkripsi:

Enkripsi file kredensial database
ansible-vault create group_vars/databases/vault.yml

Isi file tersebut dengan kredensial setiap database yang akan kita kelola:

group_vars/databases/vault.yml (terenkripsi)
---
vault_postgres_password: "S3cure-R0ot#Pass"
vault_db_user_password: "App-Us3r#Pass"
vault_mysql_root_password: "My5ql-R00t#Pass"
vault_mongo_admin_password: "M0ng0-Adm1n#Pass"
vault_mongo_user_password: "M0ng0-App#Pass"

Saat menjalankan playbook, tambahkan flag --ask-vault-pass (atau gunakan --vault-password-file untuk CI/CD). Jangan lupa task yang menyentuh kredensial diberi no_log: true agar password tidak bocor ke output playbook.

Membuat Database dan User di PostgreSQL

Mari mulai dari skenario paling umum: membuat database ecommerce beserta user aplikasi yang punya akses ke database tersebut. Karena seluruh operasi berjalan lewat TCP, kita perlu memberikan login_user dan login_password (admin) kepada setiap modul:

playbook-postgresql.yml
- name: Kelola database dan user PostgreSQL
  hosts: postgresql_servers
  become: true
  vars:
    pg_login_user: postgres
    db_name: ecommerce
    db_user: app_ecommerce
    db_owner: app_ecommerce
  tasks:
    - name: Buat database ecommerce
      community.postgresql.postgresql_db:
        name: "{{ db_name }}"
        owner: "{{ db_owner }}"
        encoding: UTF8
        login_user: "{{ pg_login_user }}"
        login_password: "{{ vault_postgres_password }}"
        no_log: true
 
    - name: Buat user aplikasi ecommerce
      community.postgresql.postgresql_user:
        name: "{{ db_user }}"
        password: "{{ vault_db_user_password }}"
        login_user: "{{ pg_login_user }}"
        login_password: "{{ vault_postgres_password }}"
        no_log: true

Perhatikan beberapa hal penting:

  • Idempotency: modul postgresql_db dengan state: present (default) akan membuat database jika belum ada, dan tidak melakukan apa pun jika sudah ada — tidak seperti CREATE DATABASE manual yang error ketika database sudah terlanjur ada.
  • no_log: true: memastikan password tidak tercetak di log, sekaligus meredam secret dari mode verbose.
  • owner: mendeklarasikan pemilik database. Menetapkan pemilik yang tepat sejak awal menghindari operasi ALTER DATABASE di kemudian hari.

Tip

Jika playbook dijalankan langsung dari server PostgreSQL itu sendiri (bukan dari control node lain), kalian bisa memakai login_unix_socket atau cukup menghilangkan login_password sehingga modul memakai autentikasi peer sebagai user lokal (misalnya user postgres). Ini lebih aman daripada membuka password admin di konfigurasi yang tidak perlu.

Manajemen Privilege: Prinsip Least Privilege

Membuat user saja tidak cukup — user aplikasi harus diberikan hak akses yang paling kecil yang memungkinkan agar aplikasi tetap berjalan. Untuk PostgreSQL, modul yang tepat adalah postgresql_privs:

playbook-privileges.yml
- name: Kelola privilege PostgreSQL
  hosts: postgresql_servers
  become: true
  tasks:
    - name: Berikan akses schema kepada user aplikasi
      community.postgresql.postgresql_privs:
        database: ecommerce
        roles: app_ecommerce
        type: schema
        objs: public
        privs: USAGE
        login_user: postgres
        login_password: "{{ vault_postgres_password }}"
 
    - name: Berikan akses tabel di schema public
      community.postgresql.postgresql_privs:
        database: ecommerce
        roles: app_ecommerce
        type: table
        objs: "public.*"
        privs: SELECT,INSERT,UPDATE,DELETE
        login_user: postgres
        login_password: "{{ vault_postgres_password }}"

Mengapa kita repot-repot memisahkan privilege? Karena di dunia nyata, breach paling sering dimulai dari kredensial yang memiliki akses terlalu luas. Jika user aplikasi adalah superuser dan kredensialnya bocor, penyerang bisa menghapus seluruh database. Dengan privilege minimal, dampak kebocoran bisa dibatasi. Pola yang umum dipakai:

  • User admin (misal postgres) — hanya dipakai saat provisioning dan migrasi, tidak untuk aplikasi.
  • User aplikasi — hanya SELECT/INSERT/UPDATE/DELETE pada database miliknya.
  • User replikasi — khusus untuk streaming replication, tidak bisa login ke aplikasi.

Membuat Database dan User di MySQL / MariaDB

Untuk MySQL/MariaDB, pola-nya hampir sama dengan community.mysql. Perhatikan bahwa user di MySQL diidentifikasi oleh kombinasi name dan host, dan privilege diberikan langsung melalui parameter priv:

playbook-mysql.yml
- name: Kelola database dan user MySQL
  hosts: mysql_servers
  become: true
  vars:
    mysql_login_user: root
    db_name: ecommerce
    db_user: app_ecommerce
  tasks:
    - name: Buat database ecommerce
      community.mysql.mysql_db:
        name: "{{ db_name }}"
        encoding: utf8mb4
        login_user: "{{ mysql_login_user }}"
        login_password: "{{ vault_mysql_root_password }}"
        login_unix_socket: /var/run/mysqld/mysqld.sock
 
    - name: Buat user aplikasi dengan privilege spesifik
      community.mysql.mysql_user:
        name: "{{ db_user }}"
        host: "10.0.0.%"
        password: "{{ vault_db_user_password }}"
        priv: "ecommerce.*:SELECT,INSERT,UPDATE,DELETE"
        state: present
        login_user: "{{ mysql_login_user }}"
        login_password: "{{ vault_mysql_root_password }}"

Sintaks priv di MySQL berbentuk "database.table:PENGATURAN". Beberapa contoh:

Nilai privArti
ecommerce.*:ALLSemua hak atas semua tabel di database ecommerce
ecommerce.*:SELECT,INSERT,UPDATE,DELETEAkses baca-tulis standar aplikasi
*.*:ALLSuperuser (sangat berbahaya untuk user aplikasi!)

Warning

Jika kalian menuliskan host: "%", user tersebut bisa login dari mana saja di jaringan — termasuk internet jika port MySQL terbuka. Batasi host ke subnet internal aplikasi (misalnya 10.0.0.%) dan pastikan port MySQL tidak diekspos ke publik. Di episode 27 nanti kita akan membahas hardening lebih lanjut.

Membuat User di MongoDB

MongoDB sedikit berbeda: user dikelola per authentication database (database: admin untuk user administratif), dan roles adalah daftar objek yang terdiri dari db dan role:

playbook-mongodb.yml
- name: Kelola user MongoDB
  hosts: mongodb_servers
  become: true
  vars:
    mongo_host: 127.0.0.1
    db_name: ecommerce
    db_user: app_ecommerce
  tasks:
    - name: Buat user aplikasi di MongoDB
      community.mongodb.mongodb_user:
        login_host: "{{ mongo_host }}"
        login_user: "{{ mongo_admin_user }}"
        login_password: "{{ vault_mongo_admin_password }}"
        database: admin
        name: "{{ db_user }}"
        password: "{{ vault_mongo_user_password }}"
        roles:
          - db: "{{ db_name }}"
            role: readWrite
        state: present

Perhatikan bahwa modul mongodb_user memerlukan login_user/login_password dari user administratif (di authentication database admin) untuk membuat user lain. MongoDB adalah document store, jadi "schema migration" di sini lebih sering berupa validasi schema di level aplikasi atau koleksi dengan validator — perbedaan konseptual yang perlu kalian ingat.

Backup Automation

Backup adalah salah satu tugas yang paling sering "dilupakan" sampai terjadi bencana. Dengan Ansible, backup bisa dijadwalkan dan dijalankan secara konsisten — dan hasilnya bisa diverifikasi.

Pendekatan paling idiomatis untuk PostgreSQL adalah memanfaatkan modul postgresql_db dengan state: dump, yang mengeluarkan file SQL dump ke target:

playbook-backup.yml
- name: Backup otomatis database PostgreSQL
  hosts: postgresql_servers
  become: true
  vars:
    backup_dir: /var/backups/postgres
    db_name: ecommerce
  tasks:
    - name: Siapkan direktori backup
      ansible.builtin.file:
        path: "{{ backup_dir }}"
        state: directory
        owner: postgres
        group: postgres
        mode: "0750"
 
    - name: Dump database ecommerce
      community.postgresql.postgresql_db:
        name: "{{ db_name }}"
        state: dump
        target: "{{ backup_dir }}/ecommerce-{{ ansible_date_time.date }}.sql"
        login_user: postgres
        login_password: "{{ vault_postgres_password }}"

Perhatikan penggunaan {{ ansible_date_time.date }}fact yang dikumpulkan Ansible dari server — sehingga setiap hari menghasilkan nama file baru. Jangan lupa menambahkan task retensi menggunakan modul ansible.builtin.find + ansible.builtin.file untuk menghapus backup yang lebih tua dari, misalnya, 7 hari, agar disk tidak penuh.

Banyak tim juga menggabungkan pendekatan modul dengan perintah pg_dump/mysqldump langsung via shell + register, terutama untuk kebutuhan yang belum didukung modul (misal backup dengan kompresi atau opsi tertentu). Contohnya:

playbook-backup-shell.yml
- name: Dump via pg_dump dengan kompresi
  ansible.builtin.shell: |
    pg_dump -h 127.0.0.1 -U postgres -Fc {{ db_name }} > "{{ backup_dir }}/{{ db_name }}-{{ ansible_date_time.date }}.dump"
  become_user: postgres
  become: true
  args:
    executable: /bin/bash
  changed_when: false
  register: backup_result

Important

Backup tanpa proses restore yang teruji bukanlah backup. Jadwalkan juga proses verifikasi — misalnya restore ke database staging sementara lalu cek jumlah baris tabel — sebelum menyatakan backup tersebut valid. Banyak insiden "data hilang" justru terungkap ketika tim mencoba restore dan menemukan dump-nya korup atau tidak lengkap.

Deployment Pattern: Replication Master-Slave

Ketika database mulai dipakai banyak server atau kebutuhannya read-heavy, kalian membutuhkan replikasi. Alih-alih mengetik konfigurasi pg_hba.conf, postgresql.auto.conf, dan pg_basebackup secara manual di setiap server, collection community.postgresql menyediakan modul postgresql_primary dan postgresql_standby:

playbook-replication.yml
- name: Konfigurasi streaming replication
  hosts: db_servers
  become: true
  vars:
    pg_primary_host: db-01.internal
  tasks:
    - name: Konfigurasi server primary
      community.postgresql.postgresql_primary:
        login_host: "{{ pg_primary_host }}"
        login_user: postgres
        login_password: "{{ vault_postgres_password }}"
        host: "{{ pg_primary_host }}"
        replication_user: repl
        replication_password: "{{ vault_pg_replication_password }}"
      when: inventory_hostname == groups['db_primary'][0]
 
    - name: Konfigurasi server standby
      community.postgresql.postgresql_standby:
        login_host: "{{ pg_primary_host }}"
        login_user: postgres
        login_password: "{{ vault_postgres_password }}"
        host: "{{ pg_primary_host }}"
        primary_conninfo: "host={{ pg_primary_host }} port=5432 user=repl password={{ vault_pg_replication_password }} application_name=standby1"
      when: inventory_hostname in groups['db_standby']

Note

Replication yang diatur manual seperti di atas cukup untuk failover manual dan distribusi beban baca. Untuk high availability yang sungguh-sungguh — dengan automatic failover dan healthcheck — produksi biasanya memakai Patroni atau Replication Manager (repmgr) yang dikelola di atas Ansible. Konsep yang kalian pelajari di sini tetap menjadi fondasinya.

Connection Pooling: PgBouncer dan ProxySQL

Koneksi database itu mahal. Setiap koneksi TCP ke PostgreSQL memakan memori dan waktu. Ketika aplikasi meledak trafiknya, jumlah koneksi bisa melampaui max_connections, dan database mulai menolak koneksi — aplikasi ikut kolaps. Solusi standarnya adalah connection pooler: aplikasi cukup memegang sedikit koneksi ke pooler, dan pooler mem-reuse koneksi ke database.

  • PgBouncer untuk PostgreSQL — mode transaction adalah pilihan paling populer.
  • ProxySQL untuk MySQL/MariaDB — selain pooling, juga punya fitur routing query dan query rewrite.

Deploy PgBouncer dengan template + handler seperti biasa:

templates/pgbouncer.ini.j2
[databases]
ecommerce = host=127.0.0.1 port=5432 dbname=ecommerce
 
[pgbouncer]
listen_addr = 0.0.0.0
listen_port = 6432
auth_type = md5
auth_file = /etc/pgbouncer/userlist.txt
pool_mode = transaction
max_client_conn = 100
default_pool_size = 20
logfile = /var/log/pgbouncer/pgbouncer.log
pidfile = /var/run/pgbouncer/pgbouncer.pid
playbook-pgbouncer.yml
- name: Deploy PgBouncer
  hosts: pgbouncer_servers
  become: true
  tasks:
    - name: Install PgBouncer
      ansible.builtin.apt:
        name: pgbouncer
        state: present
 
    - name: Render konfigurasi PgBouncer
      ansible.builtin.template:
        src: templates/pgbouncer.ini.j2
        dest: /etc/pgbouncer/pgbouncer.ini
        owner: postgres
        group: postgres
        mode: "0644"
      notify: restart pgbouncer

Aplikasi kemudian cukup mengarahkan koneksinya ke port 6432 — perubahan di sisi aplikasi minimal, sementara ketahanan database meningkat drastis. Pola yang sama berlaku untuk ProxySQL di MySQL/MariaDB.

Best Practices Mengelola Database dengan Ansible

Berikut praktik yang membedakan engineer database yang amatir dengan yang profesional:

  1. Semua kredensial di Ansible Vault — tidak ada password database plaintext di Git, di playbook, atau di inventory. Gunakan --ask-vault-pass di lokal dan --vault-password-file di CI/CD. (Detail lengkap ada di episode 14.)
  2. Operasi idempotent — gunakan modul (postgresql_db, mysql_user, dll) bukan shell + SQL mentah. Modul tahu cara membandingkan state; shell tidak. Jika terpaksa pakai shell, bungkus dengan kondisi changed_when dan register.
  3. Prinsip least privilege — pisahkan user admin, user aplikasi, dan user replikasi. Beri privilege sesempit mungkin, batasi host.
  4. Uji perubahan di staging dulu — jalankan playbook di environment staging yang datanya hasil restore backup produksi (data anonymized bila perlu). Manfaatkan ansible-playbook --check untuk dry-run dan Molecule (episode 18) untuk pengujian role.
  5. Migrasi skema harus versi — kelola file SQL migrasi di Git, jalankan secara berurutan, dan catat versi yang sudah diterapkan di tabel ledger (misal schema_migrations) agar changed_when akurat dan migrasi tidak ganda.
  6. Backup yang teruji — otomasi backup, retensi, dan proses verifikasi restore.

Kesalahan Umum (Common Pitfalls)

1. Password database plaintext di playbook atau Git

Ini kesalahan paling fatal. Password yang pernah masuk ke history Git tidak bisa benar-benar dihapus (episode 14). Selalu simpan di Ansible Vault dan gunakan no_log: true pada task yang menyentuhnya.

2. Memakai shell + SQL ketika modul tersedia

mysql dan psql via shell tidak idempotent. Contoh nyata: task yang menjalankan CREATE DATABASE akan fail di run kedua. Modul seperti mysql_db dan postgresql_db menangani hal ini dengan state: present/absent.

3. Salah menargetkan host database

Modul database terhubung via TCP ke server database. Jika port tidak bisa diakses dari control node (firewall, bind address 127.0.0.1), task akan gagal dengan Connection refused. Gunakan login_host, login_port, dan pastikan network path-nya benar. Sebaliknya, jangan mengubah bind address database ke 0.0.0.0 tanpa alasan keamanan yang kuat.

4. Privilege superuser untuk user aplikasi

Mudah tergoda memberikan ALL PRIVILEGES atau superuser agar "tidak ribet". Efeknya: satu kebocoran kredensial = database hilang total. Terapkan least privilege sejak awal.

5. Migrasi skema yang tidak idempotent

Menjalankan ALTER TABLE ... ADD COLUMN dua kali akan error. Solusinya: guard dengan IF NOT EXISTS, atau catat versi migrasi di tabel ledger. Jangan andalkan "jalan sekali saja" di production.

6. Backup tanpa retensi dan tanpa verifikasi restore

Disk penuh karena backup menumpuk, atau dump korup yang baru disadari saat bencana — keduanya sama-sama menyakitkan. Otomasikan keduanya: retensi (hapus backup > N hari) dan verifikasi (restore ke staging).

Penutup

Pada episode ini kita telah membahas automasi database dengan Ansible: collection community.postgresql, community.mysql, dan community.mongodb untuk membuat database, user, dan mengelola privilege; backup otomatis yang idempotent dan teruji; deployment pattern seperti master-slave replication dengan postgresql_primary/postgresql_standby; connection pooling dengan PgBouncer dan ProxySQL; serta best practice menyimpan kredensial di Ansible Vault dengan no_log: true.

Inti dari semua ini sederhana: database adalah aset yang paling mahal untuk diganti, jadi setiap perubahan padanya harus bisa diaudit, direproduksi, dan diuji. Dengan Ansible, pekerjaan DBA yang repetitif dan rawan menjadi konfigurasi yang deklaratif, terdokumentasi, dan idempotent.

Di episode 25 selanjutnya, kita akan membahas Monitoring & Observability Stack Automation — deployment Prometheus dan Alertmanager, Grafana dengan dashboard as code, Node Exporter, hingga logging stack seperti ELK dan Loki/Promtail — semuanya dikelola dengan Ansible agar seluruh infrastruktur yang kita bangun di series ini bisa dipantau dengan baik. Pastikan tetap semangat!

Belajar Ansible - Database Management & Automation | Belajar Ansible