Bali, Indonesia
Insights

Menjinakkan OOM-Killer dan UDP Packet Drops: Isolasi Resource CoreDNS dan PowerDNS dengan cgroups v2

October 13, 2026
0:00
Ketika query rate pada edge DNS melonjak melewati ambang 150.000 QPS, margin toleransi sistem operasi terhadap miskonfigurasi resource praktis lenyap. Pada infrastruktur modern berkecepatan tinggi, perpaduan antara CoreDNS sebagai caching layer dan PowerDNS Authoritative dengan backend relational database (seperti PostgreSQL atau MariaDB Galera) kerap menghadapi skenario degradasi katastropik: replikasi database mengalami desinkronisasi atau I/O lag, thread pool backend PowerDNS jenuh, dan CoreDNS menimbun in-flight requests di dalam Go runtime heap. Tanpa batas isolasi ketat, Linux Kernel OOM (Out-Of-Memory) killer akan memilih daemon DNS sebagai target eliminasi karena konsumsi residen memorinya melonjak secara drastis. Masalah ini tidak dapat diselesaikan hanya dengan menambah alokasi RAM atau restart otomatis via systemd. Menjaga reliabilitas daemon DNS memerlukan pemahaman mendalam tentang bagaimana kernel mengelola UDP socket buffers, bagaimana cgroups v2 mengontrol alokasi memory anonim versus page cache melalui Pressure Stall Information (PSI), serta bagaimana konfigurasi socket multiplexing (SO_REUSEPORT) mendistribusikan beban paket secara adil ke seluruh thread CPU.
Ketika database backend PowerDNS mengalami spike latensi replikasi, kueri yang belum terlayani tertahan di antrean distributor threads. Setiap request DNS over UDP bersifat stateless, namun daemon penyelesai mempertahankan status konkurensi di memori user-space hingga timeout tercapai. Jika ribuan client terus mengirimkan query rekursif, CoreDNS akan mengalokasikan goroutine tambahan beserta buffer alokasi internal untuk melacak query upstream yang menggantung.
TEXT
+-------------------------------------------------------------+
|                      Edge DNS Node                          |
|                                                             |
|  UDP Ingress: 53                                            |
|        |                                                    |
|        v                                                    |
|  +---------------------+        L1 Cache Hit                |
|  | CoreDNS (Caching)   |-------------------------> Response |
|  | cgroups: dns.slice  |                                    |
|  +---------------------+                                    |
|        | (Cache Miss / In-flight Timeout)                   |
|        v                                                    |
|  +---------------------+        DB Read                     |
|  | PowerDNS (Auth)     |-------------------------> Local DB |
|  | cgroups: pdns.slice |                           Replica  |
|  +---------------------+                              |     |
|        |                                          (Lag/Lock)|
|  (Thread Starvation)                                  v     |
|        |                                         Replication|
|   Memory Ballooning -> PSI Spike -> OOM Killer!     Fails   |
+-------------------------------------------------------------+
Secara bersamaan, kernel network subsystem menerima paket UDP baru pada ring buffer kartu jaringan (NIC). Jika CoreDNS atau PowerDNS terhambat oleh GC (Garbage Collection) pause atau lock contention pada database connection pool, daemon gagal mengeksekusi syscall recvmsg() secara cepat. Dampaknya, socket receive buffer meluap, memicu metrik RcvbufErrors dan silent drop paket UDP yang berujentung pada downtime resolver bagi aplikasi klien.
Menjalankan daemon DNS dalam satu cgroup root bersama service sistem lainnya adalah resep petaka. Dengan Linux cgroups v2, kontrol alokasi CPU, I/O, dan Memori dapat dipetakan secara granular melalui systemd slices. Isolasi ini memastikan bahwa kebocoran memori pada backend database tidak menenggelamkan CoreDNS, dan lonjakan thread PowerDNS tidak merebut alokasi CPU dari sistem logging atau metrik. Kita buat hierarki slice khusus di /etc/systemd/system/dns-infrastructure.slice:
INI
[Unit]
Description=Dedicated cgroup slice for DNS services
Before=slices.target

[Slice]
# Alokasi prioritas CPU (skala 1-10000, default 100)
CPUWeight=1000

# Batasi IO starvation menggunakan PSI limit
IOWeight=800

# Proteksi memori minimal agar page cache daemon DNS tidak direbut proses batch
MemoryMin=1G
MemoryLow=2G
MemoryHigh=3.5G
MemoryMax=4G

# Cegah krisis swap thrashing
MemorySwapMax=0
Langkah berikutnya adalah mengaitkan unit file CoreDNS (/etc/systemd/system/coredns.service.d/override.conf) ke slice tersebut dan mengatur perlindungan OOM:
INI
[Service]
Slice=dns-infrastructure.slice
LimitNOFILE=1048576
LimitMEMLOCK=infinity

# Lindungi proses dari OOM Score Killer (-1000 adalah proteksi absolut)
OOMScoreAdjust=-900

# Terapkan isolasi memori spesifik proses
MemoryHigh=1.8G
MemoryMax=2G
Restart=always
RestartSec=2s

