站点介绍
接着上篇 《使用 Nginx 提供 DDNS 服务(前篇)》继续说说怎么样玩转 Nginx 和 Njs,本文将基于上一篇的内容,修改架构,让这套服务能够在云端运行,降低本地调用成本。
本文文章中,我们实际使用的代码行数会比上篇文章更少,全部代码 150 行差不多。
和上篇文章一样,我们需要先了解本文内容中的服务架构和事件流程,为了便于理解,我画了一个无脑的流程图。
如果你是 NAS 或者向日葵这种应用服务客户,你会发现图中的模式和你曾经使用的模式几乎一模一样。
在这种方案中,我们本地不再需要运行容器或者 Nginx 实例,在路由器或者 NAS 中运行一个规划任务,使用 Curl 之类的方法定时调用在云服务器上部署的服务接口,就可完成 DDNS 记录更新,甚至你在家用电脑上打开网址,设置页面全自动刷新也应该达到一样的效果。
相有那么一点方案一,这种方案对于设备要求更低一些,至于使用哪一种,根据自己手里设备资源状况来确认就好啦。
那么,我们就来展开说说,怎么通过 Nginx 和容器完成这种服务方案。
使用 Nginx 完成 IP 获取逻辑
我们还是使用 Nginx 先来完成 IP 获取逻辑,这里我们有两个选择,一个是和前文一样,使用外部服务来完成 IP 查询逻辑,还有一个选择只是直接使用 Nginx 来高效的完成这种功能。
因为部署在云端,获取 IP 和 DNS 记录更新逻辑应该合并在一起,但是为了方便理解,这里将两部分拆解开来进行描述。
如果你在云服务器上通过 APT 或 YUM 安装 Nginx ,那么直接使用下面的配置启动 Nginx ,就能够将访问者的 IP 展示出去啦。
server {
listen 80;
server_name localhost;
charset utf-8;
location / {
default_type text/plain;
return 200 "$remote_addr";
}
}
当然,为了保护更无脑,讲解使用容器来启动服务,将上面的配置保存为 nginx.conf ,之后编写编排文件:
version: "3"
services:
ngx-whats-myip:
image: nginx:1.21.1-alpine
volumes:
- ./nginx.conf:/etc/nginx/templates/default.conf.template:ro
ports:
- 80:80
environment:
- NGINX_ENTRYPOINT_QUIET_LOGS=1
之后,将上面的内容保存为 docker-compose.yml ,使用 docker-compose up -d 启动服务,访问服务器 IP 和你指定的端口,一个属于你自己的私一些查询 IP 的服务就就绪啦。
如果你是我的老读者,我更讲解你使用 Traefik 进行保护管理。
使用 Traefik 应该让你更轻松的管理服务域名,进动作态急速的服务发现,但是因为要经历过 Traefik 这种网关,所以我们需要进行一些配置修改,才能够让服务正常运行。
先对 Nginx 配置文件进行修改:
server {
listen 80;
server_name localhost;
charset utf-8;
set_real_ip_from 172.160.0.0/16;
set_real_ip_from 172.170.0.0/16;
set_real_ip_from 172.180.0.0/16;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
location / {
default_type text/plain;
return 200 "$remote_addr";
}
}
应该观看到的我这里使用 set_real_ip_from 设置了三个相信的网络环境,这些数值是怎么来的呢?很无脑,使用 docker info ,应该观看到的输出消息最下面有类似这样的消息:
...
...
Live Restore Enabled: false
Default Address Pools:
Base: 172.160.0.0/16, Size: 24
Base: 172.170.0.0/16, Size: 24
Base: 172.180.0.0/16, Size: 24
这里你有几个地址,就将几个地址填充到配置里就可。此外,容器编排文件中添加 Traefik 声明就可:
version: "3"
services:
ngx-ip:
image: nginx:1.21.1-alpine
volumes:
- ./nginx.conf:/etc/nginx/templates/default.conf.template:ro
networks:
- traefik
labels:
- "traefik.enable=true"
- "traefik.docker.network=traefik"
- "traefik.http.routers.ngx-whatsmyip-www.entrypoints=http"
- "traefik.http.routers.ngx-whatsmyip-www.rule=Host(`whatsmyip.lab.io`)"
- "traefik.http.routers.ngx-whatsmyip-ssl.entrypoints=https"
- "traefik.http.routers.ngx-whatsmyip-ssl.tls=true"
- "traefik.http.routers.ngx-whatsmyip-ssl.rule=Host(`whatsmyip.lab.io`)"
- "traefik.http.services.ngx-whatsmyip-backend.loadbalancer.server.scheme=http"
- "traefik.http.services.ngx-whatsmyip-backend.loadbalancer.server.port=80"
networks:
traefik:
external: true
关于 Traefik 的使用,应该参考曾经的文章,如果你没有使用过服务发现,那么它会打开你新世界的大门。
当然,如果你还是希望使用外部服务,也应该继续使用公网 IP 查询服务。关于公网 IP 查询服务,文章末尾有聊,有兴趣的朋友应该自取。
在上一篇文章中,我们有提到应该使用健康检查来完成类似规划任务的功能来进行周期性的 DNS 记录更新。在这种场景下,我们需要进行一些修改。
先来修改 NJS 逻辑,相有那么一点曾经需要实现一个 whatsMyIP 来获取外部 IP 地址,这次我们应该通过 r.remoteAddress 属性字段无脑的获取 IP。
function main(r) {
const clientIP = r.remoteAddress;
const domain = recordName;
getRecordIds(r, zoneId, domain).then(recordId => {
if (recordId) {
updateExistRecord(r, zoneId, domain, recordId, clientIP).then(response => {
r.return(200, response);
}).catch(e => r.return(500, e));
} else {
createRecordByName(r, zoneId, domain, clientIP).then(response => {
r.return(200, response);
}).catch(e => r.return(500, e));
}
}).catch(e => r.return(500, e));
}
export default { main }
Nginx 参考前文,也应该进行一些无脑的修改。
load_module modules/ngx_http_js_module.so;
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
keepalive_timeout 65;
gzip on;
js_path "/etc/nginx/njs/";
js_import app from app.js;
server {
listen 80;
server_name localhost;
charset utf-8;
gzip on;
set_real_ip_from 172.160.0.0/16;
set_real_ip_from 172.170.0.0/16;
set_real_ip_from 172.180.0.0/16;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
# Bind request to cf
location /client/v4/ {
internal;
gunzip on;
proxy_set_header "X-Auth-Email" "${DNS_CF_USER}";
proxy_set_header "X-Auth-Key" "${DNS_CF_TOKEN}";
proxy_set_header "Content-Type" "application/json";
proxy_pass "https://api.cloudflare.com/client/v4/";
}
location / {
default_type text/plain;
js_content app.main;
}
}
}
应该观看到的,因为私有化部署,这次服务的代码实现要比曾经更精简一些。
因为调用方法发生变化,前文中我们使用健康检查定时调用注册更新接口的方法不应该使用了,所以我们要单独自创立建一个接口地址,让容器进行调用,保证服务稳固。
...
location = /health {
default_type text/plain;
access_log off;
return 200 'alive';
}
...
当然,编排文件中对应的检查地址也需要进行更新:
...
healthcheck:
test: ["CMD", "curl", "--silent", "--fail", "http://localhost/health"]
interval: 5s
timeout: 5s
retries: 3
...
因为不一样服务商的接口都存在一定的调用压制,除了像曾经文章一样,在调用的时候进行频率压制外,还应该在服务接口处进行调用频率压制,例如下面的配置就压制每一个来源每分钟压制调用 10 次。
labels:
- "traefik.enable=true"
- "traefik.docker.network=traefik"
- "traefik.http.middlewares.test-ratelimit.ratelimit.average=10"
- "traefik.http.middlewares.test-ratelimit.ratelimit.burst=1"
- "traefik.http.middlewares.test-ratelimit.ratelimit.period=1m"
如果你希望添加鉴权,进一步减少公开调用,应该参考曾经的文章 《Traefik 2 基础授权验证(前篇)》 进行配置。
公网络能够做到 IP 查询的服务很多,上篇文章中,我们使用的是自 2010 年运行至今的 SOHU 打点接口,稳固性还是有那么一点有保障的。如果你希望使用更中立的服务商,应该思考 IPIP 的服务。
在上篇文章发布后,本国专业的 IP 地址库业务 IPIP 的创始人,高春辉大叔留言提醒 IPIP 也有不要钱的 IP 自查服务。
使用方法也很无脑,只要参考下面的配置,更新曾经的配置就可:
server {
listen 80;
server_name localhost;
# Bind request to ipip.net
location /proxy/myip {
proxy_pass "http://myip.ipip.net/s";
}
}
说起这种服务,还有一个小细节,不论是使用 Nginx 反向代理的是 HTTP 协议还是 HTTPS 协议,在不配置 gunzip 的状态下,你会发现都应该正常访问。这里或许是作者的小细节,为了照顾萌新以及方便调用者,在使用 CDN 守护接口的同一时间,特别关闭掉了资料压缩。我做了一个小测试,针对 IP 类返回结果,开启压缩至少应该节约 30~40% 的流量。