Cloudflare API ile Toplu DNS Kaydı Yönetimi

Onlarca subdomain, yüzlerce DNS kaydı ve bunları tek tek Cloudflare panelinden yönetmeye çalışmak… Kim geçirmediki bunu? Özellikle büyük bir göç sırasında ya da toplu güncelleme yaparken Cloudflare’in web arayüzü bir noktada yetersiz kalmaya başlıyor. İşte tam bu noktada Cloudflare API devreye giriyor ve hayatı çok daha kolaylaştırıyor.

Bu yazıda Cloudflare API’yi kullanarak DNS kayıtlarını toplu olarak nasıl yönetebileceğini, gerçek dünya senaryolarıyla birlikte anlatacağım. Bash scriptleri, curl komutları ve biraz Python ile oldukça güçlü bir DNS yönetim altyapısı kurabilirsin.

Cloudflare API’ye Giriş

Cloudflare’in REST API’si son derece kapsamlı ve iyi dokümante edilmiş bir yapıya sahip. DNS yönetimi için temel olarak iki şeye ihtiyacın var:

  • API Token: Cloudflare hesabından oluşturduğun, kapsam sınırlı erişim anahtarı
  • Zone ID: Yönetmek istediğin alan adının benzersiz kimliği

API Token oluşturmak için Cloudflare panelinde My Profile > API Tokens > Create Token yolunu izle. “Edit zone DNS” şablonunu kullanarak başlamak en sağlıklısı. Bu şablon sana DNS kayıtları üzerinde okuma ve yazma yetkisi veriyor, fazlasını değil.

Zone ID’yi bulmak için ise alan adının Cloudflare dashboard’una girip sağ taraftaki “Overview” sekmesine bakman yeterli. Ya da şu şekilde API üzerinden listeleyebilirsin:

curl -s -X GET "https://api.cloudflare.com/client/v4/zones" 
  -H "Authorization: Bearer YOUR_API_TOKEN" 
  -H "Content-Type: application/json" | jq '.result[] | {name: .name, id: .id}'

Bu komutu çalıştırdığında hesabındaki tüm zone’ları ve ID’lerini göreceksin. jq kurulu değilse apt install jq veya yum install jq ile kurabilirsin, bu işleri yaparken jq olmadan yaşamak gerçekten zor.

Sık kullanacağın değişkenleri bir environment dosyasına yazıp kaydetmek iyi bir alışkanlık:

# ~/.cloudflare_env
export CF_API_TOKEN="your_api_token_here"
export CF_ZONE_ID="your_zone_id_here"
export CF_API_BASE="https://api.cloudflare.com/client/v4"

source ~/.cloudflare_env ile yükleyip kullanmaya başlayabilirsin. Tabii bu dosyayı asla Git’e commit etme ve chmod 600 ~/.cloudflare_env ile sadece kendi kullanıcına açık tut.

Mevcut DNS Kayıtlarını Listelemek ve Export Etmek

Toplu işleme başlamadan önce mevcut durumu görmen şart. Şu komutla zone’undaki tüm DNS kayıtlarını listeleyebilirsin:

curl -s -X GET "$CF_API_BASE/zones/$CF_ZONE_ID/dns_records?per_page=100" 
  -H "Authorization: Bearer $CF_API_TOKEN" 
  -H "Content-Type: application/json" | 
  jq -r '.result[] | [.type, .name, .content, .ttl, .proxied] | @tsv'

Bu komut sana tab-separated bir çıktı verir. Ama dikkat, Cloudflare varsayılan olarak sayfa başına 100 kayıt döndürüyor. Eğer 100’den fazla kaydın varsa sayfalama yapman gerekiyor. Şu script bunu otomatik olarak halleder:

#!/bin/bash
source ~/.cloudflare_env

page=1
all_records="[]"

while true; do
    response=$(curl -s -X GET 
        "$CF_API_BASE/zones/$CF_ZONE_ID/dns_records?per_page=100&page=$page" 
        -H "Authorization: Bearer $CF_API_TOKEN" 
        -H "Content-Type: application/json")

    records=$(echo "$response" | jq '.result')
    total_pages=$(echo "$response" | jq '.result_info.total_pages')
    count=$(echo "$records" | jq 'length')

    if [ "$count" -eq 0 ]; then
        break
    fi

    all_records=$(echo "$all_records $records" | jq -s 'add')
    
    echo "Sayfa $page/$total_pages islendi, $count kayit bulundu"
    
    if [ "$page" -ge "$total_pages" ]; then
        break
    fi
    
    ((page++))
done

echo "$all_records" | jq -r '.[] | [.type, .name, .content, .ttl, (.proxied | tostring)] | @csv' 
    > dns_backup_$(date +%Y%m%d_%H%M%S).csv

echo "Export tamamlandi."

Bu script çıktıyı timestamp’li bir CSV dosyasına kaydediyor. Toplu değişiklik yapmadan önce bu backup’ı almak hayat kurtarıcı olabilir.

Yeni DNS Kaydı Ekleme

Tekli kayıt eklemek basit ama biz toplu işlerden bahsediyoruz. Önce temel ekleme komutunu görelim:

curl -s -X POST "$CF_API_BASE/zones/$CF_ZONE_ID/dns_records" 
  -H "Authorization: Bearer $CF_API_TOKEN" 
  -H "Content-Type: application/json" 
  --data '{
    "type": "A",
    "name": "test.example.com",
    "content": "192.168.1.100",
    "ttl": 300,
    "proxied": false
  }' | jq '.success, .result.id'

Şimdi gerçek dünya senaryosuna geçelim. Diyelim ki 20 farklı müşteri için subdomain oluşturman gerekiyor. Bunları bir CSV dosyasından okuyup toplu ekleyeceksin.

CSV formatın şöyle olsun (records_to_add.csv):

A,customer1.example.com,10.0.1.10,300,false
A,customer2.example.com,10.0.1.11,300,false
CNAME,mail.customer1.example.com,mail.provider.com,3600,false

Bu CSV’yi işleyen script:

#!/bin/bash
source ~/.cloudflare_env

INPUT_FILE="records_to_add.csv"
SUCCESS_COUNT=0
FAIL_COUNT=0

while IFS=',' read -r type name content ttl proxied; do
    # Yorum satirlarini ve bos satirlari atla
    [[ "$type" =~ ^#.*$ || -z "$type" ]] && continue

    echo "Ekleniyor: $type $name -> $content"

    response=$(curl -s -X POST "$CF_API_BASE/zones/$CF_ZONE_ID/dns_records" 
        -H "Authorization: Bearer $CF_API_TOKEN" 
        -H "Content-Type: application/json" 
        --data "{
            "type": "$type",
            "name": "$name",
            "content": "$content",
            "ttl": $ttl,
            "proxied": $proxied
        }")

    success=$(echo "$response" | jq -r '.success')

    if [ "$success" = "true" ]; then
        record_id=$(echo "$response" | jq -r '.result.id')
        echo "  BASARILI - ID: $record_id"
        ((SUCCESS_COUNT++))
    else
        error=$(echo "$response" | jq -r '.errors[0].message')
        echo "  HATA: $error"
        ((FAIL_COUNT++))
    fi

    # API rate limit icin kisa bekleme
    sleep 0.3

done < "$INPUT_FILE"

echo ""
echo "Islem tamamlandi: $SUCCESS_COUNT basarili, $FAIL_COUNT basarisiz"

Rate limiting konusunda dikkatli olmak lazım. Cloudflare API’si ücretsiz planda dakikada 1200 istek limitine sahip, bu bolca görünüyor ama döngüleri hızlı koşturduğunda sınıra takılabilirsin. sleep 0.3 genellikle güvenli bir tampon.

DNS Kayıtlarını Toplu Güncelleme

Güncelleme işlemi için önce kaydın ID’sini bilmen gerekiyor. Adı ile ID’yi eşleştiren bir yardımcı fonksiyon yazmak çok işe yarıyor:

#!/bin/bash
source ~/.cloudflare_env

# Kayit adina gore ID bul
get_record_id() {
    local name=$1
    local type=$2
    
    curl -s -X GET 
        "$CF_API_BASE/zones/$CF_ZONE_ID/dns_records?name=$name&type=$type" 
        -H "Authorization: Bearer $CF_API_TOKEN" 
        -H "Content-Type: application/json" | 
        jq -r '.result[0].id // empty'
}

# Belirli bir IP blogundan gelen tum A kayitlarini yeni IP'ye guncelle
OLD_IP_PREFIX="10.0.1."
NEW_IP_PREFIX="10.0.2."

# Tum A kayitlarini cek
all_a_records=$(curl -s -X GET 
    "$CF_API_BASE/zones/$CF_ZONE_ID/dns_records?type=A&per_page=100" 
    -H "Authorization: Bearer $CF_API_TOKEN" 
    -H "Content-Type: application/json" | 
    jq -r '.result[] | select(.content | startswith("'"$OLD_IP_PREFIX"'")) | [.id, .name, .content] | @tsv')

while IFS=$'t' read -r record_id name old_ip; do
    new_ip="${old_ip/$OLD_IP_PREFIX/$NEW_IP_PREFIX}"
    
    echo "Guncelleniyor: $name | $old_ip -> $new_ip"
    
    response=$(curl -s -X PATCH 
        "$CF_API_BASE/zones/$CF_ZONE_ID/dns_records/$record_id" 
        -H "Authorization: Bearer $CF_API_TOKEN" 
        -H "Content-Type: application/json" 
        --data "{"content": "$new_ip"}")
    
    if echo "$response" | jq -e '.success' > /dev/null 2>&1; then
        echo "  OK"
    else
        echo "  HATA: $(echo "$response" | jq -r '.errors[0].message')"
    fi
    
    sleep 0.3
done <<< "$all_a_records"

Bu script özellikle sunucu göçlerinde çok işe yarıyor. Tüm bir IP bloğunu yeni bir blokla değiştiriyorsun, tek tek uğraşmak yok.

Python ile Daha Güçlü Toplu Yönetim

Bash ile çok şey yapılabiliyor ama karmaşık mantık için Python çok daha rahat. requests kütüphanesiyle şöyle bir araç yazabilirsin:

#!/usr/bin/env python3
import requests
import csv
import json
import time
import os
from datetime import datetime

class CloudflareDNS:
    def __init__(self, api_token, zone_id):
        self.base_url = "https://api.cloudflare.com/client/v4"
        self.zone_id = zone_id
        self.headers = {
            "Authorization": f"Bearer {api_token}",
            "Content-Type": "application/json"
        }
    
    def get_all_records(self, record_type=None):
        records = []
        page = 1
        
        while True:
            params = {"per_page": 100, "page": page}
            if record_type:
                params["type"] = record_type
            
            resp = requests.get(
                f"{self.base_url}/zones/{self.zone_id}/dns_records",
                headers=self.headers,
                params=params
            )
            data = resp.json()
            
            if not data.get("success"):
                raise Exception(f"API hatasi: {data.get('errors')}")
            
            records.extend(data["result"])
            
            if page >= data["result_info"]["total_pages"]:
                break
            page += 1
            time.sleep(0.2)
        
        return records
    
    def create_record(self, record_type, name, content, ttl=300, proxied=False):
        payload = {
            "type": record_type,
            "name": name,
            "content": content,
            "ttl": ttl,
            "proxied": proxied
        }
        resp = requests.post(
            f"{self.base_url}/zones/{self.zone_id}/dns_records",
            headers=self.headers,
            json=payload
        )
        return resp.json()
    
    def delete_record(self, record_id):
        resp = requests.delete(
            f"{self.base_url}/zones/{self.zone_id}/dns_records/{record_id}",
            headers=self.headers
        )
        return resp.json()
    
    def bulk_import_from_csv(self, filename):
        results = {"success": 0, "fail": 0, "errors": []}
        
        with open(filename, "r") as f:
            reader = csv.DictReader(f)
            for row in reader:
                result = self.create_record(
                    record_type=row["type"],
                    name=row["name"],
                    content=row["content"],
                    ttl=int(row.get("ttl", 300)),
                    proxied=row.get("proxied", "false").lower() == "true"
                )
                
                if result.get("success"):
                    results["success"] += 1
                    print(f"OK: {row['type']} {row['name']}")
                else:
                    results["fail"] += 1
                    error_msg = str(result.get("errors", "Bilinmeyen hata"))
                    results["errors"].append({"record": row["name"], "error": error_msg})
                    print(f"HATA: {row['name']} - {error_msg}")
                
                time.sleep(0.3)
        
        return results


if __name__ == "__main__":
    cf = CloudflareDNS(
        api_token=os.environ["CF_API_TOKEN"],
        zone_id=os.environ["CF_ZONE_ID"]
    )
    
    # Tum kayitlari backup al
    print("Mevcut kayitlar yedekleniyor...")
    records = cf.get_all_records()
    backup_file = f"dns_backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
    with open(backup_file, "w") as f:
        json.dump(records, f, indent=2)
    print(f"Yedek alindi: {backup_file} ({len(records)} kayit)")
    
    # CSV'den toplu import
    print("nCSV'den kayitlar import ediliyor...")
    results = cf.bulk_import_from_csv("records_to_add.csv")
    print(f"nSonuc: {results['success']} basarili, {results['fail']} basarisiz")

Bu Python sınıfını bir modül olarak kullanıp farklı scriptlerde import edebilirsin. Gerçekten işleri bir üst seviyeye taşıyan bir yaklaşım bu.

Gerçek Dünya Senaryosu: Sunucu Göçü Sırasında DNS Yönetimi

Geçen yıl 8 müşterinin DNS kayıtlarını yeni sunuculara taşımak zorunda kaldık. Her müşterinin onlarca subdomaini vardı ve hepsini sırayla güncellemek zorundaydık. Üstelik TTL’leri düşürme, göç, test ve TTL’leri geri yükseltme aşamalarını yönetmek gerekiyordu.

Şöyle bir strateji izledik:

Aşama 1: TTL’leri düşür (değişiklikten 24 saat önce)

Önce tüm A kayıtlarının TTL’ini 300 saniyeye indirdik. Bu, sonraki güncellemelerin hızlı yayılmasını sağlıyor:

#!/bin/bash
source ~/.cloudflare_env

# Tum A kayitlarinin TTL'ini 300'e indir
curl -s "$CF_API_BASE/zones/$CF_ZONE_ID/dns_records?type=A&per_page=100" 
    -H "Authorization: Bearer $CF_API_TOKEN" | 
    jq -r '.result[] | [.id, .name, .content, (.proxied | tostring)] | @tsv' | 
while IFS=$'t' read -r id name content proxied; do
    echo "TTL dusruluyor: $name"
    curl -s -X PATCH "$CF_API_BASE/zones/$CF_ZONE_ID/dns_records/$id" 
        -H "Authorization: Bearer $CF_API_TOKEN" 
        -H "Content-Type: application/json" 
        --data '{"ttl": 300}' > /dev/null
    sleep 0.3
done

echo "Tum A kayitlarinin TTL'i 300 saniyeye indirildi."

Aşama 2: IP’leri güncelle (göç sonrası)

Yeni IP adresleri ile kayıtları güncelle, test et, sorun yoksa TTL’leri tekrar yükselt.

Aşama 3: TTL’leri normalize et

# TTL'leri tekrar 3600'e al
jq -r '.result[] | [.id] | @tsv' <<< "$all_records" | 
while read -r id; do
    curl -s -X PATCH "$CF_API_BASE/zones/$CF_ZONE_ID/dns_records/$id" 
        -H "Authorization: Bearer $CF_API_TOKEN" 
        -H "Content-Type: application/json" 
        --data '{"ttl": 3600}' > /dev/null
    sleep 0.3
done

Duplicate Kayıtları Temizleme

Zamanla DNS kayıtları arasında duplikasyon oluşabiliyor. Özellikle birden fazla kişi yönetim yapıyorsa bu çok sık karşılaşılan bir problem. Şu script duplikeleri tespit edip raporluyor:

#!/bin/bash
source ~/.cloudflare_env

echo "Duplicate DNS kayitlari taranıyor..."

curl -s "$CF_API_BASE/zones/$CF_ZONE_ID/dns_records?per_page=100" 
    -H "Authorization: Bearer $CF_API_TOKEN" | 
    jq -r '.result[] | [.type, .name, .content] | @tsv' | 
    sort | uniq -d | 
while IFS=$'t' read -r type name content; do
    echo "DUPLICATE BULUNDU: $type $name -> $content"
    
    # Bu kayidin tum ID'lerini bul
    ids=$(curl -s "$CF_API_BASE/zones/$CF_ZONE_ID/dns_records?name=$name&type=$type" 
        -H "Authorization: Bearer $CF_API_TOKEN" | 
        jq -r '.result[] | select(.content == "'"$content"'") | .id')
    
    echo "  ID'ler: $ids"
    echo "  Ilki haricindeki duplikeler silinecek. Devam? (y/n)"
    read -r confirm
    
    if [ "$confirm" = "y" ]; then
        first=true
        for id in $ids; do
            if $first; then
                first=false
                continue
            fi
            curl -s -X DELETE "$CF_API_BASE/zones/$CF_ZONE_ID/dns_records/$id" 
                -H "Authorization: Bearer $CF_API_TOKEN" > /dev/null
            echo "  Silindi: $id"
        done
    fi
done

Cloudflare Proxy Durumunu Toplu Yönetme

Bazen belirli kayıtlar için Cloudflare proxy’sini (turuncu bulut) açıp kapamak gerekiyor. Özellikle mail kayıtları, FTP, SSH gibi servislerin proxy üzerinden geçmemesi gerekiyor. Şu script bu durumu denetliyor:

#!/bin/bash
source ~/.cloudflare_env

# Mail ile ilgili kayitlarda proxy kapali olmali
problematic_records=$(curl -s 
    "$CF_API_BASE/zones/$CF_ZONE_ID/dns_records?per_page=100" 
    -H "Authorization: Bearer $CF_API_TOKEN" | 
    jq -r '.result[] | 
        select(
            (.proxied == true) and 
            (.name | (startswith("mail.") or startswith("smtp.") or startswith("imap.") or startswith("pop.")))
        ) | [.id, .name, .type] | @tsv')

if [ -z "$problematic_records" ]; then
    echo "Sorunlu kayit bulunamadi. Her sey yolunda."
    exit 0
fi

echo "Proxy aktif olmamasi gereken kayitlar:"
echo "$problematic_records" | while IFS=$'t' read -r id name type; do
    echo "  - $type $name (ID: $id)"
done

echo ""
echo "Bu kayitlarda proxy kapatilsin mi? (y/n)"
read -r confirm

if [ "$confirm" = "y" ]; then
    echo "$problematic_records" | while IFS=$'t' read -r id name type; do
        curl -s -X PATCH "$CF_API_BASE/zones/$CF_ZONE_ID/dns_records/$id" 
            -H "Authorization: Bearer $CF_API_TOKEN" 
            -H "Content-Type: application/json" 
            --data '{"proxied": false}' > /dev/null
        echo "Proxy kapatildi: $name"
        sleep 0.3
    done
fi

Hata Yönetimi ve Loglama

Production ortamında bu scriptleri çalıştırırken mutlaka iyi bir loglama mekanizması kur. En basit haliyle şöyle yapabilirsin:

# Log fonksiyonu
LOG_FILE="/var/log/cloudflare_dns_$(date +%Y%m%d).log"

log() {
    local level=$1
    shift
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$level] $*" | tee -a "$LOG_FILE"
}

log "INFO" "DNS guncelleme islemi baslatildi"
log "ERROR" "Kayit guncellenemedi: $name - $error_message"
log "SUCCESS" "Tum islemler tamamlandi: $SUCCESS_COUNT kayit guncellendi"

API hata kodlarına dikkat etmek de önemli:

  • 6003: Geçersiz API Token
  • 7003: Zone bulunamadı
  • 81057: Kayıt zaten mevcut
  • 81044: Geçersiz içerik formatı
  • 10000: Authentication hatası

Bu hata kodlarını scriptlerinde yakalayıp uygun şekilde işlemek, sorun çıktığında nerede olduğunu hızlı anlamana yardımcı olur.

Cron ile Otomatik DNS Denetimi

Mevcut DNS kayıtlarının beklenen değerlerde olduğunu periyodik olarak kontrol eden bir monitoring scripti de ekleyebilirsin:

#!/bin/bash
# /opt/scripts/cf_dns_audit.sh
source ~/.cloudflare_env

EXPECTED_FILE="/opt/scripts/expected_dns.tsv"
ALERT_EMAIL="[email protected]"
ISSUES_FOUND=false

while IFS=$'t' read -r expected_type expected_name expected_content; do
    actual=$(curl -s 
        "$CF_API_BASE/zones/$CF_ZONE_ID/dns_records?name=$expected_name&type=$expected_type" 
        -H "Authorization: Bearer $CF_API_TOKEN" | 
        jq -r '.result[0].content // "NOT_FOUND"')
    
    if [ "$actual" != "$expected_content" ]; then
        echo "UYARI: $expected_name beklenen $expected_content, bulunan $actual"
        ISSUES_FOUND=true
    fi
done < "$EXPECTED_FILE"

if $ISSUES_FOUND; then
    echo "DNS tutarsizliklari tespit edildi, $ALERT_EMAIL adresine bildirim gonderildi"
    # mail -s "DNS Audit Uyarisi" $ALERT_EMAIL < /tmp/dns_audit_report.txt
fi

Bunu cron’a ekle:

# Her saat basinda calistir
0 * * * * /opt/scripts/cf_dns_audit.sh >> /var/log/cf_dns_audit.log 2>&1

Sonuç

Cloudflare API’si, DNS yönetimini gerçekten başka bir boyuta taşıyor. Tek seferlik toplu işlemler için curl ile yazılmış basit scriptler yeterli olurken, sürekli bakım ve otomasyon için Python tabanlı araçlar çok daha uygun.

Özellikle şu noktalara dikkat etmeni öneririm:

  • Her büyük işlem öncesi backup al. Silinen bir kayıt anında geri gelmiyor, tarihsel kayıtlar da tutulmuyor.
  • Rate limiting’e saygı göster. sleep 0.3 gibi küçük gecikmeler seni büyük sorunlardan korur.
  • API Token’ını minimum yetki prensibine göre oluştur. Sadece DNS düzenleme yetkisi vermek, hesap genelinde tam yetki vermekten çok daha güvenli.
  • Scriptleri önce test ortamında dene. Cloudflare’in test zone’ları için ekstra ücret ödemiyorsun, önce orada koştur.
  • Değişiklikleri logla. Kimin, ne zaman, ne değiştirdiğini bilmek sorun gidermeyi dramatik biçimde kolaylaştırır.

Bu araçları bir kez kurduğunda, onlarca subdomaini dakikalar içinde yönetmeye başlarsın. Başta biraz zaman alıyor ama sonrasında kazandığın verimlilik buna kesinlikle değiyor.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir