wayanjimmy
ENID

Setup K3s Single Node Cluster, GitOps dengan Flux

Latar Belakang #

Seperti yang saya ceritakan sebelum-nya disini dan disini, saya sedang mengembangkan Homelab pribadi untuk men-support workflow development sehari-hari. Salah satu komponen penting dari setup ini adalah bagaimana saya mengelola deployment aplikasi ke cluster Kubernetes dengan cara yang lebih deklaratif menggunakan konsep GitOps.

GitOps adalah cara mengelola infrastruktur dan aplikasi melalui Git, di mana state yang diinginkan didefinisikan di repository dan tool seperti Flux akan memastikan cluster sesuai dengan definisi tersebut.

Ada beberapa tool GitOps yang populer saat ini seperti ArgoCD dan Flux. Saya memilih Flux karena integrasi native-nya dengan SOPS untuk enkripsi secret, serta kemampuan image automation yang memungkinkan deployment otomatis ketika ada image Docker baru di push ke registry.

Namun saya tidak bisa share 1:1 bagaimana setup saya karena repo github nya saya buat private. Untuk itu saya coba replicate bagaimana saya setup awal, barangkali temen2 disini ada yang tertarik untuk bikin serupa wqwq.

Persyaratan #

Install:

Semua tools diatas bisa di-install manual atau pakai arkade, saya sendiri nyaman pakai arkade. Contohnya:

bash
arkade get kubectl flux sops

terkait lima-vm, bisa cek tulisan saya sebelum-nya membangun-local-kubernetes-development-dengan-lima-vm.

Setup K3s Single Node #

Buat dulu lima-vm instance-nya

bash
limactl create template:k3s \
    --name=k3s-demo \
    --cpus=2 \
    --memory=2 \
    --disk=20 \
    --yes

setelah instance dibuat, output dibagian akhir akan menunjukan file kubeconfig yang akan digunakan untuk mengatur cluster lewat kubectl

bash
export KUBECONFIG="/home/jimbo/.lima/k3s-demo/copied-from-guest/kubeconfig.yaml"
kubectl...

Manajemen Environment Variable #

pastikan untuk export variable diatas sebelum execute perintah terkait kubectl, buat kenyamanan saya saran pakai direnv

buat file .direnv seperti ini

bash
export KUBECONFIG="/home/jimbo/.lima/k3s-demo/copied-from-guest/kubeconfig.yaml"

semua variable yang diperlukan, nanti kita bisa tambahkan seiring dengan keperluan di file diatas.

Cek koneksi ke k3s cluster dengan kubectl #

Eksekusi perintah berikut

bash
$ kubectl cluster-info
Kubernetes control plane is running at https://127.0.0.1:6443
CoreDNS is running at https://127.0.0.1:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
Metrics-server is running at https://127.0.0.1:6443/api/v1/namespaces/kube-system/services/https:metrics-server:https/proxy

To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.

Installing Flux CD #

Buatlah sebuah git repo, yang nanti-nya repo ini adalah sebagai source of truth dari state k8s yang kita manage, ini repo contoh yang saya buat https://github.com/wayanjimmy/k3s-demo.

Sekarang kita perlu boostrap fluxcd dulu ke k3s cluster nya.

bash
flux bootstrap git \
    --url=ssh://[email protected]/wayanjimmy/k3s-demo.git \
    --private-key-file=/home/jimbo/.ssh/id_ed25519 \
    --branch=main \
    --path=clusters/k3s-demo \
    --components-extra image-reflector-controller,image-automation-controller

pastikan ssh key yang kita gunakan sudah terdaftar di github, sehingga proses boostrap dapat mengakses repo, setelah bootstrap berhasil, eksekusi perintah berikut memastikan flux sudah ready.

bash
$ flux check
 checking prerequisites
 Kubernetes 1.34.4+k3s1 >=1.32.0-0
 checking version in cluster
 distribution: flux-v2.7.5
 bootstrapped: true
 checking controllers
 helm-controller: deployment ready
 ghcr.io/fluxcd/helm-controller:v1.4.5
 image-automation-controller: deployment ready
 ghcr.io/fluxcd/image-automation-controller:v1.0.4
 image-reflector-controller: deployment ready
 ghcr.io/fluxcd/image-reflector-controller:v1.0.4
 kustomize-controller: deployment ready
 ghcr.io/fluxcd/kustomize-controller:v1.7.3
 notification-controller: deployment ready
 ghcr.io/fluxcd/notification-controller:v1.7.5
 source-controller: deployment ready
 ghcr.io/fluxcd/source-controller:v1.7.4
 checking crds
 alerts.notification.toolkit.fluxcd.io/v1beta3
 buckets.source.toolkit.fluxcd.io/v1
 externalartifacts.source.toolkit.fluxcd.io/v1
 gitrepositories.source.toolkit.fluxcd.io/v1
 helmcharts.source.toolkit.fluxcd.io/v1
 helmreleases.helm.toolkit.fluxcd.io/v2
 helmrepositories.source.toolkit.fluxcd.io/v1
 imagepolicies.image.toolkit.fluxcd.io/v1
 imagerepositories.image.toolkit.fluxcd.io/v1
 imageupdateautomations.image.toolkit.fluxcd.io/v1
 kustomizations.kustomize.toolkit.fluxcd.io/v1
 ocirepositories.source.toolkit.fluxcd.io/v1
 providers.notification.toolkit.fluxcd.io/v1beta3
 receivers.notification.toolkit.fluxcd.io/v1
 all checks passed

