DevOps Dari Homelab ke Production

Mukadimah

Assalamualaikum dan salam sejahtera.

Terima kasih kerana memilih untuk membaca buku ini. Kalau anda sedang pegang buku ini, kemungkinan besar anda sudah pun berjaya membina homelab sendiri. Anda sudah tahu pasang Proxmox, deploy container dengan Docker, dan setup rangkaian asas. Tahniah, itu bukan pencapaian kecil.

Tapi sekarang, anda mungkin tertanya-tanya, “Lepas ni apa?”

Itulah soalan yang saya sendiri pernah tanya bertahun-tahun lalu.

Perjalanan Saya

Saya bermula sebagai seorang yang suka “utak-atik” server di rumah. Satu PC lama jadi hypervisor, pasang pfSense untuk firewall, buat NAS dengan TrueNAS, dan main-main dengan Docker container. Semuanya berjalan dalam homelab kecil di bilik tidur.

Dari pengalaman saya, homelab itu sebenarnya sekolah terbaik. Di situ saya belajar tentang Linux, networking, virtualization, dan containerization tanpa perlu risau kalau server crash. Kalau rosak, format dan mula semula. Tiada siapa marah, tiada downtime yang menjejaskan pelanggan.

Tapi apabila saya mula bekerja dalam industri, saya sedar ada satu jurang yang besar. Skill teknikal homelab memang berguna, tetapi dunia production jauh lebih kompleks. Bukan sekadar “benda jalan”, tetapi bagaimana nak pastikan benda itu jalan secara konsisten, boleh dipercayai, dan boleh diskala.

Di sinilah DevOps masuk.

Saya mula belajar tentang CI/CD pipeline, Infrastructure as Code, monitoring, dan automation. Saya sedar bahawa banyak konsep DevOps sebenarnya sudah saya praktikkan dalam homelab, cuma saya tak tahu namanya dan tak tahu cara buat secara profesional.

Kenapa Buku Ini Wujud

Buku ini adalah sambungan kepada panduan homelab yang saya tulis sebelum ini. Kalau buku pertama mengajar anda cara membina homelab, buku ini akan membawa anda ke langkah seterusnya, iaitu menggunakan kemahiran homelab anda untuk memulakan kerjaya dalam DevOps.

Saya tulis buku ini kerana saya percaya ramai di luar sana yang berada dalam situasi yang sama. Anda sudah ada asas yang kukuh, tetapi perlukan panduan untuk menjembatani jurang antara homelab dan dunia profesional.

DevOps Lifecycle: Dari Code Sampai Production DEV (Development) PLAN Jira, Trello, Linear CODE Git, VS Code, GitHub BUILD Docker, npm, Maven TEST Jest, Selenium, SonarQube CI/CD PIPELINE GitHub Actions, GitLab CI OPS (Operations) DEPLOY K8s, Docker, ArgoCD OPERATE Terraform, Ansible MONITOR Grafana, Prometheus FEEDBACK Alerts, Logs, Metrics Continuous Feedback Loop
DevOps Lifecycle

Siapa Patut Baca Buku Ini

Buku ini ditulis khas untuk:

  • IT professional yang sudah ada pengalaman dengan homelab dan ingin upgrade ke DevOps
  • System administrator yang ingin beralih ke peranan yang lebih moden
  • Developer yang ingin memahami sisi operations dan deployment
  • Pelajar IT yang sudah bermain dengan homelab dan ingin tahu arah kerjaya DevOps

Saya andaikan anda sudah selesa dengan:

  • Linux command line
  • Docker dan konsep containerization
  • Asas networking (IP, DNS, firewall)
  • Virtualization (Proxmox, VirtualBox, atau seumpamanya)

Kalau anda belum selesa dengan perkara di atas, saya cadangkan baca dulu buku homelab sebelum ini.

Bagaimana Membaca Buku Ini

Setiap bab dalam buku ini dibina atas bab sebelumnya. Saya cadangkan anda membaca secara berurutan, terutamanya jika ini kali pertama anda berjinak dengan DevOps.

Setiap bab akan bermula dengan pengenalan ringkas dan senarai perkara yang anda akan belajar. Di penghujung setiap bab, ada ringkasan untuk membantu anda mengingat perkara penting.

Anda akan jumpa banyak contoh praktikal, kod, dan konfigurasi yang boleh terus digunakan. Saya juga sertakan “Nota Beginner” untuk menerangkan konsep yang mungkin baru bagi sesetengah pembaca.

Saya harap buku ini dapat membantu anda dalam perjalanan DevOps anda. Ingat, semua pakar pernah bermula sebagai beginner. Yang penting, terus belajar dan terus cuba.

Semoga berjaya.

Syafiyullah Yahya www.notainfra.com

Syarat dan Penafian

DevOps: Dari Homelab ke Production

Hak Cipta (c) 2026 Syafiyullah Yahya. Hak cipta terpelihara.

Laman web: www.notainfra.com


Tiada mana-mana bahagian dalam buku ini boleh diterbitkan semula, disimpan dalam sistem simpanan data, atau dipindahkan dalam apa jua bentuk atau cara, sama ada secara elektronik, mekanikal, fotokopi, rakaman, atau sebaliknya, tanpa kebenaran bertulis terlebih dahulu daripada penulis.

Penafian:

Buku ini ditulis untuk tujuan pendidikan dan panduan sahaja. Penulis dan penerbit tidak bertanggungjawab ke atas sebarang kerosakan, kehilangan data, atau masalah teknikal yang mungkin timbul daripada penggunaan maklumat dalam buku ini.

Semua contoh kod, konfigurasi, dan arahan dalam buku ini disediakan “seadanya” tanpa jaminan dalam apa jua bentuk. Pembaca bertanggungjawab sepenuhnya untuk menguji dan mengesahkan semua arahan sebelum menggunakannya dalam persekitaran production.

Nama produk, jenama, dan tanda dagangan yang disebut dalam buku ini adalah hak milik pemilik masing-masing dan digunakan untuk tujuan pengenalan sahaja.

Versi: 1.0 Tarikh Terbit: 2026


Ditulis dengan penuh dedikasi oleh Syafiyullah Yahya.

Untuk maklum balas, pertanyaan, atau pembetulan, sila hubungi melalui www.notainfra.com.

Bab 1: Apa Itu DevOps?

Anda mungkin pernah dengar perkataan “DevOps” berpuluh kali. Mungkin dalam iklan kerja, dalam artikel teknologi, atau dalam perbualan dengan rakan sekerja. Tapi apa sebenarnya DevOps? Adakah ia satu jawatan? Satu perisian? Satu budaya?

Jawapan ringkasnya: semua sekali. Dan dalam bab ini, kita akan kupas satu persatu supaya anda betul-betul faham apa yang anda sedang masuki.

Apa yang anda akan belajar:

  • Definisi DevOps dan kenapa ia wujud
  • Perbezaan antara DevOps dan IT tradisional
  • Tiang-tiang utama budaya DevOps
  • Peranan dan laluan kerjaya dalam DevOps
  • Bagaimana kemahiran homelab anda boleh diterjemahkan ke DevOps

1.1 Definisi DevOps

DevOps adalah gabungan dua perkataan: Development dan Operations. Pada asasnya, ia adalah satu set amalan, budaya, dan alatan yang menyatukan pasukan pembangunan perisian (Dev) dan pasukan operasi IT (Ops) supaya mereka boleh bekerja bersama dengan lebih cekap.

Dalam model tradisional, developer menulis kod dan kemudian “lempar” kepada pasukan operations untuk di-deploy. Kalau ada masalah, kedua-dua pihak saling menuding jari. Developer kata “Di mesin saya jalan,” dan Ops kata “Itu bukan masalah server.”

Nota Beginner: Istilah “Dev” merujuk kepada sesiapa yang menulis kod atau membina aplikasi. “Ops” merujuk kepada sesiapa yang menguruskan server, rangkaian, dan infrastruktur. DevOps menyatukan kedua-dua dunia ini.

DevOps menyelesaikan masalah ini dengan menghapuskan “tembok” antara kedua-dua pasukan. Dalam persekitaran DevOps, semua orang berkongsi tanggungjawab dari menulis kod sehingga memastikan ia berjalan dengan baik dalam production.

Secara praktikal, DevOps melibatkan:

  • Automation untuk proses berulang seperti testing dan deployment
  • CI/CD pipeline untuk menghantar kod dari development ke production secara konsisten
  • Infrastructure as Code (IaC) untuk menguruskan infrastruktur seperti menulis perisian
  • Monitoring dan observability untuk mengesan masalah sebelum pengguna merasakannya
  • Collaboration antara semua pihak yang terlibat dalam pembangunan perisian

1.2 DevOps vs IT Tradisional

Untuk benar-benar memahami DevOps, kita perlu bandingkan dengan cara kerja tradisional. Saya akan gunakan analogi yang mudah difahami.

Model Tradisional (Waterfall)

Bayangkan anda sedang membina rumah. Dalam model tradisional, prosesnya seperti ini:

  1. Arkitek lukis pelan (Planning)
  2. Tukang bina, bina rumah (Development)
  3. Inspektor periksa rumah (Testing)
  4. Pemilik terima kunci (Deployment)

Setiap langkah mesti selesai sebelum langkah seterusnya bermula. Kalau inspektor jumpa masalah struktur, tukang bina kena buat semula. Proses ini lambat dan mahal.

Dalam IT tradisional, developer menulis kod selama berbulan-bulan, kemudian serahkan kepada QA team untuk diuji, dan akhirnya Ops team deploy ke production. Keseluruhan proses boleh mengambil masa berminggu-minggu atau berbulan-bulan.

Model DevOps

Sekarang bayangkan pendekatan berbeza. Anda bina rumah satu bilik dulu, periksa, dan biarkan orang duduk. Kemudian tambah bilik lagi, periksa, dan bukakan untuk penghuni. Setiap penambahan kecil tetapi kerap.

Inilah intipati DevOps. Kod dihantar dalam bahagian kecil (small batches), diuji secara automatik, dan di-deploy dengan cepat. Kalau ada masalah, ia dikesan awal dan diperbaiki segera.

Berikut perbandingan ringkas:

Aspek IT Tradisional DevOps
Deployment Sebulan sekali atau kurang Berkali-kali sehari
Testing Manual, di akhir proses Automatik, berterusan
Infrastruktur Setup manual, snowflake servers Infrastructure as Code, immutable
Pasukan Dev dan Ops berasingan Pasukan bersepadu
Kegagalan Besar dan jarang Kecil dan mudah dipulihkan
Maklum balas Lambat (minggu/bulan) Pantas (minit/jam)

Nota Beginner: “Snowflake server” bermaksud server yang dikonfigurasi secara manual sehingga setiap satu unik seperti kepingan salji. Ini menyukarkan penyelenggaraan. DevOps menggantikan ini dengan server yang boleh dibina semula secara automatik menggunakan kod.

1.3 Budaya DevOps

DevOps bukan sekadar alatan. Saya ulang: DevOps bukan sekadar alatan. Ramai orang tersalah faham bahawa dengan memasang Jenkins atau menggunakan Kubernetes, mereka sudah “buat DevOps.” Itu tidak benar.

DevOps pada terasnya adalah satu budaya. Alatan hanyalah pemudah cara. Tanpa budaya yang betul, alatan terbaik dunia pun tidak akan membantu.

Mari kita lihat tiang-tiang utama budaya DevOps.

Collaboration (Kerjasama)

Dalam budaya DevOps, tembok antara pasukan diruntuhkan. Developer, Ops, QA, Security, semuanya bekerja bersama dari awal projek. Bukan lagi “saya buat bahagian saya, anda buat bahagian anda.”

Dari pengalaman saya, perubahan budaya ini selalunya yang paling susah. Teknikal boleh dipelajari dalam beberapa bulan, tetapi mengubah cara orang bekerja bersama boleh mengambil masa bertahun-tahun.

Automation (Automasi)

Kalau sesuatu tugas perlu dilakukan lebih dari sekali, ia patut diautomasi. Ini adalah prinsip asas DevOps.

Anda mungkin sudah mengamalkan ini dalam homelab anda. Mungkin anda tulis script Bash untuk backup, atau guna Docker Compose supaya tak perlu taip arahan Docker yang panjang setiap kali. Itu sudah automation.

Dalam dunia DevOps profesional, automation dibawa ke tahap seterusnya:

  • Build automation: Kod dikompil secara automatik setiap kali ada perubahan
  • Test automation: Ujian dijalankan secara automatik tanpa campur tangan manusia
  • Deployment automation: Aplikasi di-deploy ke production secara automatik selepas lulus ujian
  • Infrastructure automation: Server dan rangkaian dibina menggunakan kod

CI/CD (Continuous Integration / Continuous Delivery)

CI/CD adalah nadi DevOps. Ia adalah amalan menghantar perubahan kod secara kerap dan automatik.

Continuous Integration (CI) bermaksud setiap developer menggabungkan (merge) kod mereka ke repository utama secara kerap, biasanya beberapa kali sehari. Setiap penggabungan mencetuskan automated build dan test untuk mengesan masalah awal.

Continuous Delivery (CD) bermaksud kod yang telah lulus semua ujian sentiasa dalam keadaan sedia untuk di-deploy ke production. Deployment boleh dilakukan pada bila-bila masa dengan satu klik butang.

Continuous Deployment (juga CD) pergi satu langkah lebih jauh. Setiap perubahan yang lulus ujian akan secara automatik di-deploy ke production tanpa campur tangan manusia.

Nota Beginner: Jangan keliru antara Continuous Delivery dan Continuous Deployment. Delivery bermaksud kod sedia untuk di-deploy (tetapi manusia masih klik butang). Deployment bermaksud ia secara automatik di-deploy. Kebanyakan organisasi bermula dengan Delivery sebelum beralih ke Deployment.

Monitoring dan Observability

Dalam DevOps, kerja anda tidak tamat selepas deploy. Anda perlu sentiasa memantau aplikasi dan infrastruktur untuk memastikan semuanya berjalan lancar.

Monitoring bermaksud mengumpul data seperti penggunaan CPU, memori, masa tindak balas (response time), dan kadar ralat (error rate). Observability pergi lebih jauh dengan memberikan anda keupayaan untuk memahami keadaan dalaman sistem berdasarkan data luaran.

Kalau anda pernah guna Grafana atau Prometheus dalam homelab, anda sudah ada asas yang baik. Dalam production, monitoring menjadi lebih kritikal kerana downtime boleh menjejaskan ribuan pengguna dan menyebabkan kerugian wang.

Infrastructure as Code (IaC)

IaC bermaksud menguruskan infrastruktur (server, rangkaian, firewall) menggunakan fail konfigurasi yang boleh disimpan dalam version control. Ini bermaksud anda boleh membina semula seluruh infrastruktur anda dari kosong hanya dengan menjalankan satu arahan.

Alatan popular untuk IaC termasuk Terraform, Ansible, dan Pulumi. Kalau anda pernah guna Docker Compose atau menulis Ansible playbook untuk homelab, anda sudah mula dengan IaC tanpa menyedarinya.

DevOps Lifecycle: Dari Code Sampai Production DEV (Development) PLAN Jira, Trello, Linear CODE Git, VS Code, GitHub BUILD Docker, npm, Maven TEST Jest, Selenium, SonarQube CI/CD PIPELINE GitHub Actions, GitLab CI OPS (Operations) DEPLOY K8s, Docker, ArgoCD OPERATE Terraform, Ansible MONITOR Grafana, Prometheus FEEDBACK Alerts, Logs, Metrics Continuous Feedback Loop
DevOps Lifecycle

1.4 Peranan dan Laluan Kerjaya DevOps

Salah satu soalan yang sering ditanya ialah, “Apa sebenarnya kerja seorang DevOps engineer?” Jawapannya bergantung kepada organisasi, tetapi secara umum, terdapat beberapa peranan utama dalam ekosistem DevOps.

DevOps Engineer

Ini adalah peranan yang paling umum. DevOps engineer bertanggungjawab membina dan menyelenggara CI/CD pipeline, mengurus infrastruktur menggunakan IaC, dan memastikan proses deployment berjalan lancar.

Kemahiran utama yang diperlukan:

  • Linux administration
  • Scripting (Bash, Python)
  • CI/CD tools (Jenkins, GitLab CI, GitHub Actions)
  • Containerization (Docker, Podman)
  • Container orchestration (Kubernetes)
  • IaC tools (Terraform, Ansible)
  • Cloud platforms (AWS, Azure, GCP)

Site Reliability Engineer (SRE)

SRE adalah pendekatan Google terhadap DevOps. SRE memberi tumpuan kepada kebolehpercayaan (reliability) sistem. Mereka menetapkan Service Level Objectives (SLO) dan menggunakan error budget untuk mengimbangi antara kelajuan pembangunan dan kestabilan sistem.

Platform Engineer

Platform engineer membina dan menyelenggara platform dalaman yang memudahkan developer melakukan deployment sendiri. Mereka membina “jalan raya” supaya developer boleh fokus memandu tanpa perlu risau tentang infrastruktur.

Cloud Engineer

Cloud engineer fokus kepada pengurusan infrastruktur dalam persekitaran cloud. Mereka mereka bentuk, membina, dan mengoptimumkan sumber cloud untuk prestasi dan kos.

Security Engineer (DevSecOps)

Dalam DevSecOps, keselamatan diintegrasikan ke dalam setiap peringkat pipeline DevOps. Security engineer memastikan kod dan infrastruktur mematuhi piawaian keselamatan secara automatik.

Nota Beginner: Anda tidak perlu kuasai semua peranan ini. Pilih satu yang menarik minat anda dan fokus di situ. Kebanyakan profesional bermula sebagai DevOps engineer sebelum mengkhusus dalam bidang tertentu.

1.5 Kemahiran Homelab Anda Sudah Relevan

Ini bahagian yang saya paling suka. Kalau anda sudah ada pengalaman homelab, anda sebenarnya sudah ada banyak kemahiran yang diperlukan dalam DevOps. Anda cuma perlu tahu cara menterjemahkannya.

Mari kita lihat:

Kemahiran Homelab Terjemahan DevOps
Pasang Proxmox, buat VM Virtualization, cloud computing
Guna Docker, Docker Compose Containerization, container orchestration
Setup pfSense/OPNsense Network engineering, firewall management
Tulis script Bash untuk automation Scripting, automation
Pasang Grafana + Prometheus Monitoring, observability
Setup Nginx reverse proxy Load balancing, ingress management
Backup dan restore Disaster recovery, business continuity
Troubleshoot masalah rangkaian Incident response, debugging
Guna SSH untuk remote access Remote administration, security
Self-host Gitea Version control, Git workflows

Nampak tak? Anda sudah ada asas yang kukuh. Yang anda perlukan sekarang adalah untuk memformalkan kemahiran ini dan belajar cara menggunakannya dalam konteks profesional.

Dari pengalaman saya, orang yang datang dari latar belakang homelab selalunya lebih cepat memahami konsep DevOps berbanding orang yang belajar teori sahaja. Ini kerana anda sudah “kotor tangan” dan tahu perasaan bila server crash tengah malam.

Perbezaan utama antara homelab dan production ialah skala, kebolehpercayaan, dan kerjasama pasukan. Dalam homelab, kalau server mati, anda sahaja yang terjejas. Dalam production, ribuan pengguna mungkin terkesan. Dalam homelab, anda bekerja seorang diri. Dalam production, anda bekerja dalam pasukan.

Tetapi jangan risau. Buku ini akan membimbing anda melalui peralihan ini langkah demi langkah. Kita akan bermula dengan asas yang paling penting dalam DevOps, iaitu version control dengan Git, dan kemudian bergerak ke CI/CD, containerization, IaC, dan seterusnya.

1.6 Peralatan Yang Anda Perlukan

Sebelum kita mula bab seterusnya, pastikan anda mempunyai perkara berikut:

  1. Komputer dengan Linux (atau WSL2 pada Windows). Homelab anda boleh digunakan.
  2. Git dipasang pada mesin anda.
  3. Docker dipasang dan berjalan.
  4. Akaun GitHub atau GitLab (atau Gitea dalam homelab anda).
  5. Text editor yang anda selesa, sama ada VS Code, Vim, atau Nano.

Untuk memasang Git dan Docker pada Ubuntu/Debian:

# Pasang Git
sudo apt update
sudo apt install git -y

# Sahkan pemasangan
git --version

# Pasang Docker
sudo apt install docker.io -y
sudo systemctl enable docker
sudo systemctl start docker

# Tambah user anda ke docker group
sudo usermod -aG docker $USER

Nota Beginner: Kalau anda guna homelab, anda boleh buat satu VM khas untuk belajar DevOps. Saya cadangkan Ubuntu Server 22.04 LTS atau lebih baru. Beri sekurang-kurangnya 2 CPU, 4GB RAM, dan 40GB disk.

Selepas pasang, konfigurasikan Git dengan identiti anda:

git config --global user.name "Nama Anda"
git config --global user.email "email@anda.com"

Senarai DevOps Tools dan Fungsinya

Sebelum kita masuk ke bab seterusnya, saya nak bagi gambaran besar tentang tools yang akan kita guna sepanjang ebook ini. Tak perlu hafal semua sekarang. Anggap ini sebagai rujukan pantas yang boleh anda kembali bila-bila masa.

Peta DevOps Tools Mengikut Kategori SOURCE CONTROL Git GitHub / GitLab Bitbucket Gitea (self-host) Urus kod dan kolaborasi CI/CD GitHub Actions GitLab CI / Jenkins ArgoCD / FluxCD Drone CI / Tekton Automate build, test, deploy CONTAINERIZATION Docker Podman containerd / CRI-O Docker Compose Pakej dan jalankan aplikasi ORCHESTRATION Kubernetes (K8s) K3s / MicroK8s Docker Swarm Nomad / Helm Urus container berskala INFRASTRUCTURE Terraform Ansible / Puppet Pulumi / CloudFormation Packer / Vagrant Provision dan configure infra MONITORING Prometheus / Grafana Datadog / New Relic Uptime Kuma / Zabbix Netdata / Nagios Pantau kesihatan sistem LOGGING Loki + Promtail ELK Stack Graylog / Fluentd Splunk / Seq Kumpul dan analisa log SECURITY Trivy / Grype SonarQube / Semgrep HashiCorp Vault OWASP ZAP / Snyk Imbas kerentanan dan rahsia CLOUD PROVIDERS AWS / Google Cloud / Azure DigitalOcean / Linode Cloudflare Workers / Vercel Hetzner / Vultr / OVH Infrastruktur awan on-demand COLLABORATION Slack / Microsoft Teams Jira / Linear / Trello Confluence / Notion / Wiki.js PagerDuty / OpsGenie Komunikasi dan pengurusan projek TESTING Jest / Mocha / PyTest Selenium / Cypress / Playwright k6 / Locust (load testing) Postman / Newman Uji kualiti dan prestasi Tools berwarna gelap = paling popular dan disyorkan untuk bermula
Peta DevOps Tools

Source Control (Urus Kod)

Tool Fungsi Nota
Git Version control system untuk track perubahan kod Wajib tahu. Asas segala-galanya dalam DevOps.
GitHub Platform hosting Git dengan CI/CD, issues, PR Paling popular. Free untuk projek peribadi.
GitLab Alternatif GitHub dengan CI/CD terbina dalam Boleh self-host. Sesuai untuk organisasi.
Gitea Self-hosted Git server yang ringan Sesuai untuk homelab. Kita dah setup dalam ebook pertama.

CI/CD (Automate Build, Test, Deploy)

Tool Fungsi Nota
GitHub Actions CI/CD terbina dalam GitHub Percuma untuk repo public. Mudah bermula.
GitLab CI CI/CD terbina dalam GitLab Konfigurasi via .gitlab-ci.yml.
Jenkins CI/CD server yang paling matang Self-hosted. Sangat fleksibel tapi agak kompleks.
ArgoCD GitOps continuous delivery untuk Kubernetes Auto-sync dari Git ke K8s cluster.
Drone CI CI/CD ringan berasaskan container Setiap step jalan dalam container sendiri.

Containerization (Pakej Aplikasi)

Tool Fungsi Nota
Docker Bina dan jalankan container Standard industri. Anda dah biasa dari homelab.
Docker Compose Urus multi-container dengan YAML Sesuai untuk development dan homelab.
Podman Alternatif Docker tanpa daemon Rootless by default. Serasi dengan Docker CLI.
containerd Container runtime untuk production Digunakan oleh Kubernetes secara dalaman.

Orchestration (Urus Container Berskala)

Tool Fungsi Nota
Kubernetes (K8s) Orkestrasi container untuk production Standard industri. Kita guna K3s untuk belajar.
K3s Kubernetes ringan untuk edge dan homelab Kurang dari 100MB. Sesuai untuk bermula.
Helm Package manager untuk Kubernetes Macam apt tapi untuk K8s deployments.
Docker Swarm Orkestrasi Docker yang lebih simple Lebih mudah dari K8s tapi kurang ciri.

Infrastructure as Code (Provision dan Configure)

Tool Fungsi Nota
Terraform Provision infrastruktur secara deklaratif Cipta VM, network, cloud resources dari kod.
Ansible Configuration management dan automasi Agentless. Guna SSH. Mudah dipelajari.
Pulumi IaC menggunakan bahasa pengaturcaraan sebenar Alternatif Terraform untuk developer.
Packer Bina machine image secara automatik Cipta template VM atau AMI.

Monitoring (Pantau Kesihatan Sistem)

Tool Fungsi Nota
Prometheus Kumpul dan simpan metrik masa nyata Standard untuk monitoring dalam K8s.
Grafana Visualisasi data dalam dashboard cantik Boleh connect dengan pelbagai data source.
Uptime Kuma Pantau uptime perkhidmatan Ringan. Kita dah guna dalam homelab.
Datadog Platform monitoring all-in-one (SaaS) Mahal tapi sangat berkuasa.
Zabbix Monitoring enterprise, self-hosted Percuma. Agak kompleks untuk bermula.

Logging (Kumpul dan Analisa Log)

Tool Fungsi Nota
Loki Sistem agregasi log oleh Grafana Labs Ringan. Integrates baik dengan Grafana.
Promtail Pengumpul log untuk Loki Hantar log dari server ke Loki.
ELK Stack Elasticsearch + Logstash + Kibana Berkuasa tapi berat. Perlukan banyak RAM.
Fluentd Pengumpul log universal Sokongan banyak output destinations.

Security (Keselamatan Pipeline)

Tool Fungsi Nota
Trivy Imbas kerentanan container image Percuma, cepat, mudah integrate dalam CI/CD.
SonarQube Analisis kualiti dan keselamatan kod (SAST) Self-hosted. Sokong banyak bahasa.
OWASP ZAP Imbas kerentanan aplikasi web (DAST) Percuma. Boleh automate dalam pipeline.
HashiCorp Vault Urus secrets dan credentials Simpan API keys, password dengan selamat.
Snyk Imbas kerentanan dalam dependencies Integrate dengan GitHub. Free tier tersedia.

Cloud Providers

Provider Kelebihan Sesuai Untuk
AWS Paling banyak servis, paling besar pasaran Enterprise, persijilan, kerjaya
Google Cloud Kuat dalam data dan Kubernetes (GKE) K8s, machine learning
Azure Integrasi Microsoft, hybrid cloud Organisasi guna Microsoft stack
DigitalOcean Simple, murah, developer-friendly Projek kecil, belajar cloud
Hetzner Sangat murah, prestasi baik VPS bajet, dedicated servers

Testing (Uji Kualiti)

Tool Fungsi Nota
Jest Testing framework untuk JavaScript Paling popular untuk Node.js.
PyTest Testing framework untuk Python Simple dan berkuasa.
Selenium / Playwright End-to-end browser testing Automate ujian UI web.
k6 Load testing dan performance testing Tulis test dalam JavaScript.
Postman / Newman API testing Newman untuk automate dalam CI/CD.

Nota Beginner: Jangan rasa overwhelmed tengok senarai ni. Anda tak perlu belajar semua sekarang. Dalam ebook ini, kita akan fokus kepada tools yang paling penting dan praktikal. Mulakan dengan Git, Docker, dan GitHub Actions, kemudian tambah satu persatu mengikut keperluan anda.

Tools Mana Nak Mula Dulu?

Kalau anda baru nak bermula, ini susunan yang saya cadangkan:

  1. Git + GitHub (Bab 2). Ini asas. Tanpa ini, semua yang lain tak boleh berfungsi.
  2. Docker (Bab 4). Anda dah biasa dari homelab, sekarang bawa ke level production.
  3. GitHub Actions (Bab 3). Automate deployment pertama anda.
  4. Ansible (Bab 6). Automate server configuration.
  5. Prometheus + Grafana (Bab 7). Pantau apa yang anda deploy.
  6. Kubernetes (Bab 5). Bila anda dah selesa dengan semua di atas.

Selebihnya, tambah mengikut keperluan projek anda.

Ringkasan

Dalam bab ini, kita telah belajar:

  • DevOps ialah gabungan budaya, amalan, dan alatan yang menyatukan development dan operations
  • DevOps bukan sekadar alatan, ia adalah budaya yang menekankan kerjasama, automasi, dan penambahbaikan berterusan
  • Tiang utama DevOps termasuk CI/CD, automation, monitoring, dan Infrastructure as Code
  • Terdapat pelbagai laluan kerjaya dalam DevOps termasuk DevOps engineer, SRE, platform engineer, dan lain-lain
  • Kemahiran homelab anda sudah relevan dan boleh diterjemahkan terus ke dunia DevOps profesional

Anda sudah ada asas yang kukuh dari pengalaman homelab anda. Sekarang tiba masanya untuk membina di atas asas itu. Dalam bab seterusnya, kita akan mula dengan kemahiran yang paling fundamental dalam DevOps: Git dan version control.

Jom teruskan perjalanan ini bersama.

Bab 2: Git dan Version Control

Kalau DevOps ada satu kemahiran yang anda wajib kuasai sebelum yang lain, ia adalah Git. Serius, tanpa Git, anda tidak boleh melangkah lebih jauh dalam dunia DevOps. Setiap CI/CD pipeline bermula dengan Git. Setiap Infrastructure as Code disimpan dalam Git. Setiap kerjasama pasukan bergantung kepada Git.

Berita baiknya, Git tidak susah. Ia cuma perlu masa untuk menjadi selesa. Dan kalau anda pernah self-host Gitea dalam homelab anda, anda sudah pun berjinak dengannya.

Apa yang anda akan belajar:

  • Kenapa Git penting dalam DevOps
  • Arahan asas Git yang anda perlukan setiap hari
  • Strategi branching untuk pasukan (GitFlow dan trunk-based)
  • Pull request dan code review
  • Platform Git hosting (GitHub, GitLab, Gitea)
  • Penggunaan .gitignore dan Git hooks
  • Workflow praktikal yang boleh terus digunakan

2.1 Kenapa Git Penting

Sebelum Git, developer menyimpan kod dengan cara yang mengerikan. Ada yang guna folder bernama projek_v1, projek_v2, projek_v2_final, projek_v2_final_BETUL. Ada yang email fail zip kepada rakan sekerja. Ada yang guna shared drive dan berdoa tiada siapa tulis ganti fail mereka.

Git menyelesaikan semua masalah ini. Ia adalah sistem version control yang menjejaki setiap perubahan pada kod anda. Setiap perubahan dicatat dengan siapa yang buat, bila, dan kenapa. Anda boleh kembali ke mana-mana versi terdahulu pada bila-bila masa.

Dalam konteks DevOps, Git bukan sekadar tempat simpan kod. Ia adalah:

  • Sumber kebenaran tunggal (single source of truth) untuk semua kod dan konfigurasi
  • Pencetus (trigger) untuk CI/CD pipeline. Bila anda push kod, pipeline bermula
  • Alat kerjasama yang membolehkan ramai orang bekerja pada kod yang sama tanpa konflik
  • Audit trail yang merekodkan siapa ubah apa dan bila

Nota Beginner: Version control bukan hanya untuk developer. Sebagai DevOps engineer, anda akan simpan Terraform files, Ansible playbooks, Dockerfile, Kubernetes manifests, dan pelbagai konfigurasi dalam Git. Kalau ia boleh ditulis sebagai teks, ia patut disimpan dalam Git.

2.2 Arahan Asas Git

