Kubernetes (K3s) with Kube-OVN

Damasukma T

Damasukma T

· 5 min read
Thumbnail

Flannel — default CNI yang digunakan k3s — sederhana, ringan, dan “works out of the box.” Untuk banyak skenario seperti homelab dan cluster kecil, itu sudah cukup. Tapi Flannel tidak fit ke semua kebutuhan networking cluster Kubernetes kita. Salah satunya multi-tenancy: kita butuh pemisahan jaringan yang jelas antar tim/lingkungan (dev/stage/prod), bahkan dengan IP/subnet yang sama (di VPC/logical router yang berbeda).

Kube OVN, berbasis OVN/OVS, menawarkan isolated logical switch/subnet, VPC, floating IP, routing, dan fitur lain yang tidak dimiliki oleh Flannel atau banyak CNI lain. Termasuk: IPAM yang deterministik (static/reservasi IP lebih gampang), EIP/SNAT untuk egress yang konsisten, ACL/policy L3/L4 yang lebih ketat, opsi overlay (Geneve/VXLAN) maupun underlay/VLAN, hingga QoS/bandwidth control dan observabilitas yang lebih rapi.

Kita bisa mengadopsi Kube-OVN di k3s jika membutuhkan fitur-fitur di atas atau ingin cluster ringan dengan rasa VPC.

Setup k3s Without Default CNI

Install k3s tanpa flannel

curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="--flannel-backend=none --disable-network-policy" sh -s

Verifikasi instalasi, seharusnya status node NotReady

kubectl get nodes

image.png

Setup Kube OVN

Install Kube OVN

wget https://raw.githubusercontent.com/kubeovn/kube-ovn/release-1.12/dist/images/install.sh

bash install.sh

image.png

Jika script install nya return error, bisa kita abaikan. Ini adalah error plugin kubectl-ko saat melakukan health check pada daemonset kube-proxy, yang di mana k3s menjalakan kube-proxy embeded di proses k3s, bukan sebagai daemonset/pod.

Verifikasi instalasi

kubectl get po -A -o wide

image.png

test ping cluster ip

root@dama-k3s:~# ping 10.16.0.7
PING 10.16.0.7 (10.16.0.7) 56(84) bytes of data.
64 bytes from 10.16.0.7: icmp_seq=1 ttl=63 time=1.82 ms
^C
--- 10.16.0.7 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 1.821/1.821/1.821/0.000 ms

Membuat Pod dengan static ip

Secara default, saat kita membuat pod akan diberikan dynamic ip dari cni kube-ovn, tapi kita juga bisa mendefinisikan static ip.

apiVersion: v1
kind: Pod
metadata:
  namespace: default
  name: nginx-static
  annotations:
    ovn.kubernetes.io/ip_address: "10.16.1.123"
spec:
  containers:
    - name: nginx
      image: docker.io/library/nginx:alpine

Setelah di-apply kita bisa cek ip pod yang sudah dibuat.

image.png

Untuk subnet atau range ip default nya adalah 10.16.0.0/16, bisa diubah di script install.sh saat proses instalasi.

image.png

Create VPC

image.png

Pod Multiple IP

Multus

kubectl apply -f https://raw.githubusercontent.com/k8snetworkplumbingwg/multus-cni/master/deployments/multus-daemonset-thick.yml
kubectl -n kube-system get pods -l app=multus -w

Fix multus OOM

kubectl -n kube-system patch ds kube-multus-ds --type='json' -p='[
  {"op":"replace","path":"/spec/template/spec/containers/0/image","value":"ghcr.io/k8snetworkplumbingwg/multus-cni:stable-thick"},
  {"op":"add","path":"/spec/template/spec/containers/0/env","value":[{"name":"MALLOC_ARENA_MAX","value":"2"}]},
  {"op":"replace","path":"/spec/template/spec/containers/0/resources","value":{
    "requests":{"cpu":"50m","memory":"64Mi"},
    "limits":{"cpu":"300m","memory":"256Mi"}
  }}
]'
kubectl -n kube-system patch ds kube-multus-ds --type='json' -p='[
  {"op":"add","path":"/spec/template/spec/containers/0/env","value":[{"name":"MALLOC_ARENA_MAX","value":"2"}]},
  {"op":"replace","path":"/spec/template/spec/containers/0/resources","value":{
    "requests":{"cpu":"50m","memory":"64Mi"},
    "limits":{"cpu":"500m","memory":"512Mi"}
  }}
]'

kubectl -n kube-system rollout status ds/kube-multus-ds

Setup 1 isolated subnet

  1. setup eternal network
apiVersion: kubeovn.io/v1
kind: Subnet
metadata:
  name: ovn-vpc-external-network
spec:
  protocol: IPv4
  provider: ovn-vpc-external-network.kube-system
  cidrBlock: 15.15.15.0/24
  gateway: 15.15.15.1  # IP address of the physical gateway
  excludeIps:
  - 15.15.15.1..15.15.15.15
