使用 Nginx 镜像网站实现「文档自由」

在日常学习和工作中我们经常需要查阅一些文档,但大部分文档都部署在国外的服务器或 CDN 上,导致访问速度较慢甚至偶尔无法访问。为了解决这个问题,我们可以使用 Nginx 在自己的服务器上搭建一个镜像网站,本文以 Kubernetes 官网 为例,演示如何使用 Nginx 镜像网站实现「文档自由」。

首先我们需要准备一台云服务器并安装好 Nginx,本文使用的是 1.27 版本。另外需要一个域名作为镜像网站入口,这里以 mirror.lin2ur.cn 为例,目标是通过 kubernetes.mirror.lin2ur.cn 域名访问 Kubernetes 官网。为了提供 HTTPS 访问我们还需要为域名申请一个 SSL 证书。

0. 反向代理

镜像网站本质上就是反向代理目标网站,Nginx 反向代理的配置相信大家都很熟悉了,下面我们来编写配置文件:

nginx 复制代码
# /etc/nginx/conf.d/mirror.conf

  


map $host $mirror_host {

kubernetes.mirror.lin2ur.cn kubernetes.io;

default "";

}

  


server {

listen 80;

listen 443 ssl;

  


server_name *.mirror.lin2ur.cn;

  


ssl_certificate conf.d/tls.crt;

ssl_certificate_key conf.d/tls.key;

  


resolver 223.5.5.5 ipv6=off;

  


proxy_ssl_server_name on;

  


proxy_set_header Host "$proxy_host";

  


location / {

if ( "$mirror_host" = "" ) {

return 404;

}

proxy_redirect https://$proxy_host https://$host;

  


proxy_pass "${scheme}://${mirror_host}";

}

}

map $host $mirror_host 指令根据客户端请求域名匹配目标域名,方便后续添加新的镜像网站。

resolver 223.5.5.5 指定域名解析服务,在反向代理场景中这个配置是必须的。

proxy_ssl_server_name on 开启与上游服务器建立 SSL/TLS 连接时发送 SNI (Server Name Indication) 扩展字段,部分 CDN 厂商(如 Cloudflare) 强制要求发送 SNI。

proxy_set_header Host "$proxy_host" 设置反向代理时 Host 请求头为目标域名,默认情况下 Nginx 会将客户端请求的 Host 头发送给上游的服务。

proxy_redirect 上游服务返回 30x 跳转响应时重写 Location 响应头使客户端跳转到镜像网站域名,以 Kubernetes 官网为例,使用 HTTP 协议访问时会被重定向到 HTTPS 协议,先来看没有配置 proxy_redirect 的情况:

shell 复制代码
curl -I http://kubernetes.mirror.lin2ur.cn

HTTP/1.1 301 Moved Permanently

Location: https://kubernetes.io/

...

proxy_redirect 指令的变量展开后这个指令等效于 proxy_redirect "https://kubernetes.io" "https://kubernetes.mirror.lin2ur.cn,第一个参数指定匹配的字符串,第二个参数指定替换的字符串,因此 Location 响应头会被重写为:

shell 复制代码
curl -I http://kubernetes.mirror.lin2ur.cn

HTTP/1.1 301 Moved Permanently

Location: https://kubernetes.mirror.lin2ur.cn/

...

到这里我们就完成了初步的配置,接下来访问 https://kubernetes.mirror.lin2ur.cn 看一下效果:

可以看到大部分资源加载请求都都走了镜像网站,这部分资源使用的是相对路径因此我们无需干预。不过还是有一些「漏网之鱼」,这些资源是用绝对路径引入的因此需要特殊处理一下。

1. 处理绝对路径资源

在 Web 中 HTML 和 CSS 都可以引入外部资源,想要修改资源的引入地址我们必须从引入这些资源的资源入手。从上面的截图来看,这些外部资源的请求「发起者」都是 styleheet,也就是说是在 CSS 中引用的,通过关键字搜索我们可以找到这个 CSS 文件:

这个 CSS 是用相对路径引入的,也就是说它会经过 Nginx 的代理,这就给我们提供了修改的机会,修改方式非常简单粗暴:对外部域名进行字符串替换。在 Nginx 中能完成这项工作的是 sub_filter 指令:

nginx 复制代码
map $host $mirror_host {

kubernetes.mirror.lin2ur.cn kubernetes.io;

fonts-googleapis.mirror.lin2ur.cn fonts.googleapis.com;

jsdelivr.mirror.lin2ur.cn cdn.jsdelivr.net;

fonts-gstatic.mirror.lin2ur.cn fonts.gstatic.com;

}

  


server {

...

  


sub_filter '//fonts.googleapis.com' '//fonts-googleapis.mirror.lin2ur.cn';

sub_filter '//cdn.jsdelivr.net' '//jsdelivr.mirror.lin2ur.cn';

sub_filter '//fonts.gstatic.com' '//fonts-gstatic.mirror.lin2ur.cn';

  


sub_filter_once off;

sub_filter_types text/css;

  


proxy_set_header Accept-Encoding "";

  


location / {

...

}

}

上面的配置在基础配置上添加了 3 个镜像域名,接着就是关键指令 sub_filter,它的用法非常简单,将第一个参数指定的字符串替换为第二个参数指定的字符串,这里对新增加的 3 个镜像域名都进行了替换。

sub_filter_once 指定每个请求是否只进行一次 sub_filter 替换,设置为关闭状态以便进行多次替换。

sub_filter_types 指定需要进行替换的 MIME 类型,默认情况下 Nginx 只会对 text/html 的资源进行替换,因此需要加上 text/css

proxy_set_header Accept-Encoding "" 用于清空 Accept-Encoding 请求头,如果客户端请求中包含有 Accept-Encoding 头,上游服务可能会对响应体进行压缩,而 sub_filter 无法对压缩后的内容进行替换。

再次请求可以看到所有资源都已经被代理到镜像网站了:

对于在 HTML 中使用 linkscript 引入的资源我们也可以使用 sub_filter 进行替换,但需要注意的是 sub_filter 不支持正则表达式,因此一些复杂的替换可能需要借助 njsngx_http_substitutions_filter_module 等模块来完成。

2. 通用代理

虽然 map 指令能很方便地添加新的镜像域名,但如果目标网站引入了新的外部资源,我们还是得要手动添加,这显然不是一个完美的解决方案。为了解决这个问题我们可以搭建一个通用的代理,例如请求 https://any.mirror.lin2ur.cn/cdn.jsdelivr.net/vue.js 时,Nginx 会自动代理到 https://cdn.jsdelivr.net/vue.js,接着再配合 sub_filter 指令,我们就可以实现「一劳永逸」了:

nginx 复制代码
server {

...

  


# sub_filter '//fonts.googleapis.com' '//fonts-googleapis.mirror.lin2ur.cn';

# sub_filter '//cdn.jsdelivr.net' '//jsdelivr.mirror.lin2ur.cn';

# sub_filter '//fonts.gstatic.com' '//fonts-gstatic.mirror.lin2ur.cn';

  


sub_filter 'https://' 'https://any.mirror.lin2ur.cn/';

sub_filter 'http://' 'http://any.mirror.lin2ur.cn/';

  


location / {

if ( "$host" = "any.mirror.lin2ur.cn" ) {

rewrite /(.+)$ https://$1 last;

}

...

}

  


location ~ /any/(.+)$ {

internal;

  


set $target "$1";

proxy_redirect ~(http|https)://(.+)$ $1://any.mirror.lin2ur.cn/$2;

proxy_pass "${scheme}://$target?$args";

}

}

在配置中添加了一个新的 location ~ /any/(.+)$ 用于处理通用代理请求,然后修改了 sub_filter 指令在所有绝对路径资源前加了通用代理域名,再来看看效果:

虽然实现了一劳永逸但这种替换方式非常「简单粗暴」,这可能会导致一些问题,我们可以进行一些更精细化的配置,比如:

nginx 复制代码
sub_filter '@import "https://' '@import "https://any.mirror.lin2ur.cn/';'

