Tujuan: Kita akan menyimulasikan lingkungan enterprise (perusahaan) di mana kita membangun server DevOps sendiri tanpa bergantung pada platform lain, sehingga kita bisa membangunnya secara mandiri sesuai dengan skala perusahaan.
Arsitektur:
-
Infrastruktur: 2 Server VPS (Virtual Private Server).
-
Orkestrasi: K3s (Versi ringan dari Kubernetes).
-
Penyimpanan: Longhorn (Agar data tidak hilang saat restart).
-
Git Server: Gitea (Alternatif GitHub/GitLab).
-
CI/CD: Drone CI (Otomatisasi untuk build & deploy).
Sebelum memulai, berikut pembagian fungsi kedua VPS tersebut. Kita akan menyebutnya:
-
VPS A (Master): Otak dari cluster, mengatur semua proses.
-
VPS B (Worker): Otot dari cluster, tempat aplikasi berjalan.
Catatan: Ganti tulisan seperti <IP_PRIVATE_VPS_A> dengan alamat IP asli VPS.
Fase 1: Membangun Fondasi Klaster Kubernetes
Di fase ini, kita akan menggabungkan dua server terpisah menjadi satu kesatuan sistem (Cluster).
Langkah 1.1: Persiapan Sistem (Lakukan di KEDUA VPS)
Kita perlu membuka “pintu” (port) agar kedua server bisa saling berkomunikasi dan menginstal driver penyimpanan.
Update dependensi dan install open iscsi
sudo apt update && sudo apt upgrade -y sudo apt-get install -y open-iscsi # Dibutuhkan oleh Longhorn nanti
Instalasi K3s (Kubernetes Ringan)
Instalasi di VPS A (Master): Perintah ini akan menginstal K3s sebagai server.
curl -sfL https://get.k3s.io | sh -s - server \ --node-ip <IP_PRIVATE_VPS_A> \ --flannel-iface <NAMA_INTERFACE_PUBLIK> \ --resolv-conf /etc/resolv.confNAMA_INTERFACE_PUBLIKbiasanyaeth0atauens3. Cek dengan perintahip a
Agar VPS B bisa bergabung, ia butuh “password” (token) dari Master.
sudo cat /var/lib/rancher/k3s/server/node-token
Instalasi di VPS B (Worker): Perintah ini menginstal K3s sebagai agen yang melapor ke Master.
curl -sfL https://get.k3s.io | K3S_URL=https://<IP_PRIVATE_VPS_A>:6443 K3S_TOKEN=<TOKEN_DARI_MASTER> sh -s - agent \ --node-ip <IP_PRIVATE_VPS_B> \ --flannel-iface <NAMA_INTERFACE_PUBLIK> \ --resolv-conf /etc/resolv.conf
Kembali ke VPS A, cek apakah kedua node sudah siap. Pastikan statusnya Ready.
kubectl get nodes
Fase 2: Menyiapkan Layanan Inti
Sekarang kita akan men-deploy semua layanan pendukung ke dalam klaster. Pada tutorial ini saya menyimpan semua file konfigurasi .yaml di VPS A.
Langkah 2.1: Deploy Longhorn untuk Penyimpanan
helm terinstal ).helm repo add longhorn https://charts.longhorn.io helm repo update helm install longhorn longhorn/longhorn --namespace longhorn-system --create-namespace
Tunggu beberapa menit. Verifikasi bahwa semua pod di namespace longhorn-system berjalan dengan command berikut:
kubectl get pods -n longhorn-system
Langkah 2.2: Deploy Registry Docker Lokal
local-registry.yaml. Ini akan menjadi tempat kita menyimpan image Docker.# local-registry.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: local-registry
namespace: default
labels:
app: local-registry
spec:
replicas: 1
selector:
matchLabels:
app: local-registry
template:
metadata:
labels:
app: local-registry
spec:
containers:
- name: registry
image: registry:2
ports:
- containerPort: 5000
---
apiVersion: v1
kind: Service
metadata:
name: local-registry-svc
namespace: default
spec:
selector:
app: local-registry
ports:
- protocol: TCP
port: 5000
targetPort: 5000
Selanjutnya jalankan command berikut:
kubectl apply -f local-registry.yaml
Langkah 2.3: Deploy Gitea (Git Server )
gitea-manual.yaml.# gitea-manual.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: gitea-pvc
namespace: default
spec:
accessModes:
- ReadWriteOnce
storageClassName: longhorn
resources:
requests:
storage: 10Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: gitea
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: gitea
template:
metadata:
labels:
app: gitea
spec:
containers:
- name: gitea
image: gitea/gitea:latest
ports:
- containerPort: 3000
name: http
- containerPort: 22
name: ssh
volumeMounts:
- name: gitea-data
mountPath: /data
volumes:
- name: gitea-data
persistentVolumeClaim:
claimName: gitea-pvc
---
apiVersion: v1
kind: Service
metadata:
name: gitea-svc
namespace: default
spec:
type: NodePort
selector:
app: gitea
ports:
- name: http
protocol: TCP
port: 3000
targetPort: 3000
- name: ssh
protocol: TCP
port: 22
targetPort: 22
Kemudian jalankan command berikut:
kubectl apply -f gitea-manual.yaml
Setelah Gitea berjalan, akses melalui http://<IP_PUBLIK_VPS>:<NODE_PORT> untuk menyelesaikan instalasi awal dan membuat akun admin.
Langkah 2.4: Deploy Drone CI (Server & Runner )
Buat Izin RBAC: Buat file drone-cluster-rbac.yaml.
# drone-cluster-rbac.yaml apiVersion: v1 kind: ServiceAccount metadata: name: drone-runner-sa namespace: default --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: drone-runner-cluster-role rules: - apiGroups: [""] resources: ["pods", "pods/log", "secrets", "events", "services", "persistentvolumeclaims"] verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: drone-runner-cluster-role-binding subjects: - kind: ServiceAccount name: drone-runner-sa namespace: default roleRef: kind: ClusterRole name: drone-runner-cluster-role apiGroup: rbac.authorization.k8s.io
Terapkan file ini:
kubectl apply -f drone-cluster-rbac.yaml
Buat Aplikasi OAuth di Gitea:
Di UI Gitea, pergi ke Settings -> Applications -> Manage OAuth2 Applications. Buat aplikasi baru dan masukkan http://<IP_PUBLIK_VPS>:<NODE_PORT_DRONE>/login sebagai Redirect URI. Salin Client ID dan Client Secret yang dihasilkan.
Buat Manifes Drone: Buat file drone-manual.yaml
# drone-manual.yaml
apiVersion: v1
kind: Secret
metadata:
name: drone-secret
namespace: default
type: Opaque
stringData:
DRONE_RPC_SECRET: "GantiDenganPasswordDroneYangSangatPanjangDanAcak"
DRONE_GITEA_CLIENT_ID: "PASTE_CLIENT_ID_DARI_GITEA"
DRONE_GITEA_CLIENT_SECRET: "PASTE_CLIENT_SECRET_DARI_GITEA"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: drone-server
namespace: default
spec:
replicas: 1
selector: { matchLabels: { app: drone-server } }
template:
metadata: { labels: { app: drone-server } }
spec:
containers:
- name: drone
image: drone/drone:2
ports:
- containerPort: 80
env:
- name: DRONE_SERVER_PROTO
value: "http"
- name: DRONE_SERVER_HOST
value: "<IP_PUBLIK_VPS_A>:<NODE_PORT_DRONE>"
- name: DRONE_GITEA_SERVER
value: "http://<IP_PUBLIK_VPS_A>:<NODE_PORT_GITEA>"
- name: DRONE_RPC_SECRET
valueFrom: { secretKeyRef: { name: drone-secret, key: DRONE_RPC_SECRET } }
- name: DRONE_GITEA_CLIENT_ID
valueFrom: { secretKeyRef: { name: drone-secret, key: DRONE_GITEA_CLIENT_ID } }
- name: DRONE_GITEA_CLIENT_SECRET
valueFrom: { secretKeyRef: { name: drone-secret, key: DRONE_GITEA_CLIENT_SECRET } }
- name: DRONE_USER_CREATE
value: "username:<NAMA_ADMIN_GITEA_ANDA>,admin:true"
---
apiVersion: v1
kind: Service
metadata:
name: drone-svc
namespace: default
spec:
type: NodePort
selector: { app: drone-server }
ports:
- name: http
port: 80
targetPort: 80
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: drone-runner
namespace: default
spec:
replicas: 1
selector: { matchLabels: { app: drone-runner } }
template:
metadata: { labels: { app: drone-runner } }
spec:
serviceAccountName: drone-runner-sa
containers:
- name: runner
image: drone/drone-runner-kube:latest
ports:
- containerPort: 3000
env:
- name: DRONE_RPC_HOST
value: drone-svc
- name: DRONE_RPC_PROTO
value: http
- name: DRONE_RPC_SECRET
valueFrom: { secretKeyRef: { name: drone-secret, key: DRONE_RPC_SECRET } }
Terapkan file ini:
kubectl apply -f drone-manual.yaml
Fase 3: Menjalankan Pipeline CI/CD
Langkah 3.1: Persiapan Kode Aplikasi
Di komputer lokal, buat sebuah repositori Git baru dengan file-file berikut:
- main.go
package main import ("fmt"; "net/http" ) func main() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request ) { fmt.Fprintf(w, "Hello, Anda telah berhasil men-deploy aplikasi dengan CI/CD!") }) http.ListenAndServe(":8080", nil ) } - deployment.yaml
apiVersion: apps/v1 kind: Deployment metadata: name: hello-kubernetes spec: replicas: 1 selector: { matchLabels: { app: hello-kubernetes } } template: metadata: { labels: { app: hello-kubernetes } } spec: containers: - name: app image: IMAGE_PLACEHOLDER ports: - containerPort: 8080 --- apiVersion: v1 kind: Service metadata: name: hello-kubernetes-svc spec: type: NodePort selector: { app: hello-kubernetes } ports: - port: 80 targetPort: 8080 - .drone.yml
kind: pipeline type: kubernetes name: default steps: - name: build-and-push-with-kaniko image: gcr.io/kaniko-project/executor:v1.9.1-debug commands: - mkdir -p /kaniko/build - cp -r ./* /kaniko/build/ - /kaniko/executor --context dir:///kaniko/build/ --dockerfile /kaniko/build/Dockerfile --destination local-registry-svc:5000/hello-kubernetes:${DRONE_COMMIT_SHA:0:7} --skip-pull --insecure - name: deploy-to-kubernetes image: bitnami/kubectl commands: - sed -i "s|IMAGE_PLACEHOLDER|local-registry-svc:5000/hello-kubernetes:${DRONE_COMMIT_SHA:0:7}|g" deployment.yaml - kubectl apply -f deployment.yaml - Dockerfile
FROM golang:1.19-alpine AS builder WORKDIR /app COPY . . RUN go build -o main . FROM alpine:latest WORKDIR /app COPY --from=builder /app/main . EXPOSE 8080 CMD ["./main"]
Langkah 3.2: Proses “Image Pre-warming”
Untuk menghindari ketergantungan pada internet saat pipeline berjalan, kita akan “menyelundupkan” image yang dibutuhkan ke dalam klaster. Jalankan command berikut di VPS A dan VPS B.
docker pull gcr.io/kaniko-project/executor:v1.9.1-debug docker pull bitnami/kubectl:latest docker pull golang:1.19-alpine
Langkah 3.3: Eksekusi!
clone, build, hingga deploy. Untuk mengakses aplikasi, cari NodePort dari service hello-kubernetes-svc dan buka http://<IP_PUBLIK_VPS>:<NODE_PORT_APLIKASI>.