---
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
  name: ovn-vpc-external-network
  namespace: kube-system
spec:
  config: '{
      "cniVersion": "0.3.0",
      "type": "macvlan",
      "master": "ens3",
      "mode": "bridge",
      "ipam": {
        "type": "kube-ovn",
        "server_socket": "/run/openvswitch/kube-ovn-daemon.sock",
        "provider": "ovn-vpc-external-network.kube-system"
      }
    }'

---
kind: ConfigMap
apiVersion: v1
metadata:
  name: ovn-vpc-nat-config
  namespace: kube-system
data:
  image: docker.io/kubeovn/vpc-nat-gateway:v1.12.35

---
kind: ConfigMap
apiVersion: v1
metadata:
  name: ovn-vpc-nat-gw-config
  namespace: kube-system
data:
  enable-vpc-nat-gw: 'true'
  1. Subnet user
apiVersion: v1
kind: Namespace
metadata:
  name: ns-1001

---
kind: Vpc
apiVersion: kubeovn.io/v1
metadata:
  name: test-vpc-1001
spec:
  namespaces:
  - ns-1001
  staticRoutes:
  - cidr: 0.0.0.0/0
    nextHopIP: 10.0.1.254
    policy: policyDst

---
kind: Subnet
apiVersion: kubeovn.io/v1
metadata:
  name: net-1001
spec:
  vpc: test-vpc-1001
  cidrBlock: 10.0.1.0/24
  protocol: IPv4
  provider: net-1001.ns-1001.ovn
  namespaces:
    - ns-1001

---
kind: VpcNatGateway
apiVersion: kubeovn.io/v1
metadata:
  name: gw-1001
spec:
  vpc: test-vpc-1001
  subnet: net-1001
  lanIp: 10.0.1.254
  selector:
    - "kubernetes.io/os: linux"
  externalSubnets:
    - ovn-vpc-external-network

---
kind: IptablesEIP
apiVersion: kubeovn.io/v1
metadata:
  name: eip-1001
spec:
  natGwDp: gw-1001

---
kind: IptablesSnatRule
apiVersion: kubeovn.io/v1
metadata:
  name: snat-1001
spec:
  eip: eip-1001
  internalCIDR: 10.0.1.0/24

---
kind: IptablesDnatRule
apiVersion: kubeovn.io/v1
metadata:
  name: dnat-1001
spec:
  eip: eip-1001
  externalPort: '10001'
  internalIp: 10.0.1.2
  internalPort: '22'
  protocol: tcp

---
apiVersion: k8s.cni.cncf.io/v1
kind: NetworkAttachmentDefinition
metadata:
  name: net-1001
  namespace: ns-1001
spec:
  config: '{
    "cniVersion": "0.3.0",
    "type": "kube-ovn",
    "server_socket": "/run/openvswitch/kube-ovn-daemon.sock",
    "provider": "net-1001.ns-1001.ovn"
  }'

---
apiVersion: v1
kind: Pod
metadata:
  namespace: ns-1001
  name: nginx
  annotations:
    ovn.kubernetes.io/ip_address: "10.0.1.2"
spec:
  containers:
    - name: pod-1001
      image: docker.io/library/nginx:alpine

---
apiVersion: v1
kind: Pod
metadata:
  namespace: ns-1001
  name: ssh
  annotations:
    ovn.kubernetes.io/ip_address: "10.0.1.253"
        #k8s.v1.cni.cncf.io/networks: ns-1002/net-1002
        #net-1002.ns-1002.kubernetes.io/ip_address: 10.0.1.50
          #ovn.kubernetes.io/subnet: net-1002
        #k8s.v1.cni.cncf.io/networks: |
spec:
  containers:
    - name: ssh
      image: alpine:latest
      command: ["sh","-c","echo 'Hello from alpine'; uname -a; sleep 3600"]

---
apiVersion: v1
kind: Pod
metadata:
  name: ubuntu-dind2
  namespace: ns-1001
  annotations:
    ovn.kubernetes.io/ip_address: "10.0.1.3"
spec:
  securityContext:
    runAsUser: 0
  containers:
  - name: dind
    image: radendi/ubuntu-22-dind
    securityContext:
      privileged: true           # aman karena terkurung micro-VM Kata
      allowPrivilegeEscalation: true
    env:
      - name: DOCKER_TLS_CERTDIR
        value: ""
      - name: DOCKER_MTU
        value: "1450"
    tty: true
    stdin: true
    resources:
      requests: { cpu: "1",  memory: "1Gi" }
      limits:   { cpu: "2",  memory: "2Gi" }
Damasukma T

About Damasukma T

I enjoy exploring how systems work, breaking down complex ideas, and experimenting with emerging technologies through hands-on projects. For me, learning isn't just a phase—it's a habit and a way of life.
Copyright © 2025 . All rights reserved.