在 coding.net 上部署 Jekyll 博客

December 2014 · 3 minute read

自从 coding 推出 PaaS 演示平台以及开放自定义域名之后,很多人开始尝试在 coding 上部署自己的博客,其中就有 jekyll,coding 上就有官方推荐的 jekyll-demo。但是因为这个 Demo 的 README 文档中只是简单介绍配置步骤而已,没有详细介绍原理以及灵活配置的地方,我在参照着迁移 jekyll 博客的过程中也遇到一些问题。现在写下文章,希望能够把原理理清楚。

声明:这篇文章主要是对原来的 Demo 的几个主要思路做一个补充说明,而并非 coding 演示平台使用操作的详细教程,所以在有些细节上不一定覆盖到,建议最终的部署代码需要以官方推荐的 repo 里的代码为主。

基本原理

因为 Coding 提供的演示平台是通用的 PaaS 平台,并非类似 Github 或者 Gitcafe 的 Pages 服务,所以 jekyll 部署到演示平台需要解决三个问题:

1. 运行问题,blog 需要以常规 Web 程序的方式运行;
2. 启动脚本,部署完成后自动启动服务器;
3. 自动更新,blog 内容更新 push 后能够自动生成新的页面。

第一个问题我们可以通过 rack-jekyll 解决;第二个问题通过 Coding 约定的 Procfile 文件解决;第三个问题我们通过 Coding 的 Webhook 结合脚本解决。

1. 将 Jekyll 博客变为一个在线运行的 Rack 程序

Jekyll 原本是一个用于生成静态博客站点的框架,但是为了能够在 coding 演示平台上直接运行 Jekyll 博客,我们需要一个能够在 Unicorn 服务器上运行 Jekyll 的方法。通过原来 coding 提供的 Demo,找到了一个叫 rack-jekyll 的工具。

rack-jekyll 主要的功能如其介绍: > Transform your Jekyll app into Rack application!

就是将 Jekyll 作为 Rack 程序运行。

首先,为了能够使用 rack-jekyll 以及 unicorn,我们在 Gemfile 文件(如果没有则直接新建即可)中加入:

gem "rack-jekyll"
gem "unicorn"

这两行,然后执行 bundle install 这样,我们的项目中就成功引入 rack-jekyll 以及 unicorn 了。

其次,因为 unicorn 默认会从项目根目录下的 config.ru 文件启动,再结合 rack-jekyll 的使用说明 ,我们在 jekyll 项目根目录下要创建一个包含以下内容的文件,并且名字就是 config.ru

# config.ru
require "rack/jekyll"

run Rack::Jekyll.new

到此,可以在命令行中 cd 到当前项目根目录,执行 jekyll build 生成站点,然后再执行 unicorn 从默认配置启动服务器,成功启动后,在浏览器中访问“ http://127.0.0.1:8080 ”就可以看到博客了。

2. 添加用于 Coding 演示平台的启动脚本

上面第一步只是解决了 Jekyll 能够以 Rack 方式运行的问题而已,但是为了部署到 coding 后,项目能够正常启动,我们还需要加入启动命令。

按照 coding 在关于 Ruby 部分的演示平台文档 中的介绍得知,coding 会查找项目根目录下的 Procfile 文件,并将里边的内容作为启动命令,当此文件不存在时,则将默认使用一下启动命令:

web: bundle exec rackup config.ru -p $PORT

按照默认启动命令的格式,我们也可以写出以下 Procfile 文件,用于部署后从 unicorn 启动项目:

web: bundle exec unicorn -p $PORT -c ./unicorn.rb

完成前面两步之后,将代码 push 到 coding 上,再从演示平台一键部署的话,就应该可以成功启动 unicorn 服务器,并且能够访问你的 jekyll 博客了。但是,如果有了新文章呢?怎么自动在站点改动后重新生成站点?

3. 使用 Webhook 在 push 后自动重新生成站点内容

coding 为用户提供了 webhook 功能,方便用户在 push 代码改动后自动 POST 请求你指定的 Web URL,你可以利用这个 URL 在程序后台完成程序的自动部署等操作。更多的介绍跟使用方法请参考 “WebHook 的内容是什么?” 以及 “WebHook 是什么?我该如何使用?”

为了增加新的入口以接收 coding 的 Webhook 通知,我们可以在 config.ru 中添加新的路由,并且添加响应的处理脚本,这部分的内容我先直接拷贝官方推荐的 jekyll demo 的代码 后再做必要的解读:

# config.ru
require "bundler/setup"
Bundler.require(:default)

WEBHOOK_TOKEN = ENV['WEBHOOK_TOKEN']