Mari kita mula dengan arahan yang anda akan guna setiap hari. Saya andaikan anda sudah pasang Git seperti yang ditunjukkan dalam bab sebelumnya.

Mencipta Repository Baru

# Buat direktori baru untuk projek
mkdir projek-devops
cd projek-devops

# Inisialisasi Git repository
git init

# Anda akan nampak mesej:
# Initialized empty Git repository in /home/user/projek-devops/.git/

Arahan git init mencipta folder .git tersembunyi yang menyimpan semua sejarah dan metadata repository anda. Jangan sesekali padam folder ini secara manual.

Aliran Kerja Asas: Add, Commit, Push

Inilah aliran kerja Git yang paling asas. Anda akan ulang proses ini berpuluh kali sehari.

# 1. Buat atau ubah fail
echo "# Projek DevOps Pertama" > README.md

# 2. Semak status - lihat apa yang berubah
git status

# Output akan tunjukkan README.md sebagai "untracked file"

# 3. Tambah fail ke staging area
git add README.md

# 4. Commit perubahan dengan mesej yang bermakna
git commit -m "Tambah README untuk projek"

# 5. (Selepas setup remote) Push ke server
git push origin main

Mari kita fahamkan setiap langkah:

git status menunjukkan keadaan semasa working directory anda. Fail boleh berada dalam tiga keadaan: untracked (Git belum jejak), modified (sudah diubah tetapi belum staged), atau staged (sedia untuk commit).

git add memindahkan fail dari working directory ke staging area. Staging area adalah seperti “ruang menunggu” sebelum perubahan disimpan secara rasmi.

git commit menyimpan snapshot semua perubahan dalam staging area ke dalam sejarah Git. Setiap commit mempunyai ID unik (hash), mesej, penulis, dan cap masa.

git push menghantar commit tempatan anda ke repository remote (seperti GitHub atau GitLab).

Nota Beginner: Fikirkan Git seperti sistem “save game.” git add ialah memilih apa yang nak disimpan. git commit ialah menekan butang save. git push ialah upload save file ke cloud supaya tidak hilang.

Melihat Sejarah

# Lihat senarai commit
git log

# Lihat log dalam format ringkas (satu baris per commit)
git log --oneline

# Lihat log dengan graf visual untuk branches
git log --oneline --graph --all

# Lihat perubahan yang belum di-commit
git diff

# Lihat perubahan yang sudah di-stage
git diff --staged

Clone Repository Sedia Ada

Kebanyakan masa, anda tidak akan mula dari kosong. Anda akan clone repository yang sudah wujud.

# Clone dari GitHub
git clone https://github.com/username/repo-name.git

# Clone dari Gitea homelab anda
git clone https://gitea.homelab.local/username/repo-name.git

# Clone dan beri nama direktori berbeza
git clone https://github.com/username/repo-name.git nama-baru

Pull Perubahan Terkini

Sebelum mula bekerja setiap hari, pastikan anda pull perubahan terkini dari remote.

# Pull perubahan terkini
git pull origin main

# Atau kalau anda sudah set upstream
git pull

Saya cadangkan jadikan ini tabiat pertama setiap pagi sebelum mula menulis kod. Ini mengelakkan konflik yang tidak perlu.

2.3 Branching: Bekerja Secara Selari

Branching adalah salah satu ciri paling berkuasa dalam Git. Ia membolehkan anda bekerja pada ciri baru atau pembetulan pepijat tanpa mengganggu kod utama.

Bayangkan kod utama anda seperti jalan raya utama. Branch adalah jalan keluar sementara. Anda keluar dari jalan utama, buat kerja anda, dan apabila siap, anda masuk semula ke jalan utama.

# Lihat semua branch
git branch

# Buat branch baru
git branch feature/tambah-login

# Tukar ke branch baru
git checkout feature/tambah-login

# Atau buat dan tukar sekaligus (cara lebih ringkas)
git checkout -b feature/tambah-login

# Selepas siap, kembali ke main
git checkout main

# Gabungkan branch ke main
git merge feature/tambah-login

# Padam branch yang sudah digabung
git branch -d feature/tambah-login

Nota Beginner: Penamaan branch yang baik sangat penting. Gunakan format seperti feature/nama-ciri, bugfix/nama-pepijat, atau hotfix/nama-pembetulan. Ini memudahkan semua orang dalam pasukan memahami tujuan setiap branch.

2.4 Strategi Branching

Apabila anda bekerja dalam pasukan, anda perlukan strategi branching yang jelas supaya semua orang tahu cara bekerja bersama. Dua strategi yang paling popular ialah GitFlow dan trunk-based development.

GitFlow

