diff options
| -rw-r--r-- | content/essays/如何优雅地部署一个静态网站.md | 95 |
1 files changed, 88 insertions, 7 deletions
diff --git a/content/essays/如何优雅地部署一个静态网站.md b/content/essays/如何优雅地部署一个静态网站.md index 30b09ba7..44195a2c 100644 --- a/content/essays/如何优雅地部署一个静态网站.md +++ b/content/essays/如何优雅地部署一个静态网站.md @@ -3,6 +3,7 @@ title = '如何优雅地部署一个静态网站?' slug = 'deploying-static-sites' date = 2026-01-27 22:40:11 draft = true +toc = true +++ PaaS 软件用了太久,导致自己对部署网站这件事产生了很多不切实际的想象,总感觉不使用 Vercel、Netlify 和 Cloudflare Pages 就不够现代。实际上,静态网站就是一系列 HTML 文件和必要的静态资源而已,即便是用 Hugo 和 Hexo 等静态网站生成器,得到的也是相同的文件。这些文件如果被用户下载到本地,用浏览器直接打开,也能够正常浏览,毕竟不涉及任何服务端的计算,那为什么还要把事情弄得这么复杂呢?<!--more--> @@ -21,12 +22,12 @@ PaaS 能让开发者做到,直接提交代码库给平台,应用就能跑起 1. 将你的网站源代码推送到远程 Git 仓库。 2. 在 PaaS 平台创建一个新的应用,设置 Git 仓库并选择软件架构(如果是 Node.js 等常见技术栈,成熟的 PaaS 平台往往会自动识别) -3. PaaS 克隆 Git 仓库,运行构建脚本,然后将构建产物部署到他们提供的网络上。 +3. PaaS 克隆仓库,运行构建脚本,然后将构建产物部署到他们提供的网络上。 4. 此后每次有新的推送,PaaS 都会重新克隆仓库、构建和部署。 简单来说,你只需要把代码准备好就行,几乎不需要做任何运维操作。 -问题在于,部署一个静态网站并不复杂,运行脚本构建好网站之后,将构建产物上传到一个可以分发文件(静态资源)的服务器上就好了。这个服务器甚至不需要有计算能力,可以是对象储存桶。印象中 Vercel 会把应用部署到他们的边缘网络(edge network)上,所谓边缘指的是靠近端系统(end system)也就是用户的网络,网络的边缘部分离用户最近,所以响应速度往往很快。对于静态网站而言,这跟 CDN(内容分发网络)差不多。 +问题在于,部署一个静态网站并不复杂,运行脚本构建好网站之后,将构建产物上传到一个可以分发文件(静态资源)的服务器上就好了。这个服务器甚至不需要有计算能力,可以是对象储存桶。印象中 Vercel 会把应用部署到他们的边缘网络(edge network)上,所谓「边缘」,指的是靠近端系统(end system)也就是用户的网络,网络的边缘部分离用户最近,所以响应速度往往很快。对于静态网站而言,这跟 CDN(内容分发网络)差不多。 所以,如果不用 PaaS,能把这个过程自动化吗? @@ -45,7 +46,7 @@ Actions 是自动化的工作流,有多种触发条件,最常见的是 `push 比如,构建一个 Hugo 网站,部署远程服务器上,工作流可能需要这样写: 1. 安装 Node.js(如果有 Node.js 依赖) -2. `npm install` +2. `npm install`[^1] 3. `npm run build`(运行 Node.js 工具,比如构建 UnoCSS) 4. 安装 Go 语言依赖 5. 安装 Hugo @@ -64,7 +65,7 @@ on: push: branches: - master -: + jobs: deploy: runs-on: docker @@ -121,8 +122,88 @@ eltr.ac { 构建和部署静态网站之所以使用自动化工作流,是想要实现持续集成和持续部署,也就是在**确认**代码更改后就立刻更新生产环境的代码。一般来说,将代码合并到主分支就代表代码可以运行在生产环境了,自动化工作流可以以此为触发点,在有代码推送到主分支后就执行构建和部署步骤。在服务器上直接构建代码而不是用 Git 仓库的自动化工作流部署,最大的不足在于无法通过 `git push` 自动触发构建,但这个问题并非没有解决方案。 -我不清楚 GitHub 的情况,Forgejo 可以在仓库设置里配置 Webhook,即在某些事件发生时向指定的 Webhook 端口发送 HTTP 请求。所谓 Webhook,是相对于常规的 Web 交互方式而言的, +我不清楚 GitHub 的情况,Forgejo 可以在仓库设置里配置 Webhook,即在某些事件发生时向指定的 Webhook 端口发送 HTTP 请求。只要在服务器上开放一个 Webhook 端口,在接收到 HTTP 请求时拉取新代码并重新构建网站,然后配置 Forgejo 仓库在 `master` 分支收到新的代码推送时向这个 Webhook 端口发送 HTTP 请求,就能够实现 `git push` 后自动更新网站。宿主服务器不是容器化的最小运行环境,只要配置好了依赖和必要的软件,就不需要像自动化工作流那样,每次构建都需要重新安装依赖,更新可以在几秒内完成,体验上不会比动态网站慢。 + +具体而言,要先在服务器上配置好必要的工具。我使用 Hugo,也就需要在服务器上安装好 Go、Git 以及 Hugo。我的 VPS 安装的是 Ubuntu 系统,所以这里用 Aptitude 包管理器安装。 + +```shell +sudo apt update +sudo apt install golang-go +sudo apt install git +``` + +使用 Aptitude 安装 Hugo 的时候,我发现版本不是最新的,因此遇到了一些问题。所以我打算直接从源代码编译,先克隆仓库,然后编译,将编译后得到的二进制文件移动到 `bin`,测试能否在终端运行。 + +```shell +git clone https://github.com/gohugoio/hugo.git +cd hugo +go build . +mv hugo /usr/local/bin/ +hugo version +``` + +其实也可以直接用 Go 安装,但我忘记当时遇到了什么问题,可能是没有把 Go 的 `bin` 添加到 `$PATH`,总之没能成功安装。 + +```shell +go install github.com/gohugoio/hugo@latest +hugo version +``` + +接下来准备构建网站的脚本。先把网站的源代码克隆到本地,安装好依赖之后,创建一个 `.sh` 文件 + +```shell +# 这是托管在我自己的 Forgejo 实例上的网站 +git clone https://src.eltr.ac/eltrac/jar.git +cd jar +pnpm install # 安装 Node.js 依赖 +vim build.sh +``` + +这个文件里,按照**实际情况**编写构建网站的步骤。由于需要被执行,所以要在开头写上 `#!/bin/sh` + +```shell +#!/bin/sh + +git pull # 先拉取最新的更改 +pnpm unocss "**/*.{html,md}" -o assets/uno.css +hugo --minify +``` + +运行这个 Shell 脚本,测试能够正常构建网站。如果输出正常,就可以继续配置 Webhook 了。这里使用 adnanh 的 [webhook 实现](https://github.com/adnanh/webhook),这是一个命令行工具,能够读取 JSON 或 YAML 配置。以下是示例配置文件,用 JSON 编写。 + +```shell +# 先安装 Webhook +sudo apt-get install webhook +``` + +```JSON +// hooks.json +[ + { + "id": "build-hugo", + "execute-command": "/home/eltrac/jar/build.sh", + "command-working-directory": "/home/eltrac/jar" + } +] +``` + +执行 `webhook -hooks hooks.json`,API 就会运行在 `:9000` 端口上,我们刚刚配置的 API 路径是 `localhost:9000/hooks/build-hugo`。要允许公网访问,可以用 Caddy 配置反向代理。这里建议用 `systemctl` 运行和管理 Webhook 服务,具体步骤此处略去。 + +```Caddyfile +hooks.example.com { + reverse_proxy localhost:9000 +} +``` + +然后,在 Forgejo 仓库配置 Webhook,当有代码被推送到主分支上时,就向我们刚刚配置好的 API 端口发送 HTTP 请求。运行在服务器上的 Webhook 收到请求后,就会运行我们刚才编写好的 `build.sh`,网站就会被重新构建。 + + + + +完成后,向 Forgejo 远程仓库推送更新,测试构建脚本有没有被触发。不出问题的话,你就能获得一个几乎能在 `git push` 一秒后更新的静态网站。 + +## 对比三种方案 - +对于缺少经验的人来说,使用 PaaS 是最简单的,几乎去掉了维护一个网站所需要的所有运维技能,只需要关注写代码。不过,似乎大部分 PaaS 都只支持从 GitHub 和 GitLab 和拉取代码,或者自己手动上传代码,不支持和自托管的 Gitea 或者 Forgejo 实例集成。 -## 本地构建并同步到文件服务器或储存桶 +[^1]: 其实我更喜欢使用 `pnpm`,但如果要在自动化工作流里使用,还要额外安装,所以这里就直接用 `npm` 了。 |
