使用boto更新route53中的DNS记录

3 投票
4 回答
5243 浏览
提问于 2025-04-18 03:43

我在用boto更新R53中的dns记录时遇到了以下错误:

Traceback (most recent call last):
File "testing.py", line 106, in <module>
updateDns(load_balancer_dns)
File "testing.py", line 102, in updateDns
change.commit()
File "/usr/lib/python2.6/site-packages/boto/route53/record.py", line 149, in commit
return self.connection.change_rrsets(self.hosted_zone_id, self.to_xml())
File "/usr/lib/python2.6/site-packages/boto/route53/connection.py", line 320, in change_rrsets
body)
boto.route53.exception.DNSServerError: DNSServerError: 505 HTTP Version Not Supported

这是我用来更新dns条目的函数:

def updateDns(load_balancer_dns):
    r53 = boto.route53.connection.Route53Connection(aws_access_key_id=<access_key>,aws_secret_access_key=<secret_key>)
    zone_id = r53.get_hosted_zone_by_name(<domain_name>)
    print zone_id
    change = boto.route53.record.ResourceRecordSets(connection=r53,hosted_zone_id=zone_id)
    change.add_change_record("UPSERT", boto.route53.record.Record(name=<name>, type="CNAME", resource_records=load_balancer_dns, ttl=300))
    change.commit()
    print "record changed"
    return None

updateDns(load_balancer_dns)

有没有其他人之前遇到过这样的情况?

4 个回答

0

仅供参考:

先获取变更的ID,然后通过boto连接(conn)检查状态,直到显示“INSYNC”(同步完成)为止。

例如:

 def updateDns(load_balancer_dns):
    r53 = boto.route53.connection.Route53Connection(aws_access_key_id=<access_key>,aws_secret_access_key=<secret_key>)
    zone_id = r53.get_hosted_zone_by_name(<domain_name>)
    print zone_id
    change = boto.route53.record.ResourceRecordSets(connection=r53,hosted_zone_id=zone_id)
    change.add_change_record("UPSERT", boto.route53.record.Record(name=<name>, type="CNAME", resource_records=load_balancer_dns, ttl=300))
    _changes = change.commit()

    change_id = _changes["ChangeResourceRecordSetsResponse"]["ChangeInfo"]["Id"].split("/")[-1]

    while True:
        status = r53.get_change(change_id)["GetChangeResponse"]["ChangeInfo"]["Status"]
        if status == "INSYNC": break
        sleep(10)
0

我遇到了一个跟这个类似的问题,所以作为一个“调试”的练习,我做了一个

print zone_id

我注意到这个对象是一个字典/JSON响应,所以我把我的代码改成了

zone_id = self.r53.get_hosted_zone_by_name(self.domain).get("GetHostedZoneResponse").get("HostedZone").get("Id")

这对我来说似乎有效了 - 现在我得到了一个403错误,但至少这个错误应该更容易解决。

声明一下 - 我是Python新手,所以不确定这是不是正确的方法!

1

在编程中,有时候我们需要让程序做一些特定的事情,比如在某个条件下执行某段代码。这就像给程序设定了一些规则,让它知道在什么情况下该做什么。

比如说,如果你想让程序在用户输入的数字大于10时,显示一条消息,你就需要用到“条件语句”。这就像是在问:“如果这个条件成立,我就做这件事。”

条件语句通常是用“if”这个词来开始的,后面跟着你想要检查的条件。如果条件成立,程序就会执行你指定的代码。如果不成立,程序可以选择不做任何事情,或者执行另一段代码,这通常是用“else”来表示的。

