Pasang satu server MongoDB secara manual itu gampang. Tapi kalau harus pasang tiga node replica set dengan konfigurasi yang sama persis, keyfile yang sama, autentikasi aktif, dan bisa dijalankan ulang kapan saja tanpa merusak cluster yang sudah jalan, ceritanya jadi lain. Di tulisan ini saya mau berbagi cara saya mengotomasinya pakai Ansible.
Oh iya, semua nama host, user, dan nilai di bawah cuma contoh, jadi sesuaikan saja dengan lingkunganmu.
Yang mau dicapai
- Tiga node MongoDB (satu primary, dua secondary) dalam replica set
rs0. - Autentikasi internal antar-node menggunakan keyfile.
authorizationaktif, dengan satu user admin.- Playbook idempoten: mau dijalankan berapa kali pun hasilnya tetap sama, dan nggak mencoba
rs.initiate()lagi di cluster yang sudah hidup. - Rahasia seperti password dan keyfile disimpan di Ansible Vault, bukan ditaruh di repository sebagai teks biasa.
Prasyarat
- Tiga server Ubuntu LTS yang bisa saling terhubung di port 27017 dan bisa dipanggil lewat nama host.
- Ansible di mesin kontrol, ditambah collection
community.mongodb:
ansible-galaxy collection install community.mongodb
Modul-modul di collection ini butuh pymongo di node target. Sebelum pasang, cek dulu versi yang didukung di dokumentasi collection-nya.
Struktur proyek
mongodb-ansible/
├── inventory/
│ ├── hosts.yml
│ └── group_vars/
│ └── mongodb/
│ ├── main.yml
│ └── vault.yml # terenkripsi dengan ansible-vault
├── roles/
│ └── mongodb/
│ ├── defaults/main.yml
│ ├── handlers/main.yml
│ ├── tasks/
│ │ ├── main.yml
│ │ ├── install.yml
│ │ ├── configure.yml
│ │ ├── replicaset.yml
│ │ └── users.yml
│ └── templates/mongod.conf.j2
└── site.yml
Inventory
# inventory/hosts.yml
all:
children:
mongodb:
hosts:
mongo-01:
mongo-02:
mongo-03:
Node pertama di grup nantinya dipakai untuk inisialisasi replica set, dan saya kasih prioritas lebih tinggi supaya dia yang terpilih jadi primary. Daftar anggotanya saya tulis jelas di group_vars biar gampang dibaca:
# inventory/group_vars/mongodb/main.yml
mongodb_members:
- host: "mongo-01:27017"
priority: 2
- host: "mongo-02:27017"
- host: "mongo-03:27017"
Variabel dan rahasia
# roles/mongodb/defaults/main.yml
mongodb_version: "8.0"
mongodb_user: mongodb
mongodb_port: 27017
mongodb_bind_ip: "127.0.0.1,{{ ansible_default_ipv4.address }}"
mongodb_dbpath: /var/lib/mongodb
mongodb_replset_name: rs0
mongodb_keyfile_path: /etc/mongodb/keyfile
mongodb_admin_user: admin
Keyfile dan password admin masuk ke Vault. Isi keyfile cukup dibuat sekali:
openssl rand -base64 756
Lalu simpan di vault.yml dan enkripsi:
# inventory/group_vars/mongodb/vault.yml (sebelum dienkripsi)
mongodb_admin_password: "ganti-dengan-password-kuat"
mongodb_keyfile_content: |
<hasil openssl rand -base64 756>
ansible-vault encrypt inventory/group_vars/mongodb/vault.yml
Instalasi
# roles/mongodb/tasks/install.yml
- name: Pasang GPG key repository MongoDB
ansible.builtin.get_url:
url: "https://pgp.mongodb.com/server-{{ mongodb_version }}.asc"
dest: "/usr/share/keyrings/mongodb-server-{{ mongodb_version }}.asc"
mode: "0644"
- name: Tambahkan repository MongoDB
ansible.builtin.apt_repository:
repo: >-
deb [ arch=amd64,arm64 signed-by=/usr/share/keyrings/mongodb-server-{{ mongodb_version }}.asc ]
https://repo.mongodb.org/apt/ubuntu {{ ansible_distribution_release }}/mongodb-org/{{ mongodb_version }} multiverse
filename: "mongodb-org-{{ mongodb_version }}"
state: present
- name: Install MongoDB dan pymongo
ansible.builtin.apt:
name:
- mongodb-org
- python3-pymongo
state: present
update_cache: true
Konfigurasi dan keyfile
# roles/mongodb/templates/mongod.conf.j2
storage:
dbPath: {{ mongodb_dbpath }}
systemLog:
destination: file
logAppend: true
path: /var/log/mongodb/mongod.log
net:
port: {{ mongodb_port }}
bindIp: {{ mongodb_bind_ip }}
processManagement:
timeZoneInfo: /usr/share/zoneinfo
security:
authorization: enabled
keyFile: {{ mongodb_keyfile_path }}
replication:
replSetName: {{ mongodb_replset_name }}
# roles/mongodb/tasks/configure.yml
- name: Buat direktori keyfile
ansible.builtin.file:
path: "{{ mongodb_keyfile_path | dirname }}"
state: directory
owner: "{{ mongodb_user }}"
group: "{{ mongodb_user }}"
mode: "0700"
- name: Pasang keyfile replica set
ansible.builtin.copy:
content: "{{ mongodb_keyfile_content }}"
dest: "{{ mongodb_keyfile_path }}"
owner: "{{ mongodb_user }}"
group: "{{ mongodb_user }}"
mode: "0400"
no_log: true
notify: Restart mongod
- name: Tulis mongod.conf
ansible.builtin.template:
src: mongod.conf.j2
dest: /etc/mongod.conf
owner: root
group: root
mode: "0644"
notify: Restart mongod
# roles/mongodb/handlers/main.yml
- name: Restart mongod
ansible.builtin.systemd_service:
name: mongod
state: restarted
Yang perlu diingat, keyfile harus sama persis di semua node dan cuma boleh dibaca user mongodb. Kalau permission-nya terlalu longgar, mongod bakal menolak jalan.
Inisialisasi replica set yang idempoten
Nah, bagian ini yang paling sering bikin playbook gagal di run kedua. Begitu user admin dibuat, localhost exception langsung tertutup, jadi perintah yang tadinya bisa jalan tanpa login sekarang minta kredensial. Triknya, cek status pakai db.hello(). Perintah ini nggak butuh autentikasi dan akan mengembalikan setName kalau replica set sudah aktif.
# roles/mongodb/tasks/replicaset.yml
- name: Cek apakah replica set sudah diinisialisasi
ansible.builtin.command: >-
mongosh --quiet --port {{ mongodb_port }}
--eval "db.hello().setName || ''"
register: mongodb_rs_check
changed_when: false
run_once: true
- name: Inisialisasi replica set
community.mongodb.mongodb_replicaset:
login_host: localhost
login_port: "{{ mongodb_port }}"
replica_set: "{{ mongodb_replset_name }}"
members: "{{ mongodb_members }}"
when: mongodb_rs_check.stdout | trim == ''
run_once: true
- name: Tunggu node pertama menjadi primary
ansible.builtin.command: >-
mongosh --quiet --port {{ mongodb_port }}
--eval "db.hello().isWritablePrimary"
register: mongodb_primary_check
until: mongodb_primary_check.stdout | trim == 'true'
retries: 30
delay: 5
changed_when: false
run_once: true
run_once tanpa delegate_to akan jalan di host pertama pada play, yaitu mongo-01. Kebetulan itu juga anggota yang saya kasih prioritas 2 di mongodb_members.
Membuat user admin
# roles/mongodb/tasks/users.yml
- name: Buat user admin
community.mongodb.mongodb_user:
login_host: localhost
login_port: "{{ mongodb_port }}"
login_user: "{{ mongodb_admin_user }}"
login_password: "{{ mongodb_admin_password }}"
create_for_localhost_exception: /root/.mongodb_admin_created
replica_set: "{{ mongodb_replset_name }}"
database: admin
name: "{{ mongodb_admin_user }}"
password: "{{ mongodb_admin_password }}"
roles:
- root
update_password: on_create
state: present
run_once: true
no_log: true
Opsi create_for_localhost_exception membuat user pertama lewat localhost exception, lalu menulis file penanda. Di run berikutnya, modul akan login seperti biasa pakai login_user dan login_password.
Menyatukan semuanya
# roles/mongodb/tasks/main.yml
- name: Install
ansible.builtin.import_tasks: install.yml
- name: Konfigurasi
ansible.builtin.import_tasks: configure.yml
- name: Terapkan perubahan konfigurasi sebelum lanjut
ansible.builtin.meta: flush_handlers
- name: Pastikan mongod aktif
ansible.builtin.systemd_service:
name: mongod
state: started
enabled: true
- name: Replica set
ansible.builtin.import_tasks: replicaset.yml
- name: User
ansible.builtin.import_tasks: users.yml
# site.yml
- name: Deploy MongoDB replica set
hosts: mongodb
become: true
roles:
- mongodb
Tinggal jalankan:
ansible-playbook -i inventory/hosts.yml site.yml --ask-vault-pass
Verifikasi
mongosh "mongodb://mongo-01:27017,mongo-02:27017,mongo-03:27017/?replicaSet=rs0&authSource=admin" \
-u admin -p --eval "rs.status().members.map(m => m.name + ' ' + m.stateStr)"
Kalau semuanya lancar, akan muncul satu PRIMARY dan dua SECONDARY. Setelah itu coba jalankan playbook sekali lagi. Semua task harusnya berstatus ok tanpa ada yang changed.
Beberapa hal yang saya pelajari
- Jangan pernah commit keyfile atau password sebagai teks biasa. Pakai Ansible Vault (atau secret manager) dari hari pertama.
- Idempotensi itu harus dites, jangan cuma diasumsikan. Jalankan playbook dua kali di lingkungan uji dan pastikan run kedua bersih.
- Batasi akses jaringan. Arahkan
bindIpke interface privat saja, lalu buka port 27017 di firewall hanya untuk anggota replica set dan aplikasi yang memang perlu. - Coba failover-nya. Matikan primary di lingkungan uji, lalu pastikan aplikasi masih bisa menulis setelah election selesai.
Di tulisan berikutnya, saya bahas cara pasang Prometheus exporter dengan Ansible supaya cluster seperti ini bisa dipantau.