kalau mau cek workload terkait flux bisa pakai perintah berikut

bash
$ kubectl get pods -n flux-system
NAME                                       READY   STATUS    RESTARTS   AGE
helm-controller-68578f8447-t6v2k           1/1     Running   0          64s
kustomize-controller-7ddfbb5875-jvlvm      1/1     Running   0          64s
notification-controller-6d766f87cf-5lrvf   1/1     Running   0          64s
source-controller-6679d8bdb-dgdgs          1/1     Running   0          64s

sampai disini flux sudah terinstal, lanjut ke proses deployment apps.

Testing deploy Aplikasi Demo #

Kita akan menulis yaml menggunakan kustomization, yg merupakan semacam layer abstraksi untuk manajemen yaml k8s, namun tidak akan dibahas ditulisan ini.

Buat file berikut.

clusters/k3s-demo/demo-apps-kustomization.yaml

yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: demo-apps
  namespace: flux-system
spec:
  interval: 1m
  path: ./clusters/k3s-demo/demo-apps
  prune: true
  sourceRef:
    kind: GitRepository
    name: flux-system
  # decryption:
  #   provider: sops
  #   secretRef:
  #     name: sops-age

clusters/k3s-demo/demo-apps/namespace.yaml

yaml
apiVersion: v1
kind: Namespace
metadata:
  name: demo-apps

clusters/k3s-demo/demo-apps/nginx-demo.yaml

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-demo
  namespace: demo-apps
  labels:
    app: nginx-demo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx-demo
  template:
    metadata:
      labels:
        app: nginx-demo
    spec:
      containers:
      - name: nginx
        image: nginx:alpine
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-demo
  namespace: demo-apps
spec:
  type: NodePort
  selector:
    app: nginx-demo
  ports:
  - port: 80
    targetPort: 80
    nodePort: 30081

clusters/k3s-demo/demo-apps/kustomization.yaml

yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: demo-apps
resources:
- namespace.yaml
- nginx-demo.yaml
# - secret.yaml

Deployment nginx-demo sudah di expose melalui nodeport sehingga nanti kita bisa cek melalui port tersebut apakah aplikasi berjalan normal.

Nah bagaimana cara deployment apps ini melalui flow git ops ? Flux akan secara periodik, berdasarkan interval clusters/k3s-demo/demo-apps-kustomization.yaml, memastikan apakah definisi git repo sudah sync dengan state yang sebenarnya.

Yang perlu kita lakukan hanyalah commit semua file yaml ini dan push ke git, berikut adalah gambaran kondisi repo setelah melakukan semua file diatas di-commit https://github.com/wayanjimmy/k3s-demo/tree/81635f0f880c53a63bf8c8c53752b5d1b873a416/clusters/k3s-demo

Tunggu beberapa saat maka flux akan rekonsiliasi state-nya dan pod akan muncul

bash
$ kubectl get pods -n demo-apps
NAME                          READY   STATUS    RESTARTS   AGE
nginx-demo-6fd7fbb8f4-4jhjj   1/1     Running   0          136m

Enkripsi Secret dengan SOPS #

Flux memiliki integrasi dengan sops yang bisa kita gunakan agar secret yang di commit ke git repo tidak telanjang. Untuk referensi lebih lanjut tentang Flux dan SOPS, bisa cek tulisan Budiman JoJo.

bash
mkdir -p secrets

tambahkan .gitignore di di folder secrets menghindari private key yg akan kita generate ikut ke-commit.

secrets/.gitignore

text
*
!.gitignore

Generate sops private & public key pair

bash
$ age-keygen -o secrets/flux-age-key.txt

kalau kamu cek struktur file flux-age-key.txt akan seperti ini

text
# created: 2026-02-20T07:19:42Z
# public key: age1h44rlvy9c2ffytt78msmhjtuyuwtpyc6jje7fgjkg4x7l0hpt5dsfxlf9v
AGE-SECRET-KEY-1ECFASJKZTGVTM0K2GV4QYUXGSKL0S2DG43SWV68GWLC455LAFMYQSLGHQA

ada public key dan secret key, public key bisa kita tambahkan di-repo agar semua orang bisa melakukan enkripsi, sedangkan private akan kita tambahkan sebagai secret di k8s cluster, agar cluster dapat melakukan dekripsi.

