Middleman + S3 + Fastly で作るブログ付きスタティックサイトのススメ

どうもこんにちは、僕です。働クリッカーが大盛況につき、 zeny.io へのアクセスが急増しまして、気がついたら AWS から出てきた Data Transfer の今月の請求額がちょっとヤバそうな感じになっていました。バズるの怖いっすね。

それでも Internal Server Error など出さず、粛々と(トロくても)動き続けていたのは、種も仕掛けもなく、純粋に S3 の上に HTML を配置しただけの簡単設計だったのがよかったのですが、さすがに .zip とか .app とか配布するとなると、転送量もばかにならないし、なにより遅い。

ということで、 Fastly を導入してもうちょい早くしたよ☆というのが今日のお題です。やったね!

Middleman

middleman

Middleman は静的サイトのジェネレータです。普通に HTML を書くのはだるいけど、Rails とか Sinatra とか引っ張りだすほどじゃない……みたいな時に僕はよく使います。似たようなツールに Jekyll や Nanoc などもあるんですが、今回はそっちには触れません。

とはいえ、僕も Middleman をガッチガチに使いこなしている訳ではないのであんまり言うこともないんですが、さくっと config.rb を晒してしまうと、

Time.zone = 'Tokyo'

set :slim, pretty: false, format: :html
set :markdown_engine, :redcarpet
set :markdown, fenced_code_blocks: true, smartypants: true, no_intra_emphasis: true, tables: true

activate :syntax
activate :directory_indexes
set :index_file, 'index.html'

# 1)
activate :blog do |blog|
  blog.prefix = 'blog'
  blog.sources = '{year}-{month}-{day}-{title}.html'
  blog.permalink = '{year}/{month}/{day}/{title}.html'
  blog.layout = 'blog'
  # 2)
  blog.summary_generator = Proc.new do |article, body, length, ellipsis|
    text = Nokogiri::HTML(body).text

    article.default_summary_generator("<p>#{text}</p>", length, ellipsis)
  end
end

# 3)
activate :s3_sync do |s3_sync|
  s3_sync.region                     = 'ap-northeast-1'
  s3_sync.delete                     = false
  s3_sync.after_build                = false
  s3_sync.prefer_gzip                = true
  s3_sync.path_style                 = true
  s3_sync.reduced_redundancy_storage = false
  s3_sync.acl                        = 'public-read'
  s3_sync.encryption                 = false
  s3_sync.version_bucket             = false
  s3_sync.index_document             = 'index.html'
  s3_sync.error_document             = '404.html'
end

default_caching_policy max_age: (60 * 60 * 24 * 365), public: true, must_revalidate: true

page '/*.xml', layout: false
page '/*.json', layout: false
page '/*.txt', layout: false
page '/404.html', directory_index: false


Dir['lib/helpers/**/*.rb'].each do |helper|
  instance_eval(File.read(helper))
end

configure :build do
end

といった具合で、特に何をするでもなく、素直に blogs3_sync を使ってます。という具合です。なお、参考までに Gemfile は以下。

source 'https://rubygems.org'

gem 'html-proofer'
gem 'middleman', '>= 4.0.0'
gem 'middleman-blog'
gem 'middleman-s3_sync'
gem 'middleman-syntax'
gem 'nokogiri'
gem 'parallel'
gem 'rake'
gem 'redcarpet'
gem 'slim'
gem 'tzinfo-data', platforms: [:mswin, :mingw, :jruby]
gem 'wdm', '~> 0.1.0', platforms: [:mswin, :mingw]

大事な所

1) activate :blog

middleman 標準のブログ機能を使ってます。 prefix や permalink の設定にるいては特に何か特筆すべき場所はありません。が、単純にこれだけで済むのはありがたい……

公式のドキュメントが充実してるので、思い立ったが吉日レベルで始めることができました。実際問題、ちゃんとサイト運用していくぞーとなったときに手間がかかる方法は絶対いやだったので、単純な .md の集合でサイトを管理できたのには大分助かっています。

