Heroku上のRackアプリで動かしていたWebサイトをGitHub Pagesに移行する
背景
最近Herokuの料金プランが変更され、無料枠が1日最大18時間までになりました。Increments社では数年前から幾つかのWebサイトをHerokuを使ってホスティングしていましたが、そのうちの幾つかは静的ファイルのホスティングだけで十分実現できる内容だったので、これらをGitHub Pagesに移行させました。料金変更だけが理由ではないのですが、移行する労力を割くに値する価値があると十分に判断できる材料が揃ったので、移行させた、という背景です。今回は折角なので、HerokuからGitHub Pagesに移行させたサービスの1つである kobito.qiita.com を例に挙げて、移行にどういう作業が必要になったかについてまとめておこうと思います。ちなみにソースコードは https://github.com/increments/kobito.qiita.com で公開されています。
静的ファイルの生成
生成用ツールの選定
GitHub Pagesに移行するためには、これまでHerokuで提供していたものと同じように振る舞うような、何らかのHTMLやCSSファイルを用意する必要があります。移行の際には大きく分けて二通りの方法を考え、一つは Middleman や Jekyll、nanoc などの静的サイト生成用のフレームワークを使うようにアプリのコードを書き換える方法、もう一つは Sitespec などを使って既存のアプリのコードを極力変更せずに静的ファイルを生成する方法でした。
Sitespecの選択理由
既存の開発手順をそのままに保ちたいという理由から、今回は後者のSitespecを利用する方法を選択しました。編集に携わる開発者達が既にSinatraの使い方を覚えていたので、新たなフレームワークの学習を強いることに抵抗があったというのも理由の一つです。Sitespecについては、SitespecでRackアプリから静的サイトをつくる - Qiita という記事も書いていて、導入方法などについてはこの記事により詳しく書いています。
ファイル配信周りの挙動
https://pages.github.com/ に分かりやすい説明が書かれていて、もはや説明するまでもないという状況だと嬉しいんですが、一応細かい点について説明しておきます。
HTMLファイル以外
GitHub Pagesにファイルを配置したときの挙動について説明します。一般的には、HTML、CSS、JavaScript、画像などのファイルを配置することになると思います。HTML以外のファイルについてはパターンが分かりやすく、レポジトリ直下に foo.png
を配置すれば /foo.png
というパスでアクセスできます。このときレスポンスのContent-Typeには、それぞれのファイルの拡張子に応じた値が含まれます。例えば、foo.png
であれば image/png
、style.css
であれば text/css
になります。拡張子が付いていない場合や対応していない拡張子の場合は application/octet-stream
になるので、一般的なブラウザでこれにアクセスすると対象ファイルをダウンロードするような挙動になると思います。
HTMLファイル
HTMLの場合はパターンが少し複雑で、レポジトリ直下に foo.html
があれば /foo.html
というパスでアクセスできるというのは他の種類のファイルと変わりませんが、/foo
にアクセスしたときは、レポジトリに foo
ディレクトリが存在すれば foo/index.html
を返そうとし、ディレクトリが無ければ foo.html
を返そうとします。存在しなければ404が返るほか、foo
ディレクトリと foo.html
が存在して foo/index.html
が存在しない場合は404が返ります。
kobito.qiita.com の場合
kobito.qiita.com の場合、例えばこれまで GET /en
と GET /en/features
のようなエンドポイントを利用していたため、この場合は en.html
を生成するのではなく en/index.html
を生成しなければならないということに注意する必要がありました。移行にあたってはRackアプリ側には変更を加えない予定でしたが、唯一この対応のためにアプリ側にも変更を加える必要がありました。とはいえ、GET /en
でも GET /en/index.html
でもどちらも同じHTMLが返るという挙動はHerokuで動いている状態でも実現可能なため、先に両方で動くコードに変更して、その上で移行する、という手順を踏めました。
レポジトリ名
命名規則
GitHub Pagesでは、レポジトリ名が <owner>/<owner>.github.io
というパターンに即しているかどうかによって、少し挙動が変わります。<owner>
の部分は、r7kamuraやincrementsのような文字列が入るので適宜読み替えてください。このパターンにあてはまるもののことをUser Pages site、あるいはOrganization Pages siteと呼ぶようです (以降ユーザページと呼びます)。このパターンにあてはまらないものは、Project Pages siteと呼ぶようです (以降プロジェクトページと呼びます)。increments/kobito.qiita.com の場合はプロジェクトページですね。
使われるブランチ
ユーザページの場合、master
ブランチがGitHub Pages用のブランチとして認識され、このブランチにPushするとページが更新されます。一方、プロジェクトページの場合は、gh-pages
ブランチが使われます。increments/kobito.qiita.com の場合はプロジェクトページなので、master
ブランチにアプリのソースコードを置いて、gh-pages
に生成された静的ファイルを置くようにしています。ユーザページの例も出しておくと、r7kamura/r7kamura.com の場合は source
ブランチにアプリのソースコード、master
に生成された静的ファイルを置いています。このときレポジトリのデフォルトブランチを source
ブランチに設定しておくと、レポジトリにアクセスしたとき最初にソースコード側を表示できるので、ちょっと便利です。
用意されるURL
increments/kobito.qiita.com の場合は http://increments.github.io/kobito.qiita.com が、r7kamura/r7kamura.com の場合は http://r7kamura.com が使われます。この違いは意外と厄介で、プロジェクトページの場合はサブディレクトリを利用するような形式になるので、静的ファイルに書くURLもそれに応じた相対パス、あるいは絶対パスに変更する必要があります。しかし絶対パスを利用していると、後述するようにプロジェクトページにカスタムドメインを設定してアクセスした場合は、パスのサブディレクトリ部分は不要ということになります。increments/kobito.qiita.com の場合は、試しにまずユーザページとして公開してみて、うまく動作することを確認した後にプロジェクトページに変更しました。
ドメインの設定
GitHub側
最後にドメイン周りの設定についてです。GitHub側では、gh-pages
などのGitHub Pages用のブランチに CNAME
というファイルがあると、そのファイル内に書かれたドメイン名を認識してくれます。この辺りの細かい挙動は About custom domains for GitHub Pages sites - UserDocumentation を見たほうが良いでしょう。https://github.com/increments/kobito.qiita.com/blob/gh-pages/CNAME では kobito.qiita.com
というドメイン名が記載されています。なお、先にテスト用に別のレポジトリでCNAMEファイルを置いて設定を済ませていると、例え新しいレポジトリにCNAMEを置いてDNSサービス側を更新しても、古い方のレポジトリのデータを表示し続けてしまう可能性があるので、重複しないように気を付けましょう。
DNSサービス側
qiita.com ドメインの場合はRoute53で管理されていたので、管理用のコンソールから kobito.qiita.com 用のCNAMEのレコードを increments.github.io
に設定しました。既に公開されているものの移行だったので、試験用のCNAMEのレコードをつくってそちらで疎通確認を行ったあとで実行しています。テストで試すときは、レコードのTTLを短めに設定しておくと、間違った値を与えたときに幾らか修正しやすくなると思います。
おわり
HerokuからGitHub Pagesへの移行時に行った作業は以上になります。結構長めに書きましたが、実作業時間としては丁寧にやって3時間ぐらいで移行できたので、わりと簡単に移行できるほうだと思います。本記事とはあまり関係ありませんが、最近GitHubのエンジニアブログが開設されましたが、その中の Rearchitecting GitHub Pages - GitHub Engineering という記事でGitHub Pagesがどう設計されているかについて触れられているので、興味があれば読んでみると面白いかもしれません。