# Matikan swap sama sekali untuk CoreDNS guna menjaga latensi sub-milidetik
MemorySwapMax=0
TasksMax=8192
Untuk PowerDNS Authoritative (/etc/systemd/system/pdns.service.d/override.conf):
INI
[Service]
Slice=dns-infrastructure.slice
LimitNOFILE=1048576
OOMScoreAdjust=-800
MemoryHigh=1.5G
MemoryMax=1.8G
Restart=on-failure
RestartSec=3s
TasksMax=4096
Dengan parameter di atas, jika CoreDNS mendekati alokasi 1.8GB (MemoryHigh), kernel akan memperlambat alokasi dan memaksa proses mereklamasi memori tanpa langsung mengeksekusi proses melalui SIGKILL. Jika batas MemoryMax 2GB tertembus, hanya instans CoreDNS yang diterminasi dan segera direstart oleh systemd dalam 2 detik, tanpa mengganggu daemon lain di server.
Protokol DNS berbasis UDP sangat rentan terhadap micro-bursts. Konfigurasi default kernel Linux untuk ukuran buffer UDP (net.core.rmem_default sekitar 212KB) sering kali terisi penuh hanya dalam hitungan milidetik saat burst query terjadi. Untuk memvalidasi packet drop pada level kernel, jalankan perintah diagnostik berikut:
BASH
# Periksa apakah ada socket buffer overflow pada UDP
netstat -su | grep -E '(RcvbufErrors|SndbufErrors|InErrors)'

# Atau periksa statistik drop per interface
ip -s link show dev eth0

# Amati socket queue depth pada port 53 secara real-time
ss -n -u -a '( sport = :53 or dport = :53 )'
Jika nilai RcvbufErrors terus bertambah, ukuran buffer sistem dan kemampuan pemrosesan socket harus ditingkatkan. Terapkan konfigurasi kernel berikut di /etc/sysctl.d/99-dns-network.conf:
INI
# Naikkan batas maksimum receive and send socket buffer (64MB)
net.core.rmem_max = 67108864
net.core.wmem_max = 67108864

# Set buffer default untuk socket baru (8MB)
net.core.rmem_default = 8388608
net.core.wmem_default = 8388608

# Alokasi halaman memori minimum, default, dan maksimum untuk UDP (dalam satuan halaman/pages)
net.ipv4.udp_mem = 262144 524288 1048576

# Naikkan queue backlog kartu jaringan untuk mencegah drop sebelum masuk IP stack
net.core.netdev_max_backlog = 50000

# Izinkan socket reuseport load balancing secara efisien di level kernel
net.ipv4.tcp_fastopen = 3
Muat parameter baru dengan mengeksekusi sysctl -p /etc/sysctl.d/99-dns-network.conf.
Secara arsitektur, PowerDNS membagi thread menjadi dua: receiver-threads (membaca paket dari socket) dan distributor-threads (mengeksekusi query logika dan berinteraksi dengan database). Kesalahan fatal yang sering ditemui adalah membuat distributor-threads terlalu banyak, yang memicu lock contention masif pada database backend. Berikut konfigurasi tangguh /etc/powerdns/pdns.conf untuk server dengan 16 vCPU:
INI
# Binding IP dan port
local-address=127.0.0.1,192.168.10.15
local-port=5353

# Gunakan SO_REUSEPORT di Linux kernel
reuseport=yes

# Distribusi thread yang optimal untuk menahan socket queue
receiver-threads=4
distributor-threads=12

# Tuning cache internal PowerDNS untuk meredam kueri ke database backend
cache-ttl=60
negquery-cache-ttl=15
query-cache-ttl=60
max-cache-entries=1000000
max-packet-cache-entries=1000000

# Proteksi backend timeout agar thread tidak hang selamanya
gpgsql-host=127.0.0.1
gpgsql-port=5432
gpgsql-dbname=pdns
gpgsql-user=pdns_user
gpgsql-password=supersecretpassword

# Timeout koneksi ke database Postgres (dalam detik)
gpgsql-connect-timeout=2

# Aktifkan DNSSEC dengan algoritma ECDSA P-256 (Algorithm 13)
default-ksk-algorithm=ecdsa256
default-zsk-algorithm=ecdsa256
Penggunaan algoritma DNSSEC ECDSA P-256 (ecdsa256) sangat disarankan dibandingkan RSA-2048. ECDSA P-256 memangkas ukuran payload DNS response secara drastis, mengurangi fragmentasi UDP, dan menghemat konsumsi memori serta instruksi siklus CPU saat menandatangani zone transfer atau verifikasi signature.
CoreDNS bertindak sebagai entrypoint utama pada port 53. Jika PowerDNS pada port 5353 mengalami hiccup akibat sinkronisasi replika database, CoreDNS tidak boleh ikut mati atau kehabisan memori. Plugin cache pada CoreDNS harus diatur untuk menyajikan data usang (stale data) jika upstream mengalami kegagalan. Berikut konfigurasi CoreDNS (/etc/coredns/Corefile):
COREFILE
.:53 {
    bind 0.0.0.0
    
    # Logging error saja di lingkungan produksi untuk menghemat IOPS
    log . {
        class error
    }
    
    # Prometheus metrics exporter untuk monitoring PSI dan Goroutines
    prometheus 0.0.0.0:9153
    
    # Health check endpoint
    health :8080
    
    # Isolasi cache dengan mekanisme Serve Stale dan Prefetch
    cache 300 {
        success 65536 300 30
        denial 10000 60 10
        prefetch 3 1m 20%
        serve_stale 1h immediate
    }
    
    # Forwarding