Veritabanı Destekli DNS: PowerDNS MySQL Backend Kurulumu ve Yönetimi

Geleneksel BIND tabanlı DNS yapılandırmaları düz metin dosyalarına dayanır ve bu yaklaşım küçük ölçekli ortamlarda gayet iyi çalışır. Ancak yüzlerce domain yönetiyorsanız, her değişiklik için zone dosyasını elle düzenlemek, rndc reload çalıştırmak ve muhtemelen bir şeyleri mahvetmek kaçınılmaz hale gelir. İşte tam bu noktada PowerDNS ve MySQL backend kombinasyonu hayat kurtarır. Tüm DNS kayıtlarınız bir veritabanında yaşar, API üzerinden yönetilir ve web arayüzüyle dakikalar içinde değiştirilebilir. Bu yazıda, sıfırdan başlayarak production ortamına hazır bir PowerDNS MySQL yapılandırması kuracağız.

PowerDNS ve MySQL Backend Neden Bu Kadar Değerli?

Bir hosting şirketinde çalıştığınızı düşünün. Müşteri ekliyor, domain alıyor, A kayıtlarını değiştiriyor, subdomain istiyor. Her biri için SSH açıp zone dosyası düzenlemek yerine, bir web arayüzünden ya da API çağrısıyla saniyeler içinde kayıt ekleyebilmek müthiş bir fark yaratıyor.

PowerDNS’in MySQL backend kullanımının avantajları şunlar:

  • Dinamik güncelleme: Zone dosyası reload gerekmez, değişiklikler anında aktif olur
  • Merkezi yönetim: Tüm kayıtlar tek bir veritabanında, yedekleme trivial hale gelir
  • API desteği: PowerDNS REST API ile otomasyon çok kolay
  • Çoklu sunucu: Aynı MySQL veritabanına birden fazla PowerDNS sunucu bağlanabilir (master-slave DNS cluster)
  • Denetim imkanı: Hangi kayıt ne zaman değişti, veritabanı loglarıyla takip edilebilir
  • PowerDNS Admin gibi araçlar: Hazır web arayüzleri doğrudan bu backend’i kullanır

Sistem Gereksinimleri ve Hazırlık

Bu rehberde Ubuntu 22.04 LTS kullanacağız. CentOS/RHEL kullanıcıları için paket isimleri farklılık gösterebilir ama genel mantık aynı.

Önce sistemdeki mevcut DNS servislerini kontrol edip durduralım. systemd-resolved çakışma yaratabilir:

# Mevcut DNS servislerini kontrol et
sudo systemctl status systemd-resolved
sudo systemctl disable systemd-resolved
sudo systemctl stop systemd-resolved

# 53 portunu dinleyen bir şey var mı kontrol et
sudo ss -tlnp | grep :53
sudo lsof -i :53

Eğer 53 portunda bir şey çalışıyorsa durdurmadan PowerDNS ayağa kalkmaz. Bunu şimdiden temizleyelim.

MySQL Kurulumu ve Yapılandırması

Önce MySQL’i kuralım ve PowerDNS için gerekli veritabanını oluşturalım:

# MySQL kurulumu
sudo apt update
sudo apt install -y mysql-server

# MySQL güvenlik yapılandırması
sudo mysql_secure_installation

# MySQL servisini başlat ve enable et
sudo systemctl start mysql
sudo systemctl enable mysql

Şimdi PowerDNS için ayrı bir veritabanı ve kullanıcı oluşturalım. Production ortamında asla root kullanıcısıyla bağlanmayın:

-- MySQL'e root olarak bağlan
sudo mysql -u root -p

-- Veritabanı oluştur
CREATE DATABASE powerdns CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- Dedicated kullanıcı oluştur
CREATE USER 'pdns'@'localhost' IDENTIFIED BY 'G3rcekten_Guclu_Bir_Sifre!';

-- Yetkileri ver
GRANT ALL PRIVILEGES ON powerdns.* TO 'pdns'@'localhost';
FLUSH PRIVILEGES;

-- Veritabanına geç
USE powerdns;

Şimdi PowerDNS’in ihtiyaç duyduğu tabloları oluşturalım. Bu şema PowerDNS’in resmi önerdiği yapı:

-- PowerDNS için gerekli tablolar
CREATE TABLE domains (
  id                    INT AUTO_INCREMENT,
  name                  VARCHAR(255) NOT NULL,
  master                VARCHAR(128) DEFAULT NULL,
  last_check            INT DEFAULT NULL,
  type                  VARCHAR(6) NOT NULL,
  notified_serial       INT UNSIGNED DEFAULT NULL,
  account               VARCHAR(40) CHARACTER SET 'utf8' DEFAULT NULL,
  options               VARCHAR(65535) DEFAULT NULL,
  catalog               VARCHAR(255) DEFAULT NULL,
  PRIMARY KEY (id)
) Engine=InnoDB CHARACTER SET 'latin1';

CREATE UNIQUE INDEX name_index ON domains(name);

CREATE TABLE records (
  id                    BIGINT AUTO_INCREMENT,
  domain_id             INT DEFAULT NULL,
  name                  VARCHAR(255) DEFAULT NULL,
  type                  VARCHAR(10) DEFAULT NULL,
  content               VARCHAR(64000) DEFAULT NULL,
  ttl                   INT DEFAULT NULL,
  prio                  INT DEFAULT NULL,
  disabled              TINYINT(1) DEFAULT 0,
  ordername             VARCHAR(255) BINARY DEFAULT NULL,
  auth                  TINYINT(1) DEFAULT 1,
  PRIMARY KEY (id)
) Engine=InnoDB CHARACTER SET 'latin1';

CREATE INDEX nametype_index ON records(name,type);
CREATE INDEX domain_id ON records(domain_id);
CREATE INDEX ordername ON records (ordername);

CREATE TABLE supermasters (
  ip                    VARCHAR(64) NOT NULL,
  nameserver            VARCHAR(255) NOT NULL,
  account               VARCHAR(40) CHARACTER SET 'utf8' NOT NULL,
  PRIMARY KEY (ip, nameserver)
) Engine=InnoDB CHARACTER SET 'latin1';

CREATE TABLE comments (
  id                    INT AUTO_INCREMENT,
  domain_id             INT NOT NULL,
  name                  VARCHAR(255) NOT NULL,
  type                  VARCHAR(10) NOT NULL,
  modified_at           INT NOT NULL,
  account               VARCHAR(40) CHARACTER SET 'utf8' DEFAULT NULL,
  comment               TEXT CHARACTER SET 'utf8' NOT NULL,
  PRIMARY KEY (id)
) Engine=InnoDB CHARACTER SET 'latin1';

CREATE INDEX comments_name_type_idx ON comments (name, type);
CREATE INDEX comments_order_idx ON comments (domain_id, modified_at);

CREATE TABLE domainmetadata (
  id                    INT AUTO_INCREMENT,
  domain_id             INT NOT NULL,
  kind                  VARCHAR(32),
  content               TEXT,
  PRIMARY KEY (id)
) Engine=InnoDB CHARACTER SET 'latin1';

CREATE INDEX domainmetadata_idx ON domainmetadata (domain_id, kind);

CREATE TABLE cryptokeys (
  id                    INT AUTO_INCREMENT,
  domain_id             INT NOT NULL,
  flags                 INT NOT NULL,
  active                BOOL,
  published             BOOL DEFAULT 1,
  content               TEXT,
  PRIMARY KEY(id)
) Engine=InnoDB CHARACTER SET 'latin1';

CREATE INDEX domainidindex ON cryptokeys(domain_id);

CREATE TABLE tsigkeys (
  id                    INT AUTO_INCREMENT,
  name                  VARCHAR(255),
  algorithm             VARCHAR(50),
  secret                VARCHAR(255),
  PRIMARY KEY (id)
) Engine=InnoDB CHARACTER SET 'latin1';

CREATE UNIQUE INDEX namealgoindex ON tsigkeys(name, algorithm);

EXIT;

PowerDNS Kurulumu

MySQL hazır, şimdi PowerDNS’i kuralım:

# PowerDNS ve MySQL backend kurulumu
sudo apt install -y pdns-server pdns-backend-mysql

# Servisin durumunu kontrol et (henüz başlamayacak, config bekleniyor)
sudo systemctl status pdns

PowerDNS Yapılandırması

Ana konfigürasyon dosyası /etc/powerdns/pdns.conf dosyasıdır. Varsayılan dosyayı yedekleyip temiz bir konfigürasyon yazalım:

# Mevcut konfigürasyonu yedekle
sudo cp /etc/powerdns/pdns.conf /etc/powerdns/pdns.conf.backup

# Yeni konfigürasyon yaz
sudo nano /etc/powerdns/pdns.conf

Konfigürasyon dosyasına şunları yazın:

# Temel ayarlar
setuid=pdns
setgid=pdns