GitFlow adalah strategi yang lebih berstruktur. Ia menggunakan beberapa jenis branch:

  • main (atau master): Kod production yang stabil
  • develop: Branch integrasi untuk ciri-ciri baru
  • feature/*: Branch untuk setiap ciri baru
  • release/*: Branch untuk menyediakan release baru
  • hotfix/*: Branch untuk pembetulan segera di production
main ──────●──────────────────●──────────── (production)
            \                /
develop ─────●───●───●───●──● ───────────── (integration)
              \     / \     /
feature/a ─────●───●   \   /
                    feature/b ──●───●

Aliran kerja GitFlow:

# Mula ciri baru dari develop
git checkout develop
git checkout -b feature/user-auth

# Buat kerja, commit...
git add .
git commit -m "Tambah fungsi login"

# Selesai, gabung balik ke develop
git checkout develop
git merge feature/user-auth

# Sedia untuk release
git checkout -b release/1.0.0
# Buat testing, fix bugs...

# Deploy ke production
git checkout main
git merge release/1.0.0
git tag v1.0.0

GitFlow sesuai untuk projek yang mempunyai jadual release yang tetap, contohnya release sebulan sekali atau setiap sprint.

Trunk-Based Development

Trunk-based development adalah pendekatan yang lebih ringkas dan digemari dalam budaya DevOps moden. Semua developer bekerja pada satu branch utama (trunk), biasanya main.

main ──●──●──●──●──●──●──●──●──●── (semua kerja di sini)
        \   /       \   /
    short-lived   short-lived
     branch        branch

Prinsip utama:

  • Branch hidup tidak lebih dari satu atau dua hari
  • Perubahan dibuat dalam bahagian kecil
  • Feature flags digunakan untuk menyembunyikan ciri yang belum siap
  • CI/CD pipeline berjalan pada setiap commit ke main
# Buat branch pendek untuk satu tugas kecil
git checkout -b fix/typo-readme

# Buat perubahan kecil
git add .
git commit -m "Betulkan typo dalam README"

# Push dan buat pull request
git push origin fix/typo-readme

# Selepas di-review dan di-merge, padam branch
git branch -d fix/typo-readme

Nota Beginner: Kalau anda baru bermula, saya cadangkan trunk-based development. Ia lebih mudah difahami dan lebih sesuai dengan amalan CI/CD. GitFlow cenderung menjadi terlalu kompleks untuk pasukan kecil.

Dari pengalaman saya, kebanyakan pasukan DevOps yang matang menggunakan trunk-based development kerana ia menggalakkan perubahan kecil dan kerap, yang sememangnya nadi DevOps.

2.5 Pull Request dan Code Review

Pull request (PR) atau merge request (MR) adalah mekanisme untuk meminta rakan sepasukan menyemak kod anda sebelum ia digabungkan ke branch utama. Ini adalah amalan penting dalam DevOps kerana ia:

  • Menangkap pepijat awal sebelum masuk ke production
  • Berkongsi pengetahuan dalam pasukan
  • Mengekalkan kualiti kod yang konsisten
  • Mencipta dokumentasi tentang kenapa perubahan dibuat

Aliran Kerja Pull Request

# 1. Buat branch untuk tugasan anda
git checkout -b feature/tambah-health-check

# 2. Buat perubahan dan commit
# Katakan anda tambah health check endpoint
git add .
git commit -m "Tambah /health endpoint untuk monitoring"

# 3. Push branch ke remote
git push origin feature/tambah-health-check

# 4. Buat pull request melalui GitHub/GitLab/Gitea
# Biasanya melalui web interface, atau guna CLI:

# Untuk GitHub CLI
gh pr create --title "Tambah health check endpoint" \
  --body "Menambah /health endpoint supaya monitoring tools boleh periksa status aplikasi"

# Untuk GitLab CLI
glab mr create --title "Tambah health check endpoint" \
  --description "Menambah /health endpoint untuk monitoring"

Tips Code Review Yang Baik

Sebagai reviewer, fokus pada perkara berikut:

  1. Logik kod: Adakah kod ini betul? Ada edge case yang terlepas?
  2. Keselamatan: Ada credential yang terdedah? Input yang tidak disahkan?
  3. Kebolehbacaan: Bolehkah orang lain faham kod ini tanpa penjelasan?
  4. Testing: Ada ujian untuk perubahan ini?
  5. Dokumentasi: Perlu ke kemaskini dokumentasi?

Sebagai penulis PR, bantu reviewer anda:

  • Tulis deskripsi yang jelas tentang apa dan kenapa
  • Pastikan PR bersaiz kecil (kurang 400 baris kalau boleh)
  • Sertakan screenshot kalau ada perubahan UI
  • Jalankan semua ujian sebelum minta review

2.6 Platform Git Hosting

Anda perlukan tempat untuk menyimpan repository Git anda secara remote. Berikut adalah pilihan utama.

GitHub

Platform paling popular. Percuma untuk repository awam dan peribadi. Dilengkapi dengan GitHub Actions untuk CI/CD.

# Setup SSH key untuk GitHub
ssh-keygen -t ed25519 -C "email@anda.com"
cat ~/.ssh/id_ed25519.pub
# Salin dan tampal ke GitHub Settings > SSH Keys

# Uji sambungan
ssh -T git@github.com

GitLab

Platform lengkap dengan CI/CD terbina dalam. Boleh di-self-host. Sangat popular dalam organisasi enterprise.

Gitea (Dari Homelab Anda)

Kalau anda sudah self-host Gitea dalam homelab, tahniah. Anda sudah ada platform Git hosting sendiri. Ini sangat bagus untuk belajar kerana anda mempunyai kawalan penuh.

# Clone dari Gitea homelab
git clone git@gitea.homelab.local:username/projek.git

# Tambah remote untuk GitHub sebagai backup
git remote add github git@github.com:username/projek.git

# Push ke kedua-dua remote
git push origin main
git push github main

Nota Beginner: Saya cadangkan anda guna GitHub untuk projek awam dan portfolio, dan Gitea dalam homelab untuk eksperimen peribadi. Mempunyai profil GitHub yang aktif adalah aset besar apabila memohon kerja DevOps.

2.7 Fail .gitignore

Fail .gitignore memberitahu Git fail mana yang tidak perlu dijejaki. Ini sangat penting kerana anda tidak mahu commit perkara seperti password, API key, atau fail yang dijana secara automatik.

Buat fail .gitignore di root repository anda:

# Buat .gitignore
cat > .gitignore << 'EOF'
# Fail persekitaran dan rahsia
.env
.env.local
.env.production
*.pem
*.key
credentials.json

# Fail sistem operasi
.DS_Store
Thumbs.db

# Dependencies
node_modules/
vendor/
__pycache__/
*.pyc

# Fail yang dijana
*.log
*.tmp
dist/
build/
*.o
*.class

# Fail IDE
.vscode/
.idea/
*.swp
*.swo

# Fail Docker yang tidak perlu
docker-compose.override.yml

# Terraform state (mengandungi maklumat sensitif)
*.tfstate
*.tfstate.backup
.terraform/
EOF

Ini adalah antara kesilapan yang paling biasa saya nampak. Developer baru sering terlupa untuk setup .gitignore dan akhirnya commit fail .env yang mengandungi password database ke GitHub. Kalau ini berlaku, anggap password itu sudah terdedah dan tukar segera, walaupun anda padam fail itu kemudian. Sejarah Git menyimpan segala-galanya.

Nota Beginner: Kalau anda sudah tersilap commit fail sensitif, gunakan git filter-branch atau alat seperti BFG Repo Cleaner untuk membuangnya dari sejarah. Tetapi langkah pertama tetap menukar semua credential yang terdedah.

2.8 Git Hooks

Git hooks adalah script yang berjalan secara automatik pada titik-titik tertentu dalam aliran kerja Git. Ia adalah bentuk automation yang paling asas dan sangat berguna.

Hooks disimpan dalam folder .git/hooks/. Berikut adalah beberapa hooks yang berguna.

Pre-commit Hook

Berjalan sebelum commit dibuat. Sesuai untuk memeriksa kualiti kod.

# Buat pre-commit hook
cat > .git/hooks/pre-commit << 'HOOK'
#!/bin/bash

echo "Menjalankan pemeriksaan sebelum commit..."

# Semak kalau ada fail .env yang cuba di-commit
if git diff --cached --name-only | grep -q '\.env'; then
    echo "AMARAN: Anda cuba commit fail .env!"
    echo "Sila keluarkan fail .env dari staging area."
    exit 1
fi

# Semak kalau ada TODO yang tertinggal
if git diff --cached | grep -q 'TODO'; then
    echo "AMARAN: Terdapat TODO dalam kod anda."
    echo "Pastikan ini disengajakan."
    # Tidak block commit, hanya amaran
fi

echo "Pemeriksaan selesai. Meneruskan commit."
HOOK

# Jadikan boleh dilaksanakan
chmod +x .git/hooks/pre-commit

Commit-msg Hook

Berjalan selepas mesej commit ditulis. Sesuai untuk memastikan format mesej commit konsisten.

# Buat commit-msg hook
cat > .git/hooks/commit-msg << 'HOOK'
#!/bin/bash

commit_msg=$(cat "$1")

# Pastikan mesej commit bermula dengan huruf besar
if ! echo "$commit_msg" | head -1 | grep -qE '^[A-Z]'; then
    echo "RALAT: Mesej commit mesti bermula dengan huruf besar."
    echo "Contoh: 'Tambah fungsi login' bukan 'tambah fungsi login'"
    exit 1
fi

# Pastikan mesej commit sekurang-kurangnya 10 aksara
if [ ${#commit_msg} -lt 10 ]; then
    echo "RALAT: Mesej commit terlalu pendek (minimum 10 aksara)."
    exit 1
fi
HOOK

chmod +x .git/hooks/commit-msg

Nota Beginner: Hooks dalam folder .git/hooks/ tidak dikongsi melalui Git. Untuk berkongsi hooks dengan pasukan, simpan mereka dalam folder seperti scripts/hooks/ dan buat setup script yang mencipta symlink. Atau lebih baik lagi, gunakan alat seperti pre-commit framework.

Berkongsi Hooks Dengan Pasukan

Cara yang lebih praktikal untuk menguruskan hooks dalam pasukan:

# Simpan hooks dalam repo
mkdir -p scripts/hooks

# Pindahkan hooks ke folder yang dijejak Git
cp .git/hooks/pre-commit scripts/hooks/pre-commit
cp .git/hooks/commit-msg scripts/hooks/commit-msg

# Buat setup script
cat > scripts/setup-hooks.sh << 'SETUP'
#!/bin/bash
echo "Memasang Git hooks..."

HOOK_DIR=".git/hooks"
SCRIPT_DIR="scripts/hooks"

for hook in "$SCRIPT_DIR"/*; do
    hook_name=$(basename "$hook")
    ln -sf "../../$SCRIPT_DIR/$hook_name" "$HOOK_DIR/$hook_name"
    echo "  Dipasang: $hook_name"
done

echo "Semua hooks telah dipasang."
SETUP

chmod +x scripts/setup-hooks.sh

# Setiap ahli pasukan hanya perlu jalankan:
# ./scripts/setup-hooks.sh

2.9 Workflow Praktikal: Dari Mula Hingga Akhir

Mari kita gabungkan semua yang telah kita belajar dalam satu contoh workflow yang lengkap. Katakan anda bekerja dalam pasukan dan perlu menambah ciri health check pada aplikasi.

# 1. Pastikan anda berada di branch main dan up to date
git checkout main
git pull origin main

# 2. Buat branch baru untuk tugasan
git checkout -b feature/health-check

# 3. Buat perubahan
cat > healthcheck.py << 'EOF'
from flask import Flask, jsonify
import datetime

app = Flask(__name__)

@app.route('/health')
def health():
    return jsonify({
        "status": "healthy",
        "timestamp": datetime.datetime.now().isoformat(),
        "version": "1.0.0"
    })

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)
EOF

# 4. Semak apa yang berubah
git status
git diff

# 5. Stage dan commit
git add healthcheck.py
git commit -m "Tambah health check endpoint untuk monitoring"

# 6. Push ke remote
git push origin feature/health-check

# 7. Buat pull request (guna GitHub CLI)
gh pr create \
  --title "Tambah health check endpoint" \
  --body "## Perubahan
- Tambah /health endpoint yang mengembalikan status, timestamp, dan versi
- Endpoint ini akan digunakan oleh monitoring system

## Cara Uji
1. Jalankan: python healthcheck.py
2. Buka: http://localhost:5000/health
3. Pastikan response JSON dikembalikan"

# 8. Selepas PR di-approve dan di-merge, kemas kini local
git checkout main
git pull origin main

# 9. Padam branch yang sudah selesai
git branch -d feature/health-check

2.10 Situasi Biasa dan Penyelesaian

Berikut adalah beberapa situasi yang anda pasti akan hadapi dan cara menanganinya.

Merge Conflict

Merge conflict berlaku apabila dua orang mengubah bahagian yang sama dalam fail yang sama.

# Apabila pull atau merge menyebabkan conflict
git pull origin main
# CONFLICT (content): Merge conflict in config.yml

# Buka fail yang bermasalah
# Anda akan nampak penanda seperti ini:
# <<<<<<< HEAD
# port: 8080
# =======
# port: 3000
# >>>>>>> origin/main

# Edit fail, pilih versi yang betul, buang penanda
# Kemudian:
git add config.yml
git commit -m "Selesaikan merge conflict pada config.yml"

Undo Perubahan

# Buang perubahan pada fail yang belum di-stage
git checkout -- nama-fail.txt

# Buang fail dari staging area (tetapi kekalkan perubahan)
git reset HEAD nama-fail.txt

# Kembali ke commit sebelumnya (buat commit baru yang membatalkan)
git revert HEAD

# Lihat fail pada commit tertentu
git show abc1234:nama-fail.txt

Stash: Simpan Kerja Sementara

# Anda tengah coding, tapi perlu tukar branch segera
git stash

# Tukar branch, buat kerja lain
git checkout main
# ... buat sesuatu ...

# Kembali dan ambil semula kerja yang di-stash
git checkout feature/my-feature
git stash pop

Nota Beginner: git stash sangat berguna apabila anda perlu tukar konteks dengan cepat. Fikirkan ia seperti meletakkan kerja anda dalam laci sementara. git stash pop mengeluarkan semula dari laci.

Ringkasan

Dalam bab ini, kita telah belajar:

  • Git adalah asas DevOps. Setiap pipeline, setiap IaC, setiap kerjasama pasukan bergantung kepada Git.
  • Arahan asas (init, add, commit, push, pull, clone) adalah rutin harian anda.
  • Branching membolehkan kerja selari tanpa mengganggu kod utama.
  • GitFlow sesuai untuk release berjadual. Trunk-based development sesuai untuk CI/CD yang kerap.
  • Pull request dan code review mengekalkan kualiti kod dalam pasukan.
  • .gitignore melindungi fail sensitif daripada masuk ke repository.
  • Git hooks menjalankan pemeriksaan secara automatik untuk mengekalkan standard.

Git mungkin kelihatan mudah pada permulaan, tetapi ia mempunyai kedalaman yang luar biasa. Jangan risau kalau anda belum ingat semua arahan. Dengan masa dan amalan, ia akan menjadi sebahagian daripada memori otot anda.

Saya cadangkan anda luangkan masa beberapa hari untuk bermain dengan Git. Buat repository dalam homelab Gitea anda, cuba buat branch, simulate merge conflict, dan praktik workflow yang kita bincangkan. Lebih banyak anda praktik, lebih selesa anda akan jadi.

Dalam bab seterusnya, kita akan melihat bagaimana Git menjadi pencetus kepada CI/CD pipeline, iaitu jantung DevOps automation.

Teruskan belajar, anda sedang berada di landasan yang betul.

Bab 3: CI/CD Pipeline

Bayangkan situasi ini. Anda baru sahaja siapkan satu feature baru untuk aplikasi web anda. Anda test di local, semua berjalan lancar. Kemudian anda SSH ke server, pull code terbaru, restart service, dan harap semuanya okay. Dua jam kemudian, ada bug. Rupanya anda terlupa run test sebelum deploy. Pernah tak jadi macam ni?

Kalau pernah, anda bukan seorang. Ini adalah masalah klasik yang hampir setiap developer pernah hadapi. Dan inilah sebab CI/CD wujud.

Dalam bab ini, kita akan belajar bagaimana automate keseluruhan proses dari code commit hingga deployment. Tak perlu lagi SSH manual ke server. Tak perlu lagi harap dan doa setiap kali deploy. Semuanya automatic, consistent, dan boleh dipercayai.

Apa yang anda akan belajar:

  • Konsep CI/CD dan kenapa ia penting untuk production
  • Menulis GitHub Actions workflow dari awal untuk Node.js, Python, dan Docker
  • Asas GitLab CI sebagai alternatif
  • Pipeline stages: build, test, dan deploy
  • Menguruskan environment variables dan secrets dengan selamat
  • Deployment strategies: blue-green, rolling, dan canary
  • Hands-on: set up CI/CD untuk projek sebenar

Apa Itu CI/CD?

CI/CD adalah singkatan untuk Continuous Integration dan Continuous Delivery (atau Continuous Deployment). Mari kita pecahkan satu per satu.

Continuous Integration (CI) bermaksud setiap kali developer push code ke repository, code tersebut akan di-build dan di-test secara automatik. Kalau ada masalah, anda akan tahu dalam beberapa minit, bukan beberapa hari kemudian bila ada orang lain complain.

Continuous Delivery (CD) pula bermaksud code yang sudah lulus semua test sedia untuk di-deploy pada bila-bila masa. Anda cuma perlu klik satu butang (atau dalam kes Continuous Deployment, ia terus deploy tanpa perlu klik apa-apa).

Nota Beginner: Fikirkan CI/CD macam assembly line di kilang kereta. Setiap bahagian kereta melalui inspection station yang berbeza sebelum sampai ke customer. Kalau satu bahagian tak pass inspection, kereta tak akan keluar dari kilang. Sama juga dengan code anda.

CI/CD Pipeline: Dari Git Push Sampai Production Git Push Developer push code ke repo Build Compile code Build Docker image Test Unit test, lint Security scan Staging Deploy ke staging Integration test Approve Manual atau auto approve Production Deploy ke live Monitor CI (Continuous Integration): Automatik setiap push CD (Continuous Delivery/Deployment) Tools Popular: GitHub Actions GitLab CI Jenkins ArgoCD Drone CI Azure DevOps
CI/CD Pipeline

Kenapa Perlu Automate?

Mungkin anda fikir, “Saya seorang je yang kerja kat projek ni. Perlu ke CI/CD?” Jawapan pendek: ya. Sebab manusia buat silap. Kita lupa run test. Kita lupa build Docker image. Kita lupa update environment variable. Machine tak lupa.

Berikut beberapa sebab utama kenapa CI/CD penting:

  1. Consistency - Setiap deployment melalui proses yang sama. Tak ada variasi “oh hari tu saya deploy cara lain.”
  2. Kelajuan - Deployment yang dulu ambil 30 minit manual boleh jadi 5 minit automatic.
  3. Keyakinan - Anda tahu code yang di-deploy sudah lulus semua test.
  4. Audit trail - Setiap deployment ada rekod. Siapa deploy, bila, apa yang berubah.
  5. Rollback mudah - Kalau ada masalah, boleh revert ke version sebelumnya dengan cepat.

GitHub Actions: Bermula dengan CI/CD

GitHub Actions adalah pilihan paling popular untuk CI/CD sekarang, terutama kalau code anda sudah ada di GitHub. Ia percuma untuk public repository dan ada free tier yang generous untuk private repository.

Struktur Asas

GitHub Actions menggunakan YAML files yang disimpan dalam folder .github/workflows/ di repository anda. Setiap file adalah satu workflow.

# .github/workflows/ci.yml
name: CI Pipeline

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  build-and-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Setup environment
        run: echo "Kita bermula di sini!"

Mari fahamkan setiap bahagian:

  • name - Nama workflow anda. Akan dipaparkan di GitHub UI.
  • on - Bila workflow ini perlu run. Di sini, setiap kali ada push ke branch main atau develop, dan setiap kali ada pull request ke main.
  • jobs - Senarai kerja yang perlu dilakukan.
  • runs-on - Machine apa yang digunakan. ubuntu-latest adalah pilihan paling common.
  • steps - Langkah demi langkah apa yang perlu dilakukan.

Workflow untuk Node.js

Ini contoh workflow lengkap untuk aplikasi Node.js:

# .github/workflows/node-ci.yml
name: Node.js CI

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [18.x, 20.x, 22.x]

    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run linter
        run: npm run lint

      - name: Run tests
        run: npm test

      - name: Run build
        run: npm run build

  security-audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20.x'
      - run: npm ci
      - name: Security audit
        run: npm audit --audit-level=high

Perhatikan penggunaan strategy.matrix. Ini bermaksud workflow akan run pada tiga versi Node.js secara selari. Kalau aplikasi anda perlu support multiple versions, ini cara terbaik untuk pastikan semuanya berjalan lancar.

Nota Beginner: npm ci berbeza daripada npm install. Ia lebih strict dan sesuai untuk CI environment kerana ia install dependencies berdasarkan package-lock.json sahaja, tanpa modify apa-apa.

Workflow untuk Python

# .github/workflows/python-ci.yml
name: Python CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        python-version: ['3.10', '3.11', '3.12']

    steps:
      - uses: actions/checkout@v4

      - name: Setup Python ${{ matrix.python-version }}
        uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}

      - name: Cache pip dependencies
        uses: actions/cache@v4
        with:
          path: ~/.cache/pip
          key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements.txt
          pip install -r requirements-dev.txt

      - name: Lint with flake8
        run: |
          flake8 . --count --select=E9,F63,F7,F82 --show-source
          flake8 . --count --max-line-length=120 --statistics

      - name: Run tests with coverage
        run: |
          pytest --cov=app --cov-report=xml --cov-report=html

      - name: Upload coverage report
        uses: actions/upload-artifact@v4
        with:
          name: coverage-report-${{ matrix.python-version }}
          path: htmlcov/

Workflow untuk Docker Build dan Push

Ini adalah workflow yang paling anda perlukan untuk production. Ia build Docker image, tag dengan betul, dan push ke container registry.

# .github/workflows/docker-publish.yml
name: Docker Build and Publish

on:
  push:
    branches: [main]
    tags: ['v*']

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write

    steps:
      - uses: actions/checkout@v4

      - name: Setup Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to Container Registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=ref,event=branch
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}
            type=sha,prefix=

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

Workflow ini akan automatically tag image anda berdasarkan Git tag. Kalau anda push tag v1.2.3, image akan di-tag sebagai 1.2.3 dan 1.2. Sangat kemas untuk version management.

GitLab CI: Alternatif yang Mantap

Kalau anda menggunakan GitLab, ia mempunyai CI/CD yang built-in. Anda cuma perlu create file .gitlab-ci.yml di root repository.

# .gitlab-ci.yml
stages:
  - build
  - test
  - deploy

variables:
  NODE_VERSION: "20"

cache:
  paths:
    - node_modules/

build:
  stage: build
  image: node:${NODE_VERSION}
  script:
    - npm ci
    - npm run build
  artifacts:
    paths:
      - dist/

test:
  stage: test
  image: node:${NODE_VERSION}
  script:
    - npm ci
    - npm test
  coverage: '/Lines\s*:\s*(\d+\.?\d*)%/'

deploy-staging:
  stage: deploy
  image: alpine:latest
  script:
    - echo "Deploying to staging..."
    - ./deploy.sh staging
  environment:
    name: staging
    url: https://staging.myapp.com
  only:
    - develop

deploy-production:
  stage: deploy
  image: alpine:latest
  script:
    - echo "Deploying to production..."
    - ./deploy.sh production
  environment:
    name: production
    url: https://myapp.com
  only:
    - main
  when: manual

Perbezaan utama GitLab CI dengan GitHub Actions ialah GitLab menggunakan konsep stages dengan lebih explicit. Setiap stage mesti selesai sebelum stage seterusnya bermula. Perhatikan juga when: manual pada deploy-production. Ini bermaksud production deployment memerlukan seseorang untuk klik butang deploy secara manual. Ini adalah good practice untuk mengelakkan accidental deployment.

Pipeline Stages: Build, Test, Deploy

Sebuah CI/CD pipeline yang baik biasanya mempunyai beberapa stage yang jelas.

Stage 1: Build

Pada stage ini, code anda di-compile atau di-bundle. Untuk aplikasi Node.js, ini mungkin npm run build. Untuk Go, go build. Untuk Docker, docker build.

Tujuan utama stage ini adalah untuk pastikan code anda boleh di-build tanpa error. Kalau ada syntax error atau missing dependency, ia akan gagal di sini.

Stage 2: Test

Stage test biasanya terbahagi kepada beberapa jenis:

  • Unit tests - Test fungsi individual. Paling cepat.
  • Integration tests - Test bagaimana komponen berbeza berinteraksi.
  • End-to-end tests - Test keseluruhan flow dari perspektif pengguna.
  • Security scanning - Scan untuk known vulnerabilities.
  • Linting - Pastikan code style consistent.
# Contoh test stage yang comprehensive
test:
  runs-on: ubuntu-latest
  services:
    postgres:
      image: postgres:16
      env:
        POSTGRES_DB: testdb
        POSTGRES_USER: testuser
        POSTGRES_PASSWORD: testpass
      ports:
        - 5432:5432
      options: >-
        --health-cmd pg_isready
        --health-interval 10s
        --health-timeout 5s
        --health-retries 5

  steps:
    - uses: actions/checkout@v4
    - name: Run unit tests
      run: npm run test:unit
    - name: Run integration tests
      run: npm run test:integration
      env:
        DATABASE_URL: postgresql://testuser:testpass@localhost:5432/testdb
    - name: Run e2e tests
      run: npm run test:e2e

Perhatikan bagaimana kita boleh spin up PostgreSQL sebagai service dalam GitHub Actions. Ini bermaksud integration test anda boleh test dengan database sebenar, bukan mock.

Stage 3: Deploy

Stage deploy adalah tempat code anda sampai ke server. Bergantung pada setup anda, ini boleh jadi SSH ke server, push ke Kubernetes cluster, atau update cloud service.

Environment Variables dan Secrets

Ini bahagian yang ramai orang buat silap. Jangan sesekali hardcode passwords, API keys, atau credentials dalam code anda. Pernah nampak tak orang push AWS keys ke GitHub? Dalam beberapa minit, bots sudah guna keys tersebut untuk spin up crypto miners.

GitHub Actions Secrets

GitHub menyediakan cara yang selamat untuk menyimpan secrets:

  1. Pergi ke repository Settings > Secrets and variables > Actions
  2. Klik “New repository secret”
  3. Masukkan nama dan value

Kemudian dalam workflow:

steps:
  - name: Deploy to production
    run: |
      ssh -i key.pem ${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }} \
        "cd /app && docker compose pull && docker compose up -d"
    env:
      API_KEY: ${{ secrets.API_KEY }}
      DATABASE_URL: ${{ secrets.DATABASE_URL }}

Environment-Specific Variables

Untuk projek yang mempunyai multiple environments (staging, production), gunakan GitHub Environments:

deploy-staging:
  runs-on: ubuntu-latest
  environment: staging
  steps:
    - name: Deploy
      run: ./deploy.sh
      env:
        APP_URL: ${{ vars.APP_URL }}
        API_KEY: ${{ secrets.API_KEY }}

deploy-production:
  runs-on: ubuntu-latest
  environment: production
  needs: [deploy-staging]
  steps:
    - name: Deploy
      run: ./deploy.sh
      env:
        APP_URL: ${{ vars.APP_URL }}
        API_KEY: ${{ secrets.API_KEY }}

Setiap environment boleh mempunyai set secrets dan variables yang berbeza. Staging guna staging database, production guna production database. Simple dan selamat.

Nota Beginner: Jangan sesekali log atau print secrets dalam pipeline output. Walaupun GitHub akan cuba mask secrets secara automatik, lebih baik berjaga-jaga.

Deployment Strategies

Okay, code anda sudah lulus semua test. Sekarang, macam mana nak deploy? Ada beberapa strategi yang biasa digunakan.

Rolling Deployment

Ini adalah strategi paling simple. Server anda di-update satu per satu. Kalau anda ada 4 server, server pertama akan di-update dulu. Selepas ia siap dan healthy, server kedua pula. Dan seterusnya.

Kelebihan: Mudah difahami, tiada downtime. Kekurangan: Untuk seketika, ada dua versi aplikasi running serentak.

# Contoh rolling deployment dengan Docker Compose
deploy:
  runs-on: ubuntu-latest
  steps:
    - name: Rolling deploy
      run: |
        for server in ${{ secrets.SERVER_LIST }}; do
          echo "Deploying to $server..."
          ssh deploy@$server "
            cd /app &&
            docker compose pull &&
            docker compose up -d --no-deps --build web &&
            sleep 10 &&
            docker compose exec web curl -f http://localhost:3000/health
          "
          echo "$server deployed successfully"
        done

Blue-Green Deployment

Bayangkan anda ada dua set server yang identical. Satu dipanggil “blue” (version semasa), satu lagi “green” (version baru). Anda deploy version baru ke green. Test. Kalau okay, tukar traffic dari blue ke green. Kalau ada masalah, tukar balik ke blue. Senang.

# Contoh blue-green dengan Nginx
deploy-blue-green:
  runs-on: ubuntu-latest
  steps:
    - name: Deploy to green environment
      run: |
        ssh deploy@${{ secrets.DEPLOY_HOST }} "
          cd /app &&
          docker compose -f docker-compose.green.yml pull &&
          docker compose -f docker-compose.green.yml up -d &&
          sleep 15 &&
          curl -f http://localhost:3001/health
        "

    - name: Switch traffic to green
      run: |
        ssh deploy@${{ secrets.DEPLOY_HOST }} "
          cp /etc/nginx/conf.d/green.conf /etc/nginx/conf.d/active.conf &&
          nginx -s reload
        "

    - name: Stop blue environment
      run: |
        ssh deploy@${{ secrets.DEPLOY_HOST }} "
          docker compose -f docker-compose.blue.yml down
        "

Canary Deployment

Canary deployment adalah strategi di mana anda deploy version baru kepada sebahagian kecil users dulu. Mungkin 5% dulu. Monitor. Kalau tak ada masalah, naikkan ke 25%, 50%, dan akhirnya 100%.

Nama “canary” datang dari amalan pelombong yang bawa burung kenari ke dalam lombong. Kalau burung mati, bermaksud ada gas berbahaya. Sama juga dengan canary deployment. Kalau 5% users mengalami masalah, anda tahu ada issue sebelum ia menjejaskan semua users.

Strategi ini lebih sesuai untuk aplikasi berskala besar dan biasanya memerlukan load balancer yang menyokong traffic splitting seperti Nginx, HAProxy, atau Kubernetes Ingress.

Perbandingan Deployment Strategies

Strategi Downtime Rollback Complexity Sesuai Untuk
Rolling Tiada Sederhana Rendah Kebanyakan aplikasi
Blue-Green Tiada Sangat cepat Sederhana Aplikasi yang perlu instant rollback
Canary Tiada Cepat Tinggi Aplikasi berskala besar

Untuk kebanyakan projek homelab-to-production, rolling deployment sudah memadai. Ia simple, berkesan, dan tidak memerlukan infrastructure tambahan. Anda boleh upgrade ke blue-green atau canary bila keperluan anda bertambah.

Nota Beginner: Jangan terus jump ke canary deployment hanya kerana Netflix atau Google gunakannya. Mulakan dengan rolling deployment. Bila anda faham betul-betul bagaimana ia berfungsi, barulah pertimbangkan strategi yang lebih advanced.

Praktikal: Set Up CI/CD untuk Projek Sebenar

Mari kita buat end-to-end. Katakan anda ada sebuah Node.js API yang menggunakan Docker. Ini complete setup.

Langkah 1: Struktur Projek

my-api/
  src/
    index.js
    routes/
    middleware/
  tests/
    unit/
    integration/
  Dockerfile
  docker-compose.yml
  docker-compose.prod.yml
  .github/
    workflows/
      ci.yml
      deploy.yml
  package.json

Langkah 2: CI Workflow

# .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      - run: npm ci
      - run: npm run lint

  test:
    runs-on: ubuntu-latest
    needs: [lint]
    services:
      postgres:
        image: postgres:16
        env:
          POSTGRES_DB: testdb
          POSTGRES_USER: testuser
          POSTGRES_PASSWORD: testpass
        ports:
          - 5432:5432
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
      redis:
        image: redis:7
        ports:
          - 6379:6379

    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      - run: npm ci
      - name: Run tests
        run: npm test
        env:
          DATABASE_URL: postgresql://testuser:testpass@localhost:5432/testdb
          REDIS_URL: redis://localhost:6379

  build-image:
    runs-on: ubuntu-latest
    needs: [test]
    if: github.ref == 'refs/heads/main'
    permissions:
      contents: read
      packages: write
    steps:
      - uses: actions/checkout@v4
      - uses: docker/setup-buildx-action@v3
      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: |
            ghcr.io/${{ github.repository }}:latest
            ghcr.io/${{ github.repository }}:${{ github.sha }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

Langkah 3: Deploy Workflow

# .github/workflows/deploy.yml
name: Deploy

on:
  workflow_run:
    workflows: [CI]
    types: [completed]
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    if: ${{ github.event.workflow_run.conclusion == 'success' }}
    environment: production
    steps:
      - name: Deploy to production server
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.DEPLOY_HOST }}
          username: ${{ secrets.DEPLOY_USER }}
          key: ${{ secrets.DEPLOY_KEY }}
          script: |
            cd /opt/my-api
            export IMAGE_TAG=${{ github.sha }}
            docker compose -f docker-compose.prod.yml pull
            docker compose -f docker-compose.prod.yml up -d
            sleep 10
            curl -f http://localhost:3000/health || exit 1
            echo "Deployment successful!"

      - name: Notify on success
        if: success()
        run: |
          curl -X POST ${{ secrets.SLACK_WEBHOOK }} \
            -H 'Content-Type: application/json' \
            -d '{"text":"Deployment successful! Version: ${{ github.sha }}"}'

      - name: Rollback on failure
        if: failure()
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.DEPLOY_HOST }}
          username: ${{ secrets.DEPLOY_USER }}
          key: ${{ secrets.DEPLOY_KEY }}
          script: |
            cd /opt/my-api
            docker compose -f docker-compose.prod.yml down
            export IMAGE_TAG=$(docker images --format '{{.Tag}}' | head -2 | tail -1)
            docker compose -f docker-compose.prod.yml up -d
            echo "Rolled back to previous version"

Perhatikan beberapa perkara penting di sini. Pertama, deploy workflow hanya run selepas CI workflow berjaya. Kedua, ia menggunakan environment production yang boleh dikonfigurasi untuk memerlukan approval. Ketiga, ada step untuk rollback kalau deployment gagal. Dan keempat, ada notification ke Slack supaya team tahu status deployment.

Nota Beginner: Untuk permulaan, anda mungkin tak perlukan semua ini. Mulakan dengan CI sahaja dulu (lint + test). Selepas selesa, tambah Docker build. Kemudian baru tambah automated deployment. Jangan cuba buat semua sekaligus.

Langkah 4: Dockerfile untuk Projek

Untuk melengkapkan setup, ini Dockerfile yang sesuai untuk production:

# Dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
RUN npm prune --production

FROM node:20-alpine
RUN addgroup -g 1001 -S appgroup && \
    adduser -S appuser -u 1001 -G appgroup
WORKDIR /app
COPY --from=builder --chown=appuser:appgroup /app/dist ./dist
COPY --from=builder --chown=appuser:appgroup /app/node_modules ./node_modules
COPY --from=builder --chown=appuser:appgroup /app/package.json ./
USER appuser
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
  CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
CMD ["node", "dist/index.js"]

Langkah 5: Docker Compose untuk Production

# docker-compose.prod.yml
services:
  web:
    image: ghcr.io/username/my-api:${IMAGE_TAG:-latest}
    restart: always
    ports:
      - "3000:3000"
    env_file:
      - .env.production
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 512M

  db:
    image: postgres:16-alpine
    restart: always
    volumes:
      - pgdata:/var/lib/postgresql/data
    env_file:
      - .env.db

  redis:
    image: redis:7-alpine
    restart: always
    command: redis-server --maxmemory 256mb --maxmemory-policy allkeys-lru

volumes:
  pgdata:

Langkah 6: Test Keseluruhan Flow

Sekarang, mari test keseluruhan flow ini.

  1. Buat satu perubahan kecil dalam code anda.
  2. Push ke branch develop dan create pull request ke main.
  3. Perhatikan CI workflow bermula secara automatik di GitHub Actions tab.
  4. Tunggu lint dan test selesai. Kalau ada error, fix dan push lagi.
  5. Merge pull request ke main.
  6. Perhatikan CI workflow run lagi, kali ini termasuk Docker build dan push.
  7. Selepas CI selesai, deploy workflow akan bermula secara automatik.
  8. Check server anda untuk pastikan deployment berjaya.

Proses ini mungkin nampak banyak langkah, tapi sebenarnya selepas setup awal, anda hanya perlu buat langkah 1 hingga 5. Selebihnya berlaku secara automatik. Itulah keajaiban CI/CD.

Tips dan Best Practices

Sebelum kita tutup bab ini, beberapa tips yang saya pelajari dari pengalaman.

Cache dependencies. Setiap kali pipeline run, ia perlu download semua dependencies. Gunakan caching untuk speed up proses ini. GitHub Actions dan GitLab CI kedua-duanya menyokong caching.

Keep pipelines fast. Kalau pipeline ambil 30 minit untuk complete, developer akan mula skip CI. Idealnya, pipeline sepatutnya selesai dalam 5 hingga 10 minit. Gunakan parallel jobs dan caching untuk achieve ini.

Protect main branch. Set up branch protection rules supaya code tak boleh di-merge ke main tanpa CI passing. Ini adalah safety net yang paling penting.

Version your artifacts. Setiap Docker image, setiap build artifact, perlu ada unique version. Gunakan Git SHA atau semantic versioning. Jangan guna latest tag sahaja untuk production.

Monitor your pipelines. Set up notifications untuk pipeline failures. Kalau pipeline gagal dan tak ada siapa perasan, ia sama sahaja macam tak ada pipeline.

Don’t test in production. Gunakan staging environment yang mirror production. Deploy ke staging dulu, test, baru deploy ke production. Ini nampak macam common sense, tapi anda akan terkejut berapa ramai orang skip langkah ini.

Use branch naming conventions. Standardize nama branch dalam team anda. Contohnya, feature/add-login, fix/broken-auth, hotfix/critical-bug. Ini memudahkan pipeline untuk trigger berdasarkan jenis perubahan. Anda juga boleh configure workflow untuk run different stages bergantung pada branch pattern.

Fail fast. Letakkan checks yang paling cepat di awal pipeline. Linting biasanya selesai dalam beberapa saat, jadi letakkan ia sebelum tests yang mungkin ambil beberapa minit. Kalau linting gagal, developer tahu dalam masa yang singkat tanpa perlu tunggu semua test selesai.

Pipeline sebagai dokumentasi. Pipeline anda sebenarnya adalah dokumentasi yang hidup tentang bagaimana projek anda di-build, di-test, dan di-deploy. Developer baru boleh baca workflow files untuk faham keseluruhan proses. Pastikan ia jelas dan well-commented.

Troubleshooting CI/CD yang Common

Sebelum kita tutup, beberapa masalah yang sering saya jumpa dan cara mengatasinya.

Pipeline pass locally tapi fail di CI. Ini biasanya disebabkan oleh perbezaan environment. Pastikan Node.js/Python version yang sama digunakan. Check juga sama ada ada dependencies yang depend pada OS-specific packages. Satu tip: gunakan Docker dalam CI supaya environment sentiasa consistent.

Pipeline terlalu lambat. Audit setiap step dan tengok mana yang paling lama. Biasanya install dependencies dan Docker build yang paling lambat. Gunakan caching aggressively. Untuk Docker, gunakan BuildKit cache. Untuk npm, cache node_modules. Untuk pip, cache pip download directory.

Flaky tests. Tests yang kadang pass, kadang fail tanpa code change. Ini biasanya disebabkan oleh race conditions, external API dependencies, atau timezone issues. Fix root cause, jangan cuma retry. Tapi kalau memang perlu, kebanyakan CI platforms ada retry mechanism.

# GitHub Actions: retry step
- name: Run flaky test
  uses: nick-invision/retry@v2
  with:
    timeout_minutes: 5
    max_attempts: 3
    command: npm run test:e2e

Secrets not available. Kalau anda nampak error berkaitan secrets kosong, pastikan anda sudah add secrets di Settings. Untuk pull requests dari forked repositories, secrets tidak available secara default kerana security reasons. Ini adalah behavior yang dijangkakan.

Ringkasan

Dalam bab ini, kita telah belajar bahawa CI/CD bukan sekadar tool. Ia adalah satu amalan yang mengubah cara kita deliver software. Dengan CI, setiap code change di-validate secara automatik. Dengan CD, deployment menjadi proses yang boleh dipercayai dan repeatable.

Kita telah explore GitHub Actions dan GitLab CI sebagai platform CI/CD. Kedua-duanya mempunyai kelebihan masing-masing, tetapi konsep asasnya sama. Kita juga telah belajar tentang pipeline stages (build, test, deploy), cara menguruskan secrets dengan selamat, dan pelbagai deployment strategies.

Yang paling penting, kita telah set up satu CI/CD pipeline yang complete untuk projek sebenar. Dari lint dan test, hingga Docker build dan automated deployment dengan rollback.

Dalam bab seterusnya, kita akan mendalami Docker untuk production. Bukan basics lagi, tetapi teknik-teknik advanced seperti multi-stage builds, image optimization, dan security hardening yang anda perlukan untuk run containers dengan yakin di production.

Bab 4: Docker untuk Production

Anda sudah tahu Docker. Anda boleh tulis Dockerfile, run docker compose up, dan deploy aplikasi di homelab anda. Itu bagus. Tapi production adalah binatang yang berbeza sama sekali.

Di homelab, kalau container crash tengah malam, anda bangun esok pagi dan restart. Di production, kalau container crash tengah malam, customer call support, support escalate ke manager, manager call anda. Tidak fun.

Bab ini bukan tentang belajar Docker dari awal. Anda sudah lepas tahap itu. Bab ini tentang bagaimana run Docker dengan yakin di production. Bagaimana buat image yang kecil dan selamat. Bagaimana monitor containers. Bagaimana pastikan semuanya berjalan walaupun anda sedang tidur.

Apa yang anda akan belajar:

  • Multi-stage builds untuk image yang optimized
  • Teknik optimization Docker image (saiz, layers, security)
  • Docker registry: Harbor dan GitHub Container Registry
  • Docker Compose untuk production vs development
  • Docker networking dalam konteks production
  • Container health checks yang berkesan
  • Strategi logging untuk containers
  • Docker security best practices
  • Resource limits dan kenapa ia penting

Multi-Stage Builds

Ini adalah teknik pertama yang anda perlu kuasai untuk production. Multi-stage builds membolehkan anda gunakan satu Dockerfile untuk build dan hasilkan image yang kecil.

Masalah dengan Dockerfile biasa: anda install build tools (compiler, dev dependencies) untuk build aplikasi, tetapi tools ini tak diperlukan untuk run aplikasi. Hasilnya? Image yang besar tanpa sebab.

Contoh: Node.js

# Stage 1: Build
FROM node:20-alpine AS builder

WORKDIR /app

COPY package*.json ./
RUN npm ci

COPY . .
RUN npm run build
RUN npm prune --production

# Stage 2: Production
FROM node:20-alpine AS production

RUN addgroup -g 1001 -S appgroup && \
    adduser -S appuser -u 1001 -G appgroup

WORKDIR /app

COPY --from=builder --chown=appuser:appgroup /app/dist ./dist
COPY --from=builder --chown=appuser:appgroup /app/node_modules ./node_modules
COPY --from=builder --chown=appuser:appgroup /app/package.json ./

USER appuser

EXPOSE 3000

HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
  CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1

CMD ["node", "dist/index.js"]

Perhatikan apa yang berlaku di sini. Stage pertama install semua dependencies (termasuk devDependencies), build aplikasi, kemudian prune devDependencies. Stage kedua hanya copy files yang diperlukan. Hasilnya? Image yang jauh lebih kecil.

Contoh: Go

Go adalah contoh terbaik untuk multi-stage builds kerana Go binary adalah standalone. Anda boleh compile di satu stage dan copy binary ke image yang sangat minimal.

# Stage 1: Build
FROM golang:1.22-alpine AS builder

WORKDIR /app

COPY go.mod go.sum ./
RUN go mod download

COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /app/server ./cmd/server

# Stage 2: Production
FROM scratch

COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /app/server /server

EXPOSE 8080

ENTRYPOINT ["/server"]

Image dari scratch hanya mengandungi binary anda dan SSL certificates. Saiznya boleh jadi serendah 10 hingga 15 MB berbanding ratusan MB kalau guna image biasa.

Nota Beginner: scratch adalah “empty” image. Ia betul-betul kosong, tiada shell, tiada tools, tiada apa-apa. Sesuai untuk Go, Rust, atau mana-mana compiled language yang menghasilkan static binary. Tapi kalau anda perlu debug, gunakan alpine sebagai base image.

Contoh: Python

# Stage 1: Build
FROM python:3.12-slim AS builder

WORKDIR /app

RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Stage 2: Production
FROM python:3.12-slim AS production

RUN groupadd -r appgroup && useradd -r -g appgroup appuser

COPY --from=builder /opt/venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"

WORKDIR /app
COPY --chown=appuser:appgroup . .

USER appuser

EXPOSE 8000

CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--workers", "4", "app:create_app()"]

Docker Image Optimization

Image size bukan sekadar soal disk space. Image yang kecil bermaksud pull lebih cepat, deploy lebih cepat, dan attack surface yang lebih kecil.

Pilih Base Image yang Sesuai

node:20          ~ 1.1 GB
node:20-slim     ~ 200 MB
node:20-alpine   ~ 130 MB

Perbezaan ini significant. Kalau anda deploy 10 kali sehari, bayangkan berapa bandwidth yang anda jimat dengan alpine.

Namun, alpine menggunakan musl dan bukannya glibc. Sesetengah native dependencies mungkin tidak serasi. Kalau anda menghadapi masalah compatibility, slim adalah pilihan yang baik sebagai jalan tengah.

Optimumkan Layer Caching

Docker build setiap instruction dalam Dockerfile sebagai satu layer. Kalau satu layer berubah, semua layer selepasnya perlu rebuild. Jadi, susun instructions anda supaya yang jarang berubah berada di atas.

# BAIK: Dependencies dulu, source code kemudian
COPY package*.json ./
RUN npm ci
COPY . .

# KURANG BAIK: Semua sekali
COPY . .
RUN npm ci

Dalam contoh pertama, kalau anda hanya ubah source code tanpa ubah dependencies, Docker boleh guna cached layer untuk npm ci. Ini boleh jimat beberapa minit dalam setiap build.

Gunakan .dockerignore

Sama macam .gitignore, tapi untuk Docker. Pastikan anda tak copy file yang tak perlu ke dalam image.

# .dockerignore
node_modules
.git
.gitignore
*.md
docker-compose*.yml
.env*
tests/
coverage/
.github/

Docker Registry

Di homelab, mungkin anda cuma buat docker build dan terus run. Di production, anda perlukan registry untuk simpan dan distribute images.

GitHub Container Registry (GHCR)

Kalau code anda di GitHub, GHCR adalah pilihan paling mudah. Ia integrate terus dengan GitHub Actions dan menggunakan GITHUB_TOKEN yang sedia ada.

# Login
echo $GITHUB_TOKEN | docker login ghcr.io -u USERNAME --password-stdin

# Tag dan push
docker tag my-app:latest ghcr.io/username/my-app:v1.0.0
docker push ghcr.io/username/my-app:v1.0.0

Harbor: Self-Hosted Registry

Untuk organisasi yang memerlukan lebih kawalan, Harbor adalah pilihan yang excellent. Ia open source, mempunyai vulnerability scanning built-in, dan boleh di-host sendiri.

# docker-compose untuk Harbor (simplified)
# Untuk production, gunakan installer rasmi dari Harbor
version: '3'
services:
  registry:
    image: goharbor/harbor-core:v2.10.0
    ports:
      - "443:8443"
    volumes:
      - harbor-data:/data

Nota Beginner: Untuk permulaan, GHCR sudah lebih dari cukup. Anda cuma perlukan self-hosted registry bila ada keperluan specific seperti compliance, air-gapped environment, atau anda perlu vulnerability scanning yang lebih mendalam.

Image Tagging Strategy

Bagaimana anda tag images sangat penting untuk production. Ini beberapa pendekatan yang biasa digunakan.

Semantic Versioning menggunakan format v1.2.3. Major version untuk breaking changes, minor untuk features baru, patch untuk bug fixes. Ini paling mudah difahami oleh manusia.

Git SHA menggunakan commit hash sebagai tag, contohnya abc123f. Setiap image boleh di-trace balik ke exact commit. Sangat berguna untuk debugging.

Gabungan kedua-duanya adalah pendekatan terbaik. Tag image anda dengan kedua-dua semantic version dan Git SHA.

# Tag dengan multiple tags
docker tag my-app:latest ghcr.io/username/my-app:v1.2.3
docker tag my-app:latest ghcr.io/username/my-app:abc123f
docker tag my-app:latest ghcr.io/username/my-app:latest

# Push semua tags
docker push ghcr.io/username/my-app:v1.2.3
docker push ghcr.io/username/my-app:abc123f
docker push ghcr.io/username/my-app:latest

Untuk production deployment, selalu guna specific version tag. Gunakan latest hanya untuk development atau testing.

Cleanup Old Images

Registry boleh membesar dengan cepat kalau anda tidak cleanup. Set up retention policy untuk automatically delete old images.

# Untuk GHCR, boleh guna GitHub Actions
# Delete images yang lebih dari 30 hari dan bukan tagged version
- uses: snok/container-retention-policy@v2
  with:
    image-names: my-app
    cut-off: 30 days ago UTC
    keep-at-least: 5
    account-type: personal
    token: ${{ secrets.GITHUB_TOKEN }}

Docker Compose: Production vs Development

Anda mungkin sudah biasa dengan satu docker-compose.yml. Untuk production, anda patut ada setup yang berbeza.

Development

# docker-compose.yml (development)
services:
  app:
    build: .
    ports:
      - "3000:3000"
    volumes:
      - .:/app
      - /app/node_modules
    environment:
      - NODE_ENV=development
      - DEBUG=app:*
    command: npm run dev

  db:
    image: postgres:16
    ports:
      - "5432:5432"
    environment:
      - POSTGRES_PASSWORD=devpassword
    volumes:
      - pgdata:/var/lib/postgresql/data

volumes:
  pgdata:

Production

# docker-compose.prod.yml
services:
  app:
    image: ghcr.io/username/my-app:${IMAGE_TAG:-latest}
    restart: always
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
    env_file:
      - .env.production
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 512M
        reservations:
          cpus: '0.25'
          memory: 128M
    healthcheck:
      test: ["CMD", "wget", "--spider", "-q", "http://localhost:3000/health"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 15s
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "5"

  db:
    image: postgres:16-alpine
    restart: always
    volumes:
      - pgdata:/var/lib/postgresql/data
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - db_password
    deploy:
      resources:
        limits:
          cpus: '2.0'
          memory: 1G

secrets:
  db_password:
    file: ./secrets/db_password.txt

volumes:
  pgdata:
    driver: local

Nampak perbezaan? Production version tidak mount source code (guna pre-built image), tidak expose database port ke host, mempunyai resource limits, health checks, proper logging configuration, dan menggunakan secrets untuk sensitive data. Ini semua perkara kecil yang membezakan homelab setup dengan production setup.

Docker Networking dalam Production

Di homelab, semua container biasanya dalam satu network yang sama. Di production, anda perlu lebih berhati-hati.

services:
  nginx:
    image: nginx:alpine
    networks:
      - frontend
    ports:
      - "80:80"
      - "443:443"

  app:
    image: my-app:latest
    networks:
      - frontend
      - backend

  db:
    image: postgres:16
    networks:
      - backend

  redis:
    image: redis:7-alpine
    networks:
      - backend

networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge
    internal: true

Perhatikan internal: true pada network backend. Ini bermaksud containers dalam network ini tidak boleh access internet secara langsung. Database dan Redis hanya boleh diakses oleh app container melalui internal network. Nginx boleh access app melalui frontend network, tapi tidak boleh access database secara langsung. Ini adalah prinsip least privilege yang penting untuk security.

Container Health Checks

Health checks adalah cara Docker (atau orchestrator) tahu sama ada container anda sihat. Tanpa health check, Docker hanya tahu container running atau tidak. Ia tak tahu kalau aplikasi anda stuck dalam infinite loop atau database connection putus.

# Dalam Dockerfile
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
  CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1

Dan endpoint health check anda perlu bermakna:

// Node.js health check endpoint
app.get('/health', async (req, res) => {
  try {
    // Check database connection
    await db.query('SELECT 1');

    // Check Redis connection
    await redis.ping();

    res.status(200).json({
      status: 'healthy',
      timestamp: new Date().toISOString(),
      uptime: process.uptime(),
      checks: {
        database: 'connected',
        redis: 'connected'
      }
    });
  } catch (error) {
    res.status(503).json({
      status: 'unhealthy',
      error: error.message
    });
  }
});

Nota Beginner: Parameter --start-period penting untuk aplikasi yang mengambil masa untuk start up. Selama tempoh ini, health check failures tidak dikira. Tanpa ini, Docker mungkin restart container sebelum ia sempat start.

Strategi Logging

Di homelab, anda mungkin cuma docker logs dan scroll. Di production, anda perlukan logging yang lebih terstruktur.

Structured Logging

Tulis log dalam format JSON supaya mudah di-parse oleh tools seperti ELK Stack atau Loki.

// Daripada ini:
console.log('User logged in: john@example.com');

// Gunakan ini:
logger.info({
  event: 'user_login',
  email: 'john@example.com',
  ip: req.ip,
  timestamp: new Date().toISOString()
});

Docker Logging Drivers

# docker-compose.prod.yml
services:
  app:
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "5"
        labels: "app"
        tag: "{{.Name}}"

Untuk setup yang lebih advanced, anda boleh forward logs ke centralized logging system:

services:
  app:
    logging:
      driver: "fluentd"
      options:
        fluentd-address: "localhost:24224"
        tag: "app.{{.Name}}"

Penting untuk set max-size dan max-file. Tanpa limit ini, log files boleh memenuhi disk anda. Saya pernah lihat server yang disk penuh kerana log files. Semua services down hanya kerana tak ada disk space. Jangan jadi macam tu.

Log Levels

Pastikan anda gunakan log levels yang betul. Ini memudahkan filtering di production.

// Gunakan library seperti winston atau pino
const logger = require('pino')({
  level: process.env.LOG_LEVEL || 'info'
});

logger.debug('Detailed debugging info');    // Hanya untuk development
logger.info('User created successfully');    // Normal operations
logger.warn('Disk usage above 80%');         // Perlu perhatian
logger.error('Database connection failed');  // Ada masalah
logger.fatal('Application crashed');         // Kritikal

Di production, set log level ke info atau warn. Jangan gunakan debug di production kerana ia akan menghasilkan terlalu banyak log dan boleh menjejaskan performance.

Centralized Logging dengan Loki

Untuk setup yang lebih lengkap, anda boleh gunakan Grafana Loki untuk centralized logging. Ia lebih lightweight berbanding ELK Stack dan integrate dengan baik bersama Grafana untuk visualization.

services:
  loki:
    image: grafana/loki:2.9.0
    ports:
      - "3100:3100"
    volumes:
      - loki-data:/loki

  promtail:
    image: grafana/promtail:2.9.0
    volumes:
      - /var/log:/var/log
      - /var/lib/docker/containers:/var/lib/docker/containers:ro
    command: -config.file=/etc/promtail/config.yml

  grafana:
    image: grafana/grafana:latest
    ports:
      - "3000:3000"
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin

Nota Beginner: Kalau anda baru bermula, json-file logging driver dengan max-size limit sudah memadai. Anda boleh upgrade ke centralized logging bila anda ada multiple servers atau bila anda rasa sukar untuk troubleshoot issues dengan docker logs sahaja.

Docker Security Best Practices

Security di production bukan optional. Ini checklist yang anda perlu ikut.

1. Jangan Run sebagai Root

# Buat user baru
RUN addgroup -g 1001 -S appgroup && \
    adduser -S appuser -u 1001 -G appgroup

# Tukar kepada user tersebut
USER appuser

2. Gunakan Read-Only Filesystem

services:
  app:
    read_only: true
    tmpfs:
      - /tmp
      - /app/temp

Ini memastikan tiada siapa (termasuk attacker) boleh write ke filesystem container. Kalau aplikasi anda perlu write ke temporary files, gunakan tmpfs.

3. Drop Unnecessary Capabilities

services:
  app:
    cap_drop:
      - ALL
    cap_add:
      - NET_BIND_SERVICE
    security_opt:
      - no-new-privileges:true

4. Scan Images untuk Vulnerabilities

# Gunakan Docker Scout
docker scout cves my-app:latest

# Atau Trivy (open source)
trivy image my-app:latest

Integrate scanning ini dalam CI/CD pipeline supaya setiap image di-scan sebelum deploy:

# Dalam GitHub Actions
- name: Scan image
  uses: aquasecurity/trivy-action@master
  with:
    image-ref: ghcr.io/${{ github.repository }}:${{ github.sha }}
    exit-code: '1'
    severity: 'CRITICAL,HIGH'

5. Gunakan Specific Tags, Bukan Latest

# KURANG BAIK
FROM node:latest

# BAIK
FROM node:20.11-alpine

latest boleh berubah pada bila-bila masa. Satu hari semuanya berjalan lancar, hari berikutnya image berubah dan aplikasi anda rosak. Pin your versions.

6. Jangan Simpan Secrets dalam Image

Ini kesilapan yang kerap berlaku. Jangan copy .env file atau secrets ke dalam Docker image. Walaupun anda delete file tersebut dalam layer seterusnya, ia masih boleh diakses melalui layer sebelumnya.

# SALAH - secret masih ada dalam image layers
COPY .env .
RUN source .env && npm run build
RUN rm .env

# BETUL - guna build arguments untuk build-time secrets
ARG API_ENDPOINT
ENV API_ENDPOINT=$API_ENDPOINT
RUN npm run build

# LEBIH BETUL - guna Docker BuildKit secrets
RUN --mount=type=secret,id=env,target=/app/.env npm run build

Untuk runtime secrets, gunakan environment variables atau Docker secrets, bukan files yang di-copy ke dalam image.

7. Keep Images Updated

Set up automated process untuk rebuild images bila base image ada security update. Tools seperti Dependabot atau Renovate boleh bantu automate ini. Anda juga boleh schedule weekly rebuilds dalam CI/CD pipeline untuk pastikan base images sentiasa up to date.

Resource Limits

Ini adalah satu perkara yang sering diabaikan tetapi sangat kritikal. Tanpa resource limits, satu container yang leak memory boleh menjatuhkan keseluruhan server.

services:
  app:
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 512M
        reservations:
          cpus: '0.25'
          memory: 128M

limits bermaksud maximum resources yang container boleh guna. Kalau container cuba guna lebih dari 512MB memory, Docker akan kill container tersebut (OOM kill).

reservations bermaksud minimum resources yang dijamin untuk container. Docker akan pastikan sekurang-kurangnya resources ini tersedia.

Untuk menentukan resource limits yang sesuai, monitor aplikasi anda terlebih dahulu. Jalankan ia tanpa limits di staging environment, perhatikan berapa banyak CPU dan memory ia guna dalam keadaan normal dan di bawah load. Kemudian set limits dengan buffer yang munasabah, mungkin 1.5 hingga 2 kali ganda penggunaan normal.

Nota Beginner: Kalau anda nampak container anda kerap di-OOM kill, jangan terus naikkan memory limit. Mungkin ada memory leak dalam aplikasi anda. Check dan fix root cause dulu.

Container Restart Policies

Di homelab, kalau container crash, anda mungkin manually restart. Di production, anda perlukan restart policy yang automatik.

services:
  app:
    restart: always          # Sentiasa restart, kecuali manually stopped
  worker:
    restart: on-failure      # Restart hanya kalau exit code bukan 0
  migration:
    restart: "no"            # Jangan restart (untuk one-off tasks)

Bila guna yang mana?

  • always sesuai untuk long-running services seperti web server dan API. Ia akan restart walaupun selepas server reboot.
  • on-failure sesuai untuk background workers yang mungkin crash sesekali. Ia akan restart kalau process exit dengan error, tapi tidak kalau ia exit secara normal.
  • no sesuai untuk tasks yang hanya perlu run sekali, seperti database migration atau data seeding.

Ada juga unless-stopped yang sama macam always, tapi tidak restart selepas anda manually stop container. Ini berguna kalau anda perlu stop container untuk maintenance tanpa risau ia restart sendiri.

Docker Image Layers dan Build Cache

Memahami bagaimana Docker layers berfungsi boleh menjimatkan banyak masa build. Setiap instruction dalam Dockerfile menghasilkan satu layer. Docker cache setiap layer. Kalau instruction dan semua layers sebelumnya tidak berubah, Docker guna cached version.

Ini bermaksud susun atur instructions anda sangat penting:

# Optimized layer ordering
FROM node:20-alpine

WORKDIR /app

# Layer 1: Jarang berubah
COPY package.json package-lock.json ./

# Layer 2: Hanya rebuild bila dependencies berubah
RUN npm ci --production

# Layer 3: Berubah setiap kali ada code change
COPY . .

# Layer 4: Build step
RUN npm run build

Kalau anda cuma ubah source code tanpa ubah dependencies, Docker hanya perlu rebuild Layer 3 dan 4. Layer 1 dan 2 guna cache. Ini boleh jimat beberapa minit dalam setiap build, terutama kalau projek anda ada banyak dependencies.

Satu lagi tip: gunakan .dockerignore yang comprehensive. Setiap file yang di-COPY ke build context boleh invalidate cache. Kalau anda accidentally copy node_modules atau .git folder, cache akan invalidate setiap kali walaupun dependencies tidak berubah.

Backup dan Disaster Recovery

Ini satu topik yang ramai orang abaikan sehingga terlambat. Di production, anda perlu plan untuk worst case scenario.

Backup Database Volumes

# Backup PostgreSQL data
docker compose exec db pg_dump -U postgres mydb > backup_$(date +%Y%m%d).sql

# Atau backup volume secara langsung
docker run --rm \
  -v pgdata:/data \
  -v $(pwd)/backups:/backup \
  alpine tar czf /backup/pgdata_$(date +%Y%m%d).tar.gz -C /data .

Automated Backup dengan Cron

services:
  backup:
    image: postgres:16-alpine
    entrypoint: /bin/sh
    command: >
      -c "while true; do
        PGPASSWORD=$$POSTGRES_PASSWORD pg_dump -h db -U postgres mydb
          > /backups/backup_$$(date +%Y%m%d_%H%M%S).sql;
        find /backups -name '*.sql' -mtime +7 -delete;
        sleep 86400;
      done"
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    volumes:
      - ./backups:/backups
    secrets:
      - db_password
    depends_on:
      - db

Script ini akan backup database setiap hari dan delete backups yang lebih dari 7 hari. Simple tapi berkesan. Untuk production yang lebih serius, pertimbangkan untuk upload backups ke cloud storage seperti S3 atau MinIO supaya backups anda tidak hilang kalau server mati.

Nota Beginner: Test restore process anda secara berkala. Backup yang tidak boleh di-restore bukan backup. Set up schedule untuk test restore sekurang-kurangnya sebulan sekali.

Production Checklist

Sebelum deploy sebarang container ke production, pastikan anda sudah check perkara berikut:

[ ] Multi-stage build digunakan
[ ] Base image menggunakan specific version tag
[ ] Container tidak run sebagai root
[ ] Health check dikonfigurasi
[ ] Resource limits ditetapkan
[ ] Logging dikonfigurasi dengan size limits
[ ] .dockerignore wujud dan lengkap
[ ] Secrets tidak di-hardcode dalam image
[ ] Image sudah di-scan untuk vulnerabilities
[ ] Network segmentation ditetapkan
[ ] Restart policy dikonfigurasi
[ ] Volumes untuk persistent data dikonfigurasi

Ringkasan

Dalam bab ini, kita telah melangkah jauh dari Docker basics ke Docker production. Multi-stage builds membantu kita buat image yang kecil dan efficient. Proper networking dan security practices memastikan containers kita selamat. Health checks dan resource limits memastikan containers kita reliable.

Perkara yang paling penting untuk diingat adalah ini: production Docker bukan sekadar tentang membuat sesuatu berfungsi. Ia tentang membuat sesuatu berfungsi dengan selamat, efficient, dan boleh dipercayai walaupun pada jam 3 pagi.

Kita telah melihat perbezaan antara Docker Compose untuk development dan production. Kita telah belajar tentang container registries, logging strategies, dan security hardening. Semua ini adalah building blocks yang anda perlukan sebelum kita masuk ke topik seterusnya: Kubernetes.

Bab 5: Kubernetes Asas

Ada satu titik dalam perjalanan DevOps anda di mana Docker Compose mula terasa tidak cukup. Mungkin anda perlu deploy ke multiple servers. Mungkin anda perlu auto-scaling. Mungkin anda perlu zero-downtime deployment tanpa perlu tulis skrip yang rumit. Di situlah Kubernetes masuk.

Kubernetes (atau K8s, kerana ada 8 huruf antara K dan s) pada mulanya mungkin nampak menakutkan. Banyak terminologi baru, banyak YAML files, banyak moving parts. Tapi kalau anda sudah faham Docker, anda sebenarnya sudah mempunyai 60% daripada pengetahuan yang diperlukan. Bab ini akan bawa anda melalui 40% yang tinggal.

Kita tak akan cover semuanya. Kubernetes adalah ecosystem yang sangat luas. Tapi selepas habis bab ini, anda akan mempunyai pemahaman yang kukuh tentang core concepts dan boleh deploy aplikasi pertama anda ke K8s cluster.

Apa yang anda akan belajar:

  • Kenapa Kubernetes wujud dan masalah apa yang ia selesaikan
  • Arsitektur K8s: control plane, worker nodes, pods, services
  • Setup K3s di homelab anda (step by step)
  • Kubectl commands yang anda perlu tahu
  • Deploy aplikasi pertama ke K8s
  • Services dan Ingress untuk expose aplikasi
  • ConfigMaps dan Secrets
  • Persistent storage
  • Scaling aplikasi
  • Asas Helm
  • Bila guna K8s vs Docker Compose

Kenapa Kubernetes?

Sebelum kita terjun ke technical details, mari faham masalah yang Kubernetes selesaikan. Bayangkan anda ada 5 aplikasi microservices, setiap satu memerlukan 2 replicas, dan anda ada 3 servers.

Dengan Docker Compose, anda perlu manually decide: service A dan B di server 1, service C di server 2, dan seterusnya. Kalau server 2 mati, anda perlu manually pindahkan services ke server lain. Kalau traffic meningkat, anda perlu manually tambah replicas.

Kubernetes automate semua ini. Anda cuma perlu cakap: “Saya nak 2 replicas untuk service A.” Kubernetes akan decide di mana nak letak, dan kalau satu replica mati, ia akan automatically create yang baru.

Ini dipanggil declarative configuration. Anda declare apa yang anda nak, bukan bagaimana nak achieve ia.

Selain itu, Kubernetes memberikan anda beberapa superpower yang Docker Compose tidak ada:

  • Self-healing: Kalau container crash, K8s automatically restart. Kalau node mati, K8s pindahkan workload ke node lain.
  • Auto-scaling: K8s boleh tambah atau kurangkan replicas berdasarkan CPU/memory usage secara automatik.
  • Rolling updates: Deploy version baru tanpa downtime. K8s akan slowly replace pods lama dengan pods baru.
  • Service discovery: Setiap service dapat DNS name secara automatik. Tak perlu hardcode IP addresses.
  • Load balancing: Traffic diagihkan secara automatik antara semua replicas.

Nota Beginner: K8s bukan penyelesaian untuk semua masalah. Ia menambah complexity yang significant. Pastikan anda benar-benar perlukan features di atas sebelum adopt K8s. Banyak aplikasi berjaya dengan Docker Compose sahaja.

Arsitektur Kubernetes

Untuk faham K8s, anda perlu faham beberapa komponen utama.

Control Plane

Control plane adalah “otak” K8s. Ia terdiri daripada beberapa komponen.

API Server adalah pintu masuk utama. Semua arahan kepada K8s melalui API server, termasuk arahan dari kubectl.

etcd adalah database yang simpan semua state cluster. Ia tahu berapa replicas setiap service ada, di mana ia running, dan apa configuration mereka.

Scheduler bertanggungjawab untuk decide di mana nak run pods baru. Ia mempertimbangkan resource availability, constraints, dan affinity rules.

Controller Manager menjalankan pelbagai controllers yang memastikan actual state cluster sepadan dengan desired state. Kalau anda kata nak 3 replicas tapi hanya 2 yang running, controller manager akan buat satu lagi.

Worker Nodes

Worker nodes adalah machines yang actually run containers anda. Setiap worker node mempunyai beberapa komponen penting.

kubelet adalah agent yang run di setiap node. Ia menerima arahan dari control plane dan memastikan containers running seperti yang dikehendaki.

kube-proxy menguruskan network rules supaya traffic boleh sampai ke pods yang betul.

Container Runtime (biasanya containerd) adalah software yang actually run containers.

Pods

Pod adalah unit terkecil dalam Kubernetes. Satu pod boleh mengandungi satu atau lebih containers. Dalam kebanyakan kes, satu pod mengandungi satu container sahaja.

Nota Beginner: Fikirkan pod macam wrapper di sekeliling container anda. Kenapa perlu wrapper? Kerana K8s perlu tempat untuk letak metadata, health check info, restart policy, dan networking config. Pod menyediakan semua ini.

Services

Service dalam K8s bukan service dalam Docker Compose. Di sini, service adalah abstraksi yang menyediakan stable network endpoint untuk satu set pods. Pods boleh datang dan pergi (dihapus, dicipta semula), tetapi service address kekal sama.

Setting Up K3s di Homelab

K3s adalah lightweight Kubernetes distribution dari Rancher. Ia sesuai untuk homelab kerana ia menggunakan kurang resources berbanding full K8s, tetapi masih production-ready.

Keperluan

  • Satu atau lebih machines (boleh VMs) dengan minimum 2GB RAM dan 2 CPU cores
  • Ubuntu 22.04 atau mana-mana Linux distribution yang supported
  • Network connectivity antara semua nodes

Install K3s Server (Control Plane)

# Di server utama (control plane)
curl -sfL https://get.k3s.io | sh -

# Check status
sudo systemctl status k3s

# Dapatkan kubeconfig
sudo cat /etc/rancher/k3s/k3s.yaml

Itu sahaja. Serius. K3s boleh di-install dalam satu command. Selepas install, anda sudah ada functional Kubernetes cluster dengan satu node.

Tambah Worker Nodes

# Di server utama, dapatkan token
sudo cat /var/lib/rancher/k3s/server/node-token

# Di worker node, join cluster
curl -sfL https://get.k3s.io | K3S_URL=https://CONTROL_PLANE_IP:6443 \
  K3S_TOKEN=TOKEN_DARI_SERVER sh -

Setup kubectl di Local Machine

# Copy kubeconfig dari server
scp user@server:/etc/rancher/k3s/k3s.yaml ~/.kube/config

# Edit server address (tukar 127.0.0.1 ke server IP)
# Dalam ~/.kube/config, cari line "server:" dan tukar ke IP server

# Test connection
kubectl get nodes

Output yang anda sepatutnya nampak:

NAME        STATUS   ROLES                  AGE   VERSION
server-01   Ready    control-plane,master   10m   v1.28.5+k3s1
worker-01   Ready    <none>                 5m    v1.28.5+k3s1
worker-02   Ready    <none>                 3m    v1.28.5+k3s1

Nota Beginner: Kalau anda cuma ada satu machine, tak perlu risau. K3s boleh run semua workloads di control plane node juga. Untuk belajar, satu node sudah cukup.

Kubectl Commands yang Anda Perlu Tahu

kubectl adalah CLI tool untuk interact dengan Kubernetes cluster. Ini commands yang paling kerap digunakan.

# Lihat semua resources
kubectl get pods                    # Senarai pods
kubectl get services                # Senarai services
kubectl get deployments             # Senarai deployments
kubectl get all                     # Senarai semua resources
kubectl get all -n kube-system      # Resources dalam namespace tertentu

# Maklumat terperinci
kubectl describe pod POD_NAME       # Detail sesuatu pod
kubectl describe service SVC_NAME   # Detail sesuatu service

# Logs
kubectl logs POD_NAME               # Lihat logs
kubectl logs -f POD_NAME            # Follow logs (live)
kubectl logs POD_NAME -c CONTAINER  # Logs dari specific container

# Debug
kubectl exec -it POD_NAME -- /bin/sh    # Shell ke dalam pod
kubectl port-forward POD_NAME 8080:80   # Forward port ke local

# Apply configuration
kubectl apply -f manifest.yaml      # Create atau update resource
kubectl delete -f manifest.yaml     # Delete resource

Tips berguna: set alias supaya hidup lebih mudah.

# Tambah dalam ~/.bashrc atau ~/.zshrc
alias k='kubectl'
alias kgp='kubectl get pods'
alias kgs='kubectl get services'
alias kgd='kubectl get deployments'
alias kd='kubectl describe'
alias kl='kubectl logs'
alias ka='kubectl apply -f'

Deploy Aplikasi Pertama

Mari deploy satu aplikasi web yang simple. Dalam K8s, anda define apa yang anda nak dalam YAML files yang dipanggil manifests.

Deployment

# app-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-web-app
  labels:
    app: my-web-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-web-app
  template:
    metadata:
      labels:
        app: my-web-app
    spec:
      containers:
        - name: web
          image: ghcr.io/username/my-app:v1.0.0
          ports:
            - containerPort: 3000
          resources:
            requests:
              cpu: 100m
              memory: 128Mi
            limits:
              cpu: 500m
              memory: 256Mi
          readinessProbe:
            httpGet:
              path: /health
              port: 3000
            initialDelaySeconds: 5
            periodSeconds: 10
          livenessProbe:
            httpGet:
              path: /health
              port: 3000
            initialDelaySeconds: 15
            periodSeconds: 20

YAML ini memberitahu K8s: “Saya nak 3 replicas aplikasi saya, setiap satu guna image ini, expose port 3000, dan ini resource limits yang saya nak.”

# Apply deployment
kubectl apply -f app-deployment.yaml

# Check status
kubectl get deployments
kubectl get pods

# Tunggu sehingga semua pods Ready
kubectl rollout status deployment/my-web-app

Perhatikan dua jenis probes. readinessProbe menentukan sama ada pod sedia menerima traffic. Kalau fail, pod akan dikeluarkan dari service (tak terima traffic baru, tapi tidak di-kill). livenessProbe menentukan sama ada pod masih hidup. Kalau fail beberapa kali berturut-turut, K8s akan restart pod tersebut.

Services dan Ingress

Pods sudah running, tapi bagaimana nak access mereka? Di sinilah Services dan Ingress masuk.

ClusterIP Service

# app-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: my-web-app
spec:
  selector:
    app: my-web-app
  ports:
    - port: 80
      targetPort: 3000
  type: ClusterIP

ClusterIP service hanya boleh diakses dari dalam cluster. Ia menyediakan stable IP address dan DNS name (my-web-app.default.svc.cluster.local). Sesuai untuk internal services seperti API yang diakses oleh services lain.

NodePort Service

apiVersion: v1
kind: Service
metadata:
  name: my-web-app-nodeport
spec:
  selector:
    app: my-web-app
  ports:
    - port: 80
      targetPort: 3000
      nodePort: 30080
  type: NodePort

NodePort expose service pada setiap node di port tertentu (30080 dalam contoh ini). Anda boleh access aplikasi melalui http://NODE_IP:30080. Sesuai untuk testing, tetapi bukan untuk production.

Ingress

Untuk production, gunakan Ingress. Ia menyediakan HTTP routing, SSL termination, dan virtual hosting.

# app-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-web-app
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  tls:
    - hosts:
        - myapp.example.com
      secretName: myapp-tls
  rules:
    - host: myapp.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: my-web-app
                port:
                  number: 80

K3s datang dengan Traefik sebagai Ingress controller secara default. Anda cuma perlu create Ingress resource dan point DNS ke cluster anda.

# Apply semua resources
kubectl apply -f app-deployment.yaml
kubectl apply -f app-service.yaml
kubectl apply -f app-ingress.yaml

# Check Ingress status
kubectl get ingress

Multiple Services dengan Ingress

Salah satu kelebihan Ingress ialah anda boleh route traffic ke different services berdasarkan path atau hostname.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: multi-app-ingress
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  tls:
    - hosts:
        - api.example.com
        - dashboard.example.com
      secretName: multi-app-tls
  rules:
    - host: api.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: api-service
                port:
                  number: 80
    - host: dashboard.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: dashboard-service
                port:
                  number: 80

Dengan setup ini, satu Ingress controller boleh handle traffic untuk multiple applications. Setiap hostname diarahkan ke service yang berbeza. Ini jauh lebih kemas berbanding run multiple reverse proxies.

ConfigMaps dan Secrets

Jangan hardcode configuration dalam Docker image. Gunakan ConfigMaps untuk non-sensitive config dan Secrets untuk sensitive data.

ConfigMap

# app-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: my-web-app-config
data:
  APP_ENV: "production"
  LOG_LEVEL: "info"
  MAX_CONNECTIONS: "100"
  CACHE_TTL: "3600"

Secret

# Buat secret via kubectl
kubectl create secret generic my-web-app-secrets \
  --from-literal=DATABASE_URL='postgresql://user:pass@db:5432/mydb' \
  --from-literal=API_KEY='sk-supersecretkey123'

Atau dalam YAML (values mesti base64 encoded):

# app-secrets.yaml
apiVersion: v1
kind: Secret
metadata:
  name: my-web-app-secrets
type: Opaque
data:
  DATABASE_URL: cG9zdGdyZXNxbDovL3VzZXI6cGFzc0BkYjo1NDMyL215ZGI=
  API_KEY: c2stc3VwZXJzZWNyZXRrZXkxMjM=

Nota Beginner: Base64 bukan encryption. Sesiapa yang ada access ke cluster boleh decode secrets. Untuk security yang lebih baik, gunakan tools seperti Sealed Secrets atau external secret management seperti HashiCorp Vault.

Gunakan dalam Deployment

spec:
  containers:
    - name: web
      image: ghcr.io/username/my-app:v1.0.0
      envFrom:
        - configMapRef:
            name: my-web-app-config
        - secretRef:
            name: my-web-app-secrets
      # Atau specific keys sahaja:
      env:
        - name: DATABASE_HOST
          valueFrom:
            configMapKeyRef:
              name: my-web-app-config
              key: DATABASE_HOST
        - name: DATABASE_PASSWORD
          valueFrom:
            secretKeyRef:
              name: my-web-app-secrets
              key: DATABASE_PASSWORD

Persistent Storage

Pods adalah ephemeral. Kalau pod mati, data di dalamnya hilang. Untuk data yang perlu kekal (database, uploaded files), gunakan Persistent Volumes.

# pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: postgres-data
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi
  storageClassName: local-path
# postgres-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: postgres
spec:
  replicas: 1
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
        - name: postgres
          image: postgres:16-alpine
          ports:
            - containerPort: 5432
          env:
            - name: POSTGRES_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: postgres-secrets
                  key: password
            - name: PGDATA
              value: /var/lib/postgresql/data/pgdata
          volumeMounts:
            - name: postgres-storage
              mountPath: /var/lib/postgresql/data
          resources:
            requests:
              cpu: 250m
              memory: 256Mi
            limits:
              cpu: '1'
              memory: 1Gi
      volumes:
        - name: postgres-storage
          persistentVolumeClaim:
            claimName: postgres-data

K3s menyediakan local-path storage class secara default. Ia menyimpan data di node tempat pod running. Untuk production yang lebih robust, anda mungkin perlu NFS, Longhorn, atau cloud storage (EBS, GCE PD).

Longhorn: Distributed Storage untuk K3s

Kalau anda run K3s dengan multiple nodes, Longhorn adalah pilihan yang excellent untuk storage. Ia replicate data across nodes, jadi kalau satu node mati, data anda masih selamat.

# Install Longhorn via Helm
helm repo add longhorn https://charts.longhorn.io
helm repo update

helm install longhorn longhorn/longhorn \
  --namespace longhorn-system \
  --create-namespace \
  --set defaultSettings.defaultReplicaCount=2

Selepas install, anda boleh gunakan Longhorn sebagai storage class:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: postgres-data
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: longhorn
  resources:
    requests:
      storage: 10Gi

Nota Beginner: Untuk homelab dengan satu node, local-path sudah memadai. Anda hanya perlukan Longhorn atau distributed storage bila anda ada multiple nodes dan perlu data redundancy.

Scaling

Salah satu kelebihan utama K8s adalah scaling yang mudah.

Manual Scaling

# Scale up
kubectl scale deployment my-web-app --replicas=5

# Scale down
kubectl scale deployment my-web-app --replicas=2

Horizontal Pod Autoscaler (HPA)

# hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: my-web-app-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: my-web-app
  minReplicas: 2
  maxReplicas: 10
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70
    - type: Resource
      resource:
        name: memory
        target:
          type: Utilization
          averageUtilization: 80

Dengan HPA, K8s akan automatically tambah pods kalau CPU usage melebihi 70% atau memory usage melebihi 80%. Dan ia akan kurangkan pods kalau usage rendah. Anda set minimum 2 replicas (untuk high availability) dan maximum 10 replicas (untuk control cost).

# Apply HPA
kubectl apply -f hpa.yaml

# Monitor HPA
kubectl get hpa
kubectl describe hpa my-web-app-hpa

Namespaces

Sebelum kita pergi ke Helm, satu concept yang penting ialah namespaces. Namespace adalah cara K8s untuk memisahkan resources dalam satu cluster.

# Lihat semua namespaces
kubectl get namespaces

# Buat namespace baru
kubectl create namespace staging
kubectl create namespace production

# Deploy ke namespace tertentu
kubectl apply -f app-deployment.yaml -n staging
kubectl apply -f app-deployment.yaml -n production

# Lihat pods dalam namespace tertentu
kubectl get pods -n staging
kubectl get pods -n production

Dengan namespaces, anda boleh run staging dan production dalam satu cluster (walaupun untuk production sebenar, cluster berasingan adalah lebih selamat). Anda juga boleh set resource quotas per namespace supaya satu environment tidak menggunakan semua resources.

# resource-quota.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
  name: staging-quota
  namespace: staging
spec:
  hard:
    pods: "20"
    requests.cpu: "4"
    requests.memory: 8Gi
    limits.cpu: "8"
    limits.memory: 16Gi

Nota Beginner: Untuk homelab, anda mungkin cuma guna default namespace dan itu sudah okay. Tapi bila anda mula deploy multiple aplikasi atau environments, namespaces akan menjadi sangat berguna untuk organisasi.

Troubleshooting K8s

Sebelum kita pergi ke Helm, ini beberapa teknik troubleshooting yang sangat berguna. Anda pasti akan perlukan ini.

# Pod stuck dalam "Pending" state? Check events
kubectl describe pod POD_NAME
# Biasanya sebab: insufficient resources, PVC not bound, atau image pull error

# Pod dalam "CrashLoopBackOff"? Check logs
kubectl logs POD_NAME --previous
# "--previous" tunjuk logs dari container yang crash sebelumnya

# Pod running tapi app tak respond? Exec ke dalam pod
kubectl exec -it POD_NAME -- /bin/sh
# Check process, network, files dari dalam pod

# Service tak boleh diakses? Check endpoints
kubectl get endpoints SERVICE_NAME
# Kalau endpoints kosong, selector mungkin tidak match pod labels

# Check semua events dalam cluster
kubectl get events --sort-by='.lastTimestamp'

Debugging Kubernetes boleh jadi frustrating pada awalnya. Tapi selepas anda biasa, pattern yang sama akan muncul berulang kali. Kebanyakan masalah berpunca daripada image pull errors, resource limits yang terlalu ketat, atau selector labels yang tidak match.

Asas Helm

Bila aplikasi anda mempunyai banyak Kubernetes manifests (deployment, service, ingress, configmap, secret, pvc, hpa), managing semua YAML files ini boleh jadi rumit. Helm menyelesaikan masalah ini.

Helm adalah package manager untuk Kubernetes. Fikirkan ia macam apt untuk Ubuntu atau brew untuk macOS, tapi untuk K8s.

Install Helm

# macOS
brew install helm

# Linux
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash

Gunakan Helm Charts yang Sedia Ada

# Tambah repository
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update

# Search charts
helm search repo postgresql

# Install PostgreSQL
helm install my-postgres bitnami/postgresql \
  --set auth.postgresPassword=mypassword \
  --set primary.persistence.size=10Gi

# Senarai installations
helm list

# Uninstall
helm uninstall my-postgres

Buat Helm Chart Sendiri

# Scaffold chart baru
helm create my-app

# Struktur yang dihasilkan:
# my-app/
#   Chart.yaml          - Metadata
#   values.yaml         - Default values
#   templates/          - K8s manifest templates
#     deployment.yaml
#     service.yaml
#     ingress.yaml
#     hpa.yaml

Contoh values.yaml:

# values.yaml
replicaCount: 3

image:
  repository: ghcr.io/username/my-app
  tag: "v1.0.0"
  pullPolicy: IfNotPresent

service:
  type: ClusterIP
  port: 80
  targetPort: 3000

ingress:
  enabled: true
  host: myapp.example.com
  tls: true

resources:
  limits:
    cpu: 500m
    memory: 256Mi
  requests:
    cpu: 100m
    memory: 128Mi

autoscaling:
  enabled: true
  minReplicas: 2
  maxReplicas: 10
  targetCPUUtilization: 70

Kemudian dalam templates, anda gunakan values ini:

# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Release.Name }}
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      app: {{ .Release.Name }}
  template:
    spec:
      containers:
        - name: app
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          resources:
            {{- toYaml .Values.resources | nindent 12 }}

Deploy menggunakan Helm:

# Install
helm install my-release ./my-app

# Upgrade dengan values baru
helm upgrade my-release ./my-app --set image.tag=v1.1.0

# Upgrade dengan custom values file
helm upgrade my-release ./my-app -f production-values.yaml

# Rollback kalau ada masalah
helm rollback my-release 1

Kelebihan Helm yang paling jelas ialah anda boleh deploy aplikasi yang sama ke multiple environments dengan hanya menukar values file. Staging guna staging-values.yaml, production guna production-values.yaml. Sama templates, values yang berbeza.

Bila Guna K8s vs Docker Compose?

Ini soalan yang ramai orang tanya. Dan jawapannya bukan “K8s selalu lebih baik.” Ada masa untuk Docker Compose dan ada masa untuk Kubernetes.

Gunakan Docker Compose bila:

  • Anda deploy ke satu server sahaja
  • Aplikasi anda ada 2 hingga 5 services
  • Team anda kecil (1 hingga 3 orang)
  • Anda tak perlu auto-scaling
  • Budget terhad (K8s memerlukan lebih resources untuk cluster overhead)
  • Aplikasi anda bukan mission-critical (sedikit downtime boleh diterima)

Gunakan Kubernetes bila:

  • Anda deploy ke multiple servers
  • Anda perlukan high availability (zero downtime)
  • Anda perlukan auto-scaling
  • Anda ada banyak microservices (10+)
  • Team anda cukup besar untuk manage K8s
  • Anda sudah ada CI/CD pipeline yang mature

Nota Beginner: Jangan gunakan Kubernetes semata-mata kerana ia popular. K8s menambah complexity yang significant. Kalau Docker Compose sudah memenuhi keperluan anda, teruskan gunakannya. Ramai startup berjaya yang run on bare Docker Compose. Gunakan K8s hanya bila anda benar-benar memerlukannya.

Satu pendekatan yang bagus ialah mulakan dengan Docker Compose. Bila anda mula rasa limitasinya (perlu multi-server, perlu auto-scaling, perlu self-healing), barulah migrate ke Kubernetes. Pengalaman anda dengan Docker Compose akan memudahkan transition kerana concepts seperti services, volumes, dan networking wujud dalam kedua-duanya.

Migration Path: Docker Compose ke K8s

Kalau anda decide nak migrate, ini rough steps yang boleh anda ikut:

  1. Pastikan semua services anda sudah containerized dengan proper Dockerfiles dan images di registry.
  2. Setup K3s cluster di homelab dulu. Test dengan satu node.
  3. Convert Docker Compose services ke K8s manifests satu per satu. Mulakan dengan stateless services (web apps, APIs) kerana ia paling mudah.
  4. Migrate stateful services (databases) terakhir. Ini paling tricky kerana perlu handle persistent storage.
  5. Update CI/CD pipeline untuk deploy ke K8s cluster.
  6. Run kedua-dua environments secara selari untuk seketika. Route sebahagian traffic ke K8s untuk validate.
  7. Fully cutover ke K8s bila anda yakin semuanya stable.

Proses ini mungkin ambil beberapa minggu atau bulan bergantung pada complexity aplikasi anda. Jangan terburu-buru. Lebih baik migrate dengan yakin daripada cepat tapi penuh masalah.

Ada juga tools seperti kompose yang boleh convert Docker Compose files ke K8s manifests secara automatik. Tapi hasilnya biasanya perlu banyak tweaking. Saya recommend tulis K8s manifests sendiri supaya anda benar-benar faham apa yang berlaku.

# Kalau nak cuba kompose
kompose convert -f docker-compose.yml

# Ia akan generate K8s manifests, tapi expect banyak manual adjustment

Ringkasan

Dalam bab ini, kita telah bermula dari soalan “Kenapa Kubernetes?” dan sampai ke tahap boleh deploy, expose, scale, dan manage aplikasi di K8s cluster.

Kita telah belajar arsitektur K8s dengan control plane dan worker nodes. Kita telah setup K3s di homelab dan deploy aplikasi pertama lengkap dengan Deployment, Service, Ingress, ConfigMaps, Secrets, persistent storage, dan autoscaling.

Kita juga telah sentuh Helm sebagai cara untuk manage Kubernetes manifests dengan lebih terstruktur. Dan yang paling penting, kita telah bincangkan bila sebenarnya anda perlu K8s dan bila Docker Compose sudah memadai.

Kubernetes adalah perjalanan yang panjang. Apa yang kita cover di sini adalah asas yang kukuh. Dari sini, anda boleh explore topik yang lebih advanced seperti namespaces, network policies, RBAC, service mesh, dan monitoring. Tapi dengan apa yang anda belajar dalam bab ini, anda sudah boleh mula deploy real applications ke K8s cluster anda sendiri.

Bab 6: Infrastructure as Code

Bayangkan anda perlu setup 10 server baru untuk projek baru. Anda SSH satu per satu, install packages, configure firewall, setup users. Lepas dua jam, server ketiga ada masalah sebab anda terlupa satu langkah. Sound familiar?

Inilah masalah yang Infrastructure as Code (IaC) selesaikan. Daripada klik-klik manual atau taip command satu-satu, kita tulis code yang describe infrastructure kita. Code ini boleh di-version, di-review, dan di-reuse. Sama macam anda tulis code untuk application, kita tulis code untuk infrastructure.

Kalau anda pernah setup Proxmox VM secara manual dan rasa frustrated bila kena buat benda yang sama berulang kali, bab ini untuk anda.

Apa yang anda akan belajar:

  • Konsep Infrastructure as Code dan kenapa ia penting
  • Ansible untuk configuration management (inventory, playbooks, roles)
  • Terraform untuk provisioning infrastructure
  • Bila guna Ansible, bila guna Terraform
  • GitOps workflow untuk manage infrastructure code
  • Practical examples yang boleh terus digunakan

Apa Itu Infrastructure as Code?

Infrastructure as Code bermaksud kita define dan manage infrastructure menggunakan code files, bukan manual process. Daripada login ke server dan run commands, kita tulis semua dalam files yang boleh disimpan dalam Git.

Kenapa ini penting? Sebab manual setup ada banyak masalah.

Masalah manual setup:

  • Tidak consistent. Server A mungkin berbeza daripada Server B walaupun sepatutnya sama.
  • Tidak reproducible. Kalau server crash, boleh ke anda setup semula exactly sama?
  • Tidak scalable. Setup 3 server okay, macam mana 30? 300?
  • Tidak auditable. Siapa yang buat perubahan itu? Bila? Kenapa?

Kelebihan IaC:

  • Consistency. Setiap server di-setup exactly sama.
  • Version control. Semua perubahan di-track dalam Git.
  • Reusability. Tulis sekali, guna banyak kali.
  • Self-documenting. Code itu sendiri adalah dokumentasi.
  • Testable. Boleh test infrastructure changes sebelum apply.

Nota Beginner: Kalau anda pernah tulis script Bash untuk automate server setup, anda sebenarnya sudah buat IaC dalam bentuk paling basic. Tools macam Ansible dan Terraform bawa konsep ini ke level yang lebih structured dan powerful.

Declarative vs Imperative

Ada dua pendekatan dalam IaC yang anda perlu faham.

Imperative bermaksud anda tulis step-by-step instructions. “Install Nginx, then copy config file, then restart service.” Bash scripts adalah imperative. Anda beritahu sistem apa nak buat, langkah demi langkah.

Declarative bermaksud anda describe end state yang anda mahukan. “Saya mahu Nginx installed, config file ini di tempat ini, dan service running.” Tools akan figure out sendiri macam mana nak sampai ke state tersebut.

Terraform adalah fully declarative. Anda describe infrastructure yang anda mahu, dan Terraform determine langkah-langkah untuk capai state tersebut. Ansible berada di tengah-tengah. Playbooks ditulis secara imperative (step by step), tapi banyak modules bersifat declarative (mereka check current state sebelum buat changes).

Kelebihan declarative approach adalah idempotency. Anda boleh run code yang sama banyak kali dan hasilnya akan sentiasa sama. Kalau Nginx sudah installed, Ansible tidak akan install lagi. Kalau VM sudah exist, Terraform tidak akan create duplicate.

Mutable vs Immutable Infrastructure

Satu lagi konsep penting. Mutable infrastructure bermaksud anda update server yang sedia ada. SSH masuk, upgrade packages, tukar config. Server itu terus hidup dan berubah over time.

Immutable infrastructure bermaksud anda tidak pernah update server. Bila ada changes, anda create server baru dengan config terbaru dan destroy yang lama. Container-based deployments biasanya mengikut pendekatan ini.

Dalam practice, kebanyakan team guna combination kedua-dua pendekatan. Homelab biasanya mutable (anda SSH masuk dan tweak). Production yang mature biasanya move towards immutable (deploy new, destroy old).

Ansible: Configuration Management

Ansible adalah tool untuk automate configuration dan management server. Ia agentless, bermaksud anda tidak perlu install apa-apa pada target server. Ansible guna SSH untuk connect dan jalankan tasks.

Ini menjadikan Ansible sangat sesuai untuk homelab sebab anda tidak perlu setup agent pada setiap VM atau container. Berbanding tools lain macam Puppet atau Chef yang require agent pada setiap node, Ansible hanya perlukan SSH access dan Python pada target. Kebanyakan Linux distributions sudah ada kedua-duanya secara default.

Install Ansible

Pada Ubuntu atau Debian:

sudo apt update
sudo apt install ansible -y

Pada macOS:

brew install ansible

Verify installation:

ansible --version

Inventory: Senarai Server Anda

Inventory adalah file yang senaraikan semua server yang Ansible akan manage. Paling simple, ia adalah text file dengan IP addresses.

Buat file inventory.ini:

[webservers]
web1 ansible_host=192.168.1.101 ansible_user=syafi
web2 ansible_host=192.168.1.102 ansible_user=syafi

[databases]
db1 ansible_host=192.168.1.110 ansible_user=syafi

[monitoring]
monitor1 ansible_host=192.168.1.120 ansible_user=syafi

[homelab:children]
webservers
databases
monitoring

[all:vars]
ansible_python_interpreter=/usr/bin/python3

Anda boleh group server mengikut fungsi. [homelab:children] adalah group yang mengandungi group lain. [all:vars] set variables untuk semua hosts.

Test connection ke semua server:

ansible all -i inventory.ini -m ping

Kalau semuanya okay, anda akan nampak output SUCCESS untuk setiap host.

Nota Beginner: Pastikan SSH key anda sudah di-copy ke semua target server menggunakan ssh-copy-id. Ansible bergantung pada SSH untuk connect, jadi passwordless SSH sangat disyorkan.

Playbooks: Resipi Automation Anda

Playbook adalah YAML file yang describe tasks yang Ansible perlu jalankan. Ia macam resipi. Anda tulis langkah-langkah, dan Ansible akan ikut satu per satu.

Contoh playbook setup-webserver.yml untuk setup Nginx web server:

---
- name: Setup Nginx Web Server
  hosts: webservers
  become: yes

  vars:
    domain_name: "myapp.homelab.local"
    app_port: 3000

  tasks:
    - name: Update apt cache
      apt:
        update_cache: yes
        cache_valid_time: 3600

    - name: Install required packages
      apt:
        name:
          - nginx
          - certbot
          - python3-certbot-nginx
          - ufw
        state: present

    - name: Configure UFW firewall
      ufw:
        rule: allow
        port: "{{ item }}"
        proto: tcp
      loop:
        - "22"
        - "80"
        - "443"

    - name: Enable UFW
      ufw:
        state: enabled
        default: deny

    - name: Copy Nginx config
      template:
        src: templates/nginx.conf.j2
        dest: "/etc/nginx/sites-available/{{ domain_name }}"
      notify: Reload Nginx

    - name: Enable site
      file:
        src: "/etc/nginx/sites-available/{{ domain_name }}"
        dest: "/etc/nginx/sites-enabled/{{ domain_name }}"
        state: link
      notify: Reload Nginx

    - name: Remove default site
      file:
        path: /etc/nginx/sites-enabled/default
        state: absent
      notify: Reload Nginx

  handlers:
    - name: Reload Nginx
      service:
        name: nginx
        state: reloaded

Buat template file templates/nginx.conf.j2:

server {
    listen 80;
    server_name {{ domain_name }};

    location / {
        proxy_pass http://127.0.0.1:{{ app_port }};
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Jalankan playbook:

ansible-playbook -i inventory.ini setup-webserver.yml

Beberapa perkara penting dalam playbook di atas:

  • become: yes bermaksud run sebagai root (sudo).
  • vars define variables yang boleh digunakan dalam tasks.
  • template module guna Jinja2 templating. Variables dalam {{ }} akan diganti dengan nilai sebenar.
  • handlers hanya run bila di-trigger oleh notify. Ini elakkan restart service yang tidak perlu.
  • loop membolehkan anda repeat task untuk multiple items.

Roles: Organisasi Yang Lebih Baik

Bila playbook anda makin besar, ia jadi susah nak maintain. Roles membantu anda organise tasks, templates, files, dan variables dalam structure yang standard.

Buat role structure:

mkdir -p roles/common/{tasks,handlers,templates,files,vars,defaults}
mkdir -p roles/docker/{tasks,handlers,templates,vars,defaults}

File roles/common/tasks/main.yml:

---
- name: Update and upgrade packages
  apt:
    update_cache: yes
    upgrade: safe
    cache_valid_time: 3600

- name: Install common packages
  apt:
    name:
      - curl
      - wget
      - vim
      - htop
      - git
      - ufw
      - fail2ban
      - unattended-upgrades
    state: present

- name: Set timezone
  timezone:
    name: "{{ timezone }}"

- name: Configure fail2ban
  template:
    src: jail.local.j2
    dest: /etc/fail2ban/jail.local
  notify: Restart fail2ban

- name: Enable and start fail2ban
  service:
    name: fail2ban
    state: started
    enabled: yes

File roles/common/defaults/main.yml:

---
timezone: "Asia/Kuala_Lumpur"

File roles/docker/tasks/main.yml:

---
- name: Install Docker prerequisites
  apt:
    name:
      - ca-certificates
      - curl
      - gnupg
      - lsb-release
    state: present

- name: Add Docker GPG key
  apt_key:
    url: https://download.docker.com/linux/ubuntu/gpg
    state: present

- name: Add Docker repository
  apt_repository:
    repo: "deb https://download.docker.com/linux/ubuntu {{ ansible_distribution_release }} stable"
    state: present

- name: Install Docker
  apt:
    name:
      - docker-ce
      - docker-ce-cli
      - containerd.io
      - docker-compose-plugin
    state: present
    update_cache: yes

- name: Add user to docker group
  user:
    name: "{{ ansible_user }}"
    groups: docker
    append: yes

- name: Start and enable Docker
  service:
    name: docker
    state: started
    enabled: yes

Guna roles dalam playbook site.yml:

---
- name: Setup all servers
  hosts: all
  become: yes
  roles:
    - common

- name: Setup Docker hosts
  hosts: webservers
  become: yes
  roles:
    - docker

Jalankan:

ansible-playbook -i inventory.ini site.yml

Nota Beginner: Saya sangat recommend guna roles dari awal walaupun projek kecil. Bila projek membesar nanti, anda akan berterima kasih pada diri sendiri.

Ansible Galaxy: Roles Dari Community

Anda tidak perlu tulis semua roles dari scratch. Ansible Galaxy menyediakan ribuan roles yang sedia untuk digunakan.

# Install role dari Galaxy
ansible-galaxy install geerlingguy.docker
ansible-galaxy install geerlingguy.nodejs

# Atau guna requirements.yml

Buat file requirements.yml:

---
roles:
  - name: geerlingguy.docker
    version: "7.1.0"
  - name: geerlingguy.nodejs
    version: "6.2.0"

Install semua roles sekaligus:

ansible-galaxy install -r requirements.yml

Ansible Vault: Manage Secrets

Dalam real-world usage, anda pasti ada passwords, API keys, dan secrets lain yang perlu digunakan dalam playbooks. Jangan hardcode secrets dalam playbook files. Guna Ansible Vault untuk encrypt sensitive data.

# Create encrypted file
ansible-vault create secrets.yml

# Edit encrypted file
ansible-vault edit secrets.yml

# Encrypt existing file
ansible-vault encrypt vars/production.yml

# Run playbook dengan vault
ansible-playbook -i inventory.ini site.yml --ask-vault-pass

Contoh encrypted variables file secrets.yml:

db_password: "super-secret-password-123"
api_key: "sk-1234567890abcdef"
registry_token: "ghp_xxxxxxxxxxxx"

Guna dalam playbook:

---
- name: Deploy with secrets
  hosts: webservers
  become: yes
  vars_files:
    - secrets.yml

  tasks:
    - name: Set database password
      template:
        src: templates/env.j2
        dest: /opt/myapp/.env
        mode: "0600"

Anda boleh commit encrypted files ke Git dengan selamat. Tanpa vault password, tiada siapa boleh baca content sebenar. Ini membolehkan anda version control secrets bersama code tanpa risiko security.

Nota Beginner: Untuk team, consider guna vault password file instead of typing password setiap kali. Simpan password file di tempat selamat dan tambahkan dalam .gitignore. Jangan sesekali commit vault password file ke Git.

Terraform: Infrastructure Provisioning

Kalau Ansible adalah untuk configure server yang sudah ada, Terraform adalah untuk create infrastructure itu sendiri. Terraform boleh provision VMs, networks, DNS records, cloud resources, dan banyak lagi.

Terraform guna pendekatan declarative. Anda describe apa yang anda mahu, dan Terraform akan figure out macam mana nak capai state tersebut.

Install Terraform

# Ubuntu/Debian
wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install terraform

# macOS
brew tap hashicorp/tap
brew install hashicorp/tap/terraform

Konsep Asas Terraform

Providers adalah plugins yang membolehkan Terraform interact dengan platform tertentu. Ada providers untuk AWS, GCP, Azure, DigitalOcean, Proxmox, dan banyak lagi.

Resources adalah infrastructure objects yang anda mahu create. Contohnya VM, network, firewall rule.

State adalah record tentang infrastructure yang Terraform manage. Terraform simpan state dalam file terraform.tfstate. File ini sangat penting. Jangan delete atau edit secara manual.

Plan adalah preview tentang apa yang Terraform akan buat sebelum ia buat. Ini membolehkan anda review changes sebelum apply.

Variables membolehkan anda parameterize configuration. Daripada hardcode values, guna variables supaya code lebih flexible dan reusable.

Outputs adalah values yang Terraform display selepas apply. Contohnya IP address VM yang baru dibuat. Outputs juga boleh digunakan oleh modules lain.

Data Sources membolehkan anda query information daripada provider tanpa create resources. Contohnya, query AMI ID terbaru atau existing VPC ID.

Terraform workflow sangat straightforward. Anda tulis code, run terraform init untuk download providers, run terraform plan untuk preview, dan run terraform apply untuk execute. Kalau anda mahu destroy semua resources, run terraform destroy. Setiap langkah memberikan anda kawalan penuh terhadap apa yang berlaku kepada infrastructure anda.

Practical: Provision VM di Proxmox

Ini contoh yang sangat relevan untuk homelab. Kita akan guna Terraform untuk create VM di Proxmox.

Buat file main.tf:

terraform {
  required_providers {
    proxmox = {
      source  = "telmate/proxmox"
      version = "3.0.1-rc4"
    }
  }
}

provider "proxmox" {
  pm_api_url          = "https://192.168.1.10:8006/api2/json"
  pm_api_token_id     = "terraform@pam!terraform-token"
  pm_api_token_secret = var.proxmox_api_token
  pm_tls_insecure     = true
}

variable "proxmox_api_token" {
  description = "Proxmox API token secret"
  type        = string
  sensitive   = true
}

variable "vm_count" {
  description = "Number of VMs to create"
  type        = number
  default     = 2
}

resource "proxmox_vm_qemu" "web_server" {
  count       = var.vm_count
  name        = "web-${count.index + 1}"
  target_node = "pve"
  clone       = "ubuntu-template"

  cores   = 2
  memory  = 2048
  sockets = 1

  disk {
    storage = "local-lvm"
    size    = "20G"
    type    = "scsi"
  }

  network {
    model  = "virtio"
    bridge = "vmbr0"
  }

  os_type   = "cloud-init"
  ipconfig0 = "ip=192.168.1.${110 + count.index}/24,gw=192.168.1.1"
  ciuser    = "syafi"
  sshkeys   = file("~/.ssh/id_rsa.pub")

  lifecycle {
    ignore_changes = [
      network,
    ]
  }
}

output "vm_ips" {
  value = [for vm in proxmox_vm_qemu.web_server : vm.default_ipv4_address]
}

Buat file terraform.tfvars (jangan commit file ini ke Git):

proxmox_api_token = "your-api-token-secret-here"
vm_count          = 3

Jalankan Terraform:

# Initialize - download providers
terraform init

# Preview changes
terraform plan

# Apply changes - create the VMs
terraform apply

# Bila nak destroy semua
terraform destroy

terraform plan akan tunjukkan apa yang akan dibuat. Review dengan teliti sebelum run terraform apply. Ini salah satu kekuatan Terraform. Anda boleh nampak exactly apa yang akan berubah sebelum ia berlaku.

Nota Beginner: Sentiasa run terraform plan sebelum terraform apply. Jadikan ia habit. Dalam production, ini boleh selamatkan anda daripada accidentally delete database atau resources penting.

Practical: Provision di DigitalOcean

Untuk cloud, prosesnya sangat serupa. Cuma provider yang berbeza.

terraform {
  required_providers {
    digitalocean = {
      source  = "digitalocean/digitalocean"
      version = "~> 2.0"
    }
  }
}

provider "digitalocean" {
  token = var.do_token
}

variable "do_token" {
  description = "DigitalOcean API token"
  type        = string
  sensitive   = true
}

resource "digitalocean_droplet" "web" {
  image    = "ubuntu-24-04-x64"
  name     = "web-production"
  region   = "sgp1"
  size     = "s-1vcpu-1gb"
  ssh_keys = [digitalocean_ssh_key.default.fingerprint]

  tags = ["web", "production"]
}

resource "digitalocean_ssh_key" "default" {
  name       = "my-ssh-key"
  public_key = file("~/.ssh/id_rsa.pub")
}

resource "digitalocean_firewall" "web" {
  name = "web-firewall"

  droplet_ids = [digitalocean_droplet.web.id]

  inbound_rule {
    protocol         = "tcp"
    port_range       = "22"
    source_addresses = ["0.0.0.0/0"]
  }

  inbound_rule {
    protocol         = "tcp"
    port_range       = "80"
    source_addresses = ["0.0.0.0/0"]
  }

  inbound_rule {
    protocol         = "tcp"
    port_range       = "443"
    source_addresses = ["0.0.0.0/0"]
  }

  outbound_rule {
    protocol              = "tcp"
    port_range            = "1-65535"
    destination_addresses = ["0.0.0.0/0"]
  }
}

output "web_ip" {
  value = digitalocean_droplet.web.ipv4_address
}

Perhatikan pattern yang sama. Define provider, declare resources, run plan dan apply. Ini keindahan Terraform. Workflow yang sama untuk mana-mana platform.

Nota Beginner: Satu kesilapan yang biasa pada permulaan ialah meletakkan semua resources dalam satu file main.tf yang panjang. Walaupun Terraform membenarkan ini, practice yang lebih baik ialah pecahkan kepada beberapa files. Contohnya providers.tf untuk provider configuration, variables.tf untuk variable declarations, main.tf untuk resources utama, outputs.tf untuk output values, dan firewall.tf untuk security rules. Terraform akan automatically load semua .tf files dalam satu directory.

Terraform Modules: Reusable Components

Modules membolehkan anda package Terraform code untuk digunakan semula. Bayangkan anda selalu create VM dengan pattern yang sama. Jadikan ia module.

Structure:

terraform/
  modules/
    web-server/
      main.tf
      variables.tf
      outputs.tf
  environments/
    staging/
      main.tf
    production/
      main.tf

File modules/web-server/variables.tf:

variable "name" {
  description = "Server name"
  type        = string
}

variable "size" {
  description = "Droplet size"
  type        = string
  default     = "s-1vcpu-1gb"
}

variable "region" {
  description = "Droplet region"
  type        = string
  default     = "sgp1"
}

File modules/web-server/main.tf:

resource "digitalocean_droplet" "this" {
  image  = "ubuntu-24-04-x64"
  name   = var.name
  region = var.region
  size   = var.size
}

File modules/web-server/outputs.tf:

output "ip_address" {
  value = digitalocean_droplet.this.ipv4_address
}

output "id" {
  value = digitalocean_droplet.this.id
}

Guna module dalam environments/production/main.tf:

module "web_app" {
  source = "../../modules/web-server"
  name   = "production-web"
  size   = "s-2vcpu-4gb"
  region = "sgp1"
}

module "web_staging" {
  source = "../../modules/web-server"
  name   = "staging-web"
  size   = "s-1vcpu-1gb"
  region = "sgp1"
}

output "production_ip" {
  value = module.web_app.ip_address
}

Terraform State Management

State file adalah jantung Terraform. Ia track mapping antara code anda dan real infrastructure. Untuk projek solo atau homelab, local state file okay. Tetapi untuk team, anda perlu remote state.

Contoh remote state menggunakan S3 (atau DigitalOcean Spaces):

terraform {
  backend "s3" {
    bucket   = "my-terraform-state"
    key      = "production/terraform.tfstate"
    region   = "ap-southeast-1"
    endpoint = "https://sgp1.digitaloceanspaces.com"

    skip_credentials_validation = true
    skip_metadata_api_check     = true
    skip_requesting_account_id  = true
    skip_s3_checksum            = true
  }
}

Nota Beginner: Jangan sekali-kali commit terraform.tfstate ke Git. File ini mungkin mengandungi sensitive data seperti passwords dan API keys. Tambahkan *.tfstate dan *.tfstate.backup dalam .gitignore anda.

Terraform Commands Yang Perlu Anda Tahu

Selain init, plan, apply, dan destroy, ada beberapa commands lain yang berguna dalam daily workflow.

# Format semua Terraform files mengikut standard style
terraform fmt

# Validate configuration tanpa access provider APIs
terraform validate

# Tunjuk current state
terraform show

# List semua resources dalam state
terraform state list

# Import existing resource ke Terraform state
terraform import aws_instance.web i-1234567890abcdef0

# Remove resource dari state tanpa destroy
terraform state rm aws_instance.old_server

# Refresh state untuk match actual infrastructure
terraform refresh

# Output values
terraform output

terraform import sangat berguna bila anda ada existing infrastructure yang anda mahu start manage dengan Terraform. Daripada recreate, anda boleh import resource tersebut ke state file.

terraform fmt pastikan semua files anda mengikut standard formatting. Saya recommend run command ini sebelum setiap commit. Anda boleh setup pre-commit hook untuk automate ini.

Ansible vs Terraform: Bila Guna Yang Mana?

Soalan ini sering ditanya. Jawapan ringkas: guna kedua-duanya, untuk tujuan yang berbeza.

Terraform untuk provisioning. Create VMs, networks, DNS records, cloud resources. Terraform tahu macam mana nak create, update, dan destroy infrastructure.

Ansible untuk configuration. Install software, configure services, deploy applications, manage users. Ansible tahu macam mana nak configure server yang sudah wujud.

Workflow yang biasa:

  1. Terraform create VM baru.
  2. Terraform output IP address VM tersebut.
  3. Ansible ambil IP address itu dan configure VM. Install Docker, setup firewall, deploy app.

Contoh integration. Terraform create VM, kemudian trigger Ansible:

resource "proxmox_vm_qemu" "app_server" {
  name        = "app-server"
  target_node = "pve"
  clone       = "ubuntu-template"
  cores       = 4
  memory      = 4096

  provisioner "local-exec" {
    command = "sleep 30 && ansible-playbook -i '${self.default_ipv4_address},' setup.yml"
  }
}

Ini adalah pattern yang sangat powerful. Infrastructure provisioning dan configuration management dalam satu workflow.

Satu lagi scenario biasa. Anda guna Terraform untuk create cloud resources (VMs, databases, load balancers), kemudian Ansible untuk deploy dan configure applications pada VMs tersebut. Terraform output boleh dijadikan Ansible inventory secara dynamic.

Contoh generate Ansible inventory daripada Terraform output:

# Dapatkan IP dari Terraform output dan run Ansible
terraform output -json vm_ips | jq -r '.[]' > /tmp/inventory.txt
ansible-playbook -i /tmp/inventory.txt deploy.yml

Ini membolehkan anda automate keseluruhan flow dari infrastructure creation sampai application deployment dalam satu pipeline.

GitOps: Manage Infrastructure Dengan Git

GitOps bermaksud Git menjadi single source of truth untuk infrastructure anda. Semua changes melalui Git. Tiada manual changes.

Structure Repository

infra/
  ansible/
    inventory/
      homelab.ini
      production.ini
    playbooks/
      setup-webserver.yml
      deploy-app.yml
    roles/
      common/
      docker/
      nginx/
    ansible.cfg
    requirements.yml
  terraform/
    modules/
      web-server/
      database/
    environments/
      homelab/
        main.tf
        terraform.tfvars
      production/
        main.tf
        terraform.tfvars
  .gitignore
  README.md

.gitignore Untuk IaC

# Terraform
*.tfstate
*.tfstate.backup
.terraform/
*.tfvars
!*.tfvars.example
crash.log

# Ansible
*.retry

# Secrets
.env
*secret*
*credential*

Nota Beginner: Buat file terraform.tfvars.example dengan dummy values sebagai reference. Ini membantu team members baru tahu variables apa yang diperlukan tanpa expose actual secrets.

Workflow GitOps

Workflow yang saya recommend:

  1. Buat branch baru untuk setiap infrastructure change.
  2. Tulis code Terraform atau Ansible.
  3. Run plan/check untuk preview changes.
  4. Buat Pull Request untuk review.
  5. Merge selepas approved.
  6. Apply changes (boleh automate dengan CI/CD).
# Buat branch untuk infrastructure change
git checkout -b feat/add-monitoring-server

# Buat perubahan pada Terraform files
# Edit main.tf

# Run terraform plan untuk verify
terraform plan -out=plan.out

# Commit dan push
git add .
git commit -m "feat: add monitoring server"
git push origin feat/add-monitoring-server

# Buat PR, review, merge, then apply

Dalam production environment yang mature, terraform apply dijalankan oleh CI/CD pipeline selepas PR di-merge. Ini bermakna tiada siapa yang run Terraform dari laptop mereka. Semua melalui pipeline yang controlled dan auditable.

Ringkasan

Infrastructure as Code mengubah cara kita manage infrastructure daripada manual, error-prone process kepada automated, repeatable, dan version-controlled workflow.

Perkara penting yang anda belajar:

  • IaC menyelesaikan masalah consistency, reproducibility, dan scalability.
  • Ansible sesuai untuk configuration management. Guna inventories untuk senarai server, playbooks untuk tasks, dan roles untuk organisasi.
  • Terraform sesuai untuk infrastructure provisioning. Guna providers untuk connect ke platform, resources untuk define infrastructure, dan modules untuk reusability.
  • Guna Terraform untuk create infrastructure, Ansible untuk configure. Kedua-dua tools saling melengkapi.
  • GitOps menjadikan Git sebagai single source of truth. Semua changes melalui Git dan boleh di-track, di-review, dan di-audit.
  • Jangan commit secrets dan state files ke Git.

Pada bab seterusnya, kita akan belajar tentang monitoring dan observability. Sebab infrastructure yang automated tanpa monitoring adalah macam kereta tanpa dashboard. Anda tidak tahu apa yang sedang berlaku.

Bab 7: Monitoring dan Observability

Anda sudah belajar automate infrastructure dengan Ansible dan Terraform. Server sudah up, apps sudah deploy. Tapi macam mana anda tahu semuanya berjalan dengan baik? Macam mana anda tahu disk hampir penuh sebelum ia benar-benar penuh? Macam mana anda tahu response time API anda makin perlahan?

Ini di mana monitoring masuk. Monitoring bukan sekadar “nice to have”. Dalam production, ia adalah keperluan mutlak. Saya pernah belajar perkara ini secara hard way. Server homelab saya down selama 2 hari dan saya tidak perasan sebab tiada monitoring. Jangan jadi macam saya.

Observability pula adalah konsep yang lebih luas. Ia bukan sekadar tahu “server up atau down”, tetapi memahami kenapa sesuatu berlaku dalam sistem anda.

Apa yang anda akan belajar:

  • Tiga pillars of observability: metrics, logs, traces
  • Setup Prometheus dan Grafana untuk metrics dan dashboards
  • Loki untuk centralized logging
  • Alerting dengan Alertmanager, PagerDuty, dan Telegram
  • Uptime monitoring dengan Uptime Kuma
  • Konsep SLI, SLO, dan SLA
  • Asas incident response
  • Practical: setup full monitoring stack untuk web app

Tiga Pillars of Observability

Observability dibina atas tiga komponen utama.

1. Metrics

Metrics adalah numerical data yang menunjukkan status sistem pada sesuatu masa. Contohnya CPU usage 45%, memory usage 2.1GB, request count 1500 per minute, response time 120ms.

Metrics sangat baik untuk dashboards dan alerting sebab ia lightweight dan mudah di-aggregate.

2. Logs

Logs adalah event records yang describe apa yang berlaku dalam sistem. Contohnya “User john logged in at 14:23”, “Database connection failed: timeout after 30s”. Logs memberi context yang metrics tidak boleh bagi.

3. Traces

Traces track perjalanan sesuatu request melalui pelbagai services. Dalam microservices architecture, satu user request mungkin melalui 5 atau 6 services. Traces membantu anda faham di mana bottleneck berlaku.

Untuk homelab dan early production, fokus pada metrics dan logs dulu. Traces menjadi penting bila anda mula guna microservices architecture.

Monitoring vs Observability

Ramai orang guna istilah ini secara interchangeable, tapi sebenarnya ada perbezaan.

Monitoring bermaksud anda tahu apa yang nak dipantau. Anda setup dashboard untuk CPU usage, memory usage, response time. Anda tahu soalan yang nak ditanya.

Observability bermaksud sistem anda boleh memberitahu anda apa yang berlaku walaupun anda tidak tahu soalan yang nak ditanya. Bila ada masalah baru yang anda tidak pernah jangka, observability membolehkan anda investigate dan faham root cause tanpa perlu deploy new instrumentation.

Dalam practice, anda mula dengan monitoring (setup dashboards dan alerts untuk perkara yang anda tahu penting) dan gradually move towards observability (add more instrumentation, structured logging, tracing) seiring sistem anda menjadi lebih complex.

Pull vs Push Model

Prometheus guna pull model. Ia secara aktif scrape metrics daripada targets pada interval yang ditetapkan. Ini berbeza daripada push model (macam StatsD atau InfluxDB Telegraf) di mana applications push metrics ke central server.

Kelebihan pull model: Prometheus tahu bila sesuatu target down (sebab ia gagal scrape). Ia juga mudah untuk debug sebab anda boleh manually access metrics endpoint di browser.

Kekurangan pull model: tidak ideal untuk short-lived jobs atau serverless functions. Untuk kes macam ini, Prometheus ada Pushgateway.

Prometheus + Grafana: The Power Duo

Prometheus adalah monitoring system yang collect dan store metrics. Grafana adalah visualization tool yang buat dashboards cantik daripada data Prometheus. Bersama, mereka adalah stack monitoring paling popular dalam dunia DevOps.

Setup Prometheus dan Grafana Dengan Docker Compose

Buat file docker-compose.monitoring.yml:

services:
  prometheus:
    image: prom/prometheus:latest
    container_name: prometheus
    volumes:
      - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
      - ./prometheus/alert-rules.yml:/etc/prometheus/alert-rules.yml
      - prometheus_data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.retention.time=30d'
      - '--web.enable-lifecycle'
    ports:
      - "9090:9090"
    restart: unless-stopped
    networks:
      - monitoring

  grafana:
    image: grafana/grafana:latest
    container_name: grafana
    environment:
      - GF_SECURITY_ADMIN_USER=admin
      - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD}
      - GF_INSTALL_PLUGINS=grafana-clock-panel
    volumes:
      - grafana_data:/var/lib/grafana
      - ./grafana/provisioning:/etc/grafana/provisioning
    ports:
      - "3000:3000"
    restart: unless-stopped
    depends_on:
      - prometheus
    networks:
      - monitoring

  node-exporter:
    image: prom/node-exporter:latest
    container_name: node-exporter
    volumes:
      - /proc:/host/proc:ro
      - /sys:/host/sys:ro
      - /:/rootfs:ro
    command:
      - '--path.procfs=/host/proc'
      - '--path.rootfs=/rootfs'
      - '--path.sysfs=/host/sys'
    ports:
      - "9100:9100"
    restart: unless-stopped
    networks:
      - monitoring

  cadvisor:
    image: gcr.io/cadvisor/cadvisor:latest
    container_name: cadvisor
    volumes:
      - /:/rootfs:ro
      - /var/run:/var/run:ro
      - /sys:/sys:ro
      - /var/lib/docker/:/var/lib/docker:ro
    ports:
      - "8080:8080"
    restart: unless-stopped
    networks:
      - monitoring

  alertmanager:
    image: prom/alertmanager:latest
    container_name: alertmanager
    volumes:
      - ./alertmanager/alertmanager.yml:/etc/alertmanager/alertmanager.yml
    ports:
      - "9093:9093"
    restart: unless-stopped
    networks:
      - monitoring

volumes:
  prometheus_data:
  grafana_data:

networks:
  monitoring:
    driver: bridge

Node Exporter collect metrics daripada host machine (CPU, memory, disk, network). cAdvisor collect metrics daripada Docker containers. Kedua-duanya feed data ke Prometheus.

Konfigurasi Prometheus

Buat file prometheus/prometheus.yml:

global:
  scrape_interval: 15s
  evaluation_interval: 15s

rule_files:
  - "alert-rules.yml"

alerting:
  alertmanagers:
    - static_configs:
        - targets:
          - alertmanager:9093

scrape_configs:
  - job_name: "prometheus"
    static_configs:
      - targets: ["localhost:9090"]

  - job_name: "node-exporter"
    static_configs:
      - targets: ["node-exporter:9100"]

  - job_name: "cadvisor"
    static_configs:
      - targets: ["cadvisor:8080"]

  - job_name: "web-app"
    static_configs:
      - targets: ["web-app:8000"]
    metrics_path: /metrics
    scrape_interval: 10s

scrape_interval bermaksud Prometheus akan collect metrics setiap 15 saat. Untuk web app, kita set 10 saat sebab kita mahu data yang lebih granular.

PromQL: Query Language Untuk Metrics

PromQL adalah bahasa query untuk Prometheus. Anda perlu tahu basics untuk buat dashboards dan alerts yang berguna.

Beberapa contoh PromQL yang sering digunakan:

# CPU usage percentage
100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)

# Memory usage percentage
(1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100

# Disk usage percentage
(1 - (node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"})) * 100

# HTTP request rate (requests per second)
rate(http_requests_total[5m])

# HTTP error rate (5xx responses)
rate(http_requests_total{status=~"5.."}[5m])

# 95th percentile response time
histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m]))

# Container CPU usage
rate(container_cpu_usage_seconds_total{name!=""}[5m]) * 100

# Container memory usage
container_memory_usage_bytes{name!=""} / 1024 / 1024

Nota Beginner: Jangan takut dengan PromQL. Mulakan dengan query simple macam up (tunjuk mana targets yang up) dan node_memory_MemAvailable_bytes (memory available). Lepas tu explore dari situ.

Grafana Dashboards

Selepas Prometheus dan Grafana running, buka Grafana di http://localhost:3000. Login dengan admin credentials.

Langkah pertama, tambah Prometheus sebagai data source:

  1. Pergi ke Settings > Data Sources > Add data source.
  2. Pilih Prometheus.
  3. URL: http://prometheus:9090.
  4. Klik Save & Test.

Untuk dashboards, anda tidak perlu buat dari scratch. Import dashboards yang sedia ada dari Grafana community.

Dashboard IDs yang saya recommend:

  • 1860 untuk Node Exporter Full. Tunjuk semua metrics host.
  • 893 untuk Docker dan system monitoring.
  • 14282 untuk cAdvisor.

Pergi ke Dashboards > Import > masukkan dashboard ID > pilih Prometheus data source > Import.

Untuk custom dashboard, contoh panel configuration:

{
  "title": "Request Rate",
  "type": "timeseries",
  "datasource": "Prometheus",
  "targets": [
    {
      "expr": "rate(http_requests_total[5m])",
      "legendFormat": "{{method}} {{path}}"
    }
  ]
}

Loki: Centralized Logging

Grafana Loki adalah log aggregation system yang designed untuk work dengan Grafana. Ia macam Prometheus, tapi untuk logs. Loki tidak index full log text, ia hanya index labels. Ini menjadikan ia sangat efficient dari segi storage.

Tambah Loki dan Promtail ke Docker Compose:

  loki:
    image: grafana/loki:latest
    container_name: loki
    volumes:
      - loki_data:/loki
      - ./loki/loki-config.yml:/etc/loki/config.yml
    command: -config.file=/etc/loki/config.yml
    ports:
      - "3100:3100"
    restart: unless-stopped
    networks:
      - monitoring

  promtail:
    image: grafana/promtail:latest
    container_name: promtail
    volumes:
      - /var/log:/var/log:ro
      - /var/lib/docker/containers:/var/lib/docker/containers:ro
      - ./promtail/promtail-config.yml:/etc/promtail/config.yml
    command: -config.file=/etc/promtail/config.yml
    restart: unless-stopped
    depends_on:
      - loki
    networks:
      - monitoring

File promtail/promtail-config.yml:

server:
  http_listen_port: 9080

positions:
  filename: /tmp/positions.yaml

clients:
  - url: http://loki:3100/loki/api/v1/push

scrape_configs:
  - job_name: system
    static_configs:
      - targets:
          - localhost
        labels:
          job: varlogs
          __path__: /var/log/*.log

  - job_name: docker
    static_configs:
      - targets:
          - localhost
        labels:
          job: docker
          __path__: /var/lib/docker/containers/*/*-json.log
    pipeline_stages:
      - json:
          expressions:
            log: log
            stream: stream
            time: time
      - output:
          source: log

