利用 Cloudflare Worker 控制 Cloudflare API 实现 ANAME
ANAME 是什么?
ANAME 记录(也称为ALIAS记录)是一个自定义的 DNS 记录类型,并不是所有的 DNS 提供商都支持。ANAME 记录允许一个根域名(例如example.com而不是www.example.com)映射到另一个域名,类似于CNAME记录,但同时允许像A记录那样映射到IP地址。
ANAME 和 CNAME 主要的区别在于:
- CNAME 记录:只能用于非根域名(如 www.example.com)。
- ANAME 记录:可以用于根域名(如 example.com),并且工作原理是在DNS解析时会先进行一次查找,将目标域名解析为一个IP地址,然后像返回A记录一样返回这个IP地址。
这使得ANAME记录特别适用于需要根域名指向某个动态IP地址的服务,比如某些托管在CDN上的网站。在背后,DNS提供商会实时解析配置的目标域名,并将解析结果作为A记录返回给发起请求的DNS客户端。
ANAME 记录的缺点是它通常依赖于DNS提供商提供的特定服务和解析器逻辑,而不是标准化的DNS记录类型。这意味着,如果你把你的DNS托管服务从支持ANAME记录的提供商迁移到不支持的提供商,你可能需要寻找替代方法来实现相同的功能。
Cloudflare ANAME 支持程度
目前 Cloudflare 并不支持 ANAME 功能,但是免费版根域名的 CNAME 拉平功能可以实现类似 ANAME 的效果,子域名无法使用 CNAME 拉平功能。
利用 Cloudflare Worker 实现 ANAME 类似效果
原理
- 利用 DNS Over HTTPS 查询目标 CNAME 域名 IP
- 通过 Cloudflare API 设置子域名的 IP
Worker 代码
/**
* Welcome to Cloudflare Workers! This is your first worker.
*
* - Run "npm run dev" in your terminal to start a development server
* - Open a browser tab at http://localhost:8787/ to see your worker in action
* - Run "npm run deploy" to publish your worker
*
* Learn more at https://developers.cloudflare.com/workers/
*/
export default {
async scheduled(event, env, ctx) {
const target = 'cname.exampledns.com';
const subdomain = 'sub.example.com';
const ip = await getIP(target);
console.log(await dns(subdomain, ip));
}
};
async function getIP(domain) {
let dnsQueryURL = `https://cloudflare-dns.com/dns-query?name=${domain}&type=A`
let dnsResponse = await fetch(dnsQueryURL, {
method: 'GET',
headers: {
'accept': 'application/dns-json',
},
})
let dnsData = await dnsResponse.json()
let ip = dnsData.Answer ? dnsData.Answer[0].data : null
return ip;
}
async function dns(subdomain, newIp) {
const zoneId = 'your zone id'; // Cloudflare Zone ID
const apiToken = 'your api token'; // Cloudflare API Token
const baseUrl = `https://api.cloudflare.com/client/v4/zones/${zoneId}/dns_records`;
const headers = {
'Authorization': `Bearer ${apiToken}`,
'Content-Type': 'application/json',
};
// Step 1: List DNS records to check if the A record already exists
let existingRecord = null;
let response = await fetch(`${baseUrl}?name=${subdomain}&type=A`, { headers });
const records = await response.json();
if (records && records.result && records.result.length > 0) {
existingRecord = records.result[0];
}
// Step 2: Decide whether to update an existing record or create a new one
let method = 'POST';
let url = baseUrl;
if (existingRecord) { // Record exists, prepare to update
method = 'PUT';
url = `${baseUrl}/${existingRecord.id}`;
}
// Step 3: Perform the actual update or create operation
response = await fetch(url, {
method: method,
headers: headers,
body: JSON.stringify({
type: 'A',
name: `${subdomain}`,
content: newIp,
ttl: 1, // can be set to 1 for automatic
proxied: false, // can be set as required
}),
});
const result = await response.json();
// The response for debugging purposes
return result;
}
设置定时触发器
每5分钟执行查询 CNAME 目前域名 IP 并更新。