sub_filter 'url("https://' 'url("https://any.mirror.lin2ur.cn/';

  


sub_filter '<script src="https://' '<script src="https://any.mirror.lin2ur.cn/';

sub_filter '<link href="https://' '<link href="https://any.mirror.lin2ur.cn/';

3. 代理缓存

有同学可能会有疑问做这些的目的是什么?确实,如果云服务器是在境内,那么这个镜像网站并不能起到多大的作用,但如果能在云服务器和目标网站之间加上一层代理缓存,那么镜像网站就能发挥出它的作用了。缓存生成之后即使云服务器和目标网站之间的网络不稳定,也不会影响到用户的访问体验,甚至可以实现「秒开」。

nginx 复制代码
proxy_cache_path /tmp/nginx keys_zone=mirror:10m;

  


server {

...

proxy_cache mirror;

proxy_cache_valid 200 302 24h;

proxy_cache_valid any 1m;

proxy_cache_use_stale error timeout updating http_502;

  


proxy_ignore_headers Cache-Control;

...

}

proxy_cache_valid 指令用于设置上游返回指定状态码时缓存的有效时间,这里将 200、302 状态设置为缓存 24 小时,其余状态缓存 1 分钟;proxy_cache_use_stale 指令用于设置在缓存失效时是否使用过期缓存;proxy_ignore_headers Cache-Control 指令用于忽略上游返回的 Cache-Control 头避免 Nginx 遵循上游的缓存策略,譬如 kubernetes.io 的缓存策略是 public,max-age=0,must-revalidate,该策略允许缓存但必须验证缓存有效性,这意味着每次使用缓存 Nginx 都需要访问一次上游服务,这显然不是我们想要的,因此我们需要忽略这个响应头。

4. GZip 压缩

在给镜像站加上代理缓存后访问速度有了「质的飞跃」,不过这只是优化了 Nginx 与上游服务交互环节,别忘了我们在配置 sub_filter 时指令时清空了 Accept-Encoding 请求头,这意味着上游服务不会对内容进行任何压缩,Nginx 也会原样返回给客户端,对于一些「小水管」云服务器,动辄几十上百 KB 的资源还是会拖慢网站的加载速度,因此我们可以在 Nginx 中开启 GZip 压缩:

nginx 复制代码
server {

...

gzip on;

gzip_comp_level 5;

gzip_min_length 5000;

gzip_proxied any;

gzip_types text/html text/css text/javascript application/javascript image/svg+xml;

...

}

Gzip 的配置和静态网站的配置类似,但默认情况下 Nginx 不会对反向代理请求进行压缩,因此需要加上 gzip_proxied any 指令。

5. 总结

到这里针对 Kubernetes 官网的镜像网站就搭建好了,以上只是针对单个网站提供一个思路,实际情况可能会更复杂,灵活使用 Nginx 的指令可以解决大部分问题。

相关推荐
企业管理8MSaaS5 分钟前
了解CRM销售自动化:类型、优势、策略和工具
运维·自动化
白总Server33 分钟前
MongoDB解说
开发语言·数据库·后端·mongodb·golang·rust·php
创小董44 分钟前
智能机巢+无人机:自动化巡检技术详解
运维·自动化·无人机
计算机学姐1 小时前
基于python+django+vue的家居全屋定制系统
开发语言·vue.js·后端·python·django·numpy·web3.py
henanxiaoman1 小时前
SaltStack自动化运维部署
运维·自动化·saltstack
Run_Snails1 小时前
hcia-openEuler V1.0师资题库-试卷3
运维·服务器·网络
翔云API1 小时前
身份证识别接口的应用场景和作用
运维·服务器·开发语言·自动化·ocr
zhaowangji1 小时前
ubuntu虚拟机装载共享文件夹导致的诡异错误
linux·运维·ubuntu
张望远-长风万里1 小时前
运维监控专项学习笔记-id:0-需求场景、监控作用、监控能力
运维·笔记·学习
小崔爱读书1 小时前
普元DWS - Linux下安装DWS标准版
linux·运维·服务器