Promtail collect logs dan hantar ke Loki. Tambah Loki sebagai data source dalam Grafana sama macam anda tambah Prometheus. URL: http://loki:3100.

Dalam Grafana, anda boleh query logs dengan LogQL:

# Semua logs dari docker job
{job="docker"}

# Filter logs yang contain "error"
{job="docker"} |= "error"

# Logs dari specific container
{job="docker", container="web-app"} |= "500"

# Count errors per minute
count_over_time({job="docker"} |= "error" [1m])

Alerting: Jangan Tunggu User Complain

Monitoring tanpa alerting adalah macam pasang CCTV tapi tiada siapa yang tengok. Anda perlu alerts yang memberitahu anda bila ada masalah.

Alert Rules Dalam Prometheus

Buat file prometheus/alert-rules.yml:

groups:
  - name: system-alerts
    rules:
      - alert: HighCPUUsage
        expr: 100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 80
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "High CPU usage on {{ $labels.instance }}"
          description: "CPU usage is above 80% for 5 minutes. Current value: {{ $value }}%"

      - alert: HighMemoryUsage
        expr: (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100 > 85
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "High memory usage on {{ $labels.instance }}"
          description: "Memory usage is above 85%. Current value: {{ $value }}%"

      - alert: DiskSpaceLow
        expr: (1 - (node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"})) * 100 > 85
        for: 10m
        labels:
          severity: critical
        annotations:
          summary: "Low disk space on {{ $labels.instance }}"
          description: "Disk usage is above 85%. Current value: {{ $value }}%"

      - alert: ServiceDown
        expr: up == 0
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "Service {{ $labels.job }} is down"
          description: "{{ $labels.instance }} has been down for more than 1 minute."

  - name: app-alerts
    rules:
      - alert: HighErrorRate
        expr: rate(http_requests_total{status=~"5.."}[5m]) / rate(http_requests_total[5m]) > 0.05
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "High error rate detected"
          description: "Error rate is above 5%. Current value: {{ $value }}"

      - alert: SlowResponseTime
        expr: histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m])) > 2
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Slow response time detected"
          description: "95th percentile response time is above 2 seconds."