# Hangi arayüzden dinleyeceğiz
local-address=0.0.0.0
local-port=53

# Backend tanımı
launch=gmysql

# MySQL bağlantı bilgileri
gmysql-host=127.0.0.1
gmysql-port=3306
gmysql-dbname=powerdns
gmysql-user=pdns
gmysql-password=G3rcekten_Guclu_Bir_Sifre!
gmysql-dnssec=yes

# API ayarları (web arayüzleri için gerekli)
api=yes
api-key=supersecretapikey-bunu-degistir-123
webserver=yes
webserver-address=127.0.0.1
webserver-port=8081
webserver-allow-from=127.0.0.1,::1

# Güvenlik ve performans
allow-recursion=127.0.0.1,::1
recursive-cache-ttl=10
cache-ttl=20
negquery-cache-ttl=60
max-tcp-connections=20

# Loglama
log-dns-queries=no
loglevel=4

api-key değerini mutlaka değiştirin. Bunu üretmek için şu komutu kullanabilirsiniz:

# Güvenli random API key üretimi
openssl rand -hex 32

Şimdi PowerDNS’i başlatalım:

sudo systemctl start pdns
sudo systemctl enable pdns
sudo systemctl status pdns

# Portun dinlenip dinlenmediğini doğrula
sudo ss -tlnp | grep :53

# Test sorgusu
dig @127.0.0.1 google.com

İlk Domain ve Kayıtların Eklenmesi

PowerDNS çalışıyor, veritabanına bağlı. Şimdi ilk domain’imizi ekleyelim. Bunu hem doğrudan SQL ile hem de API üzerinden yapabiliriz.

SQL ile Domain Ekleme

-- MySQL'e bağlan
mysql -u pdns -p powerdns

-- Domain ekle (Native tip: replikasyonu harici araçla yapacağız)
INSERT INTO domains (name, type) VALUES ('ornek.com', 'NATIVE');

-- Domain ID'sini öğren
SELECT id FROM domains WHERE name='ornek.com';
-- Diyelim ki ID = 1 çıktı

-- SOA kaydı ekle (zorunlu!)
INSERT INTO records (domain_id, name, type, content, ttl) 
VALUES (1, 'ornek.com', 'SOA', 'ns1.ornek.com. hostmaster.ornek.com. 2024010101 10800 3600 604800 3600', 3600);

-- NS kayıtları ekle
INSERT INTO records (domain_id, name, type, content, ttl) 
VALUES (1, 'ornek.com', 'NS', 'ns1.ornek.com', 3600);

INSERT INTO records (domain_id, name, type, content, ttl) 
VALUES (1, 'ornek.com', 'NS', 'ns2.ornek.com', 3600);

-- A kayıtları ekle
INSERT INTO records (domain_id, name, type, content, ttl) 
VALUES (1, 'ornek.com', 'A', '203.0.113.10', 3600);

INSERT INTO records (domain_id, name, type, content, ttl) 
VALUES (1, 'www.ornek.com', 'A', '203.0.113.10', 3600);

INSERT INTO records (domain_id, name, type, content, ttl) 
VALUES (1, 'mail.ornek.com', 'A', '203.0.113.20', 3600);

-- MX kaydı ekle
INSERT INTO records (domain_id, name, type, content, ttl, prio) 
VALUES (1, 'ornek.com', 'MX', 'mail.ornek.com', 3600, 10);

-- TXT kaydı (SPF gibi)
INSERT INTO records (domain_id, name, type, content, ttl) 
VALUES (1, 'ornek.com', 'TXT', '"v=spf1 mx a ~all"', 3600);

API ile Domain ve Kayıt Yönetimi

REST API kullanmak SQL yazmaktan çok daha pratik ve otomasyon için idealdir:

# Tüm zone'ları listele
curl -s -H "X-API-Key: supersecretapikey-bunu-degistir-123" 
  http://127.0.0.1:8081/api/v1/servers/localhost/zones | python3 -m json.tool

# Yeni zone oluştur
curl -s -X POST 
  -H "X-API-Key: supersecretapikey-bunu-degistir-123" 
  -H "Content-Type: application/json" 
  -d '{
    "name": "yenidomain.com.",
    "kind": "Native",
    "nameservers": ["ns1.yenidomain.com.", "ns2.yenidomain.com."]
  }' 
  http://127.0.0.1:8081/api/v1/servers/localhost/zones | python3 -m json.tool

