Ubuntu 18.04 安装 Jekyll 的系统级兼容性问题与解决方案
1. 为什么 Ubuntu 18.04 上跑 Jekyll 不是“装个 gem 就完事”——一个被低估的系统级兼容性问题
Jekyll 是静态网站生成器里最沉稳的老派选手,它不靠实时热更新炫技,也不靠插件生态堆砌功能,而是用 Ruby 的简洁语法和 Liquid 模板的清晰逻辑,把 Markdown 文档稳稳地编译成纯 HTML 文件。这本该是极简主义者的天堂。但当你在 Ubuntu 18.04 上执行gem install jekyll,看到终端里滚动出一长串ERROR: Failed to build gem native extension,或者更糟——卡死在make步骤、报错unable to make protected void java.util.resourcebundle.setparent(注意:这个 Java 错误其实是 Ruby 编译时链接了错误的系统库导致的混淆提示),你就立刻明白:这不是 Ruby 版本太低的问题,而是整个构建链路在 Ubuntu 18.04 这个特定发行版上出现了系统级的“齿轮咬合错位”。
Ubuntu 18.04 发布于 2018 年 4 月,其默认的 Ruby 版本是 2.5.1,而当前主流 Jekyll(v4.3+)虽标称支持 Ruby 2.5+,但实际依赖的webrick、http_parser.rb等底层 gem 在编译 C 扩展时,对系统工具链有隐性要求。最关键的三个组件是:make(构建调度器)、build-essential(编译器套件集合)、以及ruby-dev(Ruby 头文件与静态库)。很多人只装了build-essential,却漏掉ruby-dev,结果make能运行,但gcc在编译nokogiri或ffi时找不到ruby.h,直接报错退出。更隐蔽的是,Ubuntu 18.04 的make默认版本是 4.1,而某些较新 gem 的Makefile中使用了 4.2+ 的语法(比如$(file >...)函数),导致make解析失败,却把错误日志淹没在大量 C 编译输出中,最终表现为“无法安装 homebrew portable ruby”——这其实是社区用户在 macOS 上遇到的同类问题,被错误地复制粘贴到 Ubuntu 场景下,形成了一个典型的“跨平台误诊”陷阱。
我第一次在一台老旧的 Dell OptiPlex 7050 上部署 Jekyll 博客时,就栽在这个坑里。当时以为是网络问题,反复gem sources --add https://ruby.taobao.org/切换镜像源;又以为是权限问题,加了sudo;最后甚至怀疑硬盘坏了,因为make进程会卡住数分钟,iostat显示磁盘 I/O 飙高。直到我用strace -e trace=execve,openat,write make -j1跟踪命令调用,才发现在gcc尝试打开/usr/include/ruby-2.5.0/ruby.h时返回ENOENT。那一刻我才意识到:不是 Ruby 太老,是开发头文件根本没装。这个教训让我后来所有基于 Ubuntu 的 Ruby 开发环境初始化脚本里,第一行永远是apt install -y build-essential ruby-dev,而不是gem install。
所以,这篇文章不讲“如何用 Jekyll 写博客”,而是聚焦一个具体、真实、高频的工程现场:在 Ubuntu 18.04 这个已进入 ESM(扩展安全维护)阶段的 LTS 系统上,如何让 Jekyll 的开发环境从零启动、稳定运行、且能长期维护。它解决的不是“能不能跑”,而是“为什么跑得磕磕绊绊”、“哪些错误日志是真线索、哪些是干扰项”、“离线环境下怎么救急”这些一线开发者真正卡住的点。如果你正对着终端里一串红色报错发呆,或者刚重装了系统想快速复现旧项目,那接下来的内容就是为你写的。
2. 构建链路四层解剖:从 apt 包管理器到 Jekyll 的 Ruby 字节码
要让 Jekyll 在 Ubuntu 18.04 上跑起来,你面对的不是一个单一的“安装命令”,而是一条横跨四个技术层级的构建流水线。每一层都可能成为故障点,而错误日志往往只暴露最后一层的表象。我们必须像拆解一台机械手表一样,逐层拨开外壳,看清每个齿轮的咬合关系。
2.1 第一层:APT 包管理器 —— 系统级依赖的基石
Ubuntu 使用 APT(Advanced Package Tool)作为底层包管理器。它不像 macOS 的 Homebrew 那样允许用户自由选择安装路径,而是严格遵循 FHS(Filesystem Hierarchy Standard)规范,将二进制文件、头文件、库文件分门别类存放在/usr/bin、/usr/include、/usr/lib等目录下。这意味着,build-essential这个元包(metapackage)安装的不是“编译器本身”,而是它所依赖的一组 deb 包清单:gcc、g++、make、dpkg-dev等。而ruby-dev同样是一个元包,它确保/usr/include/ruby-2.5.0/目录存在,并包含ruby.h、st.h等关键头文件。
提示:
build-essential和ruby-dev必须成对安装。单独安装build-essential只能让你运行make命令,但make在编译 Ruby gem 的 C 扩展时,会调用gcc,而gcc又需要ruby.h来知道 Ruby 的内存结构和 API 接口。缺少ruby-dev,gcc就像一个没有地图的司机,知道要去哪(编译),但找不到路(头文件路径)。
验证方法很简单:
# 检查 build-essential 是否完整 dpkg -l | grep build-essential # 应输出:ii build-essential 12.4ubuntu1 amd64 Informational list of build-essential packages # 检查 ruby-dev 是否安装 dpkg -l | grep ruby-dev # 应输出:ii ruby-dev 1:2.5.1 amd64 Header files for compiling extension modules for the Ruby language # 检查关键头文件是否存在 ls /usr/include/ruby-2.5.0/ruby.h # 应输出:/usr/include/ruby-2.5.0/ruby.h如果ruby-dev未安装,执行sudo apt update && sudo apt install -y build-essential ruby-dev。注意,apt update不可省略,因为 Ubuntu 18.04 的源列表可能已过期,apt install会因索引陈旧而找不到包。
2.2 第二层:Ruby 运行时与 Bundler —— 语言环境的容器
Ubuntu 18.04 自带的 Ruby 2.5.1 是一个“系统 Ruby”,它被apt严格管理,任何通过gem install安装的全局 gem 都会写入/var/lib/gems/2.5.0/目录。这带来两个隐患:一是权限问题,普通用户执行gem install jekyll可能因/var/lib/gems/目录权限不足而失败;二是污染风险,不同项目可能依赖不同版本的 Jekyll,全局安装会导致冲突。
因此,强烈建议跳过gem install jekyll,直接使用 Bundler。Bundler 是 Ruby 的依赖管理器,它的核心思想是“每个项目一个独立的 Gemfile”,所有依赖都安装在项目目录下的vendor/bundle子目录中,完全隔离,互不干扰。这不仅是最佳实践,更是 Ubuntu 18.04 上规避权限问题的最稳妥方案。
安装 Bundler 的命令是:
sudo gem install bundler虽然用了sudo,但 Bundler 本身是个轻量级工具,安装后不会修改系统 Ruby 的行为。之后,你在任何项目目录下执行bundle init,就会生成一个空的Gemfile;再执行bundle add jekyll,Bundler 就会自动解析依赖树,下载并安装 Jekyll 及其所有子依赖(如kramdown、liquid、webrick)到本地vendor/bundle中。
注意:
bundle add jekyll会自动写入Gemfile并执行bundle install。如果你的网络受限,可以先bundle init,手动编辑Gemfile,加入gem "jekyll", "~> 4.3",再运行bundle install。这样你可以精确控制版本,避免因自动升级引入不兼容变更。
2.3 第三层:Make 与 GCC —— C 扩展编译的引擎
Jekyll 本身是纯 Ruby 代码,但它的许多核心依赖(尤其是nokogiri,用于 HTML 解析;ffi,用于调用系统库)都包含用 C 编写的原生扩展(native extensions)。这些.c文件不能被 Ruby 直接执行,必须先由gcc编译成.so(共享对象)文件,再由 Ruby 动态加载。
这个过程由make驱动。当你执行bundle install时,Bundler 会为每个需要编译的 gem 调用gem build,后者内部会执行make。make的工作就是读取 gem 自带的Makefile,按顺序调用gcc、ar、ranlib等工具,完成编译、链接、打包。
Ubuntu 18.04 的make版本是 4.1,它不支持Makefile中的$(file >...)语法(该语法在 GNU Make 4.2+ 中引入,用于将文本写入文件)。而某些较新的nokogiri版本(如 1.14+)的Makefile恰好使用了此语法。结果就是make报错Makefile:xxx: *** missing separator. Stop.,但错误信息极其模糊,让人误以为是缩进问题(tab vs space),实则是版本不兼容。
解决方案有两个:
- 降级 nokogiri:在
Gemfile中指定旧版本,例如gem "nokogiri", "~> 1.13.10"。1.13.x 系列完全兼容 Make 4.1。 - 升级 make(不推荐):从源码编译安装 GNU Make 4.3。但这会破坏 APT 的包管理一致性,可能导致其他系统工具(如内核模块编译)出错,属于“为了解决一个问题,制造十个新问题”的典型反模式。
我选择方案一。在Gemfile中锁定nokogiri版本,是成本最低、风险最小的解法。它不改变系统环境,只影响当前项目,且经过大量生产环境验证。
2.4 第四层:Jekyll 服务与 Webrick —— 最终交付的 HTTP 服务器
当所有 gem 安装完毕,执行bundle exec jekyll serve,Jekyll 就会启动一个内置的 Web 服务器,默认使用webrick(Ruby 标准库的一部分)。webrick是一个纯 Ruby 实现的 HTTP 服务器,轻量、无依赖,非常适合开发预览。
但在 Ubuntu 18.04 上,webrick有一个鲜为人知的兼容性细节:它默认绑定到127.0.0.1:4000,但某些企业网络策略或老旧的防火墙规则,会阻止127.0.0.1的 loopback 流量(尽管这极不常见)。更常见的问题是,webrick在处理大量并发请求(比如你同时打开 20 个浏览器标签页刷新页面)时,会因 Ruby 的 GIL(全局解释器锁)而出现轻微卡顿,表现为页面加载延迟 1-2 秒。
这不是 bug,而是webrick的设计使然。它的定位就是“够用就好”,而非生产级服务器。因此,在开发阶段,我们接受它的轻量与简单;但若你追求极致的响应速度,可以无缝切换到puma。puma是一个高性能、多线程的 Ruby Web 服务器,它能充分利用多核 CPU,将页面刷新时间从秒级降到毫秒级。
切换方法只需两步:
- 在
Gemfile中添加gem "puma"; - 启动时加上
--server参数:bundle exec jekyll serve --server puma。
puma会自动接管 HTTP 请求,而无需修改任何 Jekyll 配置。这体现了 Bundler + Gemfile 方案的另一个巨大优势:基础设施即代码(Infrastructure as Code)。服务器选型不再是系统配置,而是项目代码的一部分,可版本化、可复现、可协作。
3. 从零开始的完整实操流程:一条命令都不容错过的精准步骤
纸上谈兵不如亲手一试。下面是我每天在新服务器上初始化 Jekyll 开发环境的标准操作流。它经过上百次重复验证,每一步都有明确目的,每一个参数都有其不可替代的理由。请务必逐字输入,不要跳过任何&&或-y。
3.1 环境初始化:清理、更新、安装系统级依赖
打开终端,执行以下命令。这是一个原子化的单行命令,确保所有前置条件一次性满足:
sudo apt update && sudo apt full-upgrade -y && sudo apt install -y build-essential ruby-dev zlib1g-dev libssl-dev libreadline-dev libyaml-dev libsqlite3-dev sqlite3 && sudo gem install --no-document bundler让我们拆解这个长命令的每一部分:
sudo apt update:刷新本地软件包索引。Ubuntu 18.04 的源列表可能已过期,不执行此步,后续apt install可能报Unable to locate package。sudo apt full-upgrade -y:执行一次完整的系统升级。full-upgrade比upgrade更激进,它会智能处理包依赖冲突,移除旧包、安装新包,确保系统内核、glibc 等基础组件处于最新可用状态。-y参数自动确认所有提示,避免交互中断。sudo apt install -y build-essential ruby-dev ...:安装构建链路所需的所有系统库。除了前面强调的build-essential和ruby-dev,还额外安装了:zlib1g-dev:zlib压缩库的开发文件,jekyll-sitemap等插件需要它来生成压缩的sitemap.xml.gz。libssl-dev:OpenSSL 开发文件,jekyll-feed插件在生成 RSS 订阅时需要 SSL 加密支持。libreadline-dev:提供命令行历史记录和编辑功能,jekyll console命令依赖它。libyaml-dev:YAML 解析库,Jekyll 的_config.yml文件解析离不开它。libsqlite3-dev和sqlite3:SQLite 数据库,虽然 Jekyll 本身不用数据库,但jekyll-search等第三方插件会用到。
注意:
sudo gem install --no-document bundler中的--no-document是关键。它跳过 RDoc 和 RI 文档的生成,能将 Bundler 安装时间从 30 秒缩短到 3 秒。对于开发环境,文档完全可以在需要时在线查阅,没必要在本地存储。
执行完毕后,验证 Ruby 和 Bundler 是否就位:
ruby -v # 应输出:ruby 2.5.1p57 (2018-03-29 revision 63023) [x86_64-linux-gnu] bundler -v # 应输出:Bundler version 2.3.25 (或类似)3.2 项目创建:用 Bundler 初始化,而非 jekyll new
很多教程教大家用jekyll new myblog创建项目。这在 Ubuntu 18.04 上是危险的,因为jekyll new会尝试全局安装jekyll和jekyll-watch,并直接写入/var/lib/gems/,极易触发权限错误。
正确做法是:先创建空目录,再用 Bundler 初始化。
mkdir ~/my-jekyll-site && cd ~/my-jekyll-site bundle initbundle init会在当前目录生成一个最简Gemfile,内容只有一行# frozen_string_literal: true。接着,我们手动编辑Gemfile,加入 Jekyll 及其关键依赖:
echo 'gem "jekyll", "~> 4.3"' >> Gemfile echo 'gem "nokogiri", "~> 1.13.10"' >> Gemfile echo 'gem "webrick", "~> 1.7"' >> Gemfile这里指定了三个 gem 的精确版本范围:
jekyll "~> 4.3"表示安装 4.3.x 系列的最新版(如 4.3.3),但不升级到 4.4.x,避免大版本不兼容。nokogiri "~> 1.13.10"是针对 Make 4.1 的兼容性锁定,1.13.10 是 1.13.x 系列的最后一个稳定版。webrick "~> 1.7"是因为 Ubuntu 18.04 的 Ruby 2.5.1 自带webrick1.4.x,但新版 Jekyll 要求webrick>= 1.6.0,显式声明可避免 Bundler 选择过旧版本。
保存Gemfile后,执行:
bundle install --path vendor/bundle--path vendor/bundle参数至关重要。它告诉 Bundler:所有 gem 都安装到当前目录下的vendor/bundle子目录中,而不是全局的/var/lib/gems/。这实现了完美的环境隔离。
3.3 静态站点骨架生成:用 bundle exec 替代全局 jekyll
现在,我们有了 Bundler 管理的依赖,但还没有任何网页文件。传统jekyll new会自动生成_posts/、_layouts/等目录,我们可以用 Bundler 的方式“借力”完成:
bundle exec jekyll _new . --forcebundle exec是 Bundler 的核心命令,它会启动一个子 shell,在其中将vendor/bundle的 bin 目录加入PATH,确保所有jekyll命令都使用我们刚刚安装的、版本受控的 gem。jekyll _new .是 Jekyll 的内部命令,_new表示调用new子命令,.表示在当前目录创建,--force强制覆盖(因为当前目录已存在Gemfile)。
执行后,你会看到熟悉的 Jekyll 骨架:
. ├── _config.yml ├── _posts/ ├── _site/ ├── about.md ├── index.md └── Gemfile此时,项目已经具备了运行一切的基础。但别急着serve,我们先做一次彻底的依赖检查:
bundle check如果输出The Gemfile's dependencies are satisfied,说明一切就绪。如果报错,比如The following gems are missing,那就说明Gemfile中声明的某个 gem 没有成功安装,需要根据错误信息回溯到bundle install步骤排查。
3.4 启动开发服务器:从 webrick 到 puma 的平滑过渡
最后一步,启动服务器:
bundle exec jekyll serve --host 0.0.0.0 --port 4000 --livereload参数详解:
--host 0.0.0.0:让服务器监听所有网络接口,而不仅是127.0.0.1。这样,你可以在同一局域网内的手机或平板上,通过http://<你的UbuntuIP>:4000访问预览,方便多端测试。--port 4000:显式指定端口,避免与其他服务冲突。--livereload:启用热重载。当你修改.md或.html文件并保存时,浏览器会自动刷新,无需手动按 F5。
如果你追求更快的响应,只需在Gemfile中添加gem "puma",然后重启服务:
echo 'gem "puma"' >> Gemfile bundle install --path vendor/bundle bundle exec jekyll serve --host 0.0.0.0 --port 4000 --livereload --server puma你会立刻感受到区别:页面刷新几乎无延迟,即使在编辑一个包含 50 篇文章的_posts/目录时,jekyll serve的重建时间也稳定在 1.2 秒左右,远优于webrick的 2.5 秒。
4. 离线环境救急指南:当没有网络时,如何让 Jekyll 继续工作
在真实的工程场景中,“离线”不是假设,而是常态。可能是你在飞机上修改博客,可能是客户内网完全断网,也可能是公司 IT 策略禁止访问外部 RubyGems 源。Ubuntu 18.04 的build-essential和ruby-dev可以通过离线 deb 包安装,但gem的世界是另一套规则。幸运的是,Bundler 为离线部署提供了原生支持,其核心思想是:把所有依赖“打包带走”。
4.1 在有网机器上制作离线 gem 包仓库
找一台网络通畅、同样运行 Ubuntu 18.04 的机器(可以是你的开发机),进入你的 Jekyll 项目目录,执行:
bundle package --all --all-platforms这个命令会做三件事:
- 扫描
Gemfile.lock,找出项目所需的所有 gem 及其确切版本(包括传递依赖)。 - 从 RubyGems.org 下载这些 gem 的
.gem文件(二进制包),存放到项目根目录下的vendor/cache/文件夹中。 - 修改
Gemfile,在顶部添加一行source "https://rubygems.org",并确保bundle package生成的缓存路径被 Bundler 识别。
执行完毕后,vendor/cache/目录下会有几十个.gem文件,例如jekyll-4.3.3.gem、nokogiri-1.13.10.gem、liquid-4.0.4.gem等。整个vendor/cache/文件夹就是你的离线 gem 仓库。
4.2 将离线仓库迁移到目标机器
将整个项目文件夹(包括vendor/cache/)通过 U 盘、SCP 或任何离线方式,复制到目标 Ubuntu 18.04 机器上。
在目标机器上,进入项目目录,首先确保系统级依赖已安装(build-essential、ruby-dev等),然后执行:
bundle config set --local path 'vendor/bundle' bundle config set --local without 'development test' bundle install --localbundle config set --local path 'vendor/bundle':设置 Bundler 本地配置,让所有 gem 安装到vendor/bundle,与在线流程一致。bundle config set --local without 'development test':跳过development和test组的 gem(如rspec、rubocop),减小体积,加快安装。bundle install --local:这是离线安装的核心命令。它会强制 Bundler 只从vendor/cache/目录读取.gem文件,完全不联网。
bundle install --local的输出会显示Using <gemname> <version>,而不是Installing <gemname> <version>,这表明它确实在使用本地缓存,而非下载。
4.3 离线环境下的常见陷阱与绕过技巧
离线安装并非万无一失,有两个经典陷阱必须提前规避:
陷阱一:nokogiri的预编译二进制包缺失nokogiri为了加速安装,会为常见平台(Linux x86_64)提供预编译的.so文件。但bundle package下载的是源码包(.gem),离线安装时仍需make编译。如果目标机器的make或gcc版本与源码包期望的不一致,就会失败。
绕过技巧:在有网机器上,用--platform参数强制下载预编译包
# 在有网机器上,执行 bundle package --all --all-platforms --platform ruby # 然后,手动下载 nokogiri 的预编译包 wget https://github.com/sparklemotion/nokogiri/releases/download/v1.13.10/nokogiri-1.13.10-x86_64-linux.gem -O vendor/cache/nokogiri-1.13.10-x86_64-linux.gemnokogiri-1.13.10-x86_64-linux.gem是专为 Linux x86_64 编译好的包,它不包含.c源码,只有.so文件,离线安装时直接解压即可,完全跳过make步骤。
陷阱二:ffigem 的系统库依赖ffi(Foreign Function Interface)用于 Ruby 调用 C 函数,它依赖系统的libffi.so库。Ubuntu 18.04 默认安装了libffi6,但ffigem 可能期望libffi7。离线安装时,ffi会报错libffi.so.7: cannot open shared object file。
绕过技巧:在有网机器上,用--without参数排除 ffi,改用纯 Ruby 替代
# 在有网机器上,Gemfile 中添加 gem "ffi", require: false # 然后,在项目根目录创建一个空的 lib/ffi.rb 文件 touch lib/ffi.rb # 这样,bundle install 会跳过 ffi,而 Jekyll 的核心功能并不强依赖 ffi这两个技巧,是我为一家金融客户的内网知识库系统定制 Jekyll 部署方案时总结出来的。他们要求所有生产环境服务器绝对离线,连ping外网都不允许。通过预编译包 + 空桩文件的组合,我们成功让 Jekyll 在零网络连接的 Ubuntu 18.04 上稳定运行了三年,从未因依赖问题宕机。
5. 故障排查全景图:从报错日志到根因定位的完整思维链
在 Ubuntu 18.04 上搭建 Jekyll,最大的挑战不是“怎么做”,而是“为什么失败”。终端里那一长串红色文字,90% 都是干扰项,真正的线索往往藏在第 3 行或第 17 行。下面这张排查全景图,是我过去五年处理上百个同类问题后提炼出的决策树。它不教你背命令,而是训练你像调试器一样思考。
5.1 第一层过滤:区分“系统错误”与“Ruby 错误”
当你看到报错,第一反应不是 Google,而是看错误信息的前缀:
- 如果以
E:、W:、dpkg:、apt:开头,例如E: Unable to locate package build-essential,这是APT 层错误,问题出在系统包管理器。解决方案只有两个:sudo apt update或检查/etc/apt/sources.list是否指向了正确的源(Ubuntu 18.04 应为bionic)。 - 如果以
ERROR:、Failed to build、cannot load such file开头,例如ERROR: Failed to build gem native extension,这是Ruby 层错误,问题出在 gem 编译或加载环节。这时才轮到 Bundler、make、ruby-dev等概念登场。
提示:一个快速判断法是执行
which make和which gcc。如果它们返回/usr/bin/make和/usr/bin/gcc,说明系统工具链正常,错误大概率在 Ruby 层;如果返回空,说明build-essential根本没装,这就是 APT 层问题。
5.2 第二层定位:make错误的三种典型模式
make报错是 Ruby gem 编译失败的最常见表象,但它背后有三种截然不同的根因:
| 错误模式 | 典型日志片段 | 根因分析 | 解决方案 |
|---|---|---|---|
| 头文件缺失 | ruby.h: No such file or directory | ruby-dev未安装,gcc找不到 Ruby 的 API 定义 | sudo apt install ruby-dev |
| Make 版本不兼容 | Makefile:123: *** missing separator. Stop.或$(file >...)语法错误 | make4.1 不支持 4.2+ 的新语法,nokogiri等 gem 的Makefile使用了新特性 | 在Gemfile中锁定nokogiri "~> 1.13.10" |
| 链接库找不到 | cannot find -lssl或cannot find -lz | libssl-dev或zlib1g-dev未安装,gcc链接时找不到动态库 | sudo apt install libssl-dev zlib1g-dev |
记住这个表格。下次bundle install卡住,直接 Ctrl+C 中断,然后看最后一屏的报错,90% 的情况都能对号入座。
5.3 第三层深挖:用strace捕捉系统调用真相
当错误日志语焉不详,比如make: *** [Makefile:45: all] Error 2,没有任何具体线索时,就需要祭出终极武器:strace。
strace是 Linux 的系统调用追踪器,它能告诉你一个程序在执行时,到底打开了哪些文件、调用了哪些系统函数、收到了什么错误码。
以nokogiri编译失败为例,先进入它的临时构建目录(通常在/tmp/下,名字类似nokogiri-20231015-12345-abcde),然后执行:
strace -e trace=openat,open,write,close make 2>&1 | grep -E "(ruby.h|ssl|zlib)"这条命令的意思是:追踪make进程的openat、open、write、close四个系统调用,将所有输出重定向到标准错误(2>&1),再用grep过滤出包含ruby.h、ssl、zlib的行。
如果输出中出现:
openat(AT_FDCWD, "/usr/include/ruby-2.5.0/ruby.h", O_RDONLY) = -1 ENOENT (No such file or directory)那么ruby-dev缺失就是铁证。
如果输出是:
openat(AT_FDCWD, "/usr/lib/x86_64-linux-gnu/libssl.so", O_RDONLY) = -1 ENOENT (No such file or directory)那就是libssl-dev的问题。
strace的强大之处在于,它不依赖错误日志的“人话描述”,而是直接呈现操作系统层面的“事实”。它是我处理那些“玄学报错”的最后防线,也是我向客户证明“问题不在我们的代码,而在系统配置”的最有力证据。
5.4 第四层验证:用bundle show确认依赖路径
当bundle install显示成功,但bundle exec jekyll serve仍报cannot load such file -- jekyll时,问题往往出在 Bundler 的路径解析上。
执行:
bundle show jekyll它会输出类似/home/user/my-jekyll-site/vendor/bundle/ruby/2.5.0/gems/jekyll-4.3.3的路径。如果这个路径不存在,或者路径中的2.5.0与你的 Ruby 版本不符(比如你升级了 Ruby,但vendor/bundle还是旧的),那就说明 Bundler 没有正确安装 gem。
此时,最干净的解决方案是:
rm -rf vendor/bundle bundle install --path vendor/bundle删除整个vendor/bundle目录,相当于给 Bundler 一个“全新开始”的机会。这比试图修复损坏的缓存要快得多,也更可靠。
6. 长期维护心得:让 Ubuntu 18.04 的 Jekyll 环境活过五年
Ubuntu 18.04 的官方支持已于 2023 年 4 月结束,但它的 ESM(Extended Security Maintenance)服务将持续到 2028 年。这意味着,只要支付订阅费用,你依然能获得关键安全补丁。对于一个静态博客生成器来说,这已经足够。但“足够”不等于“无忧”。在长达五年的维护周期里,我总结了三条血泪经验,它们无关技术细节,而是关于如何与一个“半退休”的系统和平共处。
6.1 经验一:永远不要sudo gem update --system
gem update --system会升级 RubyGems 本身。Ubuntu 18.04 的 Ruby 2.5.1 与最新的 RubyGems(如 3.4+)存在兼容性问题,升级后gem install可能直接崩溃,报错undefined method 'spec' for nil:NilClass。这不是 bug,而是 RubyGems 的 API 在大版本迭代中发生了不兼容变更。
我的做法是:将 RubyGems 版本锁定在 3.0.3.1,这是最后一个完美兼容 Ruby 2.5.x 的稳定版