Field for bermaksud condition mesti bertahan selama tempoh tersebut sebelum alert di-fire. Ini mengelakkan false alarms daripada spike yang sementara.

Alertmanager Configuration

Alertmanager handle routing dan deduplication alerts. Ia boleh hantar notifications ke pelbagai channels.

File alertmanager/alertmanager.yml:

global:
  resolve_timeout: 5m

route:
  receiver: "telegram"
  group_by: ["alertname", "severity"]
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 4h
  routes:
    - receiver: "telegram-critical"
      match:
        severity: critical
      repeat_interval: 1h

receivers:
  - name: "telegram"
    telegram_configs:
      - bot_token: "${TELEGRAM_BOT_TOKEN}"
        chat_id: ${TELEGRAM_CHAT_ID}
        message: |
          {{ range .Alerts }}
          {{ .Labels.severity | toUpper }}: {{ .Annotations.summary }}
          {{ .Annotations.description }}
          {{ end }}

  - name: "telegram-critical"
    telegram_configs:
      - bot_token: "${TELEGRAM_BOT_TOKEN}"
        chat_id: ${TELEGRAM_CHAT_ID}
        message: |
          CRITICAL ALERT
          {{ range .Alerts }}
          {{ .Annotations.summary }}
          {{ .Annotations.description }}
          {{ end }}

