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

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

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

0. 反向代理

镜像网站本质上就是反向代理目标网站,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 的情况:

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 响应头会被重写为:

curl -I http://kubernetes.mirror.lin2ur.cn
HTTP/1.1 301 Moved Permanently
Location: https://kubernetes.mirror.lin2ur.cn/
...

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

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

1. 处理绝对路径资源

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

20240715112200

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

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 无法对压缩后的内容进行替换。

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

20240715113134

对于在 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 指令,我们就可以实现「一劳永逸」了:

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 指令在所有绝对路径资源前加了通用代理域名,再来看看效果:

20240715115659

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

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. 代理缓存

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

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 压缩:

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 的指令可以解决大部分问题。

Nginx是用于HTTP,HTTPS,SMTP,POP3和IMAP协议的开源反向代理服务器,以及负载平衡器,HTTP缓存和Web服务器(原始服务器)。Nginx项目一开始就非常关注高并发,高性能和低内存使用。它获得了两节式BSD许可,并在Linux,BSD变体,Mac OS X,Solaris,AIX,HP-UX以及其他* nix版本上运行。它还具有用于Microsoft Windows的概念证明端口。 托管一些简单的静态内容 $ docker run --name some-nginx -v /some/content:/usr/share/nginx/html:ro -d nginx 或者,Dockerfile可以使用简单的方法来生成包含必要内容的新图像(这比上面的绑定安装要干净得多): FROM nginx COPY static-html-directory /usr/share/nginx/html 将此文件放置在与目录相同的目录中(“ static-html-directory”),运行docker build -t some-content-nginx .,然后启动容器: $ docker run --name some-nginx -d some-content-nginx 暴露外部端口 $ docker run --name some-nginx -d -p 8080:80 some-content-nginx 然后,您可以在浏览器中点击http://localhost:8080或http://host-ip:8080。 复杂的配置 $ docker run --name my-custom-nginx-container -v /host/path/nginx.conf:/etc/nginx/nginx.conf:ro -d nginx 有关nginx配置文件的语法的信息,请参阅官方文档(特别是《入门指南》)。 如果您希望采用默认配置,请使用以下类似内容从运行的nginx容器中复制它: $ docker run --name tmp-nginx-container -d nginx $ docker cp tmp-nginx-container:/etc/nginx/nginx.conf /host/path/nginx.conf $ docker rm -f tmp-nginx-container 也可以使用简单的Dockerfile(在中/host/path/)更干净地完成此操作: FROM nginx COPY nginx.conf /etc/nginx/nginx.conf 如果您CMD在Dockerfile中添加了自定义,请确保将包含-g daemon off;在其中CMD,以使nginx保持在前台,以便Docker可以正确跟踪进程(否则您的容器将在启动后立即停止)! 然后使用构建图像docker build -t custom-nginx .并按如下所示运行它: $ docker run --name my-custom-nginx-container -d custom-nginxNginx配置中使用环境变量 现成的nginx不支持大多数配置块中的环境变量。但是,envsubst如果您需要在nginx启动之前动态生成nginx配置,则可以将其用作解决方法。 这是使用docker-compose.yml的示例: web: image: nginx volumes: - ./mysite.template:/etc/nginx/conf.d/mysite.template ports: - "8080:80" environment: - NGINX_HOST=foobar.com - NGINX_PORT=80 command: /bin/bash -c "envsubst /etc/nginx/conf.d/default.conf && exec nginx -g 'daemon off;'" mysite.template然后,该文件可能包含如下变量引用: listen ${NGINX_PORT}; 在只读模式下运行Nginx 要以只读模式运行nginx,您需要将Docker卷安装到nginx写入信息的每个位置。默认的nginx配置需要对/var/cache和的写权限/var/run。可以通过如下运行nginx轻松地完成此操作: $ d
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值