Tutorial Ansible: Playbook Pertama untuk VPS Ubuntu 24.04
Instal Ansible dengan pipx di Ubuntu 24.04, buat inventory dan playbook untuk mengamankan VPS baru, lalu atasi error Permission denied dan sudo.
Yang Anda bangun
Satu mesin kontrol dengan Ansible terinstal, serta satu atau beberapa VPS Ubuntu 24.04 baru yang hanya berisi image bawaan. Pada akhir tutorial ini, Anda akan memiliki file inventory yang mencantumkan server, perintah ping ad-hoc yang membuktikan autentikasi berfungsi dari awal hingga akhir, dan playbook yang menjalankan seluruh checklist VPS baru sebagai kode: pengguna deploy dengan SSH key Anda, sshd yang diperkuat keamanannya, fail2ban, pembaruan tanpa intervensi, serta firewall yang mengizinkan OpenSSH sebelum menolak semua trafik lainnya. Arahkan playbook ini ke satu server atau dua puluh server. Jalankan dua kali. Pada proses kedua, tidak ada perubahan. Itulah tujuan utamanya.
Setelah lima belas tahun melakukan provisioning VPS, saya melihat pola yang jelas: semua orang menyiapkan lima server pertama secara manual, lalu menghabiskan akhir pekan untuk server keenam karena tidak ada yang mengingat perubahan pada lima server pertama. Panduan ini memperdalam pembahasan tentang mengelola beberapa server Linux. Gunakan panduan ini ketika Anda menyadari bahwa Anda mengetik apt install yang sama pada tiga terminal.
Apa sebenarnya Ansible dalam satu paragraf
Ansible bersifat agentless. Tidak ada daemon yang perlu diinstal pada server yang dikelolanya: mesin kontrol terhubung melalui SSH biasa, menyalin modul Python kecil ke target, menjalankannya, membaca JSON yang dicetaknya, lalu menghapusnya. Satu-satunya hal yang dibutuhkan target adalah python3, yang sudah tersedia pada setiap image Ubuntu standar. Kata yang penting adalah idempotent, dan maknanya sederhana: task menjelaskan state, bukan tindakan. state: present untuk sebuah package berarti “pastikan package ini terinstal”, bukan “jalankan installer”. Jika state tersebut sudah terpenuhi, Ansible tidak mengubah apa pun dan melaporkannya sebagai ok, bukan changed. Sifat ini merupakan inti produk tersebut, membuat playbook aman untuk dijalankan ulang, dan eksekusi ulang yang aman mengubah shell script menjadi infrastruktur.
Prasyarat dan hal penting sejak awal
- Mesin kontrol: laptop atau VPS kecil. Saya mengasumsikan Ubuntu 24.04; macOS bekerja dengan cara yang sama setelah pipx diinstal melalui Homebrew.
- Satu atau beberapa VPS target yang menjalankan Ubuntu 24.04 pada KVM dan dapat diakses sebagai root. Tidak ada apa pun yang diinstal pada VPS tersebut.
- Autentikasi dengan key SSH ke setiap target. Ansible menggunakan metode autentikasi yang sama dengan perintah
sshAnda. Jikassh root@hostmeminta kata sandi, Ansible gagal. - Di Ubuntu 24.04,
pip install ansiblegagal denganerror: externally-managed-environment. Ini merupakan kebijakan distro yang disengaja, bukan kerusakan. Gunakan pipx. - Spasi putih YAML merupakan sintaks. Indentasi yang salah menghasilkan
mapping values are not allowed in this context, dan karakter tab di mana pun akan menyebabkan kegagalan. - Pertahankan sesi SSH yang berfungsi tetap terbuka pada setiap target saat playbook memperketat konfigurasi sshd. Setiap kasus lockout yang pernah saya bantu pulihkan terjadi setelah sesi terakhir ditutup "untuk menguji dari lingkungan bersih".
Langkah 1: instal Ansible pada mesin kontrol dengan pipx, bukan pip
Insting klasiknya adalah pip3 install ansible. Pada image 24.04 yang benar-benar baru, langkah tersebut gagal satu tahap lebih awal, yaitu Command 'pip3' not found, but can be installed with: sudo apt install python3-pip, dan menginstal pip hanya membawa Anda ke hambatan yang sebenarnya:
pip3 install ansibleerror: externally-managed-environment
× This environment is externally managed
╰─> To install Python packages system-wide, try apt install
python3-xyz, where xyz is the package you are trying to
install.Ubuntu 24.04 menandai Python sistem sebagai dikelola secara eksternal (PEP 668), sehingga pip tidak dapat berebut file yang sama dengan apt. Jangan gunakan --break-system-packages; nama flag tersebut memang menjelaskan fungsinya. Solusi yang tepat adalah pipx. pipx memberi Ansible virtualenv terisolasi sendiri dan menempatkan binernya pada PATH Anda:
sudo apt update && sudo apt install -y pipx
pipx ensurepath
pipx install --include-deps ansibleBuka shell baru setelah pipx ensurepath agar perubahan PATH diterapkan. --include-deps bukan sekadar hiasan: paket ansible tidak menyediakan console script sendiri, ansible, ansible-playbook, dan yang lainnya adalah entry point dari dependensi ansible-core, sehingga tanpa flag tersebut pipx menolak instalasi dengan No apps associated with package ansible or its dependencies. Instal paket ansible, bukan ansible-core saja. Paket lengkap tersebut menyertakan collection komunitas, dan playbook ini menggunakan modul dari dua collection tersebut (ansible.posix dan community.general).
ansible --versionHasil yang benar diawali baris seperti ansible [core 2.19.x] dan menyebutkan Python yang digunakan; rilis core apa pun yang masih berlaku dapat digunakan untuk semua langkah di sini. Sebaliknya, ansible: command not found berarti ~/.local/bin belum ada pada PATH Anda. Buka shell baru, atau gunakan source ~/.bashrc.
Itulah seluruh proses instalasi. Mesin target tidak menerima apa pun.
Langkah 2: Akses dengan kunci SSH ke setiap target
ssh-keygen -t ed25519 -C "ansible control"
ssh-copy-id root@10.0.0.10
ssh-copy-id root@10.0.0.20Kemudian buktikan akses tersebut, satu kali untuk setiap host:
ssh root@10.0.0.10 true && echo okSatu baris tersebut memiliki dua fungsi: memastikan autentikasi dengan kunci dapat berjalan tanpa kata sandi dan mencatat kunci host di known_hosts. Lakukan sekarang, karena Ansible menampilkan kunci host yang belum tercatat sebagai prompt interaktif di tengah proses eksekusi. Kondisi ini terlihat persis seperti proses yang macet.
Langkah 3: inventory, gunakan INI terlebih dahulu, lalu YAML saat bertambah besar
Inventory adalah file teks yang mencantumkan mesin yang boleh diakses Ansible. Buat inventory.ini di direktori proyek baru:
[vps]
web1 ansible_host=10.0.0.10
web2 ansible_host=10.0.0.20
[vps:vars]
ansible_user=rootweb1 adalah alias yang Anda pilih. Nama ini muncul dalam output dan digunakan sebagai target dengan --limit web1. ansible_host adalah alamat sebenarnya. [vps] adalah grup, dan [vps:vars] menetapkan variabel untuk setiap host di dalamnya; ansible_user adalah akun yang digunakan Ansible untuk login. Di sampingnya, tambahkan ansible.cfg agar Anda tidak perlu mengetik -i lagi:
[defaults]
inventory = inventory.iniAnsible membaca ansible.cfg dari direktori saat ini. Untuk inventory yang sama dalam YAML, simpan sebagai inventory.yml lalu arahkan ansible.cfg ke nama tersebut. Format ini akan lebih Anda pilih ketika setiap host memiliki beberapa variabel:
vps:
hosts:
web1:
ansible_host: 10.0.0.10
web2:
ansible_host: 10.0.0.20
vars:
ansible_user: rootKeduanya setara. INI lebih mudah diperiksa sekilas untuk dua server, sedangkan YAML lebih mudah dikembangkan untuk dua puluh server. Pilih salah satunya dan gunakan secara konsisten.
Langkah 4: perintah ad hoc, pong hijau yang membuktikan semuanya
ansible all -m pingIni bukan ICMP. Modul ping merupakan uji menyeluruh: login SSH, penyalinan modul, eksekusi Python pada target, dan pembersihan. Hasil yang benar berwarna hijau, dengan satu blok untuk setiap host:
web1 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"ping": "pong"
}SUCCESS hijau berarti autentikasi, interpreter Python, dan transport berfungsi. Playbook juga akan berfungsi. UNREACHABLE! merah berarti transport gagal sebelum modul apa pun dijalankan. String persis dan perbaikannya tersedia di bagian mode kegagalan di bawah. Berikut dua perintah ad hoc lain yang perlu diketahui:
ansible all -a "uptime"
ansible all -m apt -a "update_cache=true upgrade=dist" --becomeAd hoc digunakan untuk tugas sekali jalan dan pemeriksaan. Apa pun yang akan Anda jalankan dua kali sebaiknya dimasukkan ke dalam playbook.
Langkah 5: playbook pertama, checklist VPS baru dalam bentuk kode
Inilah semua tindakan yang akan Anda lakukan secara manual dalam sepuluh menit pertama pada server baru. Simpan sebagai site.yml:
---
- name: Baseline a fresh Ubuntu VPS
hosts: vps
become: true
vars:
deploy_user: deploy
deploy_pubkey: "{{ lookup('file', '~/.ssh/id_ed25519.pub') }}"
baseline_packages:
- fail2ban
- unattended-upgrades
- ufw
baseline_services:
- fail2ban
- unattended-upgrades
tasks:
- name: Create the deploy user
ansible.builtin.user:
name: "{{ deploy_user }}"
groups: sudo
append: true
shell: /bin/bash
- name: Install the deploy user's SSH key
ansible.posix.authorized_key:
user: "{{ deploy_user }}"
key: "{{ deploy_pubkey }}"
- name: Passwordless sudo for the deploy user
ansible.builtin.copy:
dest: /etc/sudoers.d/deploy
content: "{{ deploy_user }} ALL=(ALL) NOPASSWD:ALL\n"
mode: "0440"
validate: /usr/sbin/visudo -cf %s
- name: Install baseline packages
ansible.builtin.apt:
name: "{{ baseline_packages }}"
state: present
update_cache: true
- name: Enable and start baseline services
ansible.builtin.service:
name: "{{ item }}"
state: started
enabled: true
loop: "{{ baseline_services }}"
- name: Harden sshd with a drop-in
ansible.builtin.copy:
dest: /etc/ssh/sshd_config.d/00-hardening.conf
content: |
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin prohibit-password
X11Forwarding no
mode: "0644"
validate: /usr/sbin/sshd -t -f %s
notify: Restart ssh
- name: Allow OpenSSH through ufw
community.general.ufw:
rule: allow
name: OpenSSH
- name: Enable ufw with default deny
community.general.ufw:
state: enabled
policy: deny
handlers:
- name: Restart ssh
ansible.builtin.service:
name: ssh
state: restartedBerikut adalah bagian yang perlu dipahami, bukan sekadar disalin:
Variabel berada di bawah vars: dan direferensikan dengan "{{ deploy_user }}". Beri tanda kutip pada seluruh ekspresi jika nilai diawali tanda kurung kurawal; jika tidak, parser YAML dapat salah membacanya. lookup('file', ...) membaca kunci publik dari mesin control saat runtime, sehingga playbook tidak membawa materi kunci apa pun.
Loop. loop: "{{ baseline_services }}" menjalankan task service satu kali untuk setiap item, dan output menampilkan setiap item pada barisnya sendiri. Perhatikan bahwa task apt menerima seluruh daftar paket sekaligus. Satu transaksi apt lebih cepat dan merupakan pola yang disarankan untuk paket. Gunakan loop untuk modul yang memang bekerja pada satu objek setiap kali.
Handler adalah konsep yang perlu dipahami. notify: Restart ssh tidak berarti "restart ssh sekarang". Perintah tersebut mengantrekan handler. Handler berjalan satu kali pada akhir play, dan hanya jika task yang memicunya benar-benar melaporkan changed. Jalankan kembali playbook besok: file drop-in sudah benar, task copy melaporkan ok, dan sshd tidak pernah di-restart. Baris validate: adalah pengaman pada pemicu. sshd memeriksa file sebelum mengganti file lama, sehingga kesalahan penulisan membuat task gagal, bukan membuat daemon berhenti.
PermitRootLogin prohibit-password, bukan no, secara sengaja. Playbook ini login sebagai root menggunakan kunci. prohibit-password menonaktifkan login root dengan password, tetapi koneksi Anda yang sedang digunakan tetap aktif. Setelah deploy user terbukti dapat digunakan (ssh deploy@10.0.0.10 sudo true, alamat biasa, karena web1 hanyalah alias yang diketahui Ansible), ubah ansible_user=deploy di inventory dan perketat menjadi no pada proses berikutnya. Lakukan hardening dalam urutan yang tidak dapat mengunci akses Anda sendiri.
Prefiks 00- penting. Untuk sebagian besar keyword yang dikenali sshd, kemunculan pertama yang dibaca akan digunakan. Pada Ubuntu, sshd_config memuat sshd_config.d/*.conf berdasarkan urutan leksikal sebelum bagian konfigurasinya sendiri. Image cloud Ubuntu 24.04 sudah menyertakan 60-cloudimg-settings.conf di direktori tersebut. Provider yang mengaktifkan login dengan password melalui cloud-init menambahkan 50-cloud-init.conf dengan PasswordAuthentication yes. Menamai file kita 00-hardening.conf membuatnya diurutkan paling awal dan mengalahkan kedua file tersebut.
Urutan task adalah pengaman firewall. Allow OpenSSH berjalan sebelum Enable ufw dengan kebijakan deny. Ansible menjalankan task secara ketat sesuai urutan yang tercantum, sehingga celah tersedia sebelum aturan pembatasan diterapkan. fail2ban tidak memerlukan konfigurasi tambahan agar berguna di sini. Default Ubuntu memantau sshd secara langsung. Pembahasan tentang tindakan jail dan pengaturan yang perlu disesuaikan tersedia dalam panduan fail2ban di Ubuntu 24.04.
Langkah 6: lakukan dry run dengan --check, lalu jalankan secara nyata
ansible-playbook site.yml --checkMode pemeriksaan membuat koneksi, menghitung tindakan yang akan dilakukan, dan tidak mengubah apa pun. Baca jumlah changed= dalam PLAY RECAP di bagian bawah. Jumlah itu menunjukkan banyaknya tugas yang akan mengubah setiap host. Ada satu batasan penting: mode pemeriksaan memiliki keterbatasan struktural ketika tugas berikutnya bergantung pada perubahan dari tugas sebelumnya. Ubuntu standard server image sudah menyertakan ufw, sehingga dry run playbook ini berjalan tanpa masalah. Namun, pada image minimal yang tidak menyertakannya, tugas ufw gagal dalam mode pemeriksaan karena mode ini tidak pernah benar-benar menginstal paket tersebut dan module tidak memiliki apa pun untuk dipanggil. Ini adalah keterbatasan dry run, bukan bug pada playbook Anda. Jika rencananya sudah benar:
ansible-playbook site.ymlSetiap tugas mencetak satu baris untuk setiap host, changed berwarna kuning dan ok berwarna hijau. Rekap seharusnya berbunyi:
PLAY RECAP *********************************************************************
web1 : ok=10 changed=9 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
web2 : ok=10 changed=9 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0Sepuluh ok terdiri atas pengumpulan fakta, delapan tugas, dan handler. changed Anda boleh berbeda dari milik saya sebanyak satu atau dua. Ubuntu standard image sudah menyertakan ufw dan unattended-upgrades, dan fail2ban menjalankan dirinya sendiri segera setelah apt menginstalnya. Karena itu, pada proses pertama, sebuah tugas dapat secara sah melaporkan ok, yaitu status yang dinyatakannya sudah terpenuhi. Angka yang harus bernilai nol adalah unreachable dan failed. Satu catatan tentang become: true: hal ini hanya formalitas selama Anda terhubung sebagai root. Namun, saat Anda mengubah ansible_user menjadi deploy, sudo benar-benar digunakan, dan file sudoers NOPASSWD yang diinstal playbook ini mencegah -K muncul pada baris perintah Anda. Tanpa file tersebut, Anda akan mendapatkan Missing sudo password, yang dibahas di bawah.
Langkah 7: jalankan dua kali, seperti apa idempotensi
Jalankan perintah yang sama sekali lagi segera:
web1 : ok=9 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0changed=0 dan ok berkurang satu karena handler yang tidak diberi notifikasi tidak pernah dijalankan. Tidak ada yang diinstal ulang, sshd tidak dimulai ulang, dan ufw tidak diubah. Inilah yang membuat playbook berfungsi sebagai audit sekaligus provisioner: tambahkan web3 ke inventory bulan depan lalu jalankan kembali; server baru akan dibangun, sedangkan server lama akan diverifikasi. Nilai changed yang bukan nol pada server yang belum Anda ubah menunjukkan adanya drift. Ini berarti seseorang mengubah secara manual hal yang seharusnya diubah di playbook.
Mulai dari sini, polanya berkembang. Playbook berikutnya yang layak ditulis adalah playbook untuk memasang WireGuard VPN pada VPS yang sama dan memperketat aturan ufw agar SSH hanya menerima koneksi melalui tunnel. Setelah itu, buat playbook untuk memasang Docker dan Compose pada setiap server aplikasi. Jika site.yml melewati tiga layar, pecah menjadi beberapa role, tetapi jangan lakukan sebelum itu.
Jenis kegagalan, beserta string yang akan Anda lihat
UNREACHABLE with Permission denied.
web1 | UNREACHABLE! => {
"changed": false,
"msg": "Failed to connect to the host via ssh: root@10.0.0.10: Permission denied (publickey).",
"unreachable": true
}Transport SSH gagal sebelum modul apa pun dijalankan: ansible_user salah, key belum pernah disalin ke host tersebut, atau key yang ditawarkan salah. Ulangi dengan ssh root@10.0.0.10 biasa, lalu gunakan ssh -v untuk melihat key yang ditawarkan. Jika SSH dengan password berfungsi tetapi Ansible tidak, Anda melewati ssh-copy-id.
Missing sudo password.
web1 | FAILED! => {
"msg": "Missing sudo password"
}Anda menetapkan become: true, terhubung sebagai pengguna non-root, dan pengguna tersebut memerlukan password untuk sudo. Tambahkan -K (--ask-become-pass) ke command line, atau berikan entri sudoers NOPASSWD kepada pengguna tersebut. Inilah alasan playbook memasang entri untuk deploy sebelum Anda beralih ke pengguna tersebut.
error: externally-managed-environment. Anda menjalankan pip terhadap Python sistem pada Ubuntu 24.04. Hal ini dibahas pada langkah 1: gunakan pipx, bukan pip, dan bukan --break-system-packages.
mapping values are not allowed in this context.
ERROR! Syntax Error while loading YAML.
mapping values are not allowed in this contextHampir selalu penyebabnya adalah indentasi: key berada pada tingkat yang salah, atau tidak ada spasi setelah titik dua. Nomor baris yang dilaporkan biasanya menunjuk di dekat kesalahan, bukan tepat pada kesalahan tersebut. Periksa juga baris di atasnya. Variannya, found character '\t' that cannot start any token, berarti ada tab yang masuk; YAML melarang tab. Biasakan menjalankan ansible-playbook site.yml --syntax-check sebelum setiap eksekusi, dan atur editor agar menggunakan indentasi dua spasi untuk YAML.
/usr/bin/python3: not found. Hal ini jarang terjadi pada image Ubuntu 24.04 standar, tetapi umum pada image minimal atau netboot. Eksekusi modul gagal karena target tidak memiliki Python. Bootstrap Python dengan modul raw, satu-satunya modul yang tidak memerlukan apa pun pada host tujuan: ansible all -m raw -a "apt-get update && apt-get install -y python3" --become, lalu jalankan kembali playbook.
FAQ
Apakah Ansible perlu diinstal pada server yang dikelolanya?
Tidak. Ansible tidak memerlukan agent: mesin pengendali mengirim modul Python berukuran kecil melalui SSH, menjalankannya, lalu menghapusnya. Target hanya memerlukan python3 dan akses SSH, yang keduanya sudah tersedia pada image Ubuntu standar. Satu-satunya instalasi dalam seluruh panduan ini dilakukan pada mesin pengendali.
Mengapa Ansible menampilkan pesan "Permission denied (publickey)"?
Blok UNREACHABLE! dengan Permission denied (publickey) menunjukkan bahwa autentikasi SSH gagal sebelum Ansible menjalankan apa pun. Periksa apakah ansible_user dalam inventory cocok dengan akun yang benar-benar Anda siapkan, apakah Anda menjalankan ssh-copy-id ke host tersebut, dan apakah ssh user@host biasa dapat masuk tanpa kata sandi. Perbaikan untuk perintah ssh biasa juga memperbaiki Ansible karena keduanya menggunakan transport yang sama.
Apa arti idempotent dalam Ansible?
Task menyatakan keadaan yang diinginkan, seperti "paket ini tersedia" atau "baris ini ada dalam file", bukan tindakan yang harus dilakukan. Jika keadaan tersebut sudah terpenuhi, Ansible tidak melakukan apa pun dan melaporkan ok, bukan changed. Karena itu, menjalankan playbook dua kali akan menampilkan changed=0 pada kali kedua, dan menjalankan ulang playbook menjadi audit yang aman, bukan instalasi ulang yang berisiko.
Sebaiknya menggunakan pip atau pipx untuk menginstal Ansible pada Ubuntu 24.04?
Gunakan pipx. Ubuntu 24.04 menandai Python sistem sebagai dikelola secara eksternal, sehingga pip install ansible gagal dengan error: externally-managed-environment sesuai rancangan. pipx install --include-deps ansible menempatkan Ansible dalam virtualenv terisolasi dan menyediakan ansible, ansible-playbook, serta perintah lainnya pada PATH Anda dengan rapi.
Apa perbedaan antara paket ansible dan ansible-core?
ansible-core adalah engine yang hanya menyertakan modul ansible.builtin. Paket ansible menyertakan core bersama collection komunitas terkurasi, termasuk ansible.posix (modul authorized_key) dan community.general (modul ufw), yang keduanya digunakan dalam panduan ini. Mulailah dengan paket lengkap. Gunakan core dan collection pilihan secara terbatas hanya jika Anda memiliki alasan untuk melakukannya.