Nota Beginner: Saya recommend guna Telegram bot untuk alerting dalam homelab. Ia free, mudah setup, dan anda akan terima notifications terus pada phone. Untuk production, consider PagerDuty atau Opsgenie yang ada on-call rotation features.

Untuk setup Telegram bot: 1. Chat dengan @BotFather di Telegram. 2. Buat bot baru dan dapatkan bot token. 3. Buat group atau channel dan dapatkan chat ID. 4. Masukkan token dan chat ID dalam Alertmanager config.

Uptime Monitoring Dengan Uptime Kuma

Uptime Kuma adalah self-hosted uptime monitoring tool. Ia check sama ada services anda accessible dari luar. Ia sangat mudah untuk setup dan mempunyai interface yang cantik.

  uptime-kuma:
    image: louislam/uptime-kuma:latest
    container_name: uptime-kuma
    volumes:
      - uptime_kuma_data:/app/data
    ports:
      - "3001:3001"
    restart: unless-stopped
    networks:
      - monitoring

Selepas deploy, buka http://localhost:3001 dan configure monitors:

  • HTTP(s) monitor untuk web apps.
  • TCP monitor untuk databases dan services.
  • Ping monitor untuk network devices.
  • DNS monitor untuk domain resolution.

Uptime Kuma boleh hantar notifications ke Telegram, Discord, Slack, dan banyak lagi. Ia juga boleh generate status page yang boleh anda share dengan users.

Saya guna Uptime Kuma untuk monitor semua services dalam homelab saya. Setiap kali sesuatu down, saya dapat notification di Telegram dalam masa 60 saat.

Best Practices Untuk Alerting

Alerting yang baik adalah tentang balance. Terlalu banyak alerts menyebabkan alert fatigue. Anda mula ignore alerts, dan bila ada real problem, anda miss it. Terlalu sedikit alerts bermaksud anda tidak tahu bila ada masalah.

Beberapa guidelines yang saya ikut:

Alert on symptoms, not causes. Alert bila response time tinggi (symptom), bukan bila CPU tinggi (cause). CPU tinggi mungkin normal semasa batch processing. Response time tinggi sentiasa bermaksud users affected.

Setiap alert perlu action. Kalau anda receive alert dan response anda adalah “okay, noted” tanpa buat apa-apa, alert tersebut perlu dibuang atau ditukar threshold. Setiap alert sepatutnya require seseorang investigate atau take action.

Group dan deduplicate alerts. Alertmanager handle ini dengan group_by. Kalau 10 servers semuanya report CPU tinggi pada masa yang sama, anda mahu satu alert yang summarize situasi, bukan 10 separate notifications.

Classify severity levels. Saya guna tiga levels. Warning bermaksud investigate bila sempat, biasanya dalam beberapa jam. Critical bermaksud perlu perhatian segera, service mungkin affected. Emergency bermaksud outage sedang berlaku, users affected right now.

SLI, SLO, dan SLA

Konsep ini penting bila anda mula deal dengan production systems dan users sebenar.

SLI (Service Level Indicator) adalah measurement sebenar performance service anda. Contoh: 99.5% requests berjaya, 95th percentile response time 200ms.

SLO (Service Level Objective) adalah target yang anda tetapkan. Contoh: “99.9% uptime setiap bulan”, “95th percentile response time mestilah di bawah 500ms”.

SLA (Service Level Agreement) adalah agreement formal dengan customer. Kalau anda gagal meet SLA, biasanya ada consequences seperti credit atau refund.

Untuk homelab, anda tidak perlu SLA. Tapi menetapkan SLOs membantu anda decide berapa banyak effort nak letak pada reliability.

Contoh SLO untuk web app:

  • Availability: 99.9% (bermaksud maximum 43.8 minit downtime sebulan)
  • Response time: p95 di bawah 500ms
  • Error rate: kurang daripada 0.1%

Buat Grafana dashboard yang track SLIs anda supaya anda sentiasa tahu sama ada anda meet SLOs atau tidak.

Asas Incident Response

Bila alert berbunyi, anda perlu tahu apa nak buat. Incident response yang baik bermakna masalah diselesaikan dengan cepat dan systematic.

Langkah-langkah basic:

  1. Acknowledge. Terima alert dan maklumkan team bahawa anda sedang investigate.
  2. Assess. Tentukan severity. Adakah ia affect users? Berapa ramai?
  3. Mitigate. Selesaikan masalah secepat mungkin. Kadang-kadang ini bermaksud rollback, kadang-kadang restart service.
  4. Communicate. Update stakeholders tentang status.
  5. Resolve. Fix the root cause.
  6. Post-mortem. Selepas incident selesai, tulis post-mortem. Apa yang berlaku, kenapa, dan macam mana nak prevent pada masa depan.

Nota Beginner: Post-mortem bukan untuk blame orang. Ia adalah blameless exercise untuk belajar daripada masalah. Fokus pada sistem dan proses, bukan individu. Dalam DevOps culture, kesilapan adalah peluang untuk improve.

Contoh Post-Mortem Template

Untuk reference, ini template post-mortem yang simple tapi effective:

# Incident Post-Mortem: [Tajuk Incident]

**Tarikh:** 2026-03-15
**Severity:** Critical
**Duration:** 45 minit (14:00 - 14:45 MYT)
**Author:** Syafi

## Summary
Web app down selama 45 minit disebabkan disk penuh pada database server.

## Timeline
- 14:00 - Alert "DiskSpaceLow" triggered pada db1
- 14:05 - On-call engineer acknowledge alert
- 14:10 - Investigate dan confirm disk 100% full
- 14:20 - Clear old log files, free up 5GB space
- 14:30 - Database service recovered
- 14:45 - All services confirmed healthy

## Root Cause
Database logs tidak di-rotate dan mengisi disk selama 3 bulan.

## Action Items
- [ ] Setup log rotation pada semua database servers
- [ ] Tambah alert untuk disk usage > 70% (early warning)
- [ ] Automate log cleanup dengan cron job

Biasakan diri tulis post-mortem walaupun untuk incident kecil di homelab. Ini membina habit yang sangat berharga untuk production environment.

Practical: Full Monitoring Stack Untuk Web App

Jom combine semuanya. Katakan anda ada Node.js web app yang anda mahu monitor sepenuhnya.

Pertama, app anda perlu expose metrics. Guna library prom-client:

const express = require('express');
const promClient = require('prom-client');

const app = express();

// Create metrics
const httpRequestsTotal = new promClient.Counter({
  name: 'http_requests_total',
  help: 'Total HTTP requests',
  labelNames: ['method', 'path', 'status']
});

const httpRequestDuration = new promClient.Histogram({
  name: 'http_request_duration_seconds',
  help: 'HTTP request duration in seconds',
  labelNames: ['method', 'path'],
  buckets: [0.01, 0.05, 0.1, 0.5, 1, 2, 5]
});

// Collect default metrics (CPU, memory, etc.)
promClient.collectDefaultMetrics();

// Middleware to track metrics
app.use((req, res, next) => {
  const end = httpRequestDuration.startTimer({ method: req.method, path: req.path });
  res.on('finish', () => {
    httpRequestsTotal.inc({ method: req.method, path: req.path, status: res.statusCode });
    end();
  });
  next();
});

// Metrics endpoint for Prometheus
app.get('/metrics', async (req, res) => {
  res.set('Content-Type', promClient.register.contentType);
  res.end(await promClient.register.metrics());
});

app.get('/', (req, res) => {
  res.json({ status: 'ok' });
});

app.listen(8000, () => {
  console.log('App running on port 8000');
});

Untuk Python Flask app pula, anda boleh guna prometheus_flask_instrumentator:

from flask import Flask
from prometheus_flask_instrumentator import FlaskInstrumentator

app = Flask(__name__)
FlaskInstrumentator().instrument(app)

@app.route('/')
def hello():
    return {'status': 'ok'}

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8000)

Idea utama sama untuk mana-mana language. Expose /metrics endpoint, dan Prometheus akan scrape data dari situ. Kebanyakan frameworks mempunyai library yang memudahkan proses ini.

Kemudian, jalankan full monitoring stack:

docker compose -f docker-compose.monitoring.yml up -d

Verify semua containers running:

docker compose -f docker-compose.monitoring.yml ps

Sekarang buka Prometheus di http://localhost:9090/targets untuk confirm semua scrape targets berstatus UP. Kalau ada yang DOWN, check network connectivity dan pastikan port betul.

Sekarang anda mempunyai:

  • Prometheus (port 9090) yang collect metrics daripada app, node exporter, dan cAdvisor.
  • Grafana (port 3000) untuk dashboards.
  • Loki (port 3100) untuk centralized logs.
  • Alertmanager (port 9093) untuk routing alerts ke Telegram.
  • Uptime Kuma (port 3001) untuk uptime monitoring.

Ini adalah setup monitoring yang solid untuk homelab dan boleh di-scale untuk production.

Monitoring Checklist

Sebelum anda consider monitoring stack anda “complete”, pastikan anda ada perkara berikut:

Infrastructure level: - CPU, memory, dan disk usage untuk setiap server. - Network traffic in dan out. - Docker container health dan resource usage.

Application level: - Request rate (requests per second). - Error rate (percentage of 5xx responses). - Response time (p50, p95, p99). - Active connections atau concurrent users.

Business level: - Signup rate, login rate, atau transaction rate. - Key business metrics yang relevant.

Alerting: - Alert untuk service down. - Alert untuk high error rate. - Alert untuk disk space low. - Alert untuk SSL certificate expiry. - Notification channel yang anda actually check (Telegram, email).

Mulakan dengan yang basic dan tambah secara gradual. Anda tidak perlu setup semuanya dalam satu hari. Yang penting adalah anda mula dan terus improve monitoring coverage anda over time.

Ringkasan

Monitoring dan observability bukan luxury. Ia adalah keperluan untuk mana-mana sistem yang anda care tentang uptime dan performance.

Perkara penting yang anda belajar:

  • Tiga pillars of observability: metrics (numerical data), logs (event records), dan traces (request journeys).
  • Prometheus collect dan store metrics. Grafana visualize data. Bersama, mereka adalah monitoring stack yang powerful.
  • PromQL adalah query language untuk Prometheus. Belajar basics untuk buat dashboards dan alerts yang berguna.
  • Loki adalah Prometheus untuk logs. Ia lightweight dan integrate baik dengan Grafana.
  • Alerting penting supaya anda tahu ada masalah sebelum users complain. Guna Telegram untuk homelab, PagerDuty untuk production.
  • Uptime Kuma adalah tool simple tapi powerful untuk monitor availability services anda.
  • SLI mengukur performance, SLO menetapkan target, SLA adalah agreement formal dengan customer.
  • Incident response yang baik adalah systematic dan blameless. Sentiasa buat post-mortem untuk belajar daripada masalah.

Pada bab seterusnya, kita akan belajar tentang perjalanan dari homelab ke cloud. Bila homelab tidak cukup lagi dan anda perlu scale ke cloud providers.

Bab 8: Dari Homelab ke Cloud

Ada satu titik dalam perjalanan homelab di mana anda mula terfikir, “Okay, macam mana kalau saya nak serve users sebenar?” Mungkin anda sudah buat app yang kawan-kawan guna, atau anda mahu host side project yang perlu uptime 24/7 tanpa bergantung pada power supply rumah anda.

Homelab adalah tempat terbaik untuk belajar. Tapi production workload ada requirements yang berbeza. Reliability, scalability, global accessibility. Ini bukan bermaksud homelab tidak berguna. Sebaliknya, skill yang anda belajar di homelab adalah foundation yang sangat solid untuk cloud.

Bab ini akan guide anda dari homelab ke cloud. Kita akan explore kenapa dan bila perlu cloud, bandingkan providers, dan deploy app sebenar ke cloud step by step.

Apa yang anda akan belajar:

  • Bila homelab tidak mencukupi dan kenapa cloud diperlukan
  • Perbandingan cloud providers (AWS, GCP, Azure, DigitalOcean)
  • Konsep cloud yang penting (VPC, IAM, load balancer, managed services)
  • Cost management supaya bil cloud tidak mengejutkan anda
  • Pendekatan hybrid: homelab dan cloud bersama
  • Practical: deploy app ke DigitalOcean dan AWS
  • Cloud certifications yang berbaloi untuk kerjaya

Kenapa Cloud? Bila Homelab Tidak Cukup

Homelab ada limitasi yang cloud boleh selesaikan.

Reliability. Rumah anda ada power outage, internet down, atau hardware failure. Cloud providers ada multiple data centers dengan redundancy yang anda tidak mampu replicate di rumah.

Scalability. Anda boleh tambah RAM pada server homelab, tapi ada limit fizikal. Dalam cloud, anda boleh scale dari 1 server ke 100 server dalam masa beberapa minit.

Global reach. Kalau users anda di US dan Europe, serve dari server di rumah anda di Malaysia bermakna latency yang tinggi. Cloud providers ada data centers di seluruh dunia.

Compliance. Sesetengah industries require data disimpan di certified data centers. Homelab tidak memenuhi requirements ini.

Bandwidth. Home internet biasanya ada upload speed yang rendah dan ISP mungkin block certain ports.

Tapi ingat, cloud bukan jawapan untuk semua benda. Untuk development, testing, learning, dan personal projects, homelab masih sangat relevan dan jauh lebih murah.

Nota Beginner: Jangan terus migrate semua ke cloud. Start dengan identify workloads yang benar-benar perlu cloud (public-facing apps, production APIs) dan keep yang lain di homelab (development, CI runners, media server, monitoring).

Perbandingan Cloud Providers

Amazon Web Services (AWS)

AWS adalah cloud provider paling besar dan paling mature. Ia mempunyai lebih daripada 200 services.

Kelebihan: - Services paling lengkap. Apa sahaja yang anda perlukan, AWS mungkin ada. - Community paling besar. Banyak tutorials, documentation, dan Stack Overflow answers. - Free tier yang generous. 12 bulan free tier untuk banyak services. - Paling banyak job opportunities.

Kekurangan: - Learning curve yang steep. Terlalu banyak services boleh jadi overwhelming. - Pricing yang complex. Bil boleh jadi mengejutkan kalau tidak monitor. - Console UI yang kurang intuitive.

Sesuai untuk: Production workloads, enterprise, kalau anda target kerjaya cloud engineering.

Google Cloud Platform (GCP)

GCP adalah cloud provider dari Google. Strong dalam data analytics, machine learning, dan Kubernetes.

Kelebihan: - Google Kubernetes Engine (GKE) adalah managed Kubernetes terbaik. - BigQuery untuk data analytics sangat powerful. - Pricing lebih transparent dan predictable. - UI yang lebih clean daripada AWS.

Kekurangan: - Kurang services berbanding AWS. - Community lebih kecil. - Google ada track record shutdown products.

Sesuai untuk: Data-heavy applications, Kubernetes workloads, machine learning.

Microsoft Azure

Azure adalah cloud provider dari Microsoft. Strong dalam enterprise dan integration dengan Microsoft ecosystem.

Kelebihan: - Integration sempurna dengan Active Directory, Office 365, dan Windows ecosystem. - Hybrid cloud capabilities yang baik. - Enterprise compliance dan security features.

Kekurangan: - Naming convention yang confusing (mereka rename services kerap). - Documentation kadang-kadang tidak up to date. - Pricing boleh jadi complex.

Sesuai untuk: Enterprise environments, organisations yang sudah guna Microsoft stack.

DigitalOcean

DigitalOcean bukan “big three” tapi ia sangat popular di kalangan developers dan small teams.

Kelebihan: - Paling simple dan straightforward. - Pricing yang transparent dan predictable. - Documentation yang sangat baik dan tutorials yang banyak. - Perfect untuk small to medium projects.

Kekurangan: - Kurang services advanced berbanding big three. - Tidak suitable untuk enterprise-scale workloads. - Kurang regions berbanding AWS atau GCP.

Sesuai untuk: Side projects, startups, small businesses, belajar cloud basics.

Recommendation saya untuk orang yang baru mula? DigitalOcean untuk hands-on learning dan side projects. AWS untuk kerjaya dan production workloads. Sebabnya simple. DigitalOcean tidak overwhelming, dan AWS adalah standard industri.

Perbandingan Ringkas

Feature AWS GCP Azure DigitalOcean
Jumlah services 200+ 100+ 200+ 20+
Free tier 12 bulan 12 bulan + always free 12 bulan $200 credit 60 hari
Pricing complexity Tinggi Sederhana Tinggi Rendah
Learning curve Steep Moderate Steep Easy
Job market Terbesar Growing Enterprise Niche
Region terdekat Singapore Singapore, Jakarta Singapore Singapore

Satu tip penting. Jangan terlalu worry tentang memilih provider yang “betul”. Skill cloud anda transferable antara providers. Kalau anda faham konsep VPC di AWS, anda boleh setup VPC di GCP atau Azure juga. Yang berbeza cuma interface dan naming.

Konsep Cloud Yang Penting

Sebelum deploy apa-apa ke cloud, anda perlu faham beberapa konsep asas.

VPC (Virtual Private Cloud)

VPC adalah virtual network anda dalam cloud. Bayangkan ia macam network rumah anda, tapi di cloud. Anda control IP ranges, subnets, routing, dan firewall rules.

VPC: 10.0.0.0/16
  ├── Public Subnet: 10.0.1.0/24   (web servers, load balancers)
  ├── Private Subnet: 10.0.2.0/24  (app servers)
  └── Database Subnet: 10.0.3.0/24 (databases, caches)

Best practice: letak web servers dalam public subnet (boleh diakses dari internet), app servers dan databases dalam private subnet (hanya boleh diakses dari dalam VPC).

IAM (Identity and Access Management)

IAM control siapa boleh buat apa dalam cloud account anda. Ini critical untuk security.

Principles: - Principle of least privilege. Beri hanya permission yang diperlukan, jangan lebih. - Jangan guna root account untuk daily tasks. - Enable MFA (Multi-Factor Authentication) untuk semua users. - Guna roles dan policies, bukan hardcode credentials.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:PutObject"
      ],
      "Resource": "arn:aws:s3:::my-app-bucket/*"
    }
  ]
}

Policy di atas hanya allow read dan write ke specific S3 bucket. Tidak boleh delete, tidak boleh access bucket lain. Ini contoh least privilege.

Load Balancer

Load balancer distribute traffic ke multiple servers. Kalau satu server down, traffic automatically pergi ke server yang lain.

Jenis load balancer: - Application Load Balancer (ALB). Layer 7, faham HTTP. Boleh route berdasarkan URL path atau hostname. - Network Load Balancer (NLB). Layer 4, handle millions of requests per second. Untuk TCP/UDP traffic.

Managed Services

Managed services bermaksud cloud provider handle maintenance, patching, backups, dan scaling untuk anda.

Contoh: - Managed Database. RDS (AWS), Cloud SQL (GCP). Anda tidak perlu worry tentang database backups, patching, atau replication. - Managed Kubernetes. EKS (AWS), GKE (GCP). Control plane di-manage oleh provider. - Managed Cache. ElastiCache (AWS). Redis atau Memcached yang di-manage.

Managed services kos lebih mahal daripada self-hosted, tapi ia save masa dan reduce operational burden. Untuk team kecil, managed services biasanya berbaloi.

Nota Beginner: Mulakan dengan managed services dan hanya self-host kalau anda ada sebab yang kukuh (cost, compliance, specific requirements). Masa engineer lebih mahal daripada kos managed services.

Cost Management

Bil cloud boleh jadi sangat mahal kalau anda tidak careful. Berikut beberapa tips.

Set budget alerts. Semua cloud providers ada billing alerts. Set alert pada 50%, 80%, dan 100% budget anda. Jangan tunggu sampai hujung bulan untuk check bil.

Guna right-sizing. Jangan guna instance besar kalau workload anda kecil. Monitor actual usage dan downsize kalau perlu.

Shutdown apa yang tidak digunakan. Development servers tidak perlu running 24/7. Automate start/stop schedule.

Guna reserved instances atau savings plans. Kalau anda tahu workload akan consistent selama setahun atau lebih, reserved pricing boleh save 30% hingga 60%.

Monitor daily. Install tools macam Infracost untuk Terraform. Ia tunjuk cost estimate sebelum anda provision resources.

# Install Infracost
brew install infracost

# Generate cost estimate dari Terraform code
infracost breakdown --path .

Output:

Name                                     Monthly Qty  Unit    Monthly Cost

aws_instance.web
├─ Instance usage (t3.small)                     730  hours         $18.98
└─ root_block_device
   └─ Storage (gp3, 20 GB)                       20  GB             $1.60

aws_db_instance.main
├─ Database instance (db.t3.micro)               730  hours         $14.98
└─ Storage (gp2, 20 GB)                          20  GB             $2.30

OVERALL TOTAL                                                      $37.86

Nota Beginner: Rule of thumb saya: kalau anda belajar, gunakan free tier. Kalau anda test, gunakan smallest instance. Kalau anda production, size berdasarkan actual load testing, bukan guessing.

Contoh Monthly Cost Estimation

Untuk memberi gambaran, ini anggaran kos bulanan untuk setup web app yang typical:

Setup minimum (side project): - 1x DigitalOcean Droplet (s-1vcpu-1gb): $6/bulan - Managed database (basic): $15/bulan - Domain name: ~$1/bulan - Total: sekitar $22/bulan

Setup production (small business): - 2x AWS EC2 t3.small: ~$38/bulan - 1x RDS db.t3.micro: ~$15/bulan - Application Load Balancer: ~$18/bulan - S3 storage (10GB): ~$0.25/bulan - CloudFront CDN: ~$5/bulan - Total: sekitar $76/bulan

Bandingkan dengan homelab di mana anda mungkin bayar RM50 hingga RM100 lebihan bil elektrik sahaja. Untuk learning dan development, homelab jauh lebih jimat. Tapi untuk serve real users dengan reliability, cloud berbaloi dengan kosnya.

Tagging Resources

Satu practice yang ramai orang abaikan tapi sangat penting untuk cost management. Tag semua cloud resources anda.

resource "aws_instance" "web" {
  # ... config ...

  tags = {
    Name        = "web-production"
    Environment = "production"
    Project     = "mywebapp"
    Owner       = "syafi"
    CostCenter  = "engineering"
  }
}

Dengan tags, anda boleh filter bil mengikut project, environment, atau team. Ini sangat membantu bila anda ada multiple projects dan mahu tahu project mana yang paling mahal.

Pendekatan Hybrid: Homelab + Cloud

Anda tidak perlu pilih satu sahaja. Ramai DevOps engineers guna pendekatan hybrid.

Homelab untuk: - Development dan testing. - CI/CD runners (save cloud compute cost). - Internal tools (wiki, password manager, file sharing). - Learning dan experimentation. - Media server dan personal apps.

Cloud untuk: - Production-facing applications. - APIs yang perlu low latency globally. - Services yang perlu high availability. - Workloads yang perlu elastic scaling.

Architecture contoh hybrid:

                    Internet
                       |
                 [Cloud - DO/AWS]
                 ├── Load Balancer
                 ├── Web App (Production)
                 ├── Database (Managed)
                 └── CDN
                       |
              VPN Tunnel (WireGuard)
                       |
                 [Homelab]
                 ├── GitLab / Gitea
                 ├── CI/CD Runners
                 ├── Dev/Staging Environment
                 ├── Monitoring (Grafana)
                 └── Backup Storage

WireGuard VPN connect homelab dan cloud securely. CI/CD runners di homelab buat builds dan push ke cloud deployment. Monitoring di homelab observe kedua-dua environments.

Practical: Deploy App ke DigitalOcean

Jom deploy web app ke DigitalOcean step by step.

Step 1: Setup DigitalOcean Account

  1. Daftar di digitalocean.com.
  2. Buat API token di Settings > API > Generate New Token.
  3. Install doctl CLI.
# macOS
brew install doctl

# Login
doctl auth init
# Paste your API token

Step 2: Provision Dengan Terraform

Buat file main.tf:

terraform {
  required_providers {
    digitalocean = {
      source  = "digitalocean/digitalocean"
      version = "~> 2.0"
    }
  }
}

provider "digitalocean" {
  token = var.do_token
}

variable "do_token" {
  type      = string
  sensitive = true
}

variable "ssh_key_fingerprint" {
  type = string
}

resource "digitalocean_droplet" "web" {
  image    = "ubuntu-24-04-x64"
  name     = "web-production"
  region   = "sgp1"
  size     = "s-1vcpu-1gb"
  ssh_keys = [var.ssh_key_fingerprint]

  tags = ["web", "production"]

  user_data = <<-EOF
    #!/bin/bash
    apt-get update
    apt-get install -y docker.io docker-compose-plugin
    systemctl enable docker
    systemctl start docker
    ufw allow 22/tcp
    ufw allow 80/tcp
    ufw allow 443/tcp
    ufw --force enable
  EOF
}