# Zone'a kayıt ekle veya güncelle (PATCH metodu kullanılır)
curl -s -X PATCH 
  -H "X-API-Key: supersecretapikey-bunu-degistir-123" 
  -H "Content-Type: application/json" 
  -d '{
    "rrsets": [
      {
        "name": "blog.yenidomain.com.",
        "type": "A",
        "ttl": 3600,
        "changetype": "REPLACE",
        "records": [
          {
            "content": "203.0.113.50",
            "disabled": false
          }
        ]
      }
    ]
  }' 
  http://127.0.0.1:8081/api/v1/servers/localhost/zones/yenidomain.com. | python3 -m json.tool

# Kayıt sil
curl -s -X PATCH 
  -H "X-API-Key: supersecretapikey-bunu-degistir-123" 
  -H "Content-Type: application/json" 
  -d '{
    "rrsets": [
      {
        "name": "blog.yenidomain.com.",
        "type": "A",
        "changetype": "DELETE"
      }
    ]
  }' 
  http://127.0.0.1:8081/api/v1/servers/localhost/zones/yenidomain.com.

Değişiklikleri Test Etme

# A kaydı sorgula
dig @127.0.0.1 ornek.com A

# MX kaydı sorgula
dig @127.0.0.1 ornek.com MX

# SOA sorgula
dig @127.0.0.1 ornek.com SOA

# Tüm kayıtları listele
dig @127.0.0.1 ornek.com ANY

# TTL dahil detaylı çıktı
dig +additional +multiline @127.0.0.1 ornek.com

PowerDNS Admin Web Arayüzü

SQL ve API ile uğraşmak istemiyorsanız, PowerDNS Admin (nüfuzu artık powerdns-admin adıyla bilinen proje) mükemmel bir web arayüzü sunar. Docker ile kurmak en pratik yol:

# Docker ve Docker Compose kurulu değilse kur
sudo apt install -y docker.io docker-compose

# PowerDNS Admin için docker-compose.yml oluştur
mkdir -p /opt/powerdns-admin
cat > /opt/powerdns-admin/docker-compose.yml << 'EOF'
version: '3'
services:
  powerdns-admin:
    image: powerdnsadmin/pda-legacy:latest
    container_name: powerdns_admin
    ports:
      - "9191:80"
    environment:
      - SECRET_KEY=super-secret-key-buraya
      - SQLALCHEMY_DATABASE_URI=mysql://pdns:[email protected]/powerdns
      - PDNS_API_KEY=supersecretapikey-bunu-degistir-123
      - PDNS_PROTO=http
      - PDNS_HOST=172.17.0.1
      - PDNS_PORT=8081
    restart: unless-stopped
EOF

cd /opt/powerdns-admin
sudo docker-compose up -d

172.17.0.1 Docker’ın host bridge arayüzü IP’sidir. Bunu ip addr show docker0 komutuyla doğrulayın. Kurulumdan sonra tarayıcıda http://sunucu-ip:9191 adresine giderek admin hesabı oluşturabilirsiniz.

Master-Slave Replikasyon Senaryosu

Gerçek dünya senaryolarında tek bir DNS sunucusu yeterli değildir. Bir primary (master) ve bir secondary (slave) DNS sunucusu kurmak, hem yedeklilik hem de yük dağılımı sağlar.

Master sunucu yapılandırmasında şu parametreler önemlidir:

# Master sunucu pdns.conf'una ekle
master=yes
slave=no
also-notify=SLAVE_IP_ADRESI

# MySQL backend, master ayarları
gmysql-dnssec=yes

Slave sunucusunda ise:

# Slave sunucu pdns.conf ayarları
master=no
slave=yes
slave-cycle-interval=60

Slave için MySQL’de supermaster kaydı oluşturulmalı:

-- Master sunucu bilgisini ekle
INSERT INTO supermasters (ip, nameserver, account) 
VALUES ('MASTER_IP', 'ns1.ornek.com', 'admin');

Güvenlik Sertleştirmesi

DNS sunucusu internete açıksa güvenlik kritik önem taşır:

# Güvenlik duvarı kuralları (UFW)
sudo ufw allow from any to any port 53 proto tcp
sudo ufw allow from any to any port 53 proto udp

# API'yi sadece localhost'a kilitle (zaten yaptık ama kontrol et)
grep webserver-address /etc/powerdns/pdns.conf

# MySQL'i sadece localhost dinletmek için
sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf
# bind-address = 127.0.0.1 satırını kontrol et

# PowerDNS log dosyasını izle
sudo journalctl -u pdns -f

# Zone transfer'ı sadece yetkili sunuculara izin ver
# pdns.conf'a ekle:
allow-axfr-ips=SLAVE_IP/32

