DNS yönetimi, bir sistemin can damarlarından biri. Elle zone dosyası düzenlemek, her değişiklik için servis yeniden başlatmak… Bunlar geçmişte kalan acı anılar olabilir. PowerDNS’in sunduğu RESTful API sayesinde DNS kayıtlarınızı programatik olarak yönetebilir, otomasyon pipeline’larınıza entegre edebilir ve insan hatasını minimuma indirebilirsiniz. Bu yazıda PowerDNS API’sini gerçek dünya senaryolarıyla ele alacağız.
PowerDNS API Nedir ve Neden Kullanmalısınız?
PowerDNS Authoritative Server, 4.x sürümünden itibaren oldukça olgun bir REST API sunuyor. Bu API sayesinde zone oluşturma, kayıt ekleme/silme/güncelleme, zone transferi yönetimi ve çok daha fazlasını HTTP istekleriyle gerçekleştirebilirsiniz.
Neden manuel yönetim yerine API tercih edilmeli?
- Otomasyon kolaylığı: Yeni bir sunucu açtığınızda DNS kaydını otomatik ekleyebilirsiniz
- Hata azaltma: Elle düzenleme sırasında oluşan yazım hataları ortadan kalkar
- CI/CD entegrasyonu: Deployment pipeline’larınıza DNS yönetimini dahil edebilirsiniz
- Audit trail: Her değişiklik loglanabilir ve izlenebilir hale gelir
- Hız: Yüzlerce kaydı saniyeler içinde güncelleyebilirsiniz
API’yi Etkinleştirmek
Önce PowerDNS Authoritative Server kurulu ve çalışıyor olması gerekiyor. API’yi etkinleştirmek için pdns.conf dosyasını düzenlemeniz gerekiyor.
# pdns.conf dosyasını aç
sudo nano /etc/powerdns/pdns.conf
Aşağıdaki satırları ekleyin veya uncomment edin:
# API'yi etkinleştir
api=yes
api-key=gizli-api-anahtariniz-buraya
# Web sunucusu ayarları
webserver=yes
webserver-address=127.0.0.1
webserver-port=8081
webserver-allow-from=127.0.0.1,192.168.1.0/24
Önemli: webserver-address için dikkatli olun. Production ortamında API’yi doğrudan internete açmayın. Bir reverse proxy veya firewall arkasında tutun.
Değişiklikleri uygulamak için servisi yeniden başlatın:
sudo systemctl restart pdns
sudo systemctl status pdns
API’nin çalışıp çalışmadığını test etmek için:
curl -s -H 'X-API-Key: gizli-api-anahtariniz-buraya'
http://localhost:8081/api/v1/servers/localhost | python3 -m json.tool
Başarılı bir yanıtta sunucu bilgilerini, backend türünü ve desteklenen zone sayısını göreceksiniz.
Temel API Konseptleri
PowerDNS API’sinde birkaç temel kavramı anlamak önemli:
- Server: PowerDNS instance’ı. Genellikle
localhostolarak referans edilir - Zone: DNS zone’u. Trailing dot ile biter:
example.com. - RRset: Resource Record Set. Aynı isim ve tipe sahip kayıtların grubu
- Canonicalization: Tüm FQDN’ler trailing dot ile yazılır
Bu kuralları çiğnerseniz API size hata döner ve saatlerce neden çalışmadığını sorgularsınız. Benden söylemesi.
Zone Yönetimi
Yeni Zone Oluşturma
İlk gerçek dünya senaryomuz: Yeni bir müşteri için zone oluşturma. Şirkette on yeni müşteri geldiğinde tek tek zone oluşturmak yerine bir script yazalım.
#!/bin/bash
# create_zone.sh - Yeni DNS zone oluşturur
PDNS_API_URL="http://localhost:8081/api/v1"
PDNS_API_KEY="gizli-api-anahtariniz-buraya"
DOMAIN="${1}"
NS1="ns1.example.com."
NS2="ns2.example.com."
if [ -z "$DOMAIN" ]; then
echo "Kullanim: $0 <domain>"
echo "Ornek: $0 yenimüşteri.com"
exit 1
fi
# Trailing dot ekle
ZONE="${DOMAIN}."
curl -s -X POST
-H "X-API-Key: ${PDNS_API_KEY}"
-H "Content-Type: application/json"
"${PDNS_API_URL}/servers/localhost/zones"
-d "{
"name": "${ZONE}",
"kind": "Native",
"nameservers": ["${NS1}", "${NS2}"],
"soa_edit_api": "INCEPTION-INCREMENT"
}" | python3 -m json.tool
echo ""
echo "Zone ${ZONE} olusturuldu!"
Bu scripti çalıştırın:
chmod +x create_zone.sh
./create_zone.sh yenimüşteri.com
soa_edit_api parametresini INCEPTION-INCREMENT olarak ayarlamak, her API değişikliğinde SOA serial’ının otomatik güncellenmesini sağlar. Bu hayat kurtarıcı bir detay.
Zone Listeleme ve Sorgulama
Mevcut zone’ları listelemek:
curl -s -H 'X-API-Key: gizli-api-anahtariniz-buraya'
http://localhost:8081/api/v1/servers/localhost/zones |
python3 -c "import sys,json; zones=json.load(sys.stdin); [print(z['name']) for z in zones]"
Belirli bir zone’un detaylarını almak:
curl -s -H 'X-API-Key: gizli-api-anahtariniz-buraya'
http://localhost:8081/api/v1/servers/localhost/zones/example.com. |
python3 -m json.tool
DNS Kayıtları Yönetimi
Kayıt Ekleme
Gerçek dünya senaryosu: Yeni bir web sunucusu deploy ettiniz ve A kaydını eklemeniz gerekiyor. Ayrıca www CNAME’ini de eklemek istiyorsunuz.
PowerDNS API’sinde kayıt yönetimi PATCH metodu ile yapılır. Kayıtları RRset olarak gönderirsiniz ve changetype ile işlem türünü belirtirsiniz.
#!/bin/bash
# add_record.sh - DNS kaydı ekler veya günceller
PDNS_API_URL="http://localhost:8081/api/v1"
PDNS_API_KEY="gizli-api-anahtariniz-buraya"
ZONE="example.com."
HOSTNAME="webserver01.example.com."
IP="192.168.1.100"
TTL=300
# A kaydı ekle
curl -s -X PATCH
-H "X-API-Key: ${PDNS_API_KEY}"
-H "Content-Type: application/json"
"${PDNS_API_URL}/servers/localhost/zones/${ZONE}"
-d "{
"rrsets": [
{
"name": "${HOSTNAME}",
"type": "A",
"ttl": ${TTL},
"changetype": "REPLACE",
"records": [
{
"content": "${IP}",
"disabled": false
}
]
}
]
}"
echo "A kaydi eklendi: ${HOSTNAME} -> ${IP}"
# www CNAME ekle
curl -s -X PATCH
-H "X-API-Key: ${PDNS_API_KEY}"
-H "Content-Type: application/json"
"${PDNS_API_URL}/servers/localhost/zones/${ZONE}"
-d "{
"rrsets": [
{
"name": "www.example.com.",
"type": "CNAME",
"ttl": ${TTL},
"changetype": "REPLACE",
"records": [
{
"content": "webserver01.example.com.",
"disabled": false
}
]
}
]
}"
echo "CNAME kaydi eklendi: www.example.com -> webserver01.example.com"
Önemli: changetype: REPLACE kullanırken dikkatli olun. Bu işlem mevcut kaydın tüm içeriğini siler ve yerini alır. Birden fazla IP ile round-robin yapmak istiyorsanız tüm IP’leri aynı anda göndermeniz gerekir.
Toplu Kayıt Ekleme
Gerçek hayatta sık karşılaşılan senaryo: Bir CSV dosyasından onlarca kaydı tek seferde yüklemek. Örneğin sunucu envanterinizi DNS’e aktarmak istiyorsunuz.
#!/bin/bash
# bulk_add_records.sh
# CSV formatı: hostname,ip,ttl
PDNS_API_URL="http://localhost:8081/api/v1"
PDNS_API_KEY="gizli-api-anahtariniz-buraya"
ZONE="example.com."
CSV_FILE="${1:-sunucular.csv}"
if [ ! -f "$CSV_FILE" ]; then
echo "CSV dosyasi bulunamadi: $CSV_FILE"
exit 1
fi
# JSON array oluştur
RRSETS="["
FIRST=true
while IFS=, read -r hostname ip ttl; do
# Başlık satırını atla
[[ "$hostname" == "hostname" ]] && continue
# Boş satırları atla
[ -z "$hostname" ] && continue
if [ "$FIRST" = true ]; then
FIRST=false
else
RRSETS+=","
fi
RRSETS+="{
"name": "${hostname}.${ZONE%?}.",
"type": "A",
"ttl": ${ttl:-300},
"changetype": "REPLACE",
"records": [{"content": "${ip}", "disabled": false}]
}"
echo "Hazırlaniyor: ${hostname} -> ${ip}"
done < "$CSV_FILE"
RRSETS+="]"
# Tek API çağrısıyla tümünü gönder
RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" -X PATCH
-H "X-API-Key: ${PDNS_API_KEY}"
-H "Content-Type: application/json"
"${PDNS_API_URL}/servers/localhost/zones/${ZONE}"
-d "{"rrsets": ${RRSETS}}")
if [ "$RESPONSE" = "204" ]; then
echo "Tum kayitlar basariyla eklendi!"
else
echo "Hata! HTTP kodu: $RESPONSE"
exit 1
fi
PowerDNS API başarılı PATCH işlemlerinde 204 No Content döner. Bunu kontrol etmek iyi bir alışkanlık.
Kayıt Silme
# Tek bir kaydı sil
curl -s -X PATCH
-H "X-API-Key: gizli-api-anahtariniz-buraya"
-H "Content-Type: application/json"
http://localhost:8081/api/v1/servers/localhost/zones/example.com.
-d '{
"rrsets": [
{
"name": "eskisunucu.example.com.",
"type": "A",
"changetype": "DELETE"
}
]
}'
Silme işleminde records alanını belirtmenize gerek yok. Sadece name, type ve changetype: DELETE yeterli.
Python ile Gelişmiş Yönetim
Bash scriptler basit işler için harika ama daha karmaşık senaryolar için Python tercih etmek mantıklı. requests kütüphanesiyle temiz bir PowerDNS client yazalım.
#!/usr/bin/env python3
# pdns_manager.py - PowerDNS API yönetim modülü
import requests
import json
import sys
from typing import List, Dict, Optional
class PowerDNSManager:
def __init__(self, api_url: str, api_key: str):
self.api_url = api_url.rstrip('/')
self.session = requests.Session()
self.session.headers.update({
'X-API-Key': api_key,
'Content-Type': 'application/json'
})
self.server = 'localhost'
def _base_url(self) -> str:
return f"{self.api_url}/servers/{self.server}/zones"
def zone_exists(self, zone: str) -> bool:
zone = self._ensure_dot(zone)
r = self.session.get(f"{self._base_url()}/{zone}")
return r.status_code == 200
def create_zone(self, zone: str, nameservers: List[str], kind: str = 'Native') -> Dict:
zone = self._ensure_dot(zone)
ns_with_dots = [self._ensure_dot(ns) for ns in nameservers]
payload = {
'name': zone,
'kind': kind,
'nameservers': ns_with_dots,
'soa_edit_api': 'INCEPTION-INCREMENT'
}
r = self.session.post(self._base_url(), json=payload)
r.raise_for_status()
print(f"[OK] Zone olusturuldu: {zone}")
return r.json()
def add_a_record(self, zone: str, hostname: str, ip: str, ttl: int = 300) -> bool:
return self._update_record(zone, hostname, 'A', ip, ttl)
def add_cname_record(self, zone: str, hostname: str, target: str, ttl: int = 300) -> bool:
return self._update_record(zone, hostname, 'CNAME', self._ensure_dot(target), ttl)
def add_mx_record(self, zone: str, hostname: str, priority: int, target: str, ttl: int = 3600) -> bool:
content = f"{priority} {self._ensure_dot(target)}"
return self._update_record(zone, hostname, 'MX', content, ttl)
def add_txt_record(self, zone: str, hostname: str, content: str, ttl: int = 300) -> bool:
txt_content = f'"{content}"'
return self._update_record(zone, hostname, 'TXT', txt_content, ttl)
def delete_record(self, zone: str, hostname: str, record_type: str) -> bool:
zone = self._ensure_dot(zone)
hostname = self._ensure_dot(hostname) if not hostname.endswith(zone) else hostname
payload = {
'rrsets': [{
'name': hostname,
'type': record_type,
'changetype': 'DELETE'
}]
}
r = self.session.patch(f"{self._base_url()}/{zone}", json=payload)
if r.status_code == 204:
print(f"[OK] Kayit silindi: {hostname} {record_type}")
return True
else:
print(f"[HATA] Kayit silinemedi: {r.status_code} - {r.text}")
return False
def _update_record(self, zone: str, hostname: str, rtype: str, content: str, ttl: int) -> bool:
zone = self._ensure_dot(zone)
fqdn = self._ensure_dot(hostname) if '.' in hostname else f"{hostname}.{zone}"
payload = {
'rrsets': [{
'name': fqdn,
'type': rtype,
'ttl': ttl,
'changetype': 'REPLACE',
'records': [{'content': content, 'disabled': False}]
}]
}
r = self.session.patch(f"{self._base_url()}/{zone}", json=payload)
if r.status_code == 204:
print(f"[OK] {rtype} kaydi eklendi: {fqdn} -> {content}")
return True
else:
print(f"[HATA] {r.status_code}: {r.text}")
return False
def _ensure_dot(self, name: str) -> str:
return name if name.endswith('.') else f"{name}."
# Kullanim ornegi
if __name__ == '__main__':
pdns = PowerDNSManager(
api_url='http://localhost:8081/api/v1',
api_key='gizli-api-anahtariniz-buraya'
)
# Yeni zone oluştur
if not pdns.zone_exists('yenimüşteri.com'):
pdns.create_zone('yenimüşteri.com', ['ns1.example.com', 'ns2.example.com'])
# Kayıtları ekle
pdns.add_a_record('yenimüşteri.com', 'yenimüşteri.com', '1.2.3.4')
pdns.add_cname_record('yenimüşteri.com', 'www.yenimüşteri.com', 'yenimüşteri.com')
pdns.add_mx_record('yenimüşteri.com', 'yenimüşteri.com', 10, 'mail.yenimüşteri.com')
pdns.add_txt_record('yenimüşteri.com', 'yenimüşteri.com', 'v=spf1 mx ~all')
Otomatik Deployment Entegrasyonu
Gerçek dünya senaryosu: Ansible veya Terraform ile sunucu açtığınızda DNS kaydının otomatik oluşturulması. Aşağıda bir Ansible task örneği var:
# tasks/dns_registration.yml
---
- name: DNS A kaydi ekle
uri:
url: "http://pdns-server:8081/api/v1/servers/localhost/zones/{{ dns_zone }}."
method: PATCH
headers:
X-API-Key: "{{ pdns_api_key }}"
Content-Type: "application/json"
body_format: json
body:
rrsets:
- name: "{{ inventory_hostname }}.{{ dns_zone }}."
type: "A"
ttl: 300
changetype: "REPLACE"
records:
- content: "{{ ansible_default_ipv4.address }}"
disabled: false
status_code: 204
delegate_to: localhost
- name: PTR kaydi ekle (reverse DNS)
uri:
url: "http://pdns-server:8081/api/v1/servers/localhost/zones/{{ reverse_zone }}."
method: PATCH
headers:
X-API-Key: "{{ pdns_api_key }}"
Content-Type: "application/json"
body_format: json
body:
rrsets:
- name: "{{ ansible_default_ipv4.address.split('.')[-1] }}.{{ reverse_zone }}."
type: "PTR"
ttl: 300
changetype: "REPLACE"
records:
- content: "{{ inventory_hostname }}.{{ dns_zone }}."
disabled: false
status_code: 204
delegate_to: localhost
when: manage_reverse_dns | default(false)
Zone Export ve Backup
DNS kayıtlarınızın yedeğini almak kritik bir operasyon. PowerDNS API bunu da kolaylaştırıyor:
#!/bin/bash
# backup_zones.sh - Tüm zone'ları yedekler
PDNS_API_URL="http://localhost:8081/api/v1"
PDNS_API_KEY="gizli-api-anahtariniz-buraya"
BACKUP_DIR="/var/backups/dns/$(date +%Y%m%d)"
mkdir -p "$BACKUP_DIR"
# Tüm zone'ları listele
ZONES=$(curl -s -H "X-API-Key: ${PDNS_API_KEY}"
"${PDNS_API_URL}/servers/localhost/zones" |
python3 -c "import sys,json; [print(z['name']) for z in json.load(sys.stdin)]")
echo "Zone yedekleme basladi: $(date)"
for ZONE in $ZONES; do
FILENAME="${BACKUP_DIR}/${ZONE%?}.json"
curl -s -H "X-API-Key: ${PDNS_API_KEY}"
"${PDNS_API_URL}/servers/localhost/zones/${ZONE}"
-o "$FILENAME"
if [ $? -eq 0 ]; then
echo "[OK] Yedeklendi: $ZONE -> $FILENAME"
else
echo "[HATA] Yedeklenemedi: $ZONE"
fi
done
# AXFR export (bind formatında)
for ZONE in $ZONES; do
FILENAME="${BACKUP_DIR}/${ZONE%?}.zone"
curl -s -H "X-API-Key: ${PDNS_API_KEY}"
"${PDNS_API_URL}/servers/localhost/zones/${ZONE}/export"
-o "$FILENAME"
echo "[OK] AXFR export: $ZONE"
done
echo "Yedekleme tamamlandi: $(date)"
echo "Konum: $BACKUP_DIR"
Bu scripti crontab’a ekleyin:
# Her gece 02:00'de DNS yedekleme
0 2 * * * /opt/scripts/backup_zones.sh >> /var/log/dns-backup.log 2>&1
Yaygın Hatalar ve Çözümleri
PowerDNS API kullanırken sık karşılaşılan sorunlar:
- 422 Unprocessable Entity: Genellikle trailing dot eksikliğinden kaynaklanır. Tüm FQDN’lerin nokta ile bittiğinden emin olun
- 404 Not Found: Zone adı yanlış veya zone mevcut değil. Zone adını kontrol edin
- 409 Conflict: Zone zaten mevcut. Oluşturmadan önce kontrol edin
- 400 Bad Request: JSON formatı hatalı veya zorunlu alan eksik. Payload’ı validate edin
- MX kaydı hatası: MX içeriği
"10 mail.example.com."formatında olmalı, sadece IP veya hostname yetmez - TXT kayıt tırnak işareti: TXT içerikleri çift tırnak içinde gönderilmeli:
""v=spf1 mx ~all""
Hata ayıklarken verbose mod kullanın:
curl -v -H 'X-API-Key: gizli-api-anahtariniz-buraya'
-H 'Content-Type: application/json'
http://localhost:8081/api/v1/servers/localhost/zones/example.com.
Güvenlik Önerileri
API güvenliği için dikkat edilmesi gerekenler:
- API key yönetimi: API key’i environment variable veya secrets manager’da saklayın, asla kaynak koda yazmayın
- Network izolasyonu: API portunu sadece gerekli IP’lere açın.
webserver-allow-fromparametresini kısıtlayın - HTTPS: Reverse proxy arkasında TLS sonlandırma yapın. Nginx veya Caddy kullanabilirsiniz
- Rate limiting: Nginx’te
limit_req_zoneile istek sayısını sınırlandırın - Monitoring: API erişim loglarını izleyin, anormal kayıt değişikliklerini alarm olarak ayarlayın
- Farklı API key’leri: Read-only işlemler için ayrı, write işlemler için ayrı key kullanın
Sonuç
PowerDNS API, DNS yönetimini gerçekten başka bir seviyeye taşıyor. Bu yazıda anlattıklarımızı özetleyelim:
- API etkinleştirme ve temel güvenlik konfigürasyonu
- Zone oluşturma ve yönetimi
- Tek ve toplu kayıt ekleme/silme işlemleri
- Python ile yeniden kullanılabilir yönetim modülü
- Ansible entegrasyonu ile otomatik DNS kaydı
- Zone backup ve export stratejileri
Bunların hepsini bir araya getirince DNS artık manuel bir iş olmaktan çıkıyor. Yeni sunucu açıldığında DNS kaydı otomatik oluşuyor, sunucu kapatıldığında otomatik siliniyor, deployment pipeline’ınız DNS değişikliklerini kendi hallediyor.
Bir adım daha ileriye gitmek isteyenler için PowerDNS Recursor API’sini de incelemenizi öneririm. Authoritative Server API ile birlikte kullanıldığında cache flush ve query istatistikleri gibi operasyonları da programatik olarak yönetebilirsiniz. DNS artık korkutucu değil, sadece bir API çağrısı uzaklığında.