如何在 Laravel Vapor 中添加无限自定义域名
发布于 作者 Jack Ellis
我们刚刚发布了自定义域名 V2,我将与大家分享所有技术细节。高潮和低谷,我所学到的东西以及如何自己动手。最终结果是高度可用且全球快速的基础设施。我们的客户喜欢它,我们也喜欢它。但让我们回到这个故事的开始。
我经营着一个简单、合乎道德的 Google Analytics 的替代方案。人们最讨厌分析的地方之一,除了它们过于复杂难以理解之外,就是它们的脚本会被广告拦截器阻止。可以理解的是,Google Analytics 被地球上每一个广告拦截器都阻止了。谷歌有太多隐私丑闻,很多人害怕向他们发送访客数据。但 Fathom 专注于隐私,不存储任何客户数据,因此不应该以相同的方式对待。所以我们不得不引入一个名为“自定义域名”的功能,我们的用户可以将自己的子域名(例如 wealdstoneraider.ronnie-pickering.com)指向我们的 Laravel Vapor 应用程序。如果您不熟悉 Vapor,它是 Laravel 团队构建的一款产品,可以让您将应用程序部署到 AWS 上的无服务器基础设施(我实际上有一个 关于它的课程,这就是我如此喜爱 Vapor 的原因)。
AWS 的服务并没有让这个自定义域名任务变得容易。如果您使用的是应用程序负载均衡器 (ALB),您最多只能使用 25 个不同的域名。如果您使用的是 AWS API Gateway,您可以获得几百个,但 AWS 不会让您超过这个限制。所以我们处于一个需要跳出框框思考的位置。
尝试 1:Vapor 与 Forge 的支持
我尝试实现自定义域名的第一件事是为每个自定义域名在 Vapor 中创建一个环境。Vapor 中的环境通常类似于“生产”或“登台”,但我决定另辟蹊径。相反,我们将“miguel-piedrafita-com”和“pjrvs-com”作为我们的环境。回想起来这听起来很愚蠢,但这在当时奏效了,我认为它非常前沿。
我通过查看 GitHub 上的 Vapor 源代码并使用 API 来构建了整个过程。我不会分享我的代码,因为现在已经毫无意义了,但它包含以下步骤
- 用户在前端输入他们的自定义域名
- 我们运行一个后台任务,通过 Vapor 将他们的域名添加到 Route53
- 我们通过 Vapor 启动 SSL 证书请求
- 我们通过电子邮件发送 SSL 证书所需的 CNAME 更改
- 用户进行更改
- 我们每隔 30 分钟检查一次以查看 AWS 是否已颁发 SSL 证书
- 当证书有效时,我们向 Forge(另一个用于部署的 Laravel 服务)发送 API 请求,该服务将打包一个单独的 Laravel 应用程序并将其部署到 Vapor,作为新环境。
- Forge 然后 ping 我们的 Vapor 设置并说“我们在这里都完成了”。
- Vapor 然后检查子域名。太棒了,它可以工作,所以我们通过电子邮件通知用户,告诉他们可以开始使用。
这从未投入生产,我羞愧地删除了所有代码。
尝试 2 – Forge 代理服务器
在我意识到第一次尝试根本无法扩展之后,我又回到了起点。我接受了必须使用代理层的事实,并且为了让 Chris Fidao 开心,我在我们的基础设施设置中添加了 EC2 服务器。
我通过 Forge 配置了一个负载均衡器,并在其中放置了 2 台服务器。计划是,我将通过 Forge 的 API 在每台服务器上创建 SSL 证书和添加站点,这样如果一台服务器脱机,另一台服务器就会接管。整个过程将在 NGINX 上运行,我们唯一的“限制”将是 Forge 的 API 速率限制。小菜一碟。
所以我把这一切都构建出来了。这太容易了。 Forge 的 API 非常棒。所以我做了任何狂热的 Twitter 用户都会做的事情,我分享了我的进度。
我对自己非常自豪。然后 Alex Bouma 走过,扔了一颗手榴弹,然后走了。
然后 Mattias Geniar 加入进来(毕竟这是他的文章),然后是 Owen Conti(他显然已经告诉我这是最好的方法,早在 2019 年 11 月),然后是 Matt Holt(Caddy 的创建者)加入进来,然后,砰,我的成品就消失了。谢谢你们。
现在我知道你在想什么……这里有很多“未发货”的东西。我通常不会陷入完美主义的泥潭,但我想在我的 无服务器 Laravel 课程 中包含最佳的解决方案作为视频。因此,如果有一个更好的解决方案可用,我有义务向我的课程成员和 Fathom 客户确保我知道它。
而这就是 Caddy 进入我生命的地方。
尝试 3:高可用 Caddy 代理层
我希望我可以回到 2019 年,告诉自己这个解决方案。也许是时机问题?毕竟,当我看到 Alex 的推文时,Caddy 2 刚刚发布。
Caddy 2 是一款具有自动 HTTPS 功能的开源 Web 服务器。因此,您不必担心管理虚拟主机或 SSL 证书,它会自动为您完成。它比我们习惯的要简单得多。
所以您已经听过所有失败的尝试,让我们来看看我们是如何解决问题的。
步骤 1. SSL 证书存储
我们需要做的第一件事是创建一个 DynamoDB 表。这将是我们的集中式存储。为什么我使用 DynamoDB?因为我不想将证书存储在服务器文件系统上。我相信我们可以管理某种 NAS,但我不知道这将如何在跨区域工作。最终,我熟悉 DynamoDB,Matt Holt 非常乐意将 Caddy DynamoDB 模块升级到 V2(感谢老兄!)。
- 在 AWS 中打开 DynamoDB
- 创建一个名为 caddy_ssl_certificates 的新表,其中包含名为 PrimaryKey 的主键
- 取消勾选默认设置,使用按需(没有免费层,但可以自动扩展)
- 创建表后,单击“备份”选项卡并启用“时间点恢复”。
- 就这样完成了
步骤 2. 创建 IAM 用户
现在我们需要在 AWS 中创建一个具有有限访问权限的用户。理论上,我们可以为 Caddy 提供一个具有 root 权限的访问密钥,但它并不需要它。
- 创建一个名为 **caddy_ssl_user** 的用户
- 勾选启用程序访问的框
- 点击直接附加现有策略
- 点击创建策略
- 选择 DynamoDB 作为您的服务,并添加基本权限,以及限制对您表的访问权限(如果您不确定这一部分,可以在我的课程中找到一个 视频演练)
- 好了。确保您保存访问密钥 ID 和秘密访问密钥
步骤 3. 在您的应用程序中创建一个路由来验证域名
我们不想让任何人都可以将他们的域名指向我们的基础设施并为他们生成 SSL 证书。不,我们只希望为我们的客户生成 SSL 证书。
为了这篇说明,我将保持一切超级简单并对其进行硬编码。如果您要将其投入生产,显然您将进行数据库是否存在检查;)
routes/web.php
Route::get('caddy-check-8q5efb6e59', 'CaddyController@check');
app\Http\ControllersCaddyController.php
<?php namespace App\Http\Controllers; use Illuminate\Http\Request;use Illuminate\Support\Facades\App; class CaddyController extends Controller{ protected static $authorizedDomains = [ 'portal.my-customers-website.com' => true ]; public function check(Request $request) { if (isset(self::$authorizedDomains[$request->query('domain')])) { return response('Domain Authorized'); } // Abort if there's no 200 response returned above abort(503); }}
您很快就会发现为什么这个端点如此重要。
步骤 4. 准备我们的 Forge 食谱
我说了 Forge 吗?Laravel Forge?你说的没错。我们使用 Forge 来托管我们的代理服务器(感谢 Taylor)。我这里有一个食谱模板,但您需要填写空白(AWS 密钥、您的网站等)。同样,如果您对这方面没有信心,我的 视频演练 会详细介绍所有这些内容
# Stop and disable NGINXsudo systemctl stop nginxsudo systemctl disable nginx # Grab the bin file I’ve hosted on the Fathom Analytics CDNsudo wget “https://cdn.usefathom.com/caddy” # Move the binary to $PATHsudo mv caddy /usr/bin/ # Make it executablesudo chmod +x /usr/bin/caddy # Create a group named caddysudo groupadd —system caddy # Create a user named caddy, with a writeable home foldersudo useradd —system \ —gid caddy \ —create-home \ —home-dir /var/lib/caddy \ —shell /usr/sbin/nologin \ —comment “Caddy web server” \ caddy # Create the environment filesudo echo ‘AWS_ACCESS_KEY=AWS_SECRET_ACCESS_KEY=AWS_REGION=‘ | sudo tee /etc/environment # Create the caddy directory & Caddyfilesudo mkdir /etc/caddysudo touch /etc/caddy/Caddyfile # Write the config filesudo echo ‘{ on_demand_tls { ask https://your-website.com/caddy-endpoint } storage dynamodb caddy_ssl_certificates} :80 { respond /health “Im healthy!” 200} :443 { on_demand } reverse_proxy https://your-website.com { header_up Host your-website.com header_up User-Custom-Domain {host} header_up X-Forwarded-Port {server_port} health_timeout 5s }}’ | sudo tee /etc/caddy/Caddyfile sudo touch /etc/systemd/system/caddy.service # Write the caddy service filesudo echo ‘# caddy.service## WARNING: This service does not use the —resume flag, so if you# use the API to make changes, they will be overwritten by the# Caddyfile next time the service is restarted. If you intend to# use Caddys API to configure it, add the —resume flag to the# `caddy run` command or use the caddy-api.service file instead. [Unit]Description=CaddyDocumentation=https://caddyserver.com.cn/docs/After=network.target [Service]User=caddyGroup=caddyExecStart=/usr/bin/caddy run —environ —config /etc/caddy/CaddyfileExecReload=/usr/bin/caddy reload —config /etc/caddy/CaddyfileTimeoutStopSec=5sLimitNOFILE=1048576LimitNPROC=512PrivateTmp=trueProtectSystem=fullAmbientCapabilities=CAP_NET_BIND_SERVICEEnvironmentFile=/etc/environment [Install]WantedBy=multi-user.target’ | sudo tee /etc/systemd/system/caddy.service # Start the servicesudo systemctl daemon-reloadsudo systemctl enable caddysudo systemctl start caddy # Remember, when making changes to the config file, you need to run#sudo systemctl reload caddy
然后我们将这个食谱添加到 Forge。哦,还要选择“Root”作为运行它的用户。
快速提醒一下,您可以看到 Caddy 从 Fathom CDN 下载。这个 Caddy bin 文件由 Caddy 的创建者编译,包括 DynamoDB 附加组件。如果您愿意,也可以 自己编译。
步骤 5. 创建我们的代理服务器
是服务器时间了!所以让我们在这里明确一点:这些服务器将运行零个 PHP 代码。它们将是最小的代理服务器。我再说一遍,我们不会在这些服务器上运行任何 PHP 代码,它们是代理服务器。
- 在创建服务器(在 Forge 中)下点击 AWS
- 服务器配置
- 任意名称,
- t3.small 或更高
- VPC 不重要
- 勾选“作为负载均衡器配置”框(保持最小化)
- 为创建的每个服务器选择不同的区域
- 当您的服务器完成设置后,复制 IP,然后加载以下 URL:http://[your-server-ip]/health。如果 Caddy 按预期运行,这将返回一条消息,这将是您的健康检查端点。
注意:Caddy 通过端口 80(HTTP)提供的唯一路径是这个健康检查,其余部分通过 443(HTTPS)提供。
步骤 6. 我们的“全局负载均衡器”
当我苦苦挣扎于负载均衡器,探索网络负载均衡器以及其他所有东西时,我偶然发现了 AWS 全球加速器。我从未听说过它,但我是在需要它的时候才发现它的。简而言之,这项服务为我们提供了单个端点(静态 IP 和 DNS 记录),但会将流量路由到多个位于不同区域的服务器。不同的区域部分让我大开眼界。这意味着如果美国的用户访问了我们的端点,他们将被路由到 us-east-1,而英国的用户(上帝保佑女王)将被路由到 eu-west-1。令人难以置信。
要做到这一点
- 打开 AWS -> AWS 全球加速器
- 创建一个加速器
- 选择一个名称并点击下一步
- 对于侦听器
- 端口:443
- 协议:TCP
- 客户端亲和性:无
- 然后点击下一步
- 添加端点组
- 选择您在 Forge 中创建的第 1 台服务器的区域
- 点击配置健康检查
- 健康检查端口:80
- 健康检查协议:HTTP
- 健康检查路径:/health
- 健康检查间隔:10
- 阈值计数:2
- 对您要添加的第 2 台服务器重复此操作
- 点击下一步
- 添加端点
- 对于每个组
- 端点类型:EC2 实例
- 端点:点击下拉菜单并找到您的服务器
- 点击创建加速器
现在去给自己泡杯茶或咖啡。我个人会选择茶,因为看到这个全球分布式的野兽端点运行时,你已经充满了肾上腺素,不想过载刺激。哦,还有,当您首次创建加速器时,它会告诉您您的服务器处于离线状态。不用担心,这只是它正在经历的一个阶段,一旦它完全配置好,就会恢复正常(通常不到 5 分钟)。
在我们等待的同时,另外说一句,您可以让您的基础设施变得更复杂。当我将我的视频发布到 无服务器 Laravel Slack 团队中的每个人时,一位朋友说他会为每个区域内的负载均衡器配置自动扩展 EC2 实例。我绝对不反对这种方法,您做您自己的。
无论如何,一旦加速器部署,您将拥有静态 IP 和 DNS 名称来使用。对于根级条目,您必须使用静态 IP。而对于 CNAME 条目,您可以使用 DNS 条目。在 Fathom,我们实际上创建了“starman.fathomdns.com”,它指向 AWS 在这里给我们的 DNS 名称,因此我们可以为我们的用户提供一个漂亮的 CNAME,让他们添加。
步骤 7. 您的第一个自动 SSL 证书
因此,选择一个子域(例如 hey.yourwebsite.com)并添加 DNS 条目。您还应该在 CaddyController 中添加 hey.yourwebsite.com 作为授权网站(参见步骤 3)。完成此操作后,加载您的网站,您应该会看到自动 SSL 代理到 Vapor。我记得在 Chrome 中,第一次运行可能会遇到问题,因为它似乎不喜欢 SSL 是动态生成的,因此如果需要,请使用多个浏览器(这仅适用于第一次加载)。
步骤 8. 与您的客户一起使用
您应该始终在您这边运行检查。当客户添加子域/域名时,您应该在模型上具有 3 种状态
- STATUS_AWAITING_DNS_CHANGES
- STATUS_AWAITING_STATUS_CHECK
- STATUS_ACTIVE
然后您需要有两个命令来处理所有这些
- CheckDNSChanges – DNS 查找以确保客户已进行更改
- CheckIfEnvironmentDeployed – 通过 https 访问网站以确保 Caddy 能够为网站生成 SSL 证书。在这里,您可以向用户发送电子邮件,让他们知道他们的域名已准备就绪
请记住,我在整个事情上都有一个大型视频演练,以及我 无服务器 Laravel 课程 中的另外 48 个视频,其中我涵盖了从初学者到高级的 Laravel Vapor 内容。
就是这样了
我希望您喜欢我在这里整理的内容。我要感谢以下人员为该过程做出的贡献:Matt Holt、Alex Bouma、Mattias Geniar、Owen Conti、Chris Fidao、Francis Lavoie 以及其他许多人。