Penggunaan ephemeral virtual machine (VM) atau bare-metal node pada CI/CD runner sering memicu kegagalan alokasi IP atau DHCP pool exhaustion. Ketika pipeline membuat dan memusnahkan puluhan VM dalam hitungan jam tanpa mekanisme release lease yang benar, subnet kehabisan alamat yang tersedia. Dampaknya, runner baru gagal mendapatkan konfigurasi jaringan dan pipeline berhenti dengan status NETWORK_ERROR.

Akar Masalah DHCP Starvation pada Ephemeral Runner

Masalah ini serupa dengan skenario klasik screaming host: host yang terus meminta sewa IP baru dalam volume tinggi dan waktu singkat. Pada infrastruktur CI/CD modern, ada tiga penyebab utama terbentuknya kondisi ini:

  • DUID dan MAC Address Churn: Sistem operasi modern berbasis systemd-networkd atau NetworkManager membuat DHCP Unique Identifier (DUID) baru berdasarkan /etc/machine-id setiap kali instance di-spawn dari image dasar. Server DHCP menganggap setiap boot sebagai host fisik baru dan menerbitkan IP baru.
  • Ungraceful Teardown: Pipeline atau autoscaler runner langsung menghancurkan VM melalui API hypervisor (seperti Proxmox, OpenStack, atau Libvirt) tanpa menjalankan clean shutdown di tingkat OS. Akibatnya, paket DHCPRELEASE tidak pernah dikirimkan ke server DHCP.
  • Default Lease Time Terlalu Panjang: Konfigurasi server DHCP bawaan umumnya mempertahankan sewa IP selama 12 hingga 24 jam. Jika durasi hidup VM hanya 5 menit, IP tersebut tertahan dan tidak bisa dialokasikan kembali ke runner lain selama berjam-jam.

Mitigasi 1: Graceful Release Hook pada Pipeline CI

Langkah preventif pertama adalah memastikan OS guest melepas sewa IP sebelum hypervisor mematikan instance. Perintah dhclient -r atau pemanggilan API resolvectl / systemd-networkd mengirimkan paket unicast DHCPRELEASE ke DHCP server.

Tambahkan trap signal atau hook after_script pada runner pipeline sebelum trigger pemusnahan node:

#!/usr/bin/env bash
set -e

# Hook cleanup sebelum VM dihancurkan
cleanup_network() {
  echo "[INFO] Melepas DHCP lease..."
  local iface="eth0"

  if command -v dhclient > /dev/null 2&1; then
    sudo dhclient -r "$iface" || true
  elif command -v networkctl > /dev/null 2&1; then
    sudo networkctl reconfigure "$iface" || true
  fi
}

trap cleanup_network EXIT

# Jalankan task build/test di sini
run_tests() {
  echo "Running CI jobs..."
  # test suite logic
}

run_tests
Catatan: Jika VM dimatikan langsung dari hypervisor (ACPI power off), pastikan skrip init guest VM menjalankan graceful network down sebelum shutdown selesai.

Mitigasi 2: Mempertahankan DUID Konsisten pada Golden Image

Jika instance di-clone dari golden image yang sama, pastikan machine-id tidak di-generate ulang menjadi random DUID jika VM menggunakan MAC address statis yang dialokasikan ulang oleh pool orchestrator. Sebaliknya, jika runner menggunakan MAC acak, pastikan klien DHCP dikonfigurasi untuk menggunakan Client Identifier berbasis hardware MAC (RFC 2132), bukan DUID (RFC 4361).

Contoh konfigurasi /etc/dhcp/dhclient.conf:

# Paksa client ID menggunakan MAC address hardware, bukan DUID
send dhcp-client-identifier = hardware;

Atau jika menggunakan systemd-networkd (misal: /etc/systemd/network/10-eth0.network):

[Match]
Name=eth0

[Network]
DHCP=yes

[DHCPv4]
ClientIdentifier=mac
SendRelease=true

Mitigasi 3: Tuning Lease Time pada Dnsmasq dan Kea DHCP

Server DHCP yang melayani subnet pengujian CI harus memiliki konfigurasi lease time yang sangat agresif dibandingkan subnet office/production.

Dnsmasq

Pada Dnsmasq, atur rentang waktu sewa menjadi 5 hingga 10 menit pada file konfigurasi (misal: /etc/dnsmasq.d/ci-subnet.conf):

# Format: dhcp-range=<start-ip>,<end-ip>,<netmask>,<lease-time>
dhcp-range=10.20.0.50,10.20.0.250,255.255.255.0,5m

# Paksa update langsung lease database
dhcp-authoritative

Kea DHCPv4

Pada ISC Kea DHCP, modifikasi blok subnet4 pada kea-dhcp4.conf:

{
  "Dhcp4": {
    "subnet4": [
      {
        "subnet": "10.20.0.0/24",
        "pools": [ { "pool": "10.20.0.50 - 10.20.0.250" } ],
        "valid-lifetime": 300,
        "renew-timer": 150,
        "rebind-timer": 250
      }
    ]
  }
}

Mitigasi 4: Otomasi Audit Lease dan DUID Churn

Pantau penggunaan pool dan deteksi stale lease secara berkala menggunakan audit script CLI sederhana untuk memeriksa file lease server (misalnya /var/lib/misc/dnsmasq.leases):

#!/usr/bin/env python3
import time
import sys

LEASE_FILE = "/var/lib/misc/dnsmasq.leases"
THRESHOLD_SECONDS = 300

def parse_leases(file_path):
    now = int(time.time())
    active_leases = 0
    expired_leases = 0
    clients = {}

    try:
        with open(file_path, "r") as f:
            for line in f:
                parts = line.strip().split()
                if len(parts) < 4:
                    continue
                expires_at, mac, ip, hostname = parts[0:4]
                expires_at = int(expires_at)

                if expires_at > now:
                    active_leases += 1
                    clients[mac] = ip
                else:
                    expired_leases += 1
    except FileNotFoundError:
        print(f"File lease {file_path} tidak ditemukan.", file=sys.stderr)
        sys.exit(1)

    print(f"Active Leases  : {active_leases}")
    print(f"Expired Leases : {expired_leases}")
    print(f"Unique MACs    : {len(clients)}")

    # Alarm sederhana bila pemakaian di atas 80% (asumsi pool size = 200)
    pool_size = 200
    if active_leases > (pool_size * 0.8):
        print("[WARNING] DHCP Pool melebihi 80% kapasitas!")

if __name__ == "__main__":
    parse_leases(LEASE_FILE)

Kesimpulan

Mencegah DHCP pool exhaustion pada kluster runner memerlukan koordinasi dua sisi: guest instance harus membersihkan status network via graceful hook (SendRelease=true atau dhclient -r), sementara server DHCP (Dnsmasq/Kea) harus menggunakan short lease time (5 menit) dan pengenalan identitas berbasis hardware MAC. Kombinasi ini menjamin IP segera kembali ke pool begitu VM selesai dihancurkan.