总之,条件语句帮助程序根据不同的情况做出不同的反应,就像我们在生活中根据情况做出不同的决定一样。

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    # src: https://stackoverflow.com/a/47166985/65706
    # courtesy of: Naftuli Kay

    #

    from __future__ import absolute_import, print_function
    from time import sleep

    import argparse
    import boto
    import pprint
    import sys

    def set_vars():
        usage_examples = '''
            clear ; poetry run python process_dns.py --action upsert --dns-zone cgfinics.com --dns-name dev.cgfinics.com --dns-value 168.10.172.10 --record-type A
            clear ; poetry run python process_dns.py --action upsert --dns-zone cgfinics.com --dns-name www.dev.cgfinics.com --dns-value  www.dev.cgfinics.com --record-type CNAME
            clear ; poetry run python process_dns.py --action delete --dns-zone cgfinics.com --dns-name dev.cgfinics.com
        '''
        parser = argparse.ArgumentParser('A quick and dirty DNS upsert to aws with boto \n\n' + usage_examples)
        parser.add_argument('--action', required=True, nargs='?',
            help="The action to perform - upsert or delete ")
        parser.add_argument('--dns-zone', required=True, nargs='?',
            help="The DNS zone to process ")
        parser.add_argument('--dns-name', required=True, nargs='?',
            help="The DNS name to process ")
        parser.add_argument('--dns-value', required=False, nargs='?',
            help="The DNS value to process ")
        parser.add_argument('--record-type', required=True, nargs='?',
            help="The DNS record type - could be A, CNAME ")
        args = parser.parse_args()
        return args


    def main():
        """Entrypoint."""
        args = set_vars()
        action = args.action
        r53 = boto.connect_route53()
        zones = r53.get_all_hosted_zones()['ListHostedZonesResponse']['HostedZones']
        dns_zone = args.dns_zone + '.' if not args.dns_zone.endswith('.') else args.dns_zone
        public_zone = find_public_zone(dns_zone , zones)
        dns_name = args.dns_name + '.' if not args.dns_name.endswith('.') else args.dns_name
        dns_value = args.dns_value
        record_type = args.record_type

        if action == "upsert":
            upsert_record(r53, public_zone, dns_name, dns_value, record_type, wait=True)
            sys.exit(0)

        if action == "delete":
            delete_record(r53, public_zone, dns_name, dns_value, record_type, wait=True)
            sys.exit(0)

        print("only the upser and delete actions are supported !!!")
        sys.exit(1)


    def find_public_zone(name, zones):
        for zone in zones:
            if zone.get('Name') == name and zone.get('Config', {}).get('PrivateZone') in [True, 'false']:
                return zone

        return None


    def find_record(r53, zone_id, name, record_type):
        records = r53.get_all_rrsets(zone_id)

        for record in records:
            if record.name == name and record.type == record_type:
                return record

        return None


    def upsert_record(r53, zone, name, record, record_type, ttl=60, wait=False):
        print("Inserting record {}[{}] -> {}; TTL={}".format(name, record_type, record, ttl))
        recordset = boto.route53.record.ResourceRecordSets(connection=r53, hosted_zone_id=zone.get('Id').split('/')[-1])
        recordset.add_change_record('UPSERT', boto.route53.record.Record(
            name=name,
            type=record_type,
            resource_records=[record],
            ttl=ttl
        ))
        changeset = recordset.commit()

        change_id = changeset['ChangeResourceRecordSetsResponse']['ChangeInfo']['Id'].split('/')[-1]

        while wait:
            status = r53.get_change(change_id)['GetChangeResponse']['ChangeInfo']['Status']
            if status == 'INSYNC':
                break

            sleep(6)


    def delete_record(r53, zone, name, record, record_type, wait=False):
        print("Deleting record {}[{}] -> {}".format(name, record_type, record))

        zone_id = zone.get('Id').split('/')[-1]
        record = find_record(r53, zone_id, name, record_type)

        if not record:
            print("No record exists.")
            return

        recordset = boto.route53.record.ResourceRecordSets(connection=r53, hosted_zone_id=zone.get('Id').split('/')[-1])
        recordset.add_change_record('DELETE', record)
        changeset = recordset.commit()

        change_id = changeset['ChangeResourceRecordSetsResponse']['ChangeInfo']['Id'].split('/')[-1]

        while wait:
            status = r53.get_change(change_id)['GetChangeResponse']['ChangeInfo']['Status']
            if status == 'INSYNC':
                break

            sleep(10)


    if __name__ == "__main__":
        main()
