Terapkan best practice penulisan manifest Puppet yang aman untuk produksi: idempotency, pengelompokan resource, pola roles dan profiles, naming convention, serta pembatasan exec. Jaga kualitas dengan puppet-lint, hiera-data centric, dan dokumentasi.

Di episode 14 kalian sudah belajar mencari dan menginstall module dari Puppet Forge serta mengembangkan module sendiri menggunakan PDK. Pada episode ini kita akan membahas aturan main yang membedakan manifest yang asal jalan dengan manifest yang aman dioperasikan di produksi: best practice.
Best practice bukan sekadar gaya penulisan. Manifest yang idempotent, terkelompok rapi, dan bebas dari efek samping exec akan menyelamatkan tim dari incident di jam 2 pagi. Pola roles dan profiles menjadi standar industri, dan alat seperti puppet-lint serta puppet strings menjaga kualitas tetap konsisten di sepanjang waktu.
Pada episode ini kita akan membahas idempotency, pengelompokan resource, pola roles dan profiles, naming convention, pembatasan penggunaan exec, prinsip hiera-data centric, serta dokumentasi otomatis dengan puppet strings.
Manifest dikatakan idempotent jika dijalankan berulang kali memberikan hasil yang sama, tanpa efek samping tambahan. Resource Puppet dirancang untuk itu: package, service, file, dan user akan membandingkan state saat ini dengan state yang diinginkan, lalu hanya mengubah yang berbeda.
package { 'nginx':
ensure => installed,
}
service { 'nginx':
ensure => running,
enable => true,
}
file { '/etc/nginx/nginx.conf':
ensure => file,
source => 'puppet:///modules/nginx/nginx.conf',
owner => 'root',
group => 'root',
mode => '0644',
require => Package['nginx'],
notify => Service['nginx'],
}Jalankan puppet apply dua kali berturut-turut; run kedua harus menampilkan status noop atau tidak ada perubahan. Jika ada resource yang terus berubah setiap run, itu indikasi manifest tidak idempotent.
Resource sebaiknya dikelompokkan berdasarkan tanggung jawab, bukan dicampur acak dalam satu file. Setiap class yang jelas fokusnya lebih mudah diuji, dipelihara, dan dipakai ulang. Class nginx menangani instalasi dan konfigurasi dasar, sedangkan konfigurasi vhost bisa dipisah ke class tersendiri.
Tip
Saat satu class bertumbuh melampaui satu layar, pecah menjadi class-child. Contoh: nginx::install, nginx::config, dan nginx::service yang di-include dari nginx::init. Ini sekaligus mempermudah pengujian unit per bagian.
Pola roles dan profiles adalah pemisahan dua lapisan yang paling direkomendasikan komunitas Puppet. Profil membungkus modul menjadi konfigurasi yang bermakna untuk sebuah peran teknis, sedangkan role menggabungkan beberapa profil menjadi satu "identitas" node.
class profile::nginx::server {
class { 'nginx': }
nginx::resource::vhost { 'api.example.com':
www_root => '/var/www/api',
}
}class role::web {
include profile::base
include profile::nginx::server
include profile::php::fpm
}Node di hiera cukup mengassign satu role, misalnya role::web, dan seluruh komposisi diurus oleh role. Profil berisi modul konkret dengan parameter nyata, sedangkan role tidak pernah berisi detail konfigurasi.
Important
Aturan emas pola ini: role hanya meng-include profil, dan profil hanya memakai modul. Jangan meletakkan konfigurasi detail langsung di role, dan jangan meng-include role dari role lain. Kedisiplinan ini menjaga hierarki tetap dangkal dan mudah dipahami.
Penamaan yang konsisten membuat manifest terbaca tanpa komentar panjang. Beberapa konvensi yang umum dipakai:
| Elemen | Konvensi | Contoh |
|---|---|---|
| Class | module dan module::subclass | nginx::server |
| Parameter | snake_case deskriptif | server_name, listen_port |
| Resource title | Nama objek nyata di sistem | Package['nginx'] |
| Variabel fakta | Ikuti Facter | $facts['networking']['ip'] |
| Module internal | Prefix nama perusahaan | perusahaan-profile_nginx |
Resource title harus unik dan bermakna, misalnya nama paket atau path file, bukan nama acak seperti rule1.
Resource exec adalah pelarian yang sering disalahgunakan. Setiap exec berpotensi mengubah sistem di luar deklaratif Puppet, sehingga harus digunakan seminimal mungkin. Jika terpaksa, selalu berikan guard agar idempotent: creates, unless, atau onlyif.
exec { 'initialise-db':
command => '/usr/local/bin/init-db.sh',
unless => '/usr/local/bin/check-db-initialised.sh',
path => ['/usr/local/bin', '/usr/bin'],
}Perhatikan bahwa exec hanya berjalan ketika guard unless mengembalikan status non-zero, sehingga pengulangan run tidak menjalankan inisialisasi dua kali.
Caution
Sebelum menulis exec, tanyakan dulu: apakah ada resource bawaan Puppet yang bisa menggantinya? Instalasi paket pakai package, file gunakan file, restart layanan gunakan service dengan notify. exec hanya untuk hal yang benar-benar tidak bisa dideklarasikan.
puppet-lint adalah linter untuk gaya penulisan manifest. Jalankan secara rutin untuk menangkap pelanggaran konvensi sebelum masuk ke review.
puppet-lint manifests/Laporan seperti quoted_strings_not_needed atau right_to_left_relationship membantu menegakkan konsistensi. Kita bisa mengabaikan aturan tertentu, misalnya arrow_on_right_operand_line, lewat file .puppet-lint.rc dengan --no-arrow_on_right_operand_line-check, tetapi jangan menonaktifkan aturan keamanan secara sembarangan.
Manifest sebaiknya sedikit berisi data dan banyak berisi logika. Pisahkan nilai yang berubah antar environment ke Hiera, bukan menanamnya sebagai default class. Dengan begitu, perbedaan staging dan produksi cukup diatur di file hieradata.
profile::nginx::server::listen_port: 443
profile::nginx::server::server_name: api.example.comclass profile::nginx::server (
Integer $listen_port = 80,
String $server_name = 'localhost',
) {
...
}Parameter class otomatis diisi dari Hiera selama key hiera sesuai nama parameter, misalnya profile::nginx::server::listen_port. Prinsip ini membuat kode tidak tersebar data yang seharusnya menjadi tanggung jawab tim operasional.
puppet strings menghasilkan dokumentasi HTML dari komentar dokumen dalam manifest dan kode Ruby. Tulis dokumentasi pada setiap class, defined type, dan parameter penting.
# @summary Mengelola instalasi dan konfigurasi Nginx.
#
# @param listen_port Port yang dipakai Nginx untuk listening.
# @param server_name Nama domain virtual host utama.
class profile::nginx::server (
Integer $listen_port = 80,
String $server_name = 'localhost',
) {Generate dokumentasi dengan puppet strings generate:
puppet strings generate --format htmlNote
Jangan lupa README module. README yang menjelaskan parameter dan contoh pemakaian membantu anggota tim baru memahami module tanpa harus membaca seluruh kode. Untuk module publik, README yang baik bahkan meningkatkan kepercayaan pengguna di Forge.
Pada episode 15 ini kalian sudah memahami bahwa manifest yang baik untuk produksi dibangun di atas idempotency, pengelompokan resource yang jelas, pola roles dan profiles, naming convention yang konsisten, serta penggunaan exec yang sangat dibatasi. Kita juga belajar menjaga kualitas dengan puppet-lint, memisahkan data ke Hiera, dan mendokumentasikan kode dengan puppet strings.
Inti yang harus dibawa pulang:
unless atau creates.Semua pola tersebut berjalan di atas resource bawaan Puppet. Namun terkadang kebutuhan lebih dari sekadar resource standar. Di episode 16 kita akan membahas Belajar Puppet - Custom Resources, Functions & Providers: defined types, custom facts untuk Facter, functions dengan modern functions API, hingga membuat custom provider per platform.