如何使用Cloudflare实现DDNS服务
使用 Cloudflare Workers 和 API 实现高性能、安全的 DDNS 服务
动态 DNS(DDNS)服务允许你将一个域名指向一个动态 IP 地址。这对于家庭网络、小型企业或任何需要从外部访问但没有静态 IP 地址的设备非常有用。传统的 DDNS 服务提供商可能存在速度、可靠性或安全性方面的问题。Cloudflare 作为全球领先的 CDN 和 DNS 提供商,提供了一个强大且免费的平台,可以用来构建我们自己的高性能、安全的 DDNS 服务。
本文将详细介绍如何利用 Cloudflare Workers 和 Cloudflare API 来实现一个自定义的 DDNS 解决方案。我们将一步步地构建一个脚本,它可以自动检测你的公网 IP 地址变化,并更新 Cloudflare 上的 DNS 记录。
为什么选择 Cloudflare 实现 DDNS?
- 性能与可靠性: Cloudflare 拥有全球分布的边缘服务器网络,这意味着你的 DNS 解析将非常快速和可靠,无论你的用户位于何处。
- 安全性: Cloudflare 提供了强大的 DDoS 防护和其他安全功能,可以保护你的域名免受攻击。
- 免费套餐: Cloudflare 的免费套餐已经足够满足大多数个人和小型项目的 DDNS 需求。
- 灵活性: 使用 Cloudflare Workers,你可以完全控制你的 DDNS 逻辑,并根据需要进行自定义。
- API支持: Cloudflare提供了完善的API,可以对DNS记录进行增删改查。
前期准备
在开始之前,你需要准备以下事项:
- 一个 Cloudflare 帐户: 如果你还没有,请前往 Cloudflare 官网 注册一个免费帐户。
- 一个域名: 你需要拥有一个域名,并将其 DNS 解析托管到 Cloudflare。如果你还没有域名,可以从任何域名注册商处购买。
- Cloudflare API Token:
- 登录 Cloudflare 仪表板。
- 点击右上角的用户头像,选择 "My Profile"。
- 在左侧菜单中选择 "API Tokens"。
- 点击 "Create Token" 按钮。
- 选择 "Edit zone DNS" 模板。
- 在 "Zone Resources" 部分,选择 "Include" -> "Specific zone",然后选择你的域名。
- 点击 "Continue to summary",然后点击 "Create Token"。
- 重要提示: 复制并安全地保存你的 API Token,因为它只显示一次。
- Node.js 和 npm(可选,但强烈推荐): 这将使你能够更轻松地开发和部署 Cloudflare Workers。如果你没有安装,请前往 Node.js 官网 下载并安装。
- 文本编辑器或 IDE: 用于编写和编辑代码。推荐使用 VS Code。
- wrangler CLI: Cloudflare提供的命令行工具,用于部署和管理workers。
步骤 1:创建 Cloudflare Worker
Cloudflare Workers 是运行在 Cloudflare 边缘网络上的无服务器函数。我们将使用 Worker 来处理 IP 地址检测和 DNS 更新逻辑。
-
安装 Wrangler CLI(如果尚未安装):
bash
npm install -g wrangler -
使用 Wrangler 登录 Cloudflare:
bash
wrangler login
按照提示在浏览器中授权 Wrangler 访问你的 Cloudflare 帐户。 -
创建 Worker 项目:
bash
wrangler generate ddns-worker
这将在当前目录下创建一个名为ddns-worker
的新文件夹,其中包含一个基本的 Worker 项目结构。 -
编辑
index.js
文件:打开
ddns-worker/index.js
文件,并将其内容替换为以下代码(稍后我们将详细解释每个部分):
```javascript
addEventListener('scheduled', event => {
event.waitUntil(handleScheduled(event));
});
async function handleScheduled(event) {
const CF_API_TOKEN = 'YOUR_CLOUDFLARE_API_TOKEN'; // 替换为你的 API Token
const ZONE_ID = 'YOUR_ZONE_ID'; // 替换为你的 Zone ID
const DNS_RECORD_NAME = 'yoursubdomain.yourdomain.com'; // 替换为你的 DDNS 记录名称,例如 ddns.example.com
const DNS_RECORD_TYPE = 'A';
try {
const currentIp = await getCurrentIp();
const cloudflareIp = await getCloudflareIp(CF_API_TOKEN, ZONE_ID, DNS_RECORD_NAME,DNS_RECORD_TYPE);
if (currentIp !== cloudflareIp) {
await updateCloudflareIp(CF_API_TOKEN, ZONE_ID, DNS_RECORD_NAME, currentIp,DNS_RECORD_TYPE);
console.log(`IP address updated: ${cloudflareIp} -> ${currentIp}`);
} else {
console.log('IP address is up to date.');
}
} catch (error) {
console.error('Error:', error);
}
}
async function getCurrentIp() {
const response = await fetch('https://api.ipify.org');
return response.text();
}
async function getCloudflareIp(apiToken, zoneId, recordName,recordType) {
const response = await fetch(
https://api.cloudflare.com/client/v4/zones/${zoneId}/dns_records?name=${recordName}&type=${recordType}
,
{
headers: {
'Authorization': Bearer ${apiToken}
,
'Content-Type': 'application/json',
},
}
);
const data = await response.json();
if (data.success && data.result.length > 0) {
return data.result[0].content;
} else {
//如果DNS记录不存在,则创建
const createResponse = await fetch(
https://api.cloudflare.com/client/v4/zones/${zoneId}/dns_records
,
{
method: 'POST',
headers: {
'Authorization': Bearer ${apiToken}
,
'Content-Type': 'application/json',
},
body: JSON.stringify({
type: recordType,
name: recordName,
content: await getCurrentIp(),
ttl: 1, // 设置TTL为1,表示自动
proxied: true, // 启用 Cloudflare 代理 (可选)
}),
}
);
const createData = await createResponse.json();
if(createData.success)
{
return createData.result.content;
}else{
throw new Error(Failed to create DNS record: ${createData.errors[0]?.message}
);
}
}
}
async function updateCloudflareIp(apiToken, zoneId, recordName, newIp,recordType) {
//先获取记录ID
const recordInfo = await fetch(
https://api.cloudflare.com/client/v4/zones/${zoneId}/dns_records?name=${recordName}&type=${recordType}
,
{
headers: {
'Authorization': Bearer ${apiToken}
,
'Content-Type': 'application/json',
},
}
);
const recordData = await recordInfo.json();
const recordId = recordData.result[0].id;
const response = await fetch(
https://api.cloudflare.com/client/v4/zones/${zoneId}/dns_records/${recordId}
,
{
method: 'PUT',
headers: {
'Authorization': Bearer ${apiToken}
,
'Content-Type': 'application/json',
},
body: JSON.stringify({
type: recordType,
name: recordName,
content: newIp,
ttl: 1, // 设置TTL为1,表示自动
proxied: true, // 启用 Cloudflare 代理 (可选)
}),
}
);
const data = await response.json();
if (!data.success) {
throw new Error(Failed to update DNS record: ${data.errors[0]?.message}
);
}
}
``
wrangler.toml` 文件**
5. **配置
打开 `wrangler.toml` 文件,进行如下修改:
* `name`:设置 Worker 的名称(例如 `ddns-worker`)。
* `zone_id`:替换为你的域名的 Zone ID。你可以在 Cloudflare 仪表板的域名概览页面找到 Zone ID。
* `route`: 如果要将 Worker 部署到特定路由(例如 `example.com/ddns/*`),请在此处配置。如果希望 Worker 在定时任务触发时运行,可以删除或注释掉 `route` 这一行。
* `triggers`: 添加定时触发器配置。如下示例表示每5分钟触发一次。
```toml
name = "ddns-worker"
type = "javascript"
account_id = "" #可以不填
zone_id = "YOUR_ZONE_ID" #替换
workers_dev = true
compatibility_date = "2023-11-21"
[triggers]
crons = ["*/5 * * * *"]
```
步骤 2:代码详解
让我们逐块分析 index.js
中的代码:
-
事件监听器:
javascript
addEventListener('scheduled', event => {
event.waitUntil(handleScheduled(event));
});这段代码监听
scheduled
事件,该事件由wrangler.toml
中配置的 Cron 表达式触发。当事件触发时,它会调用handleScheduled
函数,并使用event.waitUntil
确保函数在 Worker 终止之前完成执行。 -
handleScheduled
函数:```javascript
async function handleScheduled(event) {
const CF_API_TOKEN = 'YOUR_CLOUDFLARE_API_TOKEN'; // 替换为你的 API Token
const ZONE_ID = 'YOUR_ZONE_ID'; // 替换为你的 Zone ID
const DNS_RECORD_NAME = 'yoursubdomain.yourdomain.com'; // 替换为你的 DDNS 记录名称,例如 ddns.example.com
const DNS_RECORD_TYPE = 'A';
try {
const currentIp = await getCurrentIp();
const cloudflareIp = await getCloudflareIp(CF_API_TOKEN, ZONE_ID, DNS_RECORD_NAME,DNS_RECORD_TYPE);if (currentIp !== cloudflareIp) { await updateCloudflareIp(CF_API_TOKEN, ZONE_ID, DNS_RECORD_NAME, currentIp,DNS_RECORD_TYPE); console.log(`IP address updated: ${cloudflareIp} -> ${currentIp}`); } else { console.log('IP address is up to date.'); }
} catch (error) {
console.error('Error:', error);
}
}
```这是 DDNS 逻辑的核心部分。它首先定义了几个常量:
*CF_API_TOKEN
:你的 Cloudflare API Token。
*ZONE_ID
:你的域名的 Zone ID。
*DNS_RECORD_NAME
:你想要用于 DDNS 的 DNS 记录的完整名称(例如ddns.example.com
)。
*DNS_RECORD_TYPE
: DNS记录类型,A
记录.然后,它按顺序执行以下操作:
1. 调用getCurrentIp
函数获取当前的公网 IP 地址。
2. 调用getCloudflareIp
函数获取 Cloudflare 上该 DNS 记录的当前 IP 地址。
3. 比较两个 IP 地址。如果它们不同,则调用updateCloudflareIp
函数更新 Cloudflare 上的 DNS 记录,并记录一条日志消息。如果它们相同,则记录一条 IP 地址是最新的消息。
4. 使用try...catch
块捕获任何可能发生的错误,并将错误信息记录到控制台。 -
getCurrentIp
函数:javascript
async function getCurrentIp() {
const response = await fetch('https://api.ipify.org');
return response.text();
}这个函数使用
fetch
API 向https://api.ipify.org
发送一个请求,该服务会返回你的公网 IP 地址。然后,它从响应中提取文本内容(即 IP 地址)并返回。 -
getCloudflareIp
函数:``javascript
https://api.cloudflare.com/client/v4/zones/${zoneId}/dns_records?name=${recordName}&type=${recordType}
async function getCloudflareIp(apiToken, zoneId, recordName,recordType) {
const response = await fetch(,
Bearer ${apiToken}`,
{
headers: {
'Authorization':
'Content-Type': 'application/json',
},
}
);const data = await response.json();
if (data.success && data.result.length > 0) {
return data.result[0].content;
} else {
//如果DNS记录不存在,则创建
const createResponse = await fetch(
https://api.cloudflare.com/client/v4/zones/${zoneId}/dns_records
,
{
method: 'POST',
headers: {
'Authorization':Bearer ${apiToken}
,
'Content-Type': 'application/json',
},
body: JSON.stringify({
type: recordType,
name: recordName,
content: await getCurrentIp(),
ttl: 1, // 设置TTL为1,表示自动
proxied: true, // 启用 Cloudflare 代理 (可选)
}),
}
);
const createData = await createResponse.json();
if(createData.success)
{
return createData.result.content;
}else{
throw new Error(Failed to create DNS record: ${createData.errors[0]?.message}
);
}
}
}```
这个函数使用 Cloudflare API 获取指定 DNS 记录的当前 IP 地址。
* 它向 Cloudflare API 发送一个 GET 请求,并附带 API Token、Zone ID 和 DNS 记录名称。
* 它解析 API 响应的 JSON 数据。
* 如果响应成功且存在对应的DNS记录,它会返回该记录的content
字段(即 IP 地址)。
* 如果响应成功,但是记录不存在,则创建一条新的DNS记录,并返回IP.
* 如果失败,它会抛出一个错误。 -
updateCloudflareIp
函数:``javascript
https://api.cloudflare.com/client/v4/zones/${zoneId}/dns_records?name=${recordName}&type=${recordType}
async function updateCloudflareIp(apiToken, zoneId, recordName, newIp,recordType) {
//先获取记录ID
const recordInfo = await fetch(,
Bearer ${apiToken}`,
{
headers: {
'Authorization':
'Content-Type': 'application/json',
},
}
);
const recordData = await recordInfo.json();
const recordId = recordData.result[0].id;const response = await fetch(
https://api.cloudflare.com/client/v4/zones/${zoneId}/dns_records/${recordId}
,
{
method: 'PUT',
headers: {
'Authorization':Bearer ${apiToken}
,
'Content-Type': 'application/json',
},
body: JSON.stringify({
type: recordType,
name: recordName,
content: newIp,
ttl: 1, // 设置TTL为1,表示自动
proxied: true, // 启用 Cloudflare 代理 (可选)
}),
}
);const data = await response.json();
if (!data.success) {
throw new Error(Failed to update DNS record: ${data.errors[0]?.message}
);
}
}```
这个函数使用 Cloudflare API 更新指定 DNS 记录的 IP 地址。
* 它向 Cloudflare API 发送一个 PUT 请求,并附带 API Token、Zone ID、DNS 记录名称和新的 IP 地址。
* 先通过GET请求获取DNS记录的ID,然后通过ID进行PUT更新操作.
* 它将请求体设置为一个 JSON 对象,其中包含要更新的 DNS 记录的字段(type
、name
、content
、ttl
和proxied
)。
* 它解析 API 响应的 JSON 数据。如果响应不成功,它会抛出一个错误。
*ttl: 1
表示使用 Cloudflare 的自动 TTL,这将根据 Cloudflare 的建议自动设置 TTL 值。
*proxied: true
表示启用 Cloudflare 代理,这将隐藏你的源服务器 IP 地址,并提供 DDoS 防护和其他安全功能。如果你不希望使用 Cloudflare 代理,可以将此值设置为false
。
步骤 3:部署 Cloudflare Worker
完成代码编写和配置后,我们可以使用 Wrangler CLI 部署 Worker:
bash
wrangler publish
这将把你的 Worker 代码上传到 Cloudflare,并根据 wrangler.toml
中的配置进行部署。
部署完成后,你的 DDNS 服务就已经开始运行了!它将按照你设置的 Cron 表达式(例如每 5 分钟)自动检查你的公网 IP 地址,并在发生变化时更新 Cloudflare 上的 DNS 记录。
步骤 4: 测试
-
手动触发: 你可以使用以下命令手动触发 Worker 执行,以进行测试:
bash
wrangler dev这将模拟一个定时事件,并运行你的 Worker。你可以在控制台中查看输出,以确认 IP 地址是否正确检测和更新。
-
更改你的公网 IP 地址: 最直接的方法是重启你的路由器或调制解调器,这通常会分配一个新的公网 IP 地址。
-
检查 DNS 记录: 你可以使用
dig
命令(Linux 或 macOS)或nslookup
命令(Windows)来查询你的 DDNS 记录,并确认它是否指向了正确的 IP 地址:bash
dig yoursubdomain.yourdomain.com或
bash
nslookup yoursubdomain.yourdomain.com将
yoursubdomain.yourdomain.com
替换为你的 DDNS 记录名称。
高级配置和优化
- 错误处理和通知: 你可以在代码中添加更健壮的错误处理逻辑,例如在更新 DNS 记录失败时发送电子邮件或 Slack 通知。
- 日志记录: 你可以使用 Cloudflare Workers 的日志记录功能来记录更详细的事件信息,以便进行故障排除和监控。
- 支持多个 DNS 记录: 你可以修改代码以支持同时更新多个 DNS 记录(例如,A 记录和 AAAA 记录)。
- 自定义 IP 检测服务: 如果你不希望使用
https://api.ipify.org
,你可以替换为其他 IP 检测服务,或者编写你自己的 IP 检测逻辑。 - IPv6 支持: 修改
getCurrentIp
使用支持 IPv6 的服务 (例如https://api64.ipify.org
),以及在创建/更新 Cloudflare 记录时,如果DNS_RECORD_TYPE
设置为AAAA
,进行相应处理。 - 速率限制: Cloudflare API 有速率限制。如果你的更新频率很高,或者你需要管理大量的 DNS 记录,你可能需要考虑实现请求队列或使用其他技术来避免触发速率限制。
进阶:不同场景下的应用与调整
-
家庭网络:
- 路由器集成: 一些路由器支持自定义脚本。你可以将上述逻辑(或其部分)移植到路由器上运行的脚本中,这样就不需要单独的设备来运行 Worker。
- NAS 集成: 如果你有一个 NAS(网络附加存储)设备,例如群晖或 QNAP,它们通常有内置的 DDNS 客户端或允许安装第三方应用程序。你可以寻找与 Cloudflare API 兼容的 DDNS 客户端,或者编写一个在 NAS 上运行的脚本。
-
小型企业:
- 多个子域名: 如果你有多个需要 DDNS 的服务(例如,邮件服务器、Web 服务器、VPN 服务器),你可以为每个服务创建一个单独的子域名,并在 Worker 代码中同时更新它们。
- 负载均衡: 如果你有多个服务器提供相同的服务,你可以使用 Cloudflare 的负载均衡功能,并将 DDNS 记录指向负载均衡器。
-
云服务器:
- 开机自启动: 如果你在云服务器上运行 DDNS 客户端,你可以将其配置为开机自启动,以确保即使服务器重启也能自动更新 DNS 记录。
- 容器化: 你可以将 DDNS 客户端打包成一个 Docker 容器,这样可以更轻松地部署和管理。
-
使用场景举例:
- 远程访问家庭网络: 你可以在家中的树莓派或其他设备上运行 DDNS 客户端,然后使用域名从任何地方访问你的家庭网络。
- 搭建个人网站: 你可以在家中的电脑上搭建一个网站,并使用 DDNS 将域名指向你的电脑,这样就可以通过域名访问你的网站。
- 远程桌面连接: 你可以使用 DDNS 远程连接到家中的电脑,进行文件传输或远程控制。
- 游戏服务器: 你可以在家中的电脑上搭建一个游戏服务器,并使用 DDNS 让你的朋友可以通过域名加入游戏。
安全卫士:加固你的 DDNS 服务
- API Token 权限最小化: 确保你创建的 API Token 只具有编辑 DNS 记录的权限,而没有其他不必要的权限。
- 代码审查: 定期审查你的 Worker 代码,确保没有安全漏洞。
- 监控和告警: 监控你的 DDNS 服务的运行状态,并在出现异常情况时及时收到通知。
- 使用 Cloudflare 的安全功能: 充分利用 Cloudflare 提供的 DDoS 防护、防火墙、WAF(Web 应用程序防火墙)等安全功能,保护你的域名和服务器免受攻击。
- 避免在代码中硬编码敏感信息: 使用 Wrangler secrets 来存储 API Token,而不是直接写在代码中。
更上一层楼:其他 DDNS 实现方式
虽然本文重点介绍了使用 Cloudflare Workers 的方法,但还有其他一些实现 DDNS 的方式:
- 使用现有的 DDNS 客户端: 许多操作系统和路由器都内置了 DDNS 客户端,或者你可以安装第三方 DDNS 客户端。这些客户端通常支持各种 DDNS 服务提供商,包括 Cloudflare。
- 使用 Shell 脚本: 你可以使用 Shell 脚本(例如 Bash 或 Python)来实现 DDNS 逻辑,并通过 Cron 或其他任务调度程序定期运行它。
- 使用第三方 DDNS 服务: 有许多免费和付费的 DDNS 服务提供商,它们提供易于使用的客户端和 Web 界面。
展望未来
Cloudflare Workers 和 API 提供了一个强大而灵活的平台,可以用来构建各种自定义的应用程序,DDNS 只是其中之一。随着无服务器计算的不断发展,我们可以期待看到更多基于 Cloudflare Workers 的创新应用。 这种方法不仅提供了高性能和安全性,还赋予了我们完全的控制权和定制能力。无论你是个人用户还是企业用户,都可以利用 Cloudflare 的强大功能来构建满足你特定需求的 DDNS 解决方案。
独辟蹊径
通过本文详尽的步骤和代码解析,你应该已经掌握了如何使用 Cloudflare Workers 和 API 构建自己的 DDNS 服务。这种方法相对于传统的 DDNS 服务提供商,具有更高的性能、可靠性、安全性和灵活性。你可以根据自己的需求定制代码,并充分利用 Cloudflare 提供的各种功能。希望这篇文章能帮助你更好地理解和应用 Cloudflare Workers,开启你的无服务器计算之旅。