Ayrıca MySQL kullanıcısına gerçekten ihtiyaç duyduğundan fazla yetki vermeyin. Eğer sadece PowerDNS okuma yapacaksa SELECT yeterlidir, sadece yazma yapacak bir servis varsa gerekli tablolara özel yetki verin.

Yedekleme Stratejisi

MySQL tabanlı DNS’in en büyük avantajlarından biri yedeklemenin son derece kolay olmasıdır:

#!/bin/bash
# /opt/scripts/backup-dns.sh

BACKUP_DIR="/backup/dns"
DATE=$(date +%Y%m%d_%H%M%S)
MYSQL_USER="pdns"
MYSQL_PASS="G3rcekten_Guclu_Bir_Sifre!"
DB_NAME="powerdns"

mkdir -p $BACKUP_DIR

# Tam veritabanı yedeği
mysqldump -u $MYSQL_USER -p$MYSQL_PASS 
  --single-transaction 
  --routines 
  --triggers 
  $DB_NAME > $BACKUP_DIR/powerdns_$DATE.sql

# Yedekleri sıkıştır
gzip $BACKUP_DIR/powerdns_$DATE.sql

# 30 günden eski yedekleri sil
find $BACKUP_DIR -name "*.sql.gz" -mtime +30 -delete

echo "DNS yedekleme tamamlandi: $BACKUP_DIR/powerdns_$DATE.sql.gz"

Bu scripti cron’a ekleyin:

# Her gece 02:00'de yedek al
echo "0 2 * * * root /opt/scripts/backup-dns.sh" | sudo tee -a /etc/cron.d/dns-backup

Sorun Giderme

Sahada en sık karşılaşılan sorunlar ve çözümleri:

  • PowerDNS başlamıyor, port çakışması: sudo ss -tlnp | grep :53 ile hangi process kullandığını bul ve durdur. systemd-resolved en yaygın suçludur.
  • “Unable to connect to database” hatası: MySQL kullanıcı adı, şifre ve veritabanı adını double-check edin. mysql -u pdns -p powerdns ile manuel bağlanmayı deneyin.
  • Kayıtlar eklendi ama dig cevap vermiyor: SELECT * FROM records WHERE domain_id=1; ile kayıtların gerçekten veritabanında olduğunu doğrulayın. TTL ve content formatını kontrol edin.
  • SOA seri numarası güncellenmiyor: PowerDNS’te SOA seri numarasını otomatik artırması için soa-edit parametresini kullanabilirsiniz. pdns.conf‘a soa-edit=INCEPTION-INCREMENT ekleyin.
  • API 401 Unauthorized hatası: X-API-Key header’ının pdns.conf‘taki api-key ile birebir eşleştiğini kontrol edin. Boşluk ve tırnak işaretlerine dikkat edin.
  • Zone transfer çalışmıyor: allow-axfr-ips değerini kontrol edin, slave IP’nin doğru girildiğinden emin olun.
# Detaylı hata ayıklama için loglevel'i artır
# pdns.conf'a ekle:
# loglevel=7
# log-dns-queries=yes

sudo systemctl restart pdns
sudo journalctl -u pdns -f

Sonuç

PowerDNS ile MySQL backend kombinasyonu, DNS yönetimini gerçek anlamda ölçeklenebilir ve yönetilebilir hale getiriyor. Zone dosyası cehenneminden kurtulup veriye dayalı, API-first bir DNS altyapısına geçmek başlangıçta biraz setup gerektiriyor ama uzun vadede kazanılan verimlilik muazzam.

Bu yazıda anlattıklarımızı özetleyecek olursak, MySQL şeması oluşturup PowerDNS’i bu backend’e bağladık, ilk kayıtları hem SQL hem API üzerinden ekledik, web arayüzü için PowerDNS Admin’i Docker ile hızlıca devreye aldık ve production için güvenlik ile yedekleme konularına değindik.

Bir sonraki adım olarak DNSSEC yapılandırmasını, birden fazla sunucu arasında MySQL replikasyonunu veya Let’s Encrypt ile PowerDNS API’sini entegre eden ACME DNS validation kurulumunu inceleyebilirsiniz. Özellikle birden fazla sunucuya scale etmeniz gerekiyorsa, MySQL’in kendisini de Galera Cluster veya en azından master-replica replikasyonuyla güçlendirmenizi şiddetle tavsiye ederim. DNS’iniz çökerse, her şeyiniz çökebilir.

Yorum yapın