Provision Cluster #
Membangun kluster Kubernetes dari nol sering kali dianggap sebagai salah satu ritus peralihan paling menantang bagi seorang DevOps Engineer maupun Administrator Sistem. Proses ini melibatkan serangkaian langkah tingkat sistem yang presisi dan berurutan di banyak server: menonaktifkan swap, memuat modul kernel, menyetel parameter sysctl, menginstal container runtime, menginstal binari Kubernetes, melakukan inisialisasi control plane, mengatur konfigurasi jaringan kluster (CNI), hingga akhirnya menggabungkan worker nodes.
Melakukan langkah-langkah ini secara manual pada 3 atau 10 server tidak hanya melelahkan, tetapi juga sangat rentan terhadap kesalahan manusia (human error). Satu kesalahan kecil seperti lupa mengaktifkan SystemdCgroup pada runtime kontainer dapat menyebabkan kluster gagal berfungsi secara misterius beberapa hari kemudian. Di sinilah Ansible hadir sebagai solusi penyelamat. Dengan mengotomasi seluruh alur bootstrap kluster menggunakan playbook Ansible yang idempoten, kita dapat melahirkan kluster Kubernetes yang konsisten, terstandardisasi, dan siap produksi hanya dengan satu perintah tunggal.
Mengapa Memilih Ansible untuk Provisioning Kluster? #
Ketika kita memutuskan untuk mengotomasi siklus hidup infrastruktur Kubernetes, kita memiliki beberapa pilihan alat. Namun, Ansible menawarkan keunggulan unik untuk proses bootstrapping sistem operasi tingkat rendah.
Tantangan Bootstrap Manual:
✗ Kita harus SSH ke setiap server satu per satu untuk menjalankan perintah setup OS.
✗ Mengedit file konfigurasi sensitif seperti /etc/fstab atau config.toml secara manual rawan typo.
✗ Token inisialisasi kubeadm memiliki masa berlaku terbatas (24 jam) — menyalin token ini secara manual antar-server sangat merepotkan.
✗ Sulit untuk mereplikasi konfigurasi yang sama persis jika kita ingin membuat kluster Staging dan Produksi yang identik.
Kemudahan dengan Automasi Ansible:
✓ Satu Playbook untuk Semua Node — jalankan semua tugas persiapan OS secara paralel di seluruh server.
✓ Templating Dinamis — gunakan Jinja2 untuk menghasilkan file konfigurasi containerd dan sysctl yang bersih dan akurat.
✓ Orkestrasi Alur Kerja — Ansible dapat mendaftarkan token kubeadm di control plane, menyimpannya sebagai variabel memori, lalu meneruskannya ke worker nodes secara otomatis.
✓ Version Control — seluruh infrastruktur kluster kita didefinisikan sebagai kode (IaC) yang dapat disimpan di repositori Git.
Desain Arsitektur Jaringan dan Inventory Kluster #
Sebelum mulai menulis task Ansible, kita harus merancang topologi kluster kita terlebih dahulu. Kita akan membangun kluster dengan satu Control Plane (Master Node) dan dua Worker Nodes. Topologi komunikasi antar node dapat kita visualisasikan melalui diagram berikut:
flowchart TD
subgraph ControlPlane["Control Plane Node"]
M1["k8s-master-01 (10.0.1.10)"]
end
subgraph WorkerNodes["Worker Nodes"]
W1["k8s-worker-01 (10.0.2.10)"]
W2["k8s-worker-02 (10.0.2.11)"]
end
M1 <-->|"Join Token & API Control (Port 6443)"| W1
M1 <-->|"Join Token & API Control (Port 6443)"| W2
W1 <-->|"Pod-to-Pod CNI Overlay Network"| W2
Untuk merepresentasikan arsitektur di atas ke dalam Ansible, kita membuat file inventory terstruktur. File ini membagi server berdasarkan perannya agar kita dapat mengarahkan tugas-tugas tertentu ke kelompok server yang tepat.
# inventory/k8s-cluster/hosts.ini
[control_plane]
k8s-master-01 ansible_host=10.0.1.10 ansible_user=ubuntu
[worker_nodes]
k8s-worker-01 ansible_host=10.0.2.10 ansible_user=ubuntu
k8s-worker-02 ansible_host=10.0.2.11 ansible_user=ubuntu
[k8s_cluster:children]
control_plane
worker_nodes
Kita juga mendefinisikan variabel global kluster untuk mengatur versi paket dan konfigurasi jaringan internal kluster di dalam file group_vars:
# inventory/k8s-cluster/group_vars/k8s_cluster.yml
---
k8s_version: "1.29.2"
k8s_minor_version: "1.29"
pod_network_cidr: "192.168.0.0/16" # CIDR default untuk CNI Calico
control_plane_endpoint: "10.0.1.10:6443"
Langkah 1: Persiapan Sistem Operasi dan Kernel Linux #
Kubernetes membutuhkan konfigurasi sistem operasi tingkat rendah yang sangat spesifik sebelum komponen utamanya dapat berjalan. Dua konfigurasi paling kritis adalah menonaktifkan swap secara permanen dan mengonfigurasi modul kernel untuk menjembatani jaringan kontainer.
Mengapa Kubernetes Melarang Swap? #
Secara default, kubelet tidak akan mau berjalan jika swap aktif di server. Alasan utamanya adalah jaminan alokasi resource dan performa. Kubernetes didesain untuk menjadwalkan aplikasi berdasarkan batas resource yang ketat. Jika sistem operasi memindahkan memori Pod ke swap disk, performa aplikasi akan menurun drastis secara tidak terduga, dan Kubernetes tidak dapat menghitung penggunaan memori nyata kluster secara akurat.
Modul Kernel dan Sysctl #
Agar lalu lintas jaringan dari container di dalam Pod dapat dijembatani dengan benar di tingkat host, kita harus memuat modul kernel overlay dan br_netfilter, serta mengonfigurasi parameter sysctl agar iptables dapat melihat lalu lintas bridge jaringan Linux.
Berikut adalah implementasi tugas persiapan OS dalam role Ansible kita:
# roles/k8s-common/tasks/main.yml
---
- name: Nonaktifkan swap untuk sesi saat ini
command: swapoff -a
changed_when: false
- name: Pastikan swap dinonaktifkan secara permanen di fstab
replace:
path: /etc/fstab
regexp: '^([^#].*?\sswap\s+sw\s+.*)$'
replace: '# \1'
- name: Buat file konfigurasi load module kernel
copy:
dest: /etc/modules-load.d/k8s.conf
content: |
overlay
br_netfilter
owner: root
group: root
mode: '0644'
- name: Muat modul kernel overlay secara manual
modprobe:
name: overlay
state: present
- name: Muat modul kernel br_netfilter secara manual
modprobe:
name: br_netfilter
state: present
- name: Konfigurasi parameter sysctl untuk jaringan Kubernetes
sysctl:
name: "{{ item.key }}"
value: "{{ item.value }}"
state: present
sysctl_file: /etc/sysctl.d/99-kubernetes-cri.conf
reload: true
loop:
- { key: "net.bridge.bridge-nf-call-iptables", value: "1" }
- { key: "net.bridge.bridge-nf-call-ip6tables", value: "1" }
- { key: "net.ipv4.ip_forward", value: "1" }
Langkah 2: Konfigurasi Container Runtime (containerd) #
Sejak Kubernetes mendepresiasi Docker Shim pada versi 1.20 dan menghapusnya sepenuhnya di versi 1.24, kita harus menggunakan container runtime mandiri yang kompatibel dengan CRI (Container Runtime Interface). containerd adalah pilihan standar industri yang sangat stabil dan ringan.
Tantangan terbesar saat mendeploy containerd untuk Kubernetes adalah konfigurasi driver cgroup. Secara default, Linux menggunakan systemd sebagai sistem inisialisasi proses. Jika containerd dikonfigurasi menggunakan driver cgroup bawaannya sendiri (cgroupfs), sistem akan memiliki dua manajer cgroup yang berbeda. Hal ini dapat menyebabkan ketidakstabilan sistem ketika server kehabisan resource. Kita harus memaksa containerd untuk menggunakan driver cgroup milik systemd (SystemdCgroup = true).
# roles/containerd/tasks/main.yml
---
- name: Install dependencies awal untuk containerd
apt:
name:
- apt-transport-https
- ca-certificates
- curl
- gnupg
state: present
update_cache: true
- name: Buat direktori keyrings untuk repositori Docker
file:
path: /etc/apt/keyrings
state: directory
mode: '0755'
- name: Tambahkan GPG Key resmi Docker
apt_key:
url: https://download.docker.com/linux/ubuntu/gpg
state: present
keyring: /etc/apt/keyrings/docker.gpg
- name: Tambahkan repositori Docker untuk containerd
apt_repository:
repo: "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu {{ ansible_distribution_release }} stable"
state: present
filename: docker
- name: Install paket containerd.io
apt:
name: containerd.io
state: present
update_cache: true
- name: Buat direktori konfigurasi containerd
file:
path: /etc/containerd
state: directory
mode: '0755'
- name: Generate konfigurasi default containerd
command: containerd config default
register: containerd_config_raw
changed_when: false
- name: Tulis file konfigurasi default containerd
copy:
content: "{{ containerd_config_raw.stdout }}"
dest: /etc/containerd/config.toml
owner: root
group: root
mode: '0644'
- name: Konfigurasi containerd agar menggunakan SystemdCgroup driver
replace:
path: /etc/containerd/config.toml
regexp: 'SystemdCgroup = false'
replace: 'SystemdCgroup = true'
notify: Restart containerd
- name: Pastikan containerd aktif dan berjalan saat booting
systemd:
name: containerd
state: started
enabled: true
Kita juga membuat handler untuk merestart layanan containerd jika ada perubahan konfigurasi:
# roles/containerd/handlers/main.yml
---
- name: Restart containerd
systemd:
name: containerd
state: restarted
Langkah 3: Instalasi Komponen Kubeadm, Kubelet, dan Kubectl #
Setelah container runtime siap, langkah selanjutnya adalah memasang komponen inti Kubernetes:
- kubelet: Layanan agen utama yang bertanggung jawab menjalankan container di setiap node.
- kubeadm: Alat CLI untuk mendeploy dan melakukan inisialisasi kluster Kubernetes sesuai dengan best practice keamanan.
- kubectl: Alat CLI untuk berinteraksi dengan API Server Kubernetes.
[!IMPORTANT] Mulai pertengahan tahun 2023, Google secara resmi menutup repositori APT warisan (
apt.kubernetes.io). Kita wajib beralih menggunakan repositori komunitas baru yang beralamat dipkgs.k8s.iodengan format penamaan direktori versi minor yang spesifik.
# roles/k8s-packages/tasks/main.yml
---
- name: Buat direktori keyring untuk Kubernetes
file:
path: /etc/apt/keyrings
state: directory
mode: '0755'
- name: Unduh GPG Key repositori Kubernetes baru
apt_key:
url: "https://pkgs.k8s.io/core:/stable:/v{{ k8s_minor_version }}/deb/Release.key"
state: present
keyring: /etc/apt/keyrings/kubernetes-apt-keyring.gpg
- name: Tambahkan repositori Kubernetes baru ke sumber apt
apt_repository:
repo: "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v{{ k8s_minor_version }}/deb/ /"
state: present
filename: kubernetes
- name: Install kubelet, kubeadm, dan kubectl secara bersamaan
apt:
name:
- "kubelet={{ k8s_version }}-*"
- "kubeadm={{ k8s_version }}-*"
- "kubectl={{ k8s_version }}-*"
state: present
update_cache: true
- name: Pin versi paket agar tidak terupdate tidak sengaja saat apt upgrade
dpkg_selections:
name: "{{ item }}"
selection: hold
loop:
- kubelet
- kubeadm
- kubectl
Langkah 4: Inisialisasi Control Plane (Bootstrap Master Node) #
Sekarang, semua dependencies telah terinstal di semua node. Saatnya kita menginisialisasi node Control Plane pertama kita menggunakan perintah kubeadm init. Tugas ini hanya boleh dijalankan di server master pertama (control_plane[0]).
Setelah inisialisasi selesai, kita harus:
- Menyalin file administrasi kluster (
admin.confalias kubeconfig) ke direktori home pengguna kita agar kita dapat menjalankan perintahkubectltanpa hak akses root. - Membuat token join baru yang dinamis untuk digunakan oleh worker nodes agar dapat bergabung ke kluster secara aman.
# playbooks/init-control-plane.yml
---
- name: Inisialisasi Kubernetes Control Plane
hosts: control_plane[0]
become: true
tasks:
- name: Cek apakah Control Plane sudah pernah diinisialisasi sebelumnya
stat:
path: /etc/kubernetes/admin.conf
register: k8s_init_check
- name: Jalankan inisialisasi kubeadm di node master pertama
command: >
kubeadm init
--pod-network-cidr={{ pod_network_cidr }}
--control-plane-endpoint={{ control_plane_endpoint }}
--kubernetes-version={{ k8s_version }}
register: kubeadm_init_output
when: not k8s_init_check.stat.exists
- name: Konfigurasi Akses kubectl untuk User Biasa
hosts: control_plane[0]
become: false
tasks:
- name: Buat direktori .kube di home user
file:
path: "{{ ansible_env.HOME }}/.kube"
state: directory
mode: '0700'
- name: Salin file konfigurasi admin.conf ke direktori home user
copy:
src: /etc/kubernetes/admin.conf
dest: "{{ ansible_env.HOME }}/.kube/config"
remote_src: true
owner: "{{ ansible_user }}"
group: "{{ ansible_user }}"
mode: '0600'
become: true
- name: Ambil Perintah Join Secara Dinamis
hosts: control_plane[0]
become: true
tasks:
- name: Generate join command baru yang valid
command: kubeadm token create --print-join-command
register: temp_join_command
changed_when: false
- name: Simpan join command ke variabel global localhost
set_fact:
k8s_join_command: "{{ temp_join_command.stdout }}"
delegate_to: localhost
delegate_facts: true
Langkah 5: Setup Container Network Interface (CNI) #
Kubernetes mengasumsikan bahwa setiap Pod memiliki IP address unik sendiri yang dapat berkomunikasi langsung dengan Pod lain di kluster, bahkan jika Pod tersebut berada di host fisik yang berbeda. Untuk mewujudkan model jaringan ini, kita harus menginstal plugin CNI (Container Network Interface).
Dua opsi CNI paling populer di industri:
- Flannel: CNI yang sangat sederhana dan ringan. Ia menggunakan enkapsulasi VXLAN untuk membuat jaringan overlay dasar. Flannel sangat cocok untuk kluster kecil atau lingkungan development karena konfigurasinya yang minimal. Namun, ia tidak mendukung fitur keamanan tingkat lanjut seperti Network Policies.
- Calico: CNI tingkat lanjut yang sangat populer di tingkat produksi. Calico menggunakan protokol BGP murni tanpa enkapsulasi tambahan (jika dikonfigurasi demikian) untuk memberikan performa jaringan berkecepatan tinggi. Selain itu, ia menyediakan mesin penegak aturan keamanan (Network Policies) yang sangat kuat.
flowchart TD
subgraph Node_Master["Node Master"]
API_CNI["kube-apiserver"]
end
subgraph Node_Worker1["Node Worker 1"]
PodA["Pod A (192.168.1.10)"]
CNI1["Calico Agent (CNI)"]
PodA --> CNI1
end
subgraph Node_Worker2["Node Worker 2"]
PodB["Pod B (192.168.2.20)"]
CNI2["Calico Agent (CNI)"]
PodB --> CNI2
end
CNI1 <-->|"Tunnel Overlay VXLAN / BGP"| CNI2
Kita akan menginstal CNI Calico dengan mendeploy manifest resminya langsung dari control plane master setelah inisialisasi selesai:
# playbooks/install-cni.yml
---
- name: Deploy Jaringan CNI Calico
hosts: control_plane[0]
become: false
tasks:
- name: Unduh manifest resmi Calico Operator
get_url:
url: https://raw.githubusercontent.com/projectcalico/calico/v3.27.0/manifests/tigera-operator.yaml
dest: /tmp/tigera-operator.yaml
mode: '0644'
- name: Terapkan operator Tigera Calico ke kluster
command: kubectl create -f /tmp/tigera-operator.yaml
register: apply_operator
failed_when:
- apply_operator.rc != 0
- "'already exists' not in apply_operator.stderr"
changed_when: "'created' in apply_operator.stdout"
- name: Unduh manifest konfigurasi Custom Resources Calico
get_url:
url: https://raw.githubusercontent.com/projectcalico/calico/v3.27.0/manifests/custom-resources.yaml
dest: /tmp/custom-resources.yaml
mode: '0644'
- name: Sesuaikan CIDR Pod di manifest Calico dengan variabel kita
replace:
path: /tmp/custom-resources.yaml
regexp: 'cidr: 192.168.0.0/16'
replace: "cidr: {{ pod_network_cidr }}"
- name: Terapkan konfigurasi jaringan Calico
command: kubectl create -f /tmp/custom-resources.yaml
register: apply_resources
failed_when:
- apply_resources.rc != 0
- "'already exists' not in apply_resources.stderr"
changed_when: "'created' in apply_resources.stdout"
Langkah 6: Join Worker Nodes ke Kluster #
Setelah jaringan Control Plane dan CNI terpasang dengan baik, saatnya kita menggabungkan server-server pekerja (Worker Nodes) agar mereka dapat menerima instruksi penjadwalan Pod. Kita akan menggunakan variabel join command yang telah disimpan sebelumnya di host localhost.
Setelah menjalankan perintah join, tugas operasional terbaik adalah memastikan dan menunggu hingga status node baru tersebut dilaporkan sebagai Ready oleh API Server sebelum kita mengakhiri playbook.
# playbooks/join-workers.yml
---
- name: Gabungkan Worker Nodes ke Cluster
hosts: worker_nodes
become: true
tasks:
- name: Cek apakah node ini sudah terhubung sebelumnya
stat:
path: /etc/kubernetes/kubelet.conf
register: kubelet_conf_check
- name: Jalankan perintah join ke Control Plane
command: "{{ hostvars['localhost']['k8s_join_command'] }}"
when: not kubelet_conf_check.stat.exists
- name: Tunggu status node menjadi 'Ready' di Control Plane
command: >
kubectl get node {{ ansible_hostname }}
--kubeconfig /etc/kubernetes/admin.conf
-o jsonpath='{.status.conditions[-1].type}'
register: node_status_output
until: node_status_output.stdout == "Ready"
retries: 30
delay: 10
delegate_to: "{{ groups['control_plane'][0] }}"
changed_when: false
Langkah 7: Pengambilan Kubeconfig ke Control Node Lokal #
Langkah terakhir dari provisioning kluster adalah menyalin file admin.conf yang aman dari server master pertama ke mesin lokal (control node) kita. Ini memungkinkan kita untuk mengelola kluster Kubernetes dari jarak jauh menggunakan tool local seperti kubectl, k9s, atau playbook Ansible kita selanjutnya tanpa harus login SSH terus-menerus ke server master.
# playbooks/fetch-kubeconfig.yml
---
- name: Amankan dan Fetch Kubeconfig ke Lokal
hosts: control_plane[0]
become: true
tasks:
- name: Buat direktori penampung kubeconfig lokal di Control Node
file:
path: "{{ playbook_dir }}/kubeconfig"
state: directory
mode: '0700'
delegate_to: localhost
become: false
- name: Salin file admin.conf ke direktori playbook lokal
fetch:
src: /etc/kubernetes/admin.conf
dest: "{{ playbook_dir }}/kubeconfig/admin.conf"
flat: true
- name: Sesuaikan alamat endpoint API di file kubeconfig lokal
replace:
path: "{{ playbook_dir }}/kubeconfig/admin.conf"
regexp: 'server: https://127.0.0.1:6443'
replace: "server: https://{{ control_plane_endpoint.split(':')[0] }}:6443"
delegate_to: localhost
become: false
Sekarang kita memiliki file konfigurasi cluster lokal yang aman di path kubeconfig/admin.conf. Kita dapat menggunakannya dengan mudah dengan mengeset variabel lingkungan export KUBECONFIG=./kubeconfig/admin.conf di terminal lokal kita.
Ringkasan #
- Menonaktifkan swap wajib dilakukan secara permanen di seluruh node kluster agar kubelet dapat berjalan stabil dan menghitung resource secara akurat.
- Modul kernel
overlaydanbr_netfilterserta parameter sysctl jaringan bridge wajib aktif agar lalu lintas komunikasi kontainer dapat menjembatani host Linux dengan benar.- Gunakan
containerddengan konfigurasiSystemdCgroup = trueuntuk menyelaraskan manajemen cgroup sistem operasi dengan container runtime demi menghindari crash OOM.- Pindahkan konfigurasi repositori Kubernetes lama ke repositori komunitas baru
pkgs.k8s.iountuk mendapatkan pembaruan versi minor di atas versi 1.28+.- Gunakan
dpkg_selectionshold pada paketkubelet,kubeadm, dankubectlagar versi komponen kluster terkunci dan tidak ter-upgrade otomatis secara tidak sengaja.- Terapkan CNI (seperti Calico) segera setelah control plane siap untuk mengaktifkan komunikasi jaringan dinamis antar-Pod lintas host.
- Ambil join command secara dinamis menggunakan memori host fakta (
set_factpadalocalhost) daripada meng-hardcode token statis yang cepat kedaluwarsa di file inventory.- Ambil file
admin.confke lokal dengan penyesuaian endpoint IP agar kluster dapat dikendalikan dari jarak jauh tanpa perlu selalu melakukan SSH ke server master.
← Sebelumnya: Apa itu Kubernetes? Berikutnya: Deploy Manifest →