3

因为这个问题没有一个权威的答案,所以我这里有一个我刚拼凑出来的可用脚本:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from __future__ import absolute_import, print_function

from time import sleep

import boto


def main():
    """Entrypoint."""
    r53 = boto.connect_route53()
    zones = r53.get_all_hosted_zones()['ListHostedZonesResponse']['HostedZones']
    private_zone = find_private_zone('sub.mydomain.com.', zones)
    reverse_zone = find_private_zone('100.10.in-addr.arpa.', zones)

    upsert_record(r53, private_zone, 'dangus.sub.mydomain.com.', '127.0.0.1', 'A', wait=True)
    delete_record(r53, private_zone, 'dangus.sub.mydomain.com.', '127.0.0.1', 'A', wait=True)


def find_private_zone(name, zones):
    for zone in zones:
        if zone.get('Name') == name and zone.get('Config', {}).get('PrivateZone') in [True, 'true']:
            return zone

    return None


def find_record(r53, zone_id, name, record_type):
    records = r53.get_all_rrsets(zone_id)

    for record in records:
        if record.name == name and record.type == record_type:
            return record

    return None


def upsert_record(r53, zone, name, record, record_type, ttl=60, wait=False):
    print("Inserting record {}[{}] -> {}; TTL={}".format(name, record_type, record, ttl))
    recordset = boto.route53.record.ResourceRecordSets(connection=r53, hosted_zone_id=zone.get('Id').split('/')[-1])
    recordset.add_change_record('UPSERT', boto.route53.record.Record(
        name=name,
        type=record_type,
        resource_records=[record],
        ttl=ttl
    ))
    changeset = recordset.commit()

    change_id = changeset['ChangeResourceRecordSetsResponse']['ChangeInfo']['Id'].split('/')[-1]

    while wait:
        status = r53.get_change(change_id)['GetChangeResponse']['ChangeInfo']['Status']
        if status == 'INSYNC':
            break

        sleep(10)


def delete_record(r53, zone, name, record, record_type, wait=False):
    print("Deleting record {}[{}] -> {}".format(name, record_type, record))

    zone_id = zone.get('Id').split('/')[-1]
    record = find_record(r53, zone_id, name, record_type)

    if not record:
        print("No record exists.")
        return

    recordset = boto.route53.record.ResourceRecordSets(connection=r53, hosted_zone_id=zone.get('Id').split('/')[-1])
    recordset.add_change_record('DELETE', record)
    changeset = recordset.commit()

    change_id = changeset['ChangeResourceRecordSetsResponse']['ChangeInfo']['Id'].split('/')[-1]

    while wait:
        status = r53.get_change(change_id)['GetChangeResponse']['ChangeInfo']['Status']
        if status == 'INSYNC':
            break

        sleep(10)


if __name__ == "__main__":
    main()

不幸的是,我用过的API都没有很好的Route 53实现,最终你得用字典查找来查看服务实际返回的XML。

一些注意事项:

  • 一定要使用完全合格的域名(FQDN),这意味着每个记录都应该以点号结尾。
  • 你不能只获取你想要的托管区域,你需要获取所有的托管区域,然后再搜索你想要的那个(比如:一个与给定名称匹配的私有区域)。
  • 你需要从你的托管区域中解析出ID。
  • 你需要从你的变更集(change sets)中解析出ID。
  • 在删除记录时,必须先获取当前记录的状态(如果存在的话),然后将这个值作为删除操作的一部分发送给Route 53。

这让API的使用变得非常麻烦,但至少它符合RFC-1925的第一条规则:它能工作。

撰写回答