Cloudflare + zabbix: управление dns-записями

Несомненно Cloudflare очень удобный и полезный сервис. Он берет на себя функцию управления DNS и выступает в роли CDN-прокси для вашего сайта выполняя различные оптимизации трафика. Все это возможно при бесплатной подписке. Однако, есть один минус. Cloudflare на текущий момент не предоставляет возможности балансировки трафика или проверки состояния серверов бэкендов. Т.е. если необходимо использовать несколько ip для одного сайта, то это сделать можно просто добавив эти ip в качестве A-записей для одного имени, но при этом, если один из ip окажется недоступен, то Cloudflare покажет ошибку о недоступности пользователям, а не будет пытаться загрузить данные с работающего сервера. К счастью у сloudflare есть API, который позволяет делегировать решение этой проблемы внешнему инструменту, который будет проверять работоспособность каждого адреса и удалять проблемные адреса из конфигурации DNS. Эта заметка описывает пример такой интеграции с Zabbix.

Скрипт для управления DNS-записями Cloudflare

Для управления DNS-записями я подготовил скрипт, который использует официальный модуль python. Для его использования достачно установить необходимые компоненты:

$ yum -y install python-pip python-requests
$ pip install cloudflare

Листинг cloudflare_dns.py, в качестве аргумента ip может использоваться как ip, так и dns-имя (полезно для dyndns):

#!/bin/python

import getopt
import sys
import socket
import CloudFlare

def main():
    zone_name = 'example.com'
    email = '[email protected]'
    token = ''

    try:
        opts, args = getopt.getopt(sys.argv[1:], "d:i", ["dns=", "ip=", "add", "delete"])
    except getopt.GetoptError as err:
        print str(err)
        sys.exit(2)
    dns_name = ''
    ip_address = ''
    action = ''
    for o, a in opts:
       if o == "--add":
          action = 'add'
       elif o == "--delete":
          action = 'delete'
       elif o == "--dns":
          dns_name = a
       elif o == "--ip":
          ip_address = a
       else:
            assert False, "unhandled option"

    if ip_address == '':
        exit('ip empty')
    elif is_valid_ipv4_address(ip_address) == False:
        ip = socket.gethostbyname(ip_address)
        if is_valid_ipv4_address(ip) == True:
           ip_address = ip
        else:
           exit('wrong ip or dns')
    if dns_name == '':
        exit('dns empty')

    cf = CloudFlare.CloudFlare(email=email,token=token)

    # query for the zone name and expect only one value back
    try:
        zones = cf.zones.get(params = {'name':zone_name,'per_page':1})
    except Exception as e:
        exit('/zones.get - %s - api call failed' % (e))

    # extract the zone_id which is needed to process that zone
    zone = zones[0]
    zone_id = zone['id']


    if action == 'add':
       add_record(cf, zone_id, dns_name, ip_address)
    elif action == 'delete':
       delete_record(cf, zone_id, dns_name, ip_address)

    exit(0)

def add_record(cf, zone_id, dns_name, ip_address):
    dns_data = {
      'name':dns_name,
      'type':'A',
      'content':ip_address,
      'proxied':True,
    }
    try:
       res = cf.zones.dns_records.post(zone_id, data=dns_data)
    except Exception as e:
       exit('/zones.post - %s - api call failed' % (e))
    print 'CREATED: %s %s' % (dns_name, ip_address)
    exit(0)

def delete_record(cf, zone_id, dns_name, ip_address):
    rec_id = ''
    # request the DNS records from that zone
    try:
        dns_records = cf.zones.dns_records.get(zone_id)
    except Exception as e:
        exit('/zones/dns_records.get %d %s - api call failed' % (e, e))

    # then all the DNS records for that zone
    for dns_record in dns_records:
        if dns_record['type'] not in ['A', 'AAAA']:
            # we only deal with A / AAAA records
            continue
        if dns_record['name'] == dns_name and dns_record['content'] == ip_address:
            rec_id = dns_record['id']
        else:
            continue
    if rec_id != '':
        dns_data = {
           'name':dns_name,
           'type':'A',
           'content':ip_address,
        }
        try:
            res = cf.zones.dns_records.delete(zone_id, rec_id, data=dns_data)
        except Exception as e:
            exit('/zones.delete - %s - api call failed' % (e))
        print 'DELETED: %s %s' % (dns_name, ip_address)
    exit(0)
    
def is_valid_ipv4_address(address):
    try:
        socket.inet_pton(socket.AF_INET, address)
    except AttributeError:  # no inet_pton here, sorry
        try:
            socket.inet_aton(address)
        except socket.error:
            return False
        return address.count('.') == 3
    except socket.error:  # not a valid address
        return False
        
    return True

if __name__ == '__main__':
    main()

Интеграция Zabbix и Cloudflare для управления dns-записями

Интеграция осуществляется через Веб-мониторинг.

1.Необходимо создать отдельный сценарий для каждого адреса. Примерно так:
zabbix web
2. На сценарии необходимо навесить триггеры:
zabbix triggers
3. Заключительный этап, необходимо создать действия, которые позволят выполнять скрипт по удалению/добавлению ip-адреса в конфигурацию DNS при срабатывании триггера:
zabbix actionszabbix actions recovery