app = Proc.new do |env|
  request = Rack::Request.new(env)
  response = Rack::Response.new
  path_info = request.path_info

  if request.content_type =~ /application\/json/
    params = JSON.parse(request.body.read)
  else
    params = request.params
  end

  if request.post? && params['token'] == WEBHOOK_TOKEN
    repo_url = params['repository']['url'] rescue nil
    if repo_url
      archive_url = "#{repo_url}/archive/master"
      puts "--> updating to #{params['ref']}.."
      puts `jekyll build`
      `rm -rf $HOME/_posts; curl -s -L -o $TMPDIR/archive.zip #{archive_url}; unzip -qo -d $HOME $TMPDIR/archive.zip; cd $HOME; jekyll build`
      puts "--> done."
    else
      STDERR.puts "--> error: no url field found in params: #{params}"
    end

    ['200', { 'Conetent-Type' => 'application/json;charset=utf-8' }, ['ok']]
  else
    ['403', { 'Conetent-Type' => 'application/json;charset=utf-8' }, [{ error: 'webhook token mismatch!' }.to_json]]
  end
end

jekyll = Rack::Jekyll.new(auto: true)

run Rack::URLMap.new('/' => jekyll, '/_' => app)

首先,程序在启动时,指定了两个路由入口分别指向不同的后台程序,其中 '/' 路径指向了我们的 jekyll 程序,这个跟原来的配置目的一致;而 '/_' 路径指向了 app 这个程序。

所以,当有外部向服务器发送了一个指向 “/” 路径(比如“ http://test.codingapp.com/ ”)的请求时,服务器在内部启动了 app 的脚本。(注意,如果你希望使用别的路径名来配置 webhook 的入口,只要将下划线改成你需要的路径即可,比如: “http://test.codingapp.com/deploy")。

app 脚本首先通过请求的 Content-Type 头信息判断请求格式,并据此从请求中提取请求参数赋给 params 变量;接着脚本验证请求的合法性,要求请求必须是 POST 方式,并且参数中的 token 参数的值必须与我们在 coding 后台中配置的 token 一致。

最后,在确认请求的合法性后,脚本先清空了当前部署的项目,然后下载解压指定分支的最新代码,并且进入项目根目录($HOME环境变量)重新执行了 jekyll build 命令以重新生成静态站点,见代码:

`rm -rf $HOME/_posts; curl -s -L -o $TMPDIR/archive.zip #{archive_url}; unzip -qo -d $HOME $TMPDIR/archive.zip; cd $HOME; jekyll build`

其中值得一提的是,archive_url是在前面代码中拼接而来的链接:

archive_url = "#{repo_url}/archive/master"

请注意其中硬编码的部分 "archive/master",其中的 master 指定了是 master 分支上的代码压缩包的路径,所以假如你需要从 master 分支外的分支部署代码,请务必记得将 master 改为对应的分支名,比如我的部署分支是 coding-pages,那我这里的代码就应该改为:

archive_url = "#{repo_url}/archive/coding-pages"

完成 webhook 处理脚本后,需要重新 push 代码并且重新在演示平台部署一次,以使 config.ru 文件里的代码生效。至于如何配置 webhook ,直接参照 coding 的官方文档即可。

总结

以上的三点主要是对在 coding 上部署 jekyll 博客的关键思路的说明,通过这三点,相信你再去看原来的 README 的时候,应该就能很快理解为什么需要配置 WEBHOOK_TOKEN 环境变量以及为什么要配置 webhook 的 URL 为类似 “http://host/_" 这么奇怪的链接了吧?除此之外,你也可以根据你的需要将脚本中的代码分支从 master 改为你所需要的目标分支了。
其实用 unicorn 运行 jekyll 项目的原理还是非常简单的,知道了这些之后,将你的已有 jekyll 项目直接迁移到 coding 甚至是其他 PaaS 平台上就不是件麻烦的事了。

其他联想

  1. Octopress 博客是在 jekyll 的基础上封装而来的更高级也更方便的静态站点框架,所以按照上面的原理,将已有的 octopress 项目部署到 coding 平台上,应该也不是件难事。
  2. Octopress 本身支持另外一种部署方式,就是本地生成静态站点之后,直接执行 rake deploy 将生成后的静态站点 push 到指定的远程 repo 或者指定的分支上,从这个角度考虑,其实也可以为 jekyll 实现类似的脚本,结合 coding 演示平台的 静态站点部署 ,就可以直接部署 jekyll 博客了,这种方式就省去了 unicorn 服务器等的配置了,也不需要再使用 webhook 重新生成站点了,而且纯静态站点的方案的最大优点就是,特别节约内存。这种方案只是构想,但是值得一试。如果哪位朋友尝试成功了,请记得在评论里回复一下。