HTML にする際のテンプレートとかも、それこそどんなエンジンでもカスタマイズ出来はするものの、独自の文法とかに対応するのが面倒でいやだったので、 Rails でよく使う Slim が使えたのもありがたい。

Middleman は良く出来ているので素直に使う分にひっかかる所はそんなにありません。もっと高度なことがしたくなったらまぁ、その時はツールを変えるか、もしくは Middleman に手を入れていくことになるでしょうが、手を入れるとしてもこれくらいの規模のシステムならなんとかなるかな、という所です。

2) summary_generator

ここが多分一番手が込んでる所ですね。 Middleman の blog では最初から概要……つまり summary を生成できる仕組みが備わっています。が、個人的に嫌だったのが、その summary の中に画像や h1 などが残ってしまう所です。

単純なテキストだけの summary を吐いて欲しかったので、ここだけは改造しました。とはいえ単純に Nokogiri に突っ込んで text ノードだけ取り出してあとは <p> に閉じ込めるだけです。カンタン!

3) s3_sync

この部分でビルド後に S3 にアップロードしています。と、言ってもここもあんまり頑張ってませんね。単純にアップロードできて便利ですね、ぐらい。別にこの部分はなんでもいいと思いますが、自分で Web サーバーを保守するとかしたくなかったのでこうしました。というぐらい。

その他やってること

  • HTML::Proofer による CI 上でのリンク切れチェック
    • 未公開記事などにリンクしてるページなどがないように
    • 地味にあるのが画像のリンク切れなのでそこも注意してる
    • 外のサイトへのリンクについては気にしてません
  • Parallel が入っている理由
    • HTML::Proofer の動作を並列でやらせるためです
  • Sitemap の ping の自動化
    • CI 上でデプロイ後に Google に ping を打つようにしてます。

とまあこのくらいでしょうか。

Fastly

Fastly

Fastly は最近どこそこで名前が出てるので、知ってる人も多い CDN サービスの様な気がします。

他に CDN がいろいろあるのになんで Fastly なの?と思われるかもしれませんが、単純に

  • 裏っかわが Varnish 製らしく、 vcl でコントロール出来るのが気に入ってる
  • 単純に簡単に使えるから
  • 即時 Purge が最高すぎる

という理由です。特に、即時 Purge は本当に優秀。本当の意味での即時、ではないのですが、 10 秒くらいでキャッシュを Purge 出来るので、ほとんど即時と言って差し支えないでしょう。

実際、 CI 上からのビルド後には Fastly の Purge を叩いて最新のものをとりにいかせるようにしています。

ただ、 Fastly での配信を www ナシのドメインで配信するには DNS サービスの選定からしないといけないので注意が必要ですが、ここに関しては DNSimple を利用してしのいでいます。

記事を書いてから公開するまでの流れ

  • 手元で Markdown を書く
  • GitHub に push する
  • Circle CI でビルドとリンク切れチェックが走る
  • Circle CI から S3 にアップロードされる
  • Google への Sitemap 更新 ping が飛ぶ
  • Fastly のキャッシュが Purge される
  • おわり

という流れです。かんたんだなあ。

完全静的かと言われれば、一応 zeny.io にはお問い合わせフォーム的なのがあるのですが、そこはもう運用コストを鑑みて Google Form に任せています。簡単ですね。

なんか最近似たような構成の話見た気がするな……と思ったら Hashicorp でも同じような構成でサイトをデプロイしているのでした。向こうだと VCL 使ってリダイレクトさせたり、もうちょい Fastly を活用してる感じですね(僕は www.zeny.io から zeny.io へのリダイレクトは S3 に任せてる)。

ref: Serving Static Sites with Fastly, S3, and Middleman

ということで、まぁ安価な値段でかつメンテフリーな程度によそに運用をお任せして静的サイトを運用するなら Middleman + S3 + Fastly はかなりいい感じですよ、という宣伝でした。ここまでの構成をすると大体月に $100 いくかいかないかくらいで運用できると思います。オヤスイ!

(実際、自分で VPS とかの面倒を見ると考えた時、自分の人件費も計算にいれると結構な額になると思います)

興味があったらためしてみるといいですよ。