resource "digitalocean_firewall" "web" {
  name        = "web-firewall"
  droplet_ids = [digitalocean_droplet.web.id]

  inbound_rule {
    protocol         = "tcp"
    port_range       = "22"
    source_addresses = ["0.0.0.0/0"]
  }

  inbound_rule {
    protocol         = "tcp"
    port_range       = "80"
    source_addresses = ["0.0.0.0/0"]
  }

  inbound_rule {
    protocol         = "tcp"
    port_range       = "443"
    source_addresses = ["0.0.0.0/0"]
  }

  outbound_rule {
    protocol              = "tcp"
    port_range            = "1-65535"
    destination_addresses = ["0.0.0.0/0"]
  }

  outbound_rule {
    protocol              = "udp"
    port_range            = "1-65535"
    destination_addresses = ["0.0.0.0/0"]
  }
}

resource "digitalocean_domain" "default" {
  name = "myapp.com"
}

resource "digitalocean_record" "web" {
  domain = digitalocean_domain.default.id
  type   = "A"
  name   = "@"
  value  = digitalocean_droplet.web.ipv4_address
}

output "web_ip" {
  value = digitalocean_droplet.web.ipv4_address
}
terraform init
terraform plan
terraform apply

Step 3: Deploy App Dengan Ansible

Selepas Terraform provision server, guna Ansible untuk deploy app.

Buat deploy-playbook.yml:

---
- name: Deploy Web App
  hosts: production
  become: yes

  vars:
    app_name: "mywebapp"
    app_image: "ghcr.io/myuser/mywebapp:latest"
    app_port: 3000
    domain: "myapp.com"

  tasks:
    - name: Create app directory
      file:
        path: "/opt/{{ app_name }}"
        state: directory
        mode: "0755"

    - name: Copy docker-compose file
      template:
        src: templates/docker-compose.prod.yml.j2
        dest: "/opt/{{ app_name }}/docker-compose.yml"

    - name: Copy Nginx config
      template:
        src: templates/nginx.prod.conf.j2
        dest: "/opt/{{ app_name }}/nginx.conf"

    - name: Login to container registry
      docker_login:
        registry: ghcr.io
        username: "{{ registry_user }}"
        password: "{{ registry_token }}"

    - name: Pull latest image
      docker_image:
        name: "{{ app_image }}"
        source: pull
        force_source: yes

    - name: Start services
      community.docker.docker_compose_v2:
        project_src: "/opt/{{ app_name }}"
        state: present
        pull: "always"

    - name: Install Certbot and get SSL certificate
      shell: |
        snap install --classic certbot
        certbot --nginx -d {{ domain }} --non-interactive --agree-tos -m admin@{{ domain }}
      args:
        creates: "/etc/letsencrypt/live/{{ domain }}"

Template templates/docker-compose.prod.yml.j2:

services:
  app:
    image: {{ app_image }}
    container_name: {{ app_name }}
    restart: unless-stopped
    environment:
      - NODE_ENV=production
      - PORT={{ app_port }}
    ports:
      - "{{ app_port }}:{{ app_port }}"

  nginx:
    image: nginx:alpine
    container_name: nginx
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf
      - /etc/letsencrypt:/etc/letsencrypt:ro
    depends_on:
      - app

Deploy:

ansible-playbook -i "DROPLET_IP," deploy-playbook.yml

Practical: Deploy ke AWS

Untuk AWS, prosesnya similar tapi dengan lebih services involved.

Setup AWS CLI

# Install
brew install awscli

# Configure
aws configure
# Masukkan Access Key ID, Secret Access Key, region (ap-southeast-1)

Terraform Untuk AWS

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = "ap-southeast-1"
}

# VPC
resource "aws_vpc" "main" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_hostnames = true

  tags = {
    Name = "production-vpc"
  }
}

resource "aws_subnet" "public" {
  vpc_id                  = aws_vpc.main.id
  cidr_block              = "10.0.1.0/24"
  availability_zone       = "ap-southeast-1a"
  map_public_ip_on_launch = true

  tags = {
    Name = "public-subnet"
  }
}

resource "aws_internet_gateway" "main" {
  vpc_id = aws_vpc.main.id
}

resource "aws_route_table" "public" {
  vpc_id = aws_vpc.main.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.main.id
  }
}

resource "aws_route_table_association" "public" {
  subnet_id      = aws_subnet.public.id
  route_table_id = aws_route_table.public.id
}

# Security Group
resource "aws_security_group" "web" {
  name   = "web-sg"
  vpc_id = aws_vpc.main.id

  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

# EC2 Instance
resource "aws_instance" "web" {
  ami                    = "ami-0f935a2ecd3a7bd5c"
  instance_type          = "t3.small"
  key_name               = "my-key-pair"
  subnet_id              = aws_subnet.public.id
  vpc_security_group_ids = [aws_security_group.web.id]

  root_block_device {
    volume_size = 20
    volume_type = "gp3"
  }

  tags = {
    Name        = "web-production"
    Environment = "production"
  }
}

output "public_ip" {
  value = aws_instance.web.public_ip
}

Ini adalah setup basic AWS. Dalam real production, anda biasanya akan tambah Application Load Balancer, Auto Scaling Group, RDS untuk database, dan S3 untuk static assets. Tapi untuk start, ini sudah mencukupi.

terraform init
terraform plan
terraform apply

Selepas instance running, guna Ansible untuk configure dan deploy app sama macam contoh DigitalOcean di atas.

Cloud Certifications Yang Berbaloi

Certifications membantu validate knowledge anda dan membuka peluang kerjaya. Berikut certifications yang saya recommend.

AWS Solutions Architect Associate (SAA-C03)

Ini adalah certification cloud paling popular. Ia cover architectural best practices, core AWS services, security, dan cost optimization. Sangat berbaloi untuk sesiapa yang mahu masuk cloud engineering.

Tips preparation: - Belajar 2 hingga 3 bulan secara konsisten. - Guna kursus dari Adrian Cantrill atau Stephane Maarek. - Buat hands-on labs, bukan sekadar baca. - Practice exams dari Tutorial Dojo sangat membantu.

Certified Kubernetes Administrator (CKA)

Kalau anda target DevOps atau platform engineering role, CKA sangat valuable. Ia adalah hands-on exam di mana anda perlu solve real Kubernetes problems.

Tips preparation: - Setup Kubernetes cluster di homelab anda untuk practice. - Kursus dari Mumshad Mannambeth di KodeKloud sangat bagus. - Practice dengan killer.sh (percuma bila register exam). - Belajar kubectl shortcuts. Masa sangat terhad dalam exam.

Certification Path Yang Saya Recommend

  1. AWS Cloud Practitioner kalau anda benar-benar baru dengan cloud. Ia basic tapi foundation yang baik.
  2. AWS Solutions Architect Associate sebagai certification utama.
  3. CKA kalau anda banyak kerja dengan containers dan Kubernetes.
  4. Terraform Associate kalau anda banyak guna Terraform (ini relatively easy).

Nota Beginner: Certification bukan pengganti pengalaman. Ia melengkapi. Kalau anda ada homelab experience dan certifications, anda sudah ahead of banyak candidates. Employers value combination hands-on skills dan validated knowledge.

Ringkasan

Perjalanan dari homelab ke cloud bukan lompatan yang besar kalau anda sudah ada foundation yang betul. Konsep networking, Linux administration, containers, dan IaC yang anda belajar di homelab semua applicable di cloud.

Perkara penting yang anda belajar:

  • Cloud diperlukan untuk reliability, scalability, dan global reach yang homelab tidak boleh provide.
  • DigitalOcean paling sesuai untuk belajar dan small projects. AWS paling sesuai untuk kerjaya dan production.
  • Faham konsep VPC, IAM, load balancer, dan managed services sebelum deploy ke cloud.
  • Cost management sangat penting. Set budget alerts, right-size instances, dan shutdown apa yang tidak digunakan.
  • Pendekatan hybrid (homelab + cloud) adalah pragmatic. Guna homelab untuk development, cloud untuk production.
  • Terraform dan Ansible yang anda belajar di Bab 6 boleh digunakan untuk provision dan configure cloud resources.
  • Cloud certifications (AWS SAA, CKA) membantu validate knowledge dan buka peluang kerjaya. Combine dengan hands-on homelab experience untuk impact maximum.

Anda tidak perlu tinggalkan homelab anda. Jadikan ia lab R&D peribadi dan gunakan cloud untuk serve dunia.

Satu nasihat terakhir. Jangan tunggu sampai anda rasa “ready” untuk mula dengan cloud. Buat akaun free tier, deploy satu app kecil, dan belajar dari situ. Pengalaman hands-on lebih berharga daripada membaca 10 buku tentang cloud architecture. Homelab anda sudah beri anda foundation yang kukuh. Sekarang masanya untuk expand horizon anda ke cloud.

Bab 9: DevSecOps - Security Dalam Pipeline DevOps

Bila kita bercakap tentang DevOps, ramai yang fokus pada speed. Deploy laju, iterate cepat, automate semua benda. Tapi ada satu perkara yang selalu jadi mangsa bila kita terlalu ghairah dengan kelajuan: security.

Saya pernah buat kesilapan ini. Satu projek kecil, saya push code terus ke production tanpa scan apa-apa. Dua minggu kemudian, saya perasan ada API key yang ter-commit dalam repo. Nasib baik projek tu kecil dan API key tu boleh di-revoke dengan cepat. Tapi bayangkan kalau itu berlaku dalam production system yang handle data pelanggan.

DevSecOps bukan sekadar buzzword. Ia adalah pendekatan di mana security menjadi tanggungjawab semua orang dalam team, bukan hanya security team sahaja. Dan yang paling penting, security dimasukkan dari awal proses development, bukan ditambah di hujung sebagai afterthought.

Apa yang anda akan belajar:

  • Konsep shift-left security dan kenapa ia penting
  • SAST tools untuk scan source code
  • DAST tools untuk scan running applications
  • Container image scanning dengan Trivy dan Grype
  • Secrets management yang betul
  • Supply chain security dan SBOM
  • Compliance as code
  • Hands-on: Tambah security scanning dalam CI/CD pipeline

Shift-Left Security: Tangkap Masalah Lebih Awal

Dalam model tradisional, security testing berlaku di hujung sekali. Developer tulis code, QA test, kemudian baru security team buat assessment. Masalahnya? Kalau jumpa vulnerability di hujung, kos untuk fix adalah sangat tinggi. Code dah deploy, dependencies dah banyak, dan kadang-kadang kena redesign seluruh architecture.

Shift-left bermaksud kita gerakkan security testing ke kiri dalam timeline development. Lebih awal kita tangkap masalah, lebih murah dan mudah untuk fix.

Bayangkan macam ini: lebih senang repair foundation rumah sebelum rumah siap dibina, berbanding selepas rumah dah siap dan orang dah duduk di dalamnya.

Nota Beginner: Shift-left bukan bermaksud security team tak diperlukan lagi. Ia bermaksud developer juga ambil tanggungjawab untuk security, dan tools automatik membantu tangkap masalah common sebelum code sampai ke production.

Dalam konteks CI/CD pipeline, shift-left security bermaksud kita tambah security checks pada setiap stage:

  1. Code commit - Pre-commit hooks untuk scan secrets
  2. Build stage - SAST scanning pada source code
  3. Test stage - DAST scanning pada running application
  4. Package stage - Container image scanning
  5. Deploy stage - Infrastructure security checks

SAST: Static Application Security Testing

SAST tools menganalisis source code tanpa menjalankan aplikasi. Ia scan code anda untuk mencari patterns yang dikenali sebagai vulnerability. Contohnya SQL injection, cross-site scripting (XSS), atau penggunaan cryptographic functions yang lemah.

SonarQube

SonarQube adalah salah satu SAST tool yang paling popular. Ia bukan sahaja scan untuk security issues, tetapi juga code quality, bugs, dan code smells.

Untuk setup SonarQube dalam homelab, anda boleh gunakan Docker:

# docker-compose.yml
version: '3'
services:
  sonarqube:
    image: sonarqube:community
    ports:
      - "9000:9000"
    environment:
      - SONAR_JDBC_URL=jdbc:postgresql://db:5432/sonar
      - SONAR_JDBC_USERNAME=sonar
      - SONAR_JDBC_PASSWORD=sonar
    volumes:
      - sonarqube_data:/opt/sonarqube/data
      - sonarqube_extensions:/opt/sonarqube/extensions
    depends_on:
      - db

  db:
    image: postgres:15
    environment:
      - POSTGRES_USER=sonar
      - POSTGRES_PASSWORD=sonar
      - POSTGRES_DB=sonar
    volumes:
      - postgresql_data:/var/lib/postgresql/data

volumes:
  sonarqube_data:
  sonarqube_extensions:
  postgresql_data:

Selepas SonarQube running, anda boleh integrate dengan CI/CD pipeline. Dalam GitHub Actions, tambahkan step ini:

- name: SonarQube Scan
  uses: sonarqube-quality-gate-action@v1
  env:
    SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
    SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}

Semgrep

Semgrep adalah SAST tool yang lebih ringan dan mudah digunakan. Ia menggunakan pattern-based approach dan mempunyai banyak community rules. Saya suka Semgrep kerana ia cepat dan boleh run secara local tanpa perlu server.

# Install Semgrep
pip install semgrep

# Scan project dengan default rules
semgrep --config auto .

# Scan dengan specific ruleset
semgrep --config p/owasp-top-ten .
semgrep --config p/secrets .

Dalam CI/CD pipeline:

- name: Semgrep Scan
  uses: returntocorp/semgrep-action@v1
  with:
    config: >-
      p/security-audit
      p/secrets
      p/owasp-top-ten

Nota Beginner: Mula dengan Semgrep kalau anda baru dalam SAST. Ia lebih mudah di-setup, cepat, dan free. SonarQube bagus untuk team yang lebih besar dan perlukan dashboard serta tracking over time.

DAST: Dynamic Application Security Testing

Kalau SAST scan source code, DAST pula scan running application. Ia bertindak seperti attacker sebenar, menghantar pelbagai jenis request ke application anda untuk mencari vulnerability.

OWASP ZAP

OWASP ZAP (Zed Attack Proxy) adalah DAST tool open-source yang paling popular. Ia boleh digunakan secara manual melalui GUI, atau secara automatik dalam CI/CD pipeline.

# Dalam GitHub Actions
- name: OWASP ZAP Scan
  uses: zaproxy/action-full-scan@v0.7.0
  with:
    target: 'https://staging.myapp.com'
    rules_file_name: '.zap/rules.tsv'
    cmd_options: '-a'

Untuk baseline scan yang lebih cepat:

- name: OWASP ZAP Baseline Scan
  uses: zaproxy/action-baseline@v0.7.0
  with:
    target: 'https://staging.myapp.com'

Satu tip penting: jalankan DAST scan terhadap staging environment, bukan production. DAST scan boleh menjadi agresif dan mungkin menyebabkan side effects pada application anda.

Container Image Scanning

Bila anda menggunakan containers, anda bukan sahaja perlu scan application code, tetapi juga container images. Base images mungkin mengandungi vulnerability yang sudah diketahui. Libraries yang di-install mungkin sudah outdated.

Trivy

Trivy daripada Aqua Security adalah container scanner yang saya paling suka. Ia cepat, comprehensive, dan mudah digunakan.

# Scan container image
trivy image myapp:latest

# Scan dengan severity filter
trivy image --severity HIGH,CRITICAL myapp:latest

# Scan filesystem
trivy fs --security-checks vuln,secret,config .

# Scan Kubernetes cluster
trivy k8s --report summary cluster

Dalam CI/CD pipeline:

- name: Build Image
  run: docker build -t myapp:${{ github.sha }} .

- name: Trivy Scan
  uses: aquasecurity/trivy-action@master
  with:
    image-ref: 'myapp:${{ github.sha }}'
    format: 'sarif'
    output: 'trivy-results.sarif'
    severity: 'CRITICAL,HIGH'
    exit-code: '1'

Setting exit-code: '1' bermaksud pipeline akan gagal jika jumpa vulnerability dengan severity HIGH atau CRITICAL. Ini penting untuk memastikan images yang vulnerable tidak sampai ke production.

Grype

Grype daripada Anchore adalah alternatif kepada Trivy. Ia juga cepat dan mempunyai database vulnerability yang comprehensive.

# Install Grype
curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s

# Scan image
grype myapp:latest

# Scan dengan output format
grype myapp:latest -o json > grype-results.json

# Fail jika jumpa severity tertentu
grype myapp:latest --fail-on high

Nota Beginner: Pilih satu sahaja untuk mula. Trivy atau Grype, kedua-duanya bagus. Anda boleh cuba kedua-dua dan pilih yang mana anda lebih selesa. Yang penting ialah anda ada scanning dalam pipeline.

Secrets Management

Ini adalah area di mana saya pernah buat kesilapan, dan saya pasti ramai developer lain juga pernah. Hardcode password dalam code, commit API keys ke Git, atau simpan credentials dalam plain text config files.

HashiCorp Vault

Vault adalah gold standard untuk secrets management. Ia menyimpan secrets secara encrypted, boleh generate dynamic secrets, dan mempunyai audit log yang detailed.

# Start Vault server (dev mode untuk belajar)
vault server -dev

# Simpan secret
vault kv put secret/myapp/database \
  username="dbuser" \
  password="supersecretpassword"

# Baca secret
vault kv get secret/myapp/database

Dalam Kubernetes, anda boleh gunakan Vault Agent Injector untuk automatically inject secrets ke dalam pods:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  template:
    metadata:
      annotations:
        vault.hashicorp.com/agent-inject: "true"
        vault.hashicorp.com/role: "myapp"
        vault.hashicorp.com/agent-inject-secret-db-creds: "secret/data/myapp/database"
    spec:
      serviceAccountName: myapp
      containers:
        - name: myapp
          image: myapp:latest

Sealed Secrets

Kalau anda menggunakan GitOps (dan anda patut!), ada satu masalah. Macam mana nak simpan secrets dalam Git repo kalau secrets tu sensitive? Sealed Secrets daripada Bitnami menyelesaikan masalah ini.

Sealed Secrets encrypt secrets menggunakan public key, dan hanya controller dalam cluster yang ada private key untuk decrypt.

# Install kubeseal CLI
brew install kubeseal

# Create sealed secret
kubectl create secret generic myapp-secret \
  --from-literal=password=supersecret \
  --dry-run=client -o yaml | \
  kubeseal --format yaml > sealed-secret.yaml

File sealed-secret.yaml yang dihasilkan boleh di-commit ke Git dengan selamat kerana ia encrypted. Hanya Sealed Secrets controller dalam cluster anda yang boleh decrypt ia.

Supply Chain Security

Supply chain attacks semakin menjadi ancaman besar. Ingat insiden SolarWinds? Atau log4j? Anda perlu memastikan bahawa semua components dalam supply chain anda boleh dipercayai.

Container Image Signing

Gunakan Cosign untuk sign dan verify container images:

# Install Cosign
brew install cosign

# Generate key pair
cosign generate-key-pair

# Sign image
cosign sign --key cosign.key myregistry/myapp:v1.0

# Verify image
cosign verify --key cosign.pub myregistry/myapp:v1.0

SBOM (Software Bill of Materials)

SBOM adalah senarai lengkap semua components dalam software anda. Ia seperti senarai ramuan pada produk makanan. Jika ada vulnerability baru ditemui dalam sesuatu library, anda boleh dengan cepat check sama ada software anda terjejas.

# Generate SBOM dengan Syft
syft myapp:latest -o spdx-json > sbom.json

# Scan SBOM untuk vulnerabilities
grype sbom:sbom.json

Compliance as Code

Dalam banyak industri, ada compliance requirements yang perlu dipatuhi. Daripada manually check compliance, anda boleh automate ia menggunakan tools seperti Open Policy Agent (OPA).

# policy.rego - Pastikan containers tidak run sebagai root
package kubernetes.admission

deny[msg] {
  input.request.kind.kind == "Pod"
  container := input.request.object.spec.containers[_]
  not container.securityContext.runAsNonRoot
  msg := sprintf("Container '%v' mesti run sebagai non-root", [container.name])
}

deny[msg] {
  input.request.kind.kind == "Pod"
  container := input.request.object.spec.containers[_]
  not container.resources.limits
  msg := sprintf("Container '%v' mesti ada resource limits", [container.name])
}

Nota Beginner: Compliance as code nampak advanced, tetapi konsepnya simple. Anda tulis rules dalam code, dan system akan automatically check sama ada infrastructure anda comply dengan rules tersebut. Mula dengan rules yang simple dulu.

Hands-On: Security Scanning Dalam CI/CD Pipeline

Sekarang mari kita gabungkan semua yang kita belajar. Berikut adalah contoh complete CI/CD pipeline dengan security scanning di setiap stage:

name: Secure CI/CD Pipeline

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  secret-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Scan for secrets
        uses: trufflesecurity/trufflehog@main
        with:
          extra_args: --only-verified

  sast:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Semgrep SAST Scan
        uses: returntocorp/semgrep-action@v1
        with:
          config: >-
            p/security-audit
            p/secrets
            p/owasp-top-ten

  build-and-scan:
    needs: [secret-scan, sast]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Build Docker Image
        run: docker build -t myapp:${{ github.sha }} .

      - name: Trivy Image Scan
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: 'myapp:${{ github.sha }}'
          format: 'table'
          severity: 'CRITICAL,HIGH'
          exit-code: '1'

      - name: Generate SBOM
        uses: anchore/sbom-action@v0
        with:
          image: myapp:${{ github.sha }}
          format: spdx-json
          output-file: sbom.json

      - name: Push Image
        if: github.ref == 'refs/heads/main'
        run: |
          docker tag myapp:${{ github.sha }} myregistry/myapp:${{ github.sha }}
          docker push myregistry/myapp:${{ github.sha }}

  dast:
    needs: [build-and-scan]
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    steps:
      - name: Deploy to Staging
        run: echo "Deploy to staging environment"

      - name: OWASP ZAP Baseline Scan
        uses: zaproxy/action-baseline@v0.7.0
        with:
          target: 'https://staging.myapp.com'

  deploy:
    needs: [dast]
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    steps:
      - name: Deploy to Production
        run: echo "Deploy to production"

Pipeline ini menjalankan: 1. Secret scanning menggunakan TruffleHog untuk pastikan tiada secrets yang ter-commit 2. SAST scanning menggunakan Semgrep untuk scan source code 3. Container image scanning menggunakan Trivy 4. SBOM generation untuk supply chain visibility 5. DAST scanning menggunakan OWASP ZAP terhadap staging

Setiap stage bertindak sebagai quality gate. Jika mana-mana scan gagal, pipeline berhenti dan code tidak sampai ke production.

Ringkasan

DevSecOps bukan tentang menambah kerja. Ia tentang mengautomasikan security supaya ia berlaku secara konsisten tanpa memperlahankan development process secara ketara.

Perkara utama yang perlu diingat:

  • Shift-left security bermaksud tangkap masalah lebih awal. Lebih awal anda tangkap, lebih murah untuk fix.
  • SAST tools seperti SonarQube dan Semgrep scan source code untuk vulnerability.
  • DAST tools seperti OWASP ZAP scan running applications seperti seorang attacker.
  • Container scanning dengan Trivy atau Grype pastikan images anda tidak ada known vulnerabilities.
  • Secrets management menggunakan Vault atau Sealed Secrets, bukan hardcoded credentials.
  • Supply chain security melalui image signing dan SBOM memberikan visibility terhadap semua components.
  • Compliance as code mengautomasikan security policies.

Anda tidak perlu implement semua ini sekaligus. Mulakan dengan satu atau dua tools, dan tambahkan secara berperingkat. Yang penting ialah anda mula memikirkan security sebagai bahagian integral dalam DevOps workflow anda, bukan sesuatu yang ditambah kemudian.

Security bukan destination, ia adalah journey. Dan dengan DevSecOps, journey itu menjadi sebahagian daripada setiap commit, setiap build, dan setiap deployment.

Bab 10: Projek Praktikal - Bina Sendiri, Belajar Sendiri

Teori tanpa praktikal ibarat membaca resipi tanpa pernah memasak. Anda mungkin faham konsepnya, tapi tangan anda belum pernah rasa prosesnya. Bab ini adalah di mana anda benar-benar “masak” sendiri.

Saya sediakan 5 projek praktikal yang boleh anda bina dalam homelab. Setiap projek direka untuk mengukuhkan kemahiran yang anda telah pelajari sepanjang buku ini. Anda boleh buat projek-projek ini secara berurutan, atau pilih mana-mana yang paling menarik minat anda.

Satu nasihat: jangan hanya copy-paste. Cuba fahamkan setiap baris configuration. Bila ada error, baca error message tu dengan teliti. Proses troubleshooting itu sendiri adalah guru yang paling berkesan.

Apa yang anda akan belajar:

  • Membina full CI/CD pipeline untuk web application
  • Setup GitOps dengan ArgoCD
  • Infrastructure provisioning dengan Terraform dan Ansible
  • Complete monitoring stack
  • Multi-environment setup untuk dev, staging, dan production

Projek 1: Full CI/CD Pipeline untuk Web App

Apa yang projek ini ajar

Projek ini menggabungkan hampir semua yang anda belajar: version control, containerization, CI/CD, dan deployment ke Kubernetes. Anda akan bina satu pipeline yang lengkap dari code push hingga deployment.

Architecture

Developer Push Code
        |
        v
  GitHub Repository
        |
        v
  GitHub Actions (CI)
  - Run tests
  - Build Docker image
  - Push to registry
  - Security scan
        |
        v
  Docker Registry
  (GitHub Container Registry)
        |
        v
  K3s Cluster (CD)
  - Pull image
  - Deploy application
  - Health check

Langkah-langkah

1. Sediakan sample web application

Kita akan guna satu Node.js app yang simple. Buat repository baru dan tambah files berikut:

// app.js
const express = require('express');
const app = express();
const port = process.env.PORT || 3000;

app.get('/', (req, res) => {
  res.json({
    message: 'Hello from DevOps Pipeline!',
    version: process.env.APP_VERSION || '1.0.0',
    environment: process.env.NODE_ENV || 'development'
  });
});

app.get('/health', (req, res) => {
  res.status(200).json({ status: 'healthy' });
});

app.listen(port, () => {
  console.log(`App running on port ${port}`);
});
// package.json
{
  "name": "devops-demo-app",
  "version": "1.0.0",
  "scripts": {
    "start": "node app.js",
    "test": "jest"
  },
  "dependencies": {
    "express": "^4.18.2"
  },
  "devDependencies": {
    "jest": "^29.7.0"
  }
}

2. Buat Dockerfile

FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

FROM node:20-alpine
WORKDIR /app
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
COPY --from=builder /app/node_modules ./node_modules
COPY app.js .
USER appuser
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=3s \
  CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
CMD ["node", "app.js"]

3. Buat GitHub Actions workflow

# .github/workflows/ci-cd.yml
name: CI/CD Pipeline

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: npm ci
      - run: npm test

  build-and-push:
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    permissions:
      contents: read
      packages: write
    steps:
      - uses: actions/checkout@v4

      - name: Log in to Container Registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and Push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: |
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest

      - name: Trivy Scan
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}'
          severity: 'CRITICAL,HIGH'

  deploy:
    needs: build-and-push
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    steps:
      - uses: actions/checkout@v4

      - name: Deploy to K3s
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.K3S_HOST }}
          username: ${{ secrets.K3S_USER }}
          key: ${{ secrets.K3S_SSH_KEY }}
          script: |
            kubectl set image deployment/devops-demo \
              app=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} \
              -n production
            kubectl rollout status deployment/devops-demo -n production

4. Buat Kubernetes manifests

# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: devops-demo
  namespace: production
spec:
  replicas: 2
  selector:
    matchLabels:
      app: devops-demo
  template:
    metadata:
      labels:
        app: devops-demo
    spec:
      containers:
        - name: app
          image: ghcr.io/yourusername/devops-demo:latest
          ports:
            - containerPort: 3000
          env:
            - name: NODE_ENV
              value: "production"
          readinessProbe:
            httpGet:
              path: /health
              port: 3000
            initialDelaySeconds: 5
          livenessProbe:
            httpGet:
              path: /health
              port: 3000
            initialDelaySeconds: 10
          resources:
            requests:
              memory: "64Mi"
              cpu: "50m"
            limits:
              memory: "128Mi"
              cpu: "100m"
---
apiVersion: v1
kind: Service
metadata:
  name: devops-demo
  namespace: production
spec:
  selector:
    app: devops-demo
  ports:
    - port: 80
      targetPort: 3000
  type: ClusterIP

Nota Beginner: Projek ini nampak banyak files, tapi setiap satu ada tujuan yang jelas. Mulakan dengan buat app dan Dockerfile dulu. Test secara local. Kemudian baru tambah CI/CD dan Kubernetes deployment. Jangan cuba buat semua sekali gus.


Projek 2: GitOps dengan ArgoCD

Apa yang projek ini ajar

GitOps adalah pendekatan di mana Git repository menjadi single source of truth untuk infrastructure dan application deployment. ArgoCD akan memantau repo anda dan automatically sync perubahan ke cluster.

Architecture

Git Repository (Config Repo)
  |
  |  ArgoCD monitors
  v
ArgoCD Server
  |
  |  Syncs desired state
  v
K3s Cluster
  - Namespace: dev
  - Namespace: staging
  - Namespace: production

Langkah-langkah

1. Install ArgoCD dalam K3s

# Buat namespace
kubectl create namespace argocd

# Install ArgoCD
kubectl apply -n argocd \
  -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

# Tunggu semua pods ready
kubectl wait --for=condition=ready pod \
  -l app.kubernetes.io/name=argocd-server \
  -n argocd --timeout=300s

# Dapatkan initial admin password
kubectl -n argocd get secret argocd-initial-admin-secret \
  -o jsonpath="{.data.password}" | base64 -d

# Port forward untuk akses UI
kubectl port-forward svc/argocd-server -n argocd 8080:443

2. Install ArgoCD CLI dan login

# Install CLI
brew install argocd

# Login
argocd login localhost:8080 --username admin --password <password> --insecure

3. Buat GitOps config repository

Buat repository baru yang khusus untuk Kubernetes manifests. Strukturnya:

gitops-config/
  apps/
    devops-demo/
      base/
        deployment.yaml
        service.yaml
        kustomization.yaml
      overlays/
        dev/
          kustomization.yaml
        staging/
          kustomization.yaml
        production/
          kustomization.yaml
# apps/devops-demo/base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - deployment.yaml
  - service.yaml
# apps/devops-demo/overlays/dev/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: dev
bases:
  - ../../base
patches:
  - patch: |-
      - op: replace
        path: /spec/replicas
        value: 1
    target:
      kind: Deployment
      name: devops-demo
# apps/devops-demo/overlays/production/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: production
bases:
  - ../../base
patches:
  - patch: |-
      - op: replace
        path: /spec/replicas
        value: 3
    target:
      kind: Deployment
      name: devops-demo

4. Buat ArgoCD Application

# argocd-app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: devops-demo-production
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/yourusername/gitops-config.git
    targetRevision: main
    path: apps/devops-demo/overlays/production
  destination:
    server: https://kubernetes.default.svc
    namespace: production
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true
kubectl apply -f argocd-app.yaml

Sekarang, setiap kali anda push perubahan ke gitops-config repository, ArgoCD akan automatically detect dan sync perubahan tersebut ke cluster. Cuba update image tag dalam deployment.yaml dan push. Perhatikan ArgoCD UI untuk melihat sync process berlaku secara automatik.

Nota Beginner: GitOps mungkin terasa over-engineered untuk projek kecil. Tapi bila anda bekerja dengan team dan multiple environments, ia menyelamatkan banyak masa dan mengurangkan human error. Setiap deployment ada audit trail dalam Git history.


Projek 3: Infrastructure Provisioning dengan Terraform + Ansible

Apa yang projek ini ajar

Terraform untuk provision infrastructure (create VMs, networks, storage) dan Ansible untuk configure infrastructure tersebut (install software, configure services). Kedua-duanya bersama adalah kombinasi yang sangat powerful.

Architecture

Terraform                    Ansible
(Infrastructure)             (Configuration)
    |                            |
    v                            v
Create VMs on Proxmox  --->  Install K3s
Create Networks        --->  Configure firewall
Create Storage         --->  Deploy monitoring
    |                            |
    v                            v
     Infrastructure Ready

Langkah-langkah

1. Terraform untuk Proxmox VMs

# main.tf
terraform {
  required_providers {
    proxmox = {
      source  = "telmate/proxmox"
      version = "3.0.1rc1"
    }
  }
}

provider "proxmox" {
  pm_api_url          = var.proxmox_api_url
  pm_api_token_id     = var.proxmox_token_id
  pm_api_token_secret = var.proxmox_token_secret
  pm_tls_insecure     = true
}

resource "proxmox_vm_qemu" "k3s_master" {
  name        = "k3s-master"
  target_node = "pve"
  clone       = "ubuntu-cloud-template"

  cores   = 2
  memory  = 4096
  scsihw  = "virtio-scsi-pci"

  disk {
    storage = "local-lvm"
    size    = "30G"
    type    = "scsi"
  }

  network {
    model  = "virtio"
    bridge = "vmbr0"
  }

  ipconfig0 = "ip=192.168.1.100/24,gw=192.168.1.1"
  sshkeys   = file("~/.ssh/id_rsa.pub")
}

resource "proxmox_vm_qemu" "k3s_worker" {
  count       = 2
  name        = "k3s-worker-${count.index + 1}"
  target_node = "pve"
  clone       = "ubuntu-cloud-template"

  cores   = 2
  memory  = 4096
  scsihw  = "virtio-scsi-pci"

  disk {
    storage = "local-lvm"
    size    = "30G"
    type    = "scsi"
  }

  network {
    model  = "virtio"
    bridge = "vmbr0"
  }

  ipconfig0 = "ip=192.168.1.10${count.index + 1}/24,gw=192.168.1.1"
  sshkeys   = file("~/.ssh/id_rsa.pub")
}

# Output IP addresses untuk Ansible inventory
output "master_ip" {
  value = proxmox_vm_qemu.k3s_master.default_ipv4_address
}

output "worker_ips" {
  value = proxmox_vm_qemu.k3s_worker[*].default_ipv4_address
}
# variables.tf
variable "proxmox_api_url" {
  description = "Proxmox API URL"
  type        = string
}

variable "proxmox_token_id" {
  description = "Proxmox API token ID"
  type        = string
  sensitive   = true
}

variable "proxmox_token_secret" {
  description = "Proxmox API token secret"
  type        = string
  sensitive   = true
}

2. Ansible untuk configure K3s

# inventory/hosts.yml
all:
  children:
    masters:
      hosts:
        k3s-master:
          ansible_host: 192.168.1.100
    workers:
      hosts:
        k3s-worker-1:
          ansible_host: 192.168.1.101
        k3s-worker-2:
          ansible_host: 192.168.1.102
  vars:
    ansible_user: ubuntu
    ansible_ssh_private_key_file: ~/.ssh/id_rsa
# playbooks/setup-k3s.yml
---
- name: Setup K3s Master
  hosts: masters
  become: yes
  tasks:
    - name: Install K3s master
      shell: |
        curl -sfL https://get.k3s.io | sh -s - \
          --write-kubeconfig-mode 644 \
          --disable traefik \
          --tls-san {{ ansible_host }}
      args:
        creates: /usr/local/bin/k3s

    - name: Get node token
      slurp:
        src: /var/lib/rancher/k3s/server/node-token
      register: node_token

    - name: Store token as fact
      set_fact:
        k3s_token: "{{ node_token.content | b64decode | trim }}"

- name: Setup K3s Workers
  hosts: workers
  become: yes
  vars:
    master_ip: "{{ hostvars['k3s-master']['ansible_host'] }}"
    k3s_token: "{{ hostvars['k3s-master']['k3s_token'] }}"
  tasks:
    - name: Install K3s worker
      shell: |
        curl -sfL https://get.k3s.io | K3S_URL=https://{{ master_ip }}:6443 \
          K3S_TOKEN={{ k3s_token }} sh -
      args:
        creates: /usr/local/bin/k3s-agent

3. Jalankan

# Provision VMs
cd terraform/
terraform init
terraform plan
terraform apply

# Configure K3s
cd ../ansible/
ansible-playbook -i inventory/hosts.yml playbooks/setup-k3s.yml

Nota Beginner: Projek ini memerlukan Proxmox server. Kalau anda belum ada, anda boleh adapt Terraform config untuk provider lain seperti DigitalOcean atau AWS. Konsep yang sama, cuma provider yang berbeza.


Projek 4: Complete Monitoring Stack

Apa yang projek ini ajar

Monitoring adalah mata dan telinga anda dalam production. Projek ini setup full observability stack: metrics dengan Prometheus, visualization dengan Grafana, logs dengan Loki, dan alerting dengan Alertmanager.

Architecture

Applications & Infrastructure
    |           |           |
    v           v           v
Prometheus   Loki     Node Exporter
(Metrics)    (Logs)   (System Metrics)
    |           |           |
    +-----+-----+-----------+
          |
          v
       Grafana
    (Dashboards)
          |
          v
    Alertmanager
    (Notifications)
       |      |
       v      v
    Slack   Email

Langkah-langkah

1. Install kube-prometheus-stack menggunakan Helm

# Add Helm repo
helm repo add prometheus-community \
  https://prometheus-community.github.io/helm-charts
helm repo update

# Buat namespace
kubectl create namespace monitoring

Buat values file untuk customize installation:

# monitoring-values.yaml
grafana:
  adminPassword: "YourSecurePassword123"
  ingress:
    enabled: true
    hosts:
      - grafana.homelab.local

  additionalDataSources:
    - name: Loki
      type: loki
      url: http://loki-gateway.monitoring.svc.cluster.local
      access: proxy

prometheus:
  prometheusSpec:
    retention: 30d
    storageSpec:
      volumeClaimTemplate:
        spec:
          accessModes: ["ReadWriteOnce"]
          resources:
            requests:
              storage: 50Gi

alertmanager:
  config:
    global:
      resolve_timeout: 5m
    route:
      group_by: ['alertname', 'namespace']
      group_wait: 10s
      group_interval: 5m
      repeat_interval: 4h
      receiver: 'slack'
      routes:
        - match:
            severity: critical
          receiver: 'slack'
          repeat_interval: 1h
    receivers:
      - name: 'slack'
        slack_configs:
          - api_url: 'https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK'
            channel: '#alerts'
            title: '{{ .GroupLabels.alertname }}'
            text: >-
              {{ range .Alerts }}
              *Alert:* {{ .Annotations.summary }}
              *Severity:* {{ .Labels.severity }}
              *Description:* {{ .Annotations.description }}
              {{ end }}
# Install stack
helm install monitoring prometheus-community/kube-prometheus-stack \
  -n monitoring \
  -f monitoring-values.yaml

2. Install Loki untuk logs

helm repo add grafana https://grafana.github.io/helm-charts

# Buat Loki values
cat <<EOF > loki-values.yaml
loki:
  auth_enabled: false
  commonConfig:
    replication_factor: 1
  storage:
    type: filesystem
singleBinary:
  replicas: 1
  persistence:
    size: 20Gi
EOF

helm install loki grafana/loki -n monitoring -f loki-values.yaml

3. Install Promtail untuk collect logs

# promtail-values.yaml
config:
  clients:
    - url: http://loki-gateway.monitoring.svc.cluster.local/loki/api/v1/push
helm install promtail grafana/promtail -n monitoring -f promtail-values.yaml

4. Buat custom alert rules

# custom-alerts.yaml
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: custom-alerts
  namespace: monitoring
  labels:
    release: monitoring
spec:
  groups:
    - name: application-alerts
      rules:
        - alert: HighErrorRate
          expr: |
            rate(http_requests_total{status=~"5.."}[5m])
            / rate(http_requests_total[5m]) > 0.05
          for: 5m
          labels:
            severity: critical
          annotations:
            summary: "High error rate detected"
            description: "More than 5% of requests are failing"

        - alert: PodCrashLooping
          expr: rate(kube_pod_container_status_restarts_total[15m]) > 0
          for: 5m
          labels:
            severity: warning
          annotations:
            summary: "Pod is crash looping"
            description: "Pod {{ $labels.namespace }}/{{ $labels.pod }} is restarting frequently"

        - alert: HighMemoryUsage
          expr: |
            container_memory_working_set_bytes{container!=""}
            / container_spec_memory_limit_bytes{container!=""} > 0.85
          for: 5m
          labels:
            severity: warning
          annotations:
            summary: "Container memory usage is high"
            description: "Container {{ $labels.container }} in pod {{ $labels.pod }} using more than 85% memory"
kubectl apply -f custom-alerts.yaml

Selepas semua installed, akses Grafana di grafana.homelab.local. Import dashboard ID 1860 untuk Node Exporter dashboard dan 15760 untuk Kubernetes overview. Anda akan dapat visibility yang lengkap terhadap cluster anda.

Nota Beginner: Monitoring stack ini mungkin memerlukan resources yang agak banyak. Pastikan cluster anda mempunyai sekurang-kurangnya 8GB RAM tersedia. Kalau resource terhad, mulakan dengan Prometheus dan Grafana sahaja, dan tambah Loki kemudian.


Projek 5: Multi-Environment Setup

Apa yang projek ini ajar

Dalam production sebenar, anda tidak deploy terus ke production. Biasanya ada dev, staging, dan production environments. Projek ini mengajar cara setup dan manage multiple environments menggunakan Kubernetes namespaces dan Kustomize.

Architecture

Git Repository
    |
    v
CI/CD Pipeline
    |
    +---> Dev Namespace
    |     (auto-deploy on push)
    |
    +---> Staging Namespace
    |     (deploy on PR merge)
    |
    +---> Production Namespace
          (manual approval required)

Langkah-langkah

1. Setup namespaces dan resource quotas

# namespaces.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: dev
  labels:
    environment: dev
---
apiVersion: v1
kind: Namespace
metadata:
  name: staging
  labels:
    environment: staging
---
apiVersion: v1
kind: Namespace
metadata:
  name: production
  labels:
    environment: production
---
# Resource quota untuk dev (limit resources)
apiVersion: v1
kind: ResourceQuota
metadata:
  name: dev-quota
  namespace: dev
spec:
  hard:
    requests.cpu: "2"
    requests.memory: 4Gi
    limits.cpu: "4"
    limits.memory: 8Gi
    pods: "20"
---
# Production gets more resources
apiVersion: v1
kind: ResourceQuota
metadata:
  name: production-quota
  namespace: production
spec:
  hard:
    requests.cpu: "8"
    requests.memory: 16Gi
    limits.cpu: "16"
    limits.memory: 32Gi
    pods: "50"

2. Kustomize overlays untuk setiap environment

# base/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: webapp
spec:
  replicas: 1
  selector:
    matchLabels:
      app: webapp
  template:
    metadata:
      labels:
        app: webapp
    spec:
      containers:
        - name: webapp
          image: webapp:latest
          ports:
            - containerPort: 3000
          envFrom:
            - configMapRef:
                name: webapp-config
          resources:
            requests:
              memory: "64Mi"
              cpu: "50m"
            limits:
              memory: "128Mi"
              cpu: "100m"
# overlays/dev/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: dev
bases:
  - ../../base
patches:
  - patch: |-
      - op: replace
        path: /spec/replicas
        value: 1
    target:
      kind: Deployment
configMapGenerator:
  - name: webapp-config
    literals:
      - NODE_ENV=development
      - LOG_LEVEL=debug
      - DB_HOST=dev-db.internal
# overlays/production/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: production
bases:
  - ../../base
patches:
  - patch: |-
      - op: replace
        path: /spec/replicas
        value: 3
      - op: replace
        path: /spec/template/spec/containers/0/resources/requests/memory
        value: "256Mi"
      - op: replace
        path: /spec/template/spec/containers/0/resources/limits/memory
        value: "512Mi"
    target:
      kind: Deployment
configMapGenerator:
  - name: webapp-config
    literals:
      - NODE_ENV=production
      - LOG_LEVEL=warn
      - DB_HOST=prod-db.internal

3. CI/CD pipeline dengan environment promotion

# .github/workflows/multi-env.yml
name: Multi-Environment Deploy

on:
  push:
    branches: [main, develop]
  workflow_dispatch:
    inputs:
      environment:
        description: 'Environment to deploy'
        required: true
        type: choice
        options:
          - staging
          - production

jobs:
  build:
    runs-on: ubuntu-latest
    outputs:
      image_tag: ${{ github.sha }}
    steps:
      - uses: actions/checkout@v4
      - name: Build and Push Image
        run: |
          docker build -t ghcr.io/${{ github.repository }}:${{ github.sha }} .
          docker push ghcr.io/${{ github.repository }}:${{ github.sha }}

  deploy-dev:
    needs: build
    if: github.ref == 'refs/heads/develop'
    runs-on: ubuntu-latest
    environment: dev
    steps:
      - uses: actions/checkout@v4
      - name: Deploy to Dev
        run: |
          cd overlays/dev
          kustomize edit set image webapp=ghcr.io/${{ github.repository }}:${{ github.sha }}
          kustomize build . | kubectl apply -f -

  deploy-staging:
    needs: build
    if: github.ref == 'refs/heads/main' || github.event.inputs.environment == 'staging'
    runs-on: ubuntu-latest
    environment: staging
    steps:
      - uses: actions/checkout@v4
      - name: Deploy to Staging
        run: |
          cd overlays/staging
          kustomize edit set image webapp=ghcr.io/${{ github.repository }}:${{ github.sha }}
          kustomize build . | kubectl apply -f -

  deploy-production:
    needs: deploy-staging
    if: github.event.inputs.environment == 'production'
    runs-on: ubuntu-latest
    environment:
      name: production
      url: https://myapp.com
    steps:
      - uses: actions/checkout@v4
      - name: Deploy to Production
        run: |
          cd overlays/production
          kustomize edit set image webapp=ghcr.io/${{ github.repository }}:${{ github.sha }}
          kustomize build . | kubectl apply -f -

      - name: Verify Deployment
        run: |
          kubectl rollout status deployment/webapp -n production --timeout=300s
          kubectl get pods -n production -l app=webapp

Perhatikan bahawa production deployment menggunakan GitHub Environments dengan protection rules. Anda boleh configure require manual approval sebelum deploy ke production melalui GitHub repository settings.

Nota Beginner: Dalam homelab, ketiga-tiga environments mungkin berada dalam satu cluster yang sama, dibezakan oleh namespaces. Dalam production sebenar, staging dan production biasanya berada dalam clusters yang berbeza untuk isolation yang lebih baik.


Ringkasan

Lima projek ini memberikan anda pengalaman hands-on yang sebenar. Setiap projek membina di atas kemahiran yang anda pelajari dalam bab-bab sebelumnya.

Beberapa tips untuk mendapat manfaat maksimum daripada projek-projek ini:

  • Buat satu projek pada satu masa. Jangan rush. Fahami setiap component sebelum bergerak ke projek seterusnya.
  • Dokumentasikan pengalaman anda. Tulis blog post atau buat notes tentang apa yang anda belajar dan masalah yang anda hadapi. Ini sangat berguna untuk portfolio anda.
  • Experiment dan break things. Homelab adalah tempat yang selamat untuk buat kesilapan. Cuba modify configurations, lihat apa yang berlaku. Belajar dari kesilapan adalah cara paling berkesan.
  • Share dengan komuniti. Push projek anda ke GitHub. Ini bukan sahaja membantu orang lain, tetapi juga menunjukkan kemahiran anda kepada potential employers.

Yang paling penting, ingat bahawa setiap DevOps engineer yang berpengalaman pun bermula dari sini. Setiap projek yang anda siapkan membawa anda satu langkah lebih dekat ke matlamat anda.

Bab 11: Kerjaya DevOps - Dari Homelab ke Dunia Sebenar

Sepanjang buku ini, anda telah belajar tentang tools, technologies, dan practices dalam DevOps. Tapi ada satu soalan besar yang mungkin bermain di fikiran anda: “Macam mana nak jadikan semua ini sebagai kerjaya?”

Saya faham perasaan itu. Saya sendiri pernah berada di tempat yang sama. Belajar sendiri, experiment dalam homelab, dan tertanya-tanya sama ada kemahiran yang saya ada cukup untuk dunia sebenar. Jawapan ringkasnya: ya, cukup. Tapi ada beberapa perkara yang anda perlu tahu untuk memaksimumkan peluang anda.

Apa yang anda akan belajar:

  • Landscape kerjaya DevOps di Malaysia dan global
  • Julat gaji dan apa yang mempengaruhinya
  • Kemahiran yang dicari oleh employers
  • Certifications yang bernilai
  • Cara membina portfolio yang menarik
  • Tips resume dan interview
  • Freelancing vs full-time
  • Learning roadmap untuk jangka panjang

Landscape Kerjaya DevOps

Di Malaysia

DevOps di Malaysia sedang berkembang dengan pesat. Banyak syarikat, terutama yang berkaitan dengan fintech, e-commerce, dan cloud services, sedang aktif mencari DevOps engineers. Bandar-bandar utama seperti Kuala Lumpur, Cyberjaya, dan Penang mempunyai banyak peluang.

Beberapa trend yang saya perhatikan:

  • Bank dan institusi kewangan sedang melalui digital transformation dan memerlukan ramai DevOps engineers untuk modernize infrastructure mereka.
  • Startups biasanya mencari “full-stack DevOps” yang boleh handle everything dari CI/CD hingga infrastructure.
  • Multinational companies (MNCs) seperti yang beroperasi di Malaysia menawarkan exposure kepada large-scale systems dan global practices.
  • Government agencies juga mula adopt cloud dan DevOps practices, walaupun pada kadar yang lebih perlahan.

Global (Remote Work)

Salah satu kelebihan besar kerjaya DevOps ialah banyak posisi yang boleh dilakukan secara remote. Ini membuka peluang untuk bekerja dengan syarikat dari seluruh dunia sambil tinggal di Malaysia. Gaji remote biasanya lebih tinggi daripada gaji tempatan, dan dengan kos sara hidup di Malaysia, ini adalah situasi yang sangat menguntungkan.

Platform seperti LinkedIn, Indeed, dan Remote.co mempunyai banyak listing untuk remote DevOps positions. Ada juga platform khusus seperti We Work Remotely dan Remote OK yang fokus pada remote jobs.

Julat Gaji

Mari kita bercakap tentang nombor. Ini adalah anggaran berdasarkan pemerhatian saya terhadap pasaran semasa. Angka sebenar bergantung pada banyak faktor termasuk syarikat, lokasi, dan kemahiran spesifik anda.

Malaysia (bulanan, MYR): - Junior DevOps Engineer (0-2 tahun): RM 4,000 - RM 7,000 - Mid-level DevOps Engineer (2-5 tahun): RM 7,000 - RM 12,000 - Senior DevOps Engineer (5+ tahun): RM 12,000 - RM 20,000 - DevOps Lead/Manager: RM 18,000 - RM 30,000+

Remote/Global (bulanan, USD): - Junior: USD 3,000 - USD 5,000 - Mid-level: USD 5,000 - USD 8,000 - Senior: USD 8,000 - USD 15,000+

Nota Beginner: Jangan terlalu fokus pada gaji di peringkat awal. Fokus pada belajar dan kumpul pengalaman. Gaji akan datang apabila kemahiran anda meningkat. Saya pernah ambil role dengan gaji yang lebih rendah kerana peluang untuk belajar technologies baru, dan keputusan itu terbayar dalam jangka panjang.

Kemahiran yang Dicari oleh Employers

Berdasarkan job listings yang saya sering lihat, ini adalah kemahiran utama yang dicari:

Kemahiran Teknikal (Hard Skills): - Linux administration adalah asas. Hampir semua servers dalam production menggunakan Linux. - Containerization dengan Docker dan orchestration dengan Kubernetes. - CI/CD tools seperti GitHub Actions, GitLab CI, Jenkins, atau ArgoCD. - Infrastructure as Code menggunakan Terraform, Ansible, atau Pulumi. - Cloud platforms terutama AWS, Azure, atau GCP. Sekurang-kurangnya satu. - Scripting dalam Bash, Python, atau Go. - Monitoring dan observability dengan Prometheus, Grafana, ELK stack, atau Datadog. - Networking fundamentals termasuk DNS, load balancing, dan firewalls.

Kemahiran Insaniah (Soft Skills): - Communication adalah yang paling penting. DevOps engineers bekerja di antara development dan operations teams. Anda perlu boleh berkomunikasi dengan kedua-dua pihak. - Problem-solving kerana anda akan menghadapi masalah yang unik setiap hari. - Documentation kerana infrastructure yang tidak didokumentasikan adalah infrastructure yang suatu hari nanti akan menyebabkan masalah besar. - Collaboration kerana DevOps pada dasarnya adalah tentang breaking down silos.

Certifications yang Bernilai

Certifications bukan mandatory, tetapi ia boleh membantu terutama di peringkat awal kerjaya. Ia menunjukkan kepada employers bahawa anda mempunyai pengetahuan yang tervalidasi.

Berikut adalah certifications yang saya recommend, disusun mengikut keutamaan:

Tier 1 (Mula di sini): - AWS Certified Solutions Architect - Associate atau AWS Certified Cloud Practitioner untuk mereka yang baru mula. - Certified Kubernetes Administrator (CKA) adalah sangat bernilai kerana Kubernetes semakin menjadi standard. - Linux Foundation Certified System Administrator (LFCS) untuk membuktikan kemahiran Linux anda.

Tier 2 (Selepas ada pengalaman): - HashiCorp Certified: Terraform Associate untuk Infrastructure as Code. - AWS Certified DevOps Engineer - Professional untuk yang lebih advanced. - Certified Kubernetes Security Specialist (CKS) untuk specialization dalam security.

Tier 3 (Specialization): - Google Cloud Professional DevOps Engineer - Azure DevOps Engineer Expert

Nota Beginner: Jangan collect certifications tanpa tujuan. Pilih satu atau dua yang paling relevan dengan kerjaya path anda. Lebih baik ada satu certification yang anda benar-benar faham daripada lima yang anda hanya hafal untuk exam.

Satu lagi tip: banyak certification exams boleh didapatkan dengan diskaun melalui program seperti Linux Foundation sales events. Pantau untuk offers ini dan jimatkan kos anda.

Membina Portfolio dengan Homelab

Ini adalah kelebihan terbesar anda. Semua projek yang anda bina dalam homelab sepanjang buku ini boleh dijadikan portfolio yang sangat menarik.

Apa yang perlu ada dalam portfolio anda:

  1. GitHub repository yang terurus. Push semua configurations, scripts, dan documentation ke GitHub. Pastikan setiap repo mempunyai README yang jelas menerangkan apa projek itu, kenapa anda buat, dan macam mana nak guna.

  2. Blog atau documentation site. Tulis tentang pengalaman anda setup homelab, masalah yang anda hadapi, dan macam mana anda selesaikan. Ini menunjukkan kemahiran communication dan documentation anda. Anda boleh gunakan platform seperti Hashnode, Dev.to, atau buat blog sendiri.

  3. Architecture diagrams. Lukis architecture homelab anda. Tunjukkan macam mana semua components bersambung. Ini menunjukkan anda faham big picture, bukan hanya individual tools.

  4. Contributions kepada open-source projects. Walaupun kecil, contributions menunjukkan anda boleh bekerja dengan codebase yang besar dan collaborate dengan orang lain.

Saya pernah interview seorang candidate yang tiada pengalaman kerja dalam DevOps, tapi dia ada homelab yang documented dengan sangat baik di GitHub. Dia dapat kerja itu kerana portfolio homelabnya menunjukkan passion, kemahiran teknikal, dan initiative.

Tips Resume

Resume anda adalah first impression. Berikut beberapa tips:

  • Letak GitHub profile link di bahagian atas resume. Banyak hiring managers akan check GitHub anda sebelum interview.
  • Gunakan keywords yang specific. Bukan hanya “cloud experience”, tetapi “deployed and managed Kubernetes clusters on AWS EKS with Terraform”. Specificity menunjukkan pengalaman sebenar.
  • Highlight projek homelab anda. Kalau anda belum ada pengalaman kerja dalam DevOps, projek homelab adalah bukti kemahiran anda. Tulis ia sebagai “Projects” section dalam resume.
  • Quantify achievements. “Reduced deployment time from 2 hours to 15 minutes using CI/CD automation” lebih impactful daripada “implemented CI/CD pipeline.”
  • Tailor resume untuk setiap application. Baca job description dengan teliti dan pastikan resume anda address keperluan specific jawatan tersebut.

Persediaan Interview

Interview untuk DevOps positions biasanya terdiri daripada beberapa komponen:

Soalan teknikal yang common: - “Terangkan CI/CD pipeline yang anda pernah bina.” - “Macam mana anda handle secrets management?” - “Apa yang anda buat bila production server down?” - “Terangkan perbezaan antara Docker dan virtual machines.” - “Macam mana anda implement Infrastructure as Code?” - “Terangkan GitOps dan kenapa ia berguna.”

Scenario-based questions: - “Application anda tiba-tiba slow. Apa steps troubleshooting anda?” - “Macam mana anda design deployment strategy untuk zero-downtime?” - “Team anda nak migrate dari on-premise ke cloud. Macam mana anda approach projek ini?”

Hands-on/practical test: Sesetengah syarikat akan beri anda hands-on test. Contohnya, setup CI/CD pipeline dalam masa tertentu, atau troubleshoot failing Kubernetes deployment. Pengalaman homelab anda akan sangat membantu di sini.

Tips interview: - Jujur tentang apa yang anda tahu dan tidak tahu. Lebih baik kata “saya belum pernah guna tool itu, tapi saya tahu konsepnya dan boleh belajar” daripada berpura-pura tahu. - Ceritakan pengalaman troubleshooting. Employers suka dengar tentang masalah yang anda hadapi dan macam mana anda selesaikan. - Tunjukkan curiosity. Tanya soalan tentang tech stack syarikat, challenges yang mereka hadapi, dan bagaimana team mereka beroperasi.

Freelancing vs Full-Time

Kedua-dua path mempunyai kelebihan dan kekurangan:

Full-time: - Pendapatan yang stabil dan consistent - Benefits seperti medical, EPF, dan annual leave - Peluang untuk belajar dari team members yang berpengalaman - Exposure kepada large-scale systems - Career progression yang lebih jelas

Freelancing/Consulting: - Flexibility dalam jadual dan pemilihan projek - Potensi pendapatan yang lebih tinggi - Variety dalam jenis projek dan industries - Kebebasan untuk bekerja dari mana-mana - Perlu uruskan sendiri taxes, invoicing, dan client management

Nota Beginner: Kalau anda baru bermula, saya sangat recommend full-time dulu. Kumpulkan pengalaman selama 2 hingga 3 tahun, bina network, dan fahami macam mana production systems beroperasi. Selepas itu, anda boleh consider freelancing kalau ia sesuai dengan gaya hidup anda.

Learning Roadmap

DevOps adalah bidang yang sentiasa berubah. Anda perlu terus belajar. Berikut adalah roadmap yang saya cadangkan:

Tahun 1: Foundation - Linux administration dan scripting (Bash, Python) - Docker dan containerization - Git dan version control - Basic networking - Satu CI/CD tool (GitHub Actions) - Satu cloud provider (AWS)

Tahun 2: Intermediate - Kubernetes dan container orchestration - Infrastructure as Code (Terraform) - Configuration management (Ansible) - Monitoring dan observability - Security fundamentals (DevSecOps)

Tahun 3: Advanced - Service mesh (Istio, Linkerd) - Advanced Kubernetes (operators, CRDs) - Multi-cloud strategies - Platform engineering - Chaos engineering - Leadership dan mentoring

Berterusan: - Ikuti blog dan podcast tentang DevOps - Sertai komuniti seperti DevOps Malaysia, CNCF meetups - Contribute kepada open-source - Attend conferences (KubeCon, DevOpsDays) - Baca post-mortems dari syarikat besar untuk belajar dari insiden sebenar

Ringkasan

Kerjaya DevOps adalah salah satu kerjaya yang paling menarik dalam teknologi sekarang. Demand tinggi, gaji kompetitif, dan peluang untuk remote work menjadikan ia pilihan yang sangat baik.

Perkara yang paling penting:

  • Kemahiran praktikal lebih penting daripada certifications. Homelab anda adalah bukti terbaik kemahiran anda.
  • Bina portfolio yang solid. GitHub repos, blog posts, dan documentation menunjukkan kemahiran sebenar anda.
  • Jangan tunggu sampai “ready”. Tiada siapa yang pernah 100% ready. Mula apply, mula interview, dan belajar dari setiap pengalaman.
  • Network dengan komuniti. Ramai peluang datang melalui connections, bukan job portals.
  • Terus belajar. Bidang ini berubah dengan cepat, dan mereka yang terus belajar akan sentiasa relevan.

Anda dah ada pengetahuan. Anda dah ada kemahiran. Sekarang, langkah seterusnya adalah milik anda.

Penutup

Terima kasih kerana bersama saya sepanjang perjalanan ini.

Bila saya mula menulis buku ini, saya teringat pada diri saya sendiri beberapa tahun yang lalu. Seorang yang curious tentang DevOps, tapi keliru dengan banyaknya tools, terminologi, dan “best practices” yang seolah-olah berubah setiap minggu. Saya tahu betapa overwhelming rasanya, dan itulah sebabnya saya tulis buku ini dengan cara yang saya harap saya ada waktu saya baru bermula.

Kita telah melalui banyak perkara bersama. Daripada memahami asas Linux dan containerization, hingga membina CI/CD pipelines, mengurus Kubernetes clusters, dan mengamalkan DevSecOps. Daripada homelab yang sederhana, kita belajar konsep-konsep yang digunakan oleh syarikat besar di seluruh dunia.

Tapi inilah rahsianya: buku ini hanyalah permulaan.

Teknologi akan terus berubah. Tools baru akan muncul. Best practices hari ini mungkin akan menjadi legacy esok. Tapi prinsip-prinsip asas yang anda pelajari di sini, iaitu automation, monitoring, security, collaboration, dan continuous improvement, akan kekal relevan tidak kira apa tools yang popular pada masa hadapan.

Yang paling penting, anda sudah buktikan sesuatu kepada diri sendiri. Anda mampu belajar perkara yang kompleks. Anda mampu setup systems yang berfungsi. Anda mampu troubleshoot masalah dan cari penyelesaian. Itu adalah kemahiran yang tiada tools atau certification boleh ganti.

Beberapa pesanan terakhir saya:

Jangan berhenti experiment. Homelab anda adalah playground yang tak terhad. Cuba technologies baru, break things, dan belajar dari setiap kesilapan. Setiap kali sesuatu rosak dan anda fix, anda menjadi lebih baik.

Ajar orang lain. Cara terbaik untuk memahami sesuatu dengan mendalam ialah dengan mengajarkannya. Tulis blog, buat video, atau sekadar tolong rakan sekerja. Bila anda mengajar, anda juga belajar.

Bersabar dengan diri sendiri. Anda tidak perlu tahu semua benda. Tidak ada seorang pun yang tahu semuanya dalam DevOps. Yang penting ialah anda terus belajar dan terus mencuba.

Sertai komuniti. DevOps bukan perjalanan solo. Sertai meetups, online communities, dan forums. Berkongsi pengalaman dan belajar dari orang lain. Anda akan terkejut betapa ramainya orang yang willing untuk membantu.

Jika anda belum membaca buku companion saya tentang Homelab, saya sangat recommend anda check ia juga. Ia memberikan foundation yang lebih mendalam tentang setup homelab dari segi hardware, networking, dan virtualization, yang menjadi asas kepada semua yang kita bincangkan dalam buku ini.

Untuk resources tambahan, updates, dan artikel-artikel baru, lawati website saya di www.notainfra.com. Saya juga berkongsi tips dan pengalaman di sana secara berkala.

Akhir kata, terima kasih kerana mempercayai saya sebagai guide dalam perjalanan DevOps anda. Saya harap buku ini telah memberikan anda keyakinan dan kemahiran untuk melangkah ke hadapan dengan yakin.

Selamat maju jaya. Jumpa di production.

Syafiyullah Yahya www.notainfra.com