如何构建自己的 CDN
图片来源: pikisuperstar 创建的信息图矢量 — www.freepik.com
内容分发网络 (CDN) 通常被网站和应用程序用来加速静态元素的加载。这是通过将文件缓存在位于世界各地的 CDN 服务器上来实现的。用户通过 CDN 请求数据后,会从最近的服务器接收数据。
所有 CDN 背后的基本原理及其功能大致相同。收到文件请求后,CDN 服务器会从源服务器获取一次文件,然后将其传输给用户,并缓存副本一段时间。后续的数据请求将使用缓存进行处理。所有 CDN 都提供预加载文件、清除缓存、缓存保留时间等选项。
有时,由于各种原因,人们可能需要构建自己的 CDN,因此,我们提供了以下有关如何实现它的指南。
什么时候需要自己的 CDN?
让我们看一下可能需要创建自己的 CDN 的情况:
- 当你试图省钱时,即使是像BunnyCDN这样经济实惠的解决方案,最终也会花费你每月数百美元
- 当你想要获得永久缓存或需要保证带宽和资源时
- 现有 CDN 在您的目标区域没有 PoP
- 您需要特殊的内容传送设置
- 您希望通过更靠近用户的方式加快动态内容的交付
- 您担心第三方 CDN 可能会非法收集和使用用户数据(不符合 GDPR 的服务器)或从事其他非法活动
在大多数其他情况下,使用现有的现成解决方案更为可行。
让我们创建自己的 CDN
即使要构建一个简单的内容交付网络,您也需要以下内容:
- 域名或子域名
- 至少两台位于不同地区的服务器。服务器可以是虚拟的,也可以是专用的
- geoDNS工具。有了它,向域名发送请求的用户将被定向到最近的服务器
注册域名和订购服务器
注册域名很简单——只需在您喜欢的域名区域注册即可。对于 CDN,您还可以使用子域名,例如cdn.domainname.com。以下示例就是这种情况。
在服务器方面,您应该在目标受众所在的地区和国家/地区租用服务器。如果您的项目是跨洲的,那么选择提供全球服务器的托管服务提供商会很方便,例如PQ.hosting和DigitalOcean(提供虚拟服务器和云服务器),或OVH和Leaseweb(提供专用服务器)。
对于中小型项目,虚拟服务器通常就足够了。它们也比专用服务器便宜得多。例如,PQ.hosting提供25GB NVMe服务器,无限流量,每月仅需4.77 欧元,可在 30 多个地点使用。
让我们在不同的大洲订购三个虚拟服务器。安装时,请选择最新的 Debian。以下是我们的服务器:
-
法兰克福,IP:199.247.18.199
-
芝加哥, IP: 149.28.121.123
-
新加坡, IP: 157.230.240.216
配置 geoDNS
为了确保客户端在向我们的域或子域发送请求时被定向到正确的(最近的)服务器,我们需要一个具有 geoDNS 功能的 DNS 服务器。
geoDNS 的工作原理如下:
- 它会获取客户端的 IP(如果客户端发送了 DNS 请求)或用于处理该请求的递归 DNS 服务器的 IP。一般来说,此类递归服务器通常是互联网服务提供商的 DNS。
- 通过客户端的 IP 地址识别其所在国家或地区。此操作需要使用 GeoIP 数据库,这类数据库资源非常丰富,甚至还有不错的免费选项。
- 根据客户端的位置,geoDNS 向其返回最近的 CDN 服务器的 IP 地址。
您可以自行构建具有 geoDNS 功能的 DNS 服务器,但最好使用在世界各地设有服务器且具有开箱即用的Anycast选项的现成解决方案:
- СlouDNS,每月 9.95 美元起,GeoDNS 套餐,默认提供一个 DNS 故障转移
- Amazon Route 53,每月 35 美元起,可处理 5000 万个地理请求。DNS 故障转移功能需单独定价
- DNS Made Easy,每月 125 美元起,提供 10 个 DNS 故障转移
- Cloudflare,企业包中提供 Geo Steering 功能
订购 geoDNS 时,您需要注意套餐中包含的请求数量,并记住实际请求数量可能远远超出您的预期。任何时候都有数百万个网络爬虫、扫描器、垃圾邮件发送者和其他恶意程序在作祟。
几乎所有 DNS 服务都包含一个对 CDN 构建有用的功能——DNS 故障转移。有了它,您可以配置活动监控,这样如果服务器出现故障,系统就会自动将客户端重定向到正常运行的服务器。
对于我们的 CDN,我们使用ClouDNS及其 GeoDNS 包。
在配置文件中,添加新的 DNS 区域并指定您的域名。如果您使用的是子域名,并且主域名正在使用中,请不要忘记在添加区域后立即添加现有的 DNS 记录。下一步是为 CDN 域名/子域名创建多个 A 记录,每个记录将用于指定的区域。您可以将大洲或国家/地区指定为区域,并且美国和加拿大提供了子区域选项。
在我们的示例中,CDN 将在cdn.sayt.in子域名上运行。添加sayt.in区域后,为该子域名创建第一条 A 记录,并将所有 NA 客户端定向到芝加哥服务器:
对其他区域重复此步骤,不要忘记为默认区域创建一条记录。最终结果如下:
最后一条默认记录意味着来自所有未指定地区(欧洲、非洲、卫星互联网用户等)的请求都将被定向到法兰克福服务器。
基本 DNS 配置到此结束。剩下的就是前往注册商网站,将当前的域名服务器替换为 ClouDNS 提供的服务器。在更新过程中,我们会设置服务器。
安装 SSL 证书
我们的 CDN 将使用 HTTPS 运行,因此如果您已经拥有域或子域的 SSL 证书,请将其上传到所有服务器,例如,上传到/etc/ssl/yourdomain/目录。
如果您没有任何证书,可以从 Let's Encrypt 免费获取。ACME Shell 脚本是一个不错的选择。它拥有用户友好的客户端,更重要的是,它允许使用 ClouDNS 的 API 通过 DNS 验证域名/子域名。
我们将仅在一台服务器上安装 acme.sh——欧洲服务器(199.247.18.199),并将证书从该服务器复制到所有其他服务器。要安装它,请运行以下命令:
root@cdn:~# wget -O - https://get.acme.sh | bash; source ~/.bashrc
在安装期间,将创建一个用于自动更新证书的 CRON 任务。
证书颁发后的域名验证将通过 DNS API 进行,因此在 ClouDNS 配置文件的“经销商 API”下,创建一个新的 API 用户并为其指定密码。将生成的 auth-id 和密码输入到以下文件中:~ /.acme.sh/dnsapi/dns_cloudns.sh(不要与 dns_cloudns.sh 混淆)。以下是需要取消注释并编辑的行:
CLOUDNS_AUTH_ID=<auth-id>
CLOUDNS_AUTH_PASSWORD="<password>"
现在,让我们请求颁发cdn.sayt.in的 SSL 证书
root@cdn:~# acme.sh --issue --dns dns_cloudns -d cdn.sayt.in --reloadcmd "service nginx reload"
为了将来的使用,在参数中,我们留下了一个命令,用于每次更新证书后自动配置重启。
获取证书的过程可能需要两分钟,因此请勿中断。如果出现域验证错误,请尝试重新运行该命令。最后,我们将看到证书的下载位置。
记住这些路径,我们需要在将证书复制到其他服务器时指定它们,并且需要在服务器设置中指定它们。忽略 Nginx 配置重新加载错误——在证书更新期间,在配置完整的服务器上不会发生此错误。
至于 SSL 证书,我们剩下要做的就是将其复制到另外两台服务器,并保存证书路径。在每台服务器上创建相同的目录,然后复制证书文件:
root@cdn:~# mkdir -p /root/.acme.sh/cdn.sayt.in/
root@cdn:~# scp -r root@199.247.18.199:/root/.acme.sh/cdn.sayt.in/* /root/.acme.sh/cdn.sayt.in/
要自动更新证书,您需要在两台服务器上创建每日 CRON 任务。以下是您应该添加到 CRON 作业的命令:
scp -r root@199.247.18.199:/root/.acme.sh/cdn.sayt.in/* /root/.acme.sh/cdn.sayt.in/ && service nginx reload</pre>
请记住,连接到远程源服务器需要密钥访问,无需输入密码。别忘了创建它。
安装和配置Nginx
对于静态内容交付,我们将使用配置为缓存代理服务器的 Nginx。更新软件包列表并将其安装在所有三台服务器上:
root@cdn:~# apt update
root@cdn:~# apt install nginx
不要使用默认配置,而要使用以下配置:
nginx.conf
user www-data;
worker_processes auto;
pid /run/nginx.pid;
events {
worker_connections 4096;
multi_accept on;
}
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
access_log off;
error_log /var/log/nginx/error.log;
gzip on;
gzip_disable "msie6";
gzip_comp_level 6;
gzip_proxied any;
gzip_vary on;
gzip_types text/plain application/javascript text/javascript text/css application/json application/xml text/xml application/rss+xml;
gunzip on;
proxy_temp_path /var/cache/tmp;
proxy_cache_path /var/cache/cdn levels=1:2 keys_zone=cdn:64m max_size=20g inactive=7d;
proxy_cache_bypass $http_x_update;
server {
listen 443 ssl;
server_name cdn.sayt.in;
ssl_certificate /root/.acme.sh/cdn.sayt.in/cdn.sayt.in.cer;
ssl_certificate_key /root/.acme.sh/cdn.sayt.in/cdn.sayt.in.key;
location / {
proxy_cache cdn;
proxy_cache_key $uri$is_args$args;
proxy_cache_valid 90d;
proxy_pass https://sayt.in;
}
}
}
在配置中,让我们编辑:
- max_size — 缓存大小不超过可用磁盘空间
- inactive — 未请求的缓存数据的保留时间
- ssl_certificate和ssl_certificate_key — SSL 证书和密钥的路径
- proxy_cache_valid — 缓存数据的保留时间
- proxy_pass — CDN 请求缓存数据的源服务器地址。本例中是sayt.in
如你所见,这并非什么高深的科学。考虑到inactive和proxy_cache_valid参数之间的相似性,唯一的难点在于配置保留时间。让我们仔细看看。以下是inactive=7d和proxy_cache_valid=90d 时的情况:
- 如果 7 天内没有重复请求,数据将从缓存中删除
- 如果在 7 天内重复请求一次,则缓存将在 90 天后被视为过期,并且下一个请求将使 Nginx 从源服务器更新它
处理完nginx.conf后,重新加载配置:
root@cdn:~# service nginx reload
所以,我们的 CDN 已经可以使用了!只需每月 15 美元,我们就可以在三大洲获得 PoP 和 3TB 的流量:每个地区 1TB。
检查我们的 CDN
让我们看看从不同位置对 CDN 的 ping 操作。在这种情况下,任何 ping 服务都可以。
Ping 服务器 | 主持人 | 知识产权 | 平均时间,毫秒 |
---|---|---|---|
德国,柏林 | cdn.sayt.in | 199.247.18.199 | 9.6 |
荷兰,阿姆斯特丹 | cdn.sayt.in | 199.247.18.199 | 10.1 |
法国,巴黎 | cdn.sayt.in | 199.247.18.199 | 16.3 |
英国,伦敦 | cdn.sayt.in | 199.247.18.199 | 14.9 |
加拿大,多伦多 | cdn.sayt.in | 149.28.121.123 | 16.2 |
美国,旧金山 | cdn.sayt.in | 149.28.121.123 | 52.7 |
美国,达拉斯 | cdn.sayt.in | 149.28.121.123 | 23.1 |
美国,芝加哥 | cdn.sayt.in | 149.28.121.123 | 2.6 |
美国,纽约 | cdn.sayt.in | 149.28.121.123 | 19.8 |
新加坡 | cdn.sayt.in | 157.230.240.216 | 1.7 |
日本,东京 | cdn.sayt.in | 157.230.240.216 | 74.8 |
澳大利亚,悉尼 | cdn.sayt.in | 157.230.240.216 | 95.9 |
结果不错。现在,我们在主服务器上放置一张名为test.jpg的测试图片,并检查它通过 CDN 的加载速度。为此,可以使用Ping Admin服务。加载速度应该很快。
让我们制作一个小脚本,以防我们需要清除 CDN 点上的缓存。
清除文件
#!/bin/bash
if [ -z "$1" ]
then
echo "Purging all cache"
rm -rf /var/cache/cdn/*
else
echo "Purging $1"
FILE=`echo -n "$1" | md5sum | awk '{print $1}'`
FULLPATH=/var/cache/cdn/${FILE:31:1}/${FILE:29:2}/${FILE}
rm -f "${FULLPATH}"
fi
要清除服务器上的所有缓存,只需运行该脚本即可。如果需要清除某个文件,只需指定其路径即可:
root@cdn:~# ./purge.sh /test.jpg
要清除所有缓存,必须在所有 CDN 服务器上运行脚本。
代替结论
最后,我想给出一些有用的提示,以便您可以避免陷入我已经清除的陷阱:
- 考虑未来 CDN 的可行性和维护成本。大多数情况下,购买价格低廉的 CDN更高效、更便捷,而且通常更稳定,质量也更好。
- 为了提高 CDN 的容错能力,建议您设置 DNS 故障转移功能,以便在服务器发生故障时快速切换 A 记录。您可以在 DNS 记录控制面板中进行此操作。
- 覆盖范围广的网站需要大量的 PoP,但不要过度。如果您在 6-7 个地点(欧洲、北美(东部)、北美(西部)、新加坡、澳大利亚、香港或日本)设有服务器,用户很可能不会注意到您的 CDN 和付费 CDN 之间的区别。
- 有时,托管服务提供商不允许将租用的服务器用作 CDN。因此,如果您打算创建 CDN 服务,请务必仔细阅读托管服务提供商的条款和条件。
- 研究海底电缆图以了解各大洲如何连接,并在构建 CDN 时使用这些知识。
- 尝试从不同位置ping你的服务器。这样,你就能找到距离 PoP 最近的地区,这将有助于你配置 GeoDNS。