bash
$ kubectl create secret generic sops-age \
     --namespace=flux-system \
     --from-file=age.agekey=$(pwd)/secrets/flux-age-key.txt
secret/sops-age created

Pastikan secret sops-age sudah terbuat

bash
$ kubectl get secret sops-age -n flux-system
NAME       TYPE     DATA   AGE
sops-age   Opaque   1      2m28s

Ok ditahap ini kita tinggal mengenalkan ke demo-apps kustomization agar mengenali sops-age sebagai key untuk decrypt secret, hilangkan tanda komentar terkait dekripsi yg sebelum-nya dalam tag komentar.

clusters/k3s-demo/demo-apps-kustomization.yaml

yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: demo-apps
  namespace: flux-system
spec:
  interval: 1m
  path: ./clusters/k3s-demo/demo-apps
  prune: true
  sourceRef:
    kind: GitRepository
    name: flux-system
  decryption:                       
    provider: sops
    secretRef:                      
      name: sops-age

commit semua changes dan push dan tunggu hingga flux melakukan rekonsiliasi, kamu bisa watch dengan perintah flux untuk lebih yakin.

bash
$ flux get kustomizations -n flux-system --watch
NAME            REVISION                SUSPENDED       READY   MESSAGE
demo-apps       main@sha1:f33a03f7      False           True    Applied revision: main@sha1:f33a03f7
flux-system     main@sha1:f33a03f7      False   True    Applied revision: main@sha1:f33a03f7

Jika sudah delete pods agar konfigurasi yang baru ter-aplikasi di pod yang baru

bash
$ kubectl delete pods -l app=demo-app -n demo-apps

Memastikan Pod bisa Decrypt Secret #

Sampai pada titik ini kita tinggal memastikan apakah workload demo-apps sudah bisa melakukan decrypt terhadap secret yang sudah di encrypt menggunakan sops.

Buat file secret.

bash
$ cat clusters/k3s-demo/demo-apps/secret.yaml
apiVersion: v1
stringData:
  database: somedatabase
  password: secret
  username: admin
kind: Secret
metadata:
  name: db-credentials
  namespace: demo-apps

Encrypt file secret dengan sops

bash
$ sops --encrypt \
    --age $(echo $SOPS_PUBLIC_KEY) \
    --encrypted-regex '^(data|stringData)$' \
    --in-place clusters/k3s-demo/demo-apps/secret.yaml

setelah itu file akan ter-enkripsi seperti berikut

daftar-kan secret

clusters/k3s-demo/demo-apps/kustomization.yaml

yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: demo-apps
resources:
- namespace.yaml
- nginx-demo.yaml
- secret.yaml

Nah bagaimana memastikan kalau pod sudah dapat mengakses secret dalam keadaan decrypted? Kita bisa tambah initContainers yang mana containers ini akan dijalankan terlebih dahulu sebelum containers nginx dalam nginx-demo dijalankan.

clusters/k3s-demo/demo-apps/nginx-demo.yaml

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-demo
  namespace: demo-apps
  labels:
    app: nginx-demo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx-demo
  template:
    metadata:
      labels:
        app: nginx-demo
    spec:
      initContainers:                    
      - name: secret-test
        image: busybox:1.36
        command:                         
        - sh
        - -c
        - |
          echo "=== SOPS Secret Decryption Test ==="
          echo "DB_HOST: $DB_HOST"
        envFrom:                         
        - secretRef:                     
            name: db-credentials
      containers:
      - name: nginx
        image: nginx:alpine
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-demo
  namespace: demo-apps
spec:
  type: NodePort
  selector:
    app: nginx-demo
  ports:
  - port: 80
    targetPort: 80
    nodePort: 30081

Commit semua changes dan push, tunggu flux rekonsiliasi, kamu bisa watch log init containers tadi dengan perintah

bash
jimbo@ser4:~/.../k3s-demo$ kubectl logs -f -l app=nginx-demo -c secret-test -n demo-apps
=== SOPS Secret Decryption Test ===
DB_HOST: postgres.demo-apps.svc.cluster.local

nah disana kamu akan lihat kalau env DB_HOST dapat diakses dalam keadaan decrypted, sedangkan secret yang di git repo juga aman dalam keadaan encrypted.

Sampai saat ini proses setup flux dengan sops sudah beres!

Kesimpulan #

Sampai disini kita sudah memiliki single-node Kubernetes cluster yang dikelola dengan GitOps menggunakan Flux. Dengan setup ini, semua deployment aplikasi dapat dilakukan hanya dengan git push, sementara secret tetap aman dengan enkripsi SOPS.

Kombinasi k3s + lima-vm sangat cocok untuk ngoprek Homelab tanpa harus punya hardware khusus, semuanya bisa berjalan di laptop yang kita gunakan sehari-hari.

Berlangganan