背景
家中自建服务器或者树莓派或者软路由,需要搭建自己使用的一些系统,使用cloudflare实现穿透公网访问,由于自建服务器的不稳定性,并且网络提供商每周会重启网络设备(亦或者公网ip到期后会改变等原因)。但域名上解析的ip由于不能立刻被替换为新的ip导致服务不可用。
解决方案
配置动态脚本实现ip改变后自动更改解析即DDNS 具体可以看 这里的介绍 或者 国内版介绍
实现的前提:
实现前提
- 家里的网络需要是公网ip。 如何确认是否为公网IP? 这个比较简单,可以访问这个地址 页面返回的一个地址,然后再到自己的路由器中查看运营商分配的地址,若相同则是公网ip,若不同则是NAT ip, 具体怎么索要公网ip不同地区、不通运营商要求不同,请自行咨询.
- 需要有一台能够执行定时任务的机器,包括但不限于软路由插件、linux系统、docker服务等.
- cloudflare 中维护的一个域名
准备cloudflare账户token
由于要操作账户内的内容,我们需要生成一个api token,由于只需要操作dns解析,所以我们只需要生成一个操作dns权限的api token即可(一定要严格控制权限,千万不要使用账户的token)
首先进入个人账户中心
![prfile.png](/medias/cloudflare-ddns/profile.png)
![api-token.png](/medias/cloudflare-ddns/api-token.png)
然后点击 API Tokens 进入token管理页面
![create-token.png](/medias/cloudflare-ddns/create-token.png)
找到DNS管理点击 “Use template”
![dns-template.png](/medias/cloudflare-ddns/dns-template.png)
根据自己的需要填写权限信息,我这里指定了某域名的dns解析编辑权限
![token-detail.png](/medias/cloudflare-ddns/token-deetail.png)
点击 “continue to summary” 以后会展示生成的token和验证方案
验证token结果:
![verify-token.png](/medias/cloudflare-ddns/verify-token.png)
注意:这个token只能显示这一次,一定要保存好!
我们在token管理页面可以看见这个token记录(看不见内容),当我们不想使用的时候删除即可
![token-list.png](/medias/cloudflare-ddns/token-list.png)
创建域名解析
我们需要在cloudflare先创建一个dns解析,即我们需要动态更新ip的域名解析
![domain.png](/medias/cloudflare-ddns/domain.png)
实现动态更新DDNS
根据官方接口文档,我们需要以下参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| curl --request PUT \ --url https://api.cloudflare.com/client/v4/zones/zone_identifier/dns_records/identifier \ --header 'Content-Type: application/json' \ --header 'X-Auth-Email: ' \ --data '{ "comment": "Domain verification record", "content": "127.0.0.1", "name": "example.com", "proxied": false, "tags": [ "owner:dns-team" ], "ttl": 3600 }'
|
字段名 |
字段描述 |
参数类型 |
是否获取/已知 |
Authorization |
用户验证 token |
header 必填 |
已知 |
Content-Type |
请求类型 |
header 必填 |
已知 |
X-Auth-Email |
授权用户 |
header 必填 |
已知 |
identifier |
DNS 记录的id |
路径参数 必填 |
未知 |
zone_identifier |
域名的id |
路径参数 必填 |
未知 |
comment |
注释 |
body参数 不必填 |
已知 |
content |
记录值(服务ip) |
body参数 必填 |
未知 |
name |
记录名称 |
body参数 必填 |
已知 |
proxied |
是否cdn代理 |
body 参数 不必填 |
已知 |
tags |
标签 |
body 参数 不必填 |
已知 |
ttl |
有效时间 |
body参数 必填 |
已知 |
根据上表统计,我们目前有三项是未知的,第一项就是当前服务的ip,第二项是dns记录id 第三项是域名记录id,我们一项一项来解决
获取当前服务的ip
这里提供一些网站用来获取自己当前公网解析ip,请根据自己的情况获取,我们的目标是拿到一个ipv4地址
1 2 3 4 5 6 7 8 9
| $ curl ifconfig.me $ curl icanhazip.com $ curl ident.me $ curl ipecho.net/plain $ curl whatismyip.akamai.com $ curl tnx.nl/ip $ curl myip.dnsomatic.com $ curl ip.appspot.com $ curl -s checkip.dyndns.org | sed \'s/.*IP Address: \([0-9\.]*\).*/\1/g\'
|
我使用的是 http://ipv4.icanhazip.com/ 直接使用curl 就可以拿到
获取域名的id值
这个域名是维护在cloudflare上,那么我们肯定要通过官方文档介绍来获取
首先拿到用户ZoneID , 根据这篇文章 返回值中会有对应的域名的id
1 2 3 4 5 6 7
| AUTH_EMAIL="cloudflare注册邮箱" AUTH_TOKEN="账户中生成的token" curl --request GET \ --url https://api.cloudflare.com/client/v4/zones \ --header 'Content-Type: application/json' \ --header "X-Auth-Email: ${AUTH_EMAIL}" \ --header "Authorization: Bearer ${AUTH_TOKEN}"
|
返回中我们需要找到对应的id
![domainid.png](/medias/cloudflare-ddns/domainid.png)
获取DNS记录的id值
上一步我们拿到了域名的id,可以通过域名id拿到记录id值, 参考 这篇文章
1 2 3 4 5 6 7 8
| AUTH_EMAIL="cloudflare注册邮箱" AUTH_TOKEN="账户中生成的token" ZONE_ID="上一步获取的域名id" curl --request GET \ --url "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records" \ --header 'Content-Type: application/json' \ --header "X-Auth-Email: ${AUTH_EMAIL}" \ --header "Authorization: Bearer ${AUTH_TOKEN}"
|
找到我们要的recordID
![recordid.png](/medias/cloudflare-ddns/recordid.png)
测试更新记录是否生效
上面我们拿到了所有未知的值,接下来就是更新解析的记录值了 , 根据官方文档 得到的更新接口如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| AUTH_EMAIL="cloudflare注册邮箱" AUTH_TOKEN="账户中生成的token" ZONE_ID="上一步获取的域名id" RECORD_ID="上面获取的DNS记录的id" CURRENT_IP="当前要更新的ip"
curl --request PUT \ --url "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records/${RECORD_ID}" \ --header 'Content-Type: application/json' \ --header "X-Auth-Email: ${AUTH_EMAIL}" \ --header "Authorization: Bearer ${AUTH_TOKEN}" \ --data '{ "comment": "Auto update", "content": "${CURRENT_IP}", "name": "mail.jaffee.cn", "proxied": false, "ttl": 600, "type": "A" }'
|
![update-record.png](/medias/cloudflare-ddns/update-record.png)
再看下记录
![recordlist.png](/medias/cloudflare-ddns/recordlist.png)
动态更新脚本
上面我们已经搞定了如何获取当前ip,如何通过restful接口更新记录值,那么我们只需要动态实现实时获取并更新记录值就可以了,大致流程如下:
![update-follow.png](/medias/cloudflare-ddns/updatefollow.png)
逻辑清晰了,我们就来写一下脚本处理吧,完整的脚本如下:
注: 我们使用的jq 命令 需要进行安装 apt install jq (debian/ubuntu)或者 yum install jq (centos/readheat)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| set -e #获取公网ip地址网站 GET_IP_SITE="http://ipv4.icanhazip.com/" # CURRENT_IP=$(curl -s ${GET_IP_SITE}) # cloudflare 用户邮箱 AUTH_EMAIL="your cloudflare mail" # cloudflare 用户接口操作token AUTH_TOKEN="yout user token" # 域名id ZONE_ID="域名ID 上面已经拿到" # 记录ID RECORD_ID="DNS记录ID 上面已经拿到" # 记录 RECORD_NAME="待更新的记录值"
RECORD_DETAIL_JSON=$(curl -s --request GET \ --url "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records/${RECORD_ID}" \ --header 'Content-Type: application/json' \ --header "X-Auth-Email: ${AUTH_EMAIL}" \ --header "Authorization: Bearer ${AUTH_TOKEN}")
RECORD_IP=$(echo ${RECORD_DETAIL_JSON} | jq .result.content | sed 's/\"//g')
if [ "${CURRENT_IP}" == "${RECORD_IP}" ];then echo "IP not change.. no update action." exit 0 fi echo "Record ip = [${RECORD_IP}], Current ip = [${CURRENT_IP}], do update action!" # 走到这就更新 UPDATE_RESULT=$(curl -s --request PUT \ --url "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records/${RECORD_ID}" \ --header 'Content-Type: application/json' \ --header "X-Auth-Email: ${AUTH_EMAIL}" \ --header "Authorization: Bearer ${AUTH_TOKEN}" \ --data "{ \"comment\": \"Auto ddns update\", \"content\": \"${CURRENT_IP}\", \"name\": \"${RECORD_NAME}\", \"proxied\": false, \"ttl\": 600, \"type\": \"A\" }")
UPDATE_SUCCESS=$(echo ${UPDATE_RESULT} | jq .success) if [ "${UPDATE_SUCCESS}" == "true" ];then echo "update success!" echo "${UPDATE_RESULT}" exit 0 else echo "update fail !" echo "${UPDATE_RESULT}" exit 1 fi
|
部署方案
由于脚本没有实现定时,所以我们需要自行实现定时执行方案,这里例举几种方案
- linux crontab: 这种是最简方案,只需要我们将脚本放到服务器上并添加cron执行计划即可
假设脚本在用户家目录 ,名字为cloudflare-ddns.sh, 则可以这么写:
0/10 * * * * /bin/bash ~/cloudflare-ddns.sh >> /var/log/cloudflare-ddns.log
- 使用docker: 这就需要我们自己准备一个镜像,这里我提供一个dockerfile 脚本
假设构建目录下有一个cloudflare-ddns.sh
1 2 3 4 5 6 7 8 9 10 11 12 13
| FROM ubuntu:22.04 COPY cloudflare-ddns.sh /opt/app/cloudflare-ddns.sh RUN apt update \ && apt install -y jq cron curl \ && apt clean \ && apt autoclean \ && chmod +x /opt/app/cloudflare-ddns.sh \ && mkdir -p /var/log/cloudflare-ddns \ && echo "cloudflare ddns logs" > /var/log/cloudflare-ddns/cloudflare-ddns.log \ && echo "*/10 * * * * /bin/bash /opt/app/cloudflare-ddns.sh >> /var/log/cloudflare-ddns/cloudflare-ddns.log" > /var/spool/cron/crontabs/root \ && chown root:crontab /var/spool/cron/crontabs/root \ && chmod 600 /var/spool/cron/crontabs/root CMD service cron start && tail -f /var/log/cloudflare-ddns/cloudflare-ddns.log
|
- 使用K8S cron job: 将任务放在集群里定时执行,执行时间由集群控制,不需要人工操作定时任务
Dockerfile如下:
1 2 3 4 5 6 7
| FROM ubuntu:22.04 COPY cloudflare-ddns.sh /opt/app/cloudflare-ddns.sh RUN apt update \ && apt install -y jq curl \ && apt clean \ && apt autoclean \ && chmod +x /opt/app/cloudflare-ddns.sh
|
k8s deploy 内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| apiVersion: batch/v1 kind: CronJob metadata: name: cloudflare-mail-ddns namespace: production spec: schedule: "*/10 * * * *" successfulJobsHistoryLimit: 3 failedJobsHistoryLimit: 5 startingDeadlineSeconds: 60 jobTemplate: spec: template: spec: imagePullSecrets: - name: harborsecret containers: - name: cloudflare-mail-ddns image: xxxxxx.com:5000/base/cloudflare-ddns-mail:1.0.0 imagePullPolicy: IfNotPresent command: ["/bin/bash", "-c"] args: ["xxxxxxx"] restartPolicy: OnFailure
|
![cronJob.png](/medias/cloudflare-ddns/cronjob.png)
![jobs.png](/medias/cloudflare-ddns/jobs.png)