将 SRV DNS 记录与 python 请求库一起使用
Using SRV DNS records with the python requests library
是否可以让 Python 请求库解析带有 SRV 记录的 consul 域名,并在发出请求时使用正确的 IP 地址和端口?
例如,假设我有 serviceA 运行 端口 8080 上的 IP 地址 172.18.0.5 并且此服务已在 consul 中注册。并且假设主机的 DNS 设置为使用 consul 来解析查询。我可以提出这样的请求吗:
requests.get('http://serviceA.service.consul')
并让它等同于请求:
requests.get('http://172.18.0.5:8080')
不,你不能,除非你重写 requests
。
SRV 记录是为查找服务而设计的。
在这种情况下,您已经指示使用 http。所以客户端只会查询 serviceA.service.consul
.
的 A 或 AAAA 记录
我最终为使用 this answer 执行此操作的请求编写了一个补丁。由于请求库的更新,我不得不进行一些更改。此补丁适用于请求版本 2.11.1.
我使用 dnspython 库来解析 SRV 记录,它希望 consul 侦听 DNS 请求的 IP 地址和端口可用作环境变量 CONSUL_DNS_IP_PORT。要使用补丁,请从补丁所在的任何模块导入 requests_use_srv_records
函数,然后调用它。它只会尝试对以 .service.consul
结尾的主机使用 consul SRV 记录,其他主机将定期解析。
这是补丁:
# Python Imports
import os
from socket import error as SocketError, timeout as SocketTimeout
# 3rd Party Imports
from dns import resolver
from requests.packages.urllib3.connection import HTTPConnection
from requests.packages.urllib3.exceptions import (NewConnectionError,
ConnectTimeoutError)
from requests.packages.urllib3.util import connection
def resolve_srv_record(host):
consul_dns_ip_port = os.environ.get('CONSUL_DNS_IP_PORT',
'172.17.0.1:53')
consul_dns_ip, consul_dns_port = consul_dns_ip_port.split(':')
res = resolver.Resolver()
res.port = consul_dns_port
res.nameservers = [consul_dns_ip]
ans = resolver.query(host, 'SRV')
return ans.response.additional[0].items[0].address, ans[0].port
def patched_new_conn(self):
if self.host.endswith('.service.consul'):
hostname, port = resolve_srv_record(self.host)
else:
hostname = self.host
port = self.port
extra_kw = {}
if self.source_address:
extra_kw['source_address'] = self.source_address
if self.socket_options:
extra_kw['socket_options'] = self.socket_options
try:
conn = connection.create_connection((hostname, port),
self.timeout,
**extra_kw)
except SocketTimeout as e:
raise ConnectTimeoutError(
self, "Connection to %s timed out. (connect timeout=%s)" %
(self.host, self.timeout))
except SocketError as e:
raise NewConnectionError(
self, "Failed to establish a new connection: %s" % e)
return conn
def requests_use_srv_records():
HTTPConnection._new_conn = patched_new_conn
是否可以让 Python 请求库解析带有 SRV 记录的 consul 域名,并在发出请求时使用正确的 IP 地址和端口?
例如,假设我有 serviceA 运行 端口 8080 上的 IP 地址 172.18.0.5 并且此服务已在 consul 中注册。并且假设主机的 DNS 设置为使用 consul 来解析查询。我可以提出这样的请求吗:
requests.get('http://serviceA.service.consul')
并让它等同于请求:
requests.get('http://172.18.0.5:8080')
不,你不能,除非你重写 requests
。
SRV 记录是为查找服务而设计的。
在这种情况下,您已经指示使用 http。所以客户端只会查询 serviceA.service.consul
.
我最终为使用 this answer 执行此操作的请求编写了一个补丁。由于请求库的更新,我不得不进行一些更改。此补丁适用于请求版本 2.11.1.
我使用 dnspython 库来解析 SRV 记录,它希望 consul 侦听 DNS 请求的 IP 地址和端口可用作环境变量 CONSUL_DNS_IP_PORT。要使用补丁,请从补丁所在的任何模块导入 requests_use_srv_records
函数,然后调用它。它只会尝试对以 .service.consul
结尾的主机使用 consul SRV 记录,其他主机将定期解析。
这是补丁:
# Python Imports
import os
from socket import error as SocketError, timeout as SocketTimeout
# 3rd Party Imports
from dns import resolver
from requests.packages.urllib3.connection import HTTPConnection
from requests.packages.urllib3.exceptions import (NewConnectionError,
ConnectTimeoutError)
from requests.packages.urllib3.util import connection
def resolve_srv_record(host):
consul_dns_ip_port = os.environ.get('CONSUL_DNS_IP_PORT',
'172.17.0.1:53')
consul_dns_ip, consul_dns_port = consul_dns_ip_port.split(':')
res = resolver.Resolver()
res.port = consul_dns_port
res.nameservers = [consul_dns_ip]
ans = resolver.query(host, 'SRV')
return ans.response.additional[0].items[0].address, ans[0].port
def patched_new_conn(self):
if self.host.endswith('.service.consul'):
hostname, port = resolve_srv_record(self.host)
else:
hostname = self.host
port = self.port
extra_kw = {}
if self.source_address:
extra_kw['source_address'] = self.source_address
if self.socket_options:
extra_kw['socket_options'] = self.socket_options
try:
conn = connection.create_connection((hostname, port),
self.timeout,
**extra_kw)
except SocketTimeout as e:
raise ConnectTimeoutError(
self, "Connection to %s timed out. (connect timeout=%s)" %
(self.host, self.timeout))
except SocketError as e:
raise NewConnectionError(
self, "Failed to establish a new connection: %s" % e)
return conn
def requests_use_srv_records():
HTTPConnection._new_conn = patched_new_conn