Ручное управление DNS-записями через API или веб-интерфейс не масштабируется при 45 зонах. Мы реализовали GitOps-подход: все записи описываются в YAML-файлах, изменения проходят через merge request и деплоятся автоматически.
Структура репозитория:
dns-zones/
├── zones/
│ ├── meganet.internal.yaml
│ ├── dc1.meganet.internal.yaml
│ ├── dc2.meganet.internal.yaml
│ └── 10.in-addr.arpa.yaml
├── scripts/
│ ├── sync_zones.py
│ └── validate_zones.py
└── .gitlab-ci.yml
Формат описания зоны:
# zones/meganet.internal.yaml
zone: meganet.internal.
records:
- name: db-master
type: A
ttl: 300
content: 10.10.5.20
- name: db-replica-1
type: A
ttl: 300
content: 10.10.5.21
- name: db-replica-2
type: A
ttl: 300
content: 10.10.5.22
- name: db
type: CNAME
ttl: 60
content: db-master.meganet.internal.
- name: _postgresql._tcp
type: SRV
ttl: 300
content: "0 5432 db-master.meganet.internal."
priority: 10
Скрипт синхронизации сравнивает YAML с текущим состоянием зоны через API и применяет только diff:
#!/usr/bin/env python3
# scripts/sync_zones.py
import yaml, requests, sys
API = 'http://127.0.0.1:8081/api/v1/servers/localhost'
HEADERS = {'X-API-Key': os.environ['PDNS_API_KEY']}
def sync_zone(zone_file):
with open(zone_file) as f:
desired = yaml.safe_load(f)
zone_name = desired['zone']
current = requests.get(f'{API}/zones/{zone_name}', headers=HEADERS).json()
current_rrsets = {(r['name'], r['type']): r for r in current['rrsets']}
patch_rrsets = []
for record in desired['records']:
fqdn = f"{record['name']}.{zone_name}" if record['name'] != '@' else zone_name
key = (fqdn, record['type'])
rrset = {
'name': fqdn,
'type': record['type'],
'ttl': record.get('ttl', 3600),
'changetype': 'REPLACE',
'records': [{'content': record['content'], 'disabled': False}]
}
if key not in current_rrsets or current_rrsets[key] != rrset:
patch_rrsets.append(rrset)
if patch_rrsets:
resp = requests.patch(
f'{API}/zones/{zone_name}',
headers=HEADERS,
json={'rrsets': patch_rrsets}
)
resp.raise_for_status()
print(f'Updated {len(patch_rrsets)} records in {zone_name}')
else:
print(f'Zone {zone_name} is up to date')
CI/CD пайплайн: на merge request — валидация YAML и dry-run, на merge в main — деплой через sync_zones.py. При ошибке синтаксиса пайплайн падает до деплоя, и зона не ломается.
Оставить комментарий