You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Heroku部署Sinatra应用持久化C依赖库及修复H10报错问题

Heroku部署Ruby FFI调用C库应用H10错误及C库持久化方案

问题背景

  • 部署通过Ruby FFI调用C库的Sinatra Web应用时触发H10崩溃错误,应用本地运行完全正常
  • 依赖的Swiss Ephemeris C库需要在部署机完成编译,本地环境通过Rakefile执行编译操作,但Heroku采用临时文件系统,运行阶段生成的文件会在dyno重启/销毁后丢失
  • 先后尝试自定义Ruby版buildpack、自定义Bash版buildpack、release阶段执行rake编译三种方案,编译阶段日志显示已成功生成目标文件,但应用启动、执行heroku run bash时均找不到编译生成的libswe.so文件,报错信息如下:
/app/vendor/bundle/ruby/3.0.0/gems/ffi-1.15.5/lib/ffi/library.rb:145:in `block in ffi_lib': Could not open library '/app/src/libswe.so': /app/src/libswe.so: cannot open shared object file: No such file or directory (LoadError)

已尝试的失败方案

1. 自定义Ruby版本buildpack

核心实现代码:

#!/usr/bin/env ruby

# This script compiles an application so it can run on Heroku.
# It will install the application's specified version of Ruby, it's dependencies
# and certain framework specific requirements (such as calling `rake assets:precompile`
# for rails apps).
$stdout.sync = true

$:.unshift File.expand_path("../../../lib", __FILE__)
require "language_pack"
require "language_pack/shell_helpers"

begin

   # 自定义添加的C库编译逻辑
  `mkdir /app/src`
  `cd /app && curl -s https://www.astro.com/ftp/swisseph/swe_unix_src_2.10.02.tar.gz | tar xzvf -`
  `cd '/app/src' && make libswe.so`
   # 自定义逻辑结束

  LanguagePack::ShellHelpers.initialize_env(ARGV[2])
  if pack = LanguagePack.detect(ARGV[0], ARGV[1])
    pack.topic("Compiling #{pack.name}")
    pack.log("compile") do
      pack.compile
    end
  end
rescue Exception => e
  LanguagePack::ShellHelpers.display_error_and_exit(e)
end

2. 自定义Bash版本buildpack

核心实现代码:

#!/usr/bin/env bash
# The actual compilation code lives in `bin/support/ruby_compile`. This file instead
# bootstraps the ruby needed and then executes `bin/support/ruby_compile`

BUILD_DIR=$1
CACHE_DIR=$2
ENV_DIR=$3
BIN_DIR=$(cd $(dirname $0); pwd)
BUILDPACK_DIR=$(dirname $BIN_DIR)

# 自定义添加的C库编译逻辑
mkdir /app/src
cd /app && curl -s https://www.astro.com/ftp/swisseph/swe_unix_src_2.10.02.tar.gz | tar xzvf -
cd '/app/src' && make libswe.so
# 自定义逻辑结束

source "$BIN_DIR/support/bash_functions.sh"
heroku_buildpack_ruby_install_ruby "$BIN_DIR" "$BUILDPACK_DIR"

if detect_needs_java "$BUILD_DIR"; then
  cat <<EOM

       ## Warning: Your app needs java

       The Ruby buildpack determined your app needs java installed
       we recommend you add the jvm buildpack to your application:

         $ heroku buildpacks:add heroku/jvm --index=1

-----> Installing Java

EOM

  compile_buildpack_v2 "$BUILD_DIR" "$CACHE_DIR" "$ENV_DIR" "https://buildpack-registry.s3.us-east-1.amazonaws.com/buildpacks/heroku/jvm.tgz" "heroku/jvm"
fi

$heroku_buildpack_ruby_dir/bin/ruby $BIN_DIR/support/ruby_compile $@

3. Release阶段执行编译任务

Procfile配置:

release: bundle exec rake
web: bundle exec thin start -R config.ru -e $RACK_ENV -p ${PORT:-5000} 

对应Rakefile配置:

#require "bundler/gem_tasks"

task default: [:clean, :c_build, :get_ephe]

task :clean do
  `rm ./src/libswe.so`
  `rm -rf ephe`
end

task :c_build do
  `wget https://www.astro.com/ftp/swisseph/swe_unix_src_2.10.02.tar.gz`
  `tar xvf swe_unix_src_2.10.02.tar.gz`
  `rm swe_unix_src_2.10.02.tar.gz`
  `cd src && make libswe.so && echo "Compiled Library"` 
end

task :get_ephe do
  `mkdir ephe`
  `wget -P ephe https://www.astro.com/ftp/swisseph/ephe/seas_12.se1`
  `wget -P ephe https://www.astro.com/ftp/swisseph/ephe/seas_18.se1`
  `wget -P ephe https://www.astro.com/ftp/swisseph/ephe/sefstars.txt`
  `wget -P ephe https://www.astro.com/ftp/swisseph/ephe/semo_12.se1`
  `wget -P ephe https://www.astro.com/ftp/swisseph/ephe/semo_18.se1`
  `wget -P ephe https://www.astro.com/ftp/swisseph/ephe/sepl_12.se1`
  `wget -P ephe https://www.astro.com/ftp/swisseph/ephe/sepl_18.se1`
end

失败根本原因

  • 自定义buildpack时直接操作/app目录属于路径使用错误:buildpack编译阶段传入的第一个参数$BUILD_DIR才是最终会被打包进运行时slug的目录,编译阶段临时挂载的/app目录内容不会被带入运行时环境,所以编译完成的文件在应用启动后会消失
  • Release阶段的命令运行在一次性临时dyno上,任务执行完后该dyno会被直接销毁,生成的所有文件都不会留存,自然无法被Web dyno读取
  • Heroku不支持从dyno运行时侧直接推送文件到应用关联的Git仓库

可行解决方案

方案1:修正自定义buildpack逻辑(最优)

  • 所有下载、编译操作的输出路径都指向$BUILD_DIR(即buildpack脚本接收的第一个位置参数),不要直接写入根目录下的/app
  • 编译完成后,要么把生成的.so库所在路径加入动态链接库搜索路径,要么在Ruby FFI调用代码里写对运行时/app目录下的库存取路径
  • 可以用buildpack传入的第二个参数$CACHE_DIR缓存下载的源码压缩包,减少每次部署的重复下载量,加快编译速度

方案2:预编译后直接提交到Git仓库

  • 在和Heroku运行时环境一致的Linux x86_64环境下提前编译好libswe.so,连同需要的星历文件一起提交到项目Git仓库
  • 部署时直接引用仓库内的预编译文件即可,不需要在部署阶段执行编译操作

注意:预编译环境必须和Heroku运行时的系统架构、基础依赖版本保持一致,否则会出现库兼容性报错

方案3:通过Apt buildpack安装预编译依赖

如果目标C库有现成的预编译deb安装包,可以搭配Apt buildpack在部署阶段直接通过apt安装系统级依赖,不需要手动执行编译流程

内容的提问来源于stack exchange,提问作者Parker McGowan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 02:36:09