背景
目前的组没有远程开发环境,只提供了公用的 staging / test 环境。但是和前端或者上游连调时,常常遇到有一些小问题,改几行代码或者改个配置就能发布上去。因为目前我们的 staging / test 分支是公用分支,需要先合并到统一分支,然后部署到 non-live 环境,整个过程可能会花费数十分钟,非常的影响效率。
为了提升效率,我曾经尝试直接和同事使用开发机器进行连调,但是又遇到了一件新的烦心事:办公室网络的 IP 地址并没有 mac 地址绑定。DHCP 租约到期,或者重启电脑、重新加入公司网络之后, IP 地址会被重新分配。
大的 task 开发周期都会比较长,我和同事的连调一般也会持续比较久,因此每次 IP 有变更时,我们就需要修改下游的 IP 地址,以继续连调。但是这样做经常会遇到几个问题:
- IP 地址的修改可能也需要数十分钟,连调的效率相比部署公用分支并没有提升太多
- IP 地址有时候没改干净,部分服务调用了之前的 IP 地址,导致连调不畅
- 其他因为直接调用 IP 而遇到的诡异问题:流量路由不对、API网关转发依赖绑定的域名等等等等
以上这些问题非常的影响开发效率,因此需要思考一下如何解决? (增加划水时间)
怎么样解决问题?
既然问题已经非常明确了,那么优化这个问题的思路也很明确,什么没有造什么嘛~
优化环境治理
环境治理应该是每个现代互联网公司的必由之路。对于我们这种上下游众多的组,需要做的也是非常成熟的事情:
- 优化 CI/CD 的速度,这个主要是 CI/CD runner 的资源要给够(加钱)
- CI/CD 脚本本身需要提高资源重复利用率,提升运行效率(重构)
- 流量标记,环境标记,并提供多泳道环境治理能力(给人)
总结一下,我们需要做的事情是:
- 加钱
- 重构
- 给人
这三个词一出那么问题的解决周期就已经短不了了。。。
但是问题又在眼前,治不了本总需要先把标治了。
因此我们先暂时跳过这个,来看看还有什么治标的办法解决近渴😅
将本机 IP DDNS 解析到公网
之前在背景中,我们说到,使用 IP 进行连调的一个头疼事,主要是因为 IP 会变,那么我们能否不让 IP 进行变动呢?
咨询 IT 之后,我们得到了否定的答案。毕竟 IT 同事设置 DHCP 是完全没有问题的。对办公网每个设备都需要进行 mac 地址绑定,不论在安全性还是网络可维护性上都属于开倒车。
那么既然 IP 没法变,能不能让我们不用改动下游的调用地址呢?这时候我们应该会非常自然的想到,为什么不把本机 IP 绑到一个域名上呢?这样即使本机 IP 有了变动,也只需要修改一下解析记录,到新的 IP 上就可以了。上下游调用可以实现一次配置,开发全程。再也不需要在这上面浪费时间了!
既然使用 DNS 解析可行,那么就尝试一下吧!但是尝试之后我们发现仍然会有头疼事在这里: DNS解析变动了,还需要人来感知,然后去 DNS 服务提供商那里修改解析,导致连调仍然要被中断。(前端同学怒砸桌子声)
遇到了这个问题后,查阅了下资料,我们可以用 DDNS (Dynamic DNS,动态 DNS 解析)来解决这个问题。但是查阅市场上主流的解决方案之后,发现目前主流的 DDNS 厂商提供开箱即用的客户端,看起来都提供的是公网 DDNS 解析,却没有提供内网 IP 解析功能。
此外,再查询了下内网设备发现,目前有苹果的bonjour、微软的WS-Discovery,其中 bonjour 应该可以满足我们的需求。但是实际使用起来,不知道是因为办公室网络策略,还是因为 Mac 的防火墙配置问题,总是遇到各种各样的小问题导致无法使用。同时 bonjour 的配置成本相对比较高,对于新同学来说非常的不友好,遂放弃了这个解决方案。
既然市面上开箱即用的解决方案没有能解决问题的,那我们还是自己动手实现一个内网 IP DDNS 脚本,毕竟什么没有造什么嘛~
实现一个内网 IP DDNS 脚本
本文所有脚本都编写于 Mac OS 环境下,Windows 和 Linux 系统可能需要自己稍作修改
如何监听 IP 变化
DDNS 的核心在于 Dynamic,Dynamic 的核心在于如何感知 IP 的变化。因为设备本身都是通过 DHCP 拿到的,因此可以考虑通过监听 DHCP 的 OFFER / ACK 事件,来实现监听 IP 地址的变化。
但是这么做未免成本太高,主要是监听 DHCP 即意味着需要监听 UDP 包,整体来看开销比较大(太难了我不会)。
因此我们换一个思路,既然不能监听 DHCP,那我们轮询本机的 ifconfig / ipconfig 输出,然后使用正则表达式从里面匹配 IP 地址,最后去掉 127.0.0.1 这样的回环地址即可。
但是,这样又会遇到另一个问题,因为 ifconfig / ipconfig 的输出一般是输出全部网卡的 IP 信息,这就意味着不论是物理网卡、虚拟网卡的配置信息都会输出。但是我们并不知道办公室 IP 是被绑定到了哪个网卡(连接公司 VPN 时候,办公室 IP 是绑定在虚拟网卡的;连接公司 Wi-Fi 的时候,办公室 IP 是绑定在物理网卡的)。这样对于我们找办公网 IP 非常的不友好。因此需要对正则表达式做一定的定制。
找到我们需要的的 IP 地址
观察办公网 IP 之后,发现办公网 IP 一般分布在 10.1.0.0/16(并非实际使用网段,只是拿来举例) 的一个段内。后面查询 IT 提供的地址段,也确实如此。然后我们知道,IPv4 的地址组成,主要是 [0-255].[0-255].[0-255].[0-255]。既然目前已经确认了办公网使用的网段,我们我们只需要稍加修改正则表达式,就能得到一个简单的匹配表达式:
10.1.[0-9]{1,3}.[0-9]{1,3}
当然这个表达式仍然有一些不足:
- 第三段第四段的网络地址/广播地址没有过滤
- 没有对255以上的数字做过滤
但是考虑到 ifconfig / ipconfig 本身是系统命令,不太可能输出预期之外的地址,因此暂且用这个表达式匹配也可以。(主要是实在写不出来更好的了)
然后我们执行
ifconfig | grep -oe '10.1.[0-9]{1,3}.[0-9]{1,3}'
预期内,屏幕里会输出我们需要的 IP 地址,但是实际上一片空白。但是正则表达式明明是在在线正则表达式验证网站上验证过的,就非常让人不理解了😂。
而后,查阅资料,发现 Mac OS 自带的 grep 并不支持扩展的正则表达式😅,如果希望使用 -e 选项,需要使用 egrep 或者安装 GNU grep 来使用 -e。因为 Mac 自带了 egrep 的缘故,为了省事不额外安装软件,就直接使用了 egrep。
PS: 这里其实还是推荐使用
GNU grep-ggrep,因为得到的执行结果一般能和在线执行器保持一致。Mac 自带的软件包一言难尽。这是我找到的一篇质量很高的答案 grep 在不同操作系统上对于 -o 的实现区别
最后,我们执行
➜ ~ ifconfig | egrep -o '10.1.[0-9]{1,3}.[0-9]{1,3}'
10.1.1.1
10.1.1.255
即可输出我们想要的 IP。但是这里有时候可能会有相同网段的网络号被输出。
查看 ifconfig 输出格式之后,看到网络号会在 IP 地址之后输出,因此加一个 head -n 1 直接取第一行输出即可:
➜ ~ ifconfig | egrep -o '10.1.[0-9]{1,3}.[0-9]{1,3}' | head -n 1
10.1.1.1
利用 Cloudflare API 实现 DDNS
DDNS 的另一个要点在于 Dynamic 地修改 DNS 解析。而如何让 DNS 解析也 Dynamic,这就要看 DNS 服务商是否提供了 API 来修改 DNS 解析记录。恰好我使用的 DNS 服务商是 Cloudflare,它提供的 DNS API 编写的非常友好,因此可以直接调用它的 API 来实现 DDNS 这部分。
调用 Cloudflare API 直接参照了它的文档和 aipeach/cloudflare-api-v4-ddns 。
调用部分如下:
# cloudflare api token
# https://dash.cloudflare.com/profile/api-tokens 可以获取到
apiToken=""
# zone ID
# 可以在 cloudflare 对应的域名页面下获取到
zoneID=""
# sub record id
# can get the id from
# https://api.cloudflare.com/#dns-records-for-a-zone-list-dns-records
dnsRecordID=""
# sub record name
# eg: if u r using example.com, and want to do DDNS for ddns.example.com
# then u should set 'ddns' here
Arecord="ddns"
# office IP is result of ifconfig and filtering
officeIP="10.1.1.1"
curl -k -X PUT "https://api.cloudflare.com/client/v4/zones/${zoneID}/dns_records/${dnsRecordID}" \
-H "Authorization: Bearer ${apiToken}" -H "Content-Type: application/json" \
--data '{"type":"A","name":"'${Arecord}'","content":"'${officeIP}'","ttl":60,"proxied":false}'
这里比较坑的一点在于,现在查到的很多参考中,都使用了API Key,但是实际上 Cloudflare 已经更新了他们的文档,现在可以使用 API Token 作为调用凭据。使用 API Token 对于调用会更加友好,并且权限控制更加优秀。
编写脚本
最后我们写出来的脚本如下:
#!/bin/bash
# cloudflare api token
apiToken=""
# zone ID
zoneID=""
# sub record id
dnsRecordID=""
# sub record
Arecord=""
macDomainIP=`dig $Arecord.example.com +short | head -n 1`
officeIP=`ifconfig | egrep -o '10.1.[0-9]{1,3}.[0-9]{1,3}' | head -n 1`
if [ "$officeIP" == "" ];then
echo "plz check if you are in office or not?"
exit
else
echo "now office ip:${officeIP}, now domain record: ${macDomainIP}"
if [ "$officeIP" == "$macDomainIP" ];then
echo "no need to change"
exit
fi
curl -k -X PUT "https://api.cloudflare.com/client/v4/zones/${zoneID}/dns_records/${dnsRecordID}" \
-H "Authorization: Bearer ${apiToken}" -H "Content-Type: application/json" \
--data '{"type":"A","name":"'${Arecord}'","content":"'${officeIP}'","ttl":60,"proxied":false}'
fi
注册crontab
这里考虑到办公室 DHCP 的租约是1440min(24h),但是 DHCP 本身会自动续租,也就是说 cron 执行间隔其实8h已经足够,但是考虑到可能的断网/重启等异常情况,这里暂时将执行间隔设定为3h一次。
crontab -e
# 将以下内容写入crontab文件,记得修改文件目录
0 */3 * * * bash ddns.sh > ddns.log
总结
毕业一年半来其实很久没有写过博客。这次刚好是假期回家,刚好又没有了能打游戏的电脑,才想起来写点有意思的代码。(水一篇博客)
很多时候写代码本身其实不难,难的是持续的总结和沉淀收获,以及记录自己不断探索的过程。毕竟探索到了什么其实已经不重要,重要的是自己在探索中沉浸在思考中的“心流”状态,最后记录过程时对不足的反思和最后为下次探索总结的经验教训。每天的一小步其实在漫漫人生路中很平凡,但是一步步记录下来,在将来的某个时间点来看,其实会变得非常的有意义。最后是我非常喜欢的一句话作为尾记